@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.
- package/CHANGELOG.md +104 -0
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/components/data/code.d.ts +37 -0
- package/dist/components/data/code.d.ts.map +1 -0
- package/dist/components/data/code.js +37 -0
- package/dist/components/data/code.js.map +1 -0
- package/dist/components/data/copy-button.d.ts +39 -0
- package/dist/components/data/copy-button.d.ts.map +1 -0
- package/dist/components/data/copy-button.js +50 -0
- package/dist/components/data/copy-button.js.map +1 -0
- package/dist/components/data/detail-row.d.ts +69 -0
- package/dist/components/data/detail-row.d.ts.map +1 -0
- package/dist/components/data/detail-row.js +27 -0
- package/dist/components/data/detail-row.js.map +1 -0
- package/dist/components/data/id-display.d.ts +33 -0
- package/dist/components/data/id-display.d.ts.map +1 -0
- package/dist/components/data/id-display.js +25 -0
- package/dist/components/data/id-display.js.map +1 -0
- package/dist/components/data/masked-value.d.ts +47 -0
- package/dist/components/data/masked-value.d.ts.map +1 -0
- package/dist/components/data/masked-value.js +46 -0
- package/dist/components/data/masked-value.js.map +1 -0
- package/dist/components/data/money.d.ts +28 -0
- package/dist/components/data/money.d.ts.map +1 -0
- package/dist/components/data/money.js +22 -0
- package/dist/components/data/money.js.map +1 -0
- package/dist/components/data/phone-display.d.ts +45 -0
- package/dist/components/data/phone-display.d.ts.map +1 -0
- package/dist/components/data/phone-display.js +23 -0
- package/dist/components/data/phone-display.js.map +1 -0
- package/dist/components/data/stat-tile.d.ts +33 -0
- package/dist/components/data/stat-tile.d.ts.map +1 -0
- package/dist/components/data/stat-tile.js +18 -0
- package/dist/components/data/stat-tile.js.map +1 -0
- package/dist/components/data/timestamp-display.d.ts +14 -0
- package/dist/components/data/timestamp-display.d.ts.map +1 -0
- package/dist/components/data/timestamp-display.js +77 -0
- package/dist/components/data/timestamp-display.js.map +1 -0
- package/dist/components/patterns/inline-banner.d.ts +22 -0
- package/dist/components/patterns/inline-banner.d.ts.map +1 -0
- package/dist/components/patterns/inline-banner.js +11 -0
- package/dist/components/patterns/inline-banner.js.map +1 -0
- package/dist/components/patterns/inline-empty-state.d.ts +20 -0
- package/dist/components/patterns/inline-empty-state.d.ts.map +1 -0
- package/dist/components/patterns/inline-empty-state.js +11 -0
- package/dist/components/patterns/inline-empty-state.js.map +1 -0
- package/dist/components/patterns/live-row.d.ts +23 -0
- package/dist/components/patterns/live-row.d.ts.map +1 -0
- package/dist/components/patterns/live-row.js +48 -0
- package/dist/components/patterns/live-row.js.map +1 -0
- package/dist/components/patterns/payload-inspector.d.ts +25 -0
- package/dist/components/patterns/payload-inspector.d.ts.map +1 -0
- package/dist/components/patterns/payload-inspector.js +33 -0
- package/dist/components/patterns/payload-inspector.js.map +1 -0
- package/dist/components/patterns/route-skeleton.d.ts +15 -0
- package/dist/components/patterns/route-skeleton.d.ts.map +1 -0
- package/dist/components/patterns/route-skeleton.js +7 -0
- package/dist/components/patterns/route-skeleton.js.map +1 -0
- package/dist/components/patterns/screen-layout.d.ts +14 -0
- package/dist/components/patterns/screen-layout.d.ts.map +1 -0
- package/dist/components/patterns/screen-layout.js +11 -0
- package/dist/components/patterns/screen-layout.js.map +1 -0
- package/dist/components/patterns/stale-write-banner.d.ts +26 -0
- package/dist/components/patterns/stale-write-banner.d.ts.map +1 -0
- package/dist/components/patterns/stale-write-banner.js +28 -0
- package/dist/components/patterns/stale-write-banner.js.map +1 -0
- package/dist/components/patterns/state-timeline.d.ts +58 -0
- package/dist/components/patterns/state-timeline.d.ts.map +1 -0
- package/dist/components/patterns/state-timeline.js +103 -0
- package/dist/components/patterns/state-timeline.js.map +1 -0
- package/dist/components/primitives/badge.d.ts +27 -0
- package/dist/components/primitives/badge.d.ts.map +1 -0
- package/dist/components/primitives/badge.js +33 -0
- package/dist/components/primitives/badge.js.map +1 -0
- package/dist/components/primitives/button.d.ts +12 -0
- package/dist/components/primitives/button.d.ts.map +1 -0
- package/dist/components/primitives/button.js +42 -0
- package/dist/components/primitives/button.js.map +1 -0
- package/dist/components/primitives/calendar.d.ts +39 -0
- package/dist/components/primitives/calendar.d.ts.map +1 -0
- package/dist/components/primitives/calendar.js +102 -0
- package/dist/components/primitives/calendar.js.map +1 -0
- package/dist/components/primitives/card.d.ts +24 -0
- package/dist/components/primitives/card.d.ts.map +1 -0
- package/dist/components/primitives/card.js +28 -0
- package/dist/components/primitives/card.js.map +1 -0
- package/dist/components/primitives/checkbox.d.ts +42 -0
- package/dist/components/primitives/checkbox.d.ts.map +1 -0
- package/dist/components/primitives/checkbox.js +31 -0
- package/dist/components/primitives/checkbox.js.map +1 -0
- package/dist/components/primitives/chip-select.d.ts +38 -0
- package/dist/components/primitives/chip-select.d.ts.map +1 -0
- package/dist/components/primitives/chip-select.js +25 -0
- package/dist/components/primitives/chip-select.js.map +1 -0
- package/dist/components/primitives/command-menu.d.ts +54 -0
- package/dist/components/primitives/command-menu.d.ts.map +1 -0
- package/dist/components/primitives/command-menu.js +24 -0
- package/dist/components/primitives/command-menu.js.map +1 -0
- package/dist/components/primitives/confirm-dialog.d.ts +52 -0
- package/dist/components/primitives/confirm-dialog.d.ts.map +1 -0
- package/dist/components/primitives/confirm-dialog.js +30 -0
- package/dist/components/primitives/confirm-dialog.js.map +1 -0
- package/dist/components/primitives/date-picker.d.ts +68 -0
- package/dist/components/primitives/date-picker.d.ts.map +1 -0
- package/dist/components/primitives/date-picker.js +107 -0
- package/dist/components/primitives/date-picker.js.map +1 -0
- package/dist/components/primitives/dialog.d.ts +50 -0
- package/dist/components/primitives/dialog.d.ts.map +1 -0
- package/dist/components/primitives/dialog.js +90 -0
- package/dist/components/primitives/dialog.js.map +1 -0
- package/dist/components/primitives/drawer.d.ts +71 -0
- package/dist/components/primitives/drawer.d.ts.map +1 -0
- package/dist/components/primitives/drawer.js +93 -0
- package/dist/components/primitives/drawer.js.map +1 -0
- package/dist/components/primitives/dropdown-menu.d.ts +40 -0
- package/dist/components/primitives/dropdown-menu.d.ts.map +1 -0
- package/dist/components/primitives/dropdown-menu.js +53 -0
- package/dist/components/primitives/dropdown-menu.js.map +1 -0
- package/dist/components/primitives/form-field.d.ts +94 -0
- package/dist/components/primitives/form-field.d.ts.map +1 -0
- package/dist/components/primitives/form-field.js +27 -0
- package/dist/components/primitives/form-field.js.map +1 -0
- package/dist/components/primitives/inline-confirm.d.ts +62 -0
- package/dist/components/primitives/inline-confirm.d.ts.map +1 -0
- package/dist/components/primitives/inline-confirm.js +11 -0
- package/dist/components/primitives/inline-confirm.js.map +1 -0
- package/dist/components/primitives/input.d.ts +4 -0
- package/dist/components/primitives/input.d.ts.map +1 -0
- package/dist/components/primitives/input.js +13 -0
- package/dist/components/primitives/input.js.map +1 -0
- package/dist/components/primitives/label.d.ts +26 -0
- package/dist/components/primitives/label.d.ts.map +1 -0
- package/dist/components/primitives/label.js +33 -0
- package/dist/components/primitives/label.js.map +1 -0
- package/dist/components/primitives/pagination.d.ts +54 -0
- package/dist/components/primitives/pagination.d.ts.map +1 -0
- package/dist/components/primitives/pagination.js +31 -0
- package/dist/components/primitives/pagination.js.map +1 -0
- package/dist/components/primitives/popover.d.ts +12 -0
- package/dist/components/primitives/popover.d.ts.map +1 -0
- package/dist/components/primitives/popover.js +22 -0
- package/dist/components/primitives/popover.js.map +1 -0
- package/dist/components/primitives/progress.d.ts +25 -0
- package/dist/components/primitives/progress.d.ts.map +1 -0
- package/dist/components/primitives/progress.js +25 -0
- package/dist/components/primitives/progress.js.map +1 -0
- package/dist/components/primitives/radio-group.d.ts +47 -0
- package/dist/components/primitives/radio-group.d.ts.map +1 -0
- package/dist/components/primitives/radio-group.js +11 -0
- package/dist/components/primitives/radio-group.js.map +1 -0
- package/dist/components/primitives/select.d.ts +39 -0
- package/dist/components/primitives/select.d.ts.map +1 -0
- package/dist/components/primitives/select.js +142 -0
- package/dist/components/primitives/select.js.map +1 -0
- package/dist/components/primitives/separator.d.ts +9 -0
- package/dist/components/primitives/separator.d.ts.map +1 -0
- package/dist/components/primitives/separator.js +16 -0
- package/dist/components/primitives/separator.js.map +1 -0
- package/dist/components/primitives/side-nav.d.ts +65 -0
- package/dist/components/primitives/side-nav.d.ts.map +1 -0
- package/dist/components/primitives/side-nav.js +100 -0
- package/dist/components/primitives/side-nav.js.map +1 -0
- package/dist/components/primitives/skeleton.d.ts +22 -0
- package/dist/components/primitives/skeleton.d.ts.map +1 -0
- package/dist/components/primitives/skeleton.js +25 -0
- package/dist/components/primitives/skeleton.js.map +1 -0
- package/dist/components/primitives/spinner.d.ts +20 -0
- package/dist/components/primitives/spinner.d.ts.map +1 -0
- package/dist/components/primitives/spinner.js +21 -0
- package/dist/components/primitives/spinner.js.map +1 -0
- package/dist/components/primitives/switch.d.ts +29 -0
- package/dist/components/primitives/switch.d.ts.map +1 -0
- package/dist/components/primitives/switch.js +24 -0
- package/dist/components/primitives/switch.js.map +1 -0
- package/dist/components/primitives/table.d.ts +23 -0
- package/dist/components/primitives/table.d.ts.map +1 -0
- package/dist/components/primitives/table.js +81 -0
- package/dist/components/primitives/table.js.map +1 -0
- package/dist/components/primitives/tabs.d.ts +38 -0
- package/dist/components/primitives/tabs.d.ts.map +1 -0
- package/dist/components/primitives/tabs.js +84 -0
- package/dist/components/primitives/tabs.js.map +1 -0
- package/dist/components/primitives/textarea.d.ts +4 -0
- package/dist/components/primitives/textarea.d.ts.map +1 -0
- package/dist/components/primitives/textarea.js +11 -0
- package/dist/components/primitives/textarea.js.map +1 -0
- package/dist/components/primitives/toast.d.ts +24 -0
- package/dist/components/primitives/toast.d.ts.map +1 -0
- package/dist/components/primitives/toast.js +58 -0
- package/dist/components/primitives/toast.js.map +1 -0
- package/dist/components/primitives/tooltip.d.ts +30 -0
- package/dist/components/primitives/tooltip.d.ts.map +1 -0
- package/dist/components/primitives/tooltip.js +6 -0
- package/dist/components/primitives/tooltip.js.map +1 -0
- package/dist/components/status/state-chip.d.ts +53 -0
- package/dist/components/status/state-chip.d.ts.map +1 -0
- package/dist/components/status/state-chip.js +8 -0
- package/dist/components/status/state-chip.js.map +1 -0
- package/dist/components/status/state-mark.d.ts +21 -0
- package/dist/components/status/state-mark.d.ts.map +1 -0
- package/dist/components/status/state-mark.js +90 -0
- package/dist/components/status/state-mark.js.map +1 -0
- package/dist/components/status/status-pill.d.ts +62 -0
- package/dist/components/status/status-pill.d.ts.map +1 -0
- package/dist/components/status/status-pill.js +65 -0
- package/dist/components/status/status-pill.js.map +1 -0
- package/dist/components/status/status-tokens.d.ts +110 -0
- package/dist/components/status/status-tokens.d.ts.map +1 -0
- package/dist/components/status/status-tokens.js +102 -0
- package/dist/components/status/status-tokens.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cn.d.ts +11 -0
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/lib/cn.js +78 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/money.d.ts +73 -0
- package/dist/lib/money.d.ts.map +1 -0
- package/dist/lib/money.js +123 -0
- package/dist/lib/money.js.map +1 -0
- package/dist/lib/omit-undefined.d.ts +17 -0
- package/dist/lib/omit-undefined.d.ts.map +1 -0
- package/dist/lib/omit-undefined.js +24 -0
- package/dist/lib/omit-undefined.js.map +1 -0
- package/package.json +100 -0
- package/src/components/data/code.tsx +43 -0
- package/src/components/data/copy-button.tsx +97 -0
- package/src/components/data/detail-row.tsx +113 -0
- package/src/components/data/id-display.tsx +49 -0
- package/src/components/data/masked-value.tsx +96 -0
- package/src/components/data/money.tsx +46 -0
- package/src/components/data/phone-display.tsx +60 -0
- package/src/components/data/stat-tile.tsx +68 -0
- package/src/components/data/timestamp-display.tsx +106 -0
- package/src/components/patterns/inline-banner.tsx +60 -0
- package/src/components/patterns/inline-empty-state.tsx +45 -0
- package/src/components/patterns/live-row.tsx +75 -0
- package/src/components/patterns/payload-inspector.tsx +157 -0
- package/src/components/patterns/route-skeleton.tsx +77 -0
- package/src/components/patterns/screen-layout.tsx +44 -0
- package/src/components/patterns/stale-write-banner.tsx +44 -0
- package/src/components/patterns/state-timeline.tsx +229 -0
- package/src/components/primitives/badge.tsx +40 -0
- package/src/components/primitives/button.tsx +54 -0
- package/src/components/primitives/calendar.tsx +136 -0
- package/src/components/primitives/card.tsx +70 -0
- package/src/components/primitives/checkbox.tsx +101 -0
- package/src/components/primitives/chip-select.tsx +105 -0
- package/src/components/primitives/command-menu.tsx +92 -0
- package/src/components/primitives/confirm-dialog.tsx +109 -0
- package/src/components/primitives/date-picker.tsx +271 -0
- package/src/components/primitives/dialog.tsx +195 -0
- package/src/components/primitives/drawer.tsx +326 -0
- package/src/components/primitives/dropdown-menu.tsx +128 -0
- package/src/components/primitives/form-field.tsx +136 -0
- package/src/components/primitives/inline-confirm.tsx +118 -0
- package/src/components/primitives/input.tsx +21 -0
- package/src/components/primitives/label.tsx +43 -0
- package/src/components/primitives/pagination.tsx +110 -0
- package/src/components/primitives/popover.tsx +41 -0
- package/src/components/primitives/progress.tsx +66 -0
- package/src/components/primitives/radio-group.tsx +90 -0
- package/src/components/primitives/select.tsx +254 -0
- package/src/components/primitives/separator.tsx +49 -0
- package/src/components/primitives/side-nav.tsx +253 -0
- package/src/components/primitives/skeleton.tsx +25 -0
- package/src/components/primitives/spinner.tsx +39 -0
- package/src/components/primitives/switch.tsx +74 -0
- package/src/components/primitives/table.tsx +157 -0
- package/src/components/primitives/tabs.tsx +163 -0
- package/src/components/primitives/textarea.tsx +21 -0
- package/src/components/primitives/toast.tsx +117 -0
- package/src/components/primitives/tooltip.tsx +38 -0
- package/src/components/status/state-chip.tsx +70 -0
- package/src/components/status/state-mark.tsx +154 -0
- package/src/components/status/status-pill.tsx +153 -0
- package/src/components/status/status-tokens.ts +173 -0
- package/src/index.ts +96 -0
- package/src/lib/cn.ts +168 -0
- package/src/lib/money.ts +164 -0
- package/src/lib/omit-undefined.ts +25 -0
- 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
|