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

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 +192 -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 +435 -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 +232 -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 +245 -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 +148 -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 +95 -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 +276 -0
  43. package/src/components/bottom-sheet/bottom-sheet.variants.ts +199 -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 +100 -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 +33 -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 +78 -0
  108. package/src/components/field/field.tsx +144 -0
  109. package/src/components/field/field.types.ts +15 -0
  110. package/src/components/field/field.variants.test.ts +216 -0
  111. package/src/components/field/field.variants.ts +136 -0
  112. package/src/components/field/index.ts +21 -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 +64 -0
  144. package/src/components/pressable/index.ts +9 -0
  145. package/src/components/pressable/pressable.tsx +256 -0
  146. package/src/components/pressable/pressable.variants.test.ts +99 -0
  147. package/src/components/pressable/pressable.variants.ts +63 -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 +247 -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 +581 -0
  159. package/src/components/radio/radio.variants.ts +272 -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 +48 -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 +161 -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 +804 -0
  202. package/src/components/slider/slider.variants.ts +607 -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 +286 -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 +672 -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,258 @@
1
+ import { Children, type ReactElement, type ReactNode, useCallback, useEffect, useMemo, useRef } from "react";
2
+ import { useControllableState } from "../../hooks/use-controllable-state";
3
+ import { useFieldContext } from "../field/field.context";
4
+ import { Pressable, type PressableProps } from "../pressable";
5
+ import { type CheckboxContextValue, CheckboxProvider, useCheckboxGroupContext } from "./checkbox.context";
6
+ import type { CheckboxLabelProps } from "./checkbox.types";
7
+ import {
8
+ type CheckboxAlignment,
9
+ type CheckboxColor,
10
+ type CheckboxSize,
11
+ checkboxVariants,
12
+ resolveCheckboxAxes,
13
+ resolveCheckboxHitSlop,
14
+ } from "./checkbox.variants";
15
+ import { CheckboxBox } from "./checkbox-box";
16
+ import { CheckboxGroup } from "./checkbox-group";
17
+ import { CheckboxLabel } from "./checkbox-label";
18
+
19
+ export type CheckboxProps = Omit<PressableProps, "asChild" | "busy" | "disabled" | "onPress"> & {
20
+ /**
21
+ * Identifies this box to an enclosing `Checkbox.Group`. Required inside one,
22
+ * and ignored outside — a lone checkbox holds its own state.
23
+ */
24
+ value?: string;
25
+ /** Controlled checked state. Ignored inside a `Checkbox.Group`, which owns it. */
26
+ isChecked?: boolean;
27
+ /** Starting state while uncontrolled. */
28
+ defaultChecked?: boolean;
29
+ onCheckedChange?: (isChecked: boolean) => void;
30
+ /**
31
+ * Draws a dash rather than a tick and reports `mixed` to a screen reader, for
32
+ * a parent row over children that are only partly selected.
33
+ */
34
+ isIndeterminate?: boolean;
35
+ /** What a ticked box means. Inherited from a `Checkbox.Group` when unset. */
36
+ color?: CheckboxColor;
37
+ /** Inherited from a `Checkbox.Group` when unset. */
38
+ size?: CheckboxSize;
39
+ /** `end` pushes the box to the far edge and fills the row with the label. */
40
+ alignment?: CheckboxAlignment;
41
+ /** Reports an invalid value. Inherited from a group, then from a `Field`. */
42
+ isInvalid?: boolean;
43
+ /** Blocks the press and fades the row. Inherited from a group, then a `Field`. */
44
+ isDisabled?: boolean;
45
+ children?: ReactNode;
46
+ };
47
+
48
+ function CheckboxRoot({
49
+ value,
50
+ isChecked,
51
+ defaultChecked = false,
52
+ onCheckedChange,
53
+ isIndeterminate = false,
54
+ color,
55
+ size,
56
+ alignment,
57
+ isInvalid,
58
+ isDisabled,
59
+ feedback = "fade",
60
+ haptic = "selection",
61
+ className,
62
+ children,
63
+ ...props
64
+ }: CheckboxProps): ReactElement {
65
+ const group = useCheckboxGroupContext();
66
+ const field = useFieldContext();
67
+
68
+ const axes = resolveCheckboxAxes({
69
+ field: field ?? undefined,
70
+ group: group ?? undefined,
71
+ own: { alignment, color, isDisabled, isInvalid, size },
72
+ });
73
+
74
+ // Called unconditionally so hook order never depends on group membership;
75
+ // which state is *read* below is a plain branch. The same shape `Input` uses
76
+ // to share focus with an `Input.Group`.
77
+ const [ownChecked, setOwnChecked] = useControllableState({
78
+ defaultValue: defaultChecked,
79
+ onChange: onCheckedChange,
80
+ value: isChecked,
81
+ });
82
+
83
+ const groupValue = group ? requireValue(value) : undefined;
84
+ const checked = group && groupValue !== undefined ? group.checked.includes(groupValue) : ownChecked;
85
+
86
+ const toggle = useCallback(() => {
87
+ if (group && groupValue !== undefined) {
88
+ group.toggle(groupValue);
89
+ return;
90
+ }
91
+ setOwnChecked(!checked);
92
+ }, [checked, group, groupValue, setOwnChecked]);
93
+
94
+ // Offered to an enclosing `Field` so tapping the label or the description
95
+ // beside the box ticks it. A checkbox in a form is a small square next to a
96
+ // sentence, and the sentence is what people aim at.
97
+ //
98
+ // Registered through a ref-backed trampoline rather than `toggle` itself:
99
+ // `toggle` is a new function every time `checked` changes, and re-registering
100
+ // on each tick would re-render the whole field for nothing.
101
+ const toggleRef = useRef(toggle);
102
+ toggleRef.current = toggle;
103
+ const registerPress = field?.registerPress;
104
+ const stableToggle = useCallback(() => toggleRef.current(), []);
105
+
106
+ useEffect(() => {
107
+ if (!registerPress) return;
108
+ registerPress(stableToggle);
109
+ return () => registerPress(null);
110
+ }, [registerPress, stableToggle]);
111
+
112
+ const context = useMemo<CheckboxContextValue>(
113
+ () => ({
114
+ alignment: axes.alignment,
115
+ color: axes.color,
116
+ isChecked: checked,
117
+ isDisabled: axes.isDisabled,
118
+ isIndeterminate,
119
+ isInvalid: axes.isInvalid,
120
+ size: axes.size,
121
+ }),
122
+ [axes.alignment, axes.color, axes.isDisabled, axes.isInvalid, axes.size, checked, isIndeterminate]
123
+ );
124
+
125
+ const content = useMemo(() => wrapTextChildren(children), [children]);
126
+ // The box draws itself, including everything the fill state changes, so the
127
+ // root is left with the row and nothing else.
128
+ const rootClassName = checkboxVariants(axes).root({ className });
129
+
130
+ return (
131
+ <CheckboxProvider value={context}>
132
+ <Pressable
133
+ accessibilityRole="checkbox"
134
+ accessibilityState={{ checked: isIndeterminate ? "mixed" : checked }}
135
+ className={rootClassName}
136
+ disabled={axes.isDisabled}
137
+ feedback={feedback}
138
+ haptic={haptic}
139
+ hitSlop={resolveCheckboxHitSlop({ hasLabel: Children.count(children) > 0, size: axes.size })}
140
+ onPress={toggle}
141
+ {...props}
142
+ >
143
+ <CheckboxBox />
144
+ {content}
145
+ </Pressable>
146
+ </CheckboxProvider>
147
+ );
148
+ }
149
+
150
+ /**
151
+ * The `value` a grouped checkbox is identified by.
152
+ *
153
+ * A checkbox inside a group with nothing to be called cannot be checked, and the
154
+ * group would silently hold a list that never mentions it. Group membership is
155
+ * not visible in the child's props at compile time, so this cannot be a type
156
+ * error — it throws by name instead, the way `useCheckboxPart` does.
157
+ */
158
+ function requireValue(value: string | undefined): string {
159
+ if (value === undefined) {
160
+ throw new Error("A <Checkbox> inside a <Checkbox.Group> needs a `value` for the group to track it by.");
161
+ }
162
+ return value;
163
+ }
164
+
165
+ /**
166
+ * Wraps bare text children in a `Checkbox.Label`.
167
+ *
168
+ * React Native cannot render a string outside a `<Text>`, so
169
+ * `<Checkbox>Remember me</Checkbox>` would otherwise crash. Consecutive strings
170
+ * and numbers are collected into a single label rather than one each — the row's
171
+ * gap would otherwise space the parts of one sentence apart, the same rule and
172
+ * the same reason as `Badge`.
173
+ */
174
+ function wrapTextChildren(children: ReactNode): ReactNode {
175
+ const items = Children.toArray(children);
176
+ const output: ReactNode[] = [];
177
+ let run: (string | number)[] = [];
178
+
179
+ const flushRun = () => {
180
+ if (run.length === 0) return;
181
+ const props: CheckboxLabelProps = { children: run.join("") };
182
+ output.push(<CheckboxLabel key={`label-${output.length}`} {...props} />);
183
+ run = [];
184
+ };
185
+
186
+ for (const child of items) {
187
+ if (typeof child === "string" || typeof child === "number") {
188
+ run.push(child);
189
+ continue;
190
+ }
191
+ flushRun();
192
+ output.push(child);
193
+ }
194
+ flushRun();
195
+
196
+ return output;
197
+ }
198
+
199
+ /**
200
+ * A box that is ticked or not — on its own, or as one of a group that shares a
201
+ * value list.
202
+ *
203
+ * The root draws the box itself, so `<Checkbox />` is already a complete
204
+ * control. Anything composed inside it lands beside the box and shares its tap
205
+ * target: `<Checkbox.Label>` is the usual thing, and a bare string is wrapped in
206
+ * one automatically.
207
+ *
208
+ * **The whole `Pressable` surface comes through** — `feedback`, `haptic`,
209
+ * `pressedScale`, `onLongPress` and the rest — because the root *is* a
210
+ * `Pressable`. Two defaults differ from a bare one and only two: `fade`, since a
211
+ * spring on a 20pt square reads as a jitter, and a `selection` haptic, since a
212
+ * checkbox is a state toggle and the tick landing is the confirmation. Both are
213
+ * ordinary props, so `haptic={false}` silences it. `onPress` is the one handler
214
+ * that cannot be forwarded — the press *is* the toggle, and `onCheckedChange` is
215
+ * where a side effect goes.
216
+ *
217
+ * State works either way from one hook: pass `isChecked` to control it, or
218
+ * nothing and let it hold its own. Inside a `Checkbox.Group` the group owns it
219
+ * and the box needs a `value` to be tracked by.
220
+ *
221
+ * `isInvalid` and `isDisabled` cascade in from a `Checkbox.Group` and then from
222
+ * a `Field`, so `<Field isInvalid>` reddens the box with nothing said here — and
223
+ * an explicit prop still wins, in either direction.
224
+ *
225
+ * Inside a `Field` the box also hands its toggle back up, so the whole row is
226
+ * the target: tapping a `Field.Label` or the `Field.Description` under it ticks
227
+ * the box. That is what lets a checkbox in a form be a bare `<Checkbox />` with
228
+ * the field naming it, rather than a `Checkbox.Label` repeating the name.
229
+ *
230
+ * @example
231
+ * <Checkbox isChecked={agreed} onCheckedChange={setAgreed}>
232
+ * <Checkbox.Label>I accept the terms</Checkbox.Label>
233
+ * </Checkbox>
234
+ *
235
+ * @example
236
+ * <Checkbox.Group checked={channels} onChecked={setChannels}>
237
+ * <Checkbox value="email">Email</Checkbox>
238
+ * <Checkbox value="sms">SMS</Checkbox>
239
+ * </Checkbox.Group>
240
+ *
241
+ * @example
242
+ * <Checkbox
243
+ * alignment="end"
244
+ * color="success"
245
+ * isIndeterminate={some && !all}
246
+ * isChecked={all}
247
+ * onCheckedChange={toggleAll}
248
+ * >
249
+ * <Checkbox.Label>Select all</Checkbox.Label>
250
+ * </Checkbox>
251
+ */
252
+ export const Checkbox = Object.assign(CheckboxRoot, {
253
+ /** The box's text, inside its tap target — tapping the words toggles it. */
254
+ Label: CheckboxLabel,
255
+ /** Owns the checked list for the checkboxes inside it, and their shared axes. */
256
+ Group: CheckboxGroup,
257
+ displayName: "DelacourUI.Checkbox",
258
+ });
@@ -0,0 +1,14 @@
1
+ import type { TextPresetProps } from "../text";
2
+
3
+ /**
4
+ * The shape of the checkbox's text part.
5
+ *
6
+ * Lives in a leaf rather than in `checkbox-label.tsx` because the root imports
7
+ * it too — it wraps bare string children in a `Checkbox.Label`, and reaching for
8
+ * the type through the part file would be reaching past what it is for.
9
+ *
10
+ * `variant` is already omitted by `TextPresetProps`: the part *is* one, and
11
+ * naming a second would let a caller turn a checkbox's label into a display
12
+ * heading and lose the scale the component exists to keep.
13
+ */
14
+ export type CheckboxLabelProps = TextPresetProps;