77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
|
|
import * as React from "react"
|
||
|
|
import { cn } from "../../lib/utils"
|
||
|
|
|
||
|
|
export interface SwitchProps
|
||
|
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
|
||
|
|
checked?: boolean;
|
||
|
|
onCheckedChange?: (checked: boolean) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
|
||
|
|
({ className, checked, onCheckedChange, disabled, ...props }, ref) => {
|
||
|
|
const {
|
||
|
|
'aria-label': ariaLabel,
|
||
|
|
'aria-labelledby': ariaLabelledBy,
|
||
|
|
'aria-describedby': ariaDescribedBy,
|
||
|
|
id,
|
||
|
|
...inputProps
|
||
|
|
} = props;
|
||
|
|
|
||
|
|
const handleClick = () => {
|
||
|
|
if (!disabled && onCheckedChange) {
|
||
|
|
onCheckedChange(!checked);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
||
|
|
e.preventDefault();
|
||
|
|
if (!disabled && onCheckedChange) {
|
||
|
|
onCheckedChange(!checked);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
role="switch"
|
||
|
|
id={id}
|
||
|
|
aria-checked={checked}
|
||
|
|
aria-label={ariaLabel}
|
||
|
|
aria-labelledby={ariaLabelledBy}
|
||
|
|
aria-describedby={ariaDescribedBy}
|
||
|
|
tabIndex={disabled ? -1 : 0}
|
||
|
|
onClick={handleClick}
|
||
|
|
onKeyDown={handleKeyDown}
|
||
|
|
className={cn(
|
||
|
|
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent 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",
|
||
|
|
checked ? "bg-primary" : "bg-input",
|
||
|
|
disabled && "cursor-not-allowed opacity-50",
|
||
|
|
className
|
||
|
|
)}
|
||
|
|
>
|
||
|
|
<input
|
||
|
|
type="checkbox"
|
||
|
|
className="sr-only"
|
||
|
|
ref={ref}
|
||
|
|
checked={checked}
|
||
|
|
disabled={disabled}
|
||
|
|
tabIndex={-1}
|
||
|
|
aria-hidden="true"
|
||
|
|
onChange={(e) => onCheckedChange?.(e.target.checked)}
|
||
|
|
{...inputProps}
|
||
|
|
/>
|
||
|
|
<span
|
||
|
|
className={cn(
|
||
|
|
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform",
|
||
|
|
checked ? "translate-x-5" : "translate-x-0"
|
||
|
|
)}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
Switch.displayName = "Switch"
|
||
|
|
|
||
|
|
export { Switch }
|