From 82cafb885d6a61f33a261a5fbfc09a1c9f18e632 Mon Sep 17 00:00:00 2001 From: Ruben Kostandyan Date: Mon, 30 Mar 2026 21:52:31 +0200 Subject: [PATCH] Improve file creation UX in FileExplorer - Auto-append .forge.js when user types a bare name without an extension - Show creation input inline inside the target folder (like VS Code) instead of at the top of the panel Co-Authored-By: Claude Sonnet 4.6 --- src/components/FileExplorer.tsx | 86 ++++++++++++++++++++------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/src/components/FileExplorer.tsx b/src/components/FileExplorer.tsx index 578a6096..bc7ceac8 100644 --- a/src/components/FileExplorer.tsx +++ b/src/components/FileExplorer.tsx @@ -155,8 +155,12 @@ export function FileExplorer() { }, [selection]); const handleCreate = () => { - const name = normalizePath(newName.trim()); + let name = normalizePath(newName.trim()); if (!name) return; + // Auto-append .forge.js for bare names (no extension) when creating files + if (creating === 'file' && !name.includes('.')) { + name = `${name}.forge.js`; + } const parent = focusedFolder ? normalizePath(focusedFolder) : ''; const fullPath = parent ? normalizePath(`${parent}/${name}`) : name; if (files[fullPath]) return; @@ -300,6 +304,44 @@ export function FileExplorer() { [flatVisiblePaths, setActiveFile, setMeshPreview], ); + const renderCreationInput = (depth: number) => { + const paddingLeft = 8 + depth * 12 + 14 + 6; // match file item indent (base + depth + chevron + gap) + return ( +
+ setNewName(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') handleCreate(); + if (e.key === 'Escape') { + setCreating(null); + setNewName(''); + } + }} + onBlur={() => { + if (!newName.trim()) { + setCreating(null); + setNewName(''); + } + }} + onClick={(e) => e.stopPropagation()} + style={{ + width: '100%', + background: 'var(--fc-bg)', + border: '1px solid var(--fc-accent)', + color: 'var(--fc-text)', + fontSize: 11, + padding: '3px 6px', + outline: 'none', + boxSizing: 'border-box', + }} + /> +
+ ); + }; + const renderNode = (node: TreeNode, depth: number) => { const isFolder = node.type === 'folder'; const isExpanded = !isFolder || expandedFolders.includes(node.path); @@ -425,7 +467,12 @@ export function FileExplorer() { )} - {isFolder && isExpanded && node.children?.map((child) => renderNode(child, depth + 1))} + {isFolder && isExpanded && ( + <> + {creating && focusedFolder === node.path && renderCreationInput(depth + 1)} + {node.children?.map((child) => renderNode(child, depth + 1))} + + )} ); }; @@ -617,11 +664,11 @@ export function FileExplorer() { > Project Files
- setCreating('file')} style={{ cursor: 'pointer', color: 'var(--fc-accent)', fontSize: 12 }} title="New file"> + { setFocusedFolder(''); setCreating('file'); }} style={{ cursor: 'pointer', color: 'var(--fc-accent)', fontSize: 12 }} title="New file"> + File setCreating('folder')} + onClick={() => { setFocusedFolder(''); setCreating('folder'); }} style={{ cursor: 'pointer', color: 'var(--fc-accent)', fontSize: 12 }} title="New folder" > @@ -630,40 +677,13 @@ export function FileExplorer() {
- {creating && ( -
- setNewName(e.target.value)} - onKeyDown={(e) => { - if (e.key === 'Enter') handleCreate(); - if (e.key === 'Escape') setCreating(null); - }} - onBlur={() => { - if (!newName.trim()) setCreating(null); - }} - style={{ - width: '100%', - background: 'var(--fc-bg)', - border: '1px solid var(--fc-accent)', - color: 'var(--fc-text)', - fontSize: 11, - padding: '3px 6px', - outline: 'none', - boxSizing: 'border-box', - }} - /> -
- )} -
- {tree.length === 0 && ( + {tree.length === 0 && !creating && (
No files yet. Create a file or drop one here.
)} + {creating && !focusedFolder && renderCreationInput(0)} {tree.map((node) => renderNode(node, 0))}