@bearmenu/ui 0.8.53 → 0.8.54
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-4WW3HPIW.js → chunk-6Z6MBILC.js} +8 -4
- package/dist/{chunk-2ADCZXZJ.js → chunk-RXXD3UYP.js} +3 -1
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/banner.d.ts +1 -1
- package/dist/components/button.d.ts +2 -2
- package/dist/components/card.d.ts +2 -2
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.d.ts +15 -15
- package/dist/components/empty-state.d.ts +2 -2
- package/dist/components/event-detail-panel.d.ts +17 -6
- package/dist/components/event-detail-panel.js +58 -8
- package/dist/components/menu-item.d.ts +24 -3
- package/dist/components/menu-item.js +1 -1
- package/dist/components/multi-select-combobox.js +1 -1
- package/dist/components/popover.js +1 -1
- package/dist/components/searchable-select.js +1 -1
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/text.d.ts +2 -2
- package/dist/components/top-ranking-entry.js +1 -1
- package/package.json +221 -24
- package/src/components/event-detail-panel.stories.tsx +16 -0
- package/src/components/event-detail-panel.test.tsx +36 -0
- package/src/components/event-detail-panel.tsx +86 -14
- package/src/components/menu-item.stories.tsx +82 -0
- package/src/components/menu-item.test.tsx +55 -1
- package/src/components/menu-item.tsx +31 -4
- package/src/components/popover.tsx +3 -1
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bearmenu/ui",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.8.54",
|
|
4
|
+
"packageManager": "pnpm@10.28.1",
|
|
5
|
+
"description": "BearMenu design system \u2014 shared UI tokens, primitives, and components",
|
|
5
6
|
"license": "UNLICENSED",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
@@ -16,20 +17,226 @@
|
|
|
16
17
|
"src"
|
|
17
18
|
],
|
|
18
19
|
"exports": {
|
|
20
|
+
"./components/accordion": "./src/components/accordion.tsx",
|
|
21
|
+
"./components/action-bar": "./src/components/action-bar.tsx",
|
|
22
|
+
"./components/activity-row": "./src/components/activity-row.tsx",
|
|
23
|
+
"./components/alert": "./src/components/alert.tsx",
|
|
24
|
+
"./components/alert-dialog": "./src/components/alert-dialog.tsx",
|
|
25
|
+
"./components/aspect-ratio": "./src/components/aspect-ratio.tsx",
|
|
26
|
+
"./components/avatar": "./src/components/avatar.tsx",
|
|
27
|
+
"./components/badge": "./src/components/badge.tsx",
|
|
28
|
+
"./components/banner": "./src/components/banner.tsx",
|
|
29
|
+
"./components/booking-row": "./src/components/booking-row.tsx",
|
|
30
|
+
"./components/booking-summary-card": "./src/components/booking-summary-card.tsx",
|
|
31
|
+
"./components/breadcrumb": "./src/components/breadcrumb.tsx",
|
|
32
|
+
"./components/button": "./src/components/button.tsx",
|
|
33
|
+
"./components/button-group": "./src/components/button-group.tsx",
|
|
34
|
+
"./components/calendar": "./src/components/calendar.tsx",
|
|
35
|
+
"./components/card": "./src/components/card.tsx",
|
|
36
|
+
"./components/carousel": "./src/components/carousel.tsx",
|
|
37
|
+
"./components/category-deck": "./src/components/category-deck.tsx",
|
|
38
|
+
"./components/checkbox": "./src/components/checkbox.tsx",
|
|
39
|
+
"./components/circular-progress": "./src/components/circular-progress.tsx",
|
|
40
|
+
"./components/coach-marks": "./src/components/coach-marks.tsx",
|
|
41
|
+
"./components/collapsible": "./src/components/collapsible.tsx",
|
|
42
|
+
"./components/combobox": "./src/components/combobox.tsx",
|
|
43
|
+
"./components/command": "./src/components/command.tsx",
|
|
44
|
+
"./components/concept-card": "./src/components/concept-card.tsx",
|
|
45
|
+
"./components/concept-compare": "./src/components/concept-compare.tsx",
|
|
46
|
+
"./components/concept-compare-rail": "./src/components/concept-compare-rail.tsx",
|
|
47
|
+
"./components/concept-convergence": "./src/components/concept-convergence.tsx",
|
|
48
|
+
"./components/convergence": "./src/components/convergence.tsx",
|
|
49
|
+
"./components/coverflow": "./src/components/coverflow.tsx",
|
|
50
|
+
"./components/currency-input": "./src/components/currency-input.tsx",
|
|
51
|
+
"./components/date-input": "./src/components/date-input.tsx",
|
|
52
|
+
"./components/day-rail-chip": "./src/components/day-rail-chip.tsx",
|
|
53
|
+
"./components/description-list": "./src/components/description-list.tsx",
|
|
54
|
+
"./components/dialog": "./src/components/dialog.tsx",
|
|
55
|
+
"./components/doorway-tiles": "./src/components/doorway-tiles.tsx",
|
|
56
|
+
"./components/drawer": "./src/components/drawer.tsx",
|
|
57
|
+
"./components/drift-rail": "./src/components/drift-rail.tsx",
|
|
58
|
+
"./components/dropdown-menu": "./src/components/dropdown-menu.tsx",
|
|
59
|
+
"./components/empty-state": "./src/components/empty-state.tsx",
|
|
60
|
+
"./components/event-card": "./src/components/event-card.tsx",
|
|
61
|
+
"./components/event-compact-row": "./src/components/event-compact-row.tsx",
|
|
62
|
+
"./components/event-day-header": "./src/components/event-day-header.tsx",
|
|
63
|
+
"./components/event-day-separator": "./src/components/event-day-separator.tsx",
|
|
64
|
+
"./components/event-detail-panel": "./src/components/event-detail-panel.tsx",
|
|
65
|
+
"./components/event-hub-hero": "./src/components/event-hub-hero.tsx",
|
|
66
|
+
"./components/event-index-row": "./src/components/event-index-row.tsx",
|
|
67
|
+
"./components/event-list-row": "./src/components/event-list-row.tsx",
|
|
68
|
+
"./components/event-list-row-wide": "./src/components/event-list-row-wide.tsx",
|
|
69
|
+
"./components/event-poster-fallback": "./src/components/event-poster-fallback.tsx",
|
|
70
|
+
"./components/event-tonight-strip": "./src/components/event-tonight-strip.tsx",
|
|
71
|
+
"./components/event-types": "./src/components/event-types.ts",
|
|
72
|
+
"./components/events-empty-state": "./src/components/events-empty-state.tsx",
|
|
73
|
+
"./components/explainer-steps": "./src/components/explainer-steps.tsx",
|
|
74
|
+
"./components/facet-group": "./src/components/facet-group.tsx",
|
|
75
|
+
"./components/featured-rail-item": "./src/components/featured-rail-item.tsx",
|
|
76
|
+
"./components/field": "./src/components/field.tsx",
|
|
77
|
+
"./components/filter-category-row": "./src/components/filter-category-row.tsx",
|
|
78
|
+
"./components/form": "./src/components/form.tsx",
|
|
79
|
+
"./components/glass-ground": "./src/components/glass-ground.tsx",
|
|
80
|
+
"./components/grade-atom": "./src/components/grade-atom.tsx",
|
|
81
|
+
"./components/haptic-switch": "./src/components/haptic-switch.tsx",
|
|
82
|
+
"./components/hold-countdown": "./src/components/hold-countdown.tsx",
|
|
83
|
+
"./components/hub-surface": "./src/components/hub-surface.tsx",
|
|
84
|
+
"./components/hue-chip": "./src/components/hue-chip.tsx",
|
|
85
|
+
"./components/icon": "./src/components/icon.tsx",
|
|
86
|
+
"./components/input": "./src/components/input.tsx",
|
|
87
|
+
"./components/input-group": "./src/components/input-group.tsx",
|
|
88
|
+
"./components/input-otp": "./src/components/input-otp.tsx",
|
|
89
|
+
"./components/item": "./src/components/item.tsx",
|
|
90
|
+
"./components/kbd": "./src/components/kbd.tsx",
|
|
91
|
+
"./components/label": "./src/components/label.tsx",
|
|
92
|
+
"./components/kitchen-dossier": "./src/components/kitchen-dossier.tsx",
|
|
93
|
+
"./components/lazy-markdown": "./src/components/lazy-markdown.tsx",
|
|
94
|
+
"./components/link-button": "./src/components/link-button.tsx",
|
|
95
|
+
"./components/markdown-renderer": "./src/components/markdown-renderer.tsx",
|
|
96
|
+
"./components/menu-dead-end": "./src/components/menu-dead-end.tsx",
|
|
97
|
+
"./components/menu-family-block": "./src/components/menu-family-block.tsx",
|
|
98
|
+
"./components/menu-family-card": "./src/components/menu-family-card.tsx",
|
|
99
|
+
"./components/menu-field": "./src/components/menu-field.tsx",
|
|
100
|
+
"./components/menu-filter-chips": "./src/components/menu-filter-chips.tsx",
|
|
101
|
+
"./components/menu-group-rows": "./src/components/menu-group-rows.tsx",
|
|
102
|
+
"./components/menu-hub-hero": "./src/components/menu-hub-hero.tsx",
|
|
103
|
+
"./components/menu-index-header": "./src/components/menu-index-header.tsx",
|
|
104
|
+
"./components/menu-index-list": "./src/components/menu-index-list.tsx",
|
|
105
|
+
"./components/menu-intent-hero": "./src/components/menu-intent-hero.tsx",
|
|
106
|
+
"./components/menu-item": "./src/components/menu-item.tsx",
|
|
107
|
+
"./components/menu-pick-row": "./src/components/menu-pick-row.tsx",
|
|
108
|
+
"./components/menu-kitchen-list": "./src/components/menu-kitchen-list.tsx",
|
|
109
|
+
"./components/menu-mocks": "./src/components/menu-mocks.ts",
|
|
110
|
+
"./components/menu-types": "./src/components/menu-types.ts",
|
|
111
|
+
"./components/motion-icon": "./src/components/motion-icon.tsx",
|
|
112
|
+
"./components/motion-icon-actions": "./src/components/motion-icon-actions.tsx",
|
|
113
|
+
"./components/motion-icon-controls": "./src/components/motion-icon-controls.tsx",
|
|
114
|
+
"./components/motion-icon-set": "./src/components/motion-icon-set.tsx",
|
|
115
|
+
"./components/multi-select-combobox": "./src/components/multi-select-combobox.tsx",
|
|
116
|
+
"./components/multi-select-menu": "./src/components/multi-select-menu.tsx",
|
|
117
|
+
"./components/native-select": "./src/components/native-select.tsx",
|
|
118
|
+
"./components/open-now-list": "./src/components/open-now-list.tsx",
|
|
119
|
+
"./components/pagination": "./src/components/pagination.tsx",
|
|
120
|
+
"./components/pan-zoom-viewport": "./src/components/pan-zoom-viewport.tsx",
|
|
121
|
+
"./components/party-counter": "./src/components/party-counter.tsx",
|
|
122
|
+
"./components/phone-frame": "./src/components/phone-frame.tsx",
|
|
123
|
+
"./components/photo-ground": "./src/components/photo-ground.tsx",
|
|
124
|
+
"./components/place-about-tab": "./src/components/place-about-tab.tsx",
|
|
125
|
+
"./components/place-card": "./src/components/place-card.tsx",
|
|
126
|
+
"./components/place-details-mobile": "./src/components/place-details-mobile.tsx",
|
|
127
|
+
"./components/place-schedule-week": "./src/components/place-schedule-week.tsx",
|
|
128
|
+
"./components/place-types": "./src/components/place-types.ts",
|
|
129
|
+
"./components/popover": "./src/components/popover.tsx",
|
|
130
|
+
"./components/poster-stack": "./src/components/poster-stack.tsx",
|
|
131
|
+
"./components/poster-tape": "./src/components/poster-tape.tsx",
|
|
132
|
+
"./components/poster-wall": "./src/components/poster-wall.tsx",
|
|
133
|
+
"./components/progress": "./src/components/progress.tsx",
|
|
134
|
+
"./components/qa-block": "./src/components/qa-block.tsx",
|
|
135
|
+
"./components/qr-panel": "./src/components/qr-panel.tsx",
|
|
136
|
+
"./components/quiet-stretch": "./src/components/quiet-stretch.tsx",
|
|
137
|
+
"./components/radio-group": "./src/components/radio-group.tsx",
|
|
138
|
+
"./components/reconcile-bar": "./src/components/reconcile-bar.tsx",
|
|
139
|
+
"./components/responsive-overlay": "./src/components/responsive-overlay.tsx",
|
|
140
|
+
"./components/rating": "./src/components/rating.tsx",
|
|
141
|
+
"./components/scroll-area": "./src/components/scroll-area.tsx",
|
|
142
|
+
"./components/scroll-fade": "./src/components/scroll-fade.tsx",
|
|
143
|
+
"./components/search-bar": "./src/components/search-bar.tsx",
|
|
144
|
+
"./components/searchable-select": "./src/components/searchable-select.tsx",
|
|
145
|
+
"./components/section-header": "./src/components/section-header.tsx",
|
|
146
|
+
"./components/segmented-nav": "./src/components/segmented-nav.tsx",
|
|
147
|
+
"./components/select": "./src/components/select.tsx",
|
|
148
|
+
"./components/selection-bar": "./src/components/selection-bar.tsx",
|
|
149
|
+
"./components/separator": "./src/components/separator.tsx",
|
|
150
|
+
"./components/sheet": "./src/components/sheet.tsx",
|
|
151
|
+
"./components/signature-chip": "./src/components/signature-chip.tsx",
|
|
152
|
+
"./components/skeleton": "./src/components/skeleton.tsx",
|
|
153
|
+
"./components/skeleton-card": "./src/components/skeleton-card.tsx",
|
|
154
|
+
"./components/slider": "./src/components/slider.tsx",
|
|
155
|
+
"./components/sliding-panels": "./src/components/sliding-panels.tsx",
|
|
156
|
+
"./components/sonner": "./src/components/sonner.tsx",
|
|
157
|
+
"./components/sort-option-list": "./src/components/sort-option-list.tsx",
|
|
158
|
+
"./components/spinner": "./src/components/spinner.tsx",
|
|
159
|
+
"./components/split-share-row": "./src/components/split-share-row.tsx",
|
|
160
|
+
"./components/stat-banner": "./src/components/stat-banner.tsx",
|
|
161
|
+
"./components/stepper": "./src/components/stepper.tsx",
|
|
162
|
+
"./components/sticky-container": "./src/components/sticky-container.tsx",
|
|
163
|
+
"./components/story": "./src/components/story.tsx",
|
|
164
|
+
"./components/switch": "./src/components/switch.tsx",
|
|
165
|
+
"./components/table": "./src/components/table.tsx",
|
|
166
|
+
"./components/table-ledger-card": "./src/components/table-ledger-card.tsx",
|
|
167
|
+
"./components/table-trigger-band": "./src/components/table-trigger-band.tsx",
|
|
168
|
+
"./components/tabs": "./src/components/tabs.tsx",
|
|
169
|
+
"./components/tag": "./src/components/tag.tsx",
|
|
170
|
+
"./components/tag-group": "./src/components/tag-group.tsx",
|
|
171
|
+
"./components/text": "./src/components/text.tsx",
|
|
172
|
+
"./components/textarea": "./src/components/textarea.tsx",
|
|
173
|
+
"./components/time-slot": "./src/components/time-slot.tsx",
|
|
174
|
+
"./components/timeline-day": "./src/components/timeline-day.tsx",
|
|
175
|
+
"./components/timeline-hour": "./src/components/timeline-hour.tsx",
|
|
176
|
+
"./components/timeline-hour-puck": "./src/components/timeline-hour-puck.tsx",
|
|
177
|
+
"./components/toggle": "./src/components/toggle.tsx",
|
|
178
|
+
"./components/toggle-group": "./src/components/toggle-group.tsx",
|
|
179
|
+
"./components/tooltip": "./src/components/tooltip.tsx",
|
|
180
|
+
"./components/top-comparison": "./src/components/top-comparison.tsx",
|
|
181
|
+
"./components/top-cross-link": "./src/components/top-cross-link.tsx",
|
|
182
|
+
"./components/top-ranking-entry": "./src/components/top-ranking-entry.tsx",
|
|
183
|
+
"./components/top-ranking-hero": "./src/components/top-ranking-hero.tsx",
|
|
184
|
+
"./components/top-ranking-legend": "./src/components/top-ranking-legend.tsx",
|
|
185
|
+
"./components/top-types": "./src/components/top-types.ts",
|
|
186
|
+
"./components/use-mobile": "./src/components/use-mobile.tsx",
|
|
187
|
+
"./components/venue-events-block": "./src/components/venue-events-block.tsx",
|
|
188
|
+
"./components/venue-grade-dimensions": "./src/components/venue-grade-dimensions.tsx",
|
|
189
|
+
"./components/venue-quotes": "./src/components/venue-quotes.tsx",
|
|
190
|
+
"./components/venue-wall": "./src/components/venue-wall.tsx",
|
|
191
|
+
"./components/weekday-chip": "./src/components/weekday-chip.tsx",
|
|
192
|
+
"./components/weekday-theme-banner": "./src/components/weekday-theme-banner.tsx",
|
|
193
|
+
"./components/weekend-panel": "./src/components/weekend-panel.tsx",
|
|
19
194
|
"./globals.css": "./src/globals.css",
|
|
20
|
-
"./
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"./hooks
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"./lib
|
|
29
|
-
|
|
30
|
-
|
|
195
|
+
"./hooks/use-drag-swipe": "./src/hooks/use-drag-swipe.ts",
|
|
196
|
+
"./hooks/use-haptic": "./src/hooks/use-haptic.ts",
|
|
197
|
+
"./hooks/use-media-query": "./src/hooks/use-media-query.ts",
|
|
198
|
+
"./hooks/use-mobile": "./src/hooks/use-mobile.ts",
|
|
199
|
+
"./hooks/use-mounted-theme": "./src/hooks/use-mounted-theme.ts",
|
|
200
|
+
"./hooks/use-sticky": "./src/hooks/use-sticky.ts",
|
|
201
|
+
"./lib/card-hover": "./src/lib/card-hover.ts",
|
|
202
|
+
"./lib/grade": "./src/lib/grade.ts",
|
|
203
|
+
"./lib/icon-depth": "./src/lib/icon-depth.ts",
|
|
204
|
+
"./lib/icon-registry": "./src/lib/icon-registry.ts",
|
|
205
|
+
"./lib/motion": "./src/lib/motion.ts",
|
|
206
|
+
"./lib/overlay-pill": "./src/lib/overlay-pill.ts",
|
|
207
|
+
"./lib/utils": "./src/lib/utils.ts",
|
|
208
|
+
"./components/filter-option-chip": "./src/components/filter-option-chip.tsx",
|
|
209
|
+
"./components/filter-option-group": "./src/components/filter-option-group.tsx",
|
|
210
|
+
"./components/filter-panel-footer": "./src/components/filter-panel-footer.tsx"
|
|
211
|
+
},
|
|
212
|
+
"publishConfig": {
|
|
213
|
+
"exports": {
|
|
214
|
+
"./globals.css": "./src/globals.css",
|
|
215
|
+
"./components/*": {
|
|
216
|
+
"types": "./dist/components/*.d.ts",
|
|
217
|
+
"import": "./dist/components/*.js"
|
|
218
|
+
},
|
|
219
|
+
"./hooks/*": {
|
|
220
|
+
"types": "./dist/hooks/*.d.ts",
|
|
221
|
+
"import": "./dist/hooks/*.js"
|
|
222
|
+
},
|
|
223
|
+
"./lib/*": {
|
|
224
|
+
"types": "./dist/lib/*.d.ts",
|
|
225
|
+
"import": "./dist/lib/*.js"
|
|
226
|
+
}
|
|
31
227
|
}
|
|
32
228
|
},
|
|
229
|
+
"scripts": {
|
|
230
|
+
"storybook": "storybook dev -p 6006",
|
|
231
|
+
"build-storybook": "storybook build",
|
|
232
|
+
"build": "tsup",
|
|
233
|
+
"dev": "tsup --watch",
|
|
234
|
+
"typecheck": "tsc --noEmit",
|
|
235
|
+
"test": "vitest run",
|
|
236
|
+
"test:watch": "vitest",
|
|
237
|
+
"clean": "rm -rf dist",
|
|
238
|
+
"prepublishOnly": "pnpm run build"
|
|
239
|
+
},
|
|
33
240
|
"peerDependencies": {
|
|
34
241
|
"@hookform/resolvers": "^3.0.0 || ^5.0.0",
|
|
35
242
|
"@radix-ui/react-accordion": "*",
|
|
@@ -138,15 +345,5 @@
|
|
|
138
345
|
"vaul": "^1.1.2",
|
|
139
346
|
"vitest": "^4.1.5",
|
|
140
347
|
"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
348
|
}
|
|
152
|
-
}
|
|
349
|
+
}
|
|
@@ -283,6 +283,22 @@ export const Loading: Story = {
|
|
|
283
283
|
decorators: SideSheet.decorators,
|
|
284
284
|
};
|
|
285
285
|
|
|
286
|
+
export const EntryAndBrowseActions: Story = {
|
|
287
|
+
args: {
|
|
288
|
+
tags: [{ label: "Party", href: "/events?tags=party" }],
|
|
289
|
+
entryDetails: {
|
|
290
|
+
label: "Entry and tickets",
|
|
291
|
+
description: "Check availability with the organizer.",
|
|
292
|
+
action: (
|
|
293
|
+
<Button asChild fullWidth>
|
|
294
|
+
<a href="https://tickets.example.com">Get tickets</a>
|
|
295
|
+
</Button>
|
|
296
|
+
),
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
decorators: SideSheet.decorators,
|
|
300
|
+
};
|
|
301
|
+
|
|
286
302
|
export const Error: Story = {
|
|
287
303
|
render: () => (
|
|
288
304
|
<EventDetailPanelError
|
|
@@ -37,6 +37,42 @@ const event: EventPreview = {
|
|
|
37
37
|
const base = { event, labels, formatters, dateLine: "Tuesday · 17:30" };
|
|
38
38
|
|
|
39
39
|
describe("EventDetailPanel", () => {
|
|
40
|
+
it("opens entry information and exposes a separate booking link", () => {
|
|
41
|
+
render(
|
|
42
|
+
<EventDetailPanel
|
|
43
|
+
{...base}
|
|
44
|
+
entryDetails={{
|
|
45
|
+
label: "Entry and tickets",
|
|
46
|
+
description: "Book with the organizer.",
|
|
47
|
+
action: <a href="https://tickets.example.com">Get tickets</a>,
|
|
48
|
+
}}
|
|
49
|
+
/>,
|
|
50
|
+
);
|
|
51
|
+
expect(
|
|
52
|
+
screen.queryByRole("link", { name: "Get tickets" }),
|
|
53
|
+
).not.toBeInTheDocument();
|
|
54
|
+
fireEvent.click(screen.getByRole("button", { name: "Entry and tickets" }));
|
|
55
|
+
expect(screen.getByText("Book with the organizer.")).toBeVisible();
|
|
56
|
+
expect(screen.getByRole("link", { name: "Get tickets" })).toHaveAttribute(
|
|
57
|
+
"href",
|
|
58
|
+
"https://tickets.example.com",
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("gives browse tags destinations while keeping descriptive tags out of the tab order", () => {
|
|
63
|
+
render(
|
|
64
|
+
<EventDetailPanel
|
|
65
|
+
{...base}
|
|
66
|
+
tags={["Descriptive", { label: "Party", href: "/events?tags=party" }]}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
expect(screen.getByRole("link", { name: "Party" })).toHaveAttribute(
|
|
70
|
+
"href",
|
|
71
|
+
"/events?tags=party",
|
|
72
|
+
);
|
|
73
|
+
expect(screen.getByText("Descriptive").tagName).toBe("SPAN");
|
|
74
|
+
});
|
|
75
|
+
|
|
40
76
|
it("shows the status chip for live, soon and ended, and none when upcoming", () => {
|
|
41
77
|
const { rerender } = render(<EventDetailPanel {...base} />);
|
|
42
78
|
expect(screen.queryByText("Now")).not.toBeInTheDocument();
|
|
@@ -19,6 +19,7 @@ import { cn } from "../lib/utils";
|
|
|
19
19
|
import { GradeAtom, type GradeAtomLabels } from "./grade-atom";
|
|
20
20
|
import type { PlaceGrade } from "./place-types";
|
|
21
21
|
import { Button } from "./button";
|
|
22
|
+
import { Popover, PopoverContent, PopoverTrigger } from "./popover";
|
|
22
23
|
import { EventPosterFallback } from "./event-poster-fallback";
|
|
23
24
|
import { Separator } from "./separator";
|
|
24
25
|
import { Skeleton } from "./skeleton";
|
|
@@ -144,8 +145,15 @@ export type EventDetailPanelProps = {
|
|
|
144
145
|
/** The hype tier, rendered as given (the app's own badge) in the chip row. */
|
|
145
146
|
hype?: React.ReactNode;
|
|
146
147
|
venue?: EventDetailVenue;
|
|
147
|
-
/** Activity tags
|
|
148
|
-
tags?: string[];
|
|
148
|
+
/** Activity tags. Pass a destination to make a tag a browse action. */
|
|
149
|
+
tags?: (string | { label: string; href: string; onClick?: () => void })[];
|
|
150
|
+
/** Entry information opened by the price/free-entry control. */
|
|
151
|
+
entryDetails?: {
|
|
152
|
+
label: string;
|
|
153
|
+
description: string;
|
|
154
|
+
action?: React.ReactNode;
|
|
155
|
+
onOpenChange?: (open: boolean) => void;
|
|
156
|
+
};
|
|
149
157
|
/** Performer names, in the order the API gave them. */
|
|
150
158
|
performers?: string[];
|
|
151
159
|
/** How many performers to show before the "show all" chip. Default 5. */
|
|
@@ -197,6 +205,7 @@ export function EventDetailPanel({
|
|
|
197
205
|
hype,
|
|
198
206
|
venue,
|
|
199
207
|
tags,
|
|
208
|
+
entryDetails,
|
|
200
209
|
performers,
|
|
201
210
|
performersPreview = 5,
|
|
202
211
|
showAllPerformersLabel,
|
|
@@ -289,6 +298,8 @@ export function EventDetailPanel({
|
|
|
289
298
|
soonLabel={soonLabel}
|
|
290
299
|
hype={hype}
|
|
291
300
|
tags={tags}
|
|
301
|
+
entryDetails={entryDetails}
|
|
302
|
+
linkComponent={LinkComponent}
|
|
292
303
|
/>
|
|
293
304
|
|
|
294
305
|
{venue && (
|
|
@@ -675,7 +686,14 @@ export function EventDetailPanelSkeleton({
|
|
|
675
686
|
|
|
676
687
|
export type EventDetailChipRowProps = Pick<
|
|
677
688
|
EventDetailPanelProps,
|
|
678
|
-
|
|
689
|
+
| "event"
|
|
690
|
+
| "formatters"
|
|
691
|
+
| "status"
|
|
692
|
+
| "soonLabel"
|
|
693
|
+
| "hype"
|
|
694
|
+
| "tags"
|
|
695
|
+
| "entryDetails"
|
|
696
|
+
| "linkComponent"
|
|
679
697
|
> & {
|
|
680
698
|
/** Only the chip strings — a page rendering the row alone need not name the section headings. */
|
|
681
699
|
labels: Pick<EventDetailPanelLabels, "free" | "liveNow" | "ended">;
|
|
@@ -683,7 +701,7 @@ export type EventDetailChipRowProps = Pick<
|
|
|
683
701
|
};
|
|
684
702
|
|
|
685
703
|
/**
|
|
686
|
-
* One wrapping row
|
|
704
|
+
* One wrapping row: metadata, entry information, then activity links. Exported
|
|
687
705
|
* so the page renders the same row above its title — it is the panel's row,
|
|
688
706
|
* not a second one.
|
|
689
707
|
*/
|
|
@@ -695,11 +713,25 @@ export function EventDetailChipRow({
|
|
|
695
713
|
soonLabel,
|
|
696
714
|
hype,
|
|
697
715
|
tags,
|
|
716
|
+
entryDetails,
|
|
717
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
698
718
|
className,
|
|
699
719
|
}: EventDetailChipRowProps) {
|
|
700
720
|
const fee = resolveEventEntryFee(event);
|
|
721
|
+
const priceLabel =
|
|
722
|
+
fee.kind === "free"
|
|
723
|
+
? labels.free
|
|
724
|
+
: fee.kind === "paid"
|
|
725
|
+
? formatters.price(fee.amountRon)
|
|
726
|
+
: null;
|
|
727
|
+
const priceIcon =
|
|
728
|
+
fee.kind === "free" ? (
|
|
729
|
+
<Gift className="h-3.5 w-3.5" aria-hidden />
|
|
730
|
+
) : (
|
|
731
|
+
<Ticket className="h-3.5 w-3.5" aria-hidden />
|
|
732
|
+
);
|
|
701
733
|
return (
|
|
702
|
-
<div className={cn("flex flex-wrap gap-[7px]", className)}>
|
|
734
|
+
<div className={cn("flex flex-wrap items-center gap-[7px]", className)}>
|
|
703
735
|
{status === "live" && (
|
|
704
736
|
<span
|
|
705
737
|
className={cn(
|
|
@@ -732,7 +764,33 @@ export function EventDetailChipRow({
|
|
|
732
764
|
{labels.ended}
|
|
733
765
|
</span>
|
|
734
766
|
)}
|
|
735
|
-
{
|
|
767
|
+
{priceLabel && entryDetails ? (
|
|
768
|
+
<Popover onOpenChange={entryDetails.onOpenChange}>
|
|
769
|
+
<PopoverTrigger asChild>
|
|
770
|
+
<Button
|
|
771
|
+
variant="outline"
|
|
772
|
+
size="sm"
|
|
773
|
+
className="min-h-11 rounded-full"
|
|
774
|
+
aria-label={entryDetails.label}
|
|
775
|
+
>
|
|
776
|
+
{priceIcon}
|
|
777
|
+
{priceLabel}
|
|
778
|
+
<ChevronRight className="h-3.5 w-3.5" aria-hidden />
|
|
779
|
+
</Button>
|
|
780
|
+
</PopoverTrigger>
|
|
781
|
+
<PopoverContent
|
|
782
|
+
align="start"
|
|
783
|
+
aria-label={entryDetails.label}
|
|
784
|
+
className="flex max-w-[calc(100vw-2rem)] flex-col gap-3"
|
|
785
|
+
>
|
|
786
|
+
<p className="text-sm font-semibold">{priceLabel}</p>
|
|
787
|
+
<p className="text-sm text-muted-foreground">
|
|
788
|
+
{entryDetails.description}
|
|
789
|
+
</p>
|
|
790
|
+
{entryDetails.action}
|
|
791
|
+
</PopoverContent>
|
|
792
|
+
</Popover>
|
|
793
|
+
) : fee.kind === "free" ? (
|
|
736
794
|
<span
|
|
737
795
|
className={cn(
|
|
738
796
|
CHIP,
|
|
@@ -742,19 +800,33 @@ export function EventDetailChipRow({
|
|
|
742
800
|
<Gift className="h-3 w-3" aria-hidden />
|
|
743
801
|
{labels.free}
|
|
744
802
|
</span>
|
|
745
|
-
)
|
|
746
|
-
{fee.kind === "paid" && (
|
|
803
|
+
) : fee.kind === "paid" ? (
|
|
747
804
|
<span className={cn(CHIP, "text-foreground")}>
|
|
748
805
|
<Ticket className="h-3 w-3" aria-hidden />
|
|
749
806
|
{formatters.price(fee.amountRon)}
|
|
750
807
|
</span>
|
|
751
|
-
)}
|
|
808
|
+
) : null}
|
|
752
809
|
{hype}
|
|
753
|
-
{tags?.map((tag) =>
|
|
754
|
-
|
|
755
|
-
{tag}
|
|
756
|
-
|
|
757
|
-
|
|
810
|
+
{tags?.map((tag) =>
|
|
811
|
+
typeof tag === "string" ? (
|
|
812
|
+
<span key={tag} className="text-xs text-muted-foreground">
|
|
813
|
+
{tag}
|
|
814
|
+
</span>
|
|
815
|
+
) : (
|
|
816
|
+
<Button
|
|
817
|
+
key={tag.href}
|
|
818
|
+
asChild
|
|
819
|
+
variant="outline"
|
|
820
|
+
size="sm"
|
|
821
|
+
className="min-h-11 rounded-full"
|
|
822
|
+
>
|
|
823
|
+
<LinkComponent href={tag.href} onClick={tag.onClick}>
|
|
824
|
+
{tag.label}
|
|
825
|
+
<ChevronRight className="h-3.5 w-3.5" aria-hidden />
|
|
826
|
+
</LinkComponent>
|
|
827
|
+
</Button>
|
|
828
|
+
),
|
|
829
|
+
)}
|
|
758
830
|
</div>
|
|
759
831
|
);
|
|
760
832
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { MenuItem, type MenuItemLabels } from "./menu-item";
|
|
3
|
+
import type { MenuItemPreview } from "./menu-types";
|
|
4
|
+
import { mockGradients } from "./menu-mocks";
|
|
5
|
+
|
|
6
|
+
const tagliatelle: MenuItemPreview = {
|
|
7
|
+
id: "i1",
|
|
8
|
+
slug: "truffle-tagliatelle",
|
|
9
|
+
name: "Truffle Tagliatelle",
|
|
10
|
+
kind: "dish",
|
|
11
|
+
category_slug: "pasta",
|
|
12
|
+
description: "Fresh pasta, black truffle, parmesan.",
|
|
13
|
+
mentions: 17,
|
|
14
|
+
sizes_and_prices: [{ size: 320, price: 62 }],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const mentionLabels: MenuItemLabels = {
|
|
18
|
+
currency: "lei",
|
|
19
|
+
mentions: (count) => `Mentioned in ${count} reviews`,
|
|
20
|
+
signature: "SIGNATURE",
|
|
21
|
+
mentioned: "MENTIONED",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// The label and the kind move together: "Praised in" with the thumbs-up,
|
|
25
|
+
// "Mentioned in" with the quote glyph.
|
|
26
|
+
const praiseLabels: MenuItemLabels = {
|
|
27
|
+
...mentionLabels,
|
|
28
|
+
mentions: (count) => `Praised in ${count} reviews`,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const meta: Meta<typeof MenuItem> = {
|
|
32
|
+
title: "Menu/MenuItem",
|
|
33
|
+
component: MenuItem,
|
|
34
|
+
tags: ["autodocs"],
|
|
35
|
+
decorators: [
|
|
36
|
+
(Story) => (
|
|
37
|
+
<div className="w-[390px] bg-background">
|
|
38
|
+
<Story />
|
|
39
|
+
</div>
|
|
40
|
+
),
|
|
41
|
+
],
|
|
42
|
+
args: {
|
|
43
|
+
item: tagliatelle,
|
|
44
|
+
labels: mentionLabels,
|
|
45
|
+
formatPrice: String,
|
|
46
|
+
markGradient: mockGradients.pasta,
|
|
47
|
+
onOpen: () => {},
|
|
48
|
+
onMentions: () => {},
|
|
49
|
+
},
|
|
50
|
+
argTypes: {
|
|
51
|
+
countKind: { control: "inline-radio", options: ["mentions", "praise"] },
|
|
52
|
+
},
|
|
53
|
+
parameters: {
|
|
54
|
+
docs: {
|
|
55
|
+
description: {
|
|
56
|
+
component:
|
|
57
|
+
"THE menu row. The count at the right edge is drawn with one of two glyphs, set by `countKind`. `\"mentions\"` (the default) counts every review that named the item, complaints included, and takes the quote glyph. `\"praise\"` counts only the reviews that praised it and takes a thumbs-up. A thumbs-up never sits on a count that includes complaints, so pass `\"praise\"` only when the number is praise.",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default meta;
|
|
64
|
+
type Story = StoryObj<typeof MenuItem>;
|
|
65
|
+
|
|
66
|
+
/** The default: every mention, complaints included. Quote glyph. */
|
|
67
|
+
export const Mentions: Story = {};
|
|
68
|
+
|
|
69
|
+
/** A praise-only count. Thumbs-up, in the same slot as the quote glyph. */
|
|
70
|
+
export const Praise: Story = {
|
|
71
|
+
args: { countKind: "praise", labels: praiseLabels },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** The two kinds stacked, so the glyphs can be compared in place. */
|
|
75
|
+
export const CountKinds: Story = {
|
|
76
|
+
render: (args) => (
|
|
77
|
+
<div className="divide-y divide-border">
|
|
78
|
+
<MenuItem {...args} countKind="mentions" labels={mentionLabels} />
|
|
79
|
+
<MenuItem {...args} countKind="praise" labels={praiseLabels} />
|
|
80
|
+
</div>
|
|
81
|
+
),
|
|
82
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
2
|
import { render, screen } from "@testing-library/react";
|
|
3
|
-
import { MenuItem, type MenuItemLabels } from "./menu-item";
|
|
3
|
+
import { MentionSignal, MenuItem, type MenuItemLabels } from "./menu-item";
|
|
4
4
|
import type { MenuItemPreview } from "./menu-types";
|
|
5
5
|
|
|
6
6
|
const wine: MenuItemPreview = {
|
|
@@ -55,3 +55,57 @@ describe("MenuItem external rating", () => {
|
|
|
55
55
|
expect(screen.getByRole("img", { name: "Rated 3.9 on Vivino" })).toBeTruthy();
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
|
+
|
|
59
|
+
describe("MenuItem count glyph", () => {
|
|
60
|
+
const praiseLabels: MenuItemLabels = {
|
|
61
|
+
...labels,
|
|
62
|
+
mentions: (count) => `Praised in ${count} reviews`,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
it("draws the quote glyph when no kind is passed, so existing callers look the same", () => {
|
|
66
|
+
render(<MenuItem item={wine} labels={labels} formatPrice={String} />);
|
|
67
|
+
const count = screen.getByLabelText("Mentioned in 4 reviews");
|
|
68
|
+
const glyph = count.querySelector("svg");
|
|
69
|
+
expect(glyph).toHaveClass("lucide-message-square-quote", "h-3.5", "w-3.5", "opacity-60");
|
|
70
|
+
expect(count.querySelector(".lucide-thumbs-up")).toBeNull();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("draws the quote glyph for raw mentions, which can include complaints", () => {
|
|
74
|
+
render(<MenuItem item={wine} labels={labels} formatPrice={String} countKind="mentions" />);
|
|
75
|
+
const count = screen.getByLabelText("Mentioned in 4 reviews");
|
|
76
|
+
expect(count.querySelector("svg")).toHaveClass("lucide-message-square-quote");
|
|
77
|
+
expect(count.querySelector(".lucide-thumbs-up")).toBeNull();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("draws a thumbs-up in the quote glyph's slot when the count is praise", () => {
|
|
81
|
+
render(
|
|
82
|
+
<MenuItem
|
|
83
|
+
item={wine}
|
|
84
|
+
labels={praiseLabels}
|
|
85
|
+
formatPrice={String}
|
|
86
|
+
countKind="praise"
|
|
87
|
+
onMentions={() => {}}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
const count = screen.getByRole("button", { name: "Praised in 4 reviews" });
|
|
91
|
+
const glyph = count.querySelector("svg");
|
|
92
|
+
expect(glyph).toHaveClass("lucide-thumbs-up", "h-3.5", "w-3.5", "opacity-60");
|
|
93
|
+
expect(glyph).toHaveAttribute("aria-hidden", "true");
|
|
94
|
+
expect(count.querySelector(".lucide-message-square-quote")).toBeNull();
|
|
95
|
+
expect(count.textContent).toBe("4");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("MentionSignal count glyph", () => {
|
|
100
|
+
it("draws the quote glyph by default and the thumbs-up only for praise", () => {
|
|
101
|
+
const { rerender } = render(<MentionSignal label="Mentioned in 9 reviews" count={9} />);
|
|
102
|
+
expect(screen.getByLabelText("Mentioned in 9 reviews").querySelector("svg")).toHaveClass(
|
|
103
|
+
"lucide-message-square-quote"
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
rerender(<MentionSignal label="Praised in 9 reviews" count={9} countKind="praise" />);
|
|
107
|
+
expect(screen.getByLabelText("Praised in 9 reviews").querySelector("svg")).toHaveClass(
|
|
108
|
+
"lucide-thumbs-up"
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
});
|