@vaam-apps/ui 0.1.0

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 (285) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/LICENSE +21 -0
  3. package/README.md +286 -0
  4. package/dist/components/data/code.d.ts +37 -0
  5. package/dist/components/data/code.d.ts.map +1 -0
  6. package/dist/components/data/code.js +37 -0
  7. package/dist/components/data/code.js.map +1 -0
  8. package/dist/components/data/copy-button.d.ts +39 -0
  9. package/dist/components/data/copy-button.d.ts.map +1 -0
  10. package/dist/components/data/copy-button.js +50 -0
  11. package/dist/components/data/copy-button.js.map +1 -0
  12. package/dist/components/data/detail-row.d.ts +69 -0
  13. package/dist/components/data/detail-row.d.ts.map +1 -0
  14. package/dist/components/data/detail-row.js +27 -0
  15. package/dist/components/data/detail-row.js.map +1 -0
  16. package/dist/components/data/id-display.d.ts +33 -0
  17. package/dist/components/data/id-display.d.ts.map +1 -0
  18. package/dist/components/data/id-display.js +25 -0
  19. package/dist/components/data/id-display.js.map +1 -0
  20. package/dist/components/data/masked-value.d.ts +47 -0
  21. package/dist/components/data/masked-value.d.ts.map +1 -0
  22. package/dist/components/data/masked-value.js +46 -0
  23. package/dist/components/data/masked-value.js.map +1 -0
  24. package/dist/components/data/money.d.ts +28 -0
  25. package/dist/components/data/money.d.ts.map +1 -0
  26. package/dist/components/data/money.js +22 -0
  27. package/dist/components/data/money.js.map +1 -0
  28. package/dist/components/data/phone-display.d.ts +45 -0
  29. package/dist/components/data/phone-display.d.ts.map +1 -0
  30. package/dist/components/data/phone-display.js +23 -0
  31. package/dist/components/data/phone-display.js.map +1 -0
  32. package/dist/components/data/stat-tile.d.ts +33 -0
  33. package/dist/components/data/stat-tile.d.ts.map +1 -0
  34. package/dist/components/data/stat-tile.js +18 -0
  35. package/dist/components/data/stat-tile.js.map +1 -0
  36. package/dist/components/data/timestamp-display.d.ts +14 -0
  37. package/dist/components/data/timestamp-display.d.ts.map +1 -0
  38. package/dist/components/data/timestamp-display.js +77 -0
  39. package/dist/components/data/timestamp-display.js.map +1 -0
  40. package/dist/components/patterns/inline-banner.d.ts +22 -0
  41. package/dist/components/patterns/inline-banner.d.ts.map +1 -0
  42. package/dist/components/patterns/inline-banner.js +11 -0
  43. package/dist/components/patterns/inline-banner.js.map +1 -0
  44. package/dist/components/patterns/inline-empty-state.d.ts +20 -0
  45. package/dist/components/patterns/inline-empty-state.d.ts.map +1 -0
  46. package/dist/components/patterns/inline-empty-state.js +11 -0
  47. package/dist/components/patterns/inline-empty-state.js.map +1 -0
  48. package/dist/components/patterns/live-row.d.ts +23 -0
  49. package/dist/components/patterns/live-row.d.ts.map +1 -0
  50. package/dist/components/patterns/live-row.js +48 -0
  51. package/dist/components/patterns/live-row.js.map +1 -0
  52. package/dist/components/patterns/payload-inspector.d.ts +25 -0
  53. package/dist/components/patterns/payload-inspector.d.ts.map +1 -0
  54. package/dist/components/patterns/payload-inspector.js +33 -0
  55. package/dist/components/patterns/payload-inspector.js.map +1 -0
  56. package/dist/components/patterns/route-skeleton.d.ts +15 -0
  57. package/dist/components/patterns/route-skeleton.d.ts.map +1 -0
  58. package/dist/components/patterns/route-skeleton.js +7 -0
  59. package/dist/components/patterns/route-skeleton.js.map +1 -0
  60. package/dist/components/patterns/screen-layout.d.ts +14 -0
  61. package/dist/components/patterns/screen-layout.d.ts.map +1 -0
  62. package/dist/components/patterns/screen-layout.js +11 -0
  63. package/dist/components/patterns/screen-layout.js.map +1 -0
  64. package/dist/components/patterns/stale-write-banner.d.ts +26 -0
  65. package/dist/components/patterns/stale-write-banner.d.ts.map +1 -0
  66. package/dist/components/patterns/stale-write-banner.js +28 -0
  67. package/dist/components/patterns/stale-write-banner.js.map +1 -0
  68. package/dist/components/patterns/state-timeline.d.ts +58 -0
  69. package/dist/components/patterns/state-timeline.d.ts.map +1 -0
  70. package/dist/components/patterns/state-timeline.js +103 -0
  71. package/dist/components/patterns/state-timeline.js.map +1 -0
  72. package/dist/components/primitives/badge.d.ts +27 -0
  73. package/dist/components/primitives/badge.d.ts.map +1 -0
  74. package/dist/components/primitives/badge.js +33 -0
  75. package/dist/components/primitives/badge.js.map +1 -0
  76. package/dist/components/primitives/button.d.ts +12 -0
  77. package/dist/components/primitives/button.d.ts.map +1 -0
  78. package/dist/components/primitives/button.js +42 -0
  79. package/dist/components/primitives/button.js.map +1 -0
  80. package/dist/components/primitives/calendar.d.ts +39 -0
  81. package/dist/components/primitives/calendar.d.ts.map +1 -0
  82. package/dist/components/primitives/calendar.js +102 -0
  83. package/dist/components/primitives/calendar.js.map +1 -0
  84. package/dist/components/primitives/card.d.ts +24 -0
  85. package/dist/components/primitives/card.d.ts.map +1 -0
  86. package/dist/components/primitives/card.js +28 -0
  87. package/dist/components/primitives/card.js.map +1 -0
  88. package/dist/components/primitives/checkbox.d.ts +42 -0
  89. package/dist/components/primitives/checkbox.d.ts.map +1 -0
  90. package/dist/components/primitives/checkbox.js +31 -0
  91. package/dist/components/primitives/checkbox.js.map +1 -0
  92. package/dist/components/primitives/chip-select.d.ts +38 -0
  93. package/dist/components/primitives/chip-select.d.ts.map +1 -0
  94. package/dist/components/primitives/chip-select.js +25 -0
  95. package/dist/components/primitives/chip-select.js.map +1 -0
  96. package/dist/components/primitives/command-menu.d.ts +54 -0
  97. package/dist/components/primitives/command-menu.d.ts.map +1 -0
  98. package/dist/components/primitives/command-menu.js +24 -0
  99. package/dist/components/primitives/command-menu.js.map +1 -0
  100. package/dist/components/primitives/confirm-dialog.d.ts +52 -0
  101. package/dist/components/primitives/confirm-dialog.d.ts.map +1 -0
  102. package/dist/components/primitives/confirm-dialog.js +30 -0
  103. package/dist/components/primitives/confirm-dialog.js.map +1 -0
  104. package/dist/components/primitives/date-picker.d.ts +68 -0
  105. package/dist/components/primitives/date-picker.d.ts.map +1 -0
  106. package/dist/components/primitives/date-picker.js +107 -0
  107. package/dist/components/primitives/date-picker.js.map +1 -0
  108. package/dist/components/primitives/dialog.d.ts +50 -0
  109. package/dist/components/primitives/dialog.d.ts.map +1 -0
  110. package/dist/components/primitives/dialog.js +90 -0
  111. package/dist/components/primitives/dialog.js.map +1 -0
  112. package/dist/components/primitives/drawer.d.ts +71 -0
  113. package/dist/components/primitives/drawer.d.ts.map +1 -0
  114. package/dist/components/primitives/drawer.js +93 -0
  115. package/dist/components/primitives/drawer.js.map +1 -0
  116. package/dist/components/primitives/dropdown-menu.d.ts +40 -0
  117. package/dist/components/primitives/dropdown-menu.d.ts.map +1 -0
  118. package/dist/components/primitives/dropdown-menu.js +53 -0
  119. package/dist/components/primitives/dropdown-menu.js.map +1 -0
  120. package/dist/components/primitives/form-field.d.ts +94 -0
  121. package/dist/components/primitives/form-field.d.ts.map +1 -0
  122. package/dist/components/primitives/form-field.js +27 -0
  123. package/dist/components/primitives/form-field.js.map +1 -0
  124. package/dist/components/primitives/inline-confirm.d.ts +62 -0
  125. package/dist/components/primitives/inline-confirm.d.ts.map +1 -0
  126. package/dist/components/primitives/inline-confirm.js +11 -0
  127. package/dist/components/primitives/inline-confirm.js.map +1 -0
  128. package/dist/components/primitives/input.d.ts +4 -0
  129. package/dist/components/primitives/input.d.ts.map +1 -0
  130. package/dist/components/primitives/input.js +13 -0
  131. package/dist/components/primitives/input.js.map +1 -0
  132. package/dist/components/primitives/label.d.ts +26 -0
  133. package/dist/components/primitives/label.d.ts.map +1 -0
  134. package/dist/components/primitives/label.js +33 -0
  135. package/dist/components/primitives/label.js.map +1 -0
  136. package/dist/components/primitives/pagination.d.ts +54 -0
  137. package/dist/components/primitives/pagination.d.ts.map +1 -0
  138. package/dist/components/primitives/pagination.js +31 -0
  139. package/dist/components/primitives/pagination.js.map +1 -0
  140. package/dist/components/primitives/popover.d.ts +12 -0
  141. package/dist/components/primitives/popover.d.ts.map +1 -0
  142. package/dist/components/primitives/popover.js +22 -0
  143. package/dist/components/primitives/popover.js.map +1 -0
  144. package/dist/components/primitives/progress.d.ts +25 -0
  145. package/dist/components/primitives/progress.d.ts.map +1 -0
  146. package/dist/components/primitives/progress.js +25 -0
  147. package/dist/components/primitives/progress.js.map +1 -0
  148. package/dist/components/primitives/radio-group.d.ts +47 -0
  149. package/dist/components/primitives/radio-group.d.ts.map +1 -0
  150. package/dist/components/primitives/radio-group.js +11 -0
  151. package/dist/components/primitives/radio-group.js.map +1 -0
  152. package/dist/components/primitives/select.d.ts +39 -0
  153. package/dist/components/primitives/select.d.ts.map +1 -0
  154. package/dist/components/primitives/select.js +142 -0
  155. package/dist/components/primitives/select.js.map +1 -0
  156. package/dist/components/primitives/separator.d.ts +9 -0
  157. package/dist/components/primitives/separator.d.ts.map +1 -0
  158. package/dist/components/primitives/separator.js +16 -0
  159. package/dist/components/primitives/separator.js.map +1 -0
  160. package/dist/components/primitives/side-nav.d.ts +65 -0
  161. package/dist/components/primitives/side-nav.d.ts.map +1 -0
  162. package/dist/components/primitives/side-nav.js +100 -0
  163. package/dist/components/primitives/side-nav.js.map +1 -0
  164. package/dist/components/primitives/skeleton.d.ts +22 -0
  165. package/dist/components/primitives/skeleton.d.ts.map +1 -0
  166. package/dist/components/primitives/skeleton.js +25 -0
  167. package/dist/components/primitives/skeleton.js.map +1 -0
  168. package/dist/components/primitives/spinner.d.ts +20 -0
  169. package/dist/components/primitives/spinner.d.ts.map +1 -0
  170. package/dist/components/primitives/spinner.js +21 -0
  171. package/dist/components/primitives/spinner.js.map +1 -0
  172. package/dist/components/primitives/switch.d.ts +29 -0
  173. package/dist/components/primitives/switch.d.ts.map +1 -0
  174. package/dist/components/primitives/switch.js +24 -0
  175. package/dist/components/primitives/switch.js.map +1 -0
  176. package/dist/components/primitives/table.d.ts +23 -0
  177. package/dist/components/primitives/table.d.ts.map +1 -0
  178. package/dist/components/primitives/table.js +81 -0
  179. package/dist/components/primitives/table.js.map +1 -0
  180. package/dist/components/primitives/tabs.d.ts +38 -0
  181. package/dist/components/primitives/tabs.d.ts.map +1 -0
  182. package/dist/components/primitives/tabs.js +84 -0
  183. package/dist/components/primitives/tabs.js.map +1 -0
  184. package/dist/components/primitives/textarea.d.ts +4 -0
  185. package/dist/components/primitives/textarea.d.ts.map +1 -0
  186. package/dist/components/primitives/textarea.js +11 -0
  187. package/dist/components/primitives/textarea.js.map +1 -0
  188. package/dist/components/primitives/toast.d.ts +24 -0
  189. package/dist/components/primitives/toast.d.ts.map +1 -0
  190. package/dist/components/primitives/toast.js +58 -0
  191. package/dist/components/primitives/toast.js.map +1 -0
  192. package/dist/components/primitives/tooltip.d.ts +30 -0
  193. package/dist/components/primitives/tooltip.d.ts.map +1 -0
  194. package/dist/components/primitives/tooltip.js +6 -0
  195. package/dist/components/primitives/tooltip.js.map +1 -0
  196. package/dist/components/status/state-chip.d.ts +53 -0
  197. package/dist/components/status/state-chip.d.ts.map +1 -0
  198. package/dist/components/status/state-chip.js +8 -0
  199. package/dist/components/status/state-chip.js.map +1 -0
  200. package/dist/components/status/state-mark.d.ts +21 -0
  201. package/dist/components/status/state-mark.d.ts.map +1 -0
  202. package/dist/components/status/state-mark.js +90 -0
  203. package/dist/components/status/state-mark.js.map +1 -0
  204. package/dist/components/status/status-pill.d.ts +62 -0
  205. package/dist/components/status/status-pill.d.ts.map +1 -0
  206. package/dist/components/status/status-pill.js +65 -0
  207. package/dist/components/status/status-pill.js.map +1 -0
  208. package/dist/components/status/status-tokens.d.ts +110 -0
  209. package/dist/components/status/status-tokens.d.ts.map +1 -0
  210. package/dist/components/status/status-tokens.js +102 -0
  211. package/dist/components/status/status-tokens.js.map +1 -0
  212. package/dist/index.d.ts +76 -0
  213. package/dist/index.d.ts.map +1 -0
  214. package/dist/index.js +95 -0
  215. package/dist/index.js.map +1 -0
  216. package/dist/lib/cn.d.ts +11 -0
  217. package/dist/lib/cn.d.ts.map +1 -0
  218. package/dist/lib/cn.js +78 -0
  219. package/dist/lib/cn.js.map +1 -0
  220. package/dist/lib/money.d.ts +73 -0
  221. package/dist/lib/money.d.ts.map +1 -0
  222. package/dist/lib/money.js +123 -0
  223. package/dist/lib/money.js.map +1 -0
  224. package/dist/lib/omit-undefined.d.ts +17 -0
  225. package/dist/lib/omit-undefined.d.ts.map +1 -0
  226. package/dist/lib/omit-undefined.js +24 -0
  227. package/dist/lib/omit-undefined.js.map +1 -0
  228. package/package.json +100 -0
  229. package/src/components/data/code.tsx +43 -0
  230. package/src/components/data/copy-button.tsx +97 -0
  231. package/src/components/data/detail-row.tsx +113 -0
  232. package/src/components/data/id-display.tsx +49 -0
  233. package/src/components/data/masked-value.tsx +96 -0
  234. package/src/components/data/money.tsx +46 -0
  235. package/src/components/data/phone-display.tsx +60 -0
  236. package/src/components/data/stat-tile.tsx +68 -0
  237. package/src/components/data/timestamp-display.tsx +106 -0
  238. package/src/components/patterns/inline-banner.tsx +60 -0
  239. package/src/components/patterns/inline-empty-state.tsx +45 -0
  240. package/src/components/patterns/live-row.tsx +75 -0
  241. package/src/components/patterns/payload-inspector.tsx +157 -0
  242. package/src/components/patterns/route-skeleton.tsx +77 -0
  243. package/src/components/patterns/screen-layout.tsx +44 -0
  244. package/src/components/patterns/stale-write-banner.tsx +44 -0
  245. package/src/components/patterns/state-timeline.tsx +229 -0
  246. package/src/components/primitives/badge.tsx +40 -0
  247. package/src/components/primitives/button.tsx +54 -0
  248. package/src/components/primitives/calendar.tsx +136 -0
  249. package/src/components/primitives/card.tsx +70 -0
  250. package/src/components/primitives/checkbox.tsx +101 -0
  251. package/src/components/primitives/chip-select.tsx +105 -0
  252. package/src/components/primitives/command-menu.tsx +92 -0
  253. package/src/components/primitives/confirm-dialog.tsx +109 -0
  254. package/src/components/primitives/date-picker.tsx +271 -0
  255. package/src/components/primitives/dialog.tsx +195 -0
  256. package/src/components/primitives/drawer.tsx +326 -0
  257. package/src/components/primitives/dropdown-menu.tsx +128 -0
  258. package/src/components/primitives/form-field.tsx +136 -0
  259. package/src/components/primitives/inline-confirm.tsx +118 -0
  260. package/src/components/primitives/input.tsx +21 -0
  261. package/src/components/primitives/label.tsx +43 -0
  262. package/src/components/primitives/pagination.tsx +110 -0
  263. package/src/components/primitives/popover.tsx +41 -0
  264. package/src/components/primitives/progress.tsx +66 -0
  265. package/src/components/primitives/radio-group.tsx +90 -0
  266. package/src/components/primitives/select.tsx +254 -0
  267. package/src/components/primitives/separator.tsx +49 -0
  268. package/src/components/primitives/side-nav.tsx +253 -0
  269. package/src/components/primitives/skeleton.tsx +25 -0
  270. package/src/components/primitives/spinner.tsx +39 -0
  271. package/src/components/primitives/switch.tsx +74 -0
  272. package/src/components/primitives/table.tsx +157 -0
  273. package/src/components/primitives/tabs.tsx +163 -0
  274. package/src/components/primitives/textarea.tsx +21 -0
  275. package/src/components/primitives/toast.tsx +117 -0
  276. package/src/components/primitives/tooltip.tsx +38 -0
  277. package/src/components/status/state-chip.tsx +70 -0
  278. package/src/components/status/state-mark.tsx +154 -0
  279. package/src/components/status/status-pill.tsx +153 -0
  280. package/src/components/status/status-tokens.ts +173 -0
  281. package/src/index.ts +96 -0
  282. package/src/lib/cn.ts +168 -0
  283. package/src/lib/money.ts +164 -0
  284. package/src/lib/omit-undefined.ts +25 -0
  285. package/src/styles/theme.css +381 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,104 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release as `@vaam-apps/ui`, in its own repository. Previously
