@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/src/lib/cn.ts ADDED
@@ -0,0 +1,168 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
+
4
+ /**
5
+ * `tailwind-merge` taught about (a) this design system's own theme scales
6
+ * and (b) daisyUI's component modifiers.
7
+ *
8
+ * Stock `twMerge` only knows Tailwind's *default* theme and Tailwind's own
9
+ * utilities. Both gaps produce the same failure shape — a class that still
10
+ * parses, still looks plausible in the source, and quietly does nothing —
11
+ * and both were found by probing real class strings from this package
12
+ * rather than by reading the docs:
13
+ *
14
+ * **1. Custom font sizes were being deleted.** `--text-caption` &c. are
15
+ * theme keys stock `twMerge` has never heard of, so it classified
16
+ * `text-caption` by its fallback rule — as a *colour* — and then treated
17
+ * it as conflicting with the actual colour beside it:
18
+ *
19
+ * ```text
20
+ * twMerge("text-caption text-state-danger-fg") // → "text-state-danger-fg"
21
+ * twMerge("text-body text-foreground") // → "text-foreground"
22
+ * twMerge("text-title-sm text-foreground") // → "text-foreground"
23
+ * ```
24
+ *
25
+ * Those three strings are, verbatim, `FieldError`, `DetailList` and
26
+ * `CardHeader`. Every one of them has been rendering at the browser's
27
+ * default font size rather than its intended step, in both this package
28
+ * and any app consuming it, since `cn()` was written. Registering the
29
+ * scale under the `text` theme key is what separates the two groups again.
30
+ * This predates the tailwind-merge 3.x upgrade — v2.6.0 was checked
31
+ * directly and drops the same classes — so it is a latent bug being fixed
32
+ * here, not a regression being papered over.
33
+ *
34
+ * **2. Radius utilities did not conflict at all.** `rounded-field` /
35
+ * `rounded-box` / `rounded-selector` are daisyUI's own radius tiers, and
36
+ * stock `twMerge` left `rounded-sm rounded-field` as *both* classes, so
37
+ * the winner fell out of stylesheet order rather than call order — the
38
+ * opposite of what an override prop is for.
39
+ *
40
+ * **3. daisyUI component modifiers did not conflict either**, so
41
+ * `cn("btn btn-primary", "btn-ghost")` kept both. The group list below is
42
+ * adapted from the sibling `@vpay/ui` package, which had already derived
43
+ * it against daisyUI 5's compiled CSS; its central finding is preserved
44
+ * and worth restating, because it is counter-intuitive:
45
+ *
46
+ * > **Colour and style are two groups, not one, for `btn` and `badge`.**
47
+ * > `.btn-outline`/`.btn-dash` *read* the `--btn-color` variable that
48
+ * > `.btn-primary` sets, so `btn-primary btn-outline` is how daisyUI 5
49
+ * > spells "a primary outline button". Collapsed into one group,
50
+ * > `cn("btn-primary", "btn-outline")` returns `btn-outline` and the
51
+ * > colour silently vanishes.
52
+ *
53
+ * `ghost` and `link` stay with the *colours* rather than the styles,
54
+ * matching `@vpay/ui` and this package's own `buttonVariants`, where
55
+ * `ghost` is offered as an alternative to `primary` rather than a
56
+ * modifier on top of it.
57
+ *
58
+ * Scoped to what this package actually emits plus the daisyUI components a
59
+ * consumer is most likely to reach for directly. Add a group when a
60
+ * component gains a variant dimension, not ahead of one.
61
+ */
62
+ type ExtraClassGroupId =
63
+ | "daisy-btn-variant"
64
+ | "daisy-btn-style"
65
+ | "daisy-btn-size"
66
+ | "daisy-badge-variant"
67
+ | "daisy-badge-style"
68
+ | "daisy-badge-size"
69
+ | "daisy-alert-variant"
70
+ | "daisy-alert-style"
71
+ | "daisy-input-variant"
72
+ | "daisy-select-variant"
73
+ | "daisy-select-size"
74
+ | "daisy-checkbox-variant"
75
+ | "daisy-checkbox-size"
76
+ | "daisy-radio-variant"
77
+ | "daisy-radio-size"
78
+ | "daisy-toggle-variant"
79
+ | "daisy-toggle-size"
80
+ | "daisy-card-size"
81
+ | "daisy-table-size"
82
+ | "daisy-tabs-style"
83
+ | "daisy-tabs-size"
84
+ | "daisy-progress-variant"
85
+ | "daisy-loading-type"
86
+ | "daisy-loading-size";
87
+
88
+ /**
89
+ * The `--text-*` steps declared in `styles/theme.css`. Kept in sync with
90
+ * that file by `theme-tokens.test.ts`, which parses the stylesheet rather
91
+ * than trusting this list.
92
+ */
93
+ const FONT_SIZES = [
94
+ "micro",
95
+ "caption",
96
+ "body",
97
+ "prose",
98
+ "title-sm",
99
+ "title",
100
+ "metric",
101
+ "metric-lg",
102
+ ] as const;
103
+
104
+ /** daisyUI's radius tiers, which its own components read from. */
105
+ const RADII = ["selector", "field", "box"] as const;
106
+
107
+ const DAISY_COLOURS = [
108
+ "primary",
109
+ "secondary",
110
+ "accent",
111
+ "neutral",
112
+ "info",
113
+ "success",
114
+ "warning",
115
+ "error",
116
+ ] as const;
117
+
118
+ const DAISY_SIZES = ["xs", "sm", "md", "lg", "xl"] as const;
119
+
120
+ const twMerge = extendTailwindMerge<ExtraClassGroupId>({
121
+ extend: {
122
+ theme: {
123
+ // Registering these as *font sizes* is what stops `text-caption`
124
+ // being mistaken for a colour and dropped. See point 1 above.
125
+ text: [...FONT_SIZES],
126
+ radius: [...RADII],
127
+ },
128
+ classGroups: {
129
+ "daisy-btn-variant": [{ btn: [...DAISY_COLOURS, "ghost", "link"] }],
130
+ "daisy-btn-style": [{ btn: ["outline", "soft", "dash"] }],
131
+ "daisy-btn-size": [{ btn: [...DAISY_SIZES, "wide", "block", "square", "circle"] }],
132
+ "daisy-badge-variant": [{ badge: [...DAISY_COLOURS, "ghost"] }],
133
+ "daisy-badge-style": [{ badge: ["outline", "soft", "dash"] }],
134
+ "daisy-badge-size": [{ badge: [...DAISY_SIZES] }],
135
+ "daisy-alert-variant": [{ alert: ["info", "success", "warning", "error"] }],
136
+ "daisy-alert-style": [{ alert: ["outline", "soft", "dash"] }],
137
+ "daisy-input-variant": [{ input: [...DAISY_COLOURS, "ghost"] }],
138
+ "daisy-select-variant": [{ select: [...DAISY_COLOURS, "ghost"] }],
139
+ "daisy-select-size": [{ select: [...DAISY_SIZES] }],
140
+ "daisy-checkbox-variant": [{ checkbox: [...DAISY_COLOURS] }],
141
+ "daisy-checkbox-size": [{ checkbox: [...DAISY_SIZES] }],
142
+ "daisy-radio-variant": [{ radio: [...DAISY_COLOURS] }],
143
+ "daisy-radio-size": [{ radio: [...DAISY_SIZES] }],
144
+ "daisy-toggle-variant": [{ toggle: [...DAISY_COLOURS] }],
145
+ "daisy-toggle-size": [{ toggle: [...DAISY_SIZES] }],
146
+ "daisy-card-size": [{ card: [...DAISY_SIZES] }],
147
+ "daisy-table-size": [{ table: [...DAISY_SIZES] }],
148
+ "daisy-tabs-style": [{ tabs: ["box", "border", "lift"] }],
149
+ "daisy-tabs-size": [{ tabs: [...DAISY_SIZES] }],
150
+ "daisy-progress-variant": [{ progress: [...DAISY_COLOURS] }],
151
+ "daisy-loading-type": [{ loading: ["spinner", "dots", "ring", "ball", "bars", "infinity"] }],
152
+ "daisy-loading-size": [{ loading: [...DAISY_SIZES] }],
153
+ },
154
+ },
155
+ });
156
+
157
+ /**
158
+ * Merge class names, resolving conflicting utilities so the *last* one
159
+ * wins — Tailwind's own, this design system's custom scales, and daisyUI's
160
+ * component modifiers alike.
161
+ */
162
+ export function cn(...inputs: ClassValue[]): string {
163
+ return twMerge(clsx(inputs));
164
+ }
165
+
166
+ /** The font-size steps this package registers with `tailwind-merge`. Exported
167
+ * for `theme-tokens.test.ts`, which checks them against `theme.css`. */
168
+ export const REGISTERED_FONT_SIZES: readonly string[] = FONT_SIZES;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Money formatting from minor units, without losing precision.
3
+ *
4
+ * # Why this exists
5
+ *
6
+ * Money is the single most duplicated formatter in every application that
7
+ * handles it. The sibling payments codebase this package was generalised
8
+ * for had **four** independent implementations at the time of writing, in
9
+ * four packages, with four different signatures and three different
10
+ * hard-coded tables of which currencies have no decimal places — and one
11
+ * of them divided by a power of ten in floating point, which silently
12
+ * corrupts any amount past 2^53 minor units. All four also got the
13
+ * three-decimal currencies wrong, because every hand-written table
14
+ * remembers `JPY` and `XAF` and forgets `KWD` and `BHD`.
15
+ *
16
+ * # Three decisions, each load-bearing
17
+ *
18
+ * **Minor units in, never a decimal number.** An amount is an integer
19
+ * count of the currency's smallest unit — that is how it is stored, how
20
+ * it is transmitted, and the only representation that cannot be wrong by
21
+ * a rounding error. `bigint` and a decimal-digit `string` are accepted
22
+ * alongside `number` so an `i64` from a Rust backend survives the trip;
23
+ * JSON gives it to you as a string precisely because `number` cannot hold
24
+ * it.
25
+ *
26
+ * **The exponent comes from `Intl`, not from a table here.** `Intl`
27
+ * already ships the ISO 4217 exponent for every currency, so there is no
28
+ * list to maintain and no currency this gets wrong. Verified rather than
29
+ * assumed: `XAF`/`XOF`/`JPY`/`CLP`/`VND` resolve to 0 and `KWD`/`BHD` to
30
+ * 3, which is exactly the set hand-written tables miss.
31
+ *
32
+ * **The scaling is string surgery, never division.** The minor-unit
33
+ * integer is split into whole and fractional parts by digit position and
34
+ * handed to `Intl.NumberFormat` as a string, which accepts arbitrary
35
+ * precision. No value is ever converted to a float, so an amount larger
36
+ * than `Number.MAX_SAFE_INTEGER` formats exactly.
37
+ */
38
+
39
+ /** An integer count of a currency's smallest unit. */
40
+ export type MinorUnits = number | bigint | string;
41
+
42
+ export interface FormatMoneyOptions {
43
+ /** BCP 47 tag. Defaults to the runtime's own locale.
44
+ *
45
+ * Pass one explicitly anywhere the output must be stable — a test, a
46
+ * server-rendered page whose HTML must match the client's hydration, or
47
+ * a value a human will compare against a bank statement. */
48
+ locale?: string | undefined;
49
+ /** `symbol` → `FCFA 5,000`; `code` → `XAF 5,000`; `none` → `5,000`.
50
+ *
51
+ * `code` is the honest default for an operator console: currency
52
+ * symbols are ambiguous across locales ($ alone names at least a dozen
53
+ * currencies) and an operator reconciling against a ledger needs the
54
+ * ISO code. */
55
+ display?: "symbol" | "code" | "none";
56
+ /** Force a sign even when positive, e.g. for a ledger delta. */
57
+ signDisplay?: Intl.NumberFormatOptions["signDisplay"];
58
+ }
59
+
60
+ const DIGITS_ONLY = /^-?\d+$/;
61
+
62
+ /** Cache: constructing an `Intl.NumberFormat` is the expensive part, and a
63
+ * table renders the same currency once per row. */
64
+ const formatters = new Map<string, Intl.NumberFormat>();
65
+
66
+ function formatter(key: string, options: Intl.NumberFormatOptions, locale?: string) {
67
+ const cacheKey = `${locale ?? ""}|${key}`;
68
+ let found = formatters.get(cacheKey);
69
+ if (found === undefined) {
70
+ found = new Intl.NumberFormat(locale, options);
71
+ formatters.set(cacheKey, found);
72
+ }
73
+ return found;
74
+ }
75
+
76
+ /**
77
+ * How many decimal places this currency has, per ISO 4217, as `Intl`
78
+ * knows it. `2` for a currency `Intl` has never heard of — the majority
79
+ * case, and the same fallback `Intl` itself uses.
80
+ */
81
+ export function currencyExponent(currency: string, locale?: string): number {
82
+ return (
83
+ formatter(`exp:${currency}`, { style: "currency", currency }, locale).resolvedOptions()
84
+ .maximumFractionDigits ?? 2
85
+ );
86
+ }
87
+
88
+ /**
89
+ * Shifts a minor-unit integer right by `exponent` places, as a string.
90
+ *
91
+ * `"500"`, 2 → `"5.00"`; `"5"`, 2 → `"0.05"`; `"-5"`, 2 → `"-0.05"`;
92
+ * `"5000"`, 0 → `"5000"`.
93
+ */
94
+ function shiftDecimal(digits: string, exponent: number): string {
95
+ const negative = digits.startsWith("-");
96
+ const abs = negative ? digits.slice(1) : digits;
97
+ const sign = negative ? "-" : "";
98
+ if (exponent === 0) return sign + abs;
99
+ const padded = abs.padStart(exponent + 1, "0");
100
+ const whole = padded.slice(0, padded.length - exponent);
101
+ const fraction = padded.slice(padded.length - exponent);
102
+ return `${sign}${whole}.${fraction}`;
103
+ }
104
+
105
+ /**
106
+ * `formatMoney(500000, "XAF")` → `"XAF 500,000"`.
107
+ * `formatMoney("1250", "USD")` → `"USD 12.50"`.
108
+ *
109
+ * @throws if `minorUnits` is not an integer. A non-integer minor-unit
110
+ * amount is always a bug — usually a caller that already divided — and
111
+ * silently rounding it here would hide the real error at the point where
112
+ * it is least visible.
113
+ */
114
+ export function formatMoney(
115
+ minorUnits: MinorUnits,
116
+ currency: string,
117
+ options: FormatMoneyOptions = {},
118
+ ): string {
119
+ const { locale, display = "code", signDisplay } = options;
120
+
121
+ let digits: string;
122
+ if (typeof minorUnits === "bigint") {
123
+ digits = minorUnits.toString();
124
+ } else if (typeof minorUnits === "number") {
125
+ if (!Number.isInteger(minorUnits)) {
126
+ throw new TypeError(
127
+ `formatMoney expects an integer count of minor units, got ${minorUnits} (${currency}). ` +
128
+ "Pass 1250 for USD 12.50, not 12.5.",
129
+ );
130
+ }
131
+ digits = minorUnits.toFixed(0);
132
+ } else {
133
+ const trimmed = minorUnits.trim();
134
+ if (!DIGITS_ONLY.test(trimmed)) {
135
+ throw new TypeError(
136
+ `formatMoney expects an integer count of minor units, got ${JSON.stringify(minorUnits)} (${currency}).`,
137
+ );
138
+ }
139
+ digits = trimmed;
140
+ }
141
+
142
+ const exponent = currencyExponent(currency, locale);
143
+ const value = shiftDecimal(digits, exponent);
144
+
145
+ const intlOptions: Intl.NumberFormatOptions =
146
+ display === "none"
147
+ ? {
148
+ style: "decimal",
149
+ minimumFractionDigits: exponent,
150
+ maximumFractionDigits: exponent,
151
+ }
152
+ : {
153
+ style: "currency",
154
+ currency,
155
+ currencyDisplay: display,
156
+ };
157
+ if (signDisplay !== undefined) intlOptions.signDisplay = signDisplay;
158
+
159
+ return formatter(
160
+ `fmt:${currency}:${display}:${signDisplay ?? ""}:${exponent}`,
161
+ intlOptions,
162
+ locale,
163
+ ).format(value as unknown as number);
164
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Strips every key whose value is explicitly `undefined`, and narrows the
3
+ * return type to match: `exactOptionalPropertyTypes` (this workspace's own
4
+ * `tsconfig`) makes `{ disabled?: boolean }` reject a spread whose *type*
5
+ * is `{ disabled?: boolean | undefined }` even when the value at runtime is
6
+ * always defined for the keys that matter — every `React.*HTMLAttributes`
7
+ * type declares its optional fields as `T | undefined` explicitly, but
8
+ * Headless UI's own prop types (`ListboxProps`, `MenuItemProps`, `LabelProps`,
9
+ * …) declare theirs as plain `T`. Spreading `...props` from a native-element
10
+ * prop type onto a Headless UI component hits this every time (found
11
+ * porting `select.tsx`/`dropdown-menu.tsx`/`label.tsx` off Radix, D3) —
12
+ * this is the one general fix rather than a one-off cast at each call site.
13
+ */
14
+ export function omitUndefined<T extends Record<string, unknown>>(
15
+ props: T,
16
+ ): { [K in keyof T]: Exclude<T[K], undefined> } {
17
+ const result = {} as { [K in keyof T]: Exclude<T[K], undefined> };
18
+ for (const key of Object.keys(props) as (keyof T)[]) {
19
+ const value = props[key];
20
+ if (value !== undefined) {
21
+ result[key] = value as Exclude<T[keyof T], undefined>;
22
+ }
23
+ }
24
+ return result;
25
+ }