{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "title": "Switch",
  "description": "Sliding on/off control whose thumb travels 16 px in 150 ms, mirroring the physical toggle it imitates.",
  "dependencies": [
    "radix-ui",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/switch.tsx",
      "content": "import { Switch as SwitchPrimitive } from \"radix-ui\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\nimport {\n  GooeySwitch,\n  HapticSwitch,\n  SWITCH_ROOT_CLASS,\n  SWITCH_THUMB_CLASS,\n  type SwitchRootProps,\n} from \"@/registry/miseui/ui/switch-effects\"\n\nconst THUMB_TRAVEL_CLASS = \"data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0\"\n\ntype SwitchEffect = \"gooey\" | \"haptic\"\n\ntype SwitchProps = SwitchRootProps & {\n  /** `gooey` stretches the thumb like a drop as it travels; `haptic` dips it the moment it is pressed. */\n  effect?: SwitchEffect\n}\n\nfunction Switch({ effect, className, ...props }: SwitchProps) {\n  if (effect === \"gooey\") return <GooeySwitch className={className} {...props} />\n  if (effect === \"haptic\") return <HapticSwitch className={className} {...props} />\n  return (\n    <SwitchPrimitive.Root data-slot=\"switch\" className={cn(SWITCH_ROOT_CLASS, className)} {...props}>\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        className={cn(\n          SWITCH_THUMB_CLASS,\n          THUMB_TRAVEL_CLASS,\n          \"shadow-xs transition-transform duration-(--duration-fast) ease-(--ease-mise)\",\n        )}\n      />\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { Switch, type SwitchEffect, type SwitchProps }\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/switch-effects.tsx",
      "content": "import type * as React from \"react\"\nimport { useEffect, useId, useState } from \"react\"\nimport { Switch as SwitchPrimitive } from \"radix-ui\"\nimport { motion, useReducedMotion } from \"motion/react\"\nimport { SPRING_DEFAULT, SPRING_PRESS } from \"@/registry/miseui/lib/motion\"\nimport { cn } from \"@/registry/miseui/lib/utils\"\n\n/**\n * Track 36 x 20 px with 2 px padding, thumb 16 px: the thumb travels exactly 16 px\n * (translate-x-4) between the two ends, so the on and off positions are unambiguous.\n */\nconst TRACK_CLASS = \"h-5 w-9 p-0.5\"\nconst THUMB_CLASS = \"size-4\"\nconst THUMB_TRAVEL_PX = 16\n\nconst SWITCH_ROOT_CLASS = [\n  TRACK_CLASS,\n  \"peer inline-flex shrink-0 items-center rounded-full shadow-xs\",\n  \"transition-[background-color,box-shadow] duration-(--duration-fast) ease-(--ease-mise)\",\n  \"outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n  \"data-[state=checked]:bg-primary data-[state=unchecked]:bg-input\",\n  \"disabled:cursor-not-allowed disabled:opacity-50\",\n]\nconst SWITCH_THUMB_CLASS = [\n  THUMB_CLASS,\n  \"pointer-events-none block rounded-full bg-background\",\n  \"dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground\",\n]\n/** The accent carries the \"on\" state; the thumb turns dark on it so it stays readable in both themes. */\nconst EFFECT_TRACK_CLASS = \"data-[state=checked]:bg-mise\"\nconst EFFECT_THUMB_CLASS =\n  \"transition-colors duration-(--duration-fast) ease-(--ease-mise) data-[state=checked]:bg-mise-foreground dark:data-[state=checked]:bg-mise-foreground\"\n\n/** The tail arrives late on the same critically damped curve; the gap between head and tail is the stretch. */\nconst TAIL_SPRING = { ...SPRING_DEFAULT, duration: 0.7 }\nconst GOOEY_BLUR_PX = 1.6\nconst ALPHA_SLOPE = 18\nconst ALPHA_OFFSET = -7\nconst HAPTIC_DIP_SCALE = 0.9\n\ntype SwitchRootProps = React.ComponentProps<typeof SwitchPrimitive.Root>\n\n/** Mirrors Radix's own state, because the thumb's target position has to be known in React. */\nfunction useTrackedChecked({ checked, defaultChecked, onCheckedChange }: SwitchRootProps) {\n  const [uncontrolled, setUncontrolled] = useState(defaultChecked ?? false)\n  return {\n    on: checked ?? uncontrolled,\n    onCheckedChange: (next: boolean) => {\n      setUncontrolled(next)\n      onCheckedChange?.(next)\n    },\n  }\n}\n\nfunction GooeySwitch({ className, checked, defaultChecked, onCheckedChange, ...props }: SwitchRootProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const filterId = `switch-gooey-${useId().replace(/[^a-zA-Z0-9_-]/g, \"\")}`\n  const tracked = useTrackedChecked({ checked, defaultChecked, onCheckedChange })\n  const x = tracked.on ? THUMB_TRAVEL_PX : 0\n\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-effect=\"gooey\"\n      className={cn(SWITCH_ROOT_CLASS, EFFECT_TRACK_CLASS, \"relative\", className)}\n      checked={checked}\n      defaultChecked={defaultChecked}\n      onCheckedChange={tracked.onCheckedChange}\n      {...props}\n    >\n      {reduceMotion ? null : <GooeyFilter id={filterId} />}\n      {/* The filter spans the whole track: a filter region is measured from its element and would clip a travelling thumb. */}\n      <span className=\"absolute inset-0.5 block\" style={reduceMotion ? undefined : { filter: `url(#${filterId})` }}>\n        {reduceMotion ? null : (\n          <motion.span\n            aria-hidden=\"true\"\n            data-slot=\"switch-tail\"\n            data-state={tracked.on ? \"checked\" : \"unchecked\"}\n            className={cn(SWITCH_THUMB_CLASS, EFFECT_THUMB_CLASS, \"absolute top-0.5 left-0.5 size-3\")}\n            initial={false}\n            animate={{ x }}\n            transition={TAIL_SPRING}\n          />\n        )}\n        <SwitchPrimitive.Thumb asChild data-slot=\"switch-thumb\">\n          <motion.span\n            className={cn(SWITCH_THUMB_CLASS, EFFECT_THUMB_CLASS, \"absolute top-0 left-0\")}\n            initial={false}\n            animate={{ x }}\n            transition={reduceMotion ? { duration: 0 } : SPRING_DEFAULT}\n          />\n        </SwitchPrimitive.Thumb>\n      </span>\n    </SwitchPrimitive.Root>\n  )\n}\n\nfunction GooeyFilter({ id }: { id: string }) {\n  return (\n    <svg aria-hidden=\"true\" className=\"absolute size-0\">\n      <defs>\n        <filter id={id} x=\"-25%\" y=\"-50%\" width=\"150%\" height=\"200%\" colorInterpolationFilters=\"sRGB\">\n          <feGaussianBlur in=\"SourceGraphic\" stdDeviation={GOOEY_BLUR_PX} result=\"blur\" />\n          <feColorMatrix\n            in=\"blur\"\n            type=\"matrix\"\n            values={`1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 ${ALPHA_SLOPE} ${ALPHA_OFFSET}`}\n          />\n        </filter>\n      </defs>\n    </svg>\n  )\n}\n\nfunction usePressed() {\n  const [pressed, setPressed] = useState(false)\n  useEffect(() => {\n    if (!pressed) return undefined\n    // The pointer can lift outside the control; listen on window so the thumb never stays dipped.\n    const release = () => setPressed(false)\n    window.addEventListener(\"pointerup\", release)\n    window.addEventListener(\"pointercancel\", release)\n    return () => {\n      window.removeEventListener(\"pointerup\", release)\n      window.removeEventListener(\"pointercancel\", release)\n    }\n  }, [pressed])\n  return [pressed, setPressed] as const\n}\n\nfunction HapticSwitch({\n  className,\n  checked,\n  defaultChecked,\n  onCheckedChange,\n  disabled,\n  onPointerDown,\n  onKeyDown,\n  onKeyUp,\n  onBlur,\n  ...props\n}: SwitchRootProps) {\n  const reduceMotion = useReducedMotion() ?? false\n  const tracked = useTrackedChecked({ checked, defaultChecked, onCheckedChange })\n  const [pressed, setPressed] = usePressed()\n  const dipped = pressed && !reduceMotion\n\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"switch\"\n      data-effect=\"haptic\"\n      data-pressed={pressed ? \"true\" : undefined}\n      className={cn(\n        SWITCH_ROOT_CLASS,\n        EFFECT_TRACK_CLASS,\n        \"data-[pressed=true]:ring-4 data-[pressed=true]:ring-mise/25\",\n        className,\n      )}\n      checked={checked}\n      defaultChecked={defaultChecked}\n      onCheckedChange={tracked.onCheckedChange}\n      disabled={disabled}\n      onPointerDown={(event) => {\n        onPointerDown?.(event)\n        if (!disabled) setPressed(true)\n      }}\n      onKeyDown={(event) => {\n        onKeyDown?.(event)\n        if (event.key === \" \" || event.key === \"Enter\") setPressed(true)\n      }}\n      onKeyUp={(event) => {\n        onKeyUp?.(event)\n        setPressed(false)\n      }}\n      onBlur={(event) => {\n        onBlur?.(event)\n        setPressed(false)\n      }}\n      {...props}\n    >\n      <SwitchPrimitive.Thumb asChild data-slot=\"switch-thumb\">\n        <motion.span\n          className={cn(SWITCH_THUMB_CLASS, EFFECT_THUMB_CLASS, \"shadow-xs\")}\n          initial={false}\n          animate={{ x: tracked.on ? THUMB_TRAVEL_PX : 0, scale: dipped ? HAPTIC_DIP_SCALE : 1 }}\n          transition={\n            reduceMotion ? { duration: 0 } : { x: SPRING_DEFAULT, scale: dipped ? SPRING_PRESS : SPRING_DEFAULT }\n          }\n        />\n      </SwitchPrimitive.Thumb>\n    </SwitchPrimitive.Root>\n  )\n}\n\nexport { GooeySwitch, HapticSwitch, SWITCH_ROOT_CLASS, SWITCH_THUMB_CLASS, type SwitchRootProps }\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "forms"
  ],
  "type": "registry:ui"
}