{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "adaptive-switch",
  "title": "Adaptive Switch",
  "description": "An accessible switch with optional state labels, three sizes, and configurable motion.",
  "dependencies": [
    "@base-ui/react",
    "motion"
  ],
  "files": [
    {
      "path": "registry/base/ui/adaptive-switch.tsx",
      "content": "\"use client\";\n\nimport { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\nimport {\n  motion,\n  useReducedMotion,\n  type MotionStyle,\n  type Transition,\n  type Variants,\n} from \"motion/react\";\nimport { useState, type ReactNode } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type AdaptiveSwitchSize = \"sm\" | \"default\" | \"lg\";\nexport type AdaptiveSwitchAnimation = \"elastic\" | \"smooth\" | \"none\";\n\nexport type AdaptiveSwitchProps = Omit<\n  SwitchPrimitive.Root.Props,\n  \"children\"\n> & {\n  /** Text revealed on the left when the switch is checked. */\n  checkedLabel?: ReactNode;\n  /** Text revealed on the right when the switch is unchecked. */\n  uncheckedLabel?: ReactNode;\n  /** Controls the track and thumb dimensions. */\n  size?: AdaptiveSwitchSize;\n  /**\n   * Controls the thumb motion. Defaults to elastic when labels are present and\n   * smooth for a conventional switch.\n   */\n  animation?: AdaptiveSwitchAnimation;\n};\n\nconst ELASTIC_SETTLE_TRANSITION: Transition = {\n  duration: 0.52,\n  times: [0, 0.09, 0.2, 0.31, 0.4, 0.53, 0.64, 0.74, 0.82, 0.89, 0.95, 1],\n  ease: [0.65, 0, 0.35, 1],\n};\n\nconst REDUCED_TRANSITION: Transition = { duration: 0 };\nconst SLIDE_TRANSITION: Transition = {\n  duration: 0.14,\n  ease: [0.22, 1, 0.36, 1],\n};\nconst ELASTIC_SCALE_X = [\n  1.16, 1.5, 1.24, 0.9, 1.06, 0.97, 1.02, 0.992, 1.012, 0.996, 1,\n] as const;\nconst ELASTIC_SCALE_Y = ELASTIC_SCALE_X.map((scaleX) => 1 / scaleX);\n\nfunction getStaticThumbStyle(\n  checked: boolean,\n  hasLabels: boolean,\n): MotionStyle {\n  return {\n    x: checked ? (hasLabels ? \"100%\" : \"calc(100% - 2px)\") : \"0%\",\n    scaleX: 1,\n    scaleY: 1,\n    originX: checked ? 1 : 0,\n    originY: 0.5,\n  };\n}\n\nfunction createThumbVariants(\n  hasLabels: boolean,\n  animation: AdaptiveSwitchAnimation,\n  shouldReduceMotion: boolean,\n): Variants {\n  const checkedX = hasLabels ? \"100%\" : \"calc(100% - 2px)\";\n  const checkedWarmupX = hasLabels ? \"24%\" : \"calc(24% - 2px)\";\n  const checkedStretchX = hasLabels ? \"50%\" : \"calc(50% - 2px)\";\n  const checkedSettleX = hasLabels ? \"76%\" : \"calc(76% - 2px)\";\n\n  if (shouldReduceMotion || animation !== \"elastic\") {\n    const transition =\n      shouldReduceMotion || animation === \"none\"\n        ? REDUCED_TRANSITION\n        : SLIDE_TRANSITION;\n\n    return {\n      checked: {\n        x: checkedX,\n        scaleX: 1,\n        scaleY: 1,\n        originX: 1,\n        originY: 0.5,\n        transition,\n      },\n      unchecked: {\n        x: \"0%\",\n        scaleX: 1,\n        scaleY: 1,\n        originX: 0,\n        originY: 0.5,\n        transition,\n      },\n    };\n  }\n\n  return {\n    checked: {\n      x: [\n        null,\n        checkedWarmupX,\n        checkedStretchX,\n        checkedSettleX,\n        checkedX,\n        checkedX,\n        checkedX,\n        checkedX,\n        checkedX,\n        checkedX,\n        checkedX,\n        checkedX,\n      ],\n      scaleX: [null, ...ELASTIC_SCALE_X],\n      scaleY: [null, ...ELASTIC_SCALE_Y],\n      originX: [null, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1],\n      originY: 0.5,\n      transition: ELASTIC_SETTLE_TRANSITION,\n    },\n    unchecked: {\n      x: [\n        null,\n        \"76%\",\n        \"50%\",\n        \"24%\",\n        \"0%\",\n        \"0%\",\n        \"0%\",\n        \"0%\",\n        \"0%\",\n        \"0%\",\n        \"0%\",\n        \"0%\",\n      ],\n      scaleX: [null, ...ELASTIC_SCALE_X],\n      scaleY: [null, ...ELASTIC_SCALE_Y],\n      originX: [null, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n      originY: 0.5,\n      transition: ELASTIC_SETTLE_TRANSITION,\n    },\n  };\n}\n\nexport function AdaptiveSwitch({\n  checkedLabel,\n  uncheckedLabel,\n  size = \"default\",\n  animation,\n  className,\n  onCheckedChange,\n  ...props\n}: AdaptiveSwitchProps) {\n  const hasLabels = checkedLabel != null || uncheckedLabel != null;\n  const animationMode = animation ?? (hasLabels ? \"elastic\" : \"smooth\");\n  const shouldReduceMotion = useReducedMotion() ?? false;\n  const [hasChanged, setHasChanged] = useState(false);\n  const thumbVariants = createThumbVariants(\n    hasLabels,\n    animationMode,\n    shouldReduceMotion,\n  );\n\n  return (\n    <SwitchPrimitive.Root\n      data-slot=\"adaptive-switch\"\n      data-size={size}\n      data-with-labels={hasLabels ? \"true\" : \"false\"}\n      data-animation={animationMode}\n      data-animated={hasChanged ? \"true\" : \"false\"}\n      className={cn(\n        \"group/adaptive-switch relative inline-flex shrink-0 cursor-pointer select-none items-center overflow-visible rounded-full border border-transparent outline-none\",\n        \"data-[animated=true]:transition-[background-color,border-color,box-shadow] data-[animated=true]:duration-150 data-[animated=true]:ease-[ease]\",\n        \"after:absolute after:-inset-x-3 after:-inset-y-2\",\n        \"focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50\",\n        \"aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20\",\n        \"data-checked:bg-primary data-unchecked:bg-input\",\n        \"data-disabled:cursor-not-allowed data-disabled:opacity-50 data-readonly:cursor-default\",\n        \"dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 dark:data-unchecked:bg-input/80\",\n        \"data-[animation=none]:transition-none motion-reduce:transition-none\",\n        hasLabels\n          ? \"inline-grid min-w-16 grid-cols-2 p-0.5\"\n          : \"data-[size=default]:h-[18.4px] data-[size=default]:w-8 data-[size=lg]:h-7 data-[size=lg]:w-12 data-[size=sm]:h-[17px] data-[size=sm]:w-[30px]\",\n        hasLabels && size === \"default\" ? \"h-6\" : null,\n        hasLabels && size === \"sm\" ? \"h-5\" : null,\n        hasLabels && size === \"lg\" ? \"h-7\" : null,\n        className,\n      )}\n      onCheckedChange={(checked, eventDetails) => {\n        setHasChanged(true);\n        onCheckedChange?.(checked, eventDetails);\n      }}\n      {...props}\n    >\n      {hasLabels ? (\n        <>\n          <span\n            aria-hidden=\"true\"\n            data-slot=\"adaptive-switch-checked-label\"\n            className={cn(\n              \"col-start-1 row-start-1 inline-flex min-w-max items-center justify-center px-2.5 font-medium text-primary-foreground opacity-0\",\n              \"group-data-[animated=true]/adaptive-switch:transition-opacity group-data-[animated=true]/adaptive-switch:duration-200 group-data-[animated=true]/adaptive-switch:ease-[cubic-bezier(0.65,0,0.35,1)]\",\n              \"group-data-checked/adaptive-switch:opacity-100 group-data-[animation=none]/adaptive-switch:transition-none motion-reduce:transition-none\",\n              size === \"sm\"\n                ? \"px-2 text-[11px]\"\n                : size === \"lg\"\n                  ? \"px-2.5 text-xs\"\n                  : \"text-xs\",\n            )}\n          >\n            {checkedLabel}\n          </span>\n          <span\n            aria-hidden=\"true\"\n            data-slot=\"adaptive-switch-unchecked-label\"\n            className={cn(\n              \"col-start-2 row-start-1 inline-flex min-w-max items-center justify-center px-2.5 font-medium text-foreground opacity-0\",\n              \"group-data-[animated=true]/adaptive-switch:transition-opacity group-data-[animated=true]/adaptive-switch:duration-200 group-data-[animated=true]/adaptive-switch:ease-[cubic-bezier(0.65,0,0.35,1)]\",\n              \"group-data-unchecked/adaptive-switch:opacity-100 group-data-[animation=none]/adaptive-switch:transition-none motion-reduce:transition-none\",\n              size === \"sm\"\n                ? \"px-2 text-[11px]\"\n                : size === \"lg\"\n                  ? \"px-2.5 text-xs\"\n                  : \"text-xs\",\n            )}\n          >\n            {uncheckedLabel}\n          </span>\n        </>\n      ) : null}\n\n      <SwitchPrimitive.Thumb\n        data-slot=\"adaptive-switch-thumb\"\n        className={cn(\n          \"group/adaptive-switch-thumb pointer-events-none relative block ring-0\",\n          hasLabels\n            ? \"absolute inset-y-0.5 left-0.5 w-[calc(50%-0.125rem)]\"\n            : \"group-data-[size=default]/adaptive-switch:size-4 group-data-[size=lg]/adaptive-switch:size-6 group-data-[size=sm]/adaptive-switch:size-[15px]\",\n        )}\n        render={(thumbProps, state) => (\n          <span {...thumbProps}>\n            <motion.span\n              aria-hidden=\"true\"\n              data-slot=\"adaptive-switch-thumb-visual\"\n              initial={false}\n              animate={\n                hasChanged\n                  ? state.checked\n                    ? \"checked\"\n                    : \"unchecked\"\n                  : undefined\n              }\n              variants={thumbVariants}\n              style={\n                hasChanged\n                  ? undefined\n                  : getStaticThumbStyle(state.checked, hasLabels)\n              }\n              className={cn(\n                \"absolute inset-0 rounded-full bg-background ring-0 group-data-checked/adaptive-switch-thumb:dark:bg-primary-foreground group-data-unchecked/adaptive-switch-thumb:dark:bg-foreground\",\n                hasChanged && animationMode !== \"none\"\n                  ? \"will-change-transform\"\n                  : null,\n                hasLabels ? \"shadow-sm\" : null,\n              )}\n            />\n          </span>\n        )}\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/adaptive-switch.tsx"
    }
  ],
  "meta": {
    "tags": [
      "switch",
      "toggle",
      "status",
      "controlled",
      "uncontrolled",
      "adaptive"
    ],
    "effects": [
      "thumb-stretch",
      "damped-elastic-settle",
      "configurable-motion",
      "label-crossfade",
      "reduced-motion"
    ]
  },
  "categories": [
    "form"
  ],
  "type": "registry:ui"
}