@bearmenu/ui 0.8.17 → 0.8.18

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 (71) hide show
  1. package/dist/{chunk-WYTJR2YU.js → chunk-5CPBSNWQ.js} +6 -1
  2. package/dist/{chunk-54FQQZZJ.js → chunk-EQYB4Q77.js} +1 -1
  3. package/dist/chunk-LYREUZH6.js +156 -0
  4. package/dist/chunk-PW247TG2.js +81 -0
  5. package/dist/components/alert.d.ts +2 -2
  6. package/dist/components/badge.d.ts +1 -1
  7. package/dist/components/button.d.ts +1 -1
  8. package/dist/components/card.d.ts +2 -2
  9. package/dist/components/command.d.ts +15 -15
  10. package/dist/components/concept-compare-rail.js +2 -2
  11. package/dist/components/concept-compare.js +2 -2
  12. package/dist/components/event-card.d.ts +22 -4
  13. package/dist/components/event-card.js +95 -50
  14. package/dist/components/event-day-header.d.ts +5 -3
  15. package/dist/components/event-day-header.js +12 -4
  16. package/dist/components/event-door.d.ts +41 -0
  17. package/dist/components/event-door.js +65 -0
  18. package/dist/components/event-hub-hero.d.ts +10 -2
  19. package/dist/components/event-hub-hero.js +11 -5
  20. package/dist/components/event-tonight-strip.d.ts +50 -0
  21. package/dist/components/event-tonight-strip.js +67 -0
  22. package/dist/components/item.d.ts +2 -2
  23. package/dist/components/menu-hub-hero.d.ts +7 -1
  24. package/dist/components/menu-hub-hero.js +2 -1
  25. package/dist/components/place-card.js +1 -1
  26. package/dist/components/poster-stack.d.ts +36 -3
  27. package/dist/components/poster-stack.js +49 -18
  28. package/dist/components/snap-rail.d.ts +2 -2
  29. package/dist/components/snap-rail.js +1 -1
  30. package/dist/components/spinner.d.ts +2 -2
  31. package/dist/components/tag.d.ts +2 -2
  32. package/dist/components/text.d.ts +3 -3
  33. package/dist/components/timeline-day.d.ts +54 -0
  34. package/dist/components/timeline-day.js +65 -0
  35. package/dist/components/timeline-hour-puck.d.ts +47 -0
  36. package/dist/components/timeline-hour-puck.js +3 -0
  37. package/dist/components/timeline-hour.d.ts +96 -0
  38. package/dist/components/{timeline-band.js → timeline-hour.js} +2 -1
  39. package/dist/components/timeline-playhead.js +1 -3
  40. package/package.json +1 -1
  41. package/src/components/event-card.tsx +70 -13
  42. package/src/components/event-day-header.tsx +19 -7
  43. package/src/components/event-door.stories.tsx +88 -0
  44. package/src/components/event-door.tsx +97 -0
  45. package/src/components/event-hub-hero.tsx +21 -3
  46. package/src/components/event-tonight-strip.stories.tsx +78 -0
  47. package/src/components/event-tonight-strip.test.tsx +59 -0
  48. package/src/components/event-tonight-strip.tsx +134 -0
  49. package/src/components/menu-hub-hero.test.tsx +13 -0
  50. package/src/components/menu-hub-hero.tsx +9 -2
  51. package/src/components/poster-stack.stories.tsx +43 -1
  52. package/src/components/poster-stack.tsx +98 -19
  53. package/src/components/snap-rail.tsx +6 -1
  54. package/src/components/timeline-day.stories.tsx +201 -0
  55. package/src/components/timeline-day.test.tsx +78 -0
  56. package/src/components/timeline-day.tsx +114 -0
  57. package/src/components/timeline-hour-puck.tsx +139 -0
  58. package/src/components/timeline-hour.tsx +266 -0
  59. package/src/components/timeline-playhead.tsx +1 -1
  60. package/dist/chunk-NSNEANOQ.js +0 -104
  61. package/dist/components/hour-band.d.ts +0 -66
  62. package/dist/components/hour-band.js +0 -81
  63. package/dist/components/hour-group.d.ts +0 -67
  64. package/dist/components/hour-group.js +0 -59
  65. package/dist/components/timeline-band.d.ts +0 -64
  66. package/src/components/hour-band.stories.tsx +0 -182
  67. package/src/components/hour-band.tsx +0 -146
  68. package/src/components/hour-group.stories.tsx +0 -100
  69. package/src/components/hour-group.tsx +0 -124
  70. package/src/components/timeline-band.stories.tsx +0 -206
  71. package/src/components/timeline-band.tsx +0 -247
