{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"toggle-switch","type":"registry:ui","title":"Toggle Switch","description":"A switch whose knob rides a spring and stretches with its own speed.","dependencies":["motion"],"registryDependencies":["https://ui.mekaigames.com/r/theme.json"],"files":[{"path":"components/ui/toggle-switch.tsx","type":"registry:ui","content":"\"use client\";\n\nimport { AnimatePresence, motion, useReducedMotion, useSpring, useTransform, useVelocity } from \"motion/react\";\nimport { useEffect, useId, useState, type ChangeEvent, type ReactNode } from \"react\";\n\ntype ToggleSize = \"sm\" | \"md\" | \"lg\";\n\ntype ToggleSwitchProps = {\n  checked?: boolean;\n  defaultChecked?: boolean;\n  onCheckedChange?: (checked: boolean) => void;\n  label?: ReactNode;\n  description?: ReactNode;\n  /** Puts the label before the switch, as in a settings row. */\n  labelPosition?: \"start\" | \"end\";\n  size?: ToggleSize;\n  color?: string;\n  /** Shows a check and a cross inside the knob. */\n  showIcons?: boolean;\n  disabled?: boolean;\n  name?: string;\n  value?: string;\n  id?: string;\n  \"aria-label\"?: string;\n  className?: string;\n};\n\nconst SIZES: Record<ToggleSize, { w: number; h: number; pad: number; icon: number }> = {\n  sm: { w: 36, h: 20, pad: 2, icon: 9 },\n  md: { w: 46, h: 26, pad: 3, icon: 11 },\n  lg: { w: 58, h: 32, pad: 3, icon: 13 },\n};\n\nconst TRACK_OFF = \"#2d2824\";\n\nexport function ToggleSwitch({\n  checked,\n  defaultChecked = false,\n  onCheckedChange,\n  label,\n  description,\n  labelPosition = \"end\",\n  size = \"md\",\n  color = \"#ff784f\",\n  showIcons = false,\n  disabled = false,\n  name,\n  value = \"on\",\n  id,\n  \"aria-label\": ariaLabel,\n  className = \"\",\n}: ToggleSwitchProps) {\n  const autoId = useId();\n  const inputId = id ?? `${autoId}-switch`;\n  const descId = `${inputId}-description`;\n  const reduce = useReducedMotion();\n  const [inner, setInner] = useState(defaultChecked);\n  const on = checked ?? inner;\n\n  const s = SIZES[size];\n  const knob = s.h - s.pad * 2;\n  const travel = s.w - s.pad * 2 - knob;\n\n  // The knob rides a spring; its speed stretches it, so it squashes back as it lands.\n  const x = useSpring(on ? travel : 0, { stiffness: 520, damping: 30, mass: 0.7 });\n  const speed = useVelocity(x);\n  const vMax = travel * 11;\n  const stretch = useTransform(speed, [-vMax, 0, vMax], [1.42, 1, 1.42]);\n  const squash = useTransform(speed, [-vMax, 0, vMax], [0.86, 1, 0.86]);\n\n  useEffect(() => {\n    const target = on ? travel : 0;\n    if (reduce) x.jump(target);\n    else x.set(target);\n  }, [on, travel, reduce, x]);\n\n  function handleChange(e: ChangeEvent<HTMLInputElement>) {\n    const next = e.target.checked;\n    if (checked === undefined) setInner(next);\n    onCheckedChange?.(next);\n  }\n\n  const control = (\n    <span className={`relative inline-flex shrink-0 ${description ? \"mt-0.5\" : \"\"}`} style={{ width: s.w, height: s.h }}>\n      <input\n        id={inputId}\n        type=\"checkbox\"\n        role=\"switch\"\n        name={name}\n        value={value}\n        checked={on}\n        disabled={disabled}\n        onChange={handleChange}\n        aria-label={label ? undefined : ariaLabel}\n        aria-describedby={description ? descId : undefined}\n        className=\"peer absolute inset-0 z-10 m-0 size-full cursor-pointer appearance-none rounded-full opacity-0 disabled:cursor-not-allowed\"\n      />\n      <motion.span\n        aria-hidden=\"true\"\n        className=\"absolute inset-0 rounded-full shadow-[inset_0_1px_3px_rgb(0_0_0/0.45)] ring-1 ring-inset ring-white/10 peer-hover:ring-white/20 peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ember\"\n        initial={false}\n        animate={{ backgroundColor: on ? color : TRACK_OFF }}\n        transition={{ duration: reduce ? 0 : 0.22 }}\n      />\n      <motion.span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute grid place-items-center\"\n        style={{ x, left: s.pad, top: s.pad, width: knob, height: knob }}\n      >\n        <motion.span\n          className=\"absolute inset-0 rounded-full bg-bone shadow-[0_2px_6px_rgb(0_0_0/0.45)]\"\n          style={{ scaleX: stretch, scaleY: squash }}\n        />\n        {showIcons ? (\n          <AnimatePresence initial={false} mode=\"wait\">\n            <motion.svg\n              key={on ? \"on\" : \"off\"}\n              viewBox=\"0 0 12 12\"\n              width={s.icon}\n              height={s.icon}\n              fill=\"none\"\n              stroke={on ? color : \"#857c71\"}\n              strokeWidth={2}\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              className=\"relative\"\n              initial={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.3, rotate: on ? -60 : 60 }}\n              animate={{ opacity: 1, scale: 1, rotate: 0 }}\n              exit={reduce ? { opacity: 0 } : { opacity: 0, scale: 0.3 }}\n              transition={{ duration: reduce ? 0 : 0.16 }}\n            >\n              {on ? <path d=\"M2.5 6.4 5 8.8l4.5-5.3\" /> : <path d=\"M3.2 3.2l5.6 5.6M8.8 3.2 3.2 8.8\" />}\n            </motion.svg>\n          </AnimatePresence>\n        ) : null}\n      </motion.span>\n    </span>\n  );\n\n  if (!label && !description) {\n    return <span className={`inline-flex ${disabled ? \"opacity-50\" : \"\"} ${className}`}>{control}</span>;\n  }\n\n  return (\n    <div\n      className={`inline-flex gap-3 ${description ? \"items-start\" : \"items-center\"} ${\n        labelPosition === \"start\" ? \"flex-row-reverse justify-between\" : \"\"\n      } ${disabled ? \"opacity-50\" : \"\"} ${className}`}\n    >\n      {control}\n      <span className=\"grid min-w-0 gap-0.5\">\n        {label ? (\n          <label\n            htmlFor={inputId}\n            className={`select-none text-sm font-medium text-bone ${disabled ? \"cursor-not-allowed\" : \"cursor-pointer\"}`}\n          >\n            {label}\n          </label>\n        ) : null}\n        {description ? (\n          <span id={descId} className=\"text-xs leading-relaxed text-bone-dim\">\n            {description}\n          </span>\n        ) : null}\n      </span>\n    </div>\n  );\n}\n"}]}