feat(ui): restyle ToggleSwitch to shadcn capsule form, status-green on-state

Registry radix-luma Switch form (border-2 capsule track, oval thumb,
bg-input off-state) on the existing button API — no Radix dep, no
call-site changes. On-state uses var(--status-task-done) per user
ruling instead of bg-primary; deviation documented in the component.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
scotttong 2026-07-06 18:41:11 -07:00
parent e2c6f4b4b7
commit 3da1bbcc5a
1 changed files with 17 additions and 14 deletions

View File

@ -8,6 +8,14 @@ export interface ToggleSwitchProps
size?: "default" | "lg"; size?: "default" | "lg";
} }
/*
* Custom API (checked/onCheckedChange button, no Radix dep) with the shadcn
* radix-luma Switch's capsule form: border-2 track, oval thumb, bg-input
* off-state. Deliberate deviation from the registry: the on-state is the
* status-system green (var(--status-task-done)), not bg-primary user
* ruling, DECISION-SHEET "toggle form" note. Do not swap for the registry
* Switch without revisiting that ruling (C7).
*/
export const ToggleSwitch = React.forwardRef< export const ToggleSwitch = React.forwardRef<
HTMLButtonElement, HTMLButtonElement,
ToggleSwitchProps ToggleSwitchProps
@ -27,12 +35,13 @@ export const ToggleSwitch = React.forwardRef<
data-slot="toggle" data-slot="toggle"
disabled={disabled} disabled={disabled}
className={cn( className={cn(
"relative inline-flex shrink-0 items-center rounded-full transition-colors", "relative inline-flex shrink-0 items-center rounded-full border-2 transition-all outline-none",
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30",
"disabled:cursor-not-allowed disabled:opacity-50", "disabled:cursor-not-allowed disabled:opacity-50",
// Track: larger on mobile (<640px), standard on desktop isLg ? "h-6 w-12" : "h-5 w-11",
isLg ? "h-7 w-12 sm:h-6 sm:w-11" : "h-6 w-10 sm:h-5 sm:w-9", checked
checked ? "bg-green-600" : "bg-muted", ? "border-(--status-task-done) bg-(--status-task-done)"
: "border-transparent bg-input/90",
className, className,
)} )}
onClick={() => onCheckedChange(!checked)} onClick={() => onCheckedChange(!checked)}
@ -40,15 +49,9 @@ export const ToggleSwitch = React.forwardRef<
> >
<span <span
className={cn( className={cn(
"pointer-events-none inline-block rounded-full bg-white shadow-sm transition-transform", "pointer-events-none inline-block rounded-full bg-background shadow-sm transition-transform not-dark:bg-clip-padding dark:bg-foreground",
// Thumb isLg ? "h-5 w-7" : "h-4 w-6",
isLg ? "size-5.5 sm:size-5" : "size-4.5 sm:size-3.5", checked ? "translate-x-4" : "translate-x-0",
// Slide position
checked
? isLg
? "translate-x-5 sm:translate-x-5"
: "translate-x-5 sm:translate-x-4.5"
: "translate-x-0.5",
)} )}
/> />
</button> </button>