{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "type": "registry:ui",
  "title": "Switch",
  "description": "Native-checkbox-backed Switch primitive with binary on/off semantics, token-backed focus, invalid, disabled, read-only, density, dark mode, RTL-safe thumb placement, and FormField-compatible styling.",
  "dependencies": [
    "motion@^12.43.0"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/switch/switch.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/switch/switch.tsx",
      "content": "import {\n  forwardRef,\n  lazy,\n  Suspense,\n  useRef,\n  useState,\n  type ChangeEventHandler,\n  type InputHTMLAttributes,\n  type MouseEventHandler,\n  type MutableRefObject,\n  type Ref,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\n\nconst SpringThumb = lazy(() => import(\"./switch-spring-thumb\"));\n\nexport type SwitchControlSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface SwitchProps extends Omit<\n  InputHTMLAttributes<HTMLInputElement>,\n  \"checked\" | \"defaultChecked\" | \"onChange\" | \"size\" | \"type\"\n> {\n  \"data-slot\"?: string;\n  checked?: boolean;\n  controlSize?: SwitchControlSize;\n  defaultChecked?: boolean;\n  invalid?: boolean;\n  /** Opt in to a Motion spring on the thumb. Respects reduced motion. */\n  spring?: boolean;\n  onChange?: ChangeEventHandler<HTMLInputElement>;\n  onCheckedChange?: (checked: boolean) => void;\n}\n\nconst switchRootBaseClasses =\n  \"group/switch relative inline-flex shrink-0 items-center align-middle\";\n\nconst switchControlSizeClasses: Record<SwitchControlSize, string> = {\n  sm: \"[--switch-height:calc(var(--dt-density-control)*0.5)] [--switch-thumb-size:calc(var(--switch-height)-0.25rem)] [--switch-width:calc(var(--dt-density-control)*0.9)] h-[var(--switch-height)] w-[var(--switch-width)]\",\n  md: \"[--switch-height:calc(var(--dt-density-control)*0.6)] [--switch-thumb-size:calc(var(--switch-height)-0.25rem)] [--switch-width:calc(var(--dt-density-control)*1.1)] h-[var(--switch-height)] w-[var(--switch-width)]\",\n  lg: \"[--switch-height:calc(var(--dt-density-control)*0.7)] [--switch-thumb-size:calc(var(--switch-height)-0.25rem)] [--switch-width:calc(var(--dt-density-control)*1.3)] h-[var(--switch-height)] w-[var(--switch-width)]\",\n};\n\nconst switchThumbSizeClasses: Record<SwitchControlSize, string> = {\n  sm: \"size-[var(--switch-thumb-size)]\",\n  md: \"size-[var(--switch-thumb-size)]\",\n  lg: \"size-[var(--switch-thumb-size)]\",\n};\n\nconst switchInputClasses =\n  \"peer absolute inset-0 z-10 m-0 size-full cursor-pointer opacity-0 disabled:cursor-not-allowed\";\n\nconst switchTrackBaseClasses =\n  \"pointer-events-none flex size-full items-center rounded-full border border-input bg-muted p-[var(--dt-space-0-5)] shadow-sm outline-none motion-safe:transition-[background-color,border-color,box-shadow,opacity] motion-safe:duration-[var(--dt-motion-fast)] motion-safe:ease-control peer-disabled:opacity-60 peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-background group-disabled/field-set:opacity-60 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[invalid=true]:border-destructive data-[invalid=true]:ring-2 data-[invalid=true]:ring-destructive/15 data-[disabled=true]:opacity-60 data-[readonly=true]:bg-muted/40\";\n\nconst switchThumbBaseClasses =\n  \"shrink-0 rounded-full bg-background text-background shadow-sm data-[state=checked]:bg-primary-foreground data-[readonly=true]:bg-muted-foreground/70\";\n\nconst switchThumbTranslateClasses =\n  \"[--switch-travel:calc(var(--switch-width)-var(--switch-thumb-size)-var(--dt-space-0-5)*2-2px)] translate-x-0 motion-safe:transition-[translate,background-color] motion-safe:duration-[180ms] motion-safe:ease-control data-[state=checked]:translate-x-[var(--switch-travel)] rtl:data-[state=checked]:-translate-x-[var(--switch-travel)] data-[state=checked]:bg-primary-foreground data-[readonly=true]:bg-muted-foreground/70\";\n\nfunction isAriaInvalid(value: SwitchProps[\"aria-invalid\"]) {\n  return (\n    value === true ||\n    value === \"true\" ||\n    value === \"grammar\" ||\n    value === \"spelling\"\n  );\n}\n\nfunction getSwitchState(checked: boolean) {\n  return checked ? \"checked\" : \"unchecked\";\n}\n\nfunction setRef<T>(ref: Ref<T> | undefined, node: T | null) {\n  if (typeof ref === \"function\") {\n    ref(node);\n    return;\n  }\n\n  if (ref) {\n    // React 18 types expose object refs as read-only.\n    (ref as MutableRefObject<T | null>).current = node;\n  }\n}\n\nfunction composeRefs<T>(...refs: Array<Ref<T> | undefined>) {\n  return (node: T | null) => {\n    for (const ref of refs) {\n      setRef(ref, node);\n    }\n  };\n}\n\nexport function switchClassNames({\n  className,\n  controlSize = \"md\",\n}: Pick<SwitchProps, \"className\" | \"controlSize\"> = {}) {\n  return cn(\n    switchRootBaseClasses,\n    switchControlSizeClasses[controlSize],\n    className,\n  );\n}\n\nexport const Switch = forwardRef<HTMLInputElement, SwitchProps>(\n  (\n    {\n      \"aria-invalid\": ariaInvalid,\n      \"data-slot\": dataSlot,\n      checked,\n      className,\n      controlSize = \"md\",\n      defaultChecked = false,\n      disabled,\n      invalid = false,\n      onChange,\n      onCheckedChange,\n      onClick,\n      readOnly,\n      required,\n      spring = false,\n      ...props\n    },\n    ref,\n  ) => {\n    const inputRef = useRef<HTMLInputElement | null>(null);\n    const isControlled = checked !== undefined;\n    const [uncontrolledChecked, setUncontrolledChecked] =\n      useState(defaultChecked);\n    const checkedState = isControlled ? checked : uncontrolledChecked;\n    const resolvedInvalid = invalid || isAriaInvalid(ariaInvalid);\n    const state = getSwitchState(checkedState);\n\n    const restoreReadOnlyState = () => {\n      if (inputRef.current) {\n        inputRef.current.checked = checkedState;\n      }\n    };\n\n    const handleClick: MouseEventHandler<HTMLInputElement> = (event) => {\n      if (readOnly) {\n        event.preventDefault();\n        restoreReadOnlyState();\n        queueMicrotask(restoreReadOnlyState);\n        return;\n      }\n\n      onClick?.(event);\n    };\n\n    const handleChange: ChangeEventHandler<HTMLInputElement> = (event) => {\n      if (readOnly) {\n        event.preventDefault();\n        restoreReadOnlyState();\n        queueMicrotask(restoreReadOnlyState);\n        return;\n      }\n\n      onChange?.(event);\n\n      if (event.defaultPrevented) {\n        return;\n      }\n\n      const nextChecked = event.currentTarget.checked;\n\n      if (!isControlled) {\n        setUncontrolledChecked(nextChecked);\n      }\n\n      onCheckedChange?.(nextChecked);\n    };\n\n    const thumbProps = {\n      \"data-slot\": \"switch-thumb\",\n      \"data-size\": controlSize,\n      \"data-state\": state,\n      \"data-checked\": checkedState ? \"true\" : undefined,\n      \"data-disabled\": disabled ? \"true\" : undefined,\n      \"data-invalid\": resolvedInvalid ? \"true\" : undefined,\n      \"data-readonly\": readOnly ? \"true\" : undefined,\n      \"data-required\": required ? \"true\" : undefined,\n      className: cn(\n        switchThumbBaseClasses,\n        switchThumbSizeClasses[controlSize],\n        spring ? \"translate-x-0\" : switchThumbTranslateClasses,\n      ),\n    };\n    const staticThumb = <span {...thumbProps} />;\n\n    return (\n      <span\n        data-animation={spring ? \"spring\" : undefined}\n        data-slot={dataSlot ?? \"switch\"}\n        data-size={controlSize}\n        data-state={state}\n        data-checked={checkedState ? \"true\" : undefined}\n        data-disabled={disabled ? \"true\" : undefined}\n        data-invalid={resolvedInvalid ? \"true\" : undefined}\n        data-readonly={readOnly ? \"true\" : undefined}\n        data-required={required ? \"true\" : undefined}\n        className={switchClassNames({ className, controlSize })}\n      >\n        <input\n          {...props}\n          ref={composeRefs(inputRef, ref)}\n          aria-checked={checkedState}\n          aria-invalid={invalid ? true : ariaInvalid}\n          aria-readonly={readOnly ? true : undefined}\n          checked={checkedState}\n          className={switchInputClasses}\n          data-slot=\"switch-input\"\n          data-size={controlSize}\n          data-state={state}\n          data-checked={checkedState ? \"true\" : undefined}\n          data-disabled={disabled ? \"true\" : undefined}\n          data-invalid={resolvedInvalid ? \"true\" : undefined}\n          data-readonly={readOnly ? \"true\" : undefined}\n          data-required={required ? \"true\" : undefined}\n          disabled={disabled}\n          onChange={handleChange}\n          onClick={handleClick}\n          readOnly={readOnly}\n          required={required}\n          role=\"switch\"\n          type=\"checkbox\"\n        />\n        <span\n          aria-hidden=\"true\"\n          data-slot=\"switch-track\"\n          data-size={controlSize}\n          data-state={state}\n          data-checked={checkedState ? \"true\" : undefined}\n          data-disabled={disabled ? \"true\" : undefined}\n          data-invalid={resolvedInvalid ? \"true\" : undefined}\n          data-readonly={readOnly ? \"true\" : undefined}\n          data-required={required ? \"true\" : undefined}\n          className={cn(\n            switchTrackBaseClasses,\n            spring && \"justify-start data-[state=checked]:justify-end\",\n          )}\n        >\n          {spring ? (\n            <Suspense fallback={staticThumb}>\n              <SpringThumb {...thumbProps} checked={checkedState} />\n            </Suspense>\n          ) : (\n            staticThumb\n          )}\n        </span>\n      </span>\n    );\n  },\n);\n\nSwitch.displayName = \"Switch\";\n"
    },
    {
      "path": "packages/components/src/components/switch/switch-spring-thumb.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/switch/switch-spring-thumb.tsx",
      "content": "import { motion, useReducedMotion } from \"motion/react\";\n\ninterface SpringThumbProps {\n  checked: boolean;\n  className: string;\n  [attribute: `data-${string}`]: string | undefined;\n}\n\n/** Loaded only by switches that opt in to spring animation. */\nexport default function SwitchSpringThumb({\n  checked,\n  ...props\n}: SpringThumbProps) {\n  const reducedMotion = useReducedMotion();\n\n  return (\n    <motion.span\n      {...props}\n      data-animation=\"spring\"\n      initial={false}\n      layout={reducedMotion ? false : \"position\"}\n      layoutDependency={checked}\n      transition={{ type: \"spring\", stiffness: 500, damping: 24, mass: 0.8 }}\n    />\n  );\n}\n"
    },
    {
      "path": "packages/components/src/components/switch/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/switch/index.ts",
      "content": "export {\n  Switch,\n  switchClassNames,\n  type SwitchControlSize,\n  type SwitchProps,\n} from \"./switch\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    }
  ]
}
