{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["@radix-ui/react-switch", "motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "switch.tsx",
      "content": "\"use client\";\n\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport { animate, motion, useMotionValue, useTransform } from \"motion/react\";\nimport { forwardRef, useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\nconst componentThemeClassName =\n  \"[--ic-background:#ffffff] [--ic-foreground:#111111] [--ic-primary:#111111] [--ic-secondary:#646b75] [--ic-surface-border:#e9edf2] [--ic-border:#e3e7ec] [--ic-card:#ffffff] [--ic-card-foreground:#111111] [--ic-muted:#f5f7fa] [--ic-muted-foreground:#6d7480] [--ic-accent:#f3f5f8] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--ic-accent-foreground:#111111] [--ic-input:#e3e7ec] [--ic-ring:rgba(17,17,17,0.16)] [--ic-destructive:#dc2626] [--ic-paper:#fcfcfd] [--ic-popover-foreground:#111111] [--ic-brand:#0ea5e9] [--ic-brand-soft:#bae6fd] [--ic-shadow-soft:0_18px_38px_-24px_rgba(15,23,42,0.35)] [--ic-chart-1:oklch(0.52_0.19_254)] [--ic-chart-2:oklch(0.74_0.11_232)] [--ic-chart-3:oklch(0.42_0.16_262)] [--ic-chart-4:oklch(0.84_0.07_228)] [--ic-chart-5:oklch(0.62_0.14_240)] [--color-background:var(--ic-background)] [--color-foreground:var(--ic-foreground)] [--color-primary:var(--ic-primary)] [--color-secondary:var(--ic-secondary)] [--color-border:var(--ic-border)] [--color-card:var(--ic-card)] [--color-card-foreground:var(--ic-card-foreground)] [--color-muted:var(--ic-muted)] [--color-muted-foreground:var(--ic-muted-foreground)] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] [--color-input:var(--ic-input)] [--color-ring:var(--ic-ring)] [--color-destructive:var(--ic-destructive)] [--color-paper:var(--ic-paper)] [--color-popover-foreground:var(--ic-popover-foreground)] [--color-brand:var(--ic-brand)] [--color-brand-soft:var(--ic-brand-soft)] [--color-chart-1:var(--ic-chart-1)] [--color-chart-2:var(--ic-chart-2)] [--color-chart-3:var(--ic-chart-3)] [--color-chart-4:var(--ic-chart-4)] [--color-chart-5:var(--ic-chart-5)] dark:[--ic-background:#111111] dark:[--ic-foreground:#f6f3ec] dark:[--ic-primary:#f6f3ec] dark:[--ic-secondary:#cbc6bb] dark:[--ic-surface-border:#2a2a25] dark:[--ic-border:#2b2a25] dark:[--ic-card:#111111] dark:[--ic-card-foreground:#f6f3ec] dark:[--ic-muted:#171716] dark:[--ic-muted-foreground:#9a958a] dark:[--ic-accent:#1a1a18] [--color-accent:var(--ic-accent)] [--color-accent-foreground:var(--ic-accent-foreground)] dark:[--ic-accent-foreground:#f6f3ec] dark:[--ic-input:#2b2a25] dark:[--ic-ring:rgba(246,243,236,0.18)] dark:[--ic-destructive:#f87171] dark:[--ic-paper:#171716] dark:[--ic-popover-foreground:#f6f3ec] dark:[--ic-brand:#38bdf8] dark:[--ic-brand-soft:#0c4a6e] dark:[--ic-shadow-soft:0_20px_44px_-28px_rgba(0,0,0,0.6)] dark:[--ic-chart-1:oklch(0.68_0.17_250)] dark:[--ic-chart-2:oklch(0.82_0.09_225)] dark:[--ic-chart-3:oklch(0.58_0.15_260)] dark:[--ic-chart-4:oklch(0.75_0.12_235)] dark:[--ic-chart-5:oklch(0.88_0.06_220)]\";\n\nconst TRACK_W = 44;\nconst TRACK_H = 26;\nconst THUMB_SIZE = 20;\nconst THUMB_TRAVEL = TRACK_W - THUMB_SIZE - 4; // px from left to right (margin 2px each side)\n\nconst spring = { type: \"spring\" as const, duration: 0.35, bounce: 0.3 };\nconst springFast = { type: \"spring\" as const, duration: 0.15, bounce: 0 };\nconst springSnap = { type: \"spring\" as const, duration: 0.4, bounce: 0.5 };\n\ninterface SwitchProps\n  extends Omit<\n    React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>,\n    \"asChild\"\n  > {\n  label?: string;\n  /** @default \"right\" */\n  labelSide?: \"left\" | \"right\";\n}\n\nconst Switch = forwardRef<\n  React.ElementRef<typeof SwitchPrimitive.Root>,\n  SwitchProps\n>(\n  (\n    {\n      checked,\n      onCheckedChange,\n      label,\n      labelSide = \"right\",\n      className,\n      disabled,\n      defaultChecked,\n      ...props\n    },\n    ref\n  ) => {\n    const isControlled = checked !== undefined;\n    const [uncontrolledChecked, setUncontrolledChecked] = useState(\n      Boolean(defaultChecked)\n    );\n    const internalChecked = isControlled ? checked : uncontrolledChecked;\n\n    const thumbX = useMotionValue(internalChecked ? THUMB_TRAVEL : 0);\n    const thumbScaleX = useMotionValue(1);\n    const thumbScaleY = useMotionValue(1);\n\n    const fillOpacity = useTransform(thumbX, [0, THUMB_TRAVEL], [0, 1]);\n\n    const prevChecked = useRef(internalChecked);\n    const directionRef = useRef<1 | -1>(1);\n\n    useEffect(() => {\n      if (prevChecked.current === internalChecked) return;\n      prevChecked.current = internalChecked;\n      animate(thumbX, internalChecked ? THUMB_TRAVEL : 0, spring);\n    }, [internalChecked, thumbX]);\n\n    const handlePointerDown = () => {\n      // Flatten thumb on press (squeeze effect)\n      animate(thumbScaleX, 0.82, springFast);\n      animate(thumbScaleY, 1.1, springFast);\n    };\n\n    const handlePointerUp = () => {\n      // Snap back with bounce\n      animate(thumbScaleX, 1, springSnap);\n      animate(thumbScaleY, 1, springSnap);\n    };\n\n    const handleCheckedChange = (next: boolean) => {\n      directionRef.current = next ? 1 : -1;\n\n      if (!isControlled) {\n        setUncontrolledChecked(next);\n      }\n\n      // Squeeze in direction of travel on release\n      animate(thumbScaleX, 1.15, springFast).then(() => {\n        animate(thumbScaleX, 1, springSnap);\n      });\n      animate(thumbScaleY, 0.88, springFast).then(() => {\n        animate(thumbScaleY, 1, springSnap);\n      });\n\n      animate(thumbX, next ? THUMB_TRAVEL : 0, spring);\n      onCheckedChange?.(next);\n    };\n\n    const toggleFromLabel = () => {\n      if (disabled) return;\n      handleCheckedChange(!internalChecked);\n    };\n\n    const switchEl = (\n      <SwitchPrimitive.Root\n        checked={internalChecked}\n        className={cn(\n          componentThemeClassName,\n          \"relative inline-flex shrink-0 cursor-pointer items-center rounded-full\",\n          \"outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background\",\n          \"disabled:cursor-not-allowed disabled:opacity-50\",\n          className\n        )}\n        disabled={disabled}\n        onCheckedChange={handleCheckedChange}\n        onPointerDown={handlePointerDown}\n        onPointerLeave={handlePointerUp}\n        onPointerUp={handlePointerUp}\n        ref={ref}\n        style={{ width: TRACK_W, height: TRACK_H }}\n        {...props}\n      >\n        <span\n          className=\"absolute inset-0 rounded-full\"\n          style={{ backgroundColor: \"var(--ic-accent)\" }}\n        />\n\n        <motion.span\n          className=\"absolute inset-0 rounded-full\"\n          style={{\n            backgroundColor: \"var(--ic-foreground)\",\n            opacity: fillOpacity,\n          }}\n        />\n\n        <SwitchPrimitive.Thumb asChild>\n          <motion.span\n            className=\"pointer-events-none z-10 block rounded-full\"\n            style={{\n              width: THUMB_SIZE,\n              height: THUMB_SIZE,\n              x: thumbX,\n              scaleX: thumbScaleX,\n              scaleY: thumbScaleY,\n              marginLeft: 2,\n              backgroundColor: \"white\",\n              boxShadow:\n                \"0 1px 4px rgba(0,0,0,0.18), 0 0 0 1px rgba(0,0,0,0.06)\",\n            }}\n          />\n        </SwitchPrimitive.Thumb>\n      </SwitchPrimitive.Root>\n    );\n\n    if (!label) {\n      return switchEl;\n    }\n\n    return (\n      <div\n        className={cn(\n          componentThemeClassName,\n          \"flex cursor-pointer select-none items-center gap-2.5\",\n          disabled && \"cursor-not-allowed opacity-50\"\n        )}\n      >\n        {labelSide === \"left\" && (\n          <button\n            className=\"bg-transparent p-0 text-foreground text-sm\"\n            disabled={disabled}\n            onClick={toggleFromLabel}\n            type=\"button\"\n          >\n            {label}\n          </button>\n        )}\n        {switchEl}\n        {labelSide === \"right\" && (\n          <button\n            className=\"bg-transparent p-0 text-foreground text-sm\"\n            disabled={disabled}\n            onClick={toggleFromLabel}\n            type=\"button\"\n          >\n            {label}\n          </button>\n        )}\n      </div>\n    );\n  }\n);\n\nSwitch.displayName = \"Switch\";\n\nexport { Switch, type SwitchProps };\n",
      "type": "registry:ui"
    }
  ],
  "title": "Switch",
  "description": "Animated Radix switch with spring thumb travel, pressure-like thumb squash, foreground track fill, and optional inline label."
}
