@@ -200,12 +200,21 @@ interface StatusBarSettings {
200200 scriptPath ?: string ;
201201}
202202
203+ type SettingsSection = "display" | "terminal" | "statusbar" ;
204+
205+ const settingsSections : { id : SettingsSection ; label : string } [ ] = [
206+ { id : "display" , label : "Display" } ,
207+ { id : "terminal" , label : "Terminal" } ,
208+ { id : "statusbar" , label : "StatusBar" } ,
209+ ] ;
210+
203211function AppSettingsDialog ( { open, onClose } : { open : boolean ; onClose : ( ) => void } ) {
204212 const { shortenPaths, setShortenPaths } = useAppConfig ( ) ;
205213 const [ autoCopy , setAutoCopy ] = useState ( getAutoCopyOnSelect ) ;
206214 const [ featureTabsLayout , setFeatureTabsLayout ] = useAtom ( featureTabsLayoutAtom ) ;
207215 const [ statusBarEnabled , setStatusBarEnabled ] = useState ( false ) ;
208216 const [ statusBarScript , setStatusBarScript ] = useState ( "~/.lovstudio/lovcode/statusbar/default.sh" ) ;
217+ const [ activeSection , setActiveSection ] = useState < SettingsSection > ( "display" ) ;
209218
210219 // Load statusbar settings on open
211220 useEffect ( ( ) => {
@@ -252,79 +261,103 @@ function AppSettingsDialog({ open, onClose }: { open: boolean; onClose: () => vo
252261 return (
253262 < div className = "fixed inset-0 z-50 flex items-center justify-center" >
254263 < div className = "absolute inset-0 bg-black/50" onClick = { onClose } />
255- < div className = "relative bg-card rounded-xl border border-border shadow-xl w-[28rem] max-w-[90vw] max-h-[80vh] overflow-y-auto" >
256- < div className = "px-5 py-4 border-b border-border flex items-center justify-between sticky top-0 bg-card z-10" >
264+ < div className = "relative bg-card rounded-xl border border-border shadow-xl w-[38rem] max-w-[90vw] h-[28rem] max-h-[80vh] flex flex-col overflow-hidden" >
265+ { /* Header */ }
266+ < div className = "px-5 py-3 border-b border-border flex items-center justify-between shrink-0" >
257267 < h2 className = "text-lg font-semibold text-ink" > Settings</ h2 >
258268 < button onClick = { onClose } className = "text-muted-foreground hover:text-ink text-xl leading-none" > ×</ button >
259269 </ div >
260- < div className = "p-5 space-y-5" >
261- < div className = "space-y-3" >
262- < h3 className = "text-xs font-medium text-muted-foreground uppercase tracking-wide" > Display</ h3 >
263- < div className = "flex items-center justify-between" >
264- < div >
265- < p className = "text-sm font-medium text-ink" > Shorten paths</ p >
266- < p className = "text-xs text-muted-foreground" > Replace home directory with ~</ p >
267- </ div >
268- < Switch checked = { shortenPaths } onCheckedChange = { setShortenPaths } />
269- </ div >
270- < div className = "flex items-center justify-between" >
271- < div >
272- < p className = "text-sm font-medium text-ink" > Project tabs layout</ p >
273- < p className = "text-xs text-muted-foreground" > Position of project/feature tabs</ p >
274- </ div >
275- < div className = "flex gap-0.5 p-0.5 bg-muted rounded-lg" >
276- < button
277- onClick = { ( ) => setFeatureTabsLayout ( "horizontal" ) }
278- className = { `px-2.5 py-1 text-xs rounded-md transition-colors ${
279- featureTabsLayout === "horizontal" ? "bg-background text-ink shadow-sm" : "text-muted-foreground hover:text-ink"
280- } `}
281- >
282- Horizontal
283- </ button >
284- < button
285- onClick = { ( ) => setFeatureTabsLayout ( "vertical" ) }
286- className = { `px-2.5 py-1 text-xs rounded-md transition-colors ${
287- featureTabsLayout === "vertical" ? "bg-background text-ink shadow-sm" : "text-muted-foreground hover:text-ink"
288- } `}
289- >
290- Vertical
291- </ button >
292- </ div >
293- </ div >
270+ { /* Two-column layout */ }
271+ < div className = "flex flex-1 min-h-0" >
272+ { /* Left sidebar */ }
273+ < div className = "w-40 shrink-0 border-r border-border bg-muted/30 p-2 space-y-1" >
274+ { settingsSections . map ( ( section ) => (
275+ < button
276+ key = { section . id }
277+ onClick = { ( ) => setActiveSection ( section . id ) }
278+ className = { `w-full text-left px-3 py-1.5 rounded-lg text-sm transition-colors ${
279+ activeSection === section . id
280+ ? "bg-primary/10 text-primary font-medium"
281+ : "text-muted-foreground hover:text-ink hover:bg-muted"
282+ } `}
283+ >
284+ { section . label }
285+ </ button >
286+ ) ) }
294287 </ div >
295- < div className = "space-y-3" >
296- < h3 className = "text-xs font-medium text-muted-foreground uppercase tracking-wide" > Behavior</ h3 >
297- < div className = "flex items-center justify-between" >
298- < div >
299- < p className = "text-sm font-medium text-ink" > Auto-copy on select</ p >
300- < p className = "text-xs text-muted-foreground" > Copy selected text automatically</ p >
288+ { /* Right content */ }
289+ < div className = "flex-1 p-5 overflow-y-auto" >
290+ { activeSection === "display" && (
291+ < div className = "space-y-4" >
292+ < div className = "flex items-center justify-between" >
293+ < div >
294+ < p className = "text-sm font-medium text-ink" > Shorten paths</ p >
295+ < p className = "text-xs text-muted-foreground" > Replace home directory with ~</ p >
296+ </ div >
297+ < Switch checked = { shortenPaths } onCheckedChange = { setShortenPaths } />
298+ </ div >
299+ < div className = "flex items-center justify-between" >
300+ < div >
301+ < p className = "text-sm font-medium text-ink" > Project tabs layout</ p >
302+ < p className = "text-xs text-muted-foreground" > Position of project/feature tabs</ p >
303+ </ div >
304+ < div className = "flex gap-0.5 p-0.5 bg-muted rounded-lg" >
305+ < button
306+ onClick = { ( ) => setFeatureTabsLayout ( "horizontal" ) }
307+ className = { `px-2.5 py-1 text-xs rounded-md transition-colors ${
308+ featureTabsLayout === "horizontal" ? "bg-background text-ink shadow-sm" : "text-muted-foreground hover:text-ink"
309+ } `}
310+ >
311+ Horizontal
312+ </ button >
313+ < button
314+ onClick = { ( ) => setFeatureTabsLayout ( "vertical" ) }
315+ className = { `px-2.5 py-1 text-xs rounded-md transition-colors ${
316+ featureTabsLayout === "vertical" ? "bg-background text-ink shadow-sm" : "text-muted-foreground hover:text-ink"
317+ } `}
318+ >
319+ Vertical
320+ </ button >
321+ </ div >
322+ </ div >
301323 </ div >
302- < Switch checked = { autoCopy } onCheckedChange = { handleAutoCopyChange } />
303- </ div >
304- </ div >
305- < div className = "space-y-3" >
306- < h3 className = "text-xs font-medium text-muted-foreground uppercase tracking-wide" > StatusBar</ h3 >
307- < div className = "flex items-center justify-between" >
308- < div >
309- < p className = "text-sm font-medium text-ink" > Custom script mode</ p >
310- < p className = "text-xs text-muted-foreground" > Use a script to generate status bar content</ p >
324+ ) }
325+ { activeSection === "terminal" && (
326+ < div className = "space-y-4" >
327+ < div className = "flex items-center justify-between" >
328+ < div >
329+ < p className = "text-sm font-medium text-ink" > Auto-copy on select</ p >
330+ < p className = "text-xs text-muted-foreground" > Copy selected text automatically</ p >
331+ </ div >
332+ < Switch checked = { autoCopy } onCheckedChange = { handleAutoCopyChange } />
333+ </ div >
311334 </ div >
312- < Switch checked = { statusBarEnabled } onCheckedChange = { handleStatusBarEnabledChange } />
313- </ div >
314- { statusBarEnabled && (
315- < div className = "space-y-2 pl-0.5" >
316- < label className = "text-xs font-medium text-ink" > Script path</ label >
317- < Input
318- className = "text-xs font-mono"
319- placeholder = "~/.lovstudio/lovcode/statusbar/default.sh"
320- value = { statusBarScript }
321- onChange = { ( e ) => handleStatusBarScriptChange ( e . target . value ) }
322- />
323- < p className = "text-[10px] text-muted-foreground" >
324- Script receives JSON context via stdin. First line of stdout becomes the status bar.
325- < br />
326- < span className = "text-muted-foreground/70" > Supports ANSI color codes.</ span >
327- </ p >
335+ ) }
336+ { activeSection === "statusbar" && (
337+ < div className = "space-y-4" >
338+ < div className = "flex items-center justify-between" >
339+ < div >
340+ < p className = "text-sm font-medium text-ink" > Custom script mode</ p >
341+ < p className = "text-xs text-muted-foreground" > Use a script to generate status bar content</ p >
342+ </ div >
343+ < Switch checked = { statusBarEnabled } onCheckedChange = { handleStatusBarEnabledChange } />
344+ </ div >
345+ { statusBarEnabled && (
346+ < div className = "space-y-2" >
347+ < label className = "text-xs font-medium text-ink" > Script path</ label >
348+ < Input
349+ className = "text-xs font-mono"
350+ placeholder = "~/.lovstudio/lovcode/statusbar/default.sh"
351+ value = { statusBarScript }
352+ onChange = { ( e ) => handleStatusBarScriptChange ( e . target . value ) }
353+ />
354+ < p className = "text-[10px] text-muted-foreground" >
355+ Script receives JSON context via stdin. First line of stdout becomes the status bar.
356+ < br />
357+ < span className = "text-muted-foreground/70" > Supports ANSI color codes.</ span >
358+ </ p >
359+ </ div >
360+ ) }
328361 </ div >
329362 ) }
330363 </ div >
@@ -369,10 +402,6 @@ function ProfileDialog({ open, onClose, profile, onSave }: { open: boolean; onCl
369402 < Input id = "nickname" value = { nickname } onChange = { ( e ) => setNickname ( e . target . value ) } placeholder = "Your name" />
370403 </ div >
371404 </ div >
372- < div className = "space-y-2" >
373- < Label htmlFor = "avatar" > Avatar URL</ Label >
374- < Input id = "avatar" value = { avatarUrl } onChange = { ( e ) => setAvatarUrl ( e . target . value ) } placeholder = "https://..." />
375- </ div >
376405 </ div >
377406 < div className = "flex justify-end gap-2" >
378407 < Button variant = "outline" onClick = { onClose } > Cancel</ Button >
0 commit comments