{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "type": "registry:ui",
  "title": "Switch",
  "description": "A tactile toggle for settings that take effect immediately.",
  "categories": [
    "inputs"
  ],
  "dependencies": [
    "@radix-ui/react-switch",
    "motion"
  ],
  "registryDependencies": [
    "https://irisui.dev/r/foundation.json",
    "https://irisui.dev/r/utils.json",
    "https://irisui.dev/r/motion-tokens.json"
  ],
  "files": [
    {
      "path": "src/registry/components/switch/switch.tsx",
      "content": "\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState } from \"react\";\nimport type { ComponentPropsWithoutRef, ElementRef } from \"react\";\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport { animate, motion, useMotionValue, useReducedMotion } from \"motion/react\";\nimport type { Transition } from \"motion/react\";\nimport { cn } from \"../lib/utils\";\nimport { motionTokens } from \"../lib/motion-tokens\";\n\nexport interface SwitchProps extends ComponentPropsWithoutRef<typeof SwitchPrimitive.Root> {\n  label?: string;\n}\n\n/** Track inner width (42 - 6 padding) minus the 18px thumb; keep in sync with the track and thumb classes below. */\nconst size = 18;\nconst travel = 18;\n/** How far the thumb widens toward the other side while pressed. */\nconst stretch = 5;\n/** Critically damped: the thumb lands on its end without overshooting the state it reports. */\nconst glide: Transition = { type: \"spring\", visualDuration: 0.3, bounce: 0 };\n\nexport const Switch = forwardRef<ElementRef<typeof SwitchPrimitive.Root>, SwitchProps>(function Switch(\n  {\n    label,\n    className,\n    checked,\n    defaultChecked,\n    onCheckedChange,\n    onPointerDown,\n    onPointerUp,\n    onPointerLeave,\n    onPointerCancel,\n    onKeyDown,\n    onKeyUp,\n    onBlur,\n    ...props\n  },\n  ref,\n) {\n  const reduceMotion = useReducedMotion();\n  const [internal, setInternal] = useState(defaultChecked ?? false);\n  const [pressed, setPressed] = useState(false);\n  const on = checked ?? internal;\n  // A brief stretch along the travel, so the thumb reads as moving mass rather than a sliding dot.\n  const scaleX = useMotionValue(1);\n  const shown = useRef(on);\n  // A pointer or Space press already stretched the thumb, so its release should not add a second stretch on top.\n  const releasedAt = useRef(-Infinity);\n  useEffect(() => {\n    if (shown.current === on) return;\n    shown.current = on;\n    const fromPress = performance.now() - releasedAt.current < 250;\n    if (reduceMotion || fromPress) return;\n    const controls = animate(scaleX, [1, 1.16, 1], {\n      duration: 0.34,\n      times: [0, 0.4, 1],\n      ease: [\"easeOut\", \"easeInOut\"],\n    });\n    return () => controls.stop();\n  }, [on, reduceMotion, scaleX]);\n  const extra = pressed && !reduceMotion && !props.disabled ? stretch : 0;\n\n  return (\n    <SwitchPrimitive.Root\n      {...props}\n      ref={ref}\n      checked={on}\n      onCheckedChange={(next) => {\n        if (checked === undefined) setInternal(next);\n        onCheckedChange?.(next);\n      }}\n      onPointerDown={(event) => {\n        onPointerDown?.(event);\n        if (event.button === 0) setPressed(true);\n      }}\n      onPointerUp={(event) => {\n        onPointerUp?.(event);\n        if (pressed && !props.disabled) releasedAt.current = performance.now();\n        setPressed(false);\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        setPressed(false);\n      }}\n      onPointerCancel={(event) => {\n        onPointerCancel?.(event);\n        setPressed(false);\n      }}\n      onKeyDown={(event) => {\n        onKeyDown?.(event);\n        if (event.key === \" \") setPressed(true);\n      }}\n      onKeyUp={(event) => {\n        onKeyUp?.(event);\n        if (pressed && !props.disabled) releasedAt.current = performance.now();\n        setPressed(false);\n      }}\n      onBlur={(event) => {\n        onBlur?.(event);\n        setPressed(false);\n      }}\n      className={cn(\n        \"group/switch inline-flex min-h-control-md cursor-pointer items-center gap-3 border-0 bg-transparent p-0 text-sm/[inherit] text-ink [-webkit-tap-highlight-color:transparent] focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50\",\n        className,\n      )}\n      aria-label={props[\"aria-label\"] ?? label}\n    >\n      {/* Fixed box: the thumb moves inside it with transforms, so nothing around the switch shifts. The track crossfades between the two fills. */}\n      <span className=\"relative isolate box-border flex h-6 w-[42px] flex-none items-center rounded-full bg-control-track p-[3px] transition-[background-color] duration-fast ease-standard group-hover/switch:group-enabled/switch:group-data-[state=unchecked]/switch:bg-control-track-hover motion-reduce:transition-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:bg-control-on before:opacity-0 before:transition-opacity before:duration-standard before:ease-standard before:content-[''] group-data-[state=checked]/switch:before:opacity-100 motion-reduce:before:transition-none\">\n        {/* The thumb stretches like a held finger and keeps its far edge anchored, then travels on a spring. */}\n        <motion.span\n          className=\"size-[18px] flex-none rounded-full bg-control-thumb shadow-thumb transition-[background-color] duration-standard ease-standard will-change-transform group-data-[state=checked]/switch:bg-control-thumb-on motion-reduce:transition-none\"\n          style={{ scaleX }}\n          initial={false}\n          animate={{ x: on ? travel - extra : 0, width: size + extra }}\n          transition={reduceMotion ? { duration: 0 } : { x: glide, width: motionTokens.spring.snappy }}\n        />\n      </span>\n      {label ? <span className=\"leading-body\">{label}</span> : null}\n    </SwitchPrimitive.Root>\n  );\n});\n\nSwitch.displayName = \"Switch\";\n\nexport default Switch;\n",
      "type": "registry:component",
      "target": "@components/iris/switch/switch.tsx"
    }
  ],
  "docs": "Docs and live preview: https://irisui.dev/components/switch",
  "meta": {
    "kind": "component",
    "group": "Inputs",
    "export": "Switch",
    "tags": [
      "toggle",
      "settings"
    ],
    "docs": "https://irisui.dev/components/switch"
  }
}
