refactor(ui): shared form primitives (Switch, Section, SettingRow, HintLabel)
components/ui/form.tsx replaces the per-panel copies: - Switch — was duplicated in SettingsPanel (Switch) + ConfigPanel (Toggle) + Sidebar (inline labeled toggle). - Section (with a card variant) — was in SettingsPanel + Stats (plain) and AdminUsers (glass card). - SettingRow + HintLabel — the label+hint+control row was identical in SettingsPanel + Stats. No visual change intended (Sidebar's toggle gains the standard knob shadow).
This commit is contained in:
parent
5fcd64c1e1
commit
53c75f69e2
6 changed files with 125 additions and 147 deletions
|
|
@ -8,6 +8,7 @@ import { LS, usePersistedState } from "../lib/storage";
|
|||
import Avatar from "./Avatar";
|
||||
import { notify, type NotifSettings } from "../lib/notifications";
|
||||
import Tooltip from "./Tooltip";
|
||||
import { Section, SettingRow, Switch } from "./ui/form";
|
||||
import { useConfirm } from "./ConfirmProvider";
|
||||
|
||||
// The Settings page edits server-persisted preferences as a draft: changes apply locally
|
||||
|
|
@ -137,56 +138,6 @@ function PrefsSaveBar({ prefs }: { prefs: PrefsController }) {
|
|||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<div className="text-xs uppercase tracking-wide text-muted mb-2">{title}</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Row({
|
||||
label,
|
||||
hint,
|
||||
children,
|
||||
}: {
|
||||
label: string;
|
||||
hint?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3 py-1.5 text-sm">
|
||||
<Tooltip hint={hint ?? ""}>
|
||||
<span
|
||||
className={
|
||||
hint ? "underline decoration-dotted decoration-muted/40 underline-offset-4 cursor-help" : ""
|
||||
}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Switch({ checked, onChange }: { checked: boolean; onChange: (v: boolean) => void }) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(!checked)}
|
||||
className={`w-9 h-5 rounded-full transition relative shrink-0 ${checked ? "bg-accent" : "bg-border"}`}
|
||||
>
|
||||
<span
|
||||
className={`absolute top-0.5 w-4 h-4 rounded-full bg-white shadow transition-all ${
|
||||
checked ? "left-[18px]" : "left-0.5"
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function Appearance({ prefs }: { prefs: PrefsController }) {
|
||||
const { t } = useTranslation();
|
||||
// Controlled by App's prefs draft: each change applies locally for preview but is only
|
||||
|
|
@ -212,27 +163,27 @@ function Appearance({ prefs }: { prefs: PrefsController }) {
|
|||
</Section>
|
||||
|
||||
<Section title={t("settings.appearance.display")}>
|
||||
<Row label={t("settings.appearance.darkMode")}>
|
||||
<SettingRow label={t("settings.appearance.darkMode")}>
|
||||
<Switch
|
||||
checked={theme.mode === "dark"}
|
||||
onChange={(v) => setTheme({ ...theme, mode: v ? "dark" : "light" })}
|
||||
/>
|
||||
</Row>
|
||||
<Row label={t("settings.appearance.listView")} hint={t("settings.appearance.listViewHint")}>
|
||||
</SettingRow>
|
||||
<SettingRow label={t("settings.appearance.listView")} hint={t("settings.appearance.listViewHint")}>
|
||||
<Switch checked={view === "list"} onChange={(v) => setView(v ? "list" : "grid")} />
|
||||
</Row>
|
||||
<Row
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("settings.appearance.performanceMode")}
|
||||
hint={t("settings.appearance.performanceModeHint")}
|
||||
>
|
||||
<Switch checked={perf} onChange={setPerf} />
|
||||
</Row>
|
||||
<Row
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("settings.appearance.showHints")}
|
||||
hint={t("settings.appearance.showHintsHint")}
|
||||
>
|
||||
<Switch checked={hints} onChange={setHints} />
|
||||
</Row>
|
||||
</SettingRow>
|
||||
</Section>
|
||||
|
||||
<Section title={t("settings.appearance.textSize")}>
|
||||
|
|
@ -261,18 +212,18 @@ function Notifications({ prefs }: { prefs: PrefsController }) {
|
|||
|
||||
return (
|
||||
<Section title={t("settings.notifications.title")}>
|
||||
<Row
|
||||
<SettingRow
|
||||
label={t("settings.notifications.sound")}
|
||||
hint={t("settings.notifications.soundHint")}
|
||||
>
|
||||
<Switch checked={cfg.sound} onChange={(v) => update({ sound: v })} />
|
||||
</Row>
|
||||
<Row
|
||||
</SettingRow>
|
||||
<SettingRow
|
||||
label={t("settings.notifications.autoDismiss")}
|
||||
hint={t("settings.notifications.autoDismissHint")}
|
||||
>
|
||||
<Switch checked={autoOn} onChange={(v) => update({ durationMs: v ? 6000 : 0 })} />
|
||||
</Row>
|
||||
</SettingRow>
|
||||
{autoOn && (
|
||||
<div className="py-1.5 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue