@bearmenu/ui 0.8.3 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/chunk-2WDJR6K7.js +76 -0
  2. package/dist/chunk-637L7XQ3.js +85 -0
  3. package/dist/chunk-6XMYEZVR.js +26 -0
  4. package/dist/chunk-DNSXMHYX.js +8 -0
  5. package/dist/chunk-FDITZ2VM.js +16 -0
  6. package/dist/chunk-GPH6L4DX.js +55 -0
  7. package/dist/{chunk-N2KNBPAQ.js → chunk-QXHMPJ35.js} +1 -0
  8. package/dist/chunk-TT3WFS6S.js +379 -0
  9. package/dist/chunk-UXWAOISG.js +170 -0
  10. package/dist/{chunk-UZ3PCIE5.js → chunk-XWMRABZW.js} +2 -2
  11. package/dist/{chunk-VBA45GLP.js → chunk-ZDAHKI3A.js} +12 -2
  12. package/dist/components/alert.d.ts +2 -2
  13. package/dist/components/badge.d.ts +1 -1
  14. package/dist/components/button.d.ts +1 -1
  15. package/dist/components/card.d.ts +2 -2
  16. package/dist/components/card.js +4 -127
  17. package/dist/components/category-deck.d.ts +50 -0
  18. package/dist/components/category-deck.js +128 -0
  19. package/dist/components/command.d.ts +15 -15
  20. package/dist/components/concept-card.d.ts +43 -0
  21. package/dist/components/concept-card.js +81 -0
  22. package/dist/components/coverflow.d.ts +123 -0
  23. package/dist/components/coverflow.js +300 -0
  24. package/dist/components/date-input.d.ts +1 -1
  25. package/dist/components/day-rail-chip.d.ts +82 -0
  26. package/dist/components/day-rail-chip.js +105 -0
  27. package/dist/components/drift-rail.d.ts +28 -0
  28. package/dist/components/drift-rail.js +3 -0
  29. package/dist/components/event-card.d.ts +97 -0
  30. package/dist/components/event-card.js +161 -0
  31. package/dist/components/event-compact-row.d.ts +72 -0
  32. package/dist/components/event-compact-row.js +133 -0
  33. package/dist/components/event-day-header.d.ts +65 -0
  34. package/dist/components/event-day-header.js +95 -0
  35. package/dist/components/event-day-separator.d.ts +46 -0
  36. package/dist/components/event-day-separator.js +50 -0
  37. package/dist/components/event-detail-panel.d.ts +80 -0
  38. package/dist/components/event-detail-panel.js +251 -0
  39. package/dist/components/event-hub-hero.d.ts +34 -0
  40. package/dist/components/event-hub-hero.js +57 -0
  41. package/dist/components/event-index-row.d.ts +68 -0
  42. package/dist/components/event-index-row.js +84 -0
  43. package/dist/components/event-list-row-wide.d.ts +101 -0
  44. package/dist/components/event-list-row-wide.js +218 -0
  45. package/dist/components/event-list-row.d.ts +82 -0
  46. package/dist/components/event-list-row.js +173 -0
  47. package/dist/components/event-poster-fallback.d.ts +20 -0
  48. package/dist/components/event-poster-fallback.js +3 -0
  49. package/dist/components/event-types.d.ts +118 -0
  50. package/dist/components/event-types.js +2 -0
  51. package/dist/components/events-empty-state.d.ts +44 -0
  52. package/dist/components/events-empty-state.js +73 -0
  53. package/dist/components/facet-group.d.ts +93 -0
  54. package/dist/components/facet-group.js +222 -0
  55. package/dist/components/featured-rail-item.d.ts +42 -0
  56. package/dist/components/featured-rail-item.js +88 -0
  57. package/dist/components/hour-band.d.ts +68 -0
  58. package/dist/components/hour-band.js +53 -0
  59. package/dist/components/hour-group.d.ts +67 -0
  60. package/dist/components/hour-group.js +59 -0
  61. package/dist/components/icon.d.ts +18 -0
  62. package/dist/components/icon.js +31 -0
  63. package/dist/components/item.d.ts +2 -2
  64. package/dist/components/motion-icon-controls.d.ts +26 -0
  65. package/dist/components/motion-icon-controls.js +4 -0
  66. package/dist/components/motion-icon-set.d.ts +27 -0
  67. package/dist/components/motion-icon-set.js +139 -0
  68. package/dist/components/motion-icon.d.ts +44 -0
  69. package/dist/components/motion-icon.js +3 -0
  70. package/dist/components/multi-select-combobox.js +1 -1
  71. package/dist/components/place-about-tab.js +3 -3
  72. package/dist/components/place-details-mobile.js +3 -3
  73. package/dist/components/poster-stack.d.ts +30 -0
  74. package/dist/components/poster-stack.js +104 -0
  75. package/dist/components/poster-tape.d.ts +36 -0
  76. package/dist/components/poster-tape.js +64 -0
  77. package/dist/components/poster-wall.d.ts +25 -0
  78. package/dist/components/poster-wall.js +183 -0
  79. package/dist/components/quiet-stretch.d.ts +43 -0
  80. package/dist/components/quiet-stretch.js +54 -0
  81. package/dist/components/section-header.d.ts +53 -0
  82. package/dist/components/section-header.js +63 -0
  83. package/dist/components/segmented-nav.d.ts +62 -0
  84. package/dist/components/segmented-nav.js +54 -0
  85. package/dist/components/skeleton-card.js +1 -1
  86. package/dist/components/skeleton.d.ts +17 -0
  87. package/dist/components/skeleton.js +1 -1
  88. package/dist/components/spinner.d.ts +2 -2
  89. package/dist/components/tag-group.js +3 -26
  90. package/dist/components/tag.d.ts +2 -2
  91. package/dist/components/tag.js +1 -1
  92. package/dist/components/text.d.ts +3 -3
  93. package/dist/components/timeline-band.d.ts +46 -0
  94. package/dist/components/timeline-band.js +5 -0
  95. package/dist/components/venue-events-block.d.ts +143 -0
  96. package/dist/components/venue-events-block.js +222 -0
  97. package/dist/components/weekend-panel.d.ts +44 -0
  98. package/dist/components/weekend-panel.js +231 -0
  99. package/dist/lib/icon-registry.d.ts +58 -0
  100. package/dist/lib/icon-registry.js +39 -0
  101. package/package.json +2 -1
  102. package/src/components/accordion.stories.tsx +1 -1
  103. package/src/components/card.tsx +44 -1
  104. package/src/components/category-deck.stories.tsx +96 -0
  105. package/src/components/category-deck.tsx +227 -0
  106. package/src/components/combobox.stories.tsx +1 -1
  107. package/src/components/concept-card.stories.tsx +80 -0
  108. package/src/components/concept-card.tsx +143 -0
  109. package/src/components/coverflow.stories.tsx +240 -0
  110. package/src/components/coverflow.test.tsx +193 -0
  111. package/src/components/coverflow.tsx +550 -0
  112. package/src/components/currency-input.stories.tsx +3 -3
  113. package/src/components/day-rail-chip.stories.tsx +145 -0
  114. package/src/components/day-rail-chip.tsx +201 -0
  115. package/src/components/drift-rail.stories.tsx +92 -0
  116. package/src/components/drift-rail.tsx +86 -0
  117. package/src/components/empty-state.stories.tsx +3 -3
  118. package/src/components/event-card.stories.tsx +248 -0
  119. package/src/components/event-card.test.tsx +177 -0
  120. package/src/components/event-card.tsx +321 -0
  121. package/src/components/event-compact-row.stories.tsx +221 -0
  122. package/src/components/event-compact-row.tsx +268 -0
  123. package/src/components/event-day-header.stories.tsx +124 -0
  124. package/src/components/event-day-header.tsx +180 -0
  125. package/src/components/event-day-separator.stories.tsx +70 -0
  126. package/src/components/event-day-separator.tsx +95 -0
  127. package/src/components/event-detail-panel.stories.tsx +191 -0
  128. package/src/components/event-detail-panel.tsx +360 -0
  129. package/src/components/event-hub-hero.stories.tsx +66 -0
  130. package/src/components/event-hub-hero.tsx +110 -0
  131. package/src/components/event-index-row.stories.tsx +116 -0
  132. package/src/components/event-index-row.tsx +181 -0
  133. package/src/components/event-list-row-wide.stories.tsx +204 -0
  134. package/src/components/event-list-row-wide.tsx +386 -0
  135. package/src/components/event-list-row.stories.tsx +159 -0
  136. package/src/components/event-list-row.tsx +335 -0
  137. package/src/components/event-poster-fallback.stories.tsx +52 -0
  138. package/src/components/event-poster-fallback.tsx +31 -0
  139. package/src/components/event-types.ts +133 -0
  140. package/src/components/events-empty-state.stories.tsx +59 -0
  141. package/src/components/events-empty-state.tsx +131 -0
  142. package/src/components/facet-group.stories.tsx +247 -0
  143. package/src/components/facet-group.test.tsx +225 -0
  144. package/src/components/facet-group.tsx +352 -0
  145. package/src/components/featured-rail-item.stories.tsx +86 -0
  146. package/src/components/featured-rail-item.tsx +153 -0
  147. package/src/components/form.stories.tsx +1 -1
  148. package/src/components/hour-band.stories.tsx +159 -0
  149. package/src/components/hour-band.tsx +126 -0
  150. package/src/components/hour-group.stories.tsx +100 -0
  151. package/src/components/hour-group.tsx +124 -0
  152. package/src/components/icon.tsx +57 -0
  153. package/src/components/input-otp.stories.tsx +1 -1
  154. package/src/components/markdown-renderer.stories.tsx +1 -1
  155. package/src/components/motion-icon-controls.test.tsx +78 -0
  156. package/src/components/motion-icon-controls.tsx +552 -0
  157. package/src/components/motion-icon-set.stories.tsx +185 -0
  158. package/src/components/motion-icon-set.tsx +219 -0
  159. package/src/components/motion-icon.test.tsx +159 -0
  160. package/src/components/motion-icon.tsx +163 -0
  161. package/src/components/multi-select-combobox.stories.tsx +1 -1
  162. package/src/components/phone-frame.stories.tsx +1 -1
  163. package/src/components/poster-stack.stories.tsx +68 -0
  164. package/src/components/poster-stack.tsx +155 -0
  165. package/src/components/poster-tape.stories.tsx +70 -0
  166. package/src/components/poster-tape.tsx +122 -0
  167. package/src/components/poster-wall.stories.tsx +95 -0
  168. package/src/components/poster-wall.tsx +220 -0
  169. package/src/components/quiet-stretch.stories.tsx +62 -0
  170. package/src/components/quiet-stretch.tsx +115 -0
  171. package/src/components/rating.stories.tsx +3 -3
  172. package/src/components/searchable-select.stories.tsx +1 -1
  173. package/src/components/section-header.stories.tsx +70 -0
  174. package/src/components/section-header.tsx +134 -0
  175. package/src/components/segmented-nav.stories.tsx +118 -0
  176. package/src/components/segmented-nav.tsx +113 -0
  177. package/src/components/selection-bar.stories.tsx +1 -1
  178. package/src/components/skeleton-card.stories.tsx +3 -3
  179. package/src/components/skeleton.tsx +18 -0
  180. package/src/components/sliding-panels.stories.tsx +1 -1
  181. package/src/components/stepper.stories.tsx +1 -1
  182. package/src/components/sticky-container.stories.tsx +3 -3
  183. package/src/components/tag.tsx +11 -1
  184. package/src/components/timeline-band.stories.tsx +206 -0
  185. package/src/components/timeline-band.tsx +207 -0
  186. package/src/components/toggle-group.stories.tsx +1 -1
  187. package/src/components/tooltip.stories.tsx +2 -2
  188. package/src/components/venue-events-block.stories.tsx +216 -0
  189. package/src/components/venue-events-block.tsx +408 -0
  190. package/src/components/weekend-panel.stories.tsx +92 -0
  191. package/src/components/weekend-panel.tsx +322 -0
  192. package/src/globals.css +19 -0
  193. package/src/lib/icon-registry.ts +139 -0
  194. package/src/test/setup.ts +5 -1
