@delacour/react-native-ui 0.1.0-alpha.20260925053522

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +113 -0
  2. package/package.json +125 -0
  3. package/src/components/accordion/AGENTS.md +193 -0
  4. package/src/components/accordion/accordion-content.tsx +125 -0
  5. package/src/components/accordion/accordion-description.tsx +17 -0
  6. package/src/components/accordion/accordion-indicator.tsx +104 -0
  7. package/src/components/accordion/accordion-item.tsx +78 -0
  8. package/src/components/accordion/accordion-title.tsx +18 -0
  9. package/src/components/accordion/accordion-trigger.tsx +148 -0
  10. package/src/components/accordion/accordion.context.tsx +153 -0
  11. package/src/components/accordion/accordion.tsx +253 -0
  12. package/src/components/accordion/accordion.types.ts +11 -0
  13. package/src/components/accordion/accordion.variants.test.ts +434 -0
  14. package/src/components/accordion/accordion.variants.ts +358 -0
  15. package/src/components/accordion/index.ts +45 -0
  16. package/src/components/badge/AGENTS.md +83 -0
  17. package/src/components/badge/badge-close-button.tsx +48 -0
  18. package/src/components/badge/badge-end-content.tsx +15 -0
  19. package/src/components/badge/badge-label.tsx +24 -0
  20. package/src/components/badge/badge-start-content.tsx +16 -0
  21. package/src/components/badge/badge.context.tsx +64 -0
  22. package/src/components/badge/badge.tsx +192 -0
  23. package/src/components/badge/badge.types.ts +10 -0
  24. package/src/components/badge/badge.variants.test.ts +269 -0
  25. package/src/components/badge/badge.variants.ts +238 -0
  26. package/src/components/badge/index.ts +17 -0
  27. package/src/components/bottom-sheet/AGENTS.md +242 -0
  28. package/src/components/bottom-sheet/bottom-sheet-background.tsx +35 -0
  29. package/src/components/bottom-sheet/bottom-sheet-close.tsx +60 -0
  30. package/src/components/bottom-sheet/bottom-sheet-container.tsx +246 -0
  31. package/src/components/bottom-sheet/bottom-sheet-content.tsx +66 -0
  32. package/src/components/bottom-sheet/bottom-sheet-description.tsx +29 -0
  33. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +150 -0
  34. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +47 -0
  35. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +180 -0
  36. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +72 -0
  37. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +105 -0
  38. package/src/components/bottom-sheet/bottom-sheet-title.tsx +24 -0
  39. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +73 -0
  40. package/src/components/bottom-sheet/bottom-sheet.context.tsx +135 -0
  41. package/src/components/bottom-sheet/bottom-sheet.tsx +125 -0
  42. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +293 -0
  43. package/src/components/bottom-sheet/bottom-sheet.variants.ts +220 -0
  44. package/src/components/bottom-sheet/index.ts +40 -0
  45. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +125 -0
  46. package/src/components/button/AGENTS.md +217 -0
  47. package/src/components/button/button-end-content.tsx +15 -0
  48. package/src/components/button/button-group-separator.tsx +37 -0
  49. package/src/components/button/button-group-text.tsx +101 -0
  50. package/src/components/button/button-group.tsx +158 -0
  51. package/src/components/button/button-label.tsx +24 -0
  52. package/src/components/button/button-start-content.tsx +15 -0
  53. package/src/components/button/button.context.tsx +187 -0
  54. package/src/components/button/button.tsx +273 -0
  55. package/src/components/button/button.types.ts +10 -0
  56. package/src/components/button/button.variants.test.ts +811 -0
  57. package/src/components/button/button.variants.ts +480 -0
  58. package/src/components/button/index.ts +50 -0
  59. package/src/components/chart/AGENTS.md +210 -0
  60. package/src/components/chart/chart-area.tsx +93 -0
  61. package/src/components/chart/chart-bar.tsx +103 -0
  62. package/src/components/chart/chart-bars.tsx +75 -0
  63. package/src/components/chart/chart-candlestick.tsx +60 -0
  64. package/src/components/chart/chart-grid.tsx +33 -0
  65. package/src/components/chart/chart-legend.tsx +41 -0
  66. package/src/components/chart/chart-line.tsx +50 -0
  67. package/src/components/chart/chart-scatter.tsx +35 -0
  68. package/src/components/chart/chart-tooltip-dot.tsx +70 -0
  69. package/src/components/chart/chart-tooltip-x.tsx +121 -0
  70. package/src/components/chart/chart-tooltip-y.tsx +55 -0
  71. package/src/components/chart/chart-tooltip.tsx +133 -0
  72. package/src/components/chart/chart-x-axis.tsx +27 -0
  73. package/src/components/chart/chart-y-axis.tsx +21 -0
  74. package/src/components/chart/chart.context.tsx +127 -0
  75. package/src/components/chart/chart.tsx +448 -0
  76. package/src/components/chart/chart.types.ts +108 -0
  77. package/src/components/chart/chart.variants.test.ts +893 -0
  78. package/src/components/chart/chart.variants.ts +726 -0
  79. package/src/components/chart/index.ts +103 -0
  80. package/src/components/chart/pie-chart-center.tsx +40 -0
  81. package/src/components/chart/pie-chart-label.tsx +56 -0
  82. package/src/components/chart/pie-chart-slice.tsx +45 -0
  83. package/src/components/chart/pie-chart-tooltip.tsx +85 -0
  84. package/src/components/chart/pie-chart.context.tsx +77 -0
  85. package/src/components/chart/pie-chart.tsx +247 -0
  86. package/src/components/chart/use-chart-font.ts +25 -0
  87. package/src/components/chart/use-chart-palette.ts +38 -0
  88. package/src/components/checkbox/AGENTS.md +185 -0
  89. package/src/components/checkbox/checkbox-box.tsx +152 -0
  90. package/src/components/checkbox/checkbox-group.tsx +107 -0
  91. package/src/components/checkbox/checkbox-label.tsx +34 -0
  92. package/src/components/checkbox/checkbox.context.tsx +136 -0
  93. package/src/components/checkbox/checkbox.tsx +258 -0
  94. package/src/components/checkbox/checkbox.types.ts +14 -0
  95. package/src/components/checkbox/checkbox.variants.test.ts +634 -0
  96. package/src/components/checkbox/checkbox.variants.ts +484 -0
  97. package/src/components/checkbox/index.ts +42 -0
  98. package/src/components/field/AGENTS.md +112 -0
  99. package/src/components/field/field-content.tsx +24 -0
  100. package/src/components/field/field-description.tsx +29 -0
  101. package/src/components/field/field-error.tsx +61 -0
  102. package/src/components/field/field-group.tsx +22 -0
  103. package/src/components/field/field-label.tsx +47 -0
  104. package/src/components/field/field-legend.tsx +34 -0
  105. package/src/components/field/field-separator.tsx +81 -0
  106. package/src/components/field/field-set.tsx +22 -0
  107. package/src/components/field/field.context.tsx +95 -0
  108. package/src/components/field/field.tsx +157 -0
  109. package/src/components/field/field.types.ts +15 -0
  110. package/src/components/field/field.variants.test.ts +240 -0
  111. package/src/components/field/field.variants.ts +156 -0
  112. package/src/components/field/index.ts +22 -0
  113. package/src/components/icon/AGENTS.md +104 -0
  114. package/src/components/icon/icon.context.tsx +29 -0
  115. package/src/components/icon/icon.tsx +92 -0
  116. package/src/components/icon/icon.variants.test.ts +111 -0
  117. package/src/components/icon/icon.variants.ts +79 -0
  118. package/src/components/icon/index.ts +11 -0
  119. package/src/components/input/AGENTS.md +126 -0
  120. package/src/components/input/index.ts +27 -0
  121. package/src/components/input/input-group-decorator.tsx +84 -0
  122. package/src/components/input/input-group-prefix.tsx +16 -0
  123. package/src/components/input/input-group-suffix.tsx +15 -0
  124. package/src/components/input/input-group.tsx +140 -0
  125. package/src/components/input/input.context.tsx +87 -0
  126. package/src/components/input/input.tsx +184 -0
  127. package/src/components/input/input.types.ts +10 -0
  128. package/src/components/input/input.variants.test.ts +369 -0
  129. package/src/components/input/input.variants.ts +329 -0
  130. package/src/components/list-group/AGENTS.md +67 -0
  131. package/src/components/list-group/index.ts +20 -0
  132. package/src/components/list-group/list-group-item-content.tsx +10 -0
  133. package/src/components/list-group/list-group-item-description.tsx +12 -0
  134. package/src/components/list-group/list-group-item-prefix.tsx +30 -0
  135. package/src/components/list-group/list-group-item-suffix.tsx +55 -0
  136. package/src/components/list-group/list-group-item-title.tsx +17 -0
  137. package/src/components/list-group/list-group-item.tsx +87 -0
  138. package/src/components/list-group/list-group.context.tsx +65 -0
  139. package/src/components/list-group/list-group.tsx +116 -0
  140. package/src/components/list-group/list-group.types.ts +14 -0
  141. package/src/components/list-group/list-group.variants.test.ts +223 -0
  142. package/src/components/list-group/list-group.variants.ts +107 -0
  143. package/src/components/pressable/AGENTS.md +74 -0
  144. package/src/components/pressable/index.ts +9 -0
  145. package/src/components/pressable/pressable.tsx +261 -0
  146. package/src/components/pressable/pressable.variants.test.ts +128 -0
  147. package/src/components/pressable/pressable.variants.ts +80 -0
  148. package/src/components/provider/AGENTS.md +90 -0
  149. package/src/components/provider/index.ts +1 -0
  150. package/src/components/provider/provider.tsx +105 -0
  151. package/src/components/radio/AGENTS.md +245 -0
  152. package/src/components/radio/index.ts +35 -0
  153. package/src/components/radio/radio-group.tsx +126 -0
  154. package/src/components/radio/radio-indicator.tsx +91 -0
  155. package/src/components/radio/radio-label.tsx +39 -0
  156. package/src/components/radio/radio.context.tsx +132 -0
  157. package/src/components/radio/radio.tsx +215 -0
  158. package/src/components/radio/radio.variants.test.ts +580 -0
  159. package/src/components/radio/radio.variants.ts +271 -0
  160. package/src/components/screen/AGENTS.md +287 -0
  161. package/src/components/screen/index.ts +85 -0
  162. package/src/components/screen/screen-chat-list.tsx +485 -0
  163. package/src/components/screen/screen-content.tsx +69 -0
  164. package/src/components/screen/screen-debug.ts +43 -0
  165. package/src/components/screen/screen-error.tsx +61 -0
  166. package/src/components/screen/screen-flat-list.tsx +88 -0
  167. package/src/components/screen/screen-footer-background.tsx +60 -0
  168. package/src/components/screen/screen-footer.tsx +170 -0
  169. package/src/components/screen/screen-header.tsx +36 -0
  170. package/src/components/screen/screen-legend-list.tsx +105 -0
  171. package/src/components/screen/screen-list-component.tsx +23 -0
  172. package/src/components/screen/screen-loading.tsx +50 -0
  173. package/src/components/screen/screen-navbar-back-button.tsx +79 -0
  174. package/src/components/screen/screen-navbar-background.tsx +45 -0
  175. package/src/components/screen/screen-navbar-subtitle.tsx +26 -0
  176. package/src/components/screen/screen-navbar-title.tsx +31 -0
  177. package/src/components/screen/screen-navbar.tsx +154 -0
  178. package/src/components/screen/screen-root.tsx +38 -0
  179. package/src/components/screen/screen-scroll-area.tsx +116 -0
  180. package/src/components/screen/screen-scroll-shadow.tsx +186 -0
  181. package/src/components/screen/screen-section-list.tsx +81 -0
  182. package/src/components/screen/screen-view.tsx +59 -0
  183. package/src/components/screen/screen.context.tsx +167 -0
  184. package/src/components/screen/screen.tsx +114 -0
  185. package/src/components/screen/screen.types.ts +66 -0
  186. package/src/components/screen/screen.variants.test.ts +602 -0
  187. package/src/components/screen/screen.variants.ts +475 -0
  188. package/src/components/screen/use-screen-scroll-insets.ts +218 -0
  189. package/src/components/separator/AGENTS.md +30 -0
  190. package/src/components/separator/index.ts +8 -0
  191. package/src/components/separator/separator.tsx +85 -0
  192. package/src/components/slider/AGENTS.md +274 -0
  193. package/src/components/slider/index.ts +50 -0
  194. package/src/components/slider/slider-fill.tsx +68 -0
  195. package/src/components/slider/slider-output.tsx +55 -0
  196. package/src/components/slider/slider-thumb.tsx +193 -0
  197. package/src/components/slider/slider-track.tsx +233 -0
  198. package/src/components/slider/slider.context.tsx +118 -0
  199. package/src/components/slider/slider.tsx +321 -0
  200. package/src/components/slider/slider.types.ts +26 -0
  201. package/src/components/slider/slider.variants.test.ts +856 -0
  202. package/src/components/slider/slider.variants.ts +661 -0
  203. package/src/components/spinner/AGENTS.md +73 -0
  204. package/src/components/spinner/index.ts +17 -0
  205. package/src/components/spinner/spinner-arc.tsx +90 -0
  206. package/src/components/spinner/spinner-content.tsx +63 -0
  207. package/src/components/spinner/spinner.context.tsx +52 -0
  208. package/src/components/spinner/spinner.tsx +128 -0
  209. package/src/components/spinner/spinner.variants.test.ts +273 -0
  210. package/src/components/spinner/spinner.variants.ts +187 -0
  211. package/src/components/switch/AGENTS.md +213 -0
  212. package/src/components/switch/index.ts +42 -0
  213. package/src/components/switch/switch-content.tsx +111 -0
  214. package/src/components/switch/switch-end-content.tsx +18 -0
  215. package/src/components/switch/switch-start-content.tsx +20 -0
  216. package/src/components/switch/switch-thumb.tsx +102 -0
  217. package/src/components/switch/switch.context.tsx +79 -0
  218. package/src/components/switch/switch.tsx +423 -0
  219. package/src/components/switch/switch.types.ts +14 -0
  220. package/src/components/switch/switch.variants.test.ts +570 -0
  221. package/src/components/switch/switch.variants.ts +511 -0
  222. package/src/components/tabs/AGENTS.md +287 -0
  223. package/src/components/tabs/index.ts +69 -0
  224. package/src/components/tabs/tabs-content.tsx +65 -0
  225. package/src/components/tabs/tabs-indicator.tsx +97 -0
  226. package/src/components/tabs/tabs-label.tsx +62 -0
  227. package/src/components/tabs/tabs-list.tsx +139 -0
  228. package/src/components/tabs/tabs-pager.tsx +59 -0
  229. package/src/components/tabs/tabs-scroll-view.tsx +171 -0
  230. package/src/components/tabs/tabs-separator.tsx +73 -0
  231. package/src/components/tabs/tabs-trigger.tsx +210 -0
  232. package/src/components/tabs/tabs.context.tsx +294 -0
  233. package/src/components/tabs/tabs.tsx +435 -0
  234. package/src/components/tabs/tabs.types.ts +13 -0
  235. package/src/components/tabs/tabs.variants.test.ts +1020 -0
  236. package/src/components/tabs/tabs.variants.ts +670 -0
  237. package/src/components/text/AGENTS.md +95 -0
  238. package/src/components/text/index.ts +25 -0
  239. package/src/components/text/text.context.tsx +60 -0
  240. package/src/components/text/text.tsx +251 -0
  241. package/src/components/text/text.variants.test.ts +422 -0
  242. package/src/components/text/text.variants.ts +282 -0
  243. package/src/display-name.test.ts +145 -0
  244. package/src/docs.test.ts +98 -0
  245. package/src/expo/navigation-theme.tsx +58 -0
  246. package/src/hooks/use-controllable-state.ts +45 -0
  247. package/src/hooks/use-keyboard-state-sync.tsx +147 -0
  248. package/src/hooks/use-navigation-theme.ts +78 -0
  249. package/src/hooks/use-theme-color.ts +44 -0
  250. package/src/icons/central.ts +1 -0
  251. package/src/lib/cn.test.ts +136 -0
  252. package/src/lib/cn.ts +29 -0
  253. package/src/lib/color.test.ts +80 -0
  254. package/src/lib/color.ts +79 -0
  255. package/src/lib/compose-refs.test.ts +64 -0
  256. package/src/lib/compose-refs.ts +37 -0
  257. package/src/lib/keyboard-animation.test.ts +33 -0
  258. package/src/lib/keyboard-animation.ts +28 -0
  259. package/src/lib/merge-props.test.ts +82 -0
  260. package/src/lib/merge-props.ts +47 -0
  261. package/src/lib/navigation-theme.test.ts +49 -0
  262. package/src/lib/navigation-theme.ts +53 -0
  263. package/src/lib/slot.tsx +45 -0
  264. package/src/lib/tv.ts +20 -0
  265. package/src/styles/base.css +2 -0
  266. package/src/styles/geometry.test.ts +117 -0
  267. package/src/styles/index.css +3 -0
  268. package/src/styles/theme-tokens.test.ts +252 -0
  269. package/src/styles/theme.css +447 -0
  270. package/src/styles/tokens.css +132 -0
  271. package/src/styles/tokens.test.ts +181 -0
  272. package/src/styles/tokens.ts +86 -0
  273. package/src/uniwind-env.d.ts +1 -0
