forked from op7418/CodePilot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSettingsLayout.tsx
More file actions
112 lines (98 loc) · 4.08 KB
/
SettingsLayout.tsx
File metadata and controls
112 lines (98 loc) · 4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"use client";
import { useState, useCallback, useSyncExternalStore } from "react";
import { type Icon, Gear, Code, UserCircle, Plug, ChartBar } from "@/components/ui/icon";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import { GeneralSection } from "./GeneralSection";
import { ProviderManager } from "./ProviderManager";
import { CliSettingsSection } from "./CliSettingsSection";
import { UsageStatsSection } from "./UsageStatsSection";
import { AssistantWorkspaceSection } from "./AssistantWorkspaceSection";
import { useTranslation } from "@/hooks/useTranslation";
import type { TranslationKey } from "@/i18n";
type Section = "general" | "providers" | "cli" | "usage" | "assistant";
interface SidebarItem {
id: Section;
label: string;
icon: Icon;
}
const sidebarItems: SidebarItem[] = [
{ id: "general", label: "General", icon: Gear },
{ id: "providers", label: "Providers", icon: Plug },
{ id: "cli", label: "Claude CLI", icon: Code },
{ id: "usage", label: "Usage", icon: ChartBar },
{ id: "assistant", label: "Assistant", icon: UserCircle },
];
function getSectionFromHash(): Section {
if (typeof window === "undefined") return "general";
const hash = window.location.hash.replace("#", "");
if (sidebarItems.some((item) => item.id === hash)) {
return hash as Section;
}
return "general";
}
function subscribeToHash(callback: () => void) {
window.addEventListener("hashchange", callback);
return () => window.removeEventListener("hashchange", callback);
}
export function SettingsLayout() {
// useSyncExternalStore subscribes to hash changes without triggering
// the react-hooks/set-state-in-effect lint rule.
const hashSection = useSyncExternalStore(subscribeToHash, getSectionFromHash, () => "general" as Section);
// Local state allows immediate UI update on click before the hash updates.
const [overrideSection, setOverrideSection] = useState<Section | null>(null);
const activeSection = overrideSection ?? hashSection;
const { t } = useTranslation();
const settingsLabelKeys: Record<string, TranslationKey> = {
'General': 'settings.general',
'Providers': 'settings.providers',
'Claude CLI': 'settings.claudeCli',
'Usage': 'settings.usage',
'Assistant': 'settings.assistant',
};
const handleSectionChange = useCallback((section: Section) => {
setOverrideSection(section);
window.history.replaceState(null, "", `/settings#${section}`);
// Clear override so subsequent hash changes take effect
queueMicrotask(() => setOverrideSection(null));
}, []);
return (
<div className="flex h-full flex-col">
<div className="border-b border-border/50 px-6 pt-4 pb-4">
<h1 className="text-xl font-semibold">{t('settings.title')}</h1>
<p className="text-sm text-muted-foreground">
{t('settings.description')}
</p>
</div>
<div className="flex min-h-0 flex-1">
{/* Sidebar */}
<nav className="flex w-52 shrink-0 flex-col gap-1 border-r border-border/50 p-3">
{sidebarItems.map((item) => (
<Button
key={item.id}
variant="ghost"
onClick={() => handleSectionChange(item.id)}
className={cn(
"justify-start gap-3 px-3 py-2 text-sm font-medium text-left w-full",
activeSection === item.id
? "bg-accent text-accent-foreground"
: "text-muted-foreground hover:bg-accent/50 hover:text-foreground"
)}
>
<item.icon size={16} className="shrink-0" />
{t(settingsLabelKeys[item.label])}
</Button>
))}
</nav>
{/* Content */}
<div className="flex-1 overflow-auto p-6">
{activeSection === "general" && <GeneralSection />}
{activeSection === "providers" && <ProviderManager />}
{activeSection === "cli" && <CliSettingsSection />}
{activeSection === "usage" && <UsageStatsSection />}
{activeSection === "assistant" && <AssistantWorkspaceSection />}
</div>
</div>
</div>
);
}