File size: 1,892 Bytes
88c4c60 | 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 69 70 | "use client";
import { cn } from "@/shared/utils/cn";
export default function Toggle({
checked = false,
onChange,
label,
description,
disabled = false,
size = "md",
className,
}) {
const sizes = {
sm: { track: "w-8 h-4", thumb: "size-3", translate: "translate-x-4" },
md: { track: "w-11 h-6", thumb: "size-5", translate: "translate-x-5" },
lg: { track: "w-14 h-7", thumb: "size-6", translate: "translate-x-7" },
};
const handleClick = () => {
if (!disabled && onChange) onChange(!checked);
};
return (
<div
className={cn(
"flex items-center gap-3",
disabled && "opacity-50 cursor-not-allowed",
className
)}
>
<button
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={handleClick}
className={cn(
"relative inline-flex shrink-0 cursor-pointer rounded-full",
"transition-colors duration-200 ease-in-out",
"focus:outline-none focus:ring-2 focus:ring-brand-500/30",
checked ? "bg-brand-500" : "bg-surface-3",
sizes[size].track,
disabled && "cursor-not-allowed"
)}
>
<span
className={cn(
"pointer-events-none inline-block rounded-full bg-white shadow-sm",
"transform transition duration-200 ease-in-out",
checked ? sizes[size].translate : "translate-x-0.5",
sizes[size].thumb,
"mt-0.5"
)}
/>
</button>
{(label || description) && (
<div className="flex flex-col">
{label && (
<span className="text-sm font-medium text-text-main">{label}</span>
)}
{description && (
<span className="text-xs text-text-muted">{description}</span>
)}
</div>
)}
</div>
);
}
|