@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,132 @@
1
+ @theme {
2
+ /*
3
+ * The one number every generic corner is derived from, in the `@theme
4
+ * inline` block at the bottom of this file. shadcn's own scale, so a
5
+ * `--radius` copied out of a web app retunes every rounded edge here at
6
+ * once — and unlike the older `calc(var(--radius) - 4px)` form some theme
7
+ * generators still emit, a multiplier scales rather than shifts, so a
8
+ * square-cornered theme stays square at every step.
9
+ *
10
+ * It lives in `tokens.css` rather than beside the palette because
11
+ * `apps/web` imports only this file; a `--radius` in `theme.css` would
12
+ * leave every derived radius undefined on the docs site.
13
+ */
14
+ --radius: 0.625rem;
15
+
16
+ --text-xs: 12px;
17
+ --text-sm: 14px;
18
+ --text-base: 16px;
19
+ --text-lg: 18px;
20
+ --text-xl: 20px;
21
+ --text-2xl: 24px;
22
+ --text-3xl: 30px;
23
+
24
+ /*
25
+ * The button's three scales, stepped together: the height — which is also
26
+ * the square footprint at an `icon-*` size — the label's type, and the
27
+ * corner.
28
+ *
29
+ * The corner is half the height at every step, so a button is a capsule and
30
+ * a square one a circle. A token rather than `rounded-full` because the
31
+ * shape is a default, not a law: a kit that wants squarer buttons changes
32
+ * three numbers here and touches no component. `tokens.test.ts` keeps each
33
+ * under half its height — past that the renderer clamps and the number
34
+ * stops meaning what it says.
35
+ */
36
+ --spacing-button-sm: 36px;
37
+ --spacing-button-md: 44px;
38
+ --spacing-button-lg: 52px;
39
+
40
+ --text-button-sm: 14px;
41
+ --text-button-md: 16px;
42
+ --text-button-lg: 18px;
43
+
44
+ --radius-button-sm: 18px;
45
+ --radius-button-md: 22px;
46
+ --radius-button-lg: 26px;
47
+
48
+ /*
49
+ * Field heights, and the type scale paired step-for-step with them.
50
+ *
51
+ * A scale of its own rather than `--spacing-button-*`, even though the
52
+ * numbers agree today: a control that borrowed the button's would retune
53
+ * silently whenever the button's did.
54
+ */
55
+ --spacing-input-sm: 36px;
56
+ --spacing-input-md: 44px;
57
+ --spacing-input-lg: 52px;
58
+
59
+ --text-input-sm: 14px;
60
+ --text-input-md: 16px;
61
+ --text-input-lg: 18px;
62
+
63
+ /*
64
+ * The icon scale, shared by `Icon` and `Spinner` so a named size means the
65
+ * same edge length in both and one can replace the other without the
66
+ * layout moving. `size-icon-md`.
67
+ *
68
+ * A component's own size axis indexes into this scale rather than restating
69
+ * a number: a button's sm/md/lg icon is icon-sm/md/lg.
70
+ *
71
+ * It sizes more than glyphs. A `Checkbox`'s square reads it too, two steps
72
+ * above the tick it holds, rather than minting a scale that would have to be
73
+ * retuned in step with this one forever.
74
+ */
75
+ --spacing-icon-xs: 14px;
76
+ --spacing-icon-sm: 16px;
77
+ --spacing-icon-md: 18px;
78
+ --spacing-icon-lg: 20px;
79
+ --spacing-icon-xl: 24px;
80
+ --spacing-icon-2xl: 32px;
81
+
82
+ /*
83
+ * Screen chrome. `h-navbar-row` is the navbar's control row on its own —
84
+ * the safe-area band above it is padding, not part of this number, so one
85
+ * value holds across every device.
86
+ *
87
+ * `px-screen-gutter` is the horizontal gutter `Screen.Header`,
88
+ * `Screen.Navbar` and a screen's own content all line up against. It is one
89
+ * token rather than three `px-5`s so a change moves every edge together.
90
+ *
91
+ * The footer's padding is deliberately NOT here — see `screen.variants.ts`.
92
+ */
93
+ --spacing-navbar-row: 56px;
94
+ --spacing-screen-gutter: 20px;
95
+
96
+ /*
97
+ * A chart's height. It has no intrinsic one — a canvas fills whatever box
98
+ * it is given — so without a token every chart on a dashboard would be a
99
+ * different height and the rows would not line up.
100
+ *
101
+ * A height rather than an aspect ratio on purpose: two charts side by side
102
+ * at different widths must still agree, and `aspect-video` would give them
103
+ * different heights. Registering the names with tailwind-merge is what lets
104
+ * a caller's `aspect-video h-auto` cleanly win when they want that instead.
105
+ */
106
+ --spacing-chart-sm: 160px;
107
+ --spacing-chart-md: 220px;
108
+ --spacing-chart-lg: 280px;
109
+ }
110
+
111
+ /*
112
+ * The generic corner scale, derived from `--radius`.
113
+ *
114
+ * `inline` for the same reason the palette uses it: a utility has to read
115
+ * `var(--radius)` itself, so a theme that redeclares `--radius` reaches
116
+ * `rounded-lg` rather than a copy taken when the scale was compiled.
117
+ *
118
+ * `--radius-button-*` above is deliberately NOT part of this. A button's corner
119
+ * is half its own height — a shape, not a step on a scale — and it must not
120
+ * move when a consumer retunes `--radius`.
121
+ */
122
+ @theme inline {
123
+ --radius-xs: calc(var(--radius) * 0.4);
124
+ --radius-sm: calc(var(--radius) * 0.6);
125
+ --radius-md: calc(var(--radius) * 0.8);
126
+ --radius-lg: var(--radius);
127
+ --radius-xl: calc(var(--radius) * 1.4);
128
+ --radius-2xl: calc(var(--radius) * 1.8);
129
+ --radius-3xl: calc(var(--radius) * 2.2);
130
+ --radius-4xl: calc(var(--radius) * 2.6);
131
+ --radius-full: 9999px;
132
+ }
@@ -0,0 +1,181 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ BUTTON_RADIUS_TOKENS,
6
+ BUTTON_SIZE_TOKENS,
7
+ BUTTON_TEXT_TOKENS,
8
+ CHART_SIZE_TOKENS,
9
+ ICON_SIZE_TOKENS,
10
+ INPUT_SIZE_TOKENS,
11
+ INPUT_TEXT_TOKENS,
12
+ SCREEN_SIZE_TOKENS,
13
+ } from "./tokens";
14
+
15
+ const CSS = readFileSync(join(import.meta.dirname, "tokens.css"), "utf-8");
16
+
17
+ /** Every `--name: 12px` declaration in `tokens.css`, as a name → points map. */
18
+ function declarations(): Map<string, number> {
19
+ const found = new Map<string, number>();
20
+ for (const [, name, value] of CSS.matchAll(/--([\w-]+):\s*([\d.]+)px;/g)) {
21
+ found.set(name, Number(value));
22
+ }
23
+ return found;
24
+ }
25
+
26
+ const TOKENS = declarations();
27
+
28
+ /** Points behind a token, failing loudly rather than yielding NaN. */
29
+ function px(name: string): number {
30
+ const value = TOKENS.get(name);
31
+ if (value === undefined) throw new Error(`tokens.css defines no --${name}`);
32
+ return value;
33
+ }
34
+
35
+ describe("tokens.ts and tokens.css", () => {
36
+ // The registry drives `cn`'s tailwind-merge extension. A token in the CSS
37
+ // that is missing here is an override that silently stops working; a name
38
+ // here with no CSS behind it is a class that compiles to nothing.
39
+ test("declare exactly the same spacing tokens", () => {
40
+ const declared = [...TOKENS.keys()]
41
+ .filter((name) => name.startsWith("spacing-"))
42
+ .map((name) => name.replace("spacing-", ""))
43
+ .sort();
44
+ const registered = [
45
+ ...BUTTON_SIZE_TOKENS,
46
+ ...CHART_SIZE_TOKENS,
47
+ ...ICON_SIZE_TOKENS,
48
+ ...INPUT_SIZE_TOKENS,
49
+ ...SCREEN_SIZE_TOKENS,
50
+ ].sort();
51
+ expect(declared).toEqual(registered);
52
+ });
53
+
54
+ test("declare exactly the same button text tokens", () => {
55
+ const declared = [...TOKENS.keys()]
56
+ .filter((name) => name.startsWith("text-button-"))
57
+ .map((name) => name.replace("text-", ""))
58
+ .sort();
59
+ expect(declared).toEqual([...BUTTON_TEXT_TOKENS].sort());
60
+ });
61
+
62
+ test("declare exactly the same button radius tokens", () => {
63
+ const declared = [...TOKENS.keys()]
64
+ .filter((name) => name.startsWith("radius-button-"))
65
+ .map((name) => name.replace("radius-", ""))
66
+ .sort();
67
+ expect(declared).toEqual([...BUTTON_RADIUS_TOKENS].sort());
68
+ });
69
+
70
+ test("declare exactly the same input text tokens", () => {
71
+ const declared = [...TOKENS.keys()]
72
+ .filter((name) => name.startsWith("text-input-"))
73
+ .map((name) => name.replace("text-", ""))
74
+ .sort();
75
+ expect(declared).toEqual([...INPUT_TEXT_TOKENS].sort());
76
+ });
77
+ });
78
+
79
+ describe("the chart scale", () => {
80
+ test("ascends in the order the registry lists it", () => {
81
+ const values = CHART_SIZE_TOKENS.map((token) => px(`spacing-${token}`));
82
+ expect(values).toEqual([...values].sort((a, b) => a - b));
83
+ expect(new Set(values).size).toBe(CHART_SIZE_TOKENS.length);
84
+ });
85
+
86
+ test("stays taller than a control, since a chart is a region and not a row", () => {
87
+ // A chart shorter than an input would have no room for an axis under its
88
+ // own plot, and the labels would collide with the marks.
89
+ expect(px("spacing-chart-sm")).toBeGreaterThan(px("spacing-input-lg"));
90
+ });
91
+ });
92
+
93
+ describe("the icon scale", () => {
94
+ // Component tests compare tokens by their index in ICON_SIZE_TOKENS rather
95
+ // than by looking up points, which only means anything while the array is
96
+ // ordered. This is the test that keeps that true.
97
+ test("ascends in the order the registry lists it", () => {
98
+ const values = ICON_SIZE_TOKENS.map((token) => px(`spacing-${token}`));
99
+ expect(values).toEqual([...values].sort((a, b) => a - b));
100
+ expect(new Set(values).size).toBe(ICON_SIZE_TOKENS.length);
101
+ });
102
+ });
103
+
104
+ describe("the button scale", () => {
105
+ test("ascends in the order the registry lists it", () => {
106
+ const values = BUTTON_SIZE_TOKENS.map((token) => px(`spacing-${token}`));
107
+ expect(values).toEqual([...values].sort((a, b) => a - b));
108
+ expect(new Set(values).size).toBe(BUTTON_SIZE_TOKENS.length);
109
+ });
110
+
111
+ test("pairs each height with a label size and an icon that fit inside it", () => {
112
+ for (const [index, token] of BUTTON_SIZE_TOKENS.entries()) {
113
+ const height = px(`spacing-${token}`);
114
+ expect(px(`text-${BUTTON_TEXT_TOKENS[index]}`)).toBeLessThan(height);
115
+ // A button's icon indexes the shared scale at the same step name.
116
+ expect(px(`spacing-icon-${token.replace("button-", "")}`)).toBeLessThan(height);
117
+ }
118
+ });
119
+
120
+ test("scales the label with the height", () => {
121
+ const text = BUTTON_TEXT_TOKENS.map((token) => px(`text-${token}`));
122
+ expect(text).toEqual([...text].sort((a, b) => a - b));
123
+ });
124
+
125
+ // A radius past half the height is silently clamped to half by the
126
+ // renderer, so a number above it would stop meaning what it says — and the
127
+ // step it sits at would stop being retunable. Equal to half, which is what
128
+ // every step is today, draws the capsule the button ships as.
129
+ test("gives each height a corner it can actually draw", () => {
130
+ for (const [index, token] of BUTTON_SIZE_TOKENS.entries()) {
131
+ const height = px(`spacing-${token}`);
132
+ expect(px(`radius-${BUTTON_RADIUS_TOKENS[index]}`)).toBeLessThanOrEqual(height / 2);
133
+ }
134
+ });
135
+
136
+ test("scales the corner with the height", () => {
137
+ const radii = BUTTON_RADIUS_TOKENS.map((token) => px(`radius-${token}`));
138
+ expect(radii).toEqual([...radii].sort((a, b) => a - b));
139
+ expect(new Set(radii).size).toBe(BUTTON_RADIUS_TOKENS.length);
140
+ });
141
+
142
+ // The three scales are indexed by the same step name — `button-md` names a
143
+ // height, a label size and a corner — so a step added to one and not the
144
+ // others would compile to a class with nothing behind it.
145
+ test("names its steps the same way across all three scales", () => {
146
+ expect([...BUTTON_RADIUS_TOKENS]).toEqual([...BUTTON_SIZE_TOKENS]);
147
+ expect([...BUTTON_TEXT_TOKENS]).toEqual([...BUTTON_SIZE_TOKENS]);
148
+ });
149
+ });
150
+
151
+ describe("the input scale", () => {
152
+ test("ascends in the order the registry lists it", () => {
153
+ const values = INPUT_SIZE_TOKENS.map((token) => px(`spacing-${token}`));
154
+ expect(values).toEqual([...values].sort((a, b) => a - b));
155
+ expect(new Set(values).size).toBe(INPUT_SIZE_TOKENS.length);
156
+ });
157
+
158
+ test("pairs each height with a text size and an icon that fit inside it", () => {
159
+ for (const [index, token] of INPUT_SIZE_TOKENS.entries()) {
160
+ const height = px(`spacing-${token}`);
161
+ expect(px(`text-${INPUT_TEXT_TOKENS[index]}`)).toBeLessThan(height);
162
+ // A decorator's icon indexes the shared scale at the same step name.
163
+ expect(px(`spacing-icon-${token.replace("input-", "")}`)).toBeLessThan(height);
164
+ }
165
+ });
166
+
167
+ test("scales the text with the height", () => {
168
+ const text = INPUT_TEXT_TOKENS.map((token) => px(`text-${token}`));
169
+ expect(text).toEqual([...text].sort((a, b) => a - b));
170
+ });
171
+
172
+ // `Input` and `Button` are the two controls a form puts side by side, so a
173
+ // field that stood a step taller than the button beside it would read as a
174
+ // mistake. They are separate scales precisely so this is asserted rather
175
+ // than assumed — see the note in tokens.css.
176
+ test("stands exactly as tall as the button beside it", () => {
177
+ for (const [index, token] of INPUT_SIZE_TOKENS.entries()) {
178
+ expect(px(`spacing-${token}`)).toBe(px(`spacing-${BUTTON_SIZE_TOKENS[index]}`));
179
+ }
180
+ });
181
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Names of the semantic size tokens defined in `tokens.css`.
3
+ *
4
+ * These exist so `cn()` can teach tailwind-merge about them. Without that,
5
+ * `h-button-md` is an unrecognised utility and twMerge will not drop it when a
6
+ * caller passes `h-12` — both survive and the override silently stops working.
7
+ * {@link ../lib/cn} registers every name here, and a test asserts this list
8
+ * still matches `tokens.css`.
9
+ *
10
+ * Values live in the CSS, not here. A component references a token by writing
11
+ * the class out in full (`size-icon-md`) — Tailwind scans source text, so a
12
+ * name assembled at runtime would never be compiled.
13
+ */
14
+
15
+ /** Suffixes in the `--spacing-*` namespace: `h-button-md`, `w-button-md`. */
16
+ export const BUTTON_SIZE_TOKENS = ["button-sm", "button-md", "button-lg"] as const;
17
+
18
+ /** Suffixes in the `--spacing-*` namespace: `size-icon-md`. */
19
+ export const ICON_SIZE_TOKENS = ["icon-xs", "icon-sm", "icon-md", "icon-lg", "icon-xl", "icon-2xl"] as const;
20
+
21
+ /**
22
+ * Suffixes in the `--spacing-*` namespace: `h-navbar-row`, `px-screen-gutter`.
23
+ *
24
+ * Not a scale — two independent measurements of a screen's chrome — so unlike
25
+ * the icon and button tokens their order carries no meaning.
26
+ */
27
+ export const SCREEN_SIZE_TOKENS = ["navbar-row", "screen-gutter"] as const;
28
+
29
+ /**
30
+ * Suffixes in the `--spacing-*` namespace: `h-input-md`, `min-h-input-md`.
31
+ *
32
+ * Read on two axes, unlike the button's: a single-line field takes the height
33
+ * as a fixed one and a multiline field takes it as a floor, so both `h-` and
34
+ * `min-h-` have to be recognised.
35
+ */
36
+ export const INPUT_SIZE_TOKENS = ["input-sm", "input-md", "input-lg"] as const;
37
+
38
+ /**
39
+ * Suffixes in the `--radius-*` namespace: `rounded-button-md`.
40
+ *
41
+ * Stepped with the button's heights rather than borrowing the generic
42
+ * `--radius-*` scale, so the corner is retuned with the control it belongs to
43
+ * and a caller's `rounded-lg` still overrides it.
44
+ */
45
+ export const BUTTON_RADIUS_TOKENS = ["button-sm", "button-md", "button-lg"] as const;
46
+
47
+ /** Suffixes in the `--text-*` namespace: `text-button-md`. */
48
+ export const BUTTON_TEXT_TOKENS = ["button-sm", "button-md", "button-lg"] as const;
49
+
50
+ /** Suffixes in the `--text-*` namespace: `text-input-md`. */
51
+ export const INPUT_TEXT_TOKENS = ["input-sm", "input-md", "input-lg"] as const;
52
+
53
+ /**
54
+ * Suffixes in the `--spacing-*` namespace: `h-chart-md`.
55
+ *
56
+ * A chart canvas has no intrinsic height, so one is named here rather than
57
+ * written as a `h-56` at each call site — a dashboard's rows only line up if
58
+ * every chart agrees, and they only stay agreed if there is one number.
59
+ */
60
+ export const CHART_SIZE_TOKENS = ["chart-sm", "chart-md", "chart-lg"] as const;
61
+
62
+ /**
63
+ * The tailwind-merge extension both mergers in this package are built from.
64
+ *
65
+ * There are two, and they are easy to forget: `cn()` merges a caller's
66
+ * className, and `tv()` merges slots and variants using a tailwind-merge
67
+ * instance of its own. Extending only one leaves the other treating
68
+ * `text-button-md` as an unrecognised utility — which is how a size token ends
69
+ * up colliding with a colour and silently dropping it. One config, read by
70
+ * `lib/cn.ts` and `lib/tv.ts`, so they cannot drift apart.
71
+ */
72
+ export const TW_MERGE_CONFIG = {
73
+ extend: {
74
+ theme: {
75
+ radius: [...BUTTON_RADIUS_TOKENS],
76
+ spacing: [
77
+ ...BUTTON_SIZE_TOKENS,
78
+ ...CHART_SIZE_TOKENS,
79
+ ...ICON_SIZE_TOKENS,
80
+ ...INPUT_SIZE_TOKENS,
81
+ ...SCREEN_SIZE_TOKENS,
82
+ ],
83
+ text: [...BUTTON_TEXT_TOKENS, ...INPUT_TEXT_TOKENS],
84
+ },
85
+ },
86
+ } as const;
@@ -0,0 +1 @@
1
+ /// <reference types="uniwind/types" />