Spaces:
Running
Running
File size: 2,338 Bytes
5f3e9f5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | import { useId } from 'react'
interface ToggleProps {
label: string
description?: string
checked: boolean
onChange: (v: boolean) => void
disabled?: boolean
}
/**
* Brand-colored switch. Implemented as a real `<input type="checkbox">`
* (visually hidden) wrapped in a `<label>` so screen readers, keyboard
* navigation, and form autofill behave correctly. The visible "track"
* is decorative and tied to the input via `aria-hidden`.
*/
export default function Toggle({ label, description, checked, onChange, disabled }: ToggleProps) {
const inputId = useId()
const descId = description ? `${inputId}-desc` : undefined
return (
<label
htmlFor={inputId}
className={
'group flex items-start gap-3 ' + (disabled ? 'cursor-not-allowed' : 'cursor-pointer')
}
>
<span className="relative mt-0.5 inline-flex">
<input
id={inputId}
type="checkbox"
role="switch"
checked={checked}
disabled={disabled}
aria-describedby={descId}
onChange={(e) => onChange(e.target.checked)}
// Visually hidden but still focusable + reachable by AT.
className="peer absolute inset-0 h-full w-full cursor-inherit opacity-0 disabled:cursor-not-allowed"
/>
<span
aria-hidden="true"
className={
'inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-brand-500 ' +
(checked
? 'bg-brand-500'
: 'bg-slate-200 dark:bg-white/10') +
(disabled ? ' opacity-50' : '')
}
>
<span
className={
'inline-block h-4 w-4 rounded-full bg-white shadow-glass transition-transform ' +
(checked ? 'translate-x-4' : 'translate-x-0.5')
}
/>
</span>
</span>
<span className="min-w-0">
<span className="block text-sm font-medium text-slate-800 dark:text-slate-100">{label}</span>
{description && (
<span id={descId} className="block text-xs text-slate-500 dark:text-slate-400">
{description}
</span>
)}
</span>
</label>
)
}
|