66 lines
1.7 KiB
TypeScript
66 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* Shared building blocks for /settings rows: SettingRow lays out a
|
|
* label + description with a control on the right, and ToggleSwitch is the
|
|
* pill switch every boolean preference uses. Keeps the notification toggles
|
|
* (push, reminders, like emails) from each carrying a copy of this markup.
|
|
*/
|
|
|
|
export function SettingRow({
|
|
label,
|
|
description,
|
|
control,
|
|
}: {
|
|
label: string;
|
|
description: React.ReactNode;
|
|
control: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<ul className="divide-y divide-border">
|
|
<li className="flex items-center justify-between gap-4 py-3">
|
|
<div className="min-w-0">
|
|
<span className="text-sm text-foreground">{label}</span>
|
|
<p className="text-xs text-muted-foreground mt-0.5">{description}</p>
|
|
</div>
|
|
<div className="shrink-0">{control}</div>
|
|
</li>
|
|
</ul>
|
|
);
|
|
}
|
|
|
|
export function ToggleSwitch({
|
|
checked,
|
|
disabled,
|
|
onToggle,
|
|
label,
|
|
}: {
|
|
checked: boolean;
|
|
disabled?: boolean;
|
|
onToggle: () => void;
|
|
/** Accessible name — the row label isn't programmatically associated. */
|
|
label: string;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
role="switch"
|
|
aria-checked={checked}
|
|
aria-label={label}
|
|
onClick={onToggle}
|
|
disabled={disabled}
|
|
className={[
|
|
"relative inline-flex h-6 w-11 items-center rounded-full transition-colors disabled:opacity-60",
|
|
checked ? "bg-primary" : "bg-muted-foreground/30",
|
|
].join(" ")}
|
|
>
|
|
<span
|
|
className={[
|
|
"inline-block size-5 transform rounded-full bg-white shadow transition-transform",
|
|
checked ? "translate-x-5" : "translate-x-0.5",
|
|
].join(" ")}
|
|
/>
|
|
</button>
|
|
);
|
|
}
|