feat(ui): replace Switch with TogglePill per design audit (#687)

* chore: ignore local design-system references

Adds frontend/DESIGN.md and .design-bundle/ to .gitignore so the
design-system reference and the audit handoff bundle stay local
to each contributor's machine.

* feat(ui): replace Switch with TogglePill per design audit

Adds a mono-uppercase ON/OFF pill that replaces the sliding Switch
across every call site. The pill uses role="switch" plus aria-checked,
tints success-green when on and neutral card-tone when off, and holds
a stable 60px min-width so the label flip does not shift layout. It
reinforces the tracked-mono uppercase pattern already used for kicker
labels and table column headers, and echoes the UP/DN status language
of the sidebar stack list.

- Add TogglePill component (frontend/src/components/ui/toggle-pill.tsx)
- Replace 17 Switch call sites across 10 files (settings sections,
  SSO providers, scheduled operations, network topology, resources,
  stack auto-heal)
- Remove unused Switch wrapper and radix primitive
- Drop the @radix-ui/react-switch dependency
This commit is contained in:
Anso
2026-04-18 22:15:22 -04:00
committed by GitHub
parent bd94ef9e15
commit 88ec71fcaa
17 changed files with 85 additions and 232 deletions
-33
View File
@@ -1,33 +0,0 @@
'use client';
import * as React from 'react';
import { cn } from '@/lib/utils';
import {
Switch as AnimateSwitch,
SwitchThumb,
type SwitchProps as AnimateSwitchProps,
} from '@/components/animate-ui/primitives/radix/switch';
type SwitchProps = Omit<AnimateSwitchProps, 'children'>;
const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
({ className, ...props }, ref) => (
<AnimateSwitch
ref={ref}
className={cn(
'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className
)}
{...props}
>
<SwitchThumb
className="pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
pressedAnimation={{ scale: 1.15 }}
/>
</AnimateSwitch>
)
);
Switch.displayName = 'Switch';
export { Switch };
@@ -0,0 +1,40 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
interface TogglePillProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'> {
checked: boolean;
onChange: (next: boolean) => void;
}
const TogglePill = React.forwardRef<HTMLButtonElement, TogglePillProps>(
({ checked, onChange, className, disabled, onClick, ...props }, ref) => {
return (
<button
ref={ref}
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={(event) => {
onClick?.(event);
if (event.defaultPrevented) return;
onChange(!checked);
}}
className={cn(
'inline-flex items-center justify-center rounded-md border px-2.5 py-1 font-mono text-xs uppercase tracking-[0.18em] transition-colors min-w-[60px] focus-visible:ring-2 focus-visible:ring-brand/50 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50',
checked
? 'border-success/30 bg-success/10 text-success hover:bg-success/15'
: 'border-card-border bg-card text-stat-subtitle hover:text-stat-value',
className,
)}
{...props}
>
{checked ? 'ON' : 'OFF'}
</button>
);
},
);
TogglePill.displayName = 'TogglePill';
export { TogglePill };
export type { TogglePillProps };