11"use client" ;
22
3- import { useState , useEffect , useRef } from "react" ;
3+ import { useState , useEffect , useRef , useCallback } from "react" ;
44import {
55 FolderOpen ,
66 Folder ,
99 ArrowLeft ,
1010 HardDrive ,
1111 CornerDownLeft ,
12+ Clock ,
13+ X ,
1214} from "lucide-react" ;
1315import {
1416 Dialog ,
@@ -45,6 +47,44 @@ interface FileBrowserDialogProps {
4547 initialPath ?: string ;
4648}
4749
50+ const RECENT_FOLDERS_KEY = "file-browser-recent-folders" ;
51+ const MAX_RECENT_FOLDERS = 5 ;
52+
53+ function getRecentFolders ( ) : string [ ] {
54+ if ( typeof window === "undefined" ) return [ ] ;
55+ try {
56+ const stored = localStorage . getItem ( RECENT_FOLDERS_KEY ) ;
57+ return stored ? JSON . parse ( stored ) : [ ] ;
58+ } catch {
59+ return [ ] ;
60+ }
61+ }
62+
63+ function addRecentFolder ( path : string ) : void {
64+ if ( typeof window === "undefined" ) return ;
65+ try {
66+ const recent = getRecentFolders ( ) ;
67+ // Remove if already exists, then add to front
68+ const filtered = recent . filter ( ( p ) => p !== path ) ;
69+ const updated = [ path , ...filtered ] . slice ( 0 , MAX_RECENT_FOLDERS ) ;
70+ localStorage . setItem ( RECENT_FOLDERS_KEY , JSON . stringify ( updated ) ) ;
71+ } catch {
72+ // Ignore localStorage errors
73+ }
74+ }
75+
76+ function removeRecentFolder ( path : string ) : string [ ] {
77+ if ( typeof window === "undefined" ) return [ ] ;
78+ try {
79+ const recent = getRecentFolders ( ) ;
80+ const updated = recent . filter ( ( p ) => p !== path ) ;
81+ localStorage . setItem ( RECENT_FOLDERS_KEY , JSON . stringify ( updated ) ) ;
82+ return updated ;
83+ } catch {
84+ return [ ] ;
85+ }
86+ }
87+
4888export function FileBrowserDialog ( {
4989 open,
5090 onOpenChange,
@@ -61,8 +101,26 @@ export function FileBrowserDialog({
61101 const [ loading , setLoading ] = useState ( false ) ;
62102 const [ error , setError ] = useState ( "" ) ;
63103 const [ warning , setWarning ] = useState ( "" ) ;
104+ const [ recentFolders , setRecentFolders ] = useState < string [ ] > ( [ ] ) ;
64105 const pathInputRef = useRef < HTMLInputElement > ( null ) ;
65106
107+ // Load recent folders when dialog opens
108+ useEffect ( ( ) => {
109+ if ( open ) {
110+ setRecentFolders ( getRecentFolders ( ) ) ;
111+ }
112+ } , [ open ] ) ;
113+
114+ const handleRemoveRecent = useCallback ( ( e : React . MouseEvent , path : string ) => {
115+ e . stopPropagation ( ) ;
116+ const updated = removeRecentFolder ( path ) ;
117+ setRecentFolders ( updated ) ;
118+ } , [ ] ) ;
119+
120+ const handleSelectRecent = useCallback ( ( path : string ) => {
121+ browseDirectory ( path ) ;
122+ } , [ ] ) ;
123+
66124 const browseDirectory = async ( dirPath ?: string ) => {
67125 setLoading ( true ) ;
68126 setError ( "" ) ;
@@ -153,35 +211,42 @@ export function FileBrowserDialog({
153211
154212 const handleSelect = ( ) => {
155213 if ( currentPath ) {
214+ addRecentFolder ( currentPath ) ;
156215 onSelect ( currentPath ) ;
157216 onOpenChange ( false ) ;
158217 }
159218 } ;
160219
220+ // Helper to get folder name from path
221+ const getFolderName = ( path : string ) => {
222+ const parts = path . split ( / [ / \\ ] / ) . filter ( Boolean ) ;
223+ return parts [ parts . length - 1 ] || path ;
224+ } ;
225+
161226 return (
162227 < Dialog open = { open } onOpenChange = { onOpenChange } >
163- < DialogContent className = "bg-popover border-border max-w-2xl max-h-[80vh ] overflow-hidden flex flex-col" >
164- < DialogHeader className = "pb-2 " >
165- < DialogTitle className = "flex items-center gap-2" >
166- < FolderOpen className = "w-5 h-5 text-brand-500" />
228+ < DialogContent className = "bg-popover border-border max-w-3xl max-h-[85vh ] overflow-hidden flex flex-col p-4 " >
229+ < DialogHeader className = "pb-1 " >
230+ < DialogTitle className = "flex items-center gap-2 text-base " >
231+ < FolderOpen className = "w-4 h-4 text-brand-500" />
167232 { title }
168233 </ DialogTitle >
169- < DialogDescription className = "text-muted-foreground" >
234+ < DialogDescription className = "text-muted-foreground text-xs " >
170235 { description }
171236 </ DialogDescription >
172237 </ DialogHeader >
173238
174- < div className = "flex flex-col gap-3 min-h-[400px ] flex-1 overflow-hidden py-2 " >
239+ < div className = "flex flex-col gap-2 min-h-[350px ] flex-1 overflow-hidden py-1 " >
175240 { /* Direct path input */ }
176- < div className = "flex items-center gap-2 " >
241+ < div className = "flex items-center gap-1.5 " >
177242 < Input
178243 ref = { pathInputRef }
179244 type = "text"
180245 placeholder = "Paste or type a full path (e.g., /home/user/projects/myapp)"
181246 value = { pathInput }
182247 onChange = { ( e ) => setPathInput ( e . target . value ) }
183248 onKeyDown = { handlePathInputKeyDown }
184- className = "flex-1 font-mono text-sm "
249+ className = "flex-1 font-mono text-xs h-8 "
185250 data-testid = "path-input"
186251 disabled = { loading }
187252 />
@@ -191,16 +256,46 @@ export function FileBrowserDialog({
191256 onClick = { handleGoToPath }
192257 disabled = { loading || ! pathInput . trim ( ) }
193258 data-testid = "go-to-path-button"
259+ className = "h-8 px-2"
194260 >
195- < CornerDownLeft className = "w-4 h-4 mr-1" />
261+ < CornerDownLeft className = "w-3.5 h-3.5 mr-1" />
196262 Go
197263 </ Button >
198264 </ div >
199265
266+ { /* Recent folders */ }
267+ { recentFolders . length > 0 && (
268+ < div className = "flex flex-wrap gap-1.5 p-2 rounded-md bg-sidebar-accent/10 border border-sidebar-border" >
269+ < div className = "flex items-center gap-1 text-xs text-muted-foreground mr-1" >
270+ < Clock className = "w-3 h-3" />
271+ < span > Recent:</ span >
272+ </ div >
273+ { recentFolders . map ( ( folder ) => (
274+ < button
275+ key = { folder }
276+ onClick = { ( ) => handleSelectRecent ( folder ) }
277+ className = "group flex items-center gap-1 h-6 px-2 text-xs bg-sidebar-accent/20 hover:bg-sidebar-accent/40 rounded border border-sidebar-border transition-colors"
278+ disabled = { loading }
279+ title = { folder }
280+ >
281+ < Folder className = "w-3 h-3 text-brand-500 shrink-0" />
282+ < span className = "truncate max-w-[120px]" > { getFolderName ( folder ) } </ span >
283+ < button
284+ onClick = { ( e ) => handleRemoveRecent ( e , folder ) }
285+ className = "ml-0.5 opacity-0 group-hover:opacity-100 hover:text-destructive transition-opacity"
286+ title = "Remove from recent"
287+ >
288+ < X className = "w-3 h-3" />
289+ </ button >
290+ </ button >
291+ ) ) }
292+ </ div >
293+ ) }
294+
200295 { /* Drives selector (Windows only) */ }
201296 { drives . length > 0 && (
202- < div className = "flex flex-wrap gap-2 p-3 rounded-lg bg-sidebar-accent/10 border border-sidebar-border" >
203- < div className = "flex items-center gap-1 text-xs text-muted-foreground mr-2 " >
297+ < div className = "flex flex-wrap gap-1.5 p-2 rounded-md bg-sidebar-accent/10 border border-sidebar-border" >
298+ < div className = "flex items-center gap-1 text-xs text-muted-foreground mr-1 " >
204299 < HardDrive className = "w-3 h-3" />
205300 < span > Drives:</ span >
206301 </ div >
@@ -212,7 +307,7 @@ export function FileBrowserDialog({
212307 }
213308 size = "sm"
214309 onClick = { ( ) => handleSelectDrive ( drive ) }
215- className = "h-7 px-3 text-xs"
310+ className = "h-6 px-2 text-xs"
216311 disabled = { loading }
217312 >
218313 { drive . replace ( "\\" , "" ) }
@@ -222,57 +317,57 @@ export function FileBrowserDialog({
222317 ) }
223318
224319 { /* Current path breadcrumb */ }
225- < div className = "flex items-center gap-2 p-3 rounded-lg bg-sidebar-accent/10 border border-sidebar-border" >
320+ < div className = "flex items-center gap-1.5 p-2 rounded-md bg-sidebar-accent/10 border border-sidebar-border" >
226321 < Button
227322 variant = "ghost"
228323 size = "sm"
229324 onClick = { handleGoHome }
230- className = "h-7 px-2 "
325+ className = "h-6 px-1.5 "
231326 disabled = { loading }
232327 >
233- < Home className = "w-4 h-4 " />
328+ < Home className = "w-3.5 h-3.5 " />
234329 </ Button >
235330 { parentPath && (
236331 < Button
237332 variant = "ghost"
238333 size = "sm"
239334 onClick = { handleGoToParent }
240- className = "h-7 px-2 "
335+ className = "h-6 px-1.5 "
241336 disabled = { loading }
242337 >
243- < ArrowLeft className = "w-4 h-4 " />
338+ < ArrowLeft className = "w-3.5 h-3.5 " />
244339 </ Button >
245340 ) }
246- < div className = "flex-1 font-mono text-sm truncate text-muted-foreground" >
341+ < div className = "flex-1 font-mono text-xs truncate text-muted-foreground" >
247342 { currentPath || "Loading..." }
248343 </ div >
249344 </ div >
250345
251346 { /* Directory list */ }
252- < div className = "flex-1 overflow-y-auto border border-sidebar-border rounded-lg " >
347+ < div className = "flex-1 overflow-y-auto border border-sidebar-border rounded-md " >
253348 { loading && (
254- < div className = "flex items-center justify-center h-full p-8 " >
255- < div className = "text-sm text-muted-foreground" >
349+ < div className = "flex items-center justify-center h-full p-4 " >
350+ < div className = "text-xs text-muted-foreground" >
256351 Loading directories...
257352 </ div >
258353 </ div >
259354 ) }
260355
261356 { error && (
262- < div className = "flex items-center justify-center h-full p-8 " >
263- < div className = "text-sm text-destructive" > { error } </ div >
357+ < div className = "flex items-center justify-center h-full p-4 " >
358+ < div className = "text-xs text-destructive" > { error } </ div >
264359 </ div >
265360 ) }
266361
267362 { warning && (
268- < div className = "p-3 bg-yellow-500/10 border border-yellow-500/30 rounded-lg mb-2 " >
269- < div className = "text-sm text-yellow-500" > { warning } </ div >
363+ < div className = "p-2 bg-yellow-500/10 border border-yellow-500/30 rounded-md mb-1 " >
364+ < div className = "text-xs text-yellow-500" > { warning } </ div >
270365 </ div >
271366 ) }
272367
273368 { ! loading && ! error && ! warning && directories . length === 0 && (
274- < div className = "flex items-center justify-center h-full p-8 " >
275- < div className = "text-sm text-muted-foreground" >
369+ < div className = "flex items-center justify-center h-full p-4 " >
370+ < div className = "text-xs text-muted-foreground" >
276371 No subdirectories found
277372 </ div >
278373 </ div >
@@ -284,29 +379,29 @@ export function FileBrowserDialog({
284379 < button
285380 key = { dir . path }
286381 onClick = { ( ) => handleSelectDirectory ( dir ) }
287- className = "w-full flex items-center gap-3 p-3 hover:bg-sidebar-accent/10 transition-colors text-left group"
382+ className = "w-full flex items-center gap-2 px-2 py-1.5 hover:bg-sidebar-accent/10 transition-colors text-left group"
288383 >
289- < Folder className = "w-5 h-5 text-brand-500 shrink-0" />
290- < span className = "flex-1 truncate text-sm " > { dir . name } </ span >
291- < ChevronRight className = "w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0" />
384+ < Folder className = "w-4 h-4 text-brand-500 shrink-0" />
385+ < span className = "flex-1 truncate text-xs " > { dir . name } </ span >
386+ < ChevronRight className = "w-3.5 h-3.5 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity shrink-0" />
292387 </ button >
293388 ) ) }
294389 </ div >
295390 ) }
296391 </ div >
297392
298- < div className = "text-xs text-muted-foreground" >
393+ < div className = "text-[10px] text-muted-foreground" >
299394 Paste a full path above, or click on folders to navigate. Press
300395 Enter or click Go to jump to a path.
301396 </ div >
302397 </ div >
303398
304- < DialogFooter className = "border-t border-border pt-4 gap-2" >
305- < Button variant = "ghost" onClick = { ( ) => onOpenChange ( false ) } >
399+ < DialogFooter className = "border-t border-border pt-3 gap-2 mt-1 " >
400+ < Button variant = "ghost" size = "sm" onClick = { ( ) => onOpenChange ( false ) } >
306401 Cancel
307402 </ Button >
308- < Button onClick = { handleSelect } disabled = { ! currentPath || loading } >
309- < FolderOpen className = "w-4 h-4 mr-2 " />
403+ < Button size = "sm" onClick = { handleSelect } disabled = { ! currentPath || loading } >
404+ < FolderOpen className = "w-3.5 h-3.5 mr-1.5 " />
310405 Select Current Folder
311406 </ Button >
312407 </ DialogFooter >
0 commit comments