@bearmenu/ui 0.8.16 → 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.
- package/dist/{chunk-WYTJR2YU.js → chunk-5CPBSNWQ.js} +6 -1
- package/dist/{chunk-36KEHCET.js → chunk-5LTILG3L.js} +15 -4
- package/dist/{chunk-P43B3ONY.js → chunk-5XREWOBB.js} +3 -1
- package/dist/{chunk-CLLY6XB3.js → chunk-6VOWT4YR.js} +6 -3
- package/dist/{chunk-S33K4RC5.js → chunk-BKKQ7GCZ.js} +1 -1
- package/dist/{chunk-ZLPWEHZO.js → chunk-EQYB4Q77.js} +2 -2
- package/dist/chunk-LYREUZH6.js +156 -0
- package/dist/chunk-PW247TG2.js +81 -0
- package/dist/chunk-QY66ME5B.js +207 -0
- package/dist/{chunk-T6S2ZBV6.js → chunk-SDWN6L4O.js} +1 -1
- package/dist/{chunk-CNJQUHCU.js → chunk-SWD6CJ35.js} +2 -1
- package/dist/{chunk-HDR7V6IZ.js → chunk-TIQNM4XD.js} +8 -1
- package/dist/components/alert-dialog.js +1 -1
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/badge.js +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/button.js +1 -1
- package/dist/components/calendar.js +1 -1
- package/dist/components/card.d.ts +2 -2
- package/dist/components/carousel.js +1 -1
- package/dist/components/coach-marks.d.ts +63 -0
- package/dist/components/coach-marks.js +162 -0
- package/dist/components/combobox.js +1 -1
- package/dist/components/command.d.ts +15 -15
- package/dist/components/concept-compare-rail.js +3 -3
- package/dist/components/concept-compare.js +3 -3
- package/dist/components/concept-convergence.js +2 -2
- package/dist/components/doorway-tiles.d.ts +11 -0
- package/dist/components/doorway-tiles.js +5 -3
- package/dist/components/empty-state.js +1 -1
- 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-detail-panel.js +2 -2
- package/dist/components/event-door.d.ts +41 -0
- package/dist/components/event-door.js +65 -0
- package/dist/components/event-hub-hero.d.ts +10 -2
- package/dist/components/event-hub-hero.js +11 -5
- package/dist/components/event-index-row.js +1 -1
- package/dist/components/event-list-row-wide.js +2 -2
- package/dist/components/event-list-row.js +1 -1
- package/dist/components/event-tonight-strip.d.ts +50 -0
- package/dist/components/event-tonight-strip.js +67 -0
- package/dist/components/facet-group.d.ts +8 -1
- package/dist/components/facet-group.js +3 -0
- package/dist/components/filter-category-row.js +1 -1
- package/dist/components/hue-chip.js +1 -1
- package/dist/components/item.d.ts +2 -2
- package/dist/components/item.js +4 -1
- package/dist/components/kitchen-dossier.js +1 -1
- package/dist/components/link-button.js +1 -1
- package/dist/components/menu-family-block.d.ts +8 -1
- package/dist/components/menu-family-block.js +101 -68
- package/dist/components/menu-family-card.js +2 -2
- package/dist/components/menu-filter-chips.d.ts +12 -3
- package/dist/components/menu-filter-chips.js +22 -4
- package/dist/components/menu-hub-hero.d.ts +7 -1
- package/dist/components/menu-hub-hero.js +2 -1
- package/dist/components/menu-index-list.js +1 -1
- package/dist/components/menu-item.js +3 -3
- package/dist/components/menu-kitchen-list.d.ts +14 -2
- package/dist/components/menu-kitchen-list.js +1 -1
- package/dist/components/multi-select-combobox.js +2 -2
- package/dist/components/pagination.js +1 -1
- package/dist/components/photo-ground.d.ts +7 -1
- package/dist/components/photo-ground.js +1 -1
- package/dist/components/place-card.js +2 -2
- package/dist/components/place-details-mobile.js +1 -1
- package/dist/components/poster-stack.d.ts +36 -3
- package/dist/components/poster-stack.js +49 -18
- package/dist/components/searchable-select.js +1 -1
- package/dist/components/section-header.d.ts +7 -1
- package/dist/components/section-header.js +11 -7
- package/dist/components/signature-chip.js +2 -2
- package/dist/components/sliding-panels.js +1 -1
- package/dist/components/snap-rail.d.ts +2 -2
- package/dist/components/snap-rail.js +1 -1
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/text.d.ts +3 -3
- 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/dist/components/toggle-group.js +9 -4
- package/dist/components/toggle.d.ts +1 -1
- package/dist/components/toggle.js +1 -1
- package/dist/components/top-ranking-entry.js +3 -3
- package/package.json +1 -1
- package/src/components/badge.stories.tsx +31 -1
- package/src/components/badge.tsx +2 -0
- package/src/components/button.stories.tsx +5 -4
- package/src/components/button.test.tsx +11 -1
- package/src/components/button.tsx +16 -5
- package/src/components/coach-marks.stories.tsx +79 -0
- package/src/components/coach-marks.test.tsx +140 -0
- package/src/components/coach-marks.tsx +252 -0
- package/src/components/doorway-tiles.stories.tsx +34 -0
- package/src/components/doorway-tiles.test.tsx +27 -0
- package/src/components/doorway-tiles.tsx +30 -2
- package/src/components/event-card.tsx +70 -13
- package/src/components/event-day-header.tsx +19 -7
- package/src/components/event-door.stories.tsx +88 -0
- package/src/components/event-door.tsx +97 -0
- package/src/components/event-hub-hero.tsx +21 -3
- package/src/components/event-tonight-strip.stories.tsx +78 -0
- package/src/components/event-tonight-strip.test.tsx +59 -0
- package/src/components/event-tonight-strip.tsx +134 -0
- package/src/components/facet-group.tsx +9 -0
- package/src/components/hue-chip.test.tsx +6 -3
- package/src/components/hue-chip.tsx +5 -2
- package/src/components/item.tsx +4 -1
- package/src/components/menu-family-block.stories.tsx +12 -0
- package/src/components/menu-family-block.tsx +53 -20
- package/src/components/menu-filter-chips.tsx +32 -4
- package/src/components/menu-hub-hero.test.tsx +13 -0
- package/src/components/menu-hub-hero.tsx +9 -2
- package/src/components/menu-kitchen-list.tsx +45 -6
- package/src/components/photo-ground.tsx +7 -0
- package/src/components/poster-stack.stories.tsx +43 -1
- package/src/components/poster-stack.tsx +98 -19
- package/src/components/section-header.test.tsx +15 -0
- package/src/components/section-header.tsx +26 -12
- 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 +139 -0
- package/src/components/timeline-hour.tsx +266 -0
- package/src/components/timeline-playhead.tsx +1 -1
- package/src/components/toggle-group.stories.tsx +37 -1
- package/src/components/toggle-group.tsx +11 -6
- package/src/components/toggle.tsx +8 -0
- package/dist/chunk-NSNEANOQ.js +0 -104
- package/dist/chunk-SXQZ7DLH.js +0 -167
- 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,182 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
-
import { HourBand, HourBandSkeleton } from "./hour-band";
|
|
3
|
-
import { EventCard } from "./event-card";
|
|
4
|
-
import { EventCompactRow } from "./event-compact-row";
|
|
5
|
-
import type { EventCardLabels, EventFormatters, EventPreview } from "./event-types";
|
|
6
|
-
|
|
7
|
-
const labels: EventCardLabels = { free: "Free", liveNow: "Live now" };
|
|
8
|
-
|
|
9
|
-
const formatters: EventFormatters = {
|
|
10
|
-
time: () => "20:00",
|
|
11
|
-
day: () => "Thu, Sep 3",
|
|
12
|
-
price: (amount) => `${amount} RON`,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const event = (id: string, title: string, place: string, fee?: number): EventPreview => ({
|
|
16
|
-
id,
|
|
17
|
-
title,
|
|
18
|
-
location: place,
|
|
19
|
-
place_name: place,
|
|
20
|
-
starts_at: "2026-09-03T20:00:00",
|
|
21
|
-
entry_fee: fee,
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const meta: Meta<typeof HourBand> = {
|
|
25
|
-
title: "Events/HourBand",
|
|
26
|
-
component: HourBand,
|
|
27
|
-
tags: ["autodocs"],
|
|
28
|
-
decorators: [
|
|
29
|
-
(Story) => (
|
|
30
|
-
<div className="w-[390px] bg-background px-4">
|
|
31
|
-
<Story />
|
|
32
|
-
</div>
|
|
33
|
-
),
|
|
34
|
-
],
|
|
35
|
-
parameters: {
|
|
36
|
-
docs: {
|
|
37
|
-
description: {
|
|
38
|
-
component:
|
|
39
|
-
"One hour of the mobile timeline: a 58px gutter with a filled hour chip and a hairline dropping from beneath it, and that hour's rows beside it, ruled between. The rows carry no timestamp of their own — the band is the timestamp, which is why cards render with `hideWhen` and compact rows with `hideTime`.",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export default meta;
|
|
46
|
-
type Story = StoryObj<typeof HourBand>;
|
|
47
|
-
|
|
48
|
-
export const Default: Story = {
|
|
49
|
-
args: {
|
|
50
|
-
time: "21:00",
|
|
51
|
-
children: (
|
|
52
|
-
<>
|
|
53
|
-
<EventCard
|
|
54
|
-
event={event("1", "Karaoke Night", "VIBE OffSeason")}
|
|
55
|
-
labels={labels}
|
|
56
|
-
formatters={formatters}
|
|
57
|
-
hideWhen
|
|
58
|
-
/>
|
|
59
|
-
<EventCard
|
|
60
|
-
event={event("2", "Urban Sounds at Caro", "Caro", 0)}
|
|
61
|
-
labels={labels}
|
|
62
|
-
formatters={formatters}
|
|
63
|
-
hideWhen
|
|
64
|
-
/>
|
|
65
|
-
</>
|
|
66
|
-
),
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
/** The live hour: tinted chip and hairline. The live label is screen-reader only in this variant. */
|
|
71
|
-
export const Live: Story = {
|
|
72
|
-
args: {
|
|
73
|
-
time: "20:00",
|
|
74
|
-
isLive: true,
|
|
75
|
-
liveLabel: "NOW ON",
|
|
76
|
-
children: (
|
|
77
|
-
<EventCard
|
|
78
|
-
event={event("3", "Psihotrop — From the Beginning to Infinity", "Atelier Café", 50)}
|
|
79
|
-
labels={labels}
|
|
80
|
-
formatters={formatters}
|
|
81
|
-
hideWhen
|
|
82
|
-
liveStatus="Live now · ends around 23:00"
|
|
83
|
-
/>
|
|
84
|
-
),
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/** The shipped mobile shape: compact rows, ruled between, inside one band. */
|
|
89
|
-
export const CompactRows: Story = {
|
|
90
|
-
render: () => (
|
|
91
|
-
<HourBand time="20:00" isLive liveLabel="Now on">
|
|
92
|
-
<EventCompactRow
|
|
93
|
-
event={event("r1", "Klausen Quiz by GOtrivia", "Klausen Pubhouse", 20)}
|
|
94
|
-
labels={labels}
|
|
95
|
-
formatters={formatters}
|
|
96
|
-
hideTime
|
|
97
|
-
tags={["Drinks", "Games"]}
|
|
98
|
-
/>
|
|
99
|
-
<EventCompactRow
|
|
100
|
-
event={event("r2", "Quiz Night at Café del Mars", "Del Mars", 10)}
|
|
101
|
-
labels={labels}
|
|
102
|
-
formatters={formatters}
|
|
103
|
-
hideTime
|
|
104
|
-
tags={["Games"]}
|
|
105
|
-
/>
|
|
106
|
-
</HourBand>
|
|
107
|
-
),
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/** A single event in a quiet hour. */
|
|
111
|
-
export const OneEvent: Story = {
|
|
112
|
-
args: {
|
|
113
|
-
time: "19:00",
|
|
114
|
-
children: (
|
|
115
|
-
<EventCard
|
|
116
|
-
event={event("4", "Quiz Night", "Flying Circus")}
|
|
117
|
-
labels={labels}
|
|
118
|
-
formatters={formatters}
|
|
119
|
-
hideWhen
|
|
120
|
-
/>
|
|
121
|
-
),
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
/** Consecutive bands — the hairline should read as one continuous axis. */
|
|
126
|
-
export const StackedHours: Story = {
|
|
127
|
-
render: () => (
|
|
128
|
-
<>
|
|
129
|
-
<HourBand time="20:00" isLive liveLabel="NOW ON">
|
|
130
|
-
<EventCard
|
|
131
|
-
event={event("5", "Psihotrop Live", "Atelier Café", 50)}
|
|
132
|
-
labels={labels}
|
|
133
|
-
formatters={formatters}
|
|
134
|
-
hideWhen
|
|
135
|
-
liveStatus="Live now · ends around 23:00"
|
|
136
|
-
/>
|
|
137
|
-
</HourBand>
|
|
138
|
-
<HourBand time="21:00">
|
|
139
|
-
<EventCard
|
|
140
|
-
event={event("6", "Charity Concert & Afterparty", "Flying Circus", 30)}
|
|
141
|
-
labels={labels}
|
|
142
|
-
formatters={formatters}
|
|
143
|
-
hideWhen
|
|
144
|
-
/>
|
|
145
|
-
</HourBand>
|
|
146
|
-
<HourBand time="22:00">
|
|
147
|
-
<EventCard
|
|
148
|
-
event={event("7", "Schoolotheque Afterparty with Machedon", "Flying Circus", 25)}
|
|
149
|
-
labels={labels}
|
|
150
|
-
formatters={formatters}
|
|
151
|
-
hideWhen
|
|
152
|
-
/>
|
|
153
|
-
</HourBand>
|
|
154
|
-
</>
|
|
155
|
-
),
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Loading. The chip is blocked out at its fixed 58x24 size — a grey lozenge
|
|
160
|
-
* of that exact shape reads as "a time goes here" without inventing one — and
|
|
161
|
-
* the rows mirror `EventCompactRowSkeleton`.
|
|
162
|
-
*/
|
|
163
|
-
export const Loading: Story = {
|
|
164
|
-
render: () => <HourBandSkeleton />,
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
/** Loading beside loaded, so gutter and card geometry drift is visible at a glance. */
|
|
168
|
-
export const LoadingVsLoaded: Story = {
|
|
169
|
-
render: () => (
|
|
170
|
-
<div className="grid grid-cols-2 gap-6">
|
|
171
|
-
<HourBandSkeleton cardCount={1} />
|
|
172
|
-
<HourBand time="21:00">
|
|
173
|
-
<EventCard
|
|
174
|
-
event={event("8", "Karaoke Night", "VIBE OffSeason")}
|
|
175
|
-
labels={labels}
|
|
176
|
-
formatters={formatters}
|
|
177
|
-
hideWhen
|
|
178
|
-
/>
|
|
179
|
-
</HourBand>
|
|
180
|
-
</div>
|
|
181
|
-
),
|
|
182
|
-
};
|
|
@@ -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
|
-
}
|