69 lines
1.7 KiB
TypeScript
69 lines
1.7 KiB
TypeScript
// components/ui/Switch.tsx
|
|
'use client';
|
|
|
|
import * as React from 'react';
|
|
import clsx from 'clsx';
|
|
|
|
type SwitchProps = {
|
|
id?: string;
|
|
name?: string;
|
|
checked: boolean;
|
|
onChange: (checked: boolean) => void;
|
|
ariaLabel?: string;
|
|
ariaLabelledBy?: string;
|
|
ariaDescribedBy?: string;
|
|
disabled?: boolean;
|
|
className?: string;
|
|
};
|
|
|
|
export default function Switch({
|
|
id,
|
|
name,
|
|
checked,
|
|
onChange,
|
|
ariaLabel,
|
|
ariaLabelledBy,
|
|
ariaDescribedBy,
|
|
disabled = false,
|
|
className,
|
|
}: SwitchProps) {
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
if (disabled) return;
|
|
onChange(e.target.checked);
|
|
};
|
|
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'group relative inline-flex w-11 shrink-0 rounded-full bg-gray-200 p-0.5',
|
|
'inset-ring inset-ring-gray-900/5 outline-offset-2 outline-indigo-600',
|
|
'transition-colors duration-200 ease-in-out',
|
|
'has-checked:bg-indigo-600 has-focus-visible:outline-2',
|
|
'dark:bg-white/5 dark:inset-ring-white/10 dark:outline-indigo-500 dark:has-checked:bg-indigo-500',
|
|
disabled ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer',
|
|
className,
|
|
)}
|
|
>
|
|
<span
|
|
className={clsx(
|
|
'size-5 rounded-full bg-white shadow-xs ring-1 ring-gray-900/5',
|
|
'transition-transform duration-200 ease-in-out',
|
|
'group-has-checked:translate-x-5',
|
|
)}
|
|
/>
|
|
<input
|
|
id={id}
|
|
name={name}
|
|
type="checkbox"
|
|
className="absolute inset-0 appearance-none focus:outline-hidden"
|
|
aria-label={ariaLabel}
|
|
aria-labelledby={ariaLabelledBy}
|
|
aria-describedby={ariaDescribedBy}
|
|
checked={checked}
|
|
onChange={handleChange}
|
|
disabled={disabled}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|