6
+ `@vsms/ui`, a private package inside the vsms monorepo — extracted with
7
+ its full history, back to the commit that created it.
8
+
9
+ ### Storybook replaces the gallery
10
+
11
+ Stories live beside their components, and the `addon-a11y` panel runs
12
+ axe on every one. The gallery this replaces lived in a consuming
13
+ application and had drifted: it claimed to render every export and had
14
+ missed thirteen, among them the three carrying live rendering bugs.
15
+
16
+ ### Accessibility
17
+
18
+ - **`--subtle-foreground` failed WCAG AA on every surface** — 4.06:1 on
19
+ `--background` down to 3.40:1 on `--surface-3`, against the 4.5:1
20
+ normal text requires, across 38 usages all at 11–12px. Raised to
21
+ `#838a95`, the minimum that clears AA on the worst case. This
22
+ compresses the gap to `--muted-foreground` from ΔE 18.2 to 8.8 and is
23
+ a real, deliberate visual change. Found by the a11y addon, not by
24
+ inspection.
25
+
26
+ ### API
27
+
28
+ - `Select` and `ValueTabs` accept an explicit `undefined` for their
29
+ controlled props. Under `exactOptionalPropertyTypes` a bare `value?:
30
+ string` rejects `useState<string>()`'s own output, which made the
31
+ ordinary controlled pattern a type error.
32
+
33
+ ### Generalised for release
34
+
35
+ - **The status system is parameterised.** `StatusMeta`, the glyph
36
+ geometry and the hue classes stay here; the per-state-machine tables
37
+ moved to the consuming application. `createStatusPill(system)` binds
38
+ one table to a pill whose `state` prop accepts exactly that machine's
39
+ literals, replacing three near-identical pill components that differed
40
+ only in which table they indexed (and had already drifted: one set
41
+ `role="img"`, one did not; one supported `interactive`, two did not).
42
+ - `StateMark` now takes a `StatusMeta` rather than one application's
43
+ state enum. The previous meta-based export, `StateMarkFromMeta`, is
44
+ gone — this is it, renamed.
45
+ - `StateTimeline` is generic over the state machine, takes its `system`
46
+ and its per-state `annotations` as props, and accepts any IANA
47
+ timezone. It previously hard-coded one application's message states,
48
+ that application's own explanatory notes, and a two-value timezone
49
+ union whose UTC-offset suffix was literal (`"Z"` or `"+01"`) — wrong
50
+ for every other zone and across DST. The offset is now read from
51
+ `Intl`.
52
+ - `MsisdnDisplay` → **`PhoneDisplay`**, with the Cameroon digit grouping
53
+ and carrier table replaced by a `format` callback and a `tag` prop.
54
+ - `StateChip`'s tones are the shared `StatusHue` vocabulary rather than a
55
+ private four-entry copy, so `expired` and `parked` are now reachable.
56
+ - `EncodingPreview` (GSM-7/UCS-2 SMS segment counting) was removed. It is
57
+ application domain knowledge, not a component.
58
+ - `components/bespoke/` → `components/patterns/`.
59
+
60
+ ### Fixed
61
+
62
+ - **`cn()` silently deleted custom font sizes.** `tailwind-merge`
63
+ classifies an unrecognised `text-*` value as a colour, so
64
+ `cn("text-caption text-state-danger-fg")` returned only the colour —
65
+ affecting `FieldError`, `DetailList`, `CardHeader` and others, which
66
+ had been rendering at the browser default size. The theme's font-size
67
+ and radius scales are now registered. Present in tailwind-merge 2.6.0
68
+ as well as 3.x; a latent bug, not an upgrade regression.
69
+ - **`cn()` did not resolve daisyUI component modifiers**, so
70
+ `cn("btn btn-primary", "btn-ghost")` kept both and let stylesheet order
71
+ decide. Now grouped — with `btn`/`badge` *colour* and *style* kept
72
+ separate, because daisyUI 5's `.btn-outline` reads the variable
73
+ `.btn-primary` sets and collapsing them discards the colour.
74
+ - **`--state-warning-*` was referenced but never declared.**
75
+ `StateChip tone="warning"` and every `InlineBanner variant="warning"`
76
+ (so every `StaleWriteBanner`) emitted classes matching no rule and
77
+ rendered with no colour. Both bug classes are now build failures: see
78
+ `lib/theme-tokens.test.ts`.
79
+ - **The copy-to-clipboard affordance leaked a timer and swallowed
80
+ rejections.** Unmounting inside the 1.5s confirmation window called
81
+ `setState` on a dead component, and a refused `navigator.clipboard`
82
+ write (insecure origin, permissions policy) was an unhandled rejection
83
+ with no user-visible result. Extracted to `CopyButton` and fixed once.
84
+
85
+ ### Added
86
+
87
+ - `Money` and `formatMoney` — minor units in, `Intl`-derived exponents,
88
+ string-based scaling that stays exact past `Number.MAX_SAFE_INTEGER`.
89
+ - `Calendar`, `DatePicker`, `DateRangePicker` on `react-day-picker`,
90
+ exchanging `YYYY-MM-DD` strings rather than `Date` objects.
91
+ - `Checkbox` / `CheckboxField`, `Switch` / `SwitchField`, `Spinner`,
92
+ `Progress`, `Pagination`, `ConfirmDialog`, `MaskedValue`, `StatTile`,
93
+ `CopyButton`.
94
+
95
+ ### Dependencies
96
+
97
+ - `tailwind-merge` 2.6 → 3.6 (the 3.x line targets Tailwind v4, which
98
+ this package already required).
99
+ - `lucide-react` 0.469 → 1.44.
100
+ - `react-day-picker` 10.0 added.
101
+ - `@radix-ui/react-dialog` added as a direct dependency. It arrives
102
+ transitively through `vaul` regardless; declaring it is what lets
103
+ TypeScript name the drawer's re-exported types in the emitted
104
+ declarations (TS2742).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vaam Apps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # @vaam-apps/ui
2
+
3
+ A dark-first React component library for **operator consoles** — the
4
+ screens a person uses to find out why one record out of a hundred
5
+ thousand did not do what it was supposed to.
6
+
7
+ - **daisyUI** for styling, so component classes stay short and themeable.
8
+ - **Headless UI** for behaviour, so focus, keyboard and ARIA are not
9
+ reimplemented per component.
10
+ - **A parameterised status system**, so one glyph vocabulary serves every
11
+ state machine in an application instead of one badge component per
12
+ enum.
13
+
14
+ Requires React 18.3+ or 19, Tailwind CSS v4, and daisyUI v5.
15
+
16
+ ```sh
17
+ pnpm add @vaam-apps/ui
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ Two steps, and skipping the second is the most common way to end up with
23
+ an unstyled page.
24
+
25
+ **1. Import the token layer** into your global stylesheet, *after*
26
+ Tailwind and the daisyUI plugin:
27
+
28
+ ```css
29
+ @import "tailwindcss";
30
+ @plugin "daisyui" {
31
+ themes: false;
32
+ }
33
+ @import "@vaam-apps/ui/styles/theme.css";
34
+ @source "../node_modules/@vaam-apps/ui/dist";
35
+ ```
36
+
37
+ **`themes: false` is load-bearing, not tidiness.** This package defines
38
+ its theme under daisyUI's own `dark` name, and daisyUI's built-in `dark`
39
+ emits at a higher specificity than any custom theme block can:
40
+
41
+ ```css
42
+ /* daisyUI's built-in — specificity (0,3,1) */
43
+ :is(:root:has(input.theme-controller[value=dark]:checked),[data-theme=dark]) { … }
44
+ /* a custom theme block — specificity (0,1,0) */
45
+ :where(:root),[data-theme=dark] { … }
46
+ ```
47
+
48
+ The built-in therefore wins on every token it also defines — `base-100`,
49
+ `base-200`, `base-300`, `base-content` — regardless of import order, so
50
+ the page background, card surfaces and body text silently come out as
51
+ stock daisyUI rather than this theme. Turning the built-ins off leaves
52
+ nothing to collide with. Found by measuring a rendered page, not by
53
+ reading: `--color-base-100` resolved to `oklch(25.33% .016 252.42)` where
54
+ the theme declares `#0a0b0d`.
55
+
56
+ **2. Keep the `@source` line.** Tailwind v4 generates only the utilities
57
+ it can see used, and it does not look inside `node_modules` on its own.
58
+ Without it every component renders with no styling at all — no error, no
59
+ warning, just a page that looks broken in a way that is miserable to
60
+ debug. Adjust the relative path to reach your own `node_modules`.
61
+
62
+ The theme registers itself under daisyUI's `dark` name and sets
63
+ `prefersdark`, so `<html data-theme="dark">` (or no attribute at all)
64
+ picks it up.
65
+
66
+ ## What is in it
67
+
68
+ ### Primitives
69
+
70
+ `Button` · `Input` · `Textarea` · `Label` · `FormField` · `Select` ·
71
+ `RadioGroup` · `ChipSelect` · `Checkbox` · `Switch` · `Calendar` ·
72
+ `DatePicker` · `DateRangePicker` · `Dialog` · `ConfirmDialog` · `Drawer` ·
73
+ `Popover` · `DropdownMenu` · `CommandMenu` · `Tooltip` · `Toast` ·
74
+ `Table` · `Tabs` · `Pagination` · `Card` · `Badge` · `Separator` ·
75
+ `Skeleton` · `Spinner` · `Progress` · `SideNav` · `InlineConfirm`
76
+
77
+ ### Data display
78
+
79
+ `Money` · `IdDisplay` · `PhoneDisplay` · `MaskedValue` · `CopyButton` ·
80
+ `TimestampDisplay` · `DetailRow` / `DetailList` · `StatTile` · `Code`
81
+
82
+ ### Patterns
83
+
84
+ `ScreenStack` / `ScreenHeader` · `InlineBanner` · `InlineEmptyState` ·
85
+ `StaleWriteBanner` · `RouteSkeleton` · `PayloadInspector` ·
86
+ `StateTimeline` · `LiveRow`
87
+
88
+ ### Status
89
+
90
+ `StatusPill` · `createStatusPill` · `StateMark` · `StateChip` ·
91
+ `defineStatusSystem` · `HUE_CLASSES`
92
+
93
+ ## The status system
94
+
95
+ A status system is one state machine's presentation: every state it can
96
+ be in, mapped to a glyph, a hue, an attention level and human copy. The
97
+ library owns the vocabulary and the rendering. **The table belongs to
98
+ you**, because what a state means is domain knowledge.
99
+
100
+ ```tsx
101
+ import { createStatusPill, defineStatusSystem } from "@vaam-apps/ui";
102
+
103
+ export const ORDER_STATUS = defineStatusSystem({
104
+ pending: {
105
+ family: "in-flight", silhouette: "circle", mark: "pie-1",
106
+ hue: "neutral", filled: false, attention: "quiet",
107
+ label: "Pending", tooltip: "Awaiting payment.",
108
+ },
109
+ paid: {
110
+ family: "terminal", silhouette: "circle", mark: "check",
111
+ hue: "success", filled: true, attention: "quiet",
112
+ label: "Paid", tooltip: "Settled and captured.",
113
+ },
114
+ disputed: {
115
+ family: "unresolved", silhouette: "diamond", mark: "question",
116
+ hue: "uncertain", filled: false, attention: "loud",
117
+ label: "Disputed", tooltip: "The payer opened a chargeback.",
118
+ },
119
+ });
120
+
121
+ export const OrderStatusPill = createStatusPill(ORDER_STATUS);
122
+ ```
123
+
124
+ ```tsx
125
+ <OrderStatusPill state="disputed" showLiteral />
126
+ ```
127
+
128
+ `state` accepts exactly that machine's literals, so a status from a
129
+ different machine is a compile error. Add a second machine by calling
130
+ `createStatusPill` again — the rendering is shared, the vocabularies stay
131
+ apart.
132
+
133
+ Three things about it are deliberate:
134
+
135
+ - **Each state differs in silhouette, interior mark and fill as well as
136
+ hue.** Colour alone fails for the ~8% of men with a colour-vision
137
+ deficiency and fails completely in a monochrome screenshot pasted into
138
+ a ticket.
139
+ - **`family` is presentational, never authorisation.** A UI that greys
140
+ out "cancel" because its own table says `terminal` will be wrong the
141
+ first time the state machine changes and nobody remembers the table
142
+ exists. Propose the action; let the server refuse it.
143
+ - **`unresolved` is a first-class outcome**, distinct from success and
144
+ failure. Systems that model only two outcomes end up reporting "we
145
+ never learned what happened" as whichever is more convenient.
146
+
147
+ ## Money
148
+
149
+ ```tsx
150
+ import { formatMoney, Money } from "@vaam-apps/ui";
151
+
152
+ <Money amount={500_000} currency="XAF" /> // XAF 500,000
153
+ <Money amount="1250" currency="USD" /> // USD 12.50
154
+ formatMoney(1250, "KWD"); // KWD 1.250
155
+ ```
156
+
157
+ Amounts go in as an **integer count of minor units** — `number`,
158
+ `bigint`, or a digit string, so an `i64` from a backend survives the
159
+ trip. The decimal exponent comes from `Intl`, which knows every ISO 4217
160
+ currency including the three-decimal ones (`KWD`, `BHD`, `TND`) that
161
+ hand-written tables invariably forget. Scaling is string surgery, never
162
+ division, so an amount past `Number.MAX_SAFE_INTEGER` formats exactly.
163
+ A non-integer throws rather than being silently rounded.
164
+
165
+ ## Dates
166
+
167
+ `DatePicker` and `DateRangePicker` wrap `react-day-picker` in a popover
168
+ and exchange **`YYYY-MM-DD` strings, not `Date` objects**. A `Date` is an
169
+ instant, and an instant rendered in another zone is a different calendar
170
+ day — which is how a filter for "today" quietly returns yesterday's rows
171
+ for anyone west of the server. `Date` is confined to the internals.
172
+
173
+ ```tsx
174
+ const [range, setRange] = useState<IsoDateRange | undefined>();
175
+ <DateRangePicker value={range} onValueChange={setRange} />
176
+ ```
177
+
178
+ `Calendar` is the bare, themed `DayPicker` if you need a different shell.
179
+
180
+ ## `cn()`
181
+
182
+ `clsx` + `tailwind-merge`, extended so the later class wins for this
183
+ theme's own scales and for daisyUI's component modifiers:
184
+
185
+ ```ts
186
+ cn("btn btn-primary", "btn-ghost"); // "btn btn-ghost"
187
+ cn("btn btn-primary", "btn-outline"); // both — orthogonal in daisyUI 5
188
+ cn("rounded-sm", "rounded-field"); // "rounded-field"
189
+ cn("text-caption", "text-danger-fg"); // both — size and colour
190
+ ```
191
+
192
+ That last case is the one worth knowing about: stock `tailwind-merge`
193
+ classifies an unrecognised `text-*` value as a colour, so a custom font
194
+ size sitting beside a colour is silently deleted. The scale is registered
195
+ here so it is not.
196
+
197
+ ## Design constraints
198
+
199
+ Worth knowing before you fight them:
200
+
201
+ - **Borders, not shadows.** Cards and panels are a 1px border on a
202
+ surface step. Shadows are reserved for genuinely floating layers
203
+ (popover, dialog).
204
+ - **There is no success or warning *button*.** Those hues belong to
205
+ status; a button is primary, secondary, ghost or destructive. Mixing
206
+ them is how a status language erodes.
207
+ - **Empty states are inline status lines, not centred placards** — see
208
+ `InlineEmptyState`.
209
+ - **Dark only.** There is one theme, and no toggle. A second theme is a
210
+ real amount of work to keep honest and nothing here pretends to have
211
+ done it.
212
+
213
+ ## The component gallery
214
+
215
+ Every component is published as a Storybook at
216
+ **<https://vaam-apps.github.io/ui/>**, redeployed from `main` on every
217
+ push. The a11y panel runs axe on each story.
218
+
219
+ ## Development
220
+
221
+ ```sh
222
+ pnpm install
223
+ pnpm storybook # the component gallery, at :6006
224
+ pnpm test # unit tests + the invariant guards
225
+ pnpm typecheck
226
+ pnpm lint # biome, format and lint together
227
+ pnpm build # what npm receives: dist/ + declarations
228
+ ```
229
+
230
+ **Storybook is the visual-QA surface**, and stories live beside the
231
+ components they demonstrate. That colocation is deliberate: this package
232
+ previously had its gallery in a consuming application, where it drifted —
233
+ it claimed to render every export and had silently missed thirteen of
234
+ them, including the three that turned out to be carrying live rendering
235
+ bugs. A story in the same directory as its component at least shows up in
236
+ the same diff.
237
+
238
+ The `@storybook/addon-a11y` panel runs axe on every story. It is not
239
+ decoration: it is what caught `--subtle-foreground` failing WCAG AA on
240
+ every surface in the system, across 38 usages, by flagging fourteen
241
+ calendar weekday headers at once.
242
+
243
+ A few guards are worth knowing about before you trip one:
244
+
245
+ - **`src/lib/theme-tokens.test.ts`** scans the components for
246
+ `bg-`/`text-`/`border-` tokens this stylesheet owns and fails if one is
247
+ not declared. Tailwind generates nothing for an unknown token and says
248
+ nothing about it, which has produced invisible transparent surfaces
249
+ here twice.
250
+ - **`src/components/primitives/calendar.render.test.tsx`** renders the
251
+ calendar and asserts the classes land on the elements react-day-picker
252
+ actually emits. Its sibling `calendar.test.ts` only checks that the
253
+ `classNames` keys are real — necessary, and not sufficient: a valid key
254
+ carrying a class string written against a DOM shape that does not exist
255
+ is exactly how the selection styling silently matched nothing.
256
+ - **`src/lib/cn.test.ts`** pins the merge behaviour, including the case
257
+ where `tailwind-merge` deletes a custom font size because it mistakes
258
+ it for a colour.
259
+
260
+ ## Releasing
261
+
262
+ Tag `vX.Y.Z` matching `package.json`; `.github/workflows/release.yml`
263
+ publishes to npm through Trusted Publishing (OIDC), with no token stored
264
+ in this repository.
265
+
266
+ **Except the very first publish, which that workflow cannot do.** npm
267
+ attaches a trusted publisher to an *existing* package, so there is
268
+ nothing to configure until the name exists. Bootstrap it once by hand:
269
+
270
+ ```sh
271
+ npm login
272
+ pnpm install
273
+ pnpm publish --access public
274
+ ```
275
+
276
+ Then add the trusted publisher on npmjs.com (repository `vaam-apps/ui`,
277
+ workflow `release.yml`). Every later tag publishes tokenlessly.
278
+
279
+ ## Versioning
280
+
281
+ Pre-1.0. Minor versions may contain breaking changes; pin exactly if that
282
+ matters to you. See `CHANGELOG.md`.
283
+
284
+ ## Licence
285
+
286
+ MIT.
@@ -0,0 +1,37 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * Inline monospace emphasis for a literal term — a config key, a role key,
4
+ * a job kind, a model or field name.
5
+ *
6
+ * **Not `IdDisplay`, and the distinction is load-bearing.** `IdDisplay` is
7
+ * documented as specifically for `cs_cuid()` values: it truncates to seven
8
+ * characters with no middle ellipsis and offers a copy button, both of
9
+ * which are correct for an opaque 23-character id and wrong for anything
10
+ * else. Three separate agents independently checked their own sites
11
+ * against `schemas/vsms.cstack` and reached the same conclusion — none of
12
+ * these are `Cuid` (`AppClient.clientId` is a plain bounded `String`,
13
+ * `Role.key` is `@regex`-constrained, a route predicate is an operator or
14
+ * prefix, `providerMessageRef` is an *external* provider's id) — so
15
+ * routing them through `IdDisplay` would truncate values that must be read
16
+ * in full.
17
+ *
18
+ * This exists because `font-mono text-foreground` was the most-repeated
19
+ * class string left after the R6 factorization (31 occurrences), and
20
+ * because three route groups had already each created a byte-identical
21
+ * local `Code` component for it. Those three copies are exactly the
22
+ * "same shape, different name" duplication that produced two
23
+ * `ScreenHeader`s and two `MESSAGE_CLASSES` earlier in this effort — the
24
+ * agents that wrote them flagged the risk themselves and could not fix it,
25
+ * because a cross-route duplicate is not a route-scoped change.
26
+ *
27
+ * Renders a `<span>`, not a `<code>`: several call sites sit inside a
28
+ * `<dd>` or a sentence where `<code>` would add semantics the content does
29
+ * not have (a role key in prose is emphasis, not a code sample). A caller
30
+ * that genuinely wants `<code>` semantics should say so rather than have
31
+ * this component guess.
32
+ */
33
+ export declare function Code({ children, className, }: {
34
+ children: ReactNode;
35
+ className?: string | undefined;
36
+ }): import("react").JSX.Element;
37
+ //# sourceMappingURL=code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../../src/components/data/code.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,IAAI,CAAC,EACnB,QAAQ,EACR,SAAS,GACV,EAAE;IACD,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,+BAEA"}
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/cn.js";
3
+ /**
4
+ * Inline monospace emphasis for a literal term — a config key, a role key,
5
+ * a job kind, a model or field name.
6
+ *
7
+ * **Not `IdDisplay`, and the distinction is load-bearing.** `IdDisplay` is
8
+ * documented as specifically for `cs_cuid()` values: it truncates to seven
9
+ * characters with no middle ellipsis and offers a copy button, both of
10
+ * which are correct for an opaque 23-character id and wrong for anything
11
+ * else. Three separate agents independently checked their own sites
12
+ * against `schemas/vsms.cstack` and reached the same conclusion — none of
13
+ * these are `Cuid` (`AppClient.clientId` is a plain bounded `String`,
14
+ * `Role.key` is `@regex`-constrained, a route predicate is an operator or
15
+ * prefix, `providerMessageRef` is an *external* provider's id) — so
16
+ * routing them through `IdDisplay` would truncate values that must be read
17
+ * in full.
18
+ *
19
+ * This exists because `font-mono text-foreground` was the most-repeated
20
+ * class string left after the R6 factorization (31 occurrences), and
21
+ * because three route groups had already each created a byte-identical
22
+ * local `Code` component for it. Those three copies are exactly the
23
+ * "same shape, different name" duplication that produced two
24
+ * `ScreenHeader`s and two `MESSAGE_CLASSES` earlier in this effort — the
25
+ * agents that wrote them flagged the risk themselves and could not fix it,
26
+ * because a cross-route duplicate is not a route-scoped change.
27
+ *
28
+ * Renders a `<span>`, not a `<code>`: several call sites sit inside a
29
+ * `<dd>` or a sentence where `<code>` would add semantics the content does
30
+ * not have (a role key in prose is emphasis, not a code sample). A caller
31
+ * that genuinely wants `<code>` semantics should say so rather than have
32
+ * this component guess.
33
+ */
34
+ export function Code({ children, className, }) {
35
+ return _jsx("span", { className: cn("font-mono text-foreground", className), children: children });
36
+ }
37
+ //# sourceMappingURL=code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.js","sourceRoot":"","sources":["../../../src/components/data/code.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAElC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,IAAI,CAAC,EACnB,QAAQ,EACR,SAAS,GAIV;IACC,OAAO,eAAM,SAAS,EAAE,EAAE,CAAC,2BAA2B,EAAE,SAAS,CAAC,YAAG,QAAQ,GAAQ,CAAC;AACxF,CAAC"}
@@ -0,0 +1,39 @@
1
+ export interface CopyButtonProps {
2
+ /** The exact text put on the clipboard. Always the full, unformatted,
3
+ * machine-readable value — never what is displayed. A truncated id or a
4
+ * prettily-spaced phone number pasted into a query returns nothing. */
5
+ value: string;
6
+ /** Accessible name. Defaults to `Copy ${value}`, which is right for a
7
+ * short id and wrong for a paragraph — pass something shorter then. */
8
+ label?: string | undefined;
9
+ /** Hide until the containing `.group` is hovered or this button is
10
+ * focused. For dense tables, where a permanently visible icon on every
11
+ * row is noise. Keyboard users always reach it: focus reveals it. */
12
+ revealOnGroupHover?: boolean;
13
+ size?: 12 | 14 | 16;
14
+ className?: string | undefined;
15
+ }
16
+ /**
17
+ * Copy-to-clipboard affordance: a click, then a checkmark for a moment.
18
+ *
19
+ * Extracted because `IdDisplay` and `MsisdnDisplay` each carried their own
20
+ * byte-identical copy — the same `useState`, the same 1500ms timeout, the
21
+ * same icon swap, the same class string — and any component that later
22
+ * wanted the behaviour would have written a third.
23
+ *
24
+ * Two things the duplicated versions got wrong, fixed here rather than
25
+ * copied a third time:
26
+ *
27
+ * - **The timeout was never cleared.** Unmounting within the confirmation
28
+ * window (a row scrolling out of a virtualised table, a drawer closing)
29
+ * left a timer that fired `setCopied` on a dead component. Now cleared
30
+ * on unmount, and any in-flight timer is cleared before a new one
31
+ * starts, so a double-click cannot end the confirmation early.
32
+ * - **A rejected `writeText` was unhandled.** `navigator.clipboard` is
33
+ * unavailable on an insecure origin and can be refused by permissions
34
+ * policy; the bare `await` meant an unhandled rejection and a button
35
+ * that silently did nothing. Failure now leaves the icon unchanged,
36
+ * which is at least honest, and reports the reason to the console.
37
+ */
38
+ export declare function CopyButton({ value, label, revealOnGroupHover, size, className, }: CopyButtonProps): import("react").JSX.Element;
39
+ //# sourceMappingURL=copy-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-button.d.ts","sourceRoot":"","sources":["../../../src/components/data/copy-button.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B;;2EAEuE;IACvE,KAAK,EAAE,MAAM,CAAC;IACd;2EACuE;IACvE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B;;yEAEqE;IACrE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,kBAA0B,EAC1B,IAAS,EACT,SAAS,GACV,EAAE,eAAe,+BA4CjB"}
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Check, Copy } from "lucide-react";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
+ import { cn } from "../../lib/cn.js";
6
+ const CONFIRMATION_MS = 1500;
7
+ /**
8
+ * Copy-to-clipboard affordance: a click, then a checkmark for a moment.
9
+ *
10
+ * Extracted because `IdDisplay` and `MsisdnDisplay` each carried their own
11
+ * byte-identical copy — the same `useState`, the same 1500ms timeout, the
12
+ * same icon swap, the same class string — and any component that later
13
+ * wanted the behaviour would have written a third.
14
+ *
15
+ * Two things the duplicated versions got wrong, fixed here rather than
16
+ * copied a third time:
17
+ *
18
+ * - **The timeout was never cleared.** Unmounting within the confirmation
19
+ * window (a row scrolling out of a virtualised table, a drawer closing)
20
+ * left a timer that fired `setCopied` on a dead component. Now cleared
21
+ * on unmount, and any in-flight timer is cleared before a new one
22
+ * starts, so a double-click cannot end the confirmation early.
23
+ * - **A rejected `writeText` was unhandled.** `navigator.clipboard` is
24
+ * unavailable on an insecure origin and can be refused by permissions
25
+ * policy; the bare `await` meant an unhandled rejection and a button
26
+ * that silently did nothing. Failure now leaves the icon unchanged,
27
+ * which is at least honest, and reports the reason to the console.
28
+ */
29
+ export function CopyButton({ value, label, revealOnGroupHover = false, size = 12, className, }) {
30
+ const [copied, setCopied] = useState(false);
31
+ const timer = useRef(null);
32
+ useEffect(() => () => {
33
+ if (timer.current !== null)
34
+ clearTimeout(timer.current);
35
+ }, []);
36
+ const copy = useCallback(() => {
37
+ void navigator.clipboard.writeText(value).then(() => {
38
+ setCopied(true);
39
+ if (timer.current !== null)
40
+ clearTimeout(timer.current);
41
+ timer.current = setTimeout(() => setCopied(false), CONFIRMATION_MS);
42
+ }, (error) => {
43
+ // Insecure origin, or a permissions policy that forbids it. Leave
44
+ // the icon alone rather than claim a copy that did not happen.
45
+ console.error("Clipboard write refused", error);
46
+ });
47
+ }, [value]);
48
+ return (_jsx("button", { type: "button", onClick: copy, "aria-label": label ?? `Copy ${value}`, className: cn("shrink-0 text-subtle-foreground transition-opacity hover:text-foreground", revealOnGroupHover && "opacity-0 focus-visible:opacity-100 group-hover:opacity-100", className), children: copied ? (_jsx(Check, { size: size, strokeWidth: 1.5, className: "text-state-success-fg" })) : (_jsx(Copy, { size: size, strokeWidth: 1.5 })) }));
49
+ }
50
+ //# sourceMappingURL=copy-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy-button.js","sourceRoot":"","sources":["../../../src/components/data/copy-button.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjE,OAAO,EAAE,EAAE,EAAE,MAAM,cAAc,CAAC;AAkBlC,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,KAAK,EACL,KAAK,EACL,kBAAkB,GAAG,KAAK,EAC1B,IAAI,GAAG,EAAE,EACT,SAAS,GACO;IAChB,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,CAAuC,IAAI,CAAC,CAAC;IAEjE,SAAS,CACP,GAAG,EAAE,CAAC,GAAG,EAAE;QACT,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;YAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1D,CAAC,EACD,EAAE,CACH,CAAC;IAEF,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,KAAK,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,CAC5C,GAAG,EAAE;YACH,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;gBAAE,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACxD,KAAK,CAAC,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC,CAAC;QACtE,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;YACjB,kEAAkE;YAClE,+DAA+D;YAC/D,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;QAClD,CAAC,CACF,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,EACb,OAAO,EAAE,IAAI,gBACD,KAAK,IAAI,QAAQ,KAAK,EAAE,EACpC,SAAS,EAAE,EAAE,CACX,0EAA0E,EAC1E,kBAAkB,IAAI,6DAA6D,EACnF,SAAS,CACV,YAEA,MAAM,CAAC,CAAC,CAAC,CACR,KAAC,KAAK,IAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,SAAS,EAAC,uBAAuB,GAAG,CAC1E,CAAC,CAAC,CAAC,CACF,KAAC,IAAI,IAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,GAAI,CACvC,GACM,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { ReactNode } from "react";
2
+ /**
3
+ * A labelled value in a detail drawer or summary panel — one `dt`/`dd`
4
+ * pair, wrapped in a `<div>` so the pair can be laid out as a unit.
5
+ *
6
+ * # Why this is shared, when four route-local copies said it should not be
7
+ *
8
+ * `providers/`, `routes/`, `sender-ids/` and `webhooks/` each carry a
9
+ * `components/detail-row.tsx` whose **code is byte-identical** — only the
10
+ * doc comments differ. Each of those comments argues, in good faith, that
11
+ * the component is route-local because "the identical shape repeats
12
+ * independently in the other three, each factored the same way in its own
13
+ * file". Four files justifying their own duplication by pointing at each
14
+ * other is the clearest possible signal the thing belongs here instead.
15
+ *
16
+ * They were written by four agents working in parallel on disjoint route
17
+ * groups, none of which could edit this package — a deliberate constraint,
18
+ * because the round before it produced two `ScreenHeader`s and two
19
+ * `MESSAGE_CLASSES` from agents that *could*. The trade was that
20
+ * cross-route duplicates get reconciled centrally afterwards. This is that.
21
+ *
22
+ * # The variants are the console's real, pre-existing treatments
23
+ *
24
+ * Nothing here is a new design. Eight sites were read first and their
25
+ * looks catalogued; the three variants below are exactly what was already
26
+ * on screen, so every converted site keeps its current appearance:
27
+ *
28
+ * - `inline` — label left, value right, on one line. The default, and what
29
+ * the four route-local copies rendered.
30
+ * - `stacked` — label above value. What those same copies rendered under
31
+ * their own `stacked` prop, for a value too long to sit beside its label.
32
+ * - `divided` — label above value, hairline rule beneath, last row
33
+ * unruled. What `jobs`, `opt-outs` and `workers` each hand-rolled as a
34
+ * private `*DetailField` helper.
35
+ *
36
+ * **One deliberate normalisation, called out rather than slipped in:**
37
+ * `audit-log` and the gallery used `flex justify-between gap-4` — the
38
+ * `inline` treatment with a slightly wider gap and no vertical centring.
39
+ * They map onto `inline` here, so those two drawers shift by one spacing
40
+ * step and gain baseline centring. That is a real, if small, visual change,
41
+ * and it is the point: the console should not render the same concept two
42
+ * ways because two people typed a different gap.
43
+ */
44
+ export type DetailRowVariant = "inline" | "stacked" | "divided";
45
+ export interface DetailRowProps {
46
+ label: ReactNode;
47
+ children: ReactNode;
48
+ variant?: DetailRowVariant | undefined;
49
+ className?: string | undefined;
50
+ }
51
+ export declare function DetailRow({ label, children, variant, className }: DetailRowProps): import("react").JSX.Element;
52
+ /**
53
+ * The `<dl>` a group of [`DetailRow`]s belongs in.
54
+ *
55
+ * Exists so a caller cannot pair the rows with the wrong container — every
56
+ * one of the eight original sites wrapped its rows in a `<dl>` with its own
57
+ * spacing (`gap-2`, `gap-3`, or none), which is the same one-decision-in-
58
+ * many-places problem the rows themselves had, one level up.
59
+ *
60
+ * `divided` rows carry their own vertical rhythm through padding and the
61
+ * hairline rule, so the list adds no gap for them — passing `gap` there
62
+ * would double-space rows that are already separated by a border.
63
+ */
64
+ export declare function DetailList({ children, variant, className, }: {
65
+ children: ReactNode;
66
+ variant?: DetailRowVariant | undefined;
67
+ className?: string | undefined;
68
+ }): import("react").JSX.Element;
69
+ //# sourceMappingURL=detail-row.d.ts.map