52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
/**
|
|
* @file Switch.tsx
|
|
* @description The app's ONE on/off switch (`role="switch"`): an accent track
|
|
* with a white knob, the same size everywhere. Settings rows, webhook targets
|
|
* and the AI Tools list all draw this — do not hand-roll another one; a
|
|
* yes/no choice inside a form or list is a `Checkbox` instead.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
export function Switch({
|
|
checked,
|
|
onChange,
|
|
label,
|
|
title,
|
|
disabled = false,
|
|
}: {
|
|
checked: boolean;
|
|
onChange: (v: boolean) => void;
|
|
/** Accessible name — a switch usually sits beside its visible label. */
|
|
label?: string;
|
|
title?: string;
|
|
disabled?: boolean;
|
|
}) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
role="switch"
|
|
aria-checked={checked}
|
|
aria-label={label}
|
|
title={title}
|
|
disabled={disabled}
|
|
onClick={() => onChange(!checked)}
|
|
// On: the solid accent (white knob clears 3:1 in every theme). Off: a
|
|
// border-light track with an inset fg-muted/80 ring (≥3:1 in every theme), so the control's edge
|
|
// is visible on any surface (surface-4 alone was ~1:1 on a light card).
|
|
// Hover darkens the track (enabled only), so it reads as clickable.
|
|
className={`relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-1 focus-visible:ring-offset-surface-0 disabled:cursor-not-allowed disabled:opacity-40 ${
|
|
checked
|
|
? "bg-accent-solid enabled:hover:bg-accent-solid-hover"
|
|
: "bg-border-light ring-1 ring-inset ring-fg-muted/80 enabled:hover:ring-fg-secondary"
|
|
}`}
|
|
>
|
|
<span
|
|
aria-hidden
|
|
className={`inline-block h-4 w-4 rounded-full bg-white shadow-sm ring-1 ring-black/10 transition-transform duration-200 ease-out ${
|
|
checked ? "translate-x-[18px]" : "translate-x-0.5"
|
|
}`}
|
|
/>
|
|
</button>
|
|
);
|
|
}
|