@@ -7,6 +7,9 @@ import '/styles/editor-files.css'
77import AltArrowRightIcon from '../../../icons/solar/Alt Arrow Right.svg?react'
88import AltArrowDownIcon from '../../../icons/solar/Alt Arrow Down.svg?react'
99import CodeIcon from '../../../icons/solar/Code.svg?react'
10+ import CodeFileIcon from '../../../icons/solar/Code File.svg?react'
11+ import TrashBinIcon from '../../../icons/solar/Trash Bin.svg?react'
12+ import Pen from '../../../icons/solar/Pen.svg?react'
1013import { useShortcut } from '~/hooks/use-shortcut'
1114import type { ContextMenuState } from './use-file-tree-context-menu'
1215
@@ -114,6 +117,15 @@ export default function EditorFileStructure() {
114117 [ selectedItemId , buildContextForItem ] ,
115118 )
116119
120+ const triggerItemAction = useCallback (
121+ ( itemId : TreeItemIndex , action : ( menuState : ContextMenuState ) => void ) => {
122+ void buildContextForItem ( itemId ) . then ( ( menuState ) => {
123+ if ( menuState ) action ( menuState )
124+ } )
125+ } ,
126+ [ buildContextForItem ] ,
127+ )
128+
117129 useShortcut ( {
118130 'explorer.new-file' : ( ) => triggerExplorerAction ( editorContextMenu . handleNewFile , false ) ,
119131 'explorer.new-folder' : ( ) => triggerExplorerAction ( editorContextMenu . handleNewFolder , false ) ,
@@ -285,6 +297,7 @@ export default function EditorFileStructure() {
285297 context : TreeItemRenderContext
286298 } ) => {
287299 const Icon = item . isFolder ? ( context . isExpanded ? FolderOpenIcon : FolderIcon ) : CodeIcon
300+ const isRoot = ( item . data as { projectRoot ?: boolean } ) . projectRoot ?? false
288301
289302 const searchLower = searchTerm . toLowerCase ( )
290303 const titleLower = title . toLowerCase ( )
@@ -310,28 +323,103 @@ export default function EditorFileStructure() {
310323
311324 const isHighlighted = highlightedItemId === item . index
312325
326+ const actionBtnClass = 'cursor-pointer rounded p-0.5 hover:bg-hover flex-shrink-0'
327+
313328 return (
314329 < div
315- className = "flex h-full w-full cursor-pointer items-center"
330+ className = "group/row flex h-full w-full items-center"
316331 onContextMenu = { ( e ) => editorContextMenu . openContextMenu ( e , item . index ) }
317332 >
318333 { Icon && < Icon className = "fill-foreground w-4 flex-shrink-0" /> }
319334 < span
320- className = { `ml-1 overflow-hidden text-nowrap text-ellipsis ${
335+ className = { `ml-1 min-w-0 flex-1 overflow-hidden text-nowrap text-ellipsis ${
321336 isHighlighted ? 'outline-foreground-active rounded-sm px-1 outline-2' : ''
322337 } `}
323338 >
324339 { highlightedTitle }
325340 </ span >
341+ < div className = "ml-1 hidden items-center gap-0.5 group-hover/row:flex" >
342+ { item . isFolder && (
343+ < button
344+ className = { actionBtnClass }
345+ title = "New File"
346+ onClick = { ( mouseEvent ) => {
347+ mouseEvent . stopPropagation ( )
348+ triggerItemAction ( item . index , editorContextMenu . handleNewFile )
349+ } }
350+ >
351+ < CodeFileIcon className = "fill-foreground h-3.5 w-3.5" />
352+ </ button >
353+ ) }
354+ { item . isFolder && (
355+ < button
356+ className = { actionBtnClass }
357+ title = "New Folder"
358+ onClick = { ( mouseEvent ) => {
359+ mouseEvent . stopPropagation ( )
360+ triggerItemAction ( item . index , editorContextMenu . handleNewFolder )
361+ } }
362+ >
363+ < FolderIcon className = "fill-foreground h-3.5 w-3.5" />
364+ </ button >
365+ ) }
366+ { ! isRoot && (
367+ < button
368+ className = { actionBtnClass }
369+ title = "Rename"
370+ onClick = { ( mouseEvent ) => {
371+ mouseEvent . stopPropagation ( )
372+ triggerItemAction ( item . index , editorContextMenu . handleRename )
373+ } }
374+ >
375+ < Pen className = "stroke-foreground h-3.5 w-3.5" />
376+ </ button >
377+ ) }
378+ { ! isRoot && (
379+ < button
380+ className = { actionBtnClass }
381+ title = "Delete"
382+ onClick = { ( mouseEvent ) => {
383+ mouseEvent . stopPropagation ( )
384+ triggerItemAction ( item . index , editorContextMenu . handleDelete )
385+ } }
386+ >
387+ < TrashBinIcon className = "fill-foreground h-3.5 w-3.5" />
388+ </ button >
389+ ) }
390+ </ div >
326391 </ div >
327392 )
328393 }
329394
330395 if ( ! dataProvider ) return < LoadingSpinner message = "Loading files..." className = "p-8" />
331396
397+ const toolbarBtnClass = 'cursor-pointer rounded p-1 hover:bg-hover text-foreground'
398+
332399 return (
333400 < >
334- < Search onChange = { ( e ) => setSearchTerm ( e . target . value ) } />
401+ < div className = "border-border flex items-center justify-between border-b px-2 py-1" >
402+ < span className = "text-foreground/50 text-xs font-semibold tracking-wider uppercase" > Explorer</ span >
403+ < div className = "flex items-center gap-0.5" >
404+ < button
405+ className = { toolbarBtnClass }
406+ title = "New File"
407+ onClick = { ( ) => triggerExplorerAction ( editorContextMenu . handleNewFile , false ) }
408+ >
409+ < CodeFileIcon className = "fill-foreground h-4 w-4" />
410+ </ button >
411+ < button
412+ className = { toolbarBtnClass }
413+ title = "New Folder"
414+ onClick = { ( ) => triggerExplorerAction ( editorContextMenu . handleNewFolder , false ) }
415+ >
416+ < FolderIcon className = "fill-foreground h-4 w-4" />
417+ </ button >
418+ </ div >
419+ </ div >
420+ < div className = "mt-2" >
421+ < Search onChange = { ( changeEvent ) => setSearchTerm ( changeEvent . target . value ) } />
422+ </ div >
335423 < div
336424 className = "h-full overflow-auto pr-2"
337425 onContextMenu = { ( e ) => {
0 commit comments