@@ -1,146 +0,0 @@
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 {
8
- TIMELINE_MOBILE_GUTTER_CLASS,
9
- TimelineHourPuck,
10
- } from "./timeline-band";
11
-
12
- /**
13
- * HourBand — one hour on the timeline's axis, with that hour's events beside it.
14
- *
15
- * The mobile timeline is an app list, not a page: a 58px gutter carrying a
16
- * filled hour chip with a hairline dropping from beneath it, and the hour's
17
- * rows to the right, separated by hairlines of their own. The rows carry no
18
- * timestamp — this band IS the timestamp, which is why `EventCard` has
19
- * `hideWhen` and `EventCompactRow` has `hideTime`.
20
- *
21
- * The chip is the shared `TimelineHourPuck` in its `chip` variant, so the
22
- * desktop spine's station and this gutter's label are one object with two
23
- * looks rather than two objects that drift. The hairline starts BELOW the chip
24
- * rather than running behind it: a chip is a label at the top of its hour, a
25
- * station is a tick on a continuous rail, and the two read differently.
26
- *
27
- * `isLive` tints the chip and the hairline with `--success`; the live label is
28
- * kept for screen readers (the chip variant hides it visually). The playhead —
29
- * the exact current time — is a separate `TimelinePlayhead` rendered between
30
- * bands by the caller, because an hour spans sixty minutes and "now" is one of
31
- * them.
32
- *
33
- * ROWS ARE SEPARATED HERE, NOT IN THE ROW. `EventCompactRow` deliberately
34
- * draws no divider (its container does — see its docblock), so this band wraps
35
- * each child and rules between them. The wrapper, not the child, carries the
36
- * rule, so the row's own negative-margin hover wash is untouched. Children are
37
- * read with `Children.toArray`, which drops `null`/`false` so a conditional
38
- * first row never inherits a rule meant for a second; pass rows as siblings,
39
- * not inside a fragment — a fragment is one child and gets one wrapper.
40
- */
41
-
42
- export type HourBandProps = {
43
- /** The hour, already formatted and localised — "20:00". */
44
- time: string;
45
- /** Marks this as the hour currently in progress. */
46
- isLive?: boolean;
47
- /** Screen-reader text for the live hour, e.g. "Now on". Required to render it. */
48
- liveLabel?: string;
49
- /** The hour's events. Each direct child is one row or card. */
50
- children: React.ReactNode;
51
- /**
52
- * `rows` (default): compact rows, ruled between with a Separator. `cards`:
53
- * poster cards, stacked with a 16px gap and no rules — a card is its own
54
- * frame, and a hairline between two posters reads as a third object.
55
- */
56
- layout?: "rows" | "cards";
57
- className?: string;
58
- };
59
-
60
- export function HourBand({
61
- time,
62
- isLive,
63
- liveLabel,
64
- children,
65
- layout = "rows",
66
- className,
67
- }: HourBandProps) {
68
- const isCards = layout === "cards";
69
- return (
70
- <div className={cn("flex gap-3 pt-2.5", className)}>
71
- <div className={cn("relative shrink-0", TIMELINE_MOBILE_GUTTER_CLASS)}>
72
- <TimelineHourPuck
73
- time={time}
74
- isLive={isLive}
75
- liveLabel={liveLabel}
76
- variant="chip"
77
- />
78
- <Separator
79
- orientation="vertical"
80
- className={cn(
81
- "absolute bottom-[-10px] left-1/2 top-[30px] h-auto -translate-x-1/2",
82
- isLive && "bg-success/30",
83
- )}
84
- />
85
- </div>
86
-
87
- <div
88
- className={cn("flex min-w-0 flex-1 flex-col", isCards && "gap-4 pb-2")}
89
- >
90
- {React.Children.toArray(children).map((child, index) => (
91
- <div key={index}>
92
- {!isCards && index > 0 && <Separator />}
93
- {child}
94
- </div>
95
- ))}
96
- </div>
97
- </div>
98
- );
99
- }
100
-
101
- export type HourBandSkeletonProps = {
102
- /** Rows to reserve space for. */
103
- cardCount?: number;
104
- className?: string;
105
- };
106
-
107
- /**
108
- * The mobile timeline's loading state, mirroring `HourBand` exactly — same
109
- * gutter, same hairline, same row rhythm — so nothing jumps when data lands.
110
- *
111
- * The chip is blocked out at its real size rather than omitted: it is a fixed
112
- * 58x24 lozenge, so a grey one reads as "a time goes here" without inventing
113
- * a time. Row footprints mirror `EventCompactRowSkeleton`.
114
- */
115
- export function HourBandSkeleton({
116
- cardCount = 2,
117
- className,
118
- }: HourBandSkeletonProps) {
119
- return (
120
- <div className={cn("flex gap-3 pt-2.5", className)} aria-hidden="true">
121
- <div className={cn("relative shrink-0", TIMELINE_MOBILE_GUTTER_CLASS)}>
122
- <Skeleton className="h-6 w-[58px] rounded-[7px]" />
123
- <Separator
124
- orientation="vertical"
125
- className="absolute bottom-[-10px] left-1/2 top-[30px] h-auto -translate-x-1/2"
126
- />
127
- </div>
128
-
129
- <div className="flex min-w-0 flex-1 flex-col">
130
- {Array.from({ length: cardCount }).map((_, index) => (
131
- <div key={index}>
132
- {index > 0 && <Separator />}
133
- <div className="flex gap-3 py-[11px]">
134
- <Skeleton className="aspect-[4/5] w-16 shrink-0 rounded-[10px]" />
135
- <div className="min-w-0 flex-1">
136
- <Skeleton className="h-[14px] w-3/4" />
137
- <Skeleton className="mt-1.5 h-[11.5px] w-1/2" />
138
- <Skeleton className="mt-2 h-[22px] w-16 rounded-full" />
139
- </div>
140
- </div>
141
- </div>
142
- ))}
143
- </div>
144
- </div>
145
- );
146
- }
@@ -1,100 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import { HourGroup } from "./hour-group";
3
- import { EventCompactRow } from "./event-compact-row";
4
- import type { EventCardLabels, EventFormatters, EventPreview } from "./event-types";
5
-
6
- const labels: EventCardLabels = { free: "Free", liveNow: "Live now" };
7
-
8
- const formatters: EventFormatters = {
9
- time: (iso) => new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }),
10
- day: () => "Thu, Sep 3",
11
- price: (amount) => `${amount} RON`,
12
- };
13
-
14
- const event = (id: string, title: string, place: string, startsAt: string, fee?: number): EventPreview => ({
15
- id,
16
- title,
17
- location: place,
18
- place_name: place,
19
- starts_at: startsAt,
20
- entry_fee: fee,
21
- });
22
-
23
- const meta: Meta<typeof HourGroup> = {
24
- title: "Events/HourGroup",
25
- component: HourGroup,
26
- tags: ["autodocs"],
27
- args: { time: "21:00", countLabel: "3 events" },
28
- decorators: [
29
- (Story) => (
30
- <div className="w-[420px]">
31
- <Story />
32
- </div>
33
- ),
34
- ],
35
- parameters: {
36
- docs: {
37
- description: {
38
- component:
39
- "An hour heading inside `<TimelineBand>`'s content column — a time, a rule, and a count — with that hour's rows beneath and an optional trailing note.",
40
- },
41
- },
42
- },
43
- };
44
-
45
- export default meta;
46
- type Story = StoryObj<typeof HourGroup>;
47
-
48
- export const Default: Story = {
49
- args: {
50
- children: (
51
- <>
52
- <EventCompactRow
53
- event={event("1", "Urban Sounds at Caro", "Caro", "2026-09-03T21:00:00", 0)}
54
- labels={labels}
55
- formatters={formatters}
56
- />
57
- <EventCompactRow
58
- event={event("2", "Quiz Night", "Flying Circus", "2026-09-03T21:30:00")}
59
- labels={labels}
60
- formatters={formatters}
61
- />
62
- </>
63
- ),
64
- },
65
- };
66
-
67
- /** A trailing note after the rows, e.g. summarising overflow. */
68
- export const WithNote: Story = {
69
- args: {
70
- countLabel: "5 events",
71
- note: "3 more not shown.",
72
- children: (
73
- <EventCompactRow
74
- event={event("3", "COJO", "MATTER", "2026-09-03T21:00:00", 40)}
75
- labels={labels}
76
- formatters={formatters}
77
- />
78
- ),
79
- },
80
- };
81
-
82
- /** No rows — a quiet hour still gets its heading. */
83
- export const Empty: Story = {
84
- args: { countLabel: "0 events" },
85
- };
86
-
87
- /** The mobile variant — a half-step smaller, matching `HourBand`'s own top offset. */
88
- export const Small: Story = {
89
- args: {
90
- size: "sm",
91
- children: (
92
- <EventCompactRow
93
- size="sm"
94
- event={event("4", "Quiz Night", "Flying Circus", "2026-09-03T21:00:00")}
95
- labels={labels}
96
- formatters={formatters}
97
- />
98
- ),
99
- },
100
- };
@@ -1,124 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { cn } from "../lib/utils";
5
- import { Separator } from "./separator";
6
- import { TimelineHourPuck } from "./timeline-band";
7
-
8
- /**
9
- * HourGroup — an hour heading inside `<TimelineBand>`'s content column: a
10
- * time, a rule, and a count, with that hour's rows beneath and an optional
11
- * trailing note (e.g. "3 more not shown").
12
- *
13
- * ── `spine` — the desktop default ────────────────────────────────────────
14
- * Inside a `<TimelineBand>`, pass `spine` and the hour renders as a
15
- * `TimelineHourPuck` out ON the axis, centred on this heading's own rule,
16
- * instead of as inline text at the head of the content column. Every hour of
17
- * the day then reads as a station on the same rail, which is the entire point
18
- * of the rail; the band itself no longer draws any hour, so there is nothing
19
- * left to collide with.
20
- *
21
- * The offset is `calc(var(--timeline-spine) * -0.5)` — half the gutter, back
22
- * from the content column's left edge, which lands on the hairline. Written
23
- * with `*-0.5` rather than `/2` because a slash inside a Tailwind arbitrary
24
- * value collides with the opacity-modifier parse. The `w-0` wrapper plus the
25
- * puck's own `justify-center` is what centres it, so no `-translate-x-1/2` is
26
- * needed and the live label centres on the same line for free.
27
- *
28
- * `spine` implies the inline time is suppressed — no caller should have to
29
- * pass `spine hideTime` to avoid printing the hour twice.
30
- *
31
- * Keep the rule and the count. The rule is the only thing tying a puck to the
32
- * rows it labels across the empty gutter; without it the puck reads as a
33
- * sticker rather than a station on a track. The count carries information
34
- * present nowhere else and terminates the rule so it doesn't dissolve.
35
- *
36
- * Where a group's rows already carry their own timestamps, `hideTime` drops the
37
- * time and keeps the rule and count, so the hour is stated exactly once.
38
- *
39
- * `size="sm"` is the mobile variant, shrinking the type a half-step and
40
- * matching `<HourBand>`'s own top offset (18px) rather than the desktop
41
- * default (22px).
42
- */
43
-
44
- export type HourGroupProps = {
45
- /** The hour, already formatted and localised — "20:00". */
46
- time: string;
47
- /**
48
- * Render the hour as a puck out on `<TimelineBand>`'s spine instead of as
49
- * inline text. The desktop timeline's default; implies `hideTime`.
50
- */
51
- spine?: boolean;
52
- /** Marks this hour as the one currently in progress. `spine` only. */
53
- isLive?: boolean;
54
- /** Shown under the puck when live, e.g. "NOW". `spine` only. */
55
- liveLabel?: string;
56
- /**
57
- * Drop the leading time, keeping the rule and count. For groups whose rows
58
- * state their own time.
59
- */
60
- hideTime?: boolean;
61
- /** Caller-composed, e.g. "2 events" — mirrors `EventDayHeader`'s `meta`. */
62
- countLabel: string;
63
- /** Trailing note shown after the rows, e.g. "3 more not shown". */
64
- note?: string;
65
- /** The hour's rows. */
66
- children?: React.ReactNode;
67
- size?: "sm" | "default";
68
- className?: string;
69
- };
70
-
71
- export function HourGroup({
72
- time,
73
- spine,
74
- isLive,
75
- liveLabel,
76
- hideTime,
77
- countLabel,
78
- note,
79
- children,
80
- size = "default",
81
- className,
82
- }: HourGroupProps) {
83
- const isSm = size === "sm";
84
-
85
- return (
86
- // The group's top padding lives HERE, not on the heading row, and that is
87
- // load-bearing for `spine`: `top-1/2` resolves against the heading row's
88
- // PADDING box while `items-center` centres the rule in its CONTENT box, so
89
- // with `pt-[22px]` on the row the puck landed 19.25px down and the rule at
90
- // 30.25px — 11px apart, the puck visibly riding above the line it is
91
- // supposed to sit on. Unpadded, both resolve against the same box and the
92
- // puck's centre, the rule and the spine hairline are one line.
93
- <div className={cn(isSm ? "pt-[18px]" : "pt-[22px]", className)}>
94
- <div className={cn("flex items-center gap-2.5", spine && "relative")}>
95
- {spine && (
96
- <TimelineHourPuck
97
- time={time}
98
- isLive={isLive}
99
- liveLabel={liveLabel}
100
- className="absolute left-[calc(var(--timeline-spine)*-0.5)] top-1/2 w-0 -translate-y-1/2"
101
- />
102
- )}
103
- {!hideTime && !spine && (
104
- <span
105
- className={cn(
106
- "font-mono text-muted-foreground tabular-nums",
107
- isSm ? "text-[11.5px]" : "text-xs"
108
- )}
109
- >
110
- {time}
111
- </span>
112
- )}
113
- <Separator className="flex-1" />
114
- <span className={cn("text-muted-foreground/70", isSm ? "text-[10.5px]" : "text-[11px]")}>
115
- {countLabel}
116
- </span>
117
- </div>
118
-
119
- {children}
120
-
121
- {note && <p className="pt-[9px] text-[11.5px] text-muted-foreground">{note}</p>}
122
- </div>
123
- );
124
- }
@@ -1,206 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import { ChevronDown, TimerOff } from "lucide-react";
3
- import { TimelineBand, TimelineBandSkeleton } from "./timeline-band";
4
- import { HourGroup } from "./hour-group";
5
- import { EventCompactRow } from "./event-compact-row";
6
- import { EventCard } from "./event-card";
7
- import type { EventCardLabels, EventFormatters, EventPreview } from "./event-types";
8
-
9
- const labels: EventCardLabels = { free: "Free", liveNow: "Live now" };
10
-
11
- const formatters: EventFormatters = {
12
- time: () => "20:00",
13
- day: () => "Thu, Sep 3",
14
- price: (amount) => `${amount} RON`,
15
- };
16
-
17
- const event = (id: string, title: string, place: string, fee?: number): EventPreview => ({
18
- id,
19
- title,
20
- location: place,
21
- place_name: place,
22
- starts_at: "2026-09-03T20:00:00",
23
- entry_fee: fee,
24
- });
25
-
26
- const meta: Meta<typeof TimelineBand> = {
27
- title: "Events/TimelineBand",
28
- component: TimelineBand,
29
- tags: ["autodocs"],
30
- decorators: [
31
- (Story) => (
32
- <div className="w-[1180px] bg-background">
33
- <Story />
34
- </div>
35
- ),
36
- ],
37
- parameters: {
38
- docs: {
39
- description: {
40
- component:
41
- "The desktop timeline's three-column row — lane, spine, content — and the desktop counterpart of `<HourBand>`. The band draws the geometry and the hairline only: every hour on the axis is a puck emitted by its own `<HourGroup spine>`, so an hour appears exactly once and always on the spine. Pass an em dash as a group's `time` for a quiet day, so it still reads as a point on the axis rather than a gap in it.",
42
- },
43
- },
44
- },
45
- };
46
-
47
- export default meta;
48
- type Story = StoryObj<typeof TimelineBand>;
49
-
50
- /**
51
- * THE FULL COMPOSITION, as the source design builds it. Density falls off left
52
- * to right and top to bottom: one feature poster in the lane, then a grid of
53
- * cards for the band's own hour, then compact rows for each later hour.
54
- *
55
- * EVERY hour is a station: each `HourGroup` carries `spine`, so 20:00, 21:00
56
- * and 22:00 all sit on the rail in the same grammar, each centred on its own
57
- * heading rule. The opening group passes `hideTime` implicitly via `spine` and
58
- * holds cards rather than rows — that is what the density ladder is, not a
59
- * difference in how the hour is drawn.
60
- */
61
- export const FullBand: Story = {
62
- args: {
63
- lane: (
64
- <div className="flex flex-col gap-5 pt-3.5">
65
- <EventCard
66
- event={event("f", "Psihotrop — From the Beginning to Infinity", "Atelier Café", 50)}
67
- labels={labels}
68
- formatters={formatters}
69
- emphasis
70
- hideWhen
71
- tagLabel="Live music"
72
- />
73
- <button
74
- type="button"
75
- className="inline-flex h-9 w-max items-center gap-1.5 rounded-full border border-border bg-card px-4 text-[13px] font-semibold"
76
- >
77
- <TimerOff className="h-3.5 w-3.5" aria-hidden />7 earlier events, 14:00 — 19:00
78
- <ChevronDown className="h-3.5 w-3.5" aria-hidden />
79
- </button>
80
- </div>
81
- ),
82
- children: (
83
- <>
84
- {/* The opening hour: its own station, holding cards rather than rows. */}
85
- <HourGroup spine time="20:00 — 20:30" countLabel="4 events">
86
- <div className="grid grid-cols-3 gap-5 pt-3.5">
87
- <EventCard
88
- event={event("c1", "Quiz Night", "Flying Circus")}
89
- labels={labels}
90
- formatters={formatters}
91
- hideWhen
92
- />
93
- <EventCard
94
- event={event("c2", "Beer Battle", "Irish Music Pub", 0)}
95
- labels={labels}
96
- formatters={formatters}
97
- hideWhen
98
- />
99
- <EventCard
100
- event={event("c3", "COJO", "MATTER", 40)}
101
- labels={labels}
102
- formatters={formatters}
103
- hideWhen
104
- />
105
- </div>
106
- </HourGroup>
107
-
108
- {/* Later hours thin out into rows — same station, denser content. */}
109
- <HourGroup spine time="21:00" countLabel="2 events">
110
- <EventCompactRow
111
- event={event("r1", "Karaoke Night", "VIBE OffSeason")}
112
- labels={labels}
113
- formatters={formatters}
114
- />
115
- <EventCompactRow
116
- event={event("r2", "Urban Sounds at Caro", "Caro", 0)}
117
- labels={labels}
118
- formatters={formatters}
119
- />
120
- </HourGroup>
121
-
122
- <HourGroup spine time="22:00 — 23:00" countLabel="2 events">
123
- <EventCompactRow
124
- event={event("r3", "Schoolotheque Afterparty", "Flying Circus", 25)}
125
- labels={labels}
126
- formatters={formatters}
127
- />
128
- <EventCompactRow
129
- event={event("r4", "Összerázó Party Vol. I", "After Eight", 30)}
130
- labels={labels}
131
- formatters={formatters}
132
- />
133
- </HourGroup>
134
- </>
135
- ),
136
- },
137
- };
138
-
139
- /** A minimal band — one poster, one group of rows. */
140
- export const Default: Story = {
141
- args: {
142
- lane: (
143
- <EventCard
144
- event={event("1", "Karaoke Night", "VIBE OffSeason")}
145
- labels={labels}
146
- formatters={formatters}
147
- posterAspect="4/3"
148
- hideWhen
149
- />
150
- ),
151
- children: (
152
- <HourGroup spine time="21:00" countLabel="3 events">
153
- <EventCompactRow
154
- event={event("2", "Urban Sounds at Caro", "Caro", 0)}
155
- labels={labels}
156
- formatters={formatters}
157
- />
158
- <EventCompactRow
159
- event={event("3", "Quiz Night", "Flying Circus")}
160
- labels={labels}
161
- formatters={formatters}
162
- />
163
- </HourGroup>
164
- ),
165
- },
166
- };
167
-
168
- /** The live hour: green-bordered puck, a slow ring pulse, and a NOW ON label
169
- * beneath it. This is the ONLY live marker on the surface — there is no
170
- * separate playhead printing a second, different time beside it. */
171
- export const Live: Story = {
172
- args: {
173
- lane: (
174
- <EventCard
175
- event={event("4", "Psihotrop — From the Beginning to Infinity", "Atelier Café", 50)}
176
- labels={labels}
177
- formatters={formatters}
178
- posterAspect="4/3"
179
- hideWhen
180
- liveStatus="Live now · ends around 23:00"
181
- />
182
- ),
183
- children: (
184
- <HourGroup spine isLive liveLabel="NOW ON" time="20:00" countLabel="1 event">
185
- <EventCompactRow
186
- event={event("5", "Charity Concert & Afterparty", "Flying Circus", 30)}
187
- labels={labels}
188
- formatters={formatters}
189
- />
190
- </HourGroup>
191
- ),
192
- },
193
- };
194
-
195
- /** A quiet hour: an em dash on the axis, no lane content. */
196
- export const EmptyHour: Story = {
197
- args: {
198
- lane: null,
199
- children: <HourGroup spine time="—" countLabel="0 events" note="Nothing listed yet." />,
200
- },
201
- };
202
-
203
- /** Holds the lane/spine/content geometry so nothing jumps when data lands. */
204
- export const Skeleton: Story = {
205
- render: () => <TimelineBandSkeleton />,
206
- };