@brokenrubik/ui 0.0.0-stage → 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/README.md +264 -2
- package/dist/components/accordion.d.ts +13 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +25 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert-dialog.d.ts +21 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.js +46 -0
- package/dist/components/alert-dialog.js.map +1 -0
- package/dist/components/alert.d.ts +24 -0
- package/dist/components/alert.d.ts.map +1 -0
- package/dist/components/alert.js +42 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/avatar.d.ts +27 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +53 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +21 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +40 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +18 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +39 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +13 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +68 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/card.d.ts +15 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +31 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/checkbox.d.ts +6 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +12 -0
- package/dist/components/checkbox.js.map +1 -0
- package/dist/components/command.d.ts +24 -0
- package/dist/components/command.d.ts.map +1 -0
- package/dist/components/command.js +41 -0
- package/dist/components/command.js.map +1 -0
- package/dist/components/date-picker.d.ts +24 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +18 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +23 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js +45 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +34 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +61 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/empty-state.d.ts +13 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +25 -0
- package/dist/components/empty-state.js.map +1 -0
- package/dist/components/field.d.ts +24 -0
- package/dist/components/field.d.ts.map +1 -0
- package/dist/components/field.js +34 -0
- package/dist/components/field.js.map +1 -0
- package/dist/components/input.d.ts +17 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +18 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/kbd.d.ts +14 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.js +31 -0
- package/dist/components/kbd.js.map +1 -0
- package/dist/components/label.d.ts +6 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +11 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/pagination.d.ts +18 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +33 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/popover.d.ts +12 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +29 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +17 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +26 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/radio-group.d.ts +7 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +14 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/select.d.ts +20 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +46 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +6 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +11 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +19 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.js +47 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.js +9 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/sonner.d.ts +13 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.js +40 -0
- package/dist/components/sonner.js.map +1 -0
- package/dist/components/switch.d.ts +6 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +11 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +24 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +5 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +10 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/toggle-group.d.ts +11 -0
- package/dist/components/toggle-group.d.ts.map +1 -0
- package/dist/components/toggle-group.js +18 -0
- package/dist/components/toggle-group.js.map +1 -0
- package/dist/components/tooltip.d.ts +13 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js +24 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tone.d.ts +8 -0
- package/dist/lib/tone.d.ts.map +1 -0
- package/dist/lib/tone.js +14 -0
- package/dist/lib/tone.js.map +1 -0
- package/dist/lib/utils.d.ts +20 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +40 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles/base.css +37 -0
- package/dist/styles/fonts.css +16 -0
- package/dist/styles/styles.css +14 -0
- package/dist/styles/theme.css +170 -0
- package/dist/styles/tokens.css +228 -0
- package/docs/design-handoff.md +112 -0
- package/docs/figma-vs-tailwind.md +135 -0
- package/docs/visual-testing.md +156 -0
- package/package.json +96 -4
- package/src/components/accordion.tsx +66 -0
- package/src/components/alert-dialog.tsx +139 -0
- package/src/components/alert.tsx +87 -0
- package/src/components/avatar.tsx +105 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/button.tsx +70 -0
- package/src/components/calendar.tsx +131 -0
- package/src/components/card.tsx +72 -0
- package/src/components/checkbox.tsx +31 -0
- package/src/components/command.tsx +149 -0
- package/src/components/date-picker.tsx +86 -0
- package/src/components/dialog.tsx +126 -0
- package/src/components/dropdown-menu.tsx +225 -0
- package/src/components/empty-state.tsx +63 -0
- package/src/components/field.tsx +79 -0
- package/src/components/input.tsx +49 -0
- package/src/components/kbd.tsx +48 -0
- package/src/components/label.tsx +22 -0
- package/src/components/pagination.tsx +94 -0
- package/src/components/popover.tsx +65 -0
- package/src/components/progress.tsx +52 -0
- package/src/components/radio-group.tsx +42 -0
- package/src/components/select.tsx +167 -0
- package/src/components/separator.tsx +29 -0
- package/src/components/sheet.tsx +112 -0
- package/src/components/skeleton.tsx +16 -0
- package/src/components/sonner.tsx +58 -0
- package/src/components/switch.tsx +27 -0
- package/src/components/tabs.tsx +56 -0
- package/src/components/textarea.tsx +17 -0
- package/src/components/toggle-group.tsx +42 -0
- package/src/components/tooltip.tsx +55 -0
- package/src/index.ts +35 -0
- package/src/lib/tone.ts +16 -0
- package/src/lib/utils.ts +46 -0
- package/src/styles/base.css +37 -0
- package/src/styles/fonts.css +16 -0
- package/src/styles/styles.css +14 -0
- package/src/styles/theme.css +170 -0
- package/src/styles/tokens.css +228 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# BR Admin Library vs Tailwind / shadcn defaults
|
|
2
|
+
|
|
3
|
+
What the BR Admin Library (Figma `ONZyUryZr526mtbex2HVPk`, pages *Foundations* `2004:2` and
|
|
4
|
+
*Components · Base* `2004:3`) changes compared with Tailwind v4 and shadcn/ui out of the box.
|
|
5
|
+
Read from the Figma file on 2026-11-09; colors checked again through the Figma MCP on 2026-11-11. Every value here is in `src/styles/tokens.css` or
|
|
6
|
+
`src/styles/theme.css`.
|
|
7
|
+
|
|
8
|
+
## 1. Color tokens
|
|
9
|
+
|
|
10
|
+
Token names are the shadcn names, so `var(--primary)` in code is `primary` in Figma.
|
|
11
|
+
|
|
12
|
+
| Token | shadcn default (neutral) | BR light | BR dark |
|
|
13
|
+
| --- | --- | --- | --- |
|
|
14
|
+
| `background` | white | `#ffffff` | `#0f172a` (slate-900) |
|
|
15
|
+
| `foreground` | neutral-950 | `#0f172a` | `#e2e8f0` |
|
|
16
|
+
| `card` / `popover` | white | `#ffffff` | `#162032` (slate-875, not in Tailwind) |
|
|
17
|
+
| `primary` | neutral-900 | **`#9547ff` BR violet** | `#9547ff` |
|
|
18
|
+
| `primary-foreground` | neutral-50 | `#ffffff` | `#ffffff` |
|
|
19
|
+
| `secondary`, `muted`, `accent` | neutral-100 | `#f1f5f9` (slate-100) | `#1e293b`; accent `rgb(255 255 255 / .05)` |
|
|
20
|
+
| `muted-foreground` | neutral-500 | `#64748b` (slate-500) | `#94a3b8` |
|
|
21
|
+
| `border` | neutral-200 | `#e2e8f0` (slate-200) | `rgb(255 255 255 / .1)` |
|
|
22
|
+
| `input` | = border | **`#8391a7`** (3:1 contrast, WCAG non-text) | `#64748b` |
|
|
23
|
+
| `ring` | neutral-400 | `#9547ff` | `#9547ff` |
|
|
24
|
+
| `destructive` | red-ish oklch | `#dc2626` (red-600) | `#dc2626` |
|
|
25
|
+
|
|
26
|
+
**Tokens that shadcn does not have** (all in Figma):
|
|
27
|
+
|
|
28
|
+
- `primary-strong` `#6b1fd0` / `#a875ff` and `primary-tint` `#f4edff` / violet 20 %: violet as
|
|
29
|
+
text and as a soft fill. Rule: never use `primary` for text.
|
|
30
|
+
- State triads, *base* (dots, bars) + `-strong` (text, icons) + `-tint` (soft fills):
|
|
31
|
+
`success` `#10b981` / `#047857` / `#ecfdf5`, `warning` `#f59e0b` / `#b45309` / `#fffbeb`,
|
|
32
|
+
`destructive` `#dc2626` / `#dc2626` / `#fef2f2`. Dark: strong = 400 shades, tint = base 15 %.
|
|
33
|
+
- `highlight` `#dff95f` BR lime, "you are here" only, with `highlight-foreground` `#0f172a`.
|
|
34
|
+
- `brand-coral` `#ff707a`: marketing only.
|
|
35
|
+
- `sidebar-*`: navy sidebar in both themes (`#0f172a` light, `#0b1120` dark), lime marker, plus
|
|
36
|
+
`sidebar-muted-foreground`, `sidebar-label` and `sidebar-accent-strong` (shadcn has none of
|
|
37
|
+
them). `sidebar-ring` is violet `#a875ff` on the navy sidebar and `#9547ff` on the light one.
|
|
38
|
+
- Two more Figma modes, *Light · Sidebar claro* and *Dark · Sidebar claro*, re-point only the
|
|
39
|
+
sidebar tokens (`.sidebar-light` on `<html>`, in either theme). In the dark one, the `app-*`
|
|
40
|
+
colors keep their light values.
|
|
41
|
+
- `info` `#3b82f6` / `info-strong` `#1d4ed8` / `info-tint` `#eff6ff` (dark: `#3b82f6` /
|
|
42
|
+
`#60a5fa` / blue 15 %), the same triad as the other states.
|
|
43
|
+
- `overlay`: the dialog and sheet backdrop, black at 50 % (dark: 60 %).
|
|
44
|
+
- `surface-subtle` `#fbfdfd` (dark: = `background`): a slightly raised page area.
|
|
45
|
+
- `chart-1…5`: violet, slate-500, violet-300 `#c4a6ff`, slate-300, violet-700.
|
|
46
|
+
- `app-proma`, `app-folio`, `app-docubuilder`, `app-timma`, `app-mara`: app identity colors
|
|
47
|
+
(logo, app icon, App Rail only).
|
|
48
|
+
|
|
49
|
+
**Source:** every color above is a Figma variable in the *Theme* collection with the code syntax
|
|
50
|
+
`var(--name)`. The values were read through the Figma MCP on 2026-11-11
|
|
51
|
+
(`visual/figma/variables.json`). The only token that is not a Figma variable is `--radius`, the
|
|
52
|
+
base that the radius scale is derived from.
|
|
53
|
+
|
|
54
|
+
## 2. Radius
|
|
55
|
+
|
|
56
|
+
| | shadcn (`--radius: .625rem`) | Tailwind v4 default | **BR (Figma radius/\*)** |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| xs | – | 2 px | **4 px** (checkbox, muted key chip) |
|
|
59
|
+
| sm | 6 px | 4 px | **5 px** (menu item, toggle item, close button) |
|
|
60
|
+
| md | 8 px | 6 px | **6 px** (input, select, badge, icon button, menu panel, tooltip) |
|
|
61
|
+
| lg | 10 px | 8 px | **8 px** (button, card, alert, popover, toast) |
|
|
62
|
+
| xl | 14 px | 12 px | **12 px** (dialog) |
|
|
63
|
+
|
|
64
|
+
One knob, as in shadcn: `--radius: 0.5rem`; the scale is `lg = radius`, `md = −2`, `sm = −3`,
|
|
65
|
+
`xs = −4`, `xl = +4`.
|
|
66
|
+
|
|
67
|
+
## 3. Shadows (Figma effect styles, slate-900 tinted, not black)
|
|
68
|
+
|
|
69
|
+
| | Tailwind v4 | **BR** |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| `shadow-xs` | `0 1px 2px 0 rgb(0 0 0 / .05)` | `0 1px 2px 0 rgb(15 23 42 / .05)` |
|
|
72
|
+
| `shadow-md` | `0 4px 6px -1px / .1, 0 2px 4px -2px / .1` | `0 4px 12px -2px / .08, 0 2px 4px -2px / .05` |
|
|
73
|
+
| `shadow-lg` | `0 10px 15px -3px / .1, 0 4px 6px -4px / .1` | `0 16px 32px -8px / .16, 0 4px 8px -4px / .06` |
|
|
74
|
+
|
|
75
|
+
Cards are flat (no shadow). Outline buttons, inputs, switch thumb: `shadow-xs`. Menus,
|
|
76
|
+
popovers, tooltips, calendar: `shadow-md`. Dialogs, sheets, toasts, command: `shadow-lg`.
|
|
77
|
+
|
|
78
|
+
## 4. Typography
|
|
79
|
+
|
|
80
|
+
Tailwind: system font, `text-sm` = 14/20 is the usual UI size. **BR: IBM Plex Sans / Plex Mono
|
|
81
|
+
(the package loads them, `fonts.css`), a dense 13 px base.** The Figma text styles are `text-*`
|
|
82
|
+
utilities (size, line height, tracking, weight); Tailwind's own `text-xs … text-9xl` stay.
|
|
83
|
+
|
|
84
|
+
| Utility | Figma style | Size / line | Weight | Tracking |
|
|
85
|
+
| --- | --- | --- | --- | --- |
|
|
86
|
+
| `text-display` | Heading/Display | 32/40 | 600 | −0.48 |
|
|
87
|
+
| `text-page-title` | Heading/Page title | 20/28 | 600 | −0.4 |
|
|
88
|
+
| `text-section` | Heading/Section | 15/20 | 600 | −0.2 |
|
|
89
|
+
| `text-card-title` | Heading/Card | 14/20 | 600 | −0.1 |
|
|
90
|
+
| `text-body-lg` | Body/Large | 15/22 | 400 | 0 |
|
|
91
|
+
| `text-body` | Body/Default | **13/20** | 400 | 0 |
|
|
92
|
+
| `text-body-sm` | Body/Small | 12/16 | 400 | 0 |
|
|
93
|
+
| `text-caption` | Label/Caption | 11/16 | 400 | 0 |
|
|
94
|
+
| `text-overline` | Label/Overline (+ `uppercase`) | 11/16 | 500 | 0.4 |
|
|
95
|
+
| `text-nav` / `text-nav-label` | Nav/Item, Nav/Group label | 14/20, 12/16 | 500 | −0.15, 0 |
|
|
96
|
+
| `text-initials`, `-sm` | Label/Initials (small) | 11/16, 8/12 | 600 | 0.2 |
|
|
97
|
+
| `text-mono`, `-sm` | Mono/Default, Mono/Small | 12/16, 11/16 | 400 | 0 |
|
|
98
|
+
| `text-doc-title`, `-h2`, `-body` | Doc/\* | 36/44, 20/28, 15/26 | 600, 600, 400 | |
|
|
99
|
+
|
|
100
|
+
"Medium" styles = the same utility + `font-medium`.
|
|
101
|
+
|
|
102
|
+
## 5. Spacing
|
|
103
|
+
|
|
104
|
+
No change: Figma uses Tailwind's 4 px grid (rows and controls 4–12, sections 24–48).
|
|
105
|
+
|
|
106
|
+
## 6. Components vs shadcn new-york v4
|
|
107
|
+
|
|
108
|
+
| Component | shadcn default | BR (Figma) |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| Button | h-9 / sm h-8, `rounded-md`, `text-sm` | **h-8 / sm h-7**, 8 px radius, Body/Default Medium (13) / Body/Small Medium; secondary = slate fill (not outline) |
|
|
111
|
+
| Icon button | size-9 | **28 / 24 px**, 6 px radius |
|
|
112
|
+
| Input, Select, Date picker | h-9, `border-input` = border color, 3 px ring/50 focus | **h-8**, `input` border `#8391a7`, focus = 1.5 px violet border, Shadow/xs |
|
|
113
|
+
| Badge | pill, solid colors | **20 px, 6 px radius, tint + strong text**, 7 tones |
|
|
114
|
+
| Card | `rounded-xl`, py-6, gap-6, shadow-sm | **8 px radius, p-4, flat** |
|
|
115
|
+
| Dialog | max-w-lg, `rounded-lg`, p-6 | **480 px, 12 px radius**, title 20/28 |
|
|
116
|
+
| Tabs | segmented pill in a muted list | **underline tabs** (40 px, violet 2 px underline); segmented = ToggleGroup |
|
|
117
|
+
| Toggle group | outline items | muted track, white "on" item with Shadow/xs |
|
|
118
|
+
| Menu item | py-1.5, `text-sm` | **32 px**, 5 px radius, Body/Default; shortcut Label/Caption |
|
|
119
|
+
| Alert | card with red text for destructive | **tinted per tone**, -strong text and icon; info = bordered, violet icon |
|
|
120
|
+
| Tooltip | primary fill + arrow | **foreground fill (inverted), no arrow**, Shadow/md |
|
|
121
|
+
| Avatar | grey fallback | 20 / 28 px, **tinted per person** (purple, amber, red, emerald, slate) |
|
|
122
|
+
| Kbd ("Key Chip") | not in shadcn | lime space key (20 / 24) and muted mono hint |
|
|
123
|
+
| Empty state | not in shadcn | bordered box, 40 px icon tile |
|
|
124
|
+
|
|
125
|
+
## 7. Drift found in the apps (2026-11-09)
|
|
126
|
+
|
|
127
|
+
| App | Difference from Figma |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| Proma | `accent` is violet (`#f5effe` / `#6b21d6`) instead of slate; `radius-sm` 4 px (Figma 5); own shadow scale |
|
|
130
|
+
| Timma | dark `accent` `#1e293b` (Figma `rgb(255 255 255 / .05)`); Button `secondary` = outline and primary is semibold (Figma: slate fill, medium) |
|
|
131
|
+
| Mara, Folio | dark `accent` `#1e293b`; ~200 lines of tokens copied per app |
|
|
132
|
+
| DocuBuilder | still shadcn defaults (neutral oklch, `--radius: .625rem`) |
|
|
133
|
+
|
|
134
|
+
Each app keeps its own copy of the tokens today. With this package they import one file and
|
|
135
|
+
keep only their real exceptions (see README › Extend).
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Visual testing against Figma
|
|
2
|
+
|
|
3
|
+
The visual test proves that the components look like the BR Admin Library in Figma. It runs in
|
|
4
|
+
CI on every pull request. This page tells how it works, how to run it, how to read the result,
|
|
5
|
+
and how to change it. Read it before you change a component or the test.
|
|
6
|
+
|
|
7
|
+
## What the test checks
|
|
8
|
+
|
|
9
|
+
For each component set on the Figma page *Components · Base*:
|
|
10
|
+
|
|
11
|
+
1. **The spec page draws the set.** `/?spec=Button` renders every variant of the Figma set
|
|
12
|
+
"Button" at its Figma x/y position, in a box of the same size as the Figma set. The
|
|
13
|
+
positions come from `playground/src/spec/figma-layout.json`.
|
|
14
|
+
2. **Pixel diff.** Playwright takes a screenshot of that box at 2x. `pixelmatch` compares it with
|
|
15
|
+
the Figma PNG export (`visual/figma/<node-id>.png`, also 2x). The result is the percentage of
|
|
16
|
+
pixels that differ.
|
|
17
|
+
3. **Geometry.** In the browser, the test measures each variant (width, height) and the texts and
|
|
18
|
+
icons in it. It compares them with the Figma boxes in `visual/figma/geometry.json`.
|
|
19
|
+
|
|
20
|
+
### Pass and fail
|
|
21
|
+
|
|
22
|
+
| Check | Fails when |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| Pixel diff | The % is more than the set's value in `visual/baseline.json` + 0.5. A set with a Figma PNG but no baseline also fails. |
|
|
25
|
+
| Size, fixed side | Width or height differs by 0.75 px or more. |
|
|
26
|
+
| Size, "hug" side | Width or height differs by 2.5 px or more. (Figma "hug" means the size follows the text. Chrome draws text up to ±1 px wider or narrower per word than Figma.) |
|
|
27
|
+
| Image size | The screenshot and the Figma PNG have different sizes. |
|
|
28
|
+
| Text and icon positions | Never fails. Differences of 0.75 px or more show as warnings (`--verbose`). |
|
|
29
|
+
|
|
30
|
+
### Why there is a baseline and not 0 %
|
|
31
|
+
|
|
32
|
+
Chrome and Figma draw letters differently (rasterization and glyph widths). A component that is
|
|
33
|
+
correct still has 0–3 % of pixels that differ, all on the text edges. The baseline records that
|
|
34
|
+
accepted value for each set. A real change (a wrong padding, color or radius) adds more than
|
|
35
|
+
0.5 points or fails the geometry check.
|
|
36
|
+
|
|
37
|
+
To confirm that the test catches errors, change `h-8` to `h-9` in the Button default size and
|
|
38
|
+
run `pnpm visual -- --sets Button`: it fails with `H+4 px` on each md variant and a higher pixel
|
|
39
|
+
diff.
|
|
40
|
+
|
|
41
|
+
## Run it
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm visual # build + test all sets
|
|
45
|
+
node scripts/visual/compare.mjs --sets Button,Badge # only these sets (needs a build first)
|
|
46
|
+
node scripts/visual/compare.mjs --url http://localhost:5173 # against a running dev server
|
|
47
|
+
node scripts/visual/compare.mjs --verbose # also print the warnings
|
|
48
|
+
pnpm visual:update # build + test + accept the results
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Without `--url`, the script starts `vite preview` on port 4179 with `playground/dist`, so
|
|
52
|
+
build first (`pnpm site:build`; `pnpm visual` does it). Chromium must be installed once:
|
|
53
|
+
`pnpm exec playwright install chromium`.
|
|
54
|
+
|
|
55
|
+
Exit code: `0` = all pass, `1` = a set failed, `2` = setup error (no build, no token).
|
|
56
|
+
|
|
57
|
+
## Read the result
|
|
58
|
+
|
|
59
|
+
The terminal prints one line per set:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
PASS Badge 1.7 % (baseline 1.7 %)
|
|
63
|
+
FAIL Button 2.21 % (baseline 1.28 %)
|
|
64
|
+
✗ Variant=Primary, Size=md: H+4 px
|
|
65
|
+
✗ pixel diff 2.21 % > baseline 1.28 % + 0.5
|
|
66
|
+
NO-SPEC Tooltip –
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `H+4 px`: the code is 4 px higher than Figma. `W-1`: 1 px narrower.
|
|
70
|
+
- `NO-SPEC`: `Spec.tsx` has no renderer for this set, so the set is not tested.
|
|
71
|
+
|
|
72
|
+
Open `visual/out/report.html` in a browser. For each set it shows the Figma PNG, the code
|
|
73
|
+
screenshot and the diff image (changed pixels in pink). In CI, download the artifact
|
|
74
|
+
*visual-report* from the workflow run and open `out/report.html`.
|
|
75
|
+
|
|
76
|
+
How to find the cause:
|
|
77
|
+
|
|
78
|
+
1. Look at the diff image. Pink only on letter edges = text rasterization (normal). Pink on a
|
|
79
|
+
whole edge, a whole box or a color area = a real difference.
|
|
80
|
+
2. A size error tells which variant and which side. Compare the component classes with the
|
|
81
|
+
Figma values (padding, height, gap, border width).
|
|
82
|
+
3. Run with `--verbose` to see which text or icon moved, and by how many px.
|
|
83
|
+
|
|
84
|
+
## Change the test
|
|
85
|
+
|
|
86
|
+
### Accept a new baseline
|
|
87
|
+
|
|
88
|
+
Run `pnpm visual:update` only when you know why the numbers changed:
|
|
89
|
+
|
|
90
|
+
- The component now matches Figma better (the number went down).
|
|
91
|
+
- Figma changed and you updated the component to match.
|
|
92
|
+
- A new Chromium or font version changes the text rendering of all sets a little.
|
|
93
|
+
|
|
94
|
+
Commit `visual/baseline.json` with the change and write the reason in the pull request.
|
|
95
|
+
|
|
96
|
+
**CI and local machines.** CI runs Chromium from Playwright 1.49.1 on Ubuntu 24.04 with the
|
|
97
|
+
IBM Plex fonts that the package loads (`src/styles/fonts.css`, files from `@fontsource`). A different machine can give slightly different numbers. If CI fails
|
|
98
|
+
by a small amount on all sets, take the values from the CI artifact (`out/results.json`, field
|
|
99
|
+
`mismatch`) and put them in `visual/baseline.json`.
|
|
100
|
+
|
|
101
|
+
### Add a set
|
|
102
|
+
|
|
103
|
+
1. Make sure the set is in `playground/src/spec/figma-layout.json` (run `pnpm figma:pull` if it
|
|
104
|
+
is new in Figma).
|
|
105
|
+
2. In `playground/src/spec/Spec.tsx`, add a renderer to `renderers` with the exact Figma set
|
|
106
|
+
name as the key. The function gets the variant props (`p.Variant`, `p.Size`, `p.State`…)
|
|
107
|
+
and returns the component in that state, with the same texts and icons as Figma.
|
|
108
|
+
- States that need interaction (focus, hover): add the classes of that state (see
|
|
109
|
+
`FOCUS`).
|
|
110
|
+
- Overlays (menus, dialogs) render in a portal. Use `pin(v)` for the position and put
|
|
111
|
+
`data-spec-portal={v.name}` on the content, so the geometry check measures it.
|
|
112
|
+
- If the measured element is not the first element, put `data-spec-root` on the correct one.
|
|
113
|
+
3. Open `/?spec=<Set name>` in the dev server and compare with Figma by eye.
|
|
114
|
+
4. `pnpm visual -- --sets "<Set name>"`. Fix the component until the size errors are gone.
|
|
115
|
+
5. `pnpm visual:update` to record its baseline.
|
|
116
|
+
|
|
117
|
+
Sets without a renderer today: Menu Item, Tooltip, Calendar Day, Popover, Sheet, Command, Toast,
|
|
118
|
+
Pagination.
|
|
119
|
+
|
|
120
|
+
### Update the Figma reference
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
FIGMA_TOKEN=figd_… pnpm figma:pull # layout + geometry + PNGs
|
|
124
|
+
FIGMA_TOKEN=figd_… pnpm figma:pull -- --no-images # layout + geometry only
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The token is a Figma personal access token with the scope `file_content:read` (Figma →
|
|
128
|
+
Settings → Security → Personal access tokens). Figma limits the API rate. On HTTP 429 the
|
|
129
|
+
script waits and tries again up to 5 times. If it still fails, wait some minutes.
|
|
130
|
+
|
|
131
|
+
After a pull, `visual/figma/meta.json` has the Figma file version. Commit the pulled files with
|
|
132
|
+
the component changes that match them.
|
|
133
|
+
|
|
134
|
+
## Use the tools in another project
|
|
135
|
+
|
|
136
|
+
The scripts are not specific to these components. They need three things:
|
|
137
|
+
|
|
138
|
+
1. A Figma page with component sets. Set `FIGMA_FILE_KEY` and `FIGMA_PAGE_ID` for
|
|
139
|
+
`figma-pull.mjs`.
|
|
140
|
+
2. A page that renders a set by name at the positions in `figma-layout.json`, with
|
|
141
|
+
`id="spec"` on the box and `data-spec-variant="<variant name>"` on each variant. Copy
|
|
142
|
+
`playground/src/spec/Spec.tsx` and replace the renderers.
|
|
143
|
+
3. A built site that `vite preview` can serve, or any running server (`--url`).
|
|
144
|
+
|
|
145
|
+
The paths (`playground/…`, `visual/…`) are constants at the top of each script. For a
|
|
146
|
+
second project, copy the two scripts and change those constants.
|
|
147
|
+
|
|
148
|
+
## Files
|
|
149
|
+
|
|
150
|
+
| File | Made by | Edit by hand |
|
|
151
|
+
| --- | --- | --- |
|
|
152
|
+
| `playground/src/spec/figma-layout.json` | `figma-pull` | no |
|
|
153
|
+
| `visual/figma/*.png`, `geometry.json`, `meta.json` | `figma-pull` | no |
|
|
154
|
+
| `visual/baseline.json` | `compare --update-baseline` | only with values from a CI run |
|
|
155
|
+
| `visual/out/*` | `compare` | no (not in git) |
|
|
156
|
+
| `playground/src/spec/Spec.tsx` | people | yes |
|
package/package.json
CHANGED
|
@@ -1,6 +1,98 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brokenrubik/ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "BrokenRubik UI: the BR Admin Library (Figma) as shadcn-style React components and Tailwind v4 design tokens.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+ssh://git@github.com/BrokenRubik/ui.git"
|
|
9
|
+
},
|
|
10
|
+
"packageManager": "pnpm@10.4.1",
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=22"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"sideEffects": [
|
|
16
|
+
"*.css"
|
|
17
|
+
],
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"module": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./utils": {
|
|
27
|
+
"types": "./dist/lib/utils.d.ts",
|
|
28
|
+
"import": "./dist/lib/utils.js"
|
|
29
|
+
},
|
|
30
|
+
"./components/*": {
|
|
31
|
+
"types": "./dist/components/*.d.ts",
|
|
32
|
+
"import": "./dist/components/*.js"
|
|
33
|
+
},
|
|
34
|
+
"./styles.css": "./dist/styles/styles.css",
|
|
35
|
+
"./fonts.css": "./dist/styles/fonts.css",
|
|
36
|
+
"./tokens.css": "./dist/styles/tokens.css",
|
|
37
|
+
"./theme.css": "./dist/styles/theme.css",
|
|
38
|
+
"./base.css": "./dist/styles/base.css",
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"README.md",
|
|
45
|
+
"docs"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"provenance": true
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/copy-styles.mjs",
|
|
53
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
54
|
+
"typecheck": "tsc --noEmit && pnpm build && pnpm --filter playground typecheck",
|
|
55
|
+
"playground": "pnpm --filter playground dev",
|
|
56
|
+
"site:build": "pnpm build && pnpm --filter playground build",
|
|
57
|
+
"visual": "pnpm site:build && node scripts/visual/compare.mjs",
|
|
58
|
+
"visual:update": "pnpm site:build && node scripts/visual/compare.mjs --update-baseline",
|
|
59
|
+
"figma:pull": "node scripts/visual/figma-pull.mjs",
|
|
60
|
+
"figma:sync": "node scripts/figma-sync.mjs",
|
|
61
|
+
"prepublishOnly": "pnpm typecheck"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"react": ">=18.2",
|
|
65
|
+
"react-dom": ">=18.2",
|
|
66
|
+
"tailwindcss": ">=4.0"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@fontsource/ibm-plex-mono": "^5.2.7",
|
|
70
|
+
"@fontsource/ibm-plex-sans": "^5.2.8",
|
|
71
|
+
"class-variance-authority": "^0.7.1",
|
|
72
|
+
"clsx": "^2.1.1",
|
|
73
|
+
"cmdk": "^1.1.1",
|
|
74
|
+
"lucide-react": "^1.70.0",
|
|
75
|
+
"radix-ui": "^1.8.1",
|
|
76
|
+
"react-day-picker": "^10.0.2",
|
|
77
|
+
"sonner": "^2.0.8",
|
|
78
|
+
"tailwind-merge": "^3.7.0",
|
|
79
|
+
"tw-animate-css": "^1.4.0"
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@types/react": "^19.3.0",
|
|
83
|
+
"@types/react-dom": "^19.2.3",
|
|
84
|
+
"pixelmatch": "^6.0.0",
|
|
85
|
+
"playwright": "1.65.0",
|
|
86
|
+
"pngjs": "^7.0.0",
|
|
87
|
+
"react": "^19.3.1",
|
|
88
|
+
"react-dom": "^19.3.1",
|
|
89
|
+
"tailwindcss": "^4.3.5",
|
|
90
|
+
"typescript": "~7.0.2"
|
|
91
|
+
},
|
|
92
|
+
"pnpm": {
|
|
93
|
+
"onlyBuiltDependencies": [
|
|
94
|
+
"esbuild",
|
|
95
|
+
"@tailwindcss/oxide"
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Accordion as AccordionPrimitive } from 'radix-ui';
|
|
5
|
+
import { ChevronDownIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Figma: Components · Base › Accordion Item (2085:277). Bottom border; trigger 12 px
|
|
11
|
+
* vertical padding, Body/Default Medium, muted chevron; content Body/Default muted, 12 px
|
|
12
|
+
* bottom padding.
|
|
13
|
+
*/
|
|
14
|
+
function Accordion(props: React.ComponentProps<typeof AccordionPrimitive.Root>) {
|
|
15
|
+
return <AccordionPrimitive.Root data-slot="accordion" {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function AccordionItem({ className, ...props }: React.ComponentProps<typeof AccordionPrimitive.Item>) {
|
|
19
|
+
return (
|
|
20
|
+
<AccordionPrimitive.Item
|
|
21
|
+
data-slot="accordion-item"
|
|
22
|
+
className={cn('border-b border-border', className)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AccordionTrigger({
|
|
29
|
+
className,
|
|
30
|
+
children,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) {
|
|
33
|
+
return (
|
|
34
|
+
<AccordionPrimitive.Header className="flex">
|
|
35
|
+
<AccordionPrimitive.Trigger
|
|
36
|
+
data-slot="accordion-trigger"
|
|
37
|
+
className={cn(
|
|
38
|
+
'flex flex-1 items-center justify-between gap-4 py-3 text-left text-body font-medium text-foreground transition-all outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/40 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180',
|
|
39
|
+
className,
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
>
|
|
43
|
+
{children}
|
|
44
|
+
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
|
45
|
+
</AccordionPrimitive.Trigger>
|
|
46
|
+
</AccordionPrimitive.Header>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function AccordionContent({
|
|
51
|
+
className,
|
|
52
|
+
children,
|
|
53
|
+
...props
|
|
54
|
+
}: React.ComponentProps<typeof AccordionPrimitive.Content>) {
|
|
55
|
+
return (
|
|
56
|
+
<AccordionPrimitive.Content
|
|
57
|
+
data-slot="accordion-content"
|
|
58
|
+
className="overflow-hidden text-body text-muted-foreground data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
<div className={cn('pb-3', className)}>{children}</div>
|
|
62
|
+
</AccordionPrimitive.Content>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui';
|
|
5
|
+
import type { VariantProps } from 'class-variance-authority';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils.js';
|
|
8
|
+
import { buttonVariants } from './button.js';
|
|
9
|
+
import { overlayClassName } from './dialog.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Figma: Components · Base › Dialog, Type=Destructive (2084:160): the confirm dialog.
|
|
13
|
+
* Same chrome as Dialog, no close button; Cancel (outline) + action (default or destructive).
|
|
14
|
+
*/
|
|
15
|
+
function AlertDialog(props: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
16
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function AlertDialogTrigger(props: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
20
|
+
return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AlertDialogPortal(props: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
24
|
+
return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AlertDialogOverlay({
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
31
|
+
return (
|
|
32
|
+
<AlertDialogPrimitive.Overlay
|
|
33
|
+
data-slot="alert-dialog-overlay"
|
|
34
|
+
className={cn(overlayClassName, className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function AlertDialogContent({
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
|
44
|
+
return (
|
|
45
|
+
<AlertDialogPortal>
|
|
46
|
+
<AlertDialogOverlay />
|
|
47
|
+
<AlertDialogPrimitive.Content
|
|
48
|
+
data-slot="alert-dialog-content"
|
|
49
|
+
className={cn(
|
|
50
|
+
'fixed top-1/2 left-1/2 z-50 flex w-[calc(100%-2rem)] max-w-[480px] -translate-x-1/2 -translate-y-1/2 flex-col gap-4 rounded-xl border border-border bg-background p-6 text-body text-foreground shadow-lg outline-none data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
</AlertDialogPortal>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function AlertDialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
60
|
+
return <div data-slot="alert-dialog-header" className={cn('flex flex-col gap-1.5', className)} {...props} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function AlertDialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
data-slot="alert-dialog-footer"
|
|
67
|
+
className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AlertDialogTitle({
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
77
|
+
return (
|
|
78
|
+
<AlertDialogPrimitive.Title
|
|
79
|
+
data-slot="alert-dialog-title"
|
|
80
|
+
className={cn('text-page-title', className)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AlertDialogDescription({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
90
|
+
return (
|
|
91
|
+
<AlertDialogPrimitive.Description
|
|
92
|
+
data-slot="alert-dialog-description"
|
|
93
|
+
className={cn('text-body text-muted-foreground', className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function AlertDialogAction({
|
|
100
|
+
className,
|
|
101
|
+
variant = 'default',
|
|
102
|
+
...props
|
|
103
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
|
104
|
+
Pick<VariantProps<typeof buttonVariants>, 'variant'>) {
|
|
105
|
+
return (
|
|
106
|
+
<AlertDialogPrimitive.Action
|
|
107
|
+
data-slot="alert-dialog-action"
|
|
108
|
+
className={cn(buttonVariants({ variant }), className)}
|
|
109
|
+
{...props}
|
|
110
|
+
/>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function AlertDialogCancel({
|
|
115
|
+
className,
|
|
116
|
+
...props
|
|
117
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) {
|
|
118
|
+
return (
|
|
119
|
+
<AlertDialogPrimitive.Cancel
|
|
120
|
+
data-slot="alert-dialog-cancel"
|
|
121
|
+
className={cn(buttonVariants({ variant: 'outline' }), className)}
|
|
122
|
+
{...props}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
AlertDialog,
|
|
129
|
+
AlertDialogAction,
|
|
130
|
+
AlertDialogCancel,
|
|
131
|
+
AlertDialogContent,
|
|
132
|
+
AlertDialogDescription,
|
|
133
|
+
AlertDialogFooter,
|
|
134
|
+
AlertDialogHeader,
|
|
135
|
+
AlertDialogOverlay,
|
|
136
|
+
AlertDialogPortal,
|
|
137
|
+
AlertDialogTitle,
|
|
138
|
+
AlertDialogTrigger,
|
|
139
|
+
};
|