{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "radio-group",
  "title": "Radio Group",
  "description": "Picks one option out of a few, beside labels, with descriptions or as cards.",
  "dependencies": [
    "@base-ui/react",
    "@stylexjs/stylex"
  ],
  "registryDependencies": [
    "https://constella.aayush.cv/r/base.json"
  ],
  "files": [
    {
      "path": "src/components/primitives/radio-group.tsx",
      "content": "\"use client\";\n\nimport { Radio as RadioPrimitive } from \"@base-ui/react/radio\";\nimport { RadioGroup as RadioGroupPrimitive } from \"@base-ui/react/radio-group\";\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  motion,\n  opacities,\n  presses,\n  radii,\n  shadows,\n  sizes,\n  space,\n  strokes,\n} from \"@/lib/constella/tokens.stylex\";\n\ntype Styled<T> = Omit<T, \"className\" | \"style\"> & {\n  sx?: Sx;\n};\n\nconst radioGroupItemSizes = [\"sm\", \"default\", \"lg\"] as const;\n\ntype RadioGroupItemSize = (typeof radioGroupItemSizes)[number];\n\ntype RadioGroupItemProps = Styled<RadioPrimitive.Root.Props> & {\n  size?: RadioGroupItemSize;\n};\n\nconst on = \":is([data-checked])\";\nconst invalid = \":is([aria-invalid='true'], [data-invalid])\";\n\n// The circle, centered on the label's first line beside top-aligned text, with its hit area.\nconst circle = (size: string) => ({\n  height: size,\n  marginBlock: {\n    default: null,\n    \":is([data-orientation='horizontal'] > *, [data-slot='field-label'] > *)\": `calc((${lineHeights.textSm} - ${size}) / 2)`,\n  },\n  width: size,\n  \"::before\": {\n    insetBlock: `calc((${size} - ${sizes.hitArea}) / 2)`,\n    insetInline: `calc((${size} - ${sizes.hitArea}) / 2)`,\n  },\n});\n\nconst dot = (size: string) => ({\n  height: `calc(${size} / 2)`,\n  width: `calc(${size} / 2)`,\n});\n\nconst styles = stylex.create({\n  group: {\n    display: \"grid\",\n    gap: space.sm,\n    width: \"100%\",\n  },\n  // A round checkbox: the same box, edge, fill and press.\n  item: {\n    alignItems: \"center\",\n    // The checked edge is opaque, so the fill runs under it; clipped, it leaves a seam.\n    backgroundClip: { default: \"padding-box\", [on]: \"border-box\" },\n    backgroundColor: { default: colors.fillSubtle, [on]: colors.infoSolid },\n    borderBlockColor: {\n      default: colors.border,\n      [invalid]: colors.dangerBorder,\n      [on]: colors.infoSolid,\n    },\n    borderInlineColor: {\n      default: colors.border,\n      [invalid]: colors.dangerBorder,\n      [on]: colors.infoSolid,\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: shadows.default, [invalid]: shadows.defaultInvalid },\n    cursor: { default: \"pointer\", \":is([data-disabled])\": \"not-allowed\" },\n    display: \"inline-flex\",\n    flexShrink: 0,\n    justifyContent: \"center\",\n    opacity: { default: 1, \":is([data-disabled])\": opacities.disabled },\n    paddingBlock: 0,\n    paddingInline: 0,\n    position: \"relative\",\n    transform: {\n      default: null,\n      \":active:not([data-disabled])\": presses.icon,\n    },\n    transitionDuration: `${durations.hover}, ${durations.hover}, ${durations.press}`,\n    transitionProperty: \"background-color, border-color, transform\",\n    transitionTimingFunction: `ease, ease, ${easings.out}`,\n    \"::before\": {\n      content: \"''\",\n      position: \"absolute\",\n    },\n  },\n  sm: circle(sizes.iconSm),\n  default: circle(sizes.icon),\n  lg: circle(sizes.iconLg),\n  // The dot grows from the center and fades out; unchecking is the system answering, so it's quick.\n  indicator: {\n    backgroundColor: colors.infoOnSolid,\n    borderStartStartRadius: radii.full,\n    borderStartEndRadius: radii.full,\n    borderEndStartRadius: radii.full,\n    borderEndEndRadius: radii.full,\n    display: \"block\",\n    opacity: {\n      default: 1,\n      \":is([data-starting-style], [data-ending-style])\": 0,\n    },\n    transform: {\n      default: \"scale(1)\",\n      \":is([data-starting-style])\": `scale(${motion.dotScale})`,\n    },\n    // The dot pops a touch past its size and settles. Picked from the keyboard (only a\n    // keyboard focus matches `:focus-visible`), it doesn't move.\n    transitionDuration: {\n      default: durations.popover,\n      \":is([data-ending-style]):not(:focus-visible > *)\": durations.hover,\n      \":is(:focus-visible > *)\": \"0s\",\n    },\n    transitionProperty: {\n      default: \"transform, opacity\",\n      [media.reducedMotion]: \"opacity\",\n    },\n    transitionTimingFunction: `${easings.overshoot}, ${easings.out}`,\n  },\n  dotSm: dot(sizes.iconSm),\n  dotDefault: dot(sizes.icon),\n  dotLg: dot(sizes.iconLg),\n});\n\nconst sizeStyles = {\n  default: [styles.default, styles.dotDefault],\n  lg: [styles.lg, styles.dotLg],\n  sm: [styles.sm, styles.dotSm],\n} satisfies Record<\n  RadioGroupItemSize,\n  [stylex.StyleXStyles, stylex.StyleXStyles]\n>;\n\nfunction RadioGroup({ sx, ...props }: Styled<RadioGroupPrimitive.Props>) {\n  return (\n    <RadioGroupPrimitive\n      data-slot=\"radio-group\"\n      {...props}\n      {...stylex.props(styles.group, sx)}\n    />\n  );\n}\n\nfunction RadioGroupItem({\n  size = \"default\",\n  sx,\n  ...props\n}: RadioGroupItemProps) {\n  const [item, indicator] = sizeStyles[size];\n  return (\n    <RadioPrimitive.Root\n      data-size={size}\n      data-slot=\"radio-group-item\"\n      {...props}\n      {...stylex.props(styles.item, item, sx)}\n    >\n      <RadioPrimitive.Indicator\n        data-slot=\"radio-group-indicator\"\n        {...stylex.props(styles.indicator, indicator)}\n      />\n    </RadioPrimitive.Root>\n  );\n}\n\nexport { RadioGroup, RadioGroupItem, radioGroupItemSizes };\nexport type { RadioGroupItemProps, RadioGroupItemSize };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Usage: https://constella.aayush.cv/docs/components/radio-group/index.md",
  "type": "registry:ui"
}