11// ========================================
22// Workspace Selector Component
33// ========================================
4- // Dropdown for selecting recent workspaces with folder browser and manual path input
4+ // Dropdown for selecting recent workspaces with native folder picker and manual path input
55
6- import { useState , useCallback , useRef } from 'react' ;
6+ import { useState , useCallback } from 'react' ;
77import { ChevronDown , X , FolderOpen , Check } from 'lucide-react' ;
88import { useIntl } from 'react-intl' ;
99import { cn } from '@/lib/utils' ;
10+ import { selectFolder } from '@/lib/nativeDialog' ;
1011import { Button } from '@/components/ui/Button' ;
1112import { Input } from '@/components/ui/Input' ;
1213import {
@@ -69,7 +70,7 @@ function truncatePath(path: string, maxChars: number = 40): string {
6970 * Workspace selector component
7071 *
7172 * Provides a dropdown menu for selecting from recent workspace paths,
72- * a manual path input dialog for entering custom paths , and delete buttons
73+ * a native OS folder picker, a manual path input dialog , and delete buttons
7374 * for removing paths from recent history.
7475 *
7576 * @example
@@ -86,15 +87,9 @@ export function WorkspaceSelector({ className }: WorkspaceSelectorProps) {
8687
8788 // UI state
8889 const [ isDropdownOpen , setIsDropdownOpen ] = useState ( false ) ;
89- const [ isBrowseOpen , setIsBrowseOpen ] = useState ( false ) ;
90+ const [ isManualOpen , setIsManualOpen ] = useState ( false ) ;
9091 const [ manualPath , setManualPath ] = useState ( '' ) ;
9192
92- // Hidden file input for folder selection
93- const folderInputRef = useRef < HTMLInputElement > ( null ) ;
94-
95- /**
96- * Handle path selection from dropdown
97- */
9893 const handleSelectPath = useCallback (
9994 async ( path : string ) => {
10095 await switchWorkspace ( path ) ;
@@ -103,77 +98,30 @@ export function WorkspaceSelector({ className }: WorkspaceSelectorProps) {
10398 [ switchWorkspace ]
10499 ) ;
105100
106- /**
107- * Handle remove path from recent history
108- */
109101 const handleRemovePath = useCallback (
110102 async ( e : React . MouseEvent , path : string ) => {
111- e . stopPropagation ( ) ; // Prevent triggering selection
103+ e . stopPropagation ( ) ;
112104 await removeRecentPath ( path ) ;
113105 } ,
114106 [ removeRecentPath ]
115107 ) ;
116108
117- /**
118- * Handle open folder browser - trigger hidden file input click
119- */
120- const handleBrowseFolder = useCallback ( ( ) => {
109+ const handleBrowseFolder = useCallback ( async ( ) => {
121110 setIsDropdownOpen ( false ) ;
122- // Trigger the hidden file input click
123- folderInputRef . current ?. click ( ) ;
124- } , [ ] ) ;
125-
126- /**
127- * Handle folder selection from file input
128- */
129- const handleFolderSelect = useCallback (
130- async ( e : React . ChangeEvent < HTMLInputElement > ) => {
131- const files = e . target . files ;
132- if ( files && files . length > 0 ) {
133- // Get the path from the first file
134- const firstFile = files [ 0 ] ;
135- // The webkitRelativePath contains the full path relative to the selected folder
136- // We need to get the parent directory path
137- const relativePath = firstFile . webkitRelativePath ;
138- const folderPath = relativePath . substring ( 0 , relativePath . indexOf ( '/' ) ) ;
139-
140- // In browser environment, we can't get the full absolute path
141- // We need to ask the user to confirm or use the folder name
142- // For now, open the manual dialog with the folder name as hint
143- setManualPath ( folderPath ) ;
144- setIsBrowseOpen ( true ) ;
145- }
146- // Reset input value to allow selecting the same folder again
147- e . target . value = '' ;
148- } ,
149- [ ]
150- ) ;
111+ const selected = await selectFolder ( projectPath || undefined ) ;
112+ if ( selected ) {
113+ await switchWorkspace ( selected ) ;
114+ }
115+ } , [ projectPath , switchWorkspace ] ) ;
151116
152- /**
153- * Handle manual path submission
154- */
155117 const handleManualPathSubmit = useCallback ( async ( ) => {
156118 const trimmedPath = manualPath . trim ( ) ;
157- if ( ! trimmedPath ) {
158- return ; // TODO: Show validation error
159- }
160-
119+ if ( ! trimmedPath ) return ;
161120 await switchWorkspace ( trimmedPath ) ;
162- setIsBrowseOpen ( false ) ;
121+ setIsManualOpen ( false ) ;
163122 setManualPath ( '' ) ;
164123 } , [ manualPath , switchWorkspace ] ) ;
165124
166- /**
167- * Handle dialog cancel
168- */
169- const handleDialogCancel = useCallback ( ( ) => {
170- setIsBrowseOpen ( false ) ;
171- setManualPath ( '' ) ;
172- } , [ ] ) ;
173-
174- /**
175- * Handle keyboard events in dialog input
176- */
177125 const handleInputKeyDown = useCallback (
178126 ( e : React . KeyboardEvent < HTMLInputElement > ) => {
179127 if ( e . key === 'Enter' ) {
@@ -259,7 +207,7 @@ export function WorkspaceSelector({ className }: WorkspaceSelectorProps) {
259207
260208 { recentPaths . length > 0 && < DropdownMenuSeparator /> }
261209
262- { /* Browse button to open folder selector */ }
210+ { /* Browse button to open native folder selector */ }
263211 < DropdownMenuItem
264212 onClick = { handleBrowseFolder }
265213 className = "cursor-pointer gap-2"
@@ -279,7 +227,7 @@ export function WorkspaceSelector({ className }: WorkspaceSelectorProps) {
279227 < DropdownMenuItem
280228 onClick = { ( ) => {
281229 setIsDropdownOpen ( false ) ;
282- setIsBrowseOpen ( true ) ;
230+ setIsManualOpen ( true ) ;
283231 } }
284232 className = "cursor-pointer gap-2"
285233 >
@@ -290,20 +238,8 @@ export function WorkspaceSelector({ className }: WorkspaceSelectorProps) {
290238 </ DropdownMenuContent >
291239 </ DropdownMenu >
292240
293- { /* Hidden file input for folder selection */ }
294- { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ }
295- < input
296- ref = { folderInputRef }
297- type = "file"
298- { ...( { webkitdirectory : '' , directory : '' } as any ) }
299- style = { { display : 'none' } }
300- onChange = { handleFolderSelect }
301- aria-hidden = "true"
302- tabIndex = { - 1 }
303- />
304-
305241 { /* Manual path input dialog */ }
306- < Dialog open = { isBrowseOpen } onOpenChange = { setIsBrowseOpen } >
242+ < Dialog open = { isManualOpen } onOpenChange = { setIsManualOpen } >
307243 < DialogContent >
308244 < DialogHeader >
309245 < DialogTitle >
@@ -324,7 +260,10 @@ export function WorkspaceSelector({ className }: WorkspaceSelectorProps) {
324260 < DialogFooter >
325261 < Button
326262 variant = "outline"
327- onClick = { handleDialogCancel }
263+ onClick = { ( ) => {
264+ setIsManualOpen ( false ) ;
265+ setManualPath ( '' ) ;
266+ } }
328267 >
329268 { formatMessage ( { id : 'common.actions.cancel' } ) }
330269 </ Button >
0 commit comments