@bearmenu/ui 0.8.25 → 0.8.26
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/dist/{chunk-VEGFQSZA.js → chunk-F3XK34DV.js} +8 -0
- package/dist/chunk-L3CDXVEP.js +56 -0
- package/dist/{chunk-CZK2MRS2.js → chunk-VEFKZIK6.js} +13 -3
- package/dist/components/banner.d.ts +36 -0
- package/dist/components/banner.js +29 -0
- package/dist/components/checkbox.d.ts +9 -2
- package/dist/components/checkbox.js +1 -1
- package/dist/components/currency-input.js +1 -1
- package/dist/components/date-input.js +1 -1
- package/dist/components/event-detail-panel.js +1 -1
- package/dist/components/event-index-row.js +1 -1
- package/dist/components/event-list-row-wide.js +1 -1
- package/dist/components/event-list-row.js +1 -1
- package/dist/components/field.d.ts +15 -1
- package/dist/components/field.js +62 -21
- package/dist/components/input.d.ts +1 -0
- package/dist/components/input.js +1 -1
- package/dist/components/kitchen-dossier.js +1 -1
- package/dist/components/multi-select-combobox.js +1 -1
- package/dist/components/multi-select-menu.js +1 -1
- package/dist/components/place-card.js +1 -1
- package/dist/components/textarea.d.ts +10 -3
- package/dist/components/textarea.js +21 -6
- package/dist/components/toggle-group.js +5 -5
- package/dist/components/toggle.js +1 -1
- package/package.json +207 -23
- package/src/components/banner.stories.tsx +36 -0
- package/src/components/banner.tsx +58 -0
- package/src/components/booking-details-primitives.test.tsx +75 -0
- package/src/components/checkbox.tsx +49 -23
- package/src/components/field.stories.tsx +51 -1
- package/src/components/field.tsx +68 -9
- package/src/components/input.tsx +13 -2
- package/src/components/textarea.tsx +25 -7
- package/src/components/toggle-group.tsx +4 -4
- package/src/components/toggle.tsx +8 -0
- package/dist/chunk-NMEVJT5Q.js +0 -26
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bearmenu/ui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.26",
|
|
4
|
+
"packageManager": "pnpm@10.28.1",
|
|
4
5
|
"description": "BearMenu design system — shared UI tokens, primitives, and components",
|
|
5
6
|
"license": "UNLICENSED",
|
|
6
7
|
"repository": {
|
|
@@ -16,20 +17,213 @@
|
|
|
16
17
|
"src"
|
|
17
18
|
],
|
|
18
19
|
"exports": {
|
|
20
|
+
"./components/accordion": "./src/components/accordion.tsx",
|
|
21
|
+
"./components/alert": "./src/components/alert.tsx",
|
|
22
|
+
"./components/alert-dialog": "./src/components/alert-dialog.tsx",
|
|
23
|
+
"./components/aspect-ratio": "./src/components/aspect-ratio.tsx",
|
|
24
|
+
"./components/avatar": "./src/components/avatar.tsx",
|
|
25
|
+
"./components/badge": "./src/components/badge.tsx",
|
|
26
|
+
"./components/banner": "./src/components/banner.tsx",
|
|
27
|
+
"./components/booking-row": "./src/components/booking-row.tsx",
|
|
28
|
+
"./components/booking-summary-card": "./src/components/booking-summary-card.tsx",
|
|
29
|
+
"./components/breadcrumb": "./src/components/breadcrumb.tsx",
|
|
30
|
+
"./components/button": "./src/components/button.tsx",
|
|
31
|
+
"./components/button-group": "./src/components/button-group.tsx",
|
|
32
|
+
"./components/calendar": "./src/components/calendar.tsx",
|
|
33
|
+
"./components/card": "./src/components/card.tsx",
|
|
34
|
+
"./components/carousel": "./src/components/carousel.tsx",
|
|
35
|
+
"./components/category-deck": "./src/components/category-deck.tsx",
|
|
36
|
+
"./components/checkbox": "./src/components/checkbox.tsx",
|
|
37
|
+
"./components/circular-progress": "./src/components/circular-progress.tsx",
|
|
38
|
+
"./components/coach-marks": "./src/components/coach-marks.tsx",
|
|
39
|
+
"./components/collapsible": "./src/components/collapsible.tsx",
|
|
40
|
+
"./components/combobox": "./src/components/combobox.tsx",
|
|
41
|
+
"./components/command": "./src/components/command.tsx",
|
|
42
|
+
"./components/concept-card": "./src/components/concept-card.tsx",
|
|
43
|
+
"./components/concept-compare": "./src/components/concept-compare.tsx",
|
|
44
|
+
"./components/concept-compare-rail": "./src/components/concept-compare-rail.tsx",
|
|
45
|
+
"./components/concept-convergence": "./src/components/concept-convergence.tsx",
|
|
46
|
+
"./components/convergence": "./src/components/convergence.tsx",
|
|
47
|
+
"./components/coverflow": "./src/components/coverflow.tsx",
|
|
48
|
+
"./components/currency-input": "./src/components/currency-input.tsx",
|
|
49
|
+
"./components/date-input": "./src/components/date-input.tsx",
|
|
50
|
+
"./components/day-rail-chip": "./src/components/day-rail-chip.tsx",
|
|
51
|
+
"./components/description-list": "./src/components/description-list.tsx",
|
|
52
|
+
"./components/dialog": "./src/components/dialog.tsx",
|
|
53
|
+
"./components/doorway-tiles": "./src/components/doorway-tiles.tsx",
|
|
54
|
+
"./components/drawer": "./src/components/drawer.tsx",
|
|
55
|
+
"./components/drift-rail": "./src/components/drift-rail.tsx",
|
|
56
|
+
"./components/dropdown-menu": "./src/components/dropdown-menu.tsx",
|
|
57
|
+
"./components/empty-state": "./src/components/empty-state.tsx",
|
|
58
|
+
"./components/event-card": "./src/components/event-card.tsx",
|
|
59
|
+
"./components/event-compact-row": "./src/components/event-compact-row.tsx",
|
|
60
|
+
"./components/event-day-header": "./src/components/event-day-header.tsx",
|
|
61
|
+
"./components/event-day-separator": "./src/components/event-day-separator.tsx",
|
|
62
|
+
"./components/event-detail-panel": "./src/components/event-detail-panel.tsx",
|
|
63
|
+
"./components/event-hub-hero": "./src/components/event-hub-hero.tsx",
|
|
64
|
+
"./components/event-index-row": "./src/components/event-index-row.tsx",
|
|
65
|
+
"./components/event-list-row": "./src/components/event-list-row.tsx",
|
|
66
|
+
"./components/event-list-row-wide": "./src/components/event-list-row-wide.tsx",
|
|
67
|
+
"./components/event-poster-fallback": "./src/components/event-poster-fallback.tsx",
|
|
68
|
+
"./components/event-tonight-strip": "./src/components/event-tonight-strip.tsx",
|
|
69
|
+
"./components/event-types": "./src/components/event-types.ts",
|
|
70
|
+
"./components/events-empty-state": "./src/components/events-empty-state.tsx",
|
|
71
|
+
"./components/explainer-steps": "./src/components/explainer-steps.tsx",
|
|
72
|
+
"./components/facet-group": "./src/components/facet-group.tsx",
|
|
73
|
+
"./components/featured-rail-item": "./src/components/featured-rail-item.tsx",
|
|
74
|
+
"./components/field": "./src/components/field.tsx",
|
|
75
|
+
"./components/filter-category-row": "./src/components/filter-category-row.tsx",
|
|
76
|
+
"./components/form": "./src/components/form.tsx",
|
|
77
|
+
"./components/glass-ground": "./src/components/glass-ground.tsx",
|
|
78
|
+
"./components/grade-atom": "./src/components/grade-atom.tsx",
|
|
79
|
+
"./components/haptic-switch": "./src/components/haptic-switch.tsx",
|
|
80
|
+
"./components/hold-countdown": "./src/components/hold-countdown.tsx",
|
|
81
|
+
"./components/hub-surface": "./src/components/hub-surface.tsx",
|
|
82
|
+
"./components/hue-chip": "./src/components/hue-chip.tsx",
|
|
83
|
+
"./components/icon": "./src/components/icon.tsx",
|
|
84
|
+
"./components/input": "./src/components/input.tsx",
|
|
85
|
+
"./components/input-group": "./src/components/input-group.tsx",
|
|
86
|
+
"./components/input-otp": "./src/components/input-otp.tsx",
|
|
87
|
+
"./components/item": "./src/components/item.tsx",
|
|
88
|
+
"./components/kbd": "./src/components/kbd.tsx",
|
|
89
|
+
"./components/label": "./src/components/label.tsx",
|
|
90
|
+
"./components/kitchen-dossier": "./src/components/kitchen-dossier.tsx",
|
|
91
|
+
"./components/lazy-markdown": "./src/components/lazy-markdown.tsx",
|
|
92
|
+
"./components/link-button": "./src/components/link-button.tsx",
|
|
93
|
+
"./components/markdown-renderer": "./src/components/markdown-renderer.tsx",
|
|
94
|
+
"./components/menu-dead-end": "./src/components/menu-dead-end.tsx",
|
|
95
|
+
"./components/menu-family-block": "./src/components/menu-family-block.tsx",
|
|
96
|
+
"./components/menu-family-card": "./src/components/menu-family-card.tsx",
|
|
97
|
+
"./components/menu-field": "./src/components/menu-field.tsx",
|
|
98
|
+
"./components/menu-filter-chips": "./src/components/menu-filter-chips.tsx",
|
|
99
|
+
"./components/menu-group-rows": "./src/components/menu-group-rows.tsx",
|
|
100
|
+
"./components/menu-hub-hero": "./src/components/menu-hub-hero.tsx",
|
|
101
|
+
"./components/menu-index-header": "./src/components/menu-index-header.tsx",
|
|
102
|
+
"./components/menu-index-list": "./src/components/menu-index-list.tsx",
|
|
103
|
+
"./components/menu-intent-hero": "./src/components/menu-intent-hero.tsx",
|
|
104
|
+
"./components/menu-item": "./src/components/menu-item.tsx",
|
|
105
|
+
"./components/menu-kitchen-list": "./src/components/menu-kitchen-list.tsx",
|
|
106
|
+
"./components/menu-mocks": "./src/components/menu-mocks.ts",
|
|
107
|
+
"./components/menu-types": "./src/components/menu-types.ts",
|
|
108
|
+
"./components/motion-icon": "./src/components/motion-icon.tsx",
|
|
109
|
+
"./components/motion-icon-actions": "./src/components/motion-icon-actions.tsx",
|
|
110
|
+
"./components/motion-icon-controls": "./src/components/motion-icon-controls.tsx",
|
|
111
|
+
"./components/motion-icon-set": "./src/components/motion-icon-set.tsx",
|
|
112
|
+
"./components/multi-select-combobox": "./src/components/multi-select-combobox.tsx",
|
|
113
|
+
"./components/multi-select-menu": "./src/components/multi-select-menu.tsx",
|
|
114
|
+
"./components/native-select": "./src/components/native-select.tsx",
|
|
115
|
+
"./components/open-now-list": "./src/components/open-now-list.tsx",
|
|
116
|
+
"./components/pagination": "./src/components/pagination.tsx",
|
|
117
|
+
"./components/party-counter": "./src/components/party-counter.tsx",
|
|
118
|
+
"./components/phone-frame": "./src/components/phone-frame.tsx",
|
|
119
|
+
"./components/photo-ground": "./src/components/photo-ground.tsx",
|
|
120
|
+
"./components/place-about-tab": "./src/components/place-about-tab.tsx",
|
|
121
|
+
"./components/place-card": "./src/components/place-card.tsx",
|
|
122
|
+
"./components/place-details-mobile": "./src/components/place-details-mobile.tsx",
|
|
123
|
+
"./components/place-schedule-week": "./src/components/place-schedule-week.tsx",
|
|
124
|
+
"./components/place-types": "./src/components/place-types.ts",
|
|
125
|
+
"./components/popover": "./src/components/popover.tsx",
|
|
126
|
+
"./components/poster-stack": "./src/components/poster-stack.tsx",
|
|
127
|
+
"./components/poster-tape": "./src/components/poster-tape.tsx",
|
|
128
|
+
"./components/poster-wall": "./src/components/poster-wall.tsx",
|
|
129
|
+
"./components/progress": "./src/components/progress.tsx",
|
|
130
|
+
"./components/qa-block": "./src/components/qa-block.tsx",
|
|
131
|
+
"./components/quiet-stretch": "./src/components/quiet-stretch.tsx",
|
|
132
|
+
"./components/radio-group": "./src/components/radio-group.tsx",
|
|
133
|
+
"./components/rating": "./src/components/rating.tsx",
|
|
134
|
+
"./components/scroll-area": "./src/components/scroll-area.tsx",
|
|
135
|
+
"./components/scroll-fade": "./src/components/scroll-fade.tsx",
|
|
136
|
+
"./components/search-bar": "./src/components/search-bar.tsx",
|
|
137
|
+
"./components/searchable-select": "./src/components/searchable-select.tsx",
|
|
138
|
+
"./components/section-header": "./src/components/section-header.tsx",
|
|
139
|
+
"./components/segmented-nav": "./src/components/segmented-nav.tsx",
|
|
140
|
+
"./components/select": "./src/components/select.tsx",
|
|
141
|
+
"./components/selection-bar": "./src/components/selection-bar.tsx",
|
|
142
|
+
"./components/separator": "./src/components/separator.tsx",
|
|
143
|
+
"./components/sheet": "./src/components/sheet.tsx",
|
|
144
|
+
"./components/signature-chip": "./src/components/signature-chip.tsx",
|
|
145
|
+
"./components/skeleton": "./src/components/skeleton.tsx",
|
|
146
|
+
"./components/skeleton-card": "./src/components/skeleton-card.tsx",
|
|
147
|
+
"./components/slider": "./src/components/slider.tsx",
|
|
148
|
+
"./components/sliding-panels": "./src/components/sliding-panels.tsx",
|
|
149
|
+
"./components/sonner": "./src/components/sonner.tsx",
|
|
150
|
+
"./components/sort-option-list": "./src/components/sort-option-list.tsx",
|
|
151
|
+
"./components/spinner": "./src/components/spinner.tsx",
|
|
152
|
+
"./components/stat-banner": "./src/components/stat-banner.tsx",
|
|
153
|
+
"./components/stepper": "./src/components/stepper.tsx",
|
|
154
|
+
"./components/sticky-container": "./src/components/sticky-container.tsx",
|
|
155
|
+
"./components/story": "./src/components/story.tsx",
|
|
156
|
+
"./components/switch": "./src/components/switch.tsx",
|
|
157
|
+
"./components/table": "./src/components/table.tsx",
|
|
158
|
+
"./components/tabs": "./src/components/tabs.tsx",
|
|
159
|
+
"./components/tag": "./src/components/tag.tsx",
|
|
160
|
+
"./components/tag-group": "./src/components/tag-group.tsx",
|
|
161
|
+
"./components/text": "./src/components/text.tsx",
|
|
162
|
+
"./components/textarea": "./src/components/textarea.tsx",
|
|
163
|
+
"./components/time-slot": "./src/components/time-slot.tsx",
|
|
164
|
+
"./components/timeline-day": "./src/components/timeline-day.tsx",
|
|
165
|
+
"./components/timeline-hour": "./src/components/timeline-hour.tsx",
|
|
166
|
+
"./components/timeline-hour-puck": "./src/components/timeline-hour-puck.tsx",
|
|
167
|
+
"./components/toggle": "./src/components/toggle.tsx",
|
|
168
|
+
"./components/toggle-group": "./src/components/toggle-group.tsx",
|
|
169
|
+
"./components/tooltip": "./src/components/tooltip.tsx",
|
|
170
|
+
"./components/top-comparison": "./src/components/top-comparison.tsx",
|
|
171
|
+
"./components/top-cross-link": "./src/components/top-cross-link.tsx",
|
|
172
|
+
"./components/top-ranking-entry": "./src/components/top-ranking-entry.tsx",
|
|
173
|
+
"./components/top-ranking-hero": "./src/components/top-ranking-hero.tsx",
|
|
174
|
+
"./components/top-ranking-legend": "./src/components/top-ranking-legend.tsx",
|
|
175
|
+
"./components/top-types": "./src/components/top-types.ts",
|
|
176
|
+
"./components/use-mobile": "./src/components/use-mobile.tsx",
|
|
177
|
+
"./components/venue-events-block": "./src/components/venue-events-block.tsx",
|
|
178
|
+
"./components/venue-grade-dimensions": "./src/components/venue-grade-dimensions.tsx",
|
|
179
|
+
"./components/venue-quotes": "./src/components/venue-quotes.tsx",
|
|
180
|
+
"./components/venue-wall": "./src/components/venue-wall.tsx",
|
|
181
|
+
"./components/weekday-chip": "./src/components/weekday-chip.tsx",
|
|
182
|
+
"./components/weekday-theme-banner": "./src/components/weekday-theme-banner.tsx",
|
|
183
|
+
"./components/weekend-panel": "./src/components/weekend-panel.tsx",
|
|
19
184
|
"./globals.css": "./src/globals.css",
|
|
20
|
-
"./
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"./hooks
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"./lib
|
|
29
|
-
|
|
30
|
-
|
|
185
|
+
"./hooks/use-drag-swipe": "./src/hooks/use-drag-swipe.ts",
|
|
186
|
+
"./hooks/use-haptic": "./src/hooks/use-haptic.ts",
|
|
187
|
+
"./hooks/use-media-query": "./src/hooks/use-media-query.ts",
|
|
188
|
+
"./hooks/use-mobile": "./src/hooks/use-mobile.ts",
|
|
189
|
+
"./hooks/use-mounted-theme": "./src/hooks/use-mounted-theme.ts",
|
|
190
|
+
"./hooks/use-sticky": "./src/hooks/use-sticky.ts",
|
|
191
|
+
"./lib/card-hover": "./src/lib/card-hover.ts",
|
|
192
|
+
"./lib/grade": "./src/lib/grade.ts",
|
|
193
|
+
"./lib/icon-depth": "./src/lib/icon-depth.ts",
|
|
194
|
+
"./lib/icon-registry": "./src/lib/icon-registry.ts",
|
|
195
|
+
"./lib/motion": "./src/lib/motion.ts",
|
|
196
|
+
"./lib/overlay-pill": "./src/lib/overlay-pill.ts",
|
|
197
|
+
"./lib/utils": "./src/lib/utils.ts"
|
|
198
|
+
},
|
|
199
|
+
"publishConfig": {
|
|
200
|
+
"exports": {
|
|
201
|
+
"./globals.css": "./src/globals.css",
|
|
202
|
+
"./components/*": {
|
|
203
|
+
"types": "./dist/components/*.d.ts",
|
|
204
|
+
"import": "./dist/components/*.js"
|
|
205
|
+
},
|
|
206
|
+
"./hooks/*": {
|
|
207
|
+
"types": "./dist/hooks/*.d.ts",
|
|
208
|
+
"import": "./dist/hooks/*.js"
|
|
209
|
+
},
|
|
210
|
+
"./lib/*": {
|
|
211
|
+
"types": "./dist/lib/*.d.ts",
|
|
212
|
+
"import": "./dist/lib/*.js"
|
|
213
|
+
}
|
|
31
214
|
}
|
|
32
215
|
},
|
|
216
|
+
"scripts": {
|
|
217
|
+
"storybook": "storybook dev -p 6006",
|
|
218
|
+
"build-storybook": "storybook build",
|
|
219
|
+
"build": "tsup",
|
|
220
|
+
"dev": "tsup --watch",
|
|
221
|
+
"typecheck": "tsc --noEmit",
|
|
222
|
+
"test": "vitest run",
|
|
223
|
+
"test:watch": "vitest",
|
|
224
|
+
"clean": "rm -rf dist",
|
|
225
|
+
"prepublishOnly": "pnpm run build"
|
|
226
|
+
},
|
|
33
227
|
"peerDependencies": {
|
|
34
228
|
"@hookform/resolvers": "^3.0.0 || ^5.0.0",
|
|
35
229
|
"@radix-ui/react-accordion": "*",
|
|
@@ -138,15 +332,5 @@
|
|
|
138
332
|
"vaul": "^1.1.2",
|
|
139
333
|
"vitest": "^4.1.5",
|
|
140
334
|
"zod": "^3.25.67"
|
|
141
|
-
},
|
|
142
|
-
"scripts": {
|
|
143
|
-
"storybook": "storybook dev -p 6006",
|
|
144
|
-
"build-storybook": "storybook build",
|
|
145
|
-
"build": "tsup",
|
|
146
|
-
"dev": "tsup --watch",
|
|
147
|
-
"typecheck": "tsc --noEmit",
|
|
148
|
-
"test": "vitest run",
|
|
149
|
-
"test:watch": "vitest",
|
|
150
|
-
"clean": "rm -rf dist"
|
|
151
335
|
}
|
|
152
|
-
}
|
|
336
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { TriangleAlert } from "lucide-react";
|
|
3
|
+
import { Banner } from "./banner";
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Banner> = {
|
|
6
|
+
title: "Booking/Banner",
|
|
7
|
+
component: Banner,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
"A short inline note inside a flow: the booking flow's 'time just went', 'no table was booked', 'this venue does not take bookings'. Borderless, unlike Alert.",
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
decorators: [(Story) => <div className="w-[335px]">{Story()}</div>],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof Banner>;
|
|
22
|
+
|
|
23
|
+
/** S5: the time went while the guest was typing. */
|
|
24
|
+
export const Warning: Story = {
|
|
25
|
+
args: {
|
|
26
|
+
tone: "warning",
|
|
27
|
+
icon: <TriangleAlert aria-hidden />,
|
|
28
|
+
title: "19:30 just went",
|
|
29
|
+
children: "Someone booked it while you were filling this in. Your details are kept — pick another time.",
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** B0c: a venue that takes no bookings. */
|
|
34
|
+
export const Info: Story = {
|
|
35
|
+
args: { tone: "info", children: "This venue does not take bookings. Walk-ins only." },
|
|
36
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "../lib/utils";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Banner — a short inline note inside a flow, not a page-level alert: the
|
|
7
|
+
* booking flow's "19:30 just went" when a time is taken mid-form, "No table
|
|
8
|
+
* was booked" when a confirm fails, "This venue does not take bookings"
|
|
9
|
+
* (booking flow.html, `.bnr`).
|
|
10
|
+
*
|
|
11
|
+
* Not `Alert`: that one is a bordered card for page-level status with a
|
|
12
|
+
* pinned icon column. This is borderless, sits in the flow of a form, and
|
|
13
|
+
* reads as part of the screen — a soft ground, an optional 16px icon, an
|
|
14
|
+
* optional bold title over the body.
|
|
15
|
+
*
|
|
16
|
+
* Measured from the design: 12px radius, 12px × 14px inset, 10px between
|
|
17
|
+
* icon and text; the title at 13px bold, the body at 12.5px (90% in the
|
|
18
|
+
* warning tone), both at 1.5 leading.
|
|
19
|
+
* - `info`: the soft surface and the secondary ink.
|
|
20
|
+
* - `warning`: a 16% amber wash with the design's amber ink — darker than
|
|
21
|
+
* the solid `--warning`, which falls short of AA on its own wash at this
|
|
22
|
+
* size; dark mode uses the lifted token.
|
|
23
|
+
*/
|
|
24
|
+
const bannerVariants = cva(
|
|
25
|
+
"flex gap-2.5 rounded-xl px-3.5 py-3 text-[12.5px] leading-normal [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
tone: {
|
|
29
|
+
info: "bg-surface-2 text-ink-secondary",
|
|
30
|
+
warning:
|
|
31
|
+
"bg-[oklch(0.62_0.15_70/0.16)] text-[oklch(0.42_0.1_70)] dark:bg-warning/15 dark:text-warning",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
defaultVariants: { tone: "info" },
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export type BannerProps = Omit<React.HTMLAttributes<HTMLDivElement>, "title"> &
|
|
39
|
+
VariantProps<typeof bannerVariants> & {
|
|
40
|
+
/** A lucide icon, drawn at 16px beside the text. */
|
|
41
|
+
icon?: React.ReactNode;
|
|
42
|
+
/** The bold first line — "19:30 just went". */
|
|
43
|
+
title?: React.ReactNode;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
function Banner({ tone, icon, title, className, children, ...props }: BannerProps) {
|
|
47
|
+
return (
|
|
48
|
+
<div data-slot="banner" className={cn(bannerVariants({ tone }), className)} {...props}>
|
|
49
|
+
{icon}
|
|
50
|
+
<div className="min-w-0">
|
|
51
|
+
{title ? <b className="block text-[13px] font-bold">{title}</b> : null}
|
|
52
|
+
<div className={cn(tone === "warning" && title ? "opacity-90" : undefined)}>{children}</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { Banner, bannerVariants };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { TriangleAlert } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
import { Banner } from "./banner";
|
|
6
|
+
import { Checkbox } from "./checkbox";
|
|
7
|
+
import { Field, FieldControl, FieldError, FieldLabel, FieldOptional } from "./field";
|
|
8
|
+
import { Input } from "./input";
|
|
9
|
+
import { Textarea } from "./textarea";
|
|
10
|
+
import { toggleVariants } from "./toggle";
|
|
11
|
+
|
|
12
|
+
describe("toggleVariants chip and tab", () => {
|
|
13
|
+
it("keeps the chip's own sizing when called bare, with the default size", () => {
|
|
14
|
+
const classes = cn(toggleVariants({ variant: "chip" })).split(" ");
|
|
15
|
+
expect(classes).toContain("h-auto");
|
|
16
|
+
expect(classes).toContain("px-[13px]");
|
|
17
|
+
expect(classes).not.toContain("h-9");
|
|
18
|
+
expect(classes).not.toContain("px-2");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("keeps the tab's own sizing too", () => {
|
|
22
|
+
const classes = cn(toggleVariants({ variant: "tab", size: "lg" })).split(" ");
|
|
23
|
+
expect(classes).toContain("h-auto");
|
|
24
|
+
expect(classes).toContain("px-3");
|
|
25
|
+
expect(classes).not.toContain("h-10");
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe("booking field", () => {
|
|
30
|
+
it("labels its control, marks it invalid and describes it by the error", () => {
|
|
31
|
+
render(
|
|
32
|
+
<Field appearance="booking" invalid>
|
|
33
|
+
<FieldLabel>
|
|
34
|
+
Phone <FieldOptional>(optional)</FieldOptional>
|
|
35
|
+
</FieldLabel>
|
|
36
|
+
<FieldControl>
|
|
37
|
+
<Input appearance="booking" defaultValue="0712 34" />
|
|
38
|
+
</FieldControl>
|
|
39
|
+
<FieldError>That number looks too short.</FieldError>
|
|
40
|
+
</Field>
|
|
41
|
+
);
|
|
42
|
+
const input = screen.getByLabelText(/Phone/);
|
|
43
|
+
expect(input).toHaveAttribute("aria-invalid", "true");
|
|
44
|
+
expect(input).toHaveAccessibleDescription(/That number looks too short\./);
|
|
45
|
+
expect(input.className).toContain("h-[46px]");
|
|
46
|
+
// The booking label stays muted when the field is invalid.
|
|
47
|
+
expect(screen.getByText(/Phone/).className).not.toContain("text-destructive");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("gives the note field and the opt-in their booking shapes", () => {
|
|
51
|
+
render(
|
|
52
|
+
<>
|
|
53
|
+
<Textarea appearance="booking" aria-label="Anything we should know" />
|
|
54
|
+
<Checkbox appearance="booking" aria-label="Save these details" defaultChecked />
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
expect(screen.getByRole("textbox").className).toContain("min-h-[66px]");
|
|
58
|
+
const box = screen.getByRole("checkbox");
|
|
59
|
+
expect(box).toHaveAttribute("data-state", "checked");
|
|
60
|
+
expect(box.className).toContain("size-[17px]");
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("Banner", () => {
|
|
65
|
+
it("reads as its title over its body, with the icon beside", () => {
|
|
66
|
+
render(
|
|
67
|
+
<Banner tone="warning" icon={<TriangleAlert aria-hidden />} title="19:30 just went">
|
|
68
|
+
Someone booked it while you were filling this in.
|
|
69
|
+
</Banner>
|
|
70
|
+
);
|
|
71
|
+
const banner = screen.getByText("19:30 just went").closest("[data-slot=banner]");
|
|
72
|
+
expect(banner).toHaveTextContent("19:30 just wentSomeone booked it");
|
|
73
|
+
expect(banner?.querySelector("svg")).not.toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -2,31 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
6
|
import { Check, Minus } from "lucide-react";
|
|
6
7
|
import { cn } from "../lib/utils";
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
9
|
+
const checkboxVariants = cva(
|
|
10
|
+
"peer size-5 shrink-0 rounded-md border border-border bg-background transition-all duration-150 ease-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary data-[state=indeterminate]:bg-primary data-[state=indeterminate]:text-primary-foreground data-[state=indeterminate]:border-primary hover:border-primary/50 aria-invalid:border-destructive aria-invalid:focus-visible:ring-destructive/40",
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
/**
|
|
14
|
+
* The booking flow's opt-in ("Save these details for next time",
|
|
15
|
+
* booking flow.html): a 17px box with a 5px radius, a hairline on the
|
|
16
|
+
* soft ground when off, the brand gradient with an 11px white check
|
|
17
|
+
* when on.
|
|
18
|
+
*/
|
|
19
|
+
appearance: {
|
|
20
|
+
default: "",
|
|
21
|
+
booking:
|
|
22
|
+
"size-[17px] rounded-[5px] bg-surface-2 data-[state=checked]:border-transparent data-[state=checked]:bg-[image:var(--gradient-primary)] data-[state=checked]:text-white [&_svg]:size-[11px]",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: { appearance: "default" },
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
type CheckboxProps = React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> &
|
|
30
|
+
VariantProps<typeof checkboxVariants>;
|
|
31
|
+
|
|
32
|
+
const Checkbox = React.forwardRef<React.ElementRef<typeof CheckboxPrimitive.Root>, CheckboxProps>(
|
|
33
|
+
({ className, appearance, ...props }, ref) => (
|
|
34
|
+
<CheckboxPrimitive.Root
|
|
35
|
+
ref={ref}
|
|
36
|
+
data-slot="checkbox"
|
|
37
|
+
className={cn(checkboxVariants({ appearance }), className)}
|
|
38
|
+
{...props}
|
|
39
|
+
>
|
|
40
|
+
<CheckboxPrimitive.Indicator className={cn("flex items-center justify-center text-current")}>
|
|
41
|
+
{props.checked === "indeterminate" ? (
|
|
42
|
+
<Minus
|
|
43
|
+
className="size-3.5 animate-in fade-in-0 zoom-in-50 duration-200 ease-out"
|
|
44
|
+
strokeWidth={3}
|
|
45
|
+
/>
|
|
46
|
+
) : (
|
|
47
|
+
<Check
|
|
48
|
+
className="size-3.5 animate-in fade-in-0 zoom-in-50 duration-200 ease-out"
|
|
49
|
+
strokeWidth={3}
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
</CheckboxPrimitive.Indicator>
|
|
53
|
+
</CheckboxPrimitive.Root>
|
|
54
|
+
)
|
|
55
|
+
);
|
|
30
56
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
|
|
31
57
|
|
|
32
|
-
export { Checkbox };
|
|
58
|
+
export { Checkbox, checkboxVariants };
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
-
import {
|
|
2
|
+
import { Checkbox } from "./checkbox";
|
|
3
|
+
import {
|
|
4
|
+
Field,
|
|
5
|
+
FieldControl,
|
|
6
|
+
FieldDescription,
|
|
7
|
+
FieldError,
|
|
8
|
+
FieldLabel,
|
|
9
|
+
FieldOptional,
|
|
10
|
+
} from "./field";
|
|
3
11
|
import { Input } from "./input";
|
|
12
|
+
import { Textarea } from "./textarea";
|
|
4
13
|
|
|
5
14
|
const meta = {
|
|
6
15
|
title: "Components/Field",
|
|
@@ -34,3 +43,44 @@ export const Invalid: Story = {
|
|
|
34
43
|
</Field>
|
|
35
44
|
),
|
|
36
45
|
};
|
|
46
|
+
|
|
47
|
+
/** The booking flow's "Your details" fields (B2, S6): caption labels, 46px fields, the opt-in. */
|
|
48
|
+
export const Booking: Story = {
|
|
49
|
+
render: () => (
|
|
50
|
+
<div className="flex w-[335px] flex-col gap-3">
|
|
51
|
+
<Field appearance="booking">
|
|
52
|
+
<FieldLabel>Name</FieldLabel>
|
|
53
|
+
<FieldControl>
|
|
54
|
+
<Input appearance="booking" defaultValue="Adrian Pop" />
|
|
55
|
+
</FieldControl>
|
|
56
|
+
</Field>
|
|
57
|
+
<Field appearance="booking" invalid>
|
|
58
|
+
<FieldLabel>Phone</FieldLabel>
|
|
59
|
+
<FieldControl>
|
|
60
|
+
<Input appearance="booking" defaultValue="0712 34" />
|
|
61
|
+
</FieldControl>
|
|
62
|
+
<FieldError>That number looks too short.</FieldError>
|
|
63
|
+
</Field>
|
|
64
|
+
<Field appearance="booking">
|
|
65
|
+
<FieldLabel>
|
|
66
|
+
Email <FieldOptional>(optional)</FieldOptional>
|
|
67
|
+
</FieldLabel>
|
|
68
|
+
<FieldControl>
|
|
69
|
+
<Input appearance="booking" type="email" placeholder="you@example.com" />
|
|
70
|
+
</FieldControl>
|
|
71
|
+
</Field>
|
|
72
|
+
<Field appearance="booking">
|
|
73
|
+
<FieldLabel>
|
|
74
|
+
Anything we should know <FieldOptional>(optional)</FieldOptional>
|
|
75
|
+
</FieldLabel>
|
|
76
|
+
<FieldControl>
|
|
77
|
+
<Textarea appearance="booking" defaultValue="Birthday — a quieter corner if possible." />
|
|
78
|
+
</FieldControl>
|
|
79
|
+
</Field>
|
|
80
|
+
<label className="flex items-center gap-[9px] text-[12.5px] text-ink-secondary">
|
|
81
|
+
<Checkbox appearance="booking" defaultChecked />
|
|
82
|
+
Save these details for next time
|
|
83
|
+
</label>
|
|
84
|
+
</div>
|
|
85
|
+
),
|
|
86
|
+
};
|