Skip to content

Commit 8ca26d2

Browse files
committed
refactor(settings): add reusable settings primitives components
1 parent 0e9ceef commit 8ca26d2

14 files changed

Lines changed: 518 additions & 555 deletions
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { ComponentPropsWithoutRef, ReactNode } from "react";
2+
import { joinClassNames } from "../classNames";
3+
4+
type SettingsSectionProps = {
5+
title: ReactNode;
6+
subtitle?: ReactNode;
7+
className?: string;
8+
children: ReactNode;
9+
};
10+
11+
export function SettingsSection({
12+
title,
13+
subtitle,
14+
className,
15+
children,
16+
}: SettingsSectionProps) {
17+
return (
18+
<section className={joinClassNames("settings-section", className)}>
19+
<div className="settings-section-title">{title}</div>
20+
{subtitle ? <div className="settings-section-subtitle">{subtitle}</div> : null}
21+
{children}
22+
</section>
23+
);
24+
}
25+
26+
type SettingsSubsectionProps = {
27+
title: ReactNode;
28+
subtitle?: ReactNode;
29+
className?: string;
30+
};
31+
32+
export function SettingsSubsection({ title, subtitle, className }: SettingsSubsectionProps) {
33+
return (
34+
<div className={className}>
35+
<div className="settings-subsection-title">{title}</div>
36+
{subtitle ? <div className="settings-subsection-subtitle">{subtitle}</div> : null}
37+
</div>
38+
);
39+
}
40+
41+
type SettingsToggleRowProps = {
42+
title: ReactNode;
43+
subtitle?: ReactNode;
44+
className?: string;
45+
children: ReactNode;
46+
};
47+
48+
export function SettingsToggleRow({
49+
title,
50+
subtitle,
51+
className,
52+
children,
53+
}: SettingsToggleRowProps) {
54+
return (
55+
<div className={joinClassNames("settings-toggle-row", className)}>
56+
<div>
57+
<div className="settings-toggle-title">{title}</div>
58+
{subtitle ? <div className="settings-toggle-subtitle">{subtitle}</div> : null}
59+
</div>
60+
{children}
61+
</div>
62+
);
63+
}
64+
65+
type SettingsToggleSwitchProps = Omit<
66+
ComponentPropsWithoutRef<"button">,
67+
"type" | "children" | "className" | "aria-pressed"
68+
> & {
69+
pressed: boolean;
70+
className?: string;
71+
};
72+
73+
export function SettingsToggleSwitch({
74+
pressed,
75+
className,
76+
...props
77+
}: SettingsToggleSwitchProps) {
78+
return (
79+
<button
80+
type="button"
81+
className={joinClassNames("settings-toggle", pressed && "on", className)}
82+
aria-pressed={pressed}
83+
{...props}
84+
>
85+
<span className="settings-toggle-knob" />
86+
</button>
87+
);
88+
}

src/features/settings/components/sections/SettingsAboutSection.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
type AppBuildType,
66
} from "@services/tauri";
77
import { useUpdater } from "@/features/update/hooks/useUpdater";
8+
import { SettingsSection } from "@/features/design-system/components/settings/SettingsPrimitives";
89

