import type { ReactNode, RefObject } from 'react'; import { ChevronRight, Folder, FolderOpen } from 'lucide-react'; import { cn } from '../../../lib/utils'; import FileContextMenu from '../../FileContextMenu'; import { Input } from '../../ui/input'; import type { FileTreeNode as FileTreeNodeType, FileTreeViewMode } from '../types/types'; type FileTreeNodeProps = { item: FileTreeNodeType; level: number; viewMode: FileTreeViewMode; expandedDirs: Set; onItemClick: (item: FileTreeNodeType) => void; renderFileIcon: (filename: string) => ReactNode; formatFileSize: (bytes?: number) => string; formatRelativeTime: (date?: string) => string; onRename?: (item: FileTreeNodeType) => void; onDelete?: (item: FileTreeNodeType) => void; onNewFile?: (path: string) => void; onNewFolder?: (path: string) => void; onCopyPath?: (item: FileTreeNodeType) => void; onDownload?: (item: FileTreeNodeType) => void; onRefresh?: () => void; // Rename state for inline editing renamingItem?: FileTreeNodeType | null; renameValue?: string; setRenameValue?: (value: string) => void; handleConfirmRename?: () => void; handleCancelRename?: () => void; renameInputRef?: RefObject; operationLoading?: boolean; }; type TreeItemIconProps = { item: FileTreeNodeType; isOpen: boolean; renderFileIcon: (filename: string) => ReactNode; }; function TreeItemIcon({ item, isOpen, renderFileIcon }: TreeItemIconProps) { if (item.type === 'directory') { return ( {isOpen ? ( ) : ( )} ); } return {renderFileIcon(item.name)}; } export default function FileTreeNode({ item, level, viewMode, expandedDirs, onItemClick, renderFileIcon, formatFileSize, formatRelativeTime, onRename, onDelete, onNewFile, onNewFolder, onCopyPath, onDownload, onRefresh, renamingItem, renameValue, setRenameValue, handleConfirmRename, handleCancelRename, renameInputRef, operationLoading, }: FileTreeNodeProps) { const isDirectory = item.type === 'directory'; const isOpen = isDirectory && expandedDirs.has(item.path); const hasChildren = Boolean(isDirectory && item.children && item.children.length > 0); const isRenaming = renamingItem?.path === item.path; const nameClassName = cn( 'text-[13px] leading-tight truncate', isDirectory ? 'font-medium text-foreground' : 'text-foreground/90', ); // View mode only changes the row layout; selection, expansion, and recursion stay shared. const rowClassName = cn( viewMode === 'detailed' ? 'group grid grid-cols-12 gap-2 py-[3px] pr-2 hover:bg-accent/60 cursor-pointer items-center rounded-sm transition-colors duration-100' : viewMode === 'compact' ? 'group flex items-center justify-between py-[3px] pr-2 hover:bg-accent/60 cursor-pointer rounded-sm transition-colors duration-100' : 'group flex items-center gap-1.5 py-[3px] pr-2 cursor-pointer rounded-sm hover:bg-accent/60 transition-colors duration-100', isDirectory && isOpen && 'border-l-2 border-primary/30', (isDirectory && !isOpen) || !isDirectory ? 'border-l-2 border-transparent' : '', ); // Render rename input if this item is being renamed if (isRenaming && setRenameValue && handleConfirmRename && handleCancelRename) { return (
e.stopPropagation()} > setRenameValue(e.target.value)} onKeyDown={(e) => { e.stopPropagation(); if (e.key === 'Enter') handleConfirmRename(); if (e.key === 'Escape') handleCancelRename(); }} onBlur={() => { setTimeout(() => { handleConfirmRename(); }, 100); }} className="h-6 text-sm flex-1" disabled={operationLoading} />
); } const rowContent = (
onItemClick(item)} > {viewMode === 'detailed' ? ( <>
{item.name}
{item.type === 'file' ? formatFileSize(item.size) : ''}
{formatRelativeTime(item.modified)}
{item.permissionsRwx || ''}
) : viewMode === 'compact' ? ( <>
{item.name}
{item.type === 'file' && ( <> {formatFileSize(item.size)} {item.permissionsRwx} )}
) : ( <> {item.name} )}
); // Check if context menu callbacks are provided const hasContextMenu = onRename || onDelete || onNewFile || onNewFolder || onCopyPath || onDownload || onRefresh; return (
{hasContextMenu ? ( {rowContent} ) : ( rowContent )} {isDirectory && isOpen && hasChildren && (
)}
); }