{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "switch",
  "title": "Switch",
  "description": "Turns a setting on or off, in two sizes, beside a label or in a card.",
  "dependencies": [
    "@base-ui/react",
    "@stylexjs/stylex"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json"
  ],
  "files": [
    {
      "path": "src/components/primitives/switch.tsx",
      "content": "\"use client\";\n\nimport { Switch as SwitchPrimitive } from \"@base-ui/react/switch\";\nimport * as stylex from \"@stylexjs/stylex\";\n\nimport type { Sx } from \"@/lib/constella/styles\";\nimport {\n  colors,\n  durations,\n  easings,\n  lineHeights,\n  media,\n  opacities,\n  radii,\n  shadows,\n  sizes,\n  strokes,\n} from \"@/lib/constella/tokens.stylex\";\n\nconst switchSizes = [\"sm\", \"default\", \"lg\"] as const;\n\ntype SwitchSize = (typeof switchSizes)[number];\n\ntype SwitchProps = Omit<SwitchPrimitive.Root.Props, \"className\" | \"style\"> & {\n  size?: SwitchSize;\n  sx?: Sx;\n};\n\nconst invalid = \":is([aria-invalid='true'], [data-invalid])\";\nconst disabled = \":is([data-disabled])\";\nconst beside =\n  \":is([data-orientation='horizontal'] > *, [data-slot='field-label'] > *)\";\n\n// The track is the thumb wide twice, and the thumb plus its edge tall; the thumb travels the difference.\nconst track = (thumb: string) => ({\n  height: `calc(${thumb} + 2 * ${strokes.border})`,\n  // Beside top-aligned text, the track centers on the label's first line.\n  marginBlock: {\n    default: null,\n    [beside]: `calc((${lineHeights.textSm} - ${thumb} - 2 * ${strokes.border}) / 2)`,\n  },\n  width: `calc(2 * ${thumb})`,\n  \"::before\": {\n    insetBlock: `calc((${thumb} + 2 * ${strokes.border} - ${sizes.hitArea}) / 2)`,\n    insetInline: `calc((2 * ${thumb} - ${sizes.hitArea}) / 2)`,\n  },\n});\n\n// Held, the thumb stretches a quarter of itself toward where it will go, like a finger\n// pressing into it; checked, it stretches back from the end, so it stays inside the track.\nconst pressed = \":is([data-slot='switch']:active:not([data-disabled]) > *)\";\n\nconst rtl = \":is([dir='rtl'] *)\";\n\nconst knob = (thumb: string) => ({\n  height: thumb,\n  transform: {\n    default: \"translateX(0)\",\n    \":is([data-checked])\": `translateX(calc(${thumb} - 2 * ${strokes.border}))`,\n    [`:is([data-checked])${pressed}`]: `translateX(calc(${thumb} * 3 / 4 - 2 * ${strokes.border}))`,\n    [`:is([data-checked])${rtl}`]: `translateX(calc(2 * ${strokes.border} - ${thumb}))`,\n    [`:is([data-checked])${pressed}${rtl}`]: `translateX(calc(2 * ${strokes.border} - ${thumb} * 3 / 4))`,\n  },\n  width: { default: thumb, [pressed]: `calc(${thumb} * 5 / 4)` },\n});\n\nconst styles = stylex.create({\n  switch: {\n    alignItems: \"center\",\n    backgroundColor: {\n      default: colors.fillStrong,\n      \":is([data-checked])\": colors.infoSolid,\n    },\n    borderBlockColor: {\n      default: \"transparent\",\n      [invalid]: colors.dangerBorder,\n    },\n    borderInlineColor: {\n      default: \"transparent\",\n      [invalid]: colors.dangerBorder,\n    },\n    borderStartStartRadius: radii.full,\n    borderStartEndRadius: radii.full,\n    borderEndStartRadius: radii.full,\n    borderEndEndRadius: radii.full,\n    borderBlockStyle: \"solid\",\n    borderInlineStyle: \"solid\",\n    borderBlockWidth: strokes.border,\n    borderInlineWidth: strokes.border,\n    boxShadow: { default: null, [invalid]: shadows.defaultInvalid },\n    cursor: { default: \"pointer\", [disabled]: \"not-allowed\" },\n    display: \"inline-flex\",\n    flexShrink: 0,\n    opacity: { default: 1, [disabled]: opacities.disabled },\n    paddingBlock: 0,\n    paddingInline: 0,\n    position: \"relative\",\n    transitionDuration: durations.hover,\n    transitionProperty: \"background-color, border-color\",\n    transitionTimingFunction: \"ease\",\n    \"::before\": {\n      content: \"''\",\n      position: \"absolute\",\n    },\n  },\n  default: track(sizes.icon),\n  sm: track(sizes.iconXs),\n  lg: track(sizes.iconLg),\n  thumb: {\n    backgroundColor: {\n      default: colors.fillOnStrong,\n      \":is([data-checked])\": colors.infoOnSolid,\n    },\n    borderStartStartRadius: radii.full,\n    borderStartEndRadius: radii.full,\n    borderEndStartRadius: radii.full,\n    borderEndEndRadius: radii.full,\n    boxShadow: shadows.default,\n    display: \"block\",\n    pointerEvents: \"none\",\n    // The thumb lands with a little give; the stretch answers the press at once. Toggled from\n    // the keyboard (only a keyboard focus matches `:focus-visible`), it doesn't move.\n    transitionDuration: {\n      default: `${durations.move}, ${durations.press}, ${durations.hover}`,\n      \":is(:focus-visible > *)\": \"0s\",\n    },\n    transitionProperty: {\n      default: \"transform, width, background-color\",\n      [media.reducedMotion]: \"background-color\",\n    },\n    transitionTimingFunction: `${easings.overshoot}, ${easings.out}, ease`,\n  },\n  thumbDefault: knob(sizes.icon),\n  thumbSm: knob(sizes.iconXs),\n  thumbLg: knob(sizes.iconLg),\n});\n\nconst sizeStyles = {\n  default: [styles.default, styles.thumbDefault],\n  lg: [styles.lg, styles.thumbLg],\n  sm: [styles.sm, styles.thumbSm],\n} satisfies Record<SwitchSize, [stylex.StyleXStyles, stylex.StyleXStyles]>;\n\nfunction Switch({ size = \"default\", sx, ...props }: SwitchProps) {\n  const [root, thumb] = sizeStyles[size];\n  return (\n    <SwitchPrimitive.Root\n      data-size={size}\n      data-slot=\"switch\"\n      {...props}\n      {...stylex.props(styles.switch, root, sx)}\n    >\n      <SwitchPrimitive.Thumb\n        data-slot=\"switch-thumb\"\n        {...stylex.props(styles.thumb, thumb)}\n      />\n    </SwitchPrimitive.Root>\n  );\n}\n\nexport { Switch, switchSizes };\nexport type { SwitchProps, SwitchSize };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/switch/index.md",
  "type": "registry:ui"
}