@@ -0,0 +1,215 @@
1
+ import { Children, isValidElement, type ReactElement, type ReactNode, useCallback, useMemo } from "react";
2
+ import { useFieldContext } from "../field/field.context";
3
+ import { Pressable, type PressableProps } from "../pressable";
4
+ import { TextClassProvider } from "../text/text.context";
5
+ import { resolveTextClass } from "../text/text.variants";
6
+ import { type RadioContextValue, RadioProvider, useRadioGroupContext } from "./radio.context";
7
+ import {
8
+ RADIO_LABEL_TEXT_SIZE,
9
+ type RadioSize,
10
+ type RadioVariant,
11
+ radioVariants,
12
+ resolveIndicatorPlacement,
13
+ resolveRadioState,
14
+ } from "./radio.variants";
15
+ import { RadioGroup } from "./radio-group";
16
+ import { RadioIndicator } from "./radio-indicator";
17
+ import { RadioLabel } from "./radio-label";
18
+
19
+ export type RadioProps = Omit<PressableProps, "asChild" | "busy" | "children" | "disabled"> & {
20
+ /** Identifies this radio to its `Radio.Group`. Required there; inert without one. */
21
+ value?: string;
22
+ /** Selected state for a radio standing on its own. Ignored inside a `Radio.Group`. */
23
+ isSelected?: boolean;
24
+ /** Initial selected state while uncontrolled and ungrouped. */
25
+ defaultSelected?: boolean;
26
+ /**
27
+ * Called when this radio becomes the selection. Ignored inside a
28
+ * `Radio.Group`, which owns the callback.
29
+ *
30
+ * Only ever called with `true`: a radio does not deselect itself, which is
31
+ * `Switch` and `Checkbox` semantics rather than radio ones.
32
+ */
33
+ onSelected?: (isSelected: boolean) => void;
34
+ /** Ignored inside a `Radio.Group` — the group owns this axis. */
35
+ size?: RadioSize;
36
+ /** Ignored inside a `Radio.Group` — the group owns this axis. */
37
+ variant?: RadioVariant;
38
+ /** Wins over an enclosing `Field`. A group that names it wins over both. */
39
+ isInvalid?: boolean;
40
+ /** Wins over an enclosing `Field`. A group that names it wins over both. */
41
+ isDisabled?: boolean;
42
+ children?: ReactNode;
43
+ };
44
+
45
+ function RadioRoot({
46
+ value,
47
+ isSelected,
48
+ defaultSelected,
49
+ onSelected,
50
+ size,
51
+ variant,
52
+ isInvalid,
53
+ isDisabled,
54
+ feedback = "fade",
55
+ hitSlop = 8,
56
+ className,
57
+ children,
58
+ onPress,
59
+ ...props
60
+ }: RadioProps): ReactElement {
61
+ const group = useRadioGroupContext();
62
+ const field = useFieldContext();
63
+
64
+ const state = resolveRadioState({
65
+ field,
66
+ group,
67
+ own: { isDisabled, isInvalid, isSelected, size, value, variant },
68
+ });
69
+
70
+ if (process.env.NODE_ENV !== "production" && group && value === undefined) {
71
+ console.warn("Radio: a <Radio> inside a <Radio.Group> needs a `value`, or it can never be selected.");
72
+ }
73
+
74
+ const handlePress = useCallback(() => {
75
+ if (group) {
76
+ if (value !== undefined) group.select(value);
77
+ } else if (!state.isSelected) {
78
+ onSelected?.(true);
79
+ }
80
+ onPress?.();
81
+ }, [group, onPress, onSelected, state.isSelected, value]);
82
+
83
+ const context = useMemo<RadioContextValue>(
84
+ () => ({
85
+ isDisabled: state.isDisabled,
86
+ isInvalid: state.isInvalid,
87
+ isSelected: state.isSelected,
88
+ size: state.size,
89
+ variant: state.variant,
90
+ }),
91
+ [state.isDisabled, state.isInvalid, state.isSelected, state.size, state.variant]
92
+ );
93
+
94
+ const { content, placement } = useMemo(() => withIndicator(children), [children]);
95
+
96
+ // One treatment covers the whole subtree — a radio row has a label and no
97
+ // description part — which is the condition for publishing into the cascade.
98
+ const labelClass = useMemo(
99
+ () => resolveTextClass({ size: RADIO_LABEL_TEXT_SIZE[state.size], variant: "label" }),
100
+ [state.size]
101
+ );
102
+
103
+ return (
104
+ <RadioProvider value={context}>
105
+ <Pressable
106
+ accessibilityRole="radio"
107
+ accessibilityState={{ checked: state.isSelected }}
108
+ className={radioVariants({
109
+ isDisabled: state.isDisabled,
110
+ isIndicatorTrailing: placement === "end",
111
+ size: state.size,
112
+ }).root({ className })}
113
+ disabled={state.isDisabled}
114
+ feedback={feedback}
115
+ hitSlop={hitSlop}
116
+ onPress={handlePress}
117
+ {...props}
118
+ >
119
+ <TextClassProvider value={labelClass}>{content}</TextClassProvider>
120
+ </Pressable>
121
+ </RadioProvider>
122
+ );
123
+ }
124
+
125
+ /**
126
+ * Wraps bare text in a `Radio.Label` and settles where the indicator sits.
127
+ *
128
+ * React Native cannot render a string outside a `<Text>`, so `<Radio>Yes</Radio>`
129
+ * would otherwise crash. Consecutive strings and numbers collapse into one label
130
+ * rather than one each — `Plan {n}` is a single piece of text, and wrapping the
131
+ * parts separately would space them apart by the row's own gap.
132
+ *
133
+ * An indicator is composed in at the front only when the children hold none,
134
+ * which is what buys a settings row for free: place a `<Radio.Indicator />` last
135
+ * and the row lays out `[label and description] [ring]`, spread to the far edge,
136
+ * with nothing else said. {@link resolveIndicatorPlacement} is that decision and
137
+ * is pure, so `bun test` reaches it.
138
+ *
139
+ * Lives here rather than with the parts: it is the root that wraps its own
140
+ * children, and importing it from a part would close a cycle. See AGENTS.md.
141
+ */
142
+ function withIndicator(children: ReactNode): { content: ReactNode; placement: "start" | "end" | "none" } {
143
+ const items = Children.toArray(children);
144
+ const output: ReactNode[] = [];
145
+ const isIndicator: boolean[] = [];
146
+ let run: (string | number)[] = [];
147
+
148
+ const flushRun = () => {
149
+ if (run.length === 0) return;
150
+ output.push(<RadioLabel key={`label-${output.length}`}>{run.join("")}</RadioLabel>);
151
+ isIndicator.push(false);
152
+ run = [];
153
+ };
154
+
155
+ for (const child of items) {
156
+ if (typeof child === "string" || typeof child === "number") {
157
+ run.push(child);
158
+ continue;
159
+ }
160
+ flushRun();
161
+ output.push(child);
162
+ isIndicator.push(isValidElement(child) && child.type === RadioIndicator);
163
+ }
164
+ flushRun();
165
+
166
+ const placement = resolveIndicatorPlacement(isIndicator);
167
+ if (placement !== "none") return { content: output, placement };
168
+
169
+ return { content: [<RadioIndicator key="indicator" />, ...output], placement: "start" };
170
+ }
171
+
172
+ /**
173
+ * One option in a set where exactly one can be chosen.
174
+ *
175
+ * The whole row is the control: the ring, the label beside it and the space
176
+ * between them are one tap target, so a press anywhere on the row selects. A
177
+ * `Radio.Indicator` is composed in automatically and plain string children are
178
+ * wrapped in a `Radio.Label`, so the shortest thing that works is `<Radio
179
+ * value="pro">Pro</Radio>`.
180
+ *
181
+ * **A radio works with or without a group.** Inside a `Radio.Group` its `value`
182
+ * identifies it and the group owns the selection, the size and the variant.
183
+ * Outside one it is driven by its own `isSelected` and `onSelected`.
184
+ *
185
+ * Nearest wins for state: a group first, this radio's own props next, an
186
+ * enclosing `Field` last — so a radio inside an invalid `Field` turns destructive with
187
+ * nothing said at the call site, while `isInvalid={false}` still opts out.
188
+ *
189
+ * A radio whose row holds no text needs an `accessibilityLabel`, the same rule an
190
+ * icon-sized `Button` follows.
191
+ *
192
+ * @example
193
+ * <Radio.Group onSelected={setPlan} selected={plan ?? null} accessibilityLabel="Plan">
194
+ * <Radio value="free">Free</Radio>
195
+ * <Radio value="pro">Pro</Radio>
196
+ * </Radio.Group>
197
+ *
198
+ * @example
199
+ * <Radio value="express">
200
+ * <Radio.Label>Express</Radio.Label>
201
+ * <Text.Caption>Arrives tomorrow.</Text.Caption>
202
+ * </Radio>
203
+ *
204
+ * @example
205
+ * <Radio accessibilityLabel="Express delivery" isSelected={isExpress} onSelected={setExpress} />
206
+ */
207
+ export const Radio = Object.assign(RadioRoot, {
208
+ /** The radio's text, inside its tap target. Takes its size step from the radio's own. */
209
+ Label: RadioLabel,
210
+ /** The ring and its dot. Composed in automatically; write it out to move or replace it. */
211
+ Indicator: RadioIndicator,
212
+ /** Groups radios and owns which one is selected. */
213
+ Group: RadioGroup,
214
+ displayName: "DelacourUI.Radio",
215
+ });