mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-24 08:57:25 +00:00
feat: implement file explorer context menus and dialogs (#934)
This commit is contained in:
@@ -1,60 +1,90 @@
|
||||
import { ChevronRight, ChevronDown, Folder, File, Link, Loader2 } from 'lucide-react';
|
||||
import type { FileEntry } from '@/lib/stackFilesApi';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { FileTreeContextMenu } from './FileTreeContextMenu';
|
||||
|
||||
interface FileTreeNodeProps {
|
||||
entry: FileEntry;
|
||||
relPath: string;
|
||||
depth: number;
|
||||
isSelected: boolean;
|
||||
isExpanded?: boolean;
|
||||
isLoading?: boolean;
|
||||
onClick: () => void;
|
||||
// Context menu wiring
|
||||
canEdit: boolean;
|
||||
isPaid: boolean;
|
||||
onContextMenuRename: (relPath: string) => void;
|
||||
onContextMenuNewFile: (dirRelPath: string) => void;
|
||||
onContextMenuNewFolder: (dirRelPath: string) => void;
|
||||
onContextMenuDelete: (relPath: string, entry: FileEntry) => void;
|
||||
onContextMenuPermissions: (relPath: string, entry: FileEntry) => void;
|
||||
}
|
||||
|
||||
export function FileTreeNode({
|
||||
entry,
|
||||
relPath,
|
||||
depth,
|
||||
isSelected,
|
||||
isExpanded,
|
||||
isLoading,
|
||||
onClick,
|
||||
canEdit,
|
||||
isPaid,
|
||||
onContextMenuRename,
|
||||
onContextMenuNewFile,
|
||||
onContextMenuNewFolder,
|
||||
onContextMenuDelete,
|
||||
onContextMenuPermissions,
|
||||
}: FileTreeNodeProps) {
|
||||
const isDir = entry.type === 'directory';
|
||||
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') onClick();
|
||||
}}
|
||||
aria-expanded={isDir ? isExpanded : undefined}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 py-0.5 cursor-pointer select-none rounded-sm',
|
||||
isSelected
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'hover:bg-accent/50 text-foreground'
|
||||
)}
|
||||
style={{ paddingLeft: depth * 16 + 8 }}
|
||||
<FileTreeContextMenu
|
||||
entry={entry}
|
||||
relPath={relPath}
|
||||
canEdit={canEdit}
|
||||
isPaid={isPaid}
|
||||
onRequestRename={onContextMenuRename}
|
||||
onRequestNewFile={onContextMenuNewFile}
|
||||
onRequestNewFolder={onContextMenuNewFolder}
|
||||
onRequestDelete={onContextMenuDelete}
|
||||
onRequestPermissions={onContextMenuPermissions}
|
||||
>
|
||||
{isDir && (
|
||||
isLoading
|
||||
? <Loader2 className="w-3.5 h-3.5 shrink-0 animate-spin" strokeWidth={1.5} />
|
||||
: isExpanded
|
||||
? <ChevronDown className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
: <ChevronRight className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
)}
|
||||
{isDir
|
||||
? <Folder className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
: entry.type === 'symlink'
|
||||
? <Link className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
: <File className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
}
|
||||
<span className="font-mono text-sm truncate">{entry.name}</span>
|
||||
{entry.isProtected && (
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={onClick}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') onClick();
|
||||
}}
|
||||
aria-expanded={isDir ? isExpanded : undefined}
|
||||
className={cn(
|
||||
'flex items-center gap-1.5 py-0.5 cursor-pointer select-none rounded-sm',
|
||||
isSelected
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'hover:bg-accent/50 text-foreground'
|
||||
)}
|
||||
style={{ paddingLeft: depth * 16 + 8 }}
|
||||
>
|
||||
{isDir && (
|
||||
isLoading
|
||||
? <Loader2 className="w-3.5 h-3.5 shrink-0 animate-spin" strokeWidth={1.5} />
|
||||
: isExpanded
|
||||
? <ChevronDown className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
: <ChevronRight className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
)}
|
||||
{isDir
|
||||
? <Folder className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
: entry.type === 'symlink'
|
||||
? <Link className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
: <File className="w-3.5 h-3.5 shrink-0" strokeWidth={1.5} />
|
||||
}
|
||||
<span className="font-mono text-sm truncate">{entry.name}</span>
|
||||
{entry.isProtected && (
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-amber-500 shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
</FileTreeContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user