@bearmenu/ui 0.8.17 → 0.8.19

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 (59) hide show
  1. package/dist/{chunk-WYTJR2YU.js → chunk-5CPBSNWQ.js} +6 -1
  2. package/dist/chunk-BRXI6EDO.js +84 -0
  3. package/dist/{chunk-54FQQZZJ.js → chunk-EQYB4Q77.js} +1 -1
  4. package/dist/chunk-QRSIJC4X.js +156 -0
  5. package/dist/components/concept-compare-rail.js +2 -2
  6. package/dist/components/concept-compare.js +2 -2
  7. package/dist/components/event-card.d.ts +22 -4
  8. package/dist/components/event-card.js +95 -50
  9. package/dist/components/event-day-header.d.ts +5 -3
  10. package/dist/components/event-day-header.js +12 -4
  11. package/dist/components/event-hub-hero.d.ts +12 -2
  12. package/dist/components/event-hub-hero.js +11 -5
  13. package/dist/components/event-tonight-strip.d.ts +50 -0
  14. package/dist/components/event-tonight-strip.js +67 -0
  15. package/dist/components/menu-family-block.js +4 -3
  16. package/dist/components/menu-hub-hero.d.ts +7 -1
  17. package/dist/components/menu-hub-hero.js +2 -1
  18. package/dist/components/poster-stack.d.ts +36 -3
  19. package/dist/components/poster-stack.js +87 -48
  20. package/dist/components/snap-rail.d.ts +2 -2
  21. package/dist/components/snap-rail.js +1 -1
  22. package/dist/components/timeline-day.d.ts +54 -0
  23. package/dist/components/timeline-day.js +65 -0
  24. package/dist/components/timeline-hour-puck.d.ts +47 -0
  25. package/dist/components/timeline-hour-puck.js +3 -0
  26. package/dist/components/timeline-hour.d.ts +96 -0
  27. package/dist/components/{timeline-band.js → timeline-hour.js} +2 -1
  28. package/dist/components/timeline-playhead.js +1 -3
  29. package/package.json +1 -1
  30. package/src/components/event-card.tsx +70 -13
  31. package/src/components/event-day-header.tsx +19 -7
  32. package/src/components/event-hub-hero.tsx +25 -3
  33. package/src/components/event-tonight-strip.stories.tsx +78 -0
  34. package/src/components/event-tonight-strip.test.tsx +49 -0
  35. package/src/components/event-tonight-strip.tsx +134 -0
  36. package/src/components/menu-family-block.tsx +13 -6
  37. package/src/components/menu-hub-hero.test.tsx +13 -0
  38. package/src/components/menu-hub-hero.tsx +9 -2
  39. package/src/components/poster-stack.stories.tsx +43 -1
  40. package/src/components/poster-stack.tsx +104 -19
  41. package/src/components/snap-rail.tsx +6 -1
  42. package/src/components/timeline-day.stories.tsx +201 -0
  43. package/src/components/timeline-day.test.tsx +78 -0
  44. package/src/components/timeline-day.tsx +114 -0
  45. package/src/components/timeline-hour-puck.tsx +142 -0
  46. package/src/components/timeline-hour.tsx +266 -0
  47. package/src/components/timeline-playhead.tsx +1 -1
  48. package/dist/chunk-NSNEANOQ.js +0 -104
  49. package/dist/components/hour-band.d.ts +0 -66
  50. package/dist/components/hour-band.js +0 -81
  51. package/dist/components/hour-group.d.ts +0 -67
  52. package/dist/components/hour-group.js +0 -59
  53. package/dist/components/timeline-band.d.ts +0 -64
  54. package/src/components/hour-band.stories.tsx +0 -182
  55. package/src/components/hour-band.tsx +0 -146
  56. package/src/components/hour-group.stories.tsx +0 -100
  57. package/src/components/hour-group.tsx +0 -124
  58. package/src/components/timeline-band.stories.tsx +0 -206
  59. package/src/components/timeline-band.tsx +0 -247
