diff options
| author | zwlucas <lucas.fariamo08@gmail.com> | 2026-06-05 20:52:54 +0000 |
|---|---|---|
| committer | zwlucas <lucas.fariamo08@gmail.com> | 2026-06-05 20:52:54 +0000 |
| commit | 09f964451d7d92e9891430ec4595c1276d486aab (patch) | |
| tree | 28da4483f5c28924a8c47fceb648b1baebe88224 /src/components/SidePanel | |
| download | yace-09f964451d7d92e9891430ec4595c1276d486aab.tar.gz yace-09f964451d7d92e9891430ec4595c1276d486aab.zip | |
Signed-off-by: zwlucas <lucas.fariamo08@gmail.com>
Diffstat (limited to 'src/components/SidePanel')
| -rw-r--r-- | src/components/SidePanel/SidePanel.tsx | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/src/components/SidePanel/SidePanel.tsx b/src/components/SidePanel/SidePanel.tsx new file mode 100644 index 0000000..14668fb --- /dev/null +++ b/src/components/SidePanel/SidePanel.tsx @@ -0,0 +1,187 @@ +import { useRef, useCallback, useEffect } from 'react' +import FileTree from '@/components/Sidebar/FileTree' +import SettingsPanel from '@/components/Settings/SettingsPanel' +import { useFilesStore } from '@/stores/useFilesStore' +import { useTabsStore } from '@/stores/useTabsStore' +import { useLayoutStore } from '@/stores/useLayoutStore' + +function NewFileIcon() { + return ( + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> + <polyline points="14 2 14 8 20 8" /> + <line x1="12" y1="18" x2="12" y2="12" /> + <line x1="9" y1="15" x2="15" y2="15" /> + </svg> + ) +} + +function NewFolderIcon() { + return ( + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /> + <line x1="12" y1="11" x2="12" y2="17" /> + <line x1="9" y1="14" x2="15" y2="14" /> + </svg> + ) +} + +function OpenFolderIcon() { + return ( + <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> + <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /> + </svg> + ) +} + +export default function SidePanel() { + const fileTree = useFilesStore((s) => s.fileTree) + const rootPath = useFilesStore((s) => s.rootPath) + const loading = useFilesStore((s) => s.loading) + const openFolder = useFilesStore((s) => s.openFolder) + const createFolder = useFilesStore((s) => s.createFolder) + const createUntitledFile = useTabsStore((s) => s.createUntitledFile) + + const sidePanelWidth = useLayoutStore((s) => s.sidePanelWidth) + const setSidePanelWidth = useLayoutStore((s) => s.setSidePanelWidth) + const activeView = useLayoutStore((s) => s.activeView) + + const dragRef = useRef(false) + const panelRef = useRef<HTMLDivElement>(null) + + const handleMouseDown = useCallback(() => { + dragRef.current = true + document.body.style.cursor = 'col-resize' + document.body.style.userSelect = 'none' + }, []) + + const handleMouseMove = useCallback((e: MouseEvent) => { + if (!dragRef.current || !panelRef.current) return + const rect = panelRef.current.getBoundingClientRect() + const newWidth = e.clientX - rect.left + setSidePanelWidth(newWidth) + }, [setSidePanelWidth]) + + const handleMouseUp = useCallback(() => { + dragRef.current = false + document.body.style.cursor = '' + document.body.style.userSelect = '' + }, []) + + useEffect(() => { + window.addEventListener('mousemove', handleMouseMove) + window.addEventListener('mouseup', handleMouseUp) + return () => { + window.removeEventListener('mousemove', handleMouseMove) + window.removeEventListener('mouseup', handleMouseUp) + } + }, [handleMouseMove, handleMouseUp]) + + return ( + <div + ref={panelRef} + className="relative flex flex-col h-full panel-mica select-none shrink-0 overflow-hidden" + style={{ width: `${sidePanelWidth}px` }} + role="complementary" + aria-label="Side panel" + > + <div className="group flex items-center justify-between px-3 shrink-0 h-tabs border-b border-panel-border"> + <span className="uppercase tracking-widest text-[11px] font-medium text-zinc-500"> + {activeView === 'search' ? 'SEARCH' : activeView === 'extensions' ? 'EXTENSIONS' : activeView === 'settings' ? 'SETTINGS' : 'EXPLORER'} + </span> + <div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity duration-fast"> + {activeView === 'explorer' && ( + <> + <button + onClick={() => createUntitledFile()} + className="p-1 rounded text-zinc-500 hover:text-zinc-300 hover:bg-panel-hover transition-all duration-fast" + title="New File" + > + <NewFileIcon /> + </button> + {rootPath && ( + <button + onClick={() => createFolder()} + className="p-1 rounded text-zinc-500 hover:text-zinc-300 hover:bg-panel-hover transition-all duration-fast" + title="New Folder" + > + <NewFolderIcon /> + </button> + )} + <button + onClick={openFolder} + className="p-1 rounded text-zinc-500 hover:text-zinc-300 hover:bg-panel-hover transition-all duration-fast" + title={rootPath ? 'Open another folder' : 'Open Folder'} + > + <OpenFolderIcon /> + </button> + </> + )} + </div> + </div> + + <div className="flex-1 overflow-y-auto overflow-x-hidden"> + {activeView === 'explorer' && ( + <> + {!rootPath && !loading && ( + <div className="flex flex-col items-center gap-3 px-4 py-8 text-center"> + <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" className="text-text-muted"> + <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" /> + </svg> + <p className="text-ui-sm text-text-secondary">No folder open</p> + <button + onClick={openFolder} + className="px-3 py-1.5 text-ui-xs font-medium rounded transition-all duration-150 hover:brightness-110 bg-accent text-editor-bg" + > + Open Folder + </button> + </div> + )} + + {loading && ( + <div className="flex items-center justify-center gap-2 py-8"> + <div className="w-3 h-3 rounded-full animate-pulse bg-accent" /> + <span className="text-ui-sm text-text-secondary">Loading...</span> + </div> + )} + + {rootPath && !loading && ( + <> + <div className="px-3 py-1.5 text-ui-xs font-medium truncate text-text-muted border-b border-panel-border" title={rootPath}> + {rootPath.split(/[\\/]/).pop()} + </div> + {fileTree.length > 0 ? ( + <FileTree entries={fileTree} /> + ) : ( + <div className="py-8 text-ui-sm text-text-secondary text-center">Empty directory</div> + )} + </> + )} + </> + )} + + {activeView === 'search' && ( + <div className="flex items-center justify-center py-8 text-ui-sm text-text-muted"> + Search coming soon + </div> + )} + + {activeView === 'extensions' && ( + <div className="flex items-center justify-center py-8 text-ui-sm text-text-muted"> + Extensions coming soon + </div> + )} + + {activeView === 'settings' && ( + <SettingsPanel /> + )} + </div> + + <div + className="absolute right-0 top-0 bottom-0 w-[4px] cursor-col-resize hover:bg-accent/30 transition-colors duration-fast" + style={{ right: '-2px' }} + onMouseDown={handleMouseDown} + /> + </div> + ) +} |