{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "checkbox-switch",
  "title": "Switch checkbox",
  "description": "Styled checkbox: the Switch set. 5 decorative checkbox variations (ToggleCheckbox, PillCheckbox, SlideCheckbox, TrackCheckbox, CompactSwitchCheckbox) on the ai2 token system, powered by framer-motion, reduced-motion aware and sized sm to xl. Part of the free styled layer.",
  "dependencies": [
    "motion@^12.42.2"
  ],
  "registryDependencies": [
    "@ai2/tokens"
  ],
  "files": [
    {
      "path": "registry/ai2/styled/checkbox-switch.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/* Switch-looking checkbox family: 5 switches. ROLE CHOICE: we use role=\"switch\".\n   The reason: in ARIA a switch already derives from checkbox (the same on/off\n   semantics, the same keyboard contract), but a screen reader announces \"on/off\"\n   instead of \"checkbox, checked\". Since the control looks like a switch, having\n   the announcement be a switch too aligns appearance with semantics; saying\n   \"checkbox\" would give the user the wrong mental model. There is no third state\n   (mixed) here, so the switch's restriction costs nothing. data-slot still stays\n   \"styled-checkbox\" - this family is a member of the checkbox layer.\n   Because the track can stay under 24px the invisible touch-area extension\n   (after:-inset-1.5) is mandatory in every variant. Color comes ONLY from tokens,\n   via alpha color-mix. framer-motion switches instantly under reduced-motion. */\n\nexport type StyledSize = \"sm\" | \"md\" | \"lg\" | \"xl\"\n\nconst trackSize: Record<StyledSize, string> = {\n  sm: \"h-4 w-7\",\n  md: \"h-5 w-9\",\n  lg: \"h-6 w-11\",\n  xl: \"h-7 w-[3.25rem]\",\n}\n\nconst pillTrackSize: Record<StyledSize, string> = {\n  sm: \"h-4 w-12\",\n  md: \"h-5 w-14\",\n  lg: \"h-6 w-16\",\n  xl: \"h-7 w-20\",\n}\n\nconst thumbSize: Record<StyledSize, string> = {\n  sm: \"size-3\",\n  md: \"size-4\",\n  lg: \"size-5\",\n  xl: \"size-6\",\n}\n\n/* Basparmagin kat ettigi yol (px): ray genisligi - 4px ic bosluk - basparmak. */\nconst travel: Record<StyledSize, number> = { sm: 12, md: 16, lg: 20, xl: 24 }\nconst pillTravel: Record<StyledSize, number> = { sm: 32, md: 36, lg: 40, xl: 52 }\n\nconst textSize: Record<StyledSize, string> = {\n  sm: \"text-xs\",\n  md: \"text-sm\",\n  lg: \"text-sm\",\n  xl: \"text-base\",\n}\n\nconst capSize: Record<StyledSize, string> = {\n  sm: \"text-[0.5rem]\",\n  md: \"text-[0.5rem]\",\n  lg: \"text-[0.625rem]\",\n  xl: \"text-xs\",\n}\n\nconst trackBase =\n  \"relative inline-flex shrink-0 cursor-pointer items-center rounded-full p-0.5 outline-none transition-colors after:absolute after:-inset-1.5 focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger aria-invalid:ring-danger/20 dark:aria-invalid:ring-danger/40 [&_svg]:shrink-0 [&_i]:leading-none\"\n\nconst thumbBase = \"pointer-events-none block rounded-full shadow-sm\"\n\nconst rowBase = \"inline-flex items-center gap-2.5\"\n\n/* Transition type: instant under reduced-motion, spring otherwise. */\ntype SpringLike = { duration: number } | { type: \"spring\"; stiffness: number; damping: number }\n\ntype CheckboxProps = Omit<React.ComponentProps<\"button\">, \"onChange\"> & {\n  size?: StyledSize\n  label?: string\n  checked?: boolean\n  defaultChecked?: boolean\n  onCheckedChange?: (checked: boolean) => void\n}\n\nfunction useCheckbox(props: {\n  checked?: boolean\n  defaultChecked?: boolean\n  onCheckedChange?: (checked: boolean) => void\n}) {\n  const { checked, defaultChecked, onCheckedChange } = props\n  const reduce = useReducedMotion()\n  const [internal, setInternal] = React.useState(defaultChecked ?? false)\n  const on = checked ?? internal\n  const toggle = () => {\n    if (checked === undefined) setInternal((v) => !v)\n    onCheckedChange?.(!on)\n  }\n  const spring: SpringLike = reduce\n    ? { duration: 0 }\n    : { type: \"spring\" as const, stiffness: 500, damping: 34 }\n  return { on, toggle, spring }\n}\n\n/* Toggle: the classic switch; the track turns primary when on. */\nexport function ToggleCheckbox({\n  className,\n  size = \"md\",\n  label = \"Airplane mode\",\n  checked,\n  defaultChecked,\n  onCheckedChange,\n  ...props\n}: CheckboxProps) {\n  const { on, toggle, spring } = useCheckbox({ checked, defaultChecked, onCheckedChange })\n  const labelId = React.useId()\n  return (\n    <span className={rowBase}>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={on}\n        aria-labelledby={labelId}\n        data-slot=\"styled-checkbox\"\n        onClick={toggle}\n        className={cn(trackBase, trackSize[size], on ? \"bg-primary\" : \"bg-input\", className)}\n        {...props}\n      >\n        <motion.span\n          initial={false}\n          animate={{ x: on ? travel[size] : 0 }}\n          transition={spring}\n          className={cn(thumbBase, thumbSize[size], \"bg-background\")}\n        />\n      </button>\n      <span\n        id={labelId}\n        onClick={toggle}\n        className={cn(textSize[size], \"cursor-pointer select-none font-medium text-foreground\")}\n      >\n        {label}\n      </span>\n    </span>\n  )\n}\n\n/* Pill: rayin icinde ON/OFF yazisi tasiyan genis hap anahtar. */\nexport function PillCheckbox({\n  className,\n  size = \"md\",\n  label = \"Auto sync\",\n  checked,\n  defaultChecked,\n  onCheckedChange,\n  ...props\n}: CheckboxProps) {\n  const { on, toggle, spring } = useCheckbox({ checked, defaultChecked, onCheckedChange })\n  const labelId = React.useId()\n  return (\n    <span className={rowBase}>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={on}\n        aria-labelledby={labelId}\n        data-slot=\"styled-checkbox\"\n        onClick={toggle}\n        className={cn(pillTrackSize[size], trackBase, on ? \"bg-success\" : \"bg-input\", className)}\n        {...props}\n      >\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            capSize[size],\n            \"absolute inset-y-0 flex items-center font-semibold uppercase tracking-wide transition-colors\",\n            on ? \"left-2 text-success-foreground\" : \"right-2 text-muted-foreground\"\n          )}\n        >\n          {on ? \"On\" : \"Off\"}\n        </span>\n        <motion.span\n          initial={false}\n          animate={{ x: on ? pillTravel[size] : 0 }}\n          transition={spring}\n          className={cn(thumbBase, thumbSize[size], \"relative bg-background\")}\n        />\n      </button>\n      <span\n        id={labelId}\n        onClick={toggle}\n        className={cn(textSize[size], \"cursor-pointer select-none font-medium text-foreground\")}\n      >\n        {label}\n      </span>\n    </span>\n  )\n}\n\n/* Slide: dolgu soldan kayarak gelir, basparmak onunde ilerler. */\nexport function SlideCheckbox({\n  className,\n  size = \"md\",\n  label = \"Dark theme\",\n  checked,\n  defaultChecked,\n  onCheckedChange,\n  ...props\n}: CheckboxProps) {\n  const { on, toggle, spring } = useCheckbox({ checked, defaultChecked, onCheckedChange })\n  const labelId = React.useId()\n  return (\n    <span className={rowBase}>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={on}\n        aria-labelledby={labelId}\n        data-slot=\"styled-checkbox\"\n        onClick={toggle}\n        className={cn(trackBase, trackSize[size], \"overflow-hidden bg-input\", className)}\n        {...props}\n      >\n        <motion.span\n          aria-hidden=\"true\"\n          initial={false}\n          animate={{ scaleX: on ? 1 : 0 }}\n          transition={spring}\n          className=\"absolute inset-0 origin-left rounded-full bg-primary\"\n        />\n        <motion.span\n          initial={false}\n          animate={{ x: on ? travel[size] : 0 }}\n          transition={spring}\n          className={cn(thumbBase, thumbSize[size], \"relative bg-background\")}\n        />\n      </button>\n      <span\n        id={labelId}\n        onClick={toggle}\n        className={cn(textSize[size], \"cursor-pointer select-none font-medium text-foreground\")}\n      >\n        {label}\n      </span>\n    </span>\n  )\n}\n\n/* Track: the track stays transparent and bordered, only the thumb carries colour. */\nexport function TrackCheckbox({\n  className,\n  size = \"md\",\n  label = \"Outlined switch\",\n  checked,\n  defaultChecked,\n  onCheckedChange,\n  ...props\n}: CheckboxProps) {\n  const { on, toggle, spring } = useCheckbox({ checked, defaultChecked, onCheckedChange })\n  const labelId = React.useId()\n  return (\n    <span className={rowBase}>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={on}\n        aria-labelledby={labelId}\n        data-slot=\"styled-checkbox\"\n        onClick={toggle}\n        className={cn(\n          trackBase,\n          trackSize[size],\n          \"border bg-transparent\",\n          on\n            ? \"border-primary bg-[color-mix(in_oklab,var(--color-primary)_12%,transparent)]\"\n            : \"border-field-border\",\n          className\n        )}\n        {...props}\n      >\n        <motion.span\n          initial={false}\n          animate={{ x: on ? travel[size] - 2 : 0 }}\n          transition={spring}\n          className={cn(thumbBase, thumbSize[size], on ? \"bg-primary\" : \"bg-muted-foreground\")}\n        />\n      </button>\n      <span\n        id={labelId}\n        onClick={toggle}\n        className={cn(textSize[size], \"cursor-pointer select-none font-medium text-foreground\")}\n      >\n        {label}\n      </span>\n    </span>\n  )\n}\n\n/* CompactSwitch: a short track with a small thumb, for dense lists. */\nexport function CompactSwitchCheckbox({\n  className,\n  size = \"md\",\n  label = \"Compact row\",\n  checked,\n  defaultChecked,\n  onCheckedChange,\n  ...props\n}: CheckboxProps) {\n  const { on, toggle, spring } = useCheckbox({ checked, defaultChecked, onCheckedChange })\n  const labelId = React.useId()\n  return (\n    <span className={cn(rowBase, \"gap-2\")}>\n      <button\n        type=\"button\"\n        role=\"switch\"\n        aria-checked={on}\n        aria-labelledby={labelId}\n        data-slot=\"styled-checkbox\"\n        onClick={toggle}\n        className={cn(\n          trackBase,\n          trackSize[size],\n          \"rounded-sm p-px\",\n          on ? \"bg-primary\" : \"bg-input\",\n          className\n        )}\n        {...props}\n      >\n        <motion.span\n          initial={false}\n          animate={{ x: on ? travel[size] + 2 : 0 }}\n          transition={spring}\n          className={cn(thumbBase, thumbSize[size], \"rounded-[3px] bg-background\")}\n        />\n      </button>\n      <span\n        id={labelId}\n        onClick={toggle}\n        className={cn(textSize[size], \"cursor-pointer select-none text-foreground\")}\n      >\n        {label}\n      </span>\n    </span>\n  )\n}\n",
      "type": "registry:component",
      "target": "components/ui/checkbox-switch.tsx"
    }
  ],
  "categories": [
    "styled",
    "checkbox"
  ],
  "type": "registry:component"
}