@@ -0,0 +1,266 @@
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
+ import { TIMELINE_MOBILE_GUTTER_CLASS, TimelineHourPuck } from "./timeline-hour-puck";
8
+
9
+ /**
10
+ * TimelineHour — one hour of a day's programme, ONE DOM for both viewports.
11
+ *
12
+ * Replaces `HourBand` (the phone band: a 58px gutter with a filled chip and
13
+ * a hairline dropping from it, the hour's cards beside) and `HourGroup` (the
14
+ * desktop heading: a station out on `TimelineDay`'s spine, a rule and a
15
+ * count). They were two components because the timeline picked one per
16
+ * viewport in JavaScript, and a server render cannot know the viewport — so
17
+ * the server rendered nothing. This is both, with CSS deciding:
18
+ *
19
+ * below `lg` the section carries a 70px left inset; the puck sits at the
20
+ * top-left of that gutter as a chip, a hairline drops beneath
21
+ * it, and the heading row is hidden — the chip IS the heading.
22
+ * from `lg` the inset goes, the heading row shows (rule + count), and the
23
+ * puck moves out to the spine, centred on that rule, as a
24
+ * station. Its vertical position is a constant (`--timeline-
25
+ * heading` below), which is why the heading row has a fixed
26
+ * height: "top-1/2 of the row" needs the puck INSIDE the row,
27
+ * and inside the row it cannot also be the phone's gutter chip.
28
+ *
29
+ * The invariant `TimelineBand` used to state still holds: AN HOUR APPEARS
30
+ * EXACTLY ONCE, AND ALWAYS ON THE SPINE. Roles are how a day keeps it while
31
+ * one of its events is lifted into the desktop lane:
32
+ *
33
+ * hour the default — a station, the hour's events.
34
+ * hero the day's feature. Below `lg` a normal hour holding one
35
+ * card, in its chronological slot. From `lg` the node moves
36
+ * to `TimelineDay`'s lane column (grid placement, not source
37
+ * order), pins, and loses its puck and heading — the lane
38
+ * card carries its own start time, see `EventCard` `hideWhen`
39
+ * `"below-lg"`.
40
+ * continuation what is left of the hero's hour. Below `lg` its chip is
41
+ * `invisible` (the gutter and hairline stay, so the reader sees
42
+ * ONE band for that hour, hero first); from `lg` it shows its
43
+ * station, because the hero is in the lane now and the hour
44
+ * needs its puck back. When the hero was its hour's only
45
+ * event there is no continuation, and the hour simply leaves
46
+ * the desktop column — exactly what `HourGroup`'s
47
+ * `excludeEventId` used to do.
48
+ * unscheduled events the ingest dated but never timed. No station at any
49
+ * width — the axis encodes clock position and these have none
50
+ * — and no gutter inset: an inline heading (the caller's
51
+ * label, a rule, the count) at both widths, after the day's
52
+ * real programme.
53
+ *
54
+ * `layout`: `cards` stacks posters with a gap (a card is its own frame, a
55
+ * hairline between two posters reads as a third object) and from `lg` lays
56
+ * them on a two/three-up grid; `rows` rules between children on both widths.
57
+ * Children are read with `Children.toArray`, which drops `null`/`false`, so a
58
+ * conditional first row never inherits a rule meant for a second; pass rows
59
+ * as siblings, not inside a fragment.
60
+ */
61
+
62
+ export type TimelineHourRole = "hour" | "hero" | "continuation" | "unscheduled";
63
+
64
+ export type TimelineHourProps = {
65
+ /** The hour, already formatted and localised — "20:00". For `unscheduled`,
66
+ * the caller's label for the group ("Time to be confirmed"). */
67
+ time: string;
68
+ role?: TimelineHourRole;
69
+ /** Marks this as the hour currently in progress. */
70
+ isLive?: boolean;
71
+ /** Under the station when live, e.g. "NOW ON"; screen-reader-only in the chip. */
72
+ liveLabel?: string;
73
+ /** Desktop heading count, e.g. "3 events" — mirrors `EventDayHeader`'s `meta`.
74
+ * Also the phone heading for `unscheduled`. */
75
+ countLabel?: string;
76
+ /** Trailing note after the children, e.g. "No start time was published". */
77
+ note?: string;
78
+ layout?: "cards" | "rows";
79
+ /** The hour's events — each direct child one card or row. */
80
+ children: React.ReactNode;
81
+ /**
82
+ * Events past the hour's card cap, after `children`: cards on the phone
83
+ * (the same stack, same gap), rows from `lg` (ruled, under the grid) —
84
+ * pass `EventCard density="row-lg"`. The desktop's density ladder inside
85
+ * one hour: a few posters, then rows.
86
+ */
87
+ overflow?: React.ReactNode;
88
+ className?: string;
89
+ };
90
+
91
+ /** Where the desktop puck's centre sits: the section's `lg:pt-[22px]` plus
92
+ * half the fixed 16px heading row. */
93
+ const HEADING_CENTRE_LG = "lg:top-[30px]";
94
+
95
+ export function TimelineHour({
96
+ time,
97
+ role = "hour",
98
+ isLive,
99
+ liveLabel,
100
+ countLabel,
101
+ note,
102
+ layout = "cards",
103
+ children,
104
+ overflow,
105
+ className,
106
+ }: TimelineHourProps) {
107
+ const isHero = role === "hero";
108
+ const isContinuation = role === "continuation";
109
+ const isUnscheduled = role === "unscheduled";
110
+ const hasStation = !isUnscheduled;
111
+ const isCards = layout === "cards";
112
+ const items = React.Children.toArray(children);
113
+ const overflowItems = React.Children.toArray(overflow);
114
+
115
+ return (
116
+ <section
117
+ data-timeline-hour={role}
118
+ className={cn(
119
+ "relative flex flex-col pt-2.5",
120
+ // The phone gutter: the puck's column plus the 12px gap `HourBand` had.
121
+ hasStation && "pl-[70px] lg:pl-0",
122
+ "lg:pt-[22px]",
123
+ isHero
124
+ ? cn(
125
+ // The lane. Placed by the grid, not by source order; `self-start`
126
+ // is load-bearing — a stretched item would be the day's full
127
+ // height and never stick.
128
+ // Longhands, not `grid-row`: a `/` inside an arbitrary value
129
+ // collides with the opacity-modifier parse.
130
+ "lg:col-start-1 lg:row-start-1 lg:[grid-row-end:span_var(--timeline-rows)] lg:self-start",
131
+ "lg:sticky lg:top-[var(--timeline-lane-top)] lg:pt-3.5"
132
+ )
133
+ : "lg:col-start-3",
134
+ className
135
+ )}
136
+ >
137
+ {hasStation && (
138
+ <>
139
+ <TimelineHourPuck
140
+ time={time}
141
+ isLive={isLive}
142
+ liveLabel={liveLabel}
143
+ className={cn(
144
+ "absolute left-0 top-2.5",
145
+ TIMELINE_MOBILE_GUTTER_CLASS,
146
+ // Out on the spine, centred on the heading rule.
147
+ "lg:left-[calc(var(--timeline-spine)*-0.5)] lg:w-0 lg:-translate-y-1/2",
148
+ HEADING_CENTRE_LG,
149
+ isContinuation && "invisible lg:visible",
150
+ isHero && "lg:hidden"
151
+ )}
152
+ />
153
+ {/* The phone hairline, from under the chip to the next hour's chip;
154
+ the desktop spine is `TimelineDay`'s. */}
155
+ <Separator
156
+ orientation="vertical"
157
+ className={cn(
158
+ "absolute -bottom-2.5 left-[29px] top-10 h-auto -translate-x-1/2 lg:hidden",
159
+ isLive && "bg-success/30"
160
+ )}
161
+ />
162
+ </>
163
+ )}
164
+
165
+ {/* The heading row: hidden on the phone for a stationed hour (the chip
166
+ says it), the hero's goes with its station from `lg`. Fixed height
167
+ so the puck's centre is a constant. */}
168
+ <div
169
+ className={cn(
170
+ "items-center gap-2.5",
171
+ isUnscheduled ? "flex h-4" : "hidden lg:flex lg:h-4",
172
+ isHero && "lg:hidden"
173
+ )}
174
+ >
175
+ {isUnscheduled && (
176
+ <span className="font-mono text-[11.5px] text-muted-foreground tabular-nums lg:text-xs">
177
+ {time}
178
+ </span>
179
+ )}
180
+ <Separator className="flex-1" />
181
+ {countLabel && (
182
+ <span className="text-[10.5px] text-muted-foreground/70 lg:text-[11px]">
183
+ {countLabel}
184
+ </span>
185
+ )}
186
+ </div>
187
+
188
+ <div
189
+ className={cn(
190
+ "flex min-w-0 flex-col",
191
+ isCards && "gap-4 pb-2 lg:grid lg:grid-cols-2 lg:gap-5 lg:pb-0 lg:pt-3.5 xl:grid-cols-3",
192
+ isCards && isHero && "lg:flex lg:pt-0",
193
+ isUnscheduled && isCards && "pt-3.5"
194
+ )}
195
+ >
196
+ {isCards
197
+ ? items
198
+ : items.map((child, index) => (
199
+ <div key={index}>
200
+ {index > 0 && <Separator />}
201
+ {child}
202
+ </div>
203
+ ))}
204
+ </div>
205
+
206
+ {overflowItems.length > 0 && (
207
+ <div className="flex flex-col gap-4 pb-2 lg:gap-0 lg:pb-0 lg:pt-2">
208
+ {overflowItems.map((child, index) => (
209
+ <div key={index}>
210
+ {index > 0 && <Separator className="hidden lg:block" />}
211
+ {child}
212
+ </div>
213
+ ))}
214
+ </div>
215
+ )}
216
+
217
+ {note && <p className="pt-[9px] text-[11.5px] text-muted-foreground">{note}</p>}
218
+ </section>
219
+ );
220
+ }
221
+
222
+ export type TimelineHourSkeletonProps = {
223
+ /** Cards to reserve space for. */
224
+ cardCount?: number;
225
+ className?: string;
226
+ };
227
+
228
+ /**
229
+ * Mirrors `TimelineHour` at both widths — the phone's gutter chip and
230
+ * hairline, the desktop's grid of poster placeholders — so nothing jumps when
231
+ * data lands.
232
+ */
233
+ export function TimelineHourSkeleton({ cardCount = 2, className }: TimelineHourSkeletonProps) {
234
+ return (
235
+ <div
236
+ aria-hidden="true"
237
+ className={cn("relative flex flex-col pl-[70px] pt-2.5 lg:col-start-3 lg:pl-0 lg:pt-[22px]", className)}
238
+ >
239
+ <Skeleton
240
+ className={cn(
241
+ "absolute left-0 top-2.5 h-6 rounded-[7px]",
242
+ TIMELINE_MOBILE_GUTTER_CLASS,
243
+ "lg:left-[calc(var(--timeline-spine)*-0.5)] lg:h-[26px] lg:w-14 lg:-translate-x-1/2 lg:-translate-y-1/2",
244
+ HEADING_CENTRE_LG
245
+ )}
246
+ />
247
+ <Separator
248
+ orientation="vertical"
249
+ className="absolute -bottom-2.5 left-[29px] top-10 h-auto -translate-x-1/2 lg:hidden"
250
+ />
251
+ <div className="hidden h-4 items-center gap-2.5 lg:flex">
252
+ <Separator className="flex-1" />
253
+ <Skeleton className="h-3 w-12" />
254
+ </div>
255
+ <div className="flex flex-col gap-4 pb-2 lg:grid lg:grid-cols-2 lg:gap-5 lg:pb-0 lg:pt-3.5 xl:grid-cols-3">
256
+ {Array.from({ length: cardCount }, (_, index) => (
257
+ <div key={index} className="flex flex-col gap-2">
258
+ <Skeleton className="aspect-[4/3] w-full rounded-xl" />
259
+ <Skeleton className="h-3.5 w-4/5" />
260
+ <Skeleton className="h-3 w-1/2" />
261
+ </div>
262
+ ))}
263
+ </div>
264
+ </div>
265
+ );
266
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as React from "react";
4
4
  import { cn } from "../lib/utils";
5
- import { TIMELINE_MOBILE_GUTTER_CLASS } from "./timeline-band";
5
+ import { TIMELINE_MOBILE_GUTTER_CLASS } from "./timeline-hour-puck";
6
6
 
7
7
  /**
8
8
  * TimelinePlayhead — the "you are here" mark on the mobile timeline: the
@@ -1,104 +0,0 @@
1
- import { Separator } from './chunk-NWZGSLPU.js';
2
- import { Skeleton } from './chunk-XBBEX4SF.js';
3
- import { cn } from './chunk-FEK5XGZW.js';
4
- import { Radio } from 'lucide-react';
5
- import { jsxs, jsx } from 'react/jsx-runtime';
6
-
7
- var LIVE_PUCK_PULSE_CLASS = "bm-timeline-live-puck-pulse";
8
- var TIMELINE_MOBILE_GUTTER_CLASS = "w-[58px]";
9
- function TimelineHourPuck({
10
- time,
11
- isLive,
12
- liveLabel,
13
- variant = "station",
14
- className
15
- }) {
16
- const isChip = variant === "chip";
17
- return /* @__PURE__ */ jsxs("span", { className: cn("relative flex justify-center", className), children: [
18
- isLive && /* @__PURE__ */ jsx("style", { children: `
19
- @keyframes ${LIVE_PUCK_PULSE_CLASS} {
20
- 0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--success) 40%, transparent); }
21
- 50% { box-shadow: 0 0 0 4px color-mix(in oklch, var(--success) 0%, transparent); }
22
- }
23
- .${LIVE_PUCK_PULSE_CLASS} {
24
- animation: ${LIVE_PUCK_PULSE_CLASS} 2.4s ease-in-out infinite;
25
- }
26
- @media (prefers-reduced-motion: reduce) {
27
- .${LIVE_PUCK_PULSE_CLASS} { animation: none; }
28
- }
29
- ` }),
30
- /* @__PURE__ */ jsxs(
31
- "span",
32
- {
33
- className: cn(
34
- "whitespace-nowrap font-mono tabular-nums",
35
- isChip ? cn(
36
- "inline-flex h-6 min-w-[58px] items-center justify-center gap-1 rounded-[7px] px-1 text-[12px] font-medium",
37
- isLive ? cn("bg-success/10 text-success", LIVE_PUCK_PULSE_CLASS) : "bg-muted text-foreground"
38
- ) : cn(
39
- "rounded-[7px] border bg-background px-[7px] py-[3px] text-[12.5px] font-medium",
40
- isLive ? cn("border-success/40 text-success", LIVE_PUCK_PULSE_CLASS) : "border-border text-foreground"
41
- )
42
- ),
43
- children: [
44
- isChip && isLive && /* @__PURE__ */ jsx(Radio, { className: "h-2.5 w-2.5 shrink-0", "aria-hidden": true }),
45
- time
46
- ]
47
- }
48
- ),
49
- isLive && !!liveLabel && /* @__PURE__ */ jsx(
50
- "span",
51
- {
52
- className: cn(
53
- isChip ? "sr-only" : cn(
54
- "absolute top-[calc(100%+6px)] whitespace-nowrap bg-background px-1",
55
- "text-[9.5px] font-extrabold tracking-[0.08em] text-success"
56
- )
57
- ),
58
- children: liveLabel
59
- }
60
- )
61
- ] });
62
- }
63
- function TimelineBand({ lane, children, className }) {
64
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-stretch", className), children: [
65
- /* @__PURE__ */ jsx("div", { className: "w-[var(--timeline-lane)] shrink-0", children: lane }),
66
- /* @__PURE__ */ jsx("div", { className: "relative flex w-[var(--timeline-spine)] shrink-0 justify-center self-stretch", children: /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-full" }) }),
67
- /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col", children })
68
- ] });
69
- }
70
- function sk(width, height, radiusPx = 12) {
71
- return /* @__PURE__ */ jsx(
72
- Skeleton,
73
- {
74
- style: { width, height: `${height}px`, borderRadius: `${radiusPx}px` }
75
- }
76
- );
77
- }
78
- function TimelineBandSkeleton({ className }) {
79
- return /* @__PURE__ */ jsxs("div", { className: cn("flex items-stretch", className), "aria-hidden": "true", children: [
80
- /* @__PURE__ */ jsxs("div", { className: "flex w-[var(--timeline-lane)] shrink-0 flex-col gap-3", children: [
81
- sk("100%", 297, 12),
82
- sk("70%", 18),
83
- sk("45%", 13)
84
- ] }),
85
- /* @__PURE__ */ jsx("div", { className: "relative flex w-[var(--timeline-spine)] shrink-0 justify-center self-stretch", children: /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-full" }) }),
86
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-4", children: [
87
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-[10px]", children: Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
88
- sk("100%", 190, 12),
89
- sk("85%", 15),
90
- sk("55%", 12)
91
- ] }, index)) }),
92
- /* @__PURE__ */ jsx("div", { className: "flex flex-col", children: Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-[11px] py-2", children: [
93
- /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
94
- /* @__PURE__ */ jsx(Skeleton, { className: "h-[34px] w-[34px] shrink-0 rounded-md" }),
95
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
96
- sk("60%", 13),
97
- sk("40%", 11.5)
98
- ] })
99
- ] }, index)) })
100
- ] })
101
- ] });
102
- }
103
-
104
- export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineBand, TimelineBandSkeleton, TimelineHourPuck };
@@ -1,66 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as React from 'react';
3
-
4
- /**
5
- * HourBand — one hour on the timeline's axis, with that hour's events beside it.
6
- *
7
- * The mobile timeline is an app list, not a page: a 58px gutter carrying a
8
- * filled hour chip with a hairline dropping from beneath it, and the hour's
9
- * rows to the right, separated by hairlines of their own. The rows carry no
10
- * timestamp — this band IS the timestamp, which is why `EventCard` has
11
- * `hideWhen` and `EventCompactRow` has `hideTime`.
12
- *
13
- * The chip is the shared `TimelineHourPuck` in its `chip` variant, so the
14
- * desktop spine's station and this gutter's label are one object with two
15
- * looks rather than two objects that drift. The hairline starts BELOW the chip
16
- * rather than running behind it: a chip is a label at the top of its hour, a
17
- * station is a tick on a continuous rail, and the two read differently.
18
- *
19
- * `isLive` tints the chip and the hairline with `--success`; the live label is
20
- * kept for screen readers (the chip variant hides it visually). The playhead —
21
- * the exact current time — is a separate `TimelinePlayhead` rendered between
22
- * bands by the caller, because an hour spans sixty minutes and "now" is one of
23
- * them.
24
- *
25
- * ROWS ARE SEPARATED HERE, NOT IN THE ROW. `EventCompactRow` deliberately
26
- * draws no divider (its container does — see its docblock), so this band wraps
27
- * each child and rules between them. The wrapper, not the child, carries the
28
- * rule, so the row's own negative-margin hover wash is untouched. Children are
29
- * read with `Children.toArray`, which drops `null`/`false` so a conditional
30
- * first row never inherits a rule meant for a second; pass rows as siblings,
31
- * not inside a fragment — a fragment is one child and gets one wrapper.
32
- */
33
- type HourBandProps = {
34
- /** The hour, already formatted and localised — "20:00". */
35
- time: string;
36
- /** Marks this as the hour currently in progress. */
37
- isLive?: boolean;
38
- /** Screen-reader text for the live hour, e.g. "Now on". Required to render it. */
39
- liveLabel?: string;
40
- /** The hour's events. Each direct child is one row or card. */
41
- children: React.ReactNode;
42
- /**
43
- * `rows` (default): compact rows, ruled between with a Separator. `cards`:
44
- * poster cards, stacked with a 16px gap and no rules — a card is its own
45
- * frame, and a hairline between two posters reads as a third object.
46
- */
47
- layout?: "rows" | "cards";
48
- className?: string;
49
- };
50
- declare function HourBand({ time, isLive, liveLabel, children, layout, className, }: HourBandProps): react_jsx_runtime.JSX.Element;
51
- type HourBandSkeletonProps = {
52
- /** Rows to reserve space for. */
53
- cardCount?: number;
54
- className?: string;
55
- };
56
- /**
57
- * The mobile timeline's loading state, mirroring `HourBand` exactly — same
58
- * gutter, same hairline, same row rhythm — so nothing jumps when data lands.
59
- *
60
- * The chip is blocked out at its real size rather than omitted: it is a fixed
61
- * 58x24 lozenge, so a grey one reads as "a time goes here" without inventing
62
- * a time. Row footprints mirror `EventCompactRowSkeleton`.
63
- */
64
- declare function HourBandSkeleton({ cardCount, className, }: HourBandSkeletonProps): react_jsx_runtime.JSX.Element;
65
-
66
- export { HourBand, type HourBandProps, HourBandSkeleton, type HourBandSkeletonProps };
@@ -1,81 +0,0 @@
1
- import { TimelineHourPuck, TIMELINE_MOBILE_GUTTER_CLASS } from '../chunk-NSNEANOQ.js';
2
- import { Separator } from '../chunk-NWZGSLPU.js';
3
- import { Skeleton } from '../chunk-XBBEX4SF.js';
4
- import { cn } from '../chunk-FEK5XGZW.js';
5
- import '../chunk-MMUBH76A.js';
6
- import * as React from 'react';
7
- import { jsxs, jsx } from 'react/jsx-runtime';
8
-
9
- function HourBand({
10
- time,
11
- isLive,
12
- liveLabel,
13
- children,
14
- layout = "rows",
15
- className
16
- }) {
17
- const isCards = layout === "cards";
18
- return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-3 pt-2.5", className), children: [
19
- /* @__PURE__ */ jsxs("div", { className: cn("relative shrink-0", TIMELINE_MOBILE_GUTTER_CLASS), children: [
20
- /* @__PURE__ */ jsx(
21
- TimelineHourPuck,
22
- {
23
- time,
24
- isLive,
25
- liveLabel,
26
- variant: "chip"
27
- }
28
- ),
29
- /* @__PURE__ */ jsx(
30
- Separator,
31
- {
32
- orientation: "vertical",
33
- className: cn(
34
- "absolute bottom-[-10px] left-1/2 top-[30px] h-auto -translate-x-1/2",
35
- isLive && "bg-success/30"
36
- )
37
- }
38
- )
39
- ] }),
40
- /* @__PURE__ */ jsx(
41
- "div",
42
- {
43
- className: cn("flex min-w-0 flex-1 flex-col", isCards && "gap-4 pb-2"),
44
- children: React.Children.toArray(children).map((child, index) => /* @__PURE__ */ jsxs("div", { children: [
45
- !isCards && index > 0 && /* @__PURE__ */ jsx(Separator, {}),
46
- child
47
- ] }, index))
48
- }
49
- )
50
- ] });
51
- }
52
- function HourBandSkeleton({
53
- cardCount = 2,
54
- className
55
- }) {
56
- return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-3 pt-2.5", className), "aria-hidden": "true", children: [
57
- /* @__PURE__ */ jsxs("div", { className: cn("relative shrink-0", TIMELINE_MOBILE_GUTTER_CLASS), children: [
58
- /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-[58px] rounded-[7px]" }),
59
- /* @__PURE__ */ jsx(
60
- Separator,
61
- {
62
- orientation: "vertical",
63
- className: "absolute bottom-[-10px] left-1/2 top-[30px] h-auto -translate-x-1/2"
64
- }
65
- )
66
- ] }),
67
- /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col", children: Array.from({ length: cardCount }).map((_, index) => /* @__PURE__ */ jsxs("div", { children: [
68
- index > 0 && /* @__PURE__ */ jsx(Separator, {}),
69
- /* @__PURE__ */ jsxs("div", { className: "flex gap-3 py-[11px]", children: [
70
- /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[4/5] w-16 shrink-0 rounded-[10px]" }),
71
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
72
- /* @__PURE__ */ jsx(Skeleton, { className: "h-[14px] w-3/4" }),
73
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-[11.5px] w-1/2" }),
74
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-[22px] w-16 rounded-full" })
75
- ] })
76
- ] })
77
- ] }, index)) })
78
- ] });
79
- }
80
-
81
- export { HourBand, HourBandSkeleton };
@@ -1,67 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import * as React from 'react';
3
-
4
- /**
5
- * HourGroup — an hour heading inside `<TimelineBand>`'s content column: a
6
- * time, a rule, and a count, with that hour's rows beneath and an optional
7
- * trailing note (e.g. "3 more not shown").
8
- *
9
- * ── `spine` — the desktop default ────────────────────────────────────────
10
- * Inside a `<TimelineBand>`, pass `spine` and the hour renders as a
11
- * `TimelineHourPuck` out ON the axis, centred on this heading's own rule,
12
- * instead of as inline text at the head of the content column. Every hour of
13
- * the day then reads as a station on the same rail, which is the entire point
14
- * of the rail; the band itself no longer draws any hour, so there is nothing
15
- * left to collide with.
16
- *
17
- * The offset is `calc(var(--timeline-spine) * -0.5)` — half the gutter, back
18
- * from the content column's left edge, which lands on the hairline. Written
19
- * with `*-0.5` rather than `/2` because a slash inside a Tailwind arbitrary
20
- * value collides with the opacity-modifier parse. The `w-0` wrapper plus the
21
- * puck's own `justify-center` is what centres it, so no `-translate-x-1/2` is
22
- * needed and the live label centres on the same line for free.
23
- *
24
- * `spine` implies the inline time is suppressed — no caller should have to
25
- * pass `spine hideTime` to avoid printing the hour twice.
26
- *
27
- * Keep the rule and the count. The rule is the only thing tying a puck to the
28
- * rows it labels across the empty gutter; without it the puck reads as a
29
- * sticker rather than a station on a track. The count carries information
30
- * present nowhere else and terminates the rule so it doesn't dissolve.
31
- *
32
- * Where a group's rows already carry their own timestamps, `hideTime` drops the
33
- * time and keeps the rule and count, so the hour is stated exactly once.
34
- *
35
- * `size="sm"` is the mobile variant, shrinking the type a half-step and
36
- * matching `<HourBand>`'s own top offset (18px) rather than the desktop
37
- * default (22px).
38
- */
39
- type HourGroupProps = {
40
- /** The hour, already formatted and localised — "20:00". */
41
- time: string;
42
- /**
43
- * Render the hour as a puck out on `<TimelineBand>`'s spine instead of as
44
- * inline text. The desktop timeline's default; implies `hideTime`.
45
- */
46
- spine?: boolean;
47
- /** Marks this hour as the one currently in progress. `spine` only. */
48
- isLive?: boolean;
49
- /** Shown under the puck when live, e.g. "NOW". `spine` only. */
50
- liveLabel?: string;
51
- /**
52
- * Drop the leading time, keeping the rule and count. For groups whose rows
53
- * state their own time.
54
- */
55
- hideTime?: boolean;
56
- /** Caller-composed, e.g. "2 events" — mirrors `EventDayHeader`'s `meta`. */
57
- countLabel: string;
58
- /** Trailing note shown after the rows, e.g. "3 more not shown". */
59
- note?: string;
60
- /** The hour's rows. */
61
- children?: React.ReactNode;
62
- size?: "sm" | "default";
63
- className?: string;
64
- };
65
- declare function HourGroup({ time, spine, isLive, liveLabel, hideTime, countLabel, note, children, size, className, }: HourGroupProps): react_jsx_runtime.JSX.Element;
66
-
67
- export { HourGroup, type HourGroupProps };
@@ -1,59 +0,0 @@
1
- import { TimelineHourPuck } from '../chunk-NSNEANOQ.js';
2
- import { Separator } from '../chunk-NWZGSLPU.js';
3
- import '../chunk-XBBEX4SF.js';
4
- import { cn } from '../chunk-FEK5XGZW.js';
5
- import '../chunk-MMUBH76A.js';
6
- import { jsxs, jsx } from 'react/jsx-runtime';
7
-
8
- function HourGroup({
9
- time,
10
- spine,
11
- isLive,
12
- liveLabel,
13
- hideTime,
14
- countLabel,
15
- note,
16
- children,
17
- size = "default",
18
- className
19
- }) {
20
- const isSm = size === "sm";
21
- return (
22
- // The group's top padding lives HERE, not on the heading row, and that is
23
- // load-bearing for `spine`: `top-1/2` resolves against the heading row's
24
- // PADDING box while `items-center` centres the rule in its CONTENT box, so
25
- // with `pt-[22px]` on the row the puck landed 19.25px down and the rule at
26
- // 30.25px — 11px apart, the puck visibly riding above the line it is
27
- // supposed to sit on. Unpadded, both resolve against the same box and the
28
- // puck's centre, the rule and the spine hairline are one line.
29
- /* @__PURE__ */ jsxs("div", { className: cn(isSm ? "pt-[18px]" : "pt-[22px]", className), children: [
30
- /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2.5", spine && "relative"), children: [
31
- spine && /* @__PURE__ */ jsx(
32
- TimelineHourPuck,
33
- {
34
- time,
35
- isLive,
36
- liveLabel,
37
- className: "absolute left-[calc(var(--timeline-spine)*-0.5)] top-1/2 w-0 -translate-y-1/2"
38
- }
39
- ),
40
- !hideTime && !spine && /* @__PURE__ */ jsx(
41
- "span",
42
- {
43
- className: cn(
44
- "font-mono text-muted-foreground tabular-nums",
45
- isSm ? "text-[11.5px]" : "text-xs"
46
- ),
47
- children: time
48
- }
49
- ),
50
- /* @__PURE__ */ jsx(Separator, { className: "flex-1" }),
51
- /* @__PURE__ */ jsx("span", { className: cn("text-muted-foreground/70", isSm ? "text-[10.5px]" : "text-[11px]"), children: countLabel })
52
- ] }),
53
- children,
54
- note && /* @__PURE__ */ jsx("p", { className: "pt-[9px] text-[11.5px] text-muted-foreground", children: note })
55
- ] })
56
- );
57
- }
58
-
59
- export { HourGroup };