diff options
Diffstat (limited to 'frontend/src/components/LeftSidebar.tsx')
| -rw-r--r-- | frontend/src/components/LeftSidebar.tsx | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/frontend/src/components/LeftSidebar.tsx b/frontend/src/components/LeftSidebar.tsx new file mode 100644 index 0000000..1eaa62c --- /dev/null +++ b/frontend/src/components/LeftSidebar.tsx @@ -0,0 +1,159 @@ +import React, { useState } from 'react'; +import { Folder, FileText, Archive, ChevronLeft, ChevronRight, Trash2, MessageSquare } from 'lucide-react'; +import useFlowStore from '../store/flowStore'; + +interface LeftSidebarProps { + isOpen: boolean; + onToggle: () => void; +} + +const LeftSidebar: React.FC<LeftSidebarProps> = ({ isOpen, onToggle }) => { + const [activeTab, setActiveTab] = useState<'project' | 'files' | 'archive'>('project'); + const { archivedNodes, removeFromArchive, createNodeFromArchive, theme } = useFlowStore(); + const isDark = theme === 'dark'; + + const handleDragStart = (e: React.DragEvent, archiveId: string) => { + e.dataTransfer.setData('archiveId', archiveId); + e.dataTransfer.effectAllowed = 'copy'; + }; + + if (!isOpen) { + return ( + <div className={`border-r h-screen flex flex-col items-center py-4 w-12 z-10 transition-all duration-300 ${ + isDark ? 'border-gray-700 bg-gray-800' : 'border-gray-200 bg-white' + }`}> + <button + onClick={onToggle} + className={`p-2 rounded mb-4 ${isDark ? 'hover:bg-gray-700' : 'hover:bg-gray-100'}`} + title="Expand" + > + <ChevronRight size={20} className={isDark ? 'text-gray-400' : 'text-gray-500'} /> + </button> + {/* Icons when collapsed */} + <div className="flex flex-col gap-4"> + <Folder size={20} className={activeTab === 'project' ? "text-blue-500" : isDark ? "text-gray-500" : "text-gray-400"} /> + <FileText size={20} className={activeTab === 'files' ? "text-blue-500" : isDark ? "text-gray-500" : "text-gray-400"} /> + <Archive size={20} className={activeTab === 'archive' ? "text-blue-500" : isDark ? "text-gray-500" : "text-gray-400"} /> + </div> + </div> + ); + } + + return ( + <div className={`w-64 border-r h-screen flex flex-col shadow-xl z-10 transition-all duration-300 ${ + isDark ? 'border-gray-700 bg-gray-800' : 'border-gray-200 bg-white' + }`}> + {/* Header */} + <div className={`p-3 border-b flex justify-between items-center ${ + isDark ? 'border-gray-700 bg-gray-900' : 'border-gray-200 bg-gray-50' + }`}> + <h2 className={`font-bold text-sm uppercase ${isDark ? 'text-gray-300' : 'text-gray-700'}`}>Workspace</h2> + <button + onClick={onToggle} + className={`p-1 rounded ${isDark ? 'hover:bg-gray-700' : 'hover:bg-gray-200'}`} + > + <ChevronLeft size={16} className={isDark ? 'text-gray-400' : 'text-gray-500'} /> + </button> + </div> + + {/* Tabs */} + <div className={`flex border-b ${isDark ? 'border-gray-700' : 'border-gray-200'}`}> + <button + onClick={() => setActiveTab('project')} + className={`flex-1 p-3 text-xs flex justify-center items-center gap-2 ${ + activeTab === 'project' + ? 'border-b-2 border-blue-500 text-blue-500 font-medium' + : isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-600 hover:bg-gray-50' + }`} + > + <Folder size={14} /> Project + </button> + <button + onClick={() => setActiveTab('files')} + className={`flex-1 p-3 text-xs flex justify-center items-center gap-2 ${ + activeTab === 'files' + ? 'border-b-2 border-blue-500 text-blue-500 font-medium' + : isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-600 hover:bg-gray-50' + }`} + > + <FileText size={14} /> Files + </button> + <button + onClick={() => setActiveTab('archive')} + className={`flex-1 p-3 text-xs flex justify-center items-center gap-2 ${ + activeTab === 'archive' + ? 'border-b-2 border-blue-500 text-blue-500 font-medium' + : isDark ? 'text-gray-400 hover:bg-gray-700' : 'text-gray-600 hover:bg-gray-50' + }`} + > + <Archive size={14} /> Archive + </button> + </div> + + {/* Content Area */} + <div className={`flex-1 overflow-y-auto p-4 text-sm ${isDark ? 'text-gray-400' : 'text-gray-500'}`}> + {activeTab === 'project' && ( + <div className="flex flex-col items-center justify-center h-full opacity-50"> + <Folder size={48} className="mb-2" /> + <p>Project settings coming soon</p> + </div> + )} + {activeTab === 'files' && ( + <div className="flex flex-col items-center justify-center h-full opacity-50"> + <FileText size={48} className="mb-2" /> + <p>File manager coming soon</p> + </div> + )} + {activeTab === 'archive' && ( + <div className="space-y-2"> + {archivedNodes.length === 0 ? ( + <div className="flex flex-col items-center justify-center h-40 opacity-50"> + <Archive size={32} className="mb-2" /> + <p className="text-xs text-center"> + No archived nodes.<br/> + Right-click a node → "Add to Archive" + </p> + </div> + ) : ( + <> + <p className={`text-xs mb-2 ${isDark ? 'text-gray-500' : 'text-gray-400'}`}>Drag to canvas to create a copy</p> + {archivedNodes.map((archived) => ( + <div + key={archived.id} + draggable + onDragStart={(e) => handleDragStart(e, archived.id)} + className={`p-2 border rounded-md cursor-grab transition-colors group ${ + isDark + ? 'bg-gray-700 border-gray-600 hover:bg-gray-600 hover:border-gray-500' + : 'bg-gray-50 border-gray-200 hover:bg-gray-100 hover:border-gray-300' + }`} + > + <div className="flex items-center justify-between"> + <div className="flex items-center gap-2"> + <MessageSquare size={14} className={isDark ? 'text-gray-400' : 'text-gray-500'} /> + <span className={`text-sm font-medium truncate max-w-[140px] ${isDark ? 'text-gray-200' : ''}`}>{archived.label}</span> + </div> + <button + onClick={() => removeFromArchive(archived.id)} + className={`opacity-0 group-hover:opacity-100 p-1 rounded transition-all ${ + isDark ? 'hover:bg-red-900 text-gray-400 hover:text-red-400' : 'hover:bg-red-100 text-gray-400 hover:text-red-500' + }`} + title="Remove from archive" + > + <Trash2 size={12} /> + </button> + </div> + <div className={`text-[10px] mt-1 ${isDark ? 'text-gray-500' : 'text-gray-400'}`}>{archived.model}</div> + </div> + ))} + </> + )} + </div> + )} + </div> + </div> + ); +}; + +export default LeftSidebar; + |
