"use client"; import { animate, motion, MotionConfig, useReducedMotion } from "motion/react"; import { useEffect, useId, useRef, useState } from "react"; import { cn } from "@/lib/utils"; // Heavy, deliberate thumb — high mass keeps the travel weighty without wobble. const THUMB_SPRING = { type: "spring", stiffness: 800, damping: 80, mass: 4 } as const; export interface SwitchProps { checked: boolean; onCheckedChange: (checked: boolean) => void; disabled?: boolean; label?: string; className?: string; } export function Switch({ checked, onCheckedChange, disabled, label, className }: SwitchProps) { const id = useId(); const thumbRef = useRef(null); const reduce = useReducedMotion(); const [isPressed, setIsPressed] = useState(false); const [isPointer, setIsPointer] = useState(false); // Disabled shake feedback when pressed. useEffect(() => { if (!thumbRef.current || reduce) return; if (disabled && isPressed) { animate( thumbRef.current, { x: [0, -2, 2, -1, 0] }, { delay: 0.2, duration: 0.6 }, ); } }, [disabled, isPressed, reduce]); const squish = !disabled && isPointer && isPressed && !reduce; return ( !disabled && onCheckedChange(!checked)} onPointerDown={(e) => { setIsPressed(true); setIsPointer(e.type.startsWith("pointer")); }} onPointerUp={() => setIsPressed(false)} onPointerLeave={() => setIsPressed(false)} initial={false} data-state={checked ? "checked" : "unchecked"} className={cn( "group peer inline-flex h-7 w-12 shrink-0 cursor-pointer items-center px-1 rounded-full outline-none transition-colors duration-200", "focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", "disabled:cursor-not-allowed disabled:opacity-60", checked ? "justify-end bg-primary" : "justify-start bg-muted-foreground/60", )} > {/* Stretch toward the destination while active. */}
{label ? ( ) : null} ); }