Skip to content

Commit fc756f2

Browse files
MarkShawn2020claude
andcommitted
refactor(settings): 改用 Apple 风格双列布局
- Settings 对话框采用左侧导航 + 右侧内容双列布局 - Behavior 分类改为 Terminal(Auto-copy 属于终端操作) - 移除 Profile 对话框中的 Avatar URL 输入框 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 28489b4 commit fc756f2

1 file changed

Lines changed: 100 additions & 71 deletions

File tree

src/pages/_layout.tsx

Lines changed: 100 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
203211
function 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">&times;</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

Comments
 (0)