-
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index d425c5ee..c3d561ca 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -1,41 +1,103 @@
import React from 'react';
+import { TreeMenu } from '../ui/TreeMenu';
+import { TreeNode } from '../../types/tree';
const sidebarStyle: React.CSSProperties = {
- width: 180,
- background: '#eee',
- padding: 20,
+ width: '220px',
+ background: '#fff',
+ padding: '16px 8px',
boxSizing: 'border-box',
height: '100vh',
- position: 'absolute',
- left: 0,
- top: 0,
- zIndex: 10
+ borderRight: '1px solid #e0e0e0',
+ overflowY: 'auto',
};
-const itemStyle: React.CSSProperties = {
- border: '1px solid #999',
- padding: 8,
- marginBottom: 12,
- background: '#fff',
- borderRadius: 4,
- cursor: 'grab',
- textAlign: 'center'
+const titleStyle: React.CSSProperties = {
+ fontSize: '16px',
+ fontWeight: 'bold',
+ marginBottom: '16px',
+ color: '#222',
+ textAlign: 'left',
+ padding: 0,
+ borderBottom: '1px solid #e0e0e0',
};
-const onDragStart = (event: React.DragEvent, nodeType: string) => {
- event.dataTransfer.setData('application/reactflow', nodeType);
- event.dataTransfer.effectAllowed = 'move';
+const onDragStart = (event: React.DragEvent, node: TreeNode) => {
+ if (node.nodeType) {
+ event.dataTransfer.setData('application/reactflow', node.nodeType);
+ event.dataTransfer.effectAllowed = 'move';
+ }
};
+const onNodeClick = (node: TreeNode) => {
+ // No-op for now
+};
+
+const sidebarTreeData: TreeNode[] = [
+ {
+ id: 'uml-diagrams',
+ label: 'UML Diagrams',
+ children: [
+ {
+ id: 'structure-diagrams',
+ label: 'Structure Diagrams',
+ children: [
+ { id: 'class-diagram', label: 'Class Diagram', nodeType: 'class', draggable: true },
+ { id: 'object-diagram', label: 'Object Diagram', nodeType: 'object', draggable: true },
+ { id: 'component-diagram', label: 'Component Diagram', nodeType: 'component', draggable: true },
+ { id: 'composite-structure-diagram', label: 'Composite Structure Diagram', nodeType: 'composite', draggable: true },
+ { id: 'deployment-diagram', label: 'Deployment Diagram', nodeType: 'deployment', draggable: true },
+ { id: 'package-diagram', label: 'Package Diagram', nodeType: 'package', draggable: true },
+ { id: 'profile-diagram', label: 'Profile Diagram', nodeType: 'profile', draggable: true },
+ ],
+ },
+ {
+ id: 'behavior-diagrams',
+ label: 'Behavior Diagrams',
+ children: [
+ { id: 'use-case-diagram', label: 'Use Case Diagram', nodeType: 'use-case', draggable: true },
+ { id: 'activity-diagram', label: 'Activity Diagram', nodeType: 'activity', draggable: true },
+ { id: 'state-machine-diagram', label: 'State Machine Diagram', nodeType: 'state-machine', draggable: true },
+ {
+ id: 'interaction-diagrams',
+ label: 'Interaction Diagrams',
+ children: [
+ { id: 'sequence-diagram', label: 'Sequence Diagram', nodeType: 'sequence', draggable: true },
+ { id: 'communication-diagram', label: 'Communication Diagram', nodeType: 'communication', draggable: true },
+ { id: 'timing-diagram', label: 'Timing Diagram', nodeType: 'timing', draggable: true },
+ { id: 'interaction-overview-diagram', label: 'Interaction Overview Diagram', nodeType: 'interaction-overview', draggable: true },
+ ],
+ },
+ ],
+ },
+ ],
+ },
+ {
+ id: 'java',
+ label: 'JAVA',
+ children: [], // Ready for future children
+ },
+ {
+ id: 'c++',
+ label: 'C++',
+ children: [], // Ready for future children
+ },
+ {
+ id: 'sql',
+ label: 'SQL',
+ children: [], // Ready for future children
+ },
+];
+
export function Sidebar() {
return (
);
}
\ No newline at end of file
diff --git a/src/components/ui/TreeMenu.tsx b/src/components/ui/TreeMenu.tsx
new file mode 100644
index 00000000..b68114ca
--- /dev/null
+++ b/src/components/ui/TreeMenu.tsx
@@ -0,0 +1,93 @@
+import React, { useState } from 'react';
+import { TreeNode, TreeMenuProps } from '../../types/tree';
+
+interface TreeMenuItemProps {
+ node: TreeNode;
+ level: number;
+ onNodeClick?: (node: TreeNode) => void;
+ onDragStart?: (event: React.DragEvent, node: TreeNode) => void;
+}
+
+const TreeMenuItem: React.FC
= ({ node, level, onNodeClick, onDragStart }) => {
+ const [isExpanded, setIsExpanded] = useState(false);
+ const hasChildren = node.children && node.children.length > 0;
+
+ const handleToggle = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (hasChildren) {
+ setIsExpanded(!isExpanded);
+ }
+ if (onNodeClick) {
+ onNodeClick(node);
+ }
+ };
+
+ const handleDragStart = (event: React.DragEvent) => {
+ if (node.draggable && onDragStart) {
+ onDragStart(event, node);
+ }
+ };
+
+ const itemStyle: React.CSSProperties = {
+ padding: '4px 0 4px 0',
+ margin: 0,
+ cursor: node.draggable ? 'grab' : 'pointer',
+ background: 'none',
+ border: 'none',
+ fontSize: '14px',
+ color: '#222',
+ userSelect: 'none',
+ display: 'flex',
+ alignItems: 'center',
+ paddingLeft: `${level * 18 + 4}px`,
+ fontWeight: level === 0 ? 600 : (hasChildren ? 600 : 400),
+ };
+
+ return (
+
+
+ {hasChildren && (
+
+ {isExpanded ? '▼' : '▶'}
+
+ )}
+ {!hasChildren && }
+ {node.label}
+
+ {hasChildren && isExpanded && (
+
+ {node.children!.map((child) => (
+
+ ))}
+
+ )}
+
+ );
+};
+
+export const TreeMenu: React.FC = ({ nodes, onNodeClick, onDragStart }) => {
+ return (
+
+ {nodes.map((node) => (
+
+ ))}
+
+ );
+};
\ No newline at end of file
diff --git a/src/setupTests.ts b/src/setupTests.ts
index e56fbed5..0c4f5dac 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -3,4 +3,11 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
-import './__mocks__/resizeObserverMock';
\ No newline at end of file
+
+// Mock ResizeObserver for ReactFlow compatibility
+global.ResizeObserver = jest.fn().mockImplementation(() => ({
+ observe: jest.fn(),
+ unobserve: jest.fn(),
+ disconnect: jest.fn(),
+}));
+
diff --git a/src/styles/global.css b/src/styles/global.css
index ae9aa568..e1db3059 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -11,6 +11,7 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: #f5f5f5;
+ overflow: hidden;
}
code {
@@ -63,6 +64,37 @@ code {
cursor: grabbing;
}
+/* Responsive breakpoints */
+@media (max-width: 768px) {
+ .sidebar-responsive {
+ width: 100% !important;
+ max-width: 280px;
+ }
+
+ .header-responsive {
+ padding: 0 12px !important;
+ }
+
+ .header-responsive h1 {
+ font-size: 16px !important;
+ }
+}
+
+@media (max-width: 480px) {
+ .sidebar-responsive {
+ width: 100% !important;
+ max-width: 100%;
+ }
+
+ .header-responsive {
+ padding: 0 8px !important;
+ }
+
+ .header-responsive h1 {
+ font-size: 14px !important;
+ }
+}
+
/* Scrollbar styles */
::-webkit-scrollbar {
width: 8px;
@@ -70,17 +102,17 @@ code {
}
::-webkit-scrollbar-track {
- background: #f1f1f1;
+ background: rgba(255, 255, 255, 0.1);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
- background: #c1c1c1;
+ background: rgba(255, 255, 255, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
- background: #a8a8a8;
+ background: rgba(255, 255, 255, 0.5);
}
/* React Flow custom styles */
@@ -96,4 +128,23 @@ code {
.react-flow__minimap {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+/* Modern scrollbar for sidebar */
+.sidebar-scrollbar::-webkit-scrollbar {
+ width: 6px;
+}
+
+.sidebar-scrollbar::-webkit-scrollbar-track {
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 3px;
+}
+
+.sidebar-scrollbar::-webkit-scrollbar-thumb {
+ background: rgba(255, 255, 255, 0.3);
+ border-radius: 3px;
+}
+
+.sidebar-scrollbar::-webkit-scrollbar-thumb:hover {
+ background: rgba(255, 255, 255, 0.5);
}
\ No newline at end of file
diff --git a/src/types/flow.ts b/src/types/flow.ts
index c74f8b86..b5031933 100644
--- a/src/types/flow.ts
+++ b/src/types/flow.ts
@@ -16,7 +16,21 @@ export interface FlowData {
edges: FlowEdge[];
}
-export type NodeType = 'sequence' | 'object';
+export type NodeType =
+ | 'sequence'
+ | 'object'
+ | 'class'
+ | 'component'
+ | 'composite'
+ | 'deployment'
+ | 'package'
+ | 'profile'
+ | 'use-case'
+ | 'activity'
+ | 'state-machine'
+ | 'communication'
+ | 'timing'
+ | 'interaction-overview';
export interface DragItem {
type: NodeType;
diff --git a/src/types/index.ts b/src/types/index.ts
index 68a641ab..c0e3cea4 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -1 +1,2 @@
-export * from './flow';
\ No newline at end of file
+export * from './flow';
+export * from './tree';
\ No newline at end of file
diff --git a/src/types/tree.ts b/src/types/tree.ts
new file mode 100644
index 00000000..65719edf
--- /dev/null
+++ b/src/types/tree.ts
@@ -0,0 +1,13 @@
+export interface TreeNode {
+ id: string;
+ label: string;
+ children?: TreeNode[];
+ nodeType?: string;
+ draggable?: boolean;
+}
+
+export interface TreeMenuProps {
+ nodes: TreeNode[];
+ onNodeClick?: (node: TreeNode) => void;
+ onDragStart?: (event: React.DragEvent, node: TreeNode) => void;
+}
\ No newline at end of file