@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.
- package/dist/{chunk-WYTJR2YU.js → chunk-5CPBSNWQ.js} +6 -1
- package/dist/chunk-BRXI6EDO.js +84 -0
- package/dist/{chunk-54FQQZZJ.js → chunk-EQYB4Q77.js} +1 -1
- package/dist/chunk-QRSIJC4X.js +156 -0
- package/dist/components/concept-compare-rail.js +2 -2
- package/dist/components/concept-compare.js +2 -2
- package/dist/components/event-card.d.ts +22 -4
- package/dist/components/event-card.js +95 -50
- package/dist/components/event-day-header.d.ts +5 -3
- package/dist/components/event-day-header.js +12 -4
- package/dist/components/event-hub-hero.d.ts +12 -2
- package/dist/components/event-hub-hero.js +11 -5
- package/dist/components/event-tonight-strip.d.ts +50 -0
- package/dist/components/event-tonight-strip.js +67 -0
- package/dist/components/menu-family-block.js +4 -3
- package/dist/components/menu-hub-hero.d.ts +7 -1
- package/dist/components/menu-hub-hero.js +2 -1
- package/dist/components/poster-stack.d.ts +36 -3
- package/dist/components/poster-stack.js +87 -48
- package/dist/components/snap-rail.d.ts +2 -2
- package/dist/components/snap-rail.js +1 -1
- package/dist/components/timeline-day.d.ts +54 -0
- package/dist/components/timeline-day.js +65 -0
- package/dist/components/timeline-hour-puck.d.ts +47 -0
- package/dist/components/timeline-hour-puck.js +3 -0
- package/dist/components/timeline-hour.d.ts +96 -0
- package/dist/components/{timeline-band.js → timeline-hour.js} +2 -1
- package/dist/components/timeline-playhead.js +1 -3
- package/package.json +1 -1
- package/src/components/event-card.tsx +70 -13
- package/src/components/event-day-header.tsx +19 -7
- package/src/components/event-hub-hero.tsx +25 -3
- package/src/components/event-tonight-strip.stories.tsx +78 -0
- package/src/components/event-tonight-strip.test.tsx +49 -0
- package/src/components/event-tonight-strip.tsx +134 -0
- package/src/components/menu-family-block.tsx +13 -6
- package/src/components/menu-hub-hero.test.tsx +13 -0
- package/src/components/menu-hub-hero.tsx +9 -2
- package/src/components/poster-stack.stories.tsx +43 -1
- package/src/components/poster-stack.tsx +104 -19
- package/src/components/snap-rail.tsx +6 -1
- package/src/components/timeline-day.stories.tsx +201 -0
- package/src/components/timeline-day.test.tsx +78 -0
- package/src/components/timeline-day.tsx +114 -0
- package/src/components/timeline-hour-puck.tsx +142 -0
- package/src/components/timeline-hour.tsx +266 -0
- package/src/components/timeline-playhead.tsx +1 -1
- package/dist/chunk-NSNEANOQ.js +0 -104
- package/dist/components/hour-band.d.ts +0 -66
- package/dist/components/hour-band.js +0 -81
- package/dist/components/hour-group.d.ts +0 -67
- package/dist/components/hour-group.js +0 -59
- package/dist/components/timeline-band.d.ts +0 -64
- package/src/components/hour-band.stories.tsx +0 -182
- package/src/components/hour-band.tsx +0 -146
- package/src/components/hour-group.stories.tsx +0 -100
- package/src/components/hour-group.tsx +0 -124
- package/src/components/timeline-band.stories.tsx +0 -206
- package/src/components/timeline-band.tsx +0 -247
|
@@ -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
|
-
};
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { Radio } from "lucide-react";
|
|
5
|
-
import { cn } from "../lib/utils";
|
|
6
|
-
import { Separator } from "./separator";
|
|
7
|
-
import { Skeleton } from "./skeleton";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* TimelineBand — the desktop timeline's three-column row: lane │ spine │ content.
|
|
11
|
-
*
|
|
12
|
-
* Desktop counterpart of `<HourBand>`. Where the mobile timeline collapses the
|
|
13
|
-
* axis into a 52px gutter beside full-width cards, the desktop layout gives the
|
|
14
|
-
* poster its own lane (`--timeline-lane`), a dedicated spine
|
|
15
|
-
* (`--timeline-spine`) carrying the hairline, and a separate content column for
|
|
16
|
-
* whatever follows.
|
|
17
|
-
*
|
|
18
|
-
* ── The band does not draw hours ──────────────────────────────────────────
|
|
19
|
-
* IT USED TO, and only for the hour it opened on. Every LATER hour was an
|
|
20
|
-
* inline `HourGroup` heading in the content column instead, so one concept was
|
|
21
|
-
* drawn in two grammars 48px apart — a bordered puck ON the axis at the top of
|
|
22
|
-
* the day, bare muted text OFF it for the rest of it. An axis whose ticks are
|
|
23
|
-
* labelled once is not an axis, and that is exactly how it read: the later
|
|
24
|
-
* hours were harder to follow than the first.
|
|
25
|
-
*
|
|
26
|
-
* Now every hour is a station and `HourGroup` emits its own, via
|
|
27
|
-
* `TimelineHourPuck` below. The band keeps the geometry and the hairline and
|
|
28
|
-
* nothing else — which is also why `puckTop` is gone: each puck centres itself
|
|
29
|
-
* on its own heading rule, so there is no magic offset left for a caller to
|
|
30
|
-
* tune against a lane height.
|
|
31
|
-
*
|
|
32
|
-
* The invariant the old docblock was reaching for still holds, and states more
|
|
33
|
-
* simply now: AN HOUR APPEARS EXACTLY ONCE, AND ALWAYS ON THE SPINE.
|
|
34
|
-
*
|
|
35
|
-
* HOW THE CONTENT COLUMN IS MEANT TO BE FILLED — the source design composes it
|
|
36
|
-
* in tiers, and getting this wrong is what makes the band look flat:
|
|
37
|
-
*
|
|
38
|
-
* lane one feature card (an `EventCard` with `emphasis`), plus any
|
|
39
|
-
* "N earlier events" collapse control beneath it.
|
|
40
|
-
* content an `<HourGroup spine>` per hour — the opening one holding a grid
|
|
41
|
-
* of `EventCard`s, later ones holding the denser
|
|
42
|
-
* `<EventCompactRow>` as the night thins out.
|
|
43
|
-
*
|
|
44
|
-
* So the density falls off across the row: one poster, then a few cards, then
|
|
45
|
-
* rows.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Ring pulse on the live puck — inherited from an earlier `TimelinePlayhead`,
|
|
50
|
-
* which carried this surface's only motion. Kept at its 2.4s `ease-in-out` and
|
|
51
|
-
* its reduced-motion kill switch, but BOX-SHADOW ONLY: a `transform: scale`
|
|
52
|
-
* would make the hairline visibly breathe here, because this puck sits ON the
|
|
53
|
-
* rail. (Today's `timeline-playhead.tsx` is static and sits between bands.)
|
|
54
|
-
*/
|
|
55
|
-
const LIVE_PUCK_PULSE_CLASS = "bm-timeline-live-puck-pulse";
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The mobile axis gutter, shared by `HourBand` and `TimelinePlayhead` so the
|
|
59
|
-
* hour chips and the current-time label sit in one column. 58px is the width
|
|
60
|
-
* of a five-glyph 24-hour label ("20:00") in the chip's mono type plus its
|
|
61
|
-
* padding; a 12-hour locale's "09:00 AM" overflows it, which is one of the
|
|
62
|
-
* reasons the events surfaces format 24-hour everywhere.
|
|
63
|
-
*/
|
|
64
|
-
export const TIMELINE_MOBILE_GUTTER_CLASS = "w-[58px]";
|
|
65
|
-
|
|
66
|
-
export type TimelineHourPuckProps = {
|
|
67
|
-
/** The hour, already formatted and localised — or an em dash for a quiet day. */
|
|
68
|
-
time: string;
|
|
69
|
-
/** Marks this as the hour currently in progress. */
|
|
70
|
-
isLive?: boolean;
|
|
71
|
-
/** Shown under the puck when live, e.g. "NOW ON". Required to render it. */
|
|
72
|
-
liveLabel?: string;
|
|
73
|
-
/**
|
|
74
|
-
* `station` (default): the bordered puck that sits ON the hairline, for the
|
|
75
|
-
* desktop spine. `chip`: a filled, fixed-width lozenge with the hairline
|
|
76
|
-
* starting BELOW it, for the mobile gutter — an app's time label, not a
|
|
77
|
-
* station on a rail. In `chip` the live label is visually hidden: the tinted
|
|
78
|
-
* chip plus a small on-air glyph are the visible signal (the glyph so the
|
|
79
|
-
* state never rests on colour alone once the pulse is reduced away) and the
|
|
80
|
-
* label stays for screen readers only.
|
|
81
|
-
*/
|
|
82
|
-
variant?: "station" | "chip";
|
|
83
|
-
className?: string;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* The station on the timeline's axis — ONE object, positioned by its host:
|
|
88
|
-
* `HourGroup` centres it on its own heading rule out in the desktop spine,
|
|
89
|
-
* `HourBand` pins it to the top of the mobile gutter.
|
|
90
|
-
*
|
|
91
|
-
* Extracted because it was hand-rolled twice and the copies had already
|
|
92
|
-
* drifted — this one bordered and `rounded-[7px] px-[7px]`, `hour-band.tsx`'s
|
|
93
|
-
* borderless and `rounded-md px-1.5`. The `bg-background` fill punches through
|
|
94
|
-
* the hairline so the rail reads as continuous behind the label rather than
|
|
95
|
-
* butting into it.
|
|
96
|
-
*
|
|
97
|
-
* The live label is absolutely positioned rather than a flex sibling: hosts
|
|
98
|
-
* centre this puck with `-translate-y-1/2`, and a two-item column would centre
|
|
99
|
-
* the STACK, lifting the puck off the line it exists to sit on.
|
|
100
|
-
*/
|
|
101
|
-
export function TimelineHourPuck({
|
|
102
|
-
time,
|
|
103
|
-
isLive,
|
|
104
|
-
liveLabel,
|
|
105
|
-
variant = "station",
|
|
106
|
-
className,
|
|
107
|
-
}: TimelineHourPuckProps) {
|
|
108
|
-
const isChip = variant === "chip";
|
|
109
|
-
|
|
110
|
-
return (
|
|
111
|
-
<span className={cn("relative flex justify-center", className)}>
|
|
112
|
-
{isLive && (
|
|
113
|
-
<style>{`
|
|
114
|
-
@keyframes ${LIVE_PUCK_PULSE_CLASS} {
|
|
115
|
-
0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklch, var(--success) 40%, transparent); }
|
|
116
|
-
50% { box-shadow: 0 0 0 4px color-mix(in oklch, var(--success) 0%, transparent); }
|
|
117
|
-
}
|
|
118
|
-
.${LIVE_PUCK_PULSE_CLASS} {
|
|
119
|
-
animation: ${LIVE_PUCK_PULSE_CLASS} 2.4s ease-in-out infinite;
|
|
120
|
-
}
|
|
121
|
-
@media (prefers-reduced-motion: reduce) {
|
|
122
|
-
.${LIVE_PUCK_PULSE_CLASS} { animation: none; }
|
|
123
|
-
}
|
|
124
|
-
`}</style>
|
|
125
|
-
)}
|
|
126
|
-
<span
|
|
127
|
-
className={cn(
|
|
128
|
-
"whitespace-nowrap font-mono tabular-nums",
|
|
129
|
-
isChip
|
|
130
|
-
? cn(
|
|
131
|
-
"inline-flex h-6 min-w-[58px] items-center justify-center gap-1 rounded-[7px] px-1 text-[12px] font-medium",
|
|
132
|
-
isLive ? cn("bg-success/10 text-success", LIVE_PUCK_PULSE_CLASS) : "bg-muted text-foreground"
|
|
133
|
-
)
|
|
134
|
-
: cn(
|
|
135
|
-
"rounded-[7px] border bg-background px-[7px] py-[3px] text-[12.5px] font-medium",
|
|
136
|
-
isLive
|
|
137
|
-
? cn("border-success/40 text-success", LIVE_PUCK_PULSE_CLASS)
|
|
138
|
-
: "border-border text-foreground"
|
|
139
|
-
)
|
|
140
|
-
)}
|
|
141
|
-
>
|
|
142
|
-
{isChip && isLive && <Radio className="h-2.5 w-2.5 shrink-0" aria-hidden />}
|
|
143
|
-
{time}
|
|
144
|
-
</span>
|
|
145
|
-
{isLive && !!liveLabel && (
|
|
146
|
-
<span
|
|
147
|
-
className={cn(
|
|
148
|
-
isChip
|
|
149
|
-
? "sr-only"
|
|
150
|
-
: cn(
|
|
151
|
-
"absolute top-[calc(100%+6px)] whitespace-nowrap bg-background px-1",
|
|
152
|
-
"text-[9.5px] font-extrabold tracking-[0.08em] text-success"
|
|
153
|
-
)
|
|
154
|
-
)}
|
|
155
|
-
>
|
|
156
|
-
{liveLabel}
|
|
157
|
-
</span>
|
|
158
|
-
)}
|
|
159
|
-
</span>
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export type TimelineBandProps = {
|
|
164
|
-
/** The lane column — usually a poster card, or empty for a quiet hour. */
|
|
165
|
-
lane: React.ReactNode;
|
|
166
|
-
/** The content column, normally one `<HourGroup spine>` per hour. */
|
|
167
|
-
children: React.ReactNode;
|
|
168
|
-
className?: string;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export function TimelineBand({ lane, children, className }: TimelineBandProps) {
|
|
172
|
-
return (
|
|
173
|
-
<div className={cn("flex items-stretch", className)}>
|
|
174
|
-
<div className="w-[var(--timeline-lane)] shrink-0">{lane}</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">{children}</div>
|
|
181
|
-
</div>
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* A fixed-size shimmer block. `radiusPx` only needs to be passed for the
|
|
187
|
-
* poster-sized blocks (12, matching `rounded-xl`) — every thinner text-line
|
|
188
|
-
* height here (13–18px) is less than twice that radius, so the browser clamps
|
|
189
|
-
* it to a pill automatically even left at the `Skeleton` default.
|
|
190
|
-
*/
|
|
191
|
-
function sk(width: string, height: number, radiusPx = 12) {
|
|
192
|
-
return (
|
|
193
|
-
<Skeleton
|
|
194
|
-
style={{ width, height: `${height}px`, borderRadius: `${radiusPx}px` }}
|
|
195
|
-
/>
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export type TimelineBandSkeletonProps = {
|
|
200
|
-
className?: string;
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Holds the lane/spine/content geometry so nothing jumps when data lands.
|
|
205
|
-
* The spine renders its hairline with no puck — there's no hour to show yet.
|
|
206
|
-
*/
|
|
207
|
-
export function TimelineBandSkeleton({ className }: TimelineBandSkeletonProps) {
|
|
208
|
-
return (
|
|
209
|
-
<div className={cn("flex items-stretch", className)} aria-hidden="true">
|
|
210
|
-
<div className="flex w-[var(--timeline-lane)] shrink-0 flex-col gap-3">
|
|
211
|
-
{sk("100%", 297, 12)}
|
|
212
|
-
{sk("70%", 18)}
|
|
213
|
-
{sk("45%", 13)}
|
|
214
|
-
</div>
|
|
215
|
-
|
|
216
|
-
<div className="relative flex w-[var(--timeline-spine)] shrink-0 justify-center self-stretch">
|
|
217
|
-
<Separator orientation="vertical" className="h-full" />
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
<div className="flex min-w-0 flex-1 flex-col gap-4">
|
|
221
|
-
<div className="grid grid-cols-3 gap-[10px]">
|
|
222
|
-
{Array.from({ length: 3 }, (_, index) => (
|
|
223
|
-
<div key={index} className="flex flex-col gap-2">
|
|
224
|
-
{sk("100%", 190, 12)}
|
|
225
|
-
{sk("85%", 15)}
|
|
226
|
-
{sk("55%", 12)}
|
|
227
|
-
</div>
|
|
228
|
-
))}
|
|
229
|
-
</div>
|
|
230
|
-
|
|
231
|
-
<div className="flex flex-col">
|
|
232
|
-
{Array.from({ length: 3 }, (_, index) => (
|
|
233
|
-
<div key={index} className="relative flex items-center gap-[11px] py-2">
|
|
234
|
-
<Separator className="absolute inset-x-0 top-0" />
|
|
235
|
-
|
|
236
|
-
<Skeleton className="h-[34px] w-[34px] shrink-0 rounded-md" />
|
|
237
|
-
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
|
|
238
|
-
{sk("60%", 13)}
|
|
239
|
-
{sk("40%", 11.5)}
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
))}
|
|
243
|
-
</div>
|
|
244
|
-
</div>
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
|
-
}
|