import React, { useState, useRef, useCallback, useEffect } from "react"; import { createPortal } from "react-dom"; import { cn } from "@/lib/utils"; import { Folder, File, FileText, FileImage, FileVideo, FileAudio, Archive, Code, Settings, Download, Upload, ChevronLeft, ChevronRight, RefreshCw, ArrowUp, FileSymlink, Move, GitCompare, Edit, } from "lucide-react"; import { useTranslation } from "react-i18next"; import type { FileItem } from "../../../types/index.js"; interface CreateIntent { id: string; type: "file" | "directory"; defaultName: string; currentName: string; } function formatFileSize(bytes?: number): string { if (bytes === undefined || bytes === null) return "-"; if (bytes === 0) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let size = bytes; let unitIndex = 0; while (size >= 1024 && unitIndex < units.length - 1) { size /= 1024; unitIndex++; } const formattedSize = size < 10 && unitIndex > 0 ? size.toFixed(1) : Math.round(size).toString(); return `${formattedSize} ${units[unitIndex]}`; } interface DragState { type: "none" | "internal" | "external"; files: FileItem[]; draggedFiles?: FileItem[]; target?: FileItem; counter: number; mousePosition?: { x: number; y: number }; } interface FileManagerGridProps { files: FileItem[]; selectedFiles: FileItem[]; onFileSelect: (file: FileItem, multiSelect?: boolean) => void; onFileOpen: (file: FileItem) => void; onSelectionChange: (files: FileItem[]) => void; currentPath: string; isLoading?: boolean; onPathChange: (path: string) => void; onRefresh: () => void; onUpload?: (files: FileList) => void; onDownload?: (files: FileItem[]) => void; onContextMenu?: (event: React.MouseEvent, file?: FileItem) => void; viewMode?: "grid" | "list"; onRename?: (file: FileItem, newName: string) => void; editingFile?: FileItem | null; onStartEdit?: (file: FileItem) => void; onCancelEdit?: () => void; onDelete?: (files: FileItem[]) => void; onCopy?: (files: FileItem[]) => void; onCut?: (files: FileItem[]) => void; onPaste?: () => void; onUndo?: () => void; onFileDrop?: (draggedFiles: FileItem[], targetFile: FileItem) => void; onFileDiff?: (file1: FileItem, file2: FileItem) => void; onSystemDragStart?: (files: FileItem[]) => void; onSystemDragEnd?: (e: DragEvent, files: FileItem[]) => void; hasClipboard?: boolean; createIntent?: CreateIntent | null; onConfirmCreate?: (name: string) => void; onCancelCreate?: () => void; } const getFileIcon = (file: FileItem, viewMode: "grid" | "list" = "grid") => { const iconClass = viewMode === "grid" ? "w-8 h-8" : "w-6 h-6"; if (file.type === "directory") { return ; } if (file.type === "link") { return ; } const ext = file.name.split(".").pop()?.toLowerCase(); switch (ext) { case "txt": case "md": case "readme": return ; case "png": case "jpg": case "jpeg": case "gif": case "bmp": case "svg": return ; case "mp4": case "avi": case "mkv": case "mov": return ; case "mp3": case "wav": case "flac": case "ogg": return ; case "zip": case "tar": case "gz": case "rar": case "7z": return ; case "js": case "ts": case "jsx": case "tsx": case "py": case "java": case "cpp": case "c": case "cs": case "php": case "rb": case "go": case "rs": return ; case "json": case "xml": case "yaml": case "yml": case "toml": case "ini": case "conf": case "config": return ; default: return ; } }; export function FileManagerGrid({ files, selectedFiles, onFileSelect, onFileOpen, onSelectionChange, currentPath, isLoading, onPathChange, onRefresh, onUpload, onDownload, onContextMenu, viewMode = "grid", onRename, editingFile, onStartEdit, onCancelEdit, onDelete, onCopy, onCut, onPaste, onUndo, onFileDrop, onFileDiff, onSystemDragStart, onSystemDragEnd, hasClipboard, createIntent, onConfirmCreate, onCancelCreate, }: FileManagerGridProps) { const { t } = useTranslation(); const gridRef = useRef(null); const [editingName, setEditingName] = useState(""); const [dragState, setDragState] = useState({ type: "none", files: [], counter: 0, }); useEffect(() => { const handleGlobalMouseMove = (e: MouseEvent) => { if (dragState.type === "internal" && dragState.files.length > 0) { setDragState((prev) => ({ ...prev, mousePosition: { x: e.clientX, y: e.clientY }, })); } }; if (dragState.type === "internal" && dragState.files.length > 0) { document.addEventListener("mousemove", handleGlobalMouseMove); return () => document.removeEventListener("mousemove", handleGlobalMouseMove); } }, [dragState.type, dragState.files.length]); const editInputRef = useRef(null); useEffect(() => { if (editingFile) { setEditingName(editingFile.name); setTimeout(() => { editInputRef.current?.focus(); editInputRef.current?.select(); }, 0); } }, [editingFile]); const handleEditConfirm = () => { if ( editingFile && onRename && editingName.trim() && editingName !== editingFile.name ) { onRename(editingFile, editingName.trim()); } onCancelEdit?.(); }; const handleEditCancel = () => { setEditingName(""); onCancelEdit?.(); }; const handleEditKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); handleEditConfirm(); } else if (e.key === "Escape") { e.preventDefault(); handleEditCancel(); } }; const handleFileDragStart = (e: React.DragEvent, file: FileItem) => { const filesToDrag = selectedFiles.includes(file) ? selectedFiles : [file]; setDragState({ type: "internal", files: filesToDrag, draggedFiles: filesToDrag, counter: 0, mousePosition: { x: e.clientX, y: e.clientY }, }); const dragData = { type: "internal_files", files: filesToDrag.map((f) => f.path), }; e.dataTransfer.setData("text/plain", JSON.stringify(dragData)); e.dataTransfer.effectAllowed = "move"; }; const handleFileDragOver = (e: React.DragEvent, targetFile: FileItem) => { e.preventDefault(); e.stopPropagation(); if ( dragState.type === "internal" && !dragState.files.some((f) => f.path === targetFile.path) ) { setDragState((prev) => ({ ...prev, target: targetFile })); e.dataTransfer.dropEffect = "move"; } }; const handleFileDragLeave = (e: React.DragEvent, targetFile: FileItem) => { e.preventDefault(); e.stopPropagation(); if (dragState.target?.path === targetFile.path) { setDragState((prev) => ({ ...prev, target: undefined })); } }; const handleFileDrop = (e: React.DragEvent, targetFile: FileItem) => { e.preventDefault(); e.stopPropagation(); if (dragState.type !== "internal" || dragState.files.length === 0) { setDragState((prev) => ({ ...prev, target: undefined })); return; } const isDroppingOnSelf = dragState.files.some( (f) => f.path === targetFile.path, ); if (isDroppingOnSelf) { setDragState({ type: "none", files: [], counter: 0 }); return; } if (targetFile.type === "directory") { onFileDrop?.(dragState.files, targetFile); } else if ( targetFile.type === "file" && dragState.files.length === 1 && dragState.files[0].type === "file" ) { onFileDiff?.(dragState.files[0], targetFile); } setDragState({ type: "none", files: [], counter: 0 }); }; const handleFileDragEnd = (e: React.DragEvent) => { const draggedFiles = dragState.draggedFiles || []; setDragState({ type: "none", files: [], counter: 0 }); onSystemDragEnd?.(e.nativeEvent, draggedFiles); }; const [isSelecting, setIsSelecting] = useState(false); const [selectionStart, setSelectionStart] = useState<{ x: number; y: number; } | null>(null); const [selectionRect, setSelectionRect] = useState<{ x: number; y: number; width: number; height: number; } | null>(null); const [justFinishedSelecting, setJustFinishedSelecting] = useState(false); const [navigationHistory, setNavigationHistory] = useState([ currentPath, ]); const [historyIndex, setHistoryIndex] = useState(0); const [isEditingPath, setIsEditingPath] = useState(false); const [editPathValue, setEditPathValue] = useState(currentPath); useEffect(() => { const lastPath = navigationHistory[historyIndex]; if (currentPath !== lastPath) { const newHistory = navigationHistory.slice(0, historyIndex + 1); newHistory.push(currentPath); setNavigationHistory(newHistory); setHistoryIndex(newHistory.length - 1); } }, [currentPath]); const goBack = () => { if (historyIndex > 0) { const newIndex = historyIndex - 1; setHistoryIndex(newIndex); onPathChange(navigationHistory[newIndex]); } }; const goForward = () => { if (historyIndex < navigationHistory.length - 1) { const newIndex = historyIndex + 1; setHistoryIndex(newIndex); onPathChange(navigationHistory[newIndex]); } }; const goUp = () => { const parts = currentPath.split("/").filter(Boolean); if (parts.length > 0) { parts.pop(); const parentPath = "/" + parts.join("/"); onPathChange(parentPath); } else if (currentPath !== "/") { onPathChange("/"); } }; const pathParts = currentPath.split("/").filter(Boolean); const navigateToPath = (index: number) => { if (index === -1) { onPathChange("/"); } else { const newPath = "/" + pathParts.slice(0, index + 1).join("/"); onPathChange(newPath); } }; const startEditingPath = () => { setEditPathValue(currentPath); setIsEditingPath(true); }; const cancelEditingPath = () => { setIsEditingPath(false); setEditPathValue(currentPath); }; const confirmEditingPath = () => { const trimmedPath = editPathValue.trim(); if (trimmedPath) { const normalizedPath = trimmedPath.startsWith("/") ? trimmedPath : "/" + trimmedPath; onPathChange(normalizedPath); } setIsEditingPath(false); }; const handlePathInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); confirmEditingPath(); } else if (e.key === "Escape") { e.preventDefault(); cancelEditingPath(); } }; useEffect(() => { if (!isEditingPath) { setEditPathValue(currentPath); } }, [currentPath, isEditingPath]); const handleDragEnter = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); const isInternalDrag = dragState.type === "internal"; if (!isInternalDrag) { setDragState((prev) => ({ ...prev, type: "external", counter: prev.counter + 1, })); } }, [dragState.type], ); const handleDragLeave = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); const isInternalDrag = dragState.type === "internal"; if (!isInternalDrag && dragState.type === "external") { setDragState((prev) => { const newCounter = prev.counter - 1; return { ...prev, counter: newCounter, type: newCounter <= 0 ? "none" : "external", }; }); } }, [dragState.type, dragState.counter], ); const handleDragOver = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); const isInternalDrag = dragState.type === "internal"; if (isInternalDrag) { setDragState((prev) => ({ ...prev, mousePosition: { x: e.clientX, y: e.clientY }, })); e.dataTransfer.dropEffect = "move"; } else { e.dataTransfer.dropEffect = "copy"; } }, [dragState.type], ); const handleWheel = useCallback((e: React.WheelEvent) => { e.stopPropagation(); }, []); const handleMouseDown = useCallback((e: React.MouseEvent) => { if (e.target === e.currentTarget && e.button === 0) { e.preventDefault(); const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const startX = e.clientX - rect.left; const startY = e.clientY - rect.top; setIsSelecting(true); setSelectionStart({ x: startX, y: startY }); setSelectionRect({ x: startX, y: startY, width: 0, height: 0 }); setJustFinishedSelecting(false); } }, []); const handleMouseMove = useCallback( (e: React.MouseEvent) => { if (isSelecting && selectionStart && gridRef.current) { const rect = gridRef.current.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; const x = Math.min(selectionStart.x, currentX); const y = Math.min(selectionStart.y, currentY); const width = Math.abs(currentX - selectionStart.x); const height = Math.abs(currentY - selectionStart.y); setSelectionRect({ x, y, width, height }); if (gridRef.current) { const fileElements = gridRef.current.querySelectorAll("[data-file-path]"); const selectedPaths: string[] = []; fileElements.forEach((element) => { const elementRect = element.getBoundingClientRect(); const containerRect = gridRef.current!.getBoundingClientRect(); const relativeElementRect = { left: elementRect.left - containerRect.left, top: elementRect.top - containerRect.top, right: elementRect.right - containerRect.left, bottom: elementRect.bottom - containerRect.top, }; const selectionBox = { left: x, top: y, right: x + width, bottom: y + height, }; const intersects = !( relativeElementRect.right < selectionBox.left || relativeElementRect.left > selectionBox.right || relativeElementRect.bottom < selectionBox.top || relativeElementRect.top > selectionBox.bottom ); if (intersects) { const filePath = element.getAttribute("data-file-path"); if (filePath) { selectedPaths.push(filePath); } } }); const newSelection = files.filter((file) => selectedPaths.includes(file.path), ); onSelectionChange(newSelection); } } }, [isSelecting, selectionStart, files, onSelectionChange], ); const handleMouseUp = useCallback( (e: React.MouseEvent) => { if (isSelecting) { setIsSelecting(false); setSelectionStart(null); setSelectionRect(null); const startPos = selectionStart; if (startPos) { const rect = gridRef.current?.getBoundingClientRect(); if (rect) { const endX = e.clientX - rect.left; const endY = e.clientY - rect.top; const distance = Math.sqrt( Math.pow(endX - startPos.x, 2) + Math.pow(endY - startPos.y, 2), ); if (distance > 5) { setJustFinishedSelecting(true); setTimeout(() => { setJustFinishedSelecting(false); }, 50); } else { setJustFinishedSelecting(false); } } } } }, [isSelecting, selectionStart], ); useEffect(() => { const handleGlobalMouseUp = (e: MouseEvent) => { if (isSelecting) { setIsSelecting(false); setSelectionStart(null); setSelectionRect(null); setJustFinishedSelecting(true); setTimeout(() => { setJustFinishedSelecting(false); }, 50); } }; const handleGlobalMouseMove = (e: MouseEvent) => { if (isSelecting && selectionStart && gridRef.current) { const rect = gridRef.current.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; const x = Math.min(selectionStart.x, currentX); const y = Math.min(selectionStart.y, currentY); const width = Math.abs(currentX - selectionStart.x); const height = Math.abs(currentY - selectionStart.y); setSelectionRect({ x, y, width, height }); } }; if (isSelecting) { document.addEventListener("mouseup", handleGlobalMouseUp); document.addEventListener("mousemove", handleGlobalMouseMove); return () => { document.removeEventListener("mouseup", handleGlobalMouseUp); document.removeEventListener("mousemove", handleGlobalMouseMove); }; } }, [isSelecting, selectionStart]); const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault(); e.stopPropagation(); if (dragState.type === "internal") { setDragState({ type: "none", files: [], counter: 0 }); } else if (dragState.type === "external") { if (onUpload && e.dataTransfer.files.length > 0) { onUpload(e.dataTransfer.files); } } setDragState({ type: "none", files: [], counter: 0 }); }, [onUpload, onDownload, dragState], ); const handleFileClick = (file: FileItem, event: React.MouseEvent) => { event.stopPropagation(); if (gridRef.current) { gridRef.current.focus(); } if (event.detail === 2) { onFileOpen(file); } else { const multiSelect = event.ctrlKey || event.metaKey; const rangeSelect = event.shiftKey; if (rangeSelect && selectedFiles.length > 0) { const lastSelected = selectedFiles[selectedFiles.length - 1]; const currentIndex = files.findIndex((f) => f.path === file.path); const lastIndex = files.findIndex((f) => f.path === lastSelected.path); if (currentIndex !== -1 && lastIndex !== -1) { const start = Math.min(currentIndex, lastIndex); const end = Math.max(currentIndex, lastIndex); const rangeFiles = files.slice(start, end + 1); onSelectionChange(rangeFiles); } } else if (multiSelect) { const isSelected = selectedFiles.some((f) => f.path === file.path); if (isSelected) { onSelectionChange(selectedFiles.filter((f) => f.path !== file.path)); } else { onSelectionChange([...selectedFiles, file]); } } else { onSelectionChange([file]); } } }; const handleGridClick = (event: React.MouseEvent) => { if (gridRef.current) { gridRef.current.focus(); } if ( event.target === event.currentTarget && !isSelecting && !justFinishedSelecting ) { onSelectionChange([]); } }; useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { const activeElement = document.activeElement; if ( activeElement && (activeElement.tagName === "INPUT" || activeElement.tagName === "TEXTAREA" || activeElement.contentEditable === "true") ) { return; } switch (event.key) { case "Escape": onSelectionChange([]); break; case "a": case "A": if (event.ctrlKey || event.metaKey) { event.preventDefault(); onSelectionChange([...files]); } break; case "c": case "C": if ( (event.ctrlKey || event.metaKey) && selectedFiles.length > 0 && onCopy ) { event.preventDefault(); onCopy(selectedFiles); } break; case "x": case "X": if ( (event.ctrlKey || event.metaKey) && selectedFiles.length > 0 && onCut ) { event.preventDefault(); onCut(selectedFiles); } break; case "v": case "V": if ((event.ctrlKey || event.metaKey) && onPaste && hasClipboard) { event.preventDefault(); onPaste(); } break; case "z": case "Z": if ((event.ctrlKey || event.metaKey) && onUndo) { event.preventDefault(); onUndo(); } break; case "Delete": if (selectedFiles.length > 0 && onDelete) { onDelete(selectedFiles); } break; case "F6": if (selectedFiles.length === 1 && onStartEdit) { event.preventDefault(); onStartEdit(selectedFiles[0]); } break; case "y": case "Y": if (event.ctrlKey || event.metaKey) { event.preventDefault(); onRefresh(); } break; } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [ selectedFiles, files, onSelectionChange, onRefresh, onDelete, onCopy, onCut, onPaste, onUndo, ]); if (isLoading) { return (

{t("common.loading")}

); } return (
{isEditingPath ? (
setEditPathValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { confirmEditingPath(); } else if (e.key === "Escape") { cancelEditingPath(); } }} className="flex-1 px-2 py-1 bg-dark-hover border border-dark-border rounded text-sm focus:outline-none focus:ring-1 focus:ring-primary" placeholder={t("fileManager.enterPath")} autoFocus />
) : ( <> {pathParts.map((part, index) => ( {index < pathParts.length - 1 && ( / )} ))} )}
onContextMenu?.(e)} tabIndex={0} > {dragState.type === "external" && (

{t("fileManager.dragFilesToUpload")}

{t("fileManager.dragSystemFilesToUpload")}

)} {files.length === 0 && !createIntent ? (

{t("fileManager.emptyFolder")}

{t("fileManager.dragSystemFilesToUpload")}
{t("fileManager.dragFilesToWindowToDownload")}
) : viewMode === "grid" ? (
{createIntent && ( )} {files.map((file) => { const isSelected = selectedFiles.some( (f) => f.path === file.path, ); return (
f.path === file.path) && "opacity-50", )} title={`${file.name} - Selected: ${isSelected} - SelectedCount: ${selectedFiles.length}`} onClick={(e) => handleFileClick(file, e)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu?.(e, file); }} onDragStart={(e) => handleFileDragStart(e, file)} onDragOver={(e) => handleFileDragOver(e, file)} onDragLeave={(e) => handleFileDragLeave(e, file)} onDrop={(e) => handleFileDrop(e, file)} onDragEnd={handleFileDragEnd} >
{getFileIcon(file, viewMode)}
{editingFile?.path === file.path ? ( setEditingName(e.target.value)} onKeyDown={handleEditKeyDown} onBlur={handleEditConfirm} className={cn( "max-w-[120px] min-w-[60px] w-fit rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs shadow-xs transition-[color,box-shadow] outline-none", "text-center text-foreground placeholder:text-muted-foreground", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]", )} onClick={(e) => e.stopPropagation()} /> ) : (

{file.name}

)} {file.type === "file" && file.size !== undefined && file.size !== null && (

{formatFileSize(file.size)}

)} {file.type === "link" && file.linkTarget && (

→ {file.linkTarget}

)}
); })}
) : ( /* List view */
{createIntent && ( )} {files.map((file) => { const isSelected = selectedFiles.some( (f) => f.path === file.path, ); return (
f.path === file.path) && "opacity-50", )} onClick={(e) => handleFileClick(file, e)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu?.(e, file); }} onDragStart={(e) => handleFileDragStart(e, file)} onDragOver={(e) => handleFileDragOver(e, file)} onDragLeave={(e) => handleFileDragLeave(e, file)} onDrop={(e) => handleFileDrop(e, file)} onDragEnd={handleFileDragEnd} >
{getFileIcon(file, viewMode)}
{editingFile?.path === file.path ? ( setEditingName(e.target.value)} onKeyDown={handleEditKeyDown} onBlur={handleEditConfirm} className={cn( "flex-1 min-w-0 max-w-[200px] rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none", "text-foreground placeholder:text-muted-foreground", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]", )} onClick={(e) => e.stopPropagation()} /> ) : (

{file.name}

)} {file.type === "link" && file.linkTarget && (

→ {file.linkTarget}

)} {file.modified && (

{file.modified}

)}
{file.type === "file" && file.size !== undefined && file.size !== null && (

{formatFileSize(file.size)}

)}
{file.permissions && (

{file.permissions}

)}
); })}
)} {isSelecting && selectionRect && (
)}
{t("fileManager.itemCount", { count: files.length })} {selectedFiles.length > 0 && ( {t("fileManager.selectedCount", { count: selectedFiles.length })} )}
{dragState.type === "internal" && (dragState.files.length > 0 || dragState.draggedFiles?.length > 0) && dragState.mousePosition && createPortal(
{(() => { const files = dragState.files.length > 0 ? dragState.files : dragState.draggedFiles || []; return dragState.target ? ( dragState.target.type === "directory" ? ( <> {t("fileManager.moveTo", { name: dragState.target.name, })} ) : ( <> {t("fileManager.diffCompareWith", { name: dragState.target.name, })} ) ) : ( <> {t("fileManager.dragOutsideToDownload", { count: files.length, })} ); })()}
, document.body, )}
); } function CreateIntentGridItem({ intent, onConfirm, onCancel, }: { intent: CreateIntent; onConfirm?: (name: string) => void; onCancel?: () => void; }) { const { t } = useTranslation(); const [inputName, setInputName] = useState(intent.currentName); const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); inputRef.current?.select(); }, []); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); onConfirm?.(inputName.trim()); } else if (e.key === "Escape") { e.preventDefault(); onCancel?.(); } }; return (
{intent.type === "directory" ? ( ) : ( )}
setInputName(e.target.value)} onKeyDown={handleKeyDown} onBlur={() => onConfirm?.(inputName.trim())} className="w-full max-w-[120px] rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs text-center text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px] outline-none" placeholder={ intent.type === "directory" ? t("fileManager.folderName") : t("fileManager.fileName") } />
); } function CreateIntentListItem({ intent, onConfirm, onCancel, }: { intent: CreateIntent; onConfirm?: (name: string) => void; onCancel?: () => void; }) { const { t } = useTranslation(); const [inputName, setInputName] = useState(intent.currentName); const inputRef = useRef(null); useEffect(() => { inputRef.current?.focus(); inputRef.current?.select(); }, []); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter") { e.preventDefault(); onConfirm?.(inputName.trim()); } else if (e.key === "Escape") { e.preventDefault(); onCancel?.(); } }; return (
{intent.type === "directory" ? ( ) : ( )}
setInputName(e.target.value)} onKeyDown={handleKeyDown} onBlur={() => onConfirm?.(inputName.trim())} className="flex-1 min-w-0 max-w-[200px] rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-sm text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px] outline-none" placeholder={ intent.type === "directory" ? t("fileManager.folderName") : t("fileManager.fileName") } />
); }