910
function formatBytes(value: number) {
1011
if (!Number.isFinite(value) || value <= 0) {
@@ -75,7 +76,7 @@ export function SettingsAboutSection() {
7576
: new Date(parsedBuildDate).toLocaleString();
7677

7778
return (
78-
<section className="settings-section">
79+
<SettingsSection title="About" subtitle="App version, build metadata, and update controls.">
7980
<div className="settings-field">
8081
<div className="settings-help">
8182
Version: <code>{__APP_VERSION__}</code>
@@ -163,6 +164,6 @@ export function SettingsAboutSection() {
163164
)}
164165
</div>
165166
</div>
166-
</section>
167+
</SettingsSection>
167168
);
168169
}

src/features/settings/components/sections/SettingsAgentsSection.tsx

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import {
77
} from "@/features/shared/components/MagicSparkleIcon";
88
import type { SettingsAgentsSectionProps } from "@settings/hooks/useSettingsAgentsSection";
99
import { fileManagerName, openInFileManagerLabel } from "@utils/platformPaths";
10+
import {
11+
SettingsSection,
12+
SettingsSubsection,
13+
SettingsToggleRow,
14+
SettingsToggleSwitch,
15+
} from "@/features/design-system/components/settings/SettingsPrimitives";
1016

1117
const FALLBACK_AGENT_MODELS: ModelOption[] = [
1218
{
@@ -344,23 +350,19 @@ export function SettingsAgentsSection({
344350
};
345351

346352
return (
347-
<section className="settings-section">
348-
<div className="settings-section-title">Agents</div>
349-
<div className="settings-section-subtitle">
350-
Configure multi-agent mode, limits, and custom agent roles.
351-
</div>
353+
<SettingsSection
354+
title="Agents"
355+
subtitle="Configure multi-agent mode, limits, and custom agent roles."
356+
>
352357
<div className="settings-help settings-agents-builtins-help">
353358
Built-in roles from Codex are still available: <code>default</code>, <code>explorer</code>,
354359
and <code>worker</code>.
355360
</div>
356361

357-
<div className="settings-toggle-row">
358-
<div>
359-
<div className="settings-toggle-title">Config file</div>
360-
<div className="settings-toggle-subtitle">
361-
Open global Codex config in {fileManagerName()}.
362-
</div>
363-
</div>
362+
<SettingsToggleRow
363+
title="Config file"
364+
subtitle={<>Open global Codex config in {fileManagerName()}.</>}
365+
>
364366
<div className="settings-agents-actions">
365367
<button type="button" className="ghost" onClick={onRefresh} disabled={isLoading}>
366368
Refresh
@@ -374,33 +376,31 @@ export function SettingsAgentsSection({
374376
{openInFileManagerLabel()}
375377
</button>
376378
</div>
377-
</div>
379+
</SettingsToggleRow>
378380

379-
<div className="settings-toggle-row">
380-
<div>
381-
<div className="settings-toggle-title">Enable Multi-Agent</div>
382-
<div className="settings-toggle-subtitle">
381+
<SettingsToggleRow
382+
title="Enable Multi-Agent"
383+
subtitle={
384+
<>
383385
Writes <code>features.multi_agent</code> in config.toml.
384-
</div>
385-
</div>
386-
<button
387-
type="button"
388-
className={`settings-toggle ${settings?.multiAgentEnabled ? "on" : ""}`}
386+
</>
387+
}
388+
>
389+
<SettingsToggleSwitch
390+
pressed={settings?.multiAgentEnabled ?? false}
389391
onClick={() => void handleToggleMultiAgent()}
390-
aria-pressed={settings?.multiAgentEnabled ?? false}
391392
disabled={!settings || isUpdatingCore}
392-
>
393-
<span className="settings-toggle-knob" />
394-
</button>
395-
</div>
393+
/>
394+
</SettingsToggleRow>
396395

397-
<div className="settings-toggle-row">
398-
<div>
399-
<div className="settings-toggle-title">Max Threads</div>
400-
<div className="settings-toggle-subtitle">
396+
<SettingsToggleRow
397+
title="Max Threads"
398+
subtitle={
399+
<>
401400
Maximum open agent threads. Valid range: <code>1-12</code>. Changes save immediately.
402-
</div>
403-
</div>
401+
</>
402+
}
403+
>
404404
<div className="settings-agents-stepper" role="group" aria-label="Maximum agent threads">
405405
<button
406406
type="button"
@@ -428,15 +428,16 @@ export function SettingsAgentsSection({
428428
429429
</button>
430430
</div>
431-
</div>
431+
</SettingsToggleRow>
432432

433-
<div className="settings-toggle-row">
434-
<div>
435-
<div className="settings-toggle-title">Max Depth</div>
436-
<div className="settings-toggle-subtitle">
433+
<SettingsToggleRow
434+
title="Max Depth"
435+
subtitle={
436+
<>
437437
Maximum nested spawn depth. Valid range: <code>1-4</code>. Changes save immediately.
438-
</div>
439-
</div>
438+
</>
439+
}
440+
>
440441
<div className="settings-agents-stepper" role="group" aria-label="Maximum agent depth">
441442
<button
442443
type="button"
@@ -464,12 +465,16 @@ export function SettingsAgentsSection({
464465
465466
</button>
466467
</div>
467-
</div>
468-
469-
<div className="settings-subsection-title">Create Agent</div>
470-
<div className="settings-subsection-subtitle">
471-
Add a custom role under <code>[agents.&lt;name&gt;]</code> and create its config file.
472-
</div>
468+
</SettingsToggleRow>
469+
470+
<SettingsSubsection
471+
title="Create Agent"
472+
subtitle={
473+
<>
474+
Add a custom role under <code>[agents.&lt;name&gt;]</code> and create its config file.
475+
</>
476+
}
477+
/>
473478
<div className="settings-field settings-agents-form">
474479
<div className="settings-agents-description-row">
475480
<label className="settings-label" htmlFor="settings-agent-create-name">
@@ -596,10 +601,10 @@ export function SettingsAgentsSection({
596601
{createError && <div className="settings-agents-error">{createError}</div>}
597602
</div>
598603

599-
<div className="settings-subsection-title">Configured Agents</div>
600-
<div className="settings-subsection-subtitle">
601-
Manage custom roles and their per-agent config files.
602-
</div>
604+
<SettingsSubsection
605+
title="Configured Agents"
606+
subtitle="Manage custom roles and their per-agent config files."
607+
/>
603608

604609
{settings && settings.agents.length === 0 && !isLoading && (
605610
<div className="settings-help">No custom agents configured yet.</div>
@@ -857,6 +862,6 @@ export function SettingsAgentsSection({
857862
{isLoading && <div className="settings-help">Loading agents settings...</div>}
858863
{openPathError && <div className="settings-agents-error">{openPathError}</div>}
859864
{error && <div className="settings-agents-error">{error}</div>}
860-
</section>
865+
</SettingsSection>
861866
);
862867
}

0 commit comments

Comments
 (0)