@@ -0,0 +1,207 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { cn } from "../lib/utils";
5
+ import { Separator } from "./separator";
6
+ import { Skeleton } from "./skeleton";
7
+
8
+ /**
9
+ * TimelineBand — the desktop timeline's three-column row: lane │ spine │ content.
10
+ *
11
+ * Desktop counterpart of `<HourBand>`. Where the mobile timeline collapses the
12
+ * axis into a 52px gutter beside full-width cards, the desktop layout gives the
13
+ * poster its own lane (`--timeline-lane`), a dedicated spine
14
+ * (`--timeline-spine`) carrying the hairline, and a separate content column for
15
+ * whatever follows.
16
+ *
17
+ * ── The band does not draw hours ──────────────────────────────────────────
18
+ * IT USED TO, and only for the hour it opened on. Every LATER hour was an
19
+ * inline `HourGroup` heading in the content column instead, so one concept was
20
+ * drawn in two grammars 48px apart — a bordered puck ON the axis at the top of
21
+ * the day, bare muted text OFF it for the rest of it. An axis whose ticks are
22
+ * labelled once is not an axis, and that is exactly how it read: the later
23
+ * hours were harder to follow than the first.
24
+ *
25
+ * Now every hour is a station and `HourGroup` emits its own, via
26
+ * `TimelineHourPuck` below. The band keeps the geometry and the hairline and
27
+ * nothing else — which is also why `puckTop` is gone: each puck centres itself
28
+ * on its own heading rule, so there is no magic offset left for a caller to
29
+ * tune against a lane height.
30
+ *
31
+ * The invariant the old docblock was reaching for still holds, and states more
32
+ * simply now: AN HOUR APPEARS EXACTLY ONCE, AND ALWAYS ON THE SPINE.
33
+ *
34
+ * HOW THE CONTENT COLUMN IS MEANT TO BE FILLED — the source design composes it
35
+ * in tiers, and getting this wrong is what makes the band look flat:
36
+ *
37
+ * lane one feature card (an `EventCard` with `emphasis`), plus any
38
+ * "N earlier events" collapse control beneath it.
39
+ * content an `<HourGroup spine>` per hour — the opening one holding a grid
40
+ * of `EventCard`s, later ones holding the denser
41
+ * `<EventCompactRow>` as the night thins out.
42
+ *
43
+ * So the density falls off across the row: one poster, then a few cards, then
44
+ * rows.
45
+ */
46
+
47
+ /**
48
+ * Ring pulse on the live puck — inherited from the deleted `TimelinePlayhead`,
49
+ * which carried this surface's only motion. Kept at its 2.4s `ease-in-out` and
50
+ * its reduced-motion kill switch, but BOX-SHADOW ONLY: the playhead's
51
+ * `transform: scale` would make the hairline visibly breathe here, because
52
+ * unlike the playhead this puck sits ON the rail.
53
+ */
54
+ const LIVE_PUCK_PULSE_CLASS = "bm-timeline-live-puck-pulse";
55
+
56
+ export type TimelineHourPuckProps = {
57
+ /** The hour, already formatted and localised — or an em dash for a quiet day. */
58
+ time: string;
59
+ /** Marks this as the hour currently in progress. */
60
+ isLive?: boolean;
61
+ /** Shown under the puck when live, e.g. "NOW ON". Required to render it. */
62
+ liveLabel?: string;
63
+ className?: string;
64
+ };
65
+
66
+ /**
67
+ * The station on the timeline's axis — ONE object, positioned by its host:
68
+ * `HourGroup` centres it on its own heading rule out in the desktop spine,
69
+ * `HourBand` pins it to the top of the mobile gutter.
70
+ *
71
+ * Extracted because it was hand-rolled twice and the copies had already
72
+ * drifted — this one bordered and `rounded-[7px] px-[7px]`, `hour-band.tsx`'s
73
+ * borderless and `rounded-md px-1.5`. The `bg-background` fill punches through
74
+ * the hairline so the rail reads as continuous behind the label rather than
75
+ * butting into it.
76
+ *
77
+ * The live label is absolutely positioned rather than a flex sibling: hosts
78
+ * centre this puck with `-translate-y-1/2`, and a two-item column would centre
79
+ * the STACK, lifting the puck off the line it exists to sit on.
80
+ */
81
+ export function TimelineHourPuck({ time, isLive, liveLabel, className }: TimelineHourPuckProps) {
82
+ return (
83
+ <span className={cn("relative flex justify-center", className)}>
84
+ {isLive && (
85
+ <style>{`
86
+ @keyframes ${LIVE_PUCK_PULSE_CLASS} {
87
+ 0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--success) 40%, transparent); }
88
+ 50% { box-shadow: 0 0 0 4px color-mix(in oklch, var(--success) 0%, transparent); }
89
+ }
90
+ .${LIVE_PUCK_PULSE_CLASS} {
91
+ animation: ${LIVE_PUCK_PULSE_CLASS} 2.4s ease-in-out infinite;
92
+ }
93
+ @media (prefers-reduced-motion: reduce) {
94
+ .${LIVE_PUCK_PULSE_CLASS} { animation: none; }
95
+ }
96
+ `}</style>
97
+ )}
98
+ <span
99
+ className={cn(
100
+ "whitespace-nowrap rounded-[7px] border bg-background px-[7px] py-[3px]",
101
+ "font-mono text-[12.5px] font-medium tabular-nums",
102
+ isLive
103
+ ? cn("border-success/40 text-success", LIVE_PUCK_PULSE_CLASS)
104
+ : "border-border text-foreground"
105
+ )}
106
+ >
107
+ {time}
108
+ </span>
109
+ {isLive && !!liveLabel && (
110
+ <span
111
+ className={cn(
112
+ "absolute top-[calc(100%+6px)] whitespace-nowrap bg-background px-1",
113
+ "text-[9.5px] font-extrabold tracking-[0.08em] text-success"
114
+ )}
115
+ >
116
+ {liveLabel}
117
+ </span>
118
+ )}
119
+ </span>
120
+ );
121
+ }
122
+
123
+ export type TimelineBandProps = {
124
+ /** The lane column — usually a poster card, or empty for a quiet hour. */
125
+ lane: React.ReactNode;
126
+ /** The content column, normally one `<HourGroup spine>` per hour. */
127
+ children: React.ReactNode;
128
+ className?: string;
129
+ };
130
+
131
+ export function TimelineBand({ lane, children, className }: TimelineBandProps) {
132
+ return (
133
+ <div className={cn("flex items-stretch", className)}>
134
+ <div className="w-[var(--timeline-lane)] shrink-0">{lane}</div>
135
+
136
+ <div className="relative flex w-[var(--timeline-spine)] shrink-0 justify-center self-stretch">
137
+ <Separator orientation="vertical" className="h-full" />
138
+ </div>
139
+
140
+ <div className="flex min-w-0 flex-1 flex-col">{children}</div>
141
+ </div>
142
+ );
143
+ }
144
+
145
+ /**
146
+ * A fixed-size shimmer block. `radiusPx` only needs to be passed for the
147
+ * poster-sized blocks (12, matching `rounded-xl`) — every thinner text-line
148
+ * height here (13–18px) is less than twice that radius, so the browser clamps
149
+ * it to a pill automatically even left at the `Skeleton` default.
150
+ */
151
+ function sk(width: string, height: number, radiusPx = 12) {
152
+ return (
153
+ <Skeleton
154
+ style={{ width, height: `${height}px`, borderRadius: `${radiusPx}px` }}
155
+ />
156
+ );
157
+ }
158
+
159
+ export type TimelineBandSkeletonProps = {
160
+ className?: string;
161
+ };
162
+
163
+ /**
164
+ * Holds the lane/spine/content geometry so nothing jumps when data lands.
165
+ * The spine renders its hairline with no puck — there's no hour to show yet.
166
+ */
167
+ export function TimelineBandSkeleton({ className }: TimelineBandSkeletonProps) {
168
+ return (
169
+ <div className={cn("flex items-stretch", className)} aria-hidden="true">
170
+ <div className="flex w-[var(--timeline-lane)] shrink-0 flex-col gap-3">
171
+ {sk("100%", 297, 12)}
172
+ {sk("70%", 18)}
173
+ {sk("45%", 13)}
174
+ </div>
175
+
176
+ <div className="relative flex w-[var(--timeline-spine)] shrink-0 justify-center self-stretch">
177
+ <Separator orientation="vertical" className="h-full" />
178
+ </div>
179
+
180
+ <div className="flex min-w-0 flex-1 flex-col gap-4">
181
+ <div className="grid grid-cols-3 gap-[10px]">
182
+ {Array.from({ length: 3 }, (_, index) => (
183
+ <div key={index} className="flex flex-col gap-2">
184
+ {sk("100%", 190, 12)}
185
+ {sk("85%", 15)}
186
+ {sk("55%", 12)}
187
+ </div>
188
+ ))}
189
+ </div>
190
+
191
+ <div className="flex flex-col">
192
+ {Array.from({ length: 3 }, (_, index) => (
193
+ <div key={index} className="relative flex items-center gap-[11px] py-2">
194
+ <Separator className="absolute inset-x-0 top-0" />
195
+
196
+ <Skeleton className="h-[34px] w-[34px] shrink-0 rounded-md" />
197
+ <div className="flex min-w-0 flex-1 flex-col gap-1.5">
198
+ {sk("60%", 13)}
199
+ {sk("40%", 11.5)}
200
+ </div>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ </div>
205
+ </div>
206
+ );
207
+ }
@@ -9,7 +9,7 @@ const meta = {
9
9
  } satisfies Meta<typeof ToggleGroup>;
10
10
 
11
11
  export default meta;
12
- type Story = StoryObj<typeof meta>;
12
+ type Story = StoryObj<typeof ToggleGroup>;
13
13
 
14
14
  export const Default: Story = {
15
15
  render: () => (
@@ -3,7 +3,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tool
3
3
  import { Button } from "./button";
4
4
  import { Plus } from "lucide-react";
5
5
 
6
- const meta = {
6
+ const meta: Meta<typeof Tooltip> = {
7
7
  title: "Components/Tooltip",
8
8
  component: Tooltip,
9
9
  tags: ["autodocs"],
@@ -14,7 +14,7 @@ const meta = {
14
14
  </TooltipProvider>
15
15
  ),
16
16
  ],
17
- } satisfies Meta<typeof Tooltip>;
17
+ };
18
18
 
19
19
  export default meta;
20
20
  type Story = StoryObj<typeof meta>;
@@ -0,0 +1,216 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import {
3
+ VenueEventsBlock,
4
+ VenueEventsBlockSkeleton,
5
+ type VenueEventsBlockDish,
6
+ type VenueEventsBlockNight,
7
+ } from "./venue-events-block";
8
+
9
+ const nights: VenueEventsBlockNight[] = [
10
+ { id: "n1", dayLabel: "Fri", title: "Psihotrop — Infinity", timeLabel: "20:00", href: "/events/n1" },
11
+ { id: "n2", dayLabel: "Sat", title: "Warehouse Sessions", timeLabel: "22:00", href: "/events/n2" },
12
+ { id: "n3", dayLabel: "Sun", title: "Jazz at the Attic", timeLabel: "19:30", href: "/events/n3" },
13
+ ];
14
+
15
+ const dishes: VenueEventsBlockDish[] = [
16
+ {
17
+ id: "d1",
18
+ name: "Smoked brisket burger",
19
+ description: "Slow-smoked 14h, pickled onion, house sauce",
20
+ priceLabel: "54 RON",
21
+ quantityLabel: "Serves 1",
22
+ mentionsLabel: "18",
23
+ },
24
+ {
25
+ id: "d2",
26
+ name: "Truffle fries",
27
+ description: "Parmesan, rosemary salt",
28
+ priceLabel: "28 RON",
29
+ mentionsLabel: "9",
30
+ },
31
+ {
32
+ id: "d3",
33
+ name: "Craft lager, 0.5l",
34
+ priceLabel: "22 RON",
35
+ },
36
+ ];
37
+
38
+ // Explicitly annotated rather than `satisfies`: the meta-level decorator makes
39
+ // the inferred type reference storybook's internal CSF module, which tsc cannot
40
+ // name portably (TS2742).
41
+ const meta: Meta<typeof VenueEventsBlock> = {
42
+ title: "Events/VenueEventsBlock",
43
+ component: VenueEventsBlock,
44
+ tags: ["autodocs"],
45
+ args: {
46
+ venue: {
47
+ name: "Flying Circus",
48
+ ratingLabel: "4.7",
49
+ metaLabel: "pub · $$",
50
+ isOpenNow: true,
51
+ href: "/places/flying-circus",
52
+ },
53
+ nights,
54
+ allEventsLabel: "All 12 events at Flying Circus",
55
+ allEventsHref: "/places/flying-circus/events",
56
+ menuHeader: <h3 className="mb-1 text-sm font-bold">From the menu</h3>,
57
+ dishes,
58
+ footnote: "Menu prices last confirmed 2 days ago.",
59
+ },
60
+ decorators: [
61
+ (Story) => (
62
+ <div className="max-w-2xl">
63
+ <Story />
64
+ </div>
65
+ ),
66
+ ],
67
+ parameters: {
68
+ docs: {
69
+ description: {
70
+ component:
71
+ "'One venue, a whole fortnight' — the venue and its upcoming nights on the left, its menu on the right. Stacks to a single column on mobile, menu below.",
72
+ },
73
+ },
74
+ },
75
+ };
76
+
77
+ export default meta;
78
+ type Story = StoryObj<typeof VenueEventsBlock>;
79
+
80
+ export const Default: Story = {};
81
+
82
+ /** No thumbnail, not currently open, no rating yet. */
83
+ export const MinimalVenue: Story = {
84
+ args: {
85
+ venue: { name: "The Attic", metaLabel: "wine bar" },
86
+ },
87
+ };
88
+
89
+ /** Neither the venue header, night rows, nor the chip are links — a read-only summary block. */
90
+ export const NoLinks: Story = {
91
+ args: {
92
+ venue: { name: "Flying Circus", ratingLabel: "4.7", metaLabel: "pub · $$", isOpenNow: true },
93
+ nights: nights.map((night) => ({ ...night, href: undefined })),
94
+ allEventsHref: undefined,
95
+ },
96
+ };
97
+
98
+ export const Skeleton: Story = {
99
+ render: () => (
100
+ <div className="max-w-2xl">
101
+ <VenueEventsBlockSkeleton />
102
+ </div>
103
+ ),
104
+ };
105
+
106
+ /**
107
+ * `menuContent` in use — a consuming app renders its OWN menu-row component
108
+ * here instead of the block's built-in dish row, so a dish looks identical
109
+ * whether it is met on this block, the venue's menu page, or anywhere else
110
+ * that app renders a row. `dishes` is omitted: it is ignored once
111
+ * `menuContent` is given.
112
+ */
113
+ export const WithCustomMenuContent: Story = {
114
+ args: {
115
+ dishes: undefined,
116
+ menuContent: (
117
+ <ul>
118
+ {dishes.map((dish, index) => (
119
+ <li
120
+ key={dish.id}
121
+ className="relative flex items-start justify-between gap-3 border-2 border-dashed border-primary/40 py-3 first:border-t-0"
122
+ >
123
+ <div className="min-w-0">
124
+ <p className="text-[15px] font-semibold">{dish.name}</p>
125
+ <p className="text-xs text-muted-foreground">
126
+ Stand-in for the host app&apos;s own menu row, item {index + 1}
127
+ </p>
128
+ </div>
129
+ <span className="shrink-0 text-sm font-bold text-primary">{dish.priceLabel}</span>
130
+ </li>
131
+ ))}
132
+ </ul>
133
+ ),
134
+ },
135
+ parameters: {
136
+ docs: {
137
+ description: {
138
+ story:
139
+ "The DS renders only the layout column; the row markup itself belongs to whatever menu row the host app already owns.",
140
+ },
141
+ },
142
+ },
143
+ };
144
+
145
+ /**
146
+ * All three content slots in use at once — `venueContent`, `nightsContent`,
147
+ * and `menuContent` — the shape a real consuming app actually reaches for:
148
+ * its own place card up top, its own event row per night, its own menu row
149
+ * per dish. `venue`, `nights`, and `dishes` are all omitted; none of them are
150
+ * read once their slot is given. This is the block's real-world shape more
151
+ * than `Default` is — `Default` exists to prove the plain-data fallback still
152
+ * renders something reasonable on its own.
153
+ */
154
+ export const WithAllContentSlots: Story = {
155
+ args: {
156
+ venue: undefined,
157
+ venueContent: (
158
+ <div className="flex gap-3 rounded-xl border-2 border-dashed border-primary/40 p-3">
159
+ <div className="h-[72px] w-28 shrink-0 rounded-lg bg-muted" />
160
+ <div className="min-w-0 flex-1">
161
+ <p className="text-sm font-semibold">Stand-in for the host app&apos;s own place card</p>
162
+ <p className="mt-1 line-clamp-2 text-xs text-muted-foreground">
163
+ Real photo, cuisine/price meta, and the place&apos;s description — the app&apos;s
164
+ canonical card, not a second one owned by this block.
165
+ </p>
166
+ </div>
167
+ </div>
168
+ ),
169
+ nights: undefined,
170
+ nightsContent: (
171
+ <ul>
172
+ {nights.map((night, index) => (
173
+ <li
174
+ key={night.id}
175
+ className="relative flex items-center gap-3 border-2 border-dashed border-primary/40 py-2 first:border-t-0"
176
+ >
177
+ <div className="h-[34px] w-[34px] shrink-0 rounded-md bg-muted" />
178
+ <div className="min-w-0 flex-1">
179
+ <p className="truncate text-[13px] font-semibold">{night.title}</p>
180
+ <p className="truncate text-[11.5px] text-muted-foreground">
181
+ Stand-in for the host app&apos;s own event row, night {index + 1}
182
+ </p>
183
+ </div>
184
+ </li>
185
+ ))}
186
+ </ul>
187
+ ),
188
+ dishes: undefined,
189
+ menuContent: (
190
+ <ul>
191
+ {dishes.map((dish, index) => (
192
+ <li
193
+ key={dish.id}
194
+ className="relative flex items-start justify-between gap-3 border-2 border-dashed border-primary/40 py-3 first:border-t-0"
195
+ >
196
+ <div className="min-w-0">
197
+ <p className="text-[15px] font-semibold">{dish.name}</p>
198
+ <p className="text-xs text-muted-foreground">
199
+ Stand-in for the host app&apos;s own menu row, item {index + 1}
200
+ </p>
201
+ </div>
202
+ <span className="shrink-0 text-sm font-bold text-primary">{dish.priceLabel}</span>
203
+ </li>
204
+ ))}
205
+ </ul>
206
+ ),
207
+ },
208
+ parameters: {
209
+ docs: {
210
+ description: {
211
+ story:
212
+ "The block renders nothing but the two-column layout and the chip; every row shape — venue, night, dish — belongs to the host app.",
213
+ },
214
+ },
215
+ },
216
+ };