@bearmenu/ui 0.8.4 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-637L7XQ3.js +85 -0
- package/dist/chunk-6XMYEZVR.js +26 -0
- package/dist/chunk-DNSXMHYX.js +8 -0
- package/dist/chunk-FDITZ2VM.js +16 -0
- package/dist/chunk-GPH6L4DX.js +55 -0
- package/dist/{chunk-N2KNBPAQ.js → chunk-QXHMPJ35.js} +1 -0
- package/dist/chunk-TT3WFS6S.js +379 -0
- package/dist/chunk-UXWAOISG.js +170 -0
- package/dist/{chunk-UZ3PCIE5.js → chunk-XWMRABZW.js} +2 -2
- package/dist/{chunk-VBA45GLP.js → chunk-ZDAHKI3A.js} +12 -2
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/card.d.ts +2 -2
- package/dist/components/card.js +4 -127
- package/dist/components/category-deck.d.ts +50 -0
- package/dist/components/category-deck.js +128 -0
- package/dist/components/command.d.ts +15 -15
- package/dist/components/concept-card.d.ts +43 -0
- package/dist/components/concept-card.js +81 -0
- package/dist/components/coverflow.d.ts +123 -0
- package/dist/components/coverflow.js +300 -0
- package/dist/components/date-input.d.ts +1 -1
- package/dist/components/day-rail-chip.d.ts +82 -0
- package/dist/components/day-rail-chip.js +105 -0
- package/dist/components/drift-rail.d.ts +28 -0
- package/dist/components/drift-rail.js +3 -0
- package/dist/components/event-card.d.ts +97 -0
- package/dist/components/event-card.js +161 -0
- package/dist/components/event-compact-row.d.ts +72 -0
- package/dist/components/event-compact-row.js +133 -0
- package/dist/components/event-day-header.d.ts +65 -0
- package/dist/components/event-day-header.js +95 -0
- package/dist/components/event-day-separator.d.ts +46 -0
- package/dist/components/event-day-separator.js +50 -0
- package/dist/components/event-detail-panel.d.ts +80 -0
- package/dist/components/event-detail-panel.js +251 -0
- package/dist/components/event-hub-hero.d.ts +34 -0
- package/dist/components/event-hub-hero.js +57 -0
- package/dist/components/event-index-row.d.ts +68 -0
- package/dist/components/event-index-row.js +84 -0
- package/dist/components/event-list-row-wide.d.ts +101 -0
- package/dist/components/event-list-row-wide.js +218 -0
- package/dist/components/event-list-row.d.ts +82 -0
- package/dist/components/event-list-row.js +173 -0
- package/dist/components/event-poster-fallback.d.ts +20 -0
- package/dist/components/event-poster-fallback.js +3 -0
- package/dist/components/event-types.d.ts +118 -0
- package/dist/components/event-types.js +2 -0
- package/dist/components/events-empty-state.d.ts +44 -0
- package/dist/components/events-empty-state.js +73 -0
- package/dist/components/facet-group.d.ts +93 -0
- package/dist/components/facet-group.js +222 -0
- package/dist/components/featured-rail-item.d.ts +42 -0
- package/dist/components/featured-rail-item.js +88 -0
- package/dist/components/hour-band.d.ts +68 -0
- package/dist/components/hour-band.js +53 -0
- package/dist/components/hour-group.d.ts +67 -0
- package/dist/components/hour-group.js +59 -0
- package/dist/components/item.d.ts +2 -2
- package/dist/components/motion-icon-controls.js +3 -379
- package/dist/components/multi-select-combobox.js +1 -1
- package/dist/components/place-about-tab.js +3 -3
- package/dist/components/place-details-mobile.js +3 -3
- package/dist/components/poster-stack.d.ts +30 -0
- package/dist/components/poster-stack.js +104 -0
- package/dist/components/poster-tape.d.ts +36 -0
- package/dist/components/poster-tape.js +64 -0
- package/dist/components/poster-wall.d.ts +25 -0
- package/dist/components/poster-wall.js +183 -0
- package/dist/components/quiet-stretch.d.ts +43 -0
- package/dist/components/quiet-stretch.js +54 -0
- package/dist/components/section-header.d.ts +53 -0
- package/dist/components/section-header.js +63 -0
- package/dist/components/segmented-nav.d.ts +62 -0
- package/dist/components/segmented-nav.js +54 -0
- package/dist/components/skeleton-card.js +1 -1
- package/dist/components/skeleton.d.ts +17 -0
- package/dist/components/skeleton.js +1 -1
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tag-group.js +3 -26
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/tag.js +1 -1
- package/dist/components/text.d.ts +3 -3
- package/dist/components/timeline-band.d.ts +46 -0
- package/dist/components/timeline-band.js +5 -0
- package/dist/components/venue-events-block.d.ts +143 -0
- package/dist/components/venue-events-block.js +222 -0
- package/dist/components/weekend-panel.d.ts +44 -0
- package/dist/components/weekend-panel.js +231 -0
- package/package.json +1 -1
- package/src/components/card.tsx +44 -1
- package/src/components/category-deck.stories.tsx +96 -0
- package/src/components/category-deck.tsx +227 -0
- package/src/components/concept-card.stories.tsx +80 -0
- package/src/components/concept-card.tsx +143 -0
- package/src/components/coverflow.stories.tsx +240 -0
- package/src/components/coverflow.test.tsx +193 -0
- package/src/components/coverflow.tsx +550 -0
- package/src/components/day-rail-chip.stories.tsx +145 -0
- package/src/components/day-rail-chip.tsx +201 -0
- package/src/components/drift-rail.stories.tsx +92 -0
- package/src/components/drift-rail.tsx +86 -0
- package/src/components/event-card.stories.tsx +248 -0
- package/src/components/event-card.test.tsx +177 -0
- package/src/components/event-card.tsx +321 -0
- package/src/components/event-compact-row.stories.tsx +221 -0
- package/src/components/event-compact-row.tsx +268 -0
- package/src/components/event-day-header.stories.tsx +124 -0
- package/src/components/event-day-header.tsx +180 -0
- package/src/components/event-day-separator.stories.tsx +70 -0
- package/src/components/event-day-separator.tsx +95 -0
- package/src/components/event-detail-panel.stories.tsx +191 -0
- package/src/components/event-detail-panel.tsx +360 -0
- package/src/components/event-hub-hero.stories.tsx +66 -0
- package/src/components/event-hub-hero.tsx +110 -0
- package/src/components/event-index-row.stories.tsx +116 -0
- package/src/components/event-index-row.tsx +181 -0
- package/src/components/event-list-row-wide.stories.tsx +204 -0
- package/src/components/event-list-row-wide.tsx +386 -0
- package/src/components/event-list-row.stories.tsx +159 -0
- package/src/components/event-list-row.tsx +335 -0
- package/src/components/event-poster-fallback.stories.tsx +52 -0
- package/src/components/event-poster-fallback.tsx +31 -0
- package/src/components/event-types.ts +133 -0
- package/src/components/events-empty-state.stories.tsx +59 -0
- package/src/components/events-empty-state.tsx +131 -0
- package/src/components/facet-group.stories.tsx +247 -0
- package/src/components/facet-group.test.tsx +225 -0
- package/src/components/facet-group.tsx +352 -0
- package/src/components/featured-rail-item.stories.tsx +86 -0
- package/src/components/featured-rail-item.tsx +153 -0
- package/src/components/hour-band.stories.tsx +159 -0
- package/src/components/hour-band.tsx +126 -0
- package/src/components/hour-group.stories.tsx +100 -0
- package/src/components/hour-group.tsx +124 -0
- package/src/components/poster-stack.stories.tsx +68 -0
- package/src/components/poster-stack.tsx +155 -0
- package/src/components/poster-tape.stories.tsx +70 -0
- package/src/components/poster-tape.tsx +122 -0
- package/src/components/poster-wall.stories.tsx +95 -0
- package/src/components/poster-wall.tsx +220 -0
- package/src/components/quiet-stretch.stories.tsx +62 -0
- package/src/components/quiet-stretch.tsx +115 -0
- package/src/components/section-header.stories.tsx +70 -0
- package/src/components/section-header.tsx +134 -0
- package/src/components/segmented-nav.stories.tsx +118 -0
- package/src/components/segmented-nav.tsx +113 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/tag.tsx +11 -1
- package/src/components/timeline-band.stories.tsx +206 -0
- package/src/components/timeline-band.tsx +207 -0
- package/src/components/venue-events-block.stories.tsx +216 -0
- package/src/components/venue-events-block.tsx +408 -0
- package/src/components/weekend-panel.stories.tsx +92 -0
- package/src/components/weekend-panel.tsx +322 -0
- package/src/globals.css +19 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
import { EventPosterFallback } from "./event-poster-fallback";
|
|
4
|
+
import { Separator } from "./separator";
|
|
5
|
+
import { Tag } from "./tag";
|
|
6
|
+
import { TagGroup } from "./tag-group";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* EventIndexRow — the row of an exhaustive, crawlable event index.
|
|
10
|
+
*
|
|
11
|
+
* NOT `<EventListRowWide>`: that row belongs to the list VIEW, and it carries
|
|
12
|
+
* a price column and a Reserve chip because the list view is where you act.
|
|
13
|
+
* This row makes no claim to be operable — it is a catalogue entry. Its only
|
|
14
|
+
* affordances are two real links: the title (stretched over the whole row via
|
|
15
|
+
* `after:absolute after:inset-0`) and the venue (escaping that overlay with
|
|
16
|
+
* `relative z-10`, the same nested-link pattern `<EventListRowWide>` uses for
|
|
17
|
+
* `menuHref`). That venue link is why this could not be a variant of
|
|
18
|
+
* `<EventCompactRow>` or `<EventListRow>`: both wrap their entire body in one
|
|
19
|
+
* anchor, so a second link inside would be a nested `<a>` — invalid HTML that
|
|
20
|
+
* browsers unnest, breaking both links.
|
|
21
|
+
*
|
|
22
|
+
* NO `"use client"`, AND THAT IS LOAD-BEARING. This row's surface is a
|
|
23
|
+
* server-only prerendered page carrying ~50 of them, and every sibling row in
|
|
24
|
+
* this family is a client component for one reason: `useState` to catch
|
|
25
|
+
* `<img onError>`. Here the fallback is layered BEHIND the poster instead, so
|
|
26
|
+
* a failed image reveals the designed gradient with no JS at all. The
|
|
27
|
+
* consequence to accept: an image that returns 200 but decodes to garbage
|
|
28
|
+
* shows an empty box rather than the fallback.
|
|
29
|
+
*
|
|
30
|
+
* THE DESCRIPTION IS NEVER TRUNCATED IN THE MARKUP — it is clamped with CSS
|
|
31
|
+
* (`line-clamp`), so the full text stays in the DOM. Callers must pass the
|
|
32
|
+
* whole string. On this surface that is the entire point: the text is the
|
|
33
|
+
* page's body copy, and a row that cut it at N characters would be deleting
|
|
34
|
+
* the thing the section exists to carry.
|
|
35
|
+
*
|
|
36
|
+
* ROW RULES: this row draws its own top rule, same as `<EventListRow>` and
|
|
37
|
+
* `<EventListRowWide>`. Pass `hideRule` on the first row under a heading that
|
|
38
|
+
* already ends in one (`<EventDaySeparator>` does), or you get two rules a few
|
|
39
|
+
* pixels apart.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
export type EventIndexRowLinkProps = {
|
|
43
|
+
href: string;
|
|
44
|
+
className?: string;
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function DefaultLink({ href, ...props }: EventIndexRowLinkProps) {
|
|
49
|
+
return <a href={href} {...props} />;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type EventIndexRowProps = {
|
|
53
|
+
/** Rendered inside `as` and allowed to WRAP — a document-outline heading is never truncated. */
|
|
54
|
+
title: string;
|
|
55
|
+
/** Heading level for `title`. Defaults to `h3`, one below the group headings. */
|
|
56
|
+
as?: "h2" | "h3" | "h4";
|
|
57
|
+
href: string;
|
|
58
|
+
/** Leading emphasised fact on the meta line — caller-composed date + time, e.g. "26 iulie, 20:00–23:00". */
|
|
59
|
+
when: string;
|
|
60
|
+
venue?: string;
|
|
61
|
+
/** Makes the venue a real second link. Omit for a venue with no place page. */
|
|
62
|
+
venueHref?: string;
|
|
63
|
+
/** Plain text, never a chip: "intrare liberă" / "bilet 40 lei". */
|
|
64
|
+
fee?: string;
|
|
65
|
+
/** Display labels. `EventPreview.tags` is taxonomy coordinates the DS cannot translate, so the caller resolves and caps them. */
|
|
66
|
+
tags?: string[];
|
|
67
|
+
/** FULL text. Clamped to 3 lines (mobile) / 2 (md+) in CSS only. */
|
|
68
|
+
description?: string;
|
|
69
|
+
posterUrl?: string;
|
|
70
|
+
fallback?: ReactNode;
|
|
71
|
+
/** Suppresses the top rule. Use on the first row under a heading that draws its own. */
|
|
72
|
+
hideRule?: boolean;
|
|
73
|
+
linkComponent?: ComponentType<EventIndexRowLinkProps>;
|
|
74
|
+
className?: string;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export function EventIndexRow({
|
|
78
|
+
title,
|
|
79
|
+
as: Heading = "h3",
|
|
80
|
+
href,
|
|
81
|
+
when,
|
|
82
|
+
venue,
|
|
83
|
+
venueHref,
|
|
84
|
+
fee,
|
|
85
|
+
tags,
|
|
86
|
+
description,
|
|
87
|
+
posterUrl,
|
|
88
|
+
fallback = <EventPosterFallback />,
|
|
89
|
+
hideRule,
|
|
90
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
91
|
+
className,
|
|
92
|
+
}: EventIndexRowProps) {
|
|
93
|
+
const dot = (
|
|
94
|
+
<span className="px-1 text-border" aria-hidden>
|
|
95
|
+
·
|
|
96
|
+
</span>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className={cn("relative", className)}>
|
|
101
|
+
{!hideRule && <Separator className="absolute inset-x-0 top-0" />}
|
|
102
|
+
|
|
103
|
+
<div className="relative -mx-3 flex gap-3 rounded-lg px-3 py-4 transition-colors duration-150 hover:bg-card/60 motion-reduce:transition-none md:gap-4 md:py-5">
|
|
104
|
+
{/* Square poster at the same 72/104 the rest of the row family uses —
|
|
105
|
+
the editorial surfaces above this one are 4:3, but a third ratio
|
|
106
|
+
inside the ROW family would break the sibling-shape rule for one
|
|
107
|
+
section's sake. The fallback sits behind the image rather than
|
|
108
|
+
beside it in a ternary, which is what lets this whole row stay a
|
|
109
|
+
server component. */}
|
|
110
|
+
<div className="relative aspect-square w-[72px] shrink-0 overflow-hidden rounded-[9px] bg-muted md:w-[104px]">
|
|
111
|
+
<div className="absolute inset-0">{fallback}</div>
|
|
112
|
+
{posterUrl && (
|
|
113
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
114
|
+
<img
|
|
115
|
+
src={posterUrl}
|
|
116
|
+
alt=""
|
|
117
|
+
loading="lazy"
|
|
118
|
+
decoding="async"
|
|
119
|
+
className="absolute inset-0 h-full w-full object-cover"
|
|
120
|
+
/>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className="min-w-0 flex-1">
|
|
125
|
+
<Heading className="text-[15px] font-bold leading-snug tracking-[-0.01em] text-foreground md:text-[16.5px]">
|
|
126
|
+
<LinkComponent
|
|
127
|
+
href={href}
|
|
128
|
+
className="rounded-sm transition-colors duration-150 after:absolute after:inset-0 hover:text-primary motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
129
|
+
>
|
|
130
|
+
{title}
|
|
131
|
+
</LinkComponent>
|
|
132
|
+
</Heading>
|
|
133
|
+
|
|
134
|
+
<p className="mt-1 text-[12px] leading-5 text-muted-foreground md:text-[12.5px]">
|
|
135
|
+
<span className="font-semibold tabular-nums text-foreground">{when}</span>
|
|
136
|
+
{venue && (
|
|
137
|
+
<>
|
|
138
|
+
{dot}
|
|
139
|
+
{venueHref ? (
|
|
140
|
+
<LinkComponent
|
|
141
|
+
href={venueHref}
|
|
142
|
+
className="relative z-10 rounded-sm font-semibold text-foreground/85 underline decoration-border underline-offset-2 transition-colors duration-150 hover:text-primary hover:decoration-primary motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
143
|
+
>
|
|
144
|
+
{venue}
|
|
145
|
+
</LinkComponent>
|
|
146
|
+
) : (
|
|
147
|
+
<span className="font-semibold text-foreground/85">{venue}</span>
|
|
148
|
+
)}
|
|
149
|
+
</>
|
|
150
|
+
)}
|
|
151
|
+
{fee && (
|
|
152
|
+
<>
|
|
153
|
+
{dot}
|
|
154
|
+
{fee}
|
|
155
|
+
</>
|
|
156
|
+
)}
|
|
157
|
+
</p>
|
|
158
|
+
|
|
159
|
+
{tags && tags.length > 0 && (
|
|
160
|
+
// `outline`, not the default: default `Tag` is `bg-muted`, and this
|
|
161
|
+
// row's surface is a `bg-muted/50` band — a 0.019-L delta in light
|
|
162
|
+
// mode, which is invisible. `outline` reads on both.
|
|
163
|
+
<TagGroup className="mt-2">
|
|
164
|
+
{tags.map((tag) => (
|
|
165
|
+
<Tag key={tag} size="sm" variant="outline">
|
|
166
|
+
{tag}
|
|
167
|
+
</Tag>
|
|
168
|
+
))}
|
|
169
|
+
</TagGroup>
|
|
170
|
+
)}
|
|
171
|
+
|
|
172
|
+
{description && (
|
|
173
|
+
<p className="mt-2 line-clamp-3 max-w-[620px] text-[13px] leading-[1.6] text-muted-foreground md:line-clamp-2 md:text-[13.5px] md:leading-[1.55]">
|
|
174
|
+
{description}
|
|
175
|
+
</p>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { EventListRowWide, EventListRowWideSkeleton } from "./event-list-row-wide";
|
|
3
|
+
import type { EventListRowWideLabels } from "./event-list-row-wide";
|
|
4
|
+
import type { EventDateBadgeFormatters, EventPreview } from "./event-types";
|
|
5
|
+
|
|
6
|
+
const labels: EventListRowWideLabels = {
|
|
7
|
+
free: "Free",
|
|
8
|
+
liveNow: "On now",
|
|
9
|
+
reserve: "Reserve",
|
|
10
|
+
notifyMe: "Notify me",
|
|
11
|
+
soldOut: "Sold out",
|
|
12
|
+
seeMenu: "See the menu",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const formatters: EventDateBadgeFormatters = {
|
|
16
|
+
time: (iso) => new Date(iso).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" }),
|
|
17
|
+
day: (iso) =>
|
|
18
|
+
new Date(iso).toLocaleDateString("en-GB", { weekday: "short", day: "numeric", month: "short" }),
|
|
19
|
+
price: (amount) => `${amount} RON`,
|
|
20
|
+
dayNumber: (iso) => String(new Date(iso).getDate()).padStart(2, "0"),
|
|
21
|
+
monthShort: (iso) =>
|
|
22
|
+
new Date(iso).toLocaleDateString("en-GB", { month: "short" }).toUpperCase(),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const event = (
|
|
26
|
+
id: string,
|
|
27
|
+
title: string,
|
|
28
|
+
place: string,
|
|
29
|
+
startsAt: string,
|
|
30
|
+
fee?: number,
|
|
31
|
+
description?: string
|
|
32
|
+
): EventPreview => ({
|
|
33
|
+
id,
|
|
34
|
+
title,
|
|
35
|
+
location: place,
|
|
36
|
+
place_name: place,
|
|
37
|
+
starts_at: startsAt,
|
|
38
|
+
entry_fee: fee,
|
|
39
|
+
description,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const description =
|
|
43
|
+
"Rooftop set from sunset to close, resident DJs on the decks all night, kitchen open until midnight with the full grill menu.";
|
|
44
|
+
|
|
45
|
+
const meta: Meta<typeof EventListRowWide> = {
|
|
46
|
+
title: "Events/EventListRowWide",
|
|
47
|
+
component: EventListRowWide,
|
|
48
|
+
tags: ["autodocs"],
|
|
49
|
+
args: { labels, formatters },
|
|
50
|
+
decorators: [
|
|
51
|
+
(Story) => (
|
|
52
|
+
<div className="w-[860px]">
|
|
53
|
+
<Story />
|
|
54
|
+
</div>
|
|
55
|
+
),
|
|
56
|
+
],
|
|
57
|
+
parameters: {
|
|
58
|
+
docs: {
|
|
59
|
+
description: {
|
|
60
|
+
component:
|
|
61
|
+
"The desktop list row — denser than the poster card, richer than the mobile list row, with a description and a dedicated action column. The row root is a plain container: the title is the primary link with a stretched overlay (so the whole row stays one click target), and 'See the menu' — rendered only when `menuHref` is set — is a second, real link layered above the overlay so it wins clicks in its own area.",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default meta;
|
|
68
|
+
type Story = StoryObj<typeof EventListRowWide>;
|
|
69
|
+
|
|
70
|
+
export const Default: Story = {
|
|
71
|
+
args: {
|
|
72
|
+
event: event("1", "Psihotrop Live", "Atelier Café", "2026-09-05T20:00:00", 50, description),
|
|
73
|
+
tags: ["Live music", "Rooftop"],
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The nested-link pattern: the title is the primary link (stretched over the
|
|
79
|
+
* whole row via `after:absolute after:inset-0`), and "See the menu" is a real,
|
|
80
|
+
* independently targeted link that wins clicks in its own area.
|
|
81
|
+
*/
|
|
82
|
+
export const WithMenuLink: Story = {
|
|
83
|
+
args: {
|
|
84
|
+
event: event("1b", "Psihotrop Live", "Atelier Café", "2026-09-05T20:00:00", 50, description),
|
|
85
|
+
tags: ["Live music", "Rooftop"],
|
|
86
|
+
href: "/events/psihotrop-live",
|
|
87
|
+
menuHref: "/places/atelier-cafe/menu",
|
|
88
|
+
onClick: (_event, mouseEvent) => mouseEvent.preventDefault(),
|
|
89
|
+
},
|
|
90
|
+
parameters: {
|
|
91
|
+
docs: {
|
|
92
|
+
description: {
|
|
93
|
+
story:
|
|
94
|
+
"Click the title anywhere in the row to open the event; click 'See the menu' to go to the venue's menu instead — both are real links, not one anchor with dead text nested inside it.",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** No `menuHref` — the affordance is omitted entirely rather than rendered as dead text. */
|
|
101
|
+
export const WithoutMenuLink: Story = {
|
|
102
|
+
args: {
|
|
103
|
+
event: event("1c", "Psihotrop Live", "Atelier Café", "2026-09-05T20:00:00", 50, description),
|
|
104
|
+
tags: ["Live music", "Rooftop"],
|
|
105
|
+
href: "/events/psihotrop-live",
|
|
106
|
+
onClick: (_event, mouseEvent) => mouseEvent.preventDefault(),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const FreeEntry: Story = {
|
|
111
|
+
args: {
|
|
112
|
+
event: event("2", "Vinyl Happy Hour", "Zorki Photo Cafe", "2026-09-04T18:00:00", 0, description),
|
|
113
|
+
tags: ["Vinyl", "Happy hour"],
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/** No published fee is "TBC", never "Free". */
|
|
118
|
+
export const UnknownPrice: Story = {
|
|
119
|
+
args: { event: event("3", "Quiz Night", "Flying Circus", "2026-09-03T19:00:00") },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Live tints the date badge and adds the on-air chip. */
|
|
123
|
+
export const LiveNow: Story = {
|
|
124
|
+
args: {
|
|
125
|
+
event: event("4", "Charity Concert & Afterparty", "Flying Circus", "2026-09-03T21:00:00", 30, description),
|
|
126
|
+
isLive: true,
|
|
127
|
+
tags: ["Charity", "Concert"],
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** Sold out dims the row and swaps the CTA to "Notify me". */
|
|
132
|
+
export const SoldOut: Story = {
|
|
133
|
+
args: {
|
|
134
|
+
event: event("5", "Friday Night Techno", "Form Space", "2026-09-04T22:00:00", 50, description),
|
|
135
|
+
isSoldOut: true,
|
|
136
|
+
tags: ["Techno", "Club night"],
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/** No `description` on the event — the block collapses instead of leaving a gap. */
|
|
141
|
+
export const NoDescription: Story = {
|
|
142
|
+
args: {
|
|
143
|
+
event: event("6", "Stand-up Comedy Night", "Klausen Pubhouse", "2026-09-04T19:30:00", 45),
|
|
144
|
+
tags: ["Comedy"],
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Loading state — identical geometry to `Default`, so nothing shifts on data arrival. */
|
|
149
|
+
export const Loading: Story = {
|
|
150
|
+
render: () => <EventListRowWideSkeleton />,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/** `NoDescription`'s narrower geometry, loading. */
|
|
154
|
+
export const LoadingNoDescription: Story = {
|
|
155
|
+
render: () => <EventListRowWideSkeleton showDescription={false} />,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/** A loading row above a loaded one, so any geometry drift is visible at a glance. */
|
|
159
|
+
export const LoadingVsLoaded: Story = {
|
|
160
|
+
render: () => (
|
|
161
|
+
<div>
|
|
162
|
+
<EventListRowWideSkeleton />
|
|
163
|
+
<EventListRowWide
|
|
164
|
+
event={event("1", "Psihotrop Live", "Atelier Café", "2026-09-05T20:00:00", 50, description)}
|
|
165
|
+
labels={labels}
|
|
166
|
+
formatters={formatters}
|
|
167
|
+
tags={["Live music", "Rooftop"]}
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** A day's worth of rows, as the desktop list view stacks them. */
|
|
174
|
+
export const ADay: Story = {
|
|
175
|
+
render: () => (
|
|
176
|
+
<div>
|
|
177
|
+
<EventListRowWide
|
|
178
|
+
event={event("a", "Psihotrop Live", "Atelier Café", "2026-09-03T20:00:00", 50, description)}
|
|
179
|
+
labels={labels}
|
|
180
|
+
formatters={formatters}
|
|
181
|
+
tags={["Live music", "Rooftop"]}
|
|
182
|
+
isLive
|
|
183
|
+
/>
|
|
184
|
+
<EventListRowWide
|
|
185
|
+
event={event("b", "Quiz Night", "Flying Circus", "2026-09-03T20:00:00")}
|
|
186
|
+
labels={labels}
|
|
187
|
+
formatters={formatters}
|
|
188
|
+
/>
|
|
189
|
+
<EventListRowWide
|
|
190
|
+
event={event("c", "Beer Battle", "Irish Music Pub", "2026-09-03T20:00:00", 0, description)}
|
|
191
|
+
labels={labels}
|
|
192
|
+
formatters={formatters}
|
|
193
|
+
tags={["Nightlife"]}
|
|
194
|
+
/>
|
|
195
|
+
<EventListRowWide
|
|
196
|
+
event={event("e", "Schoolotheque Afterparty", "Flying Circus", "2026-09-03T22:00:00", 25, description)}
|
|
197
|
+
labels={labels}
|
|
198
|
+
formatters={formatters}
|
|
199
|
+
tags={["Afterparty"]}
|
|
200
|
+
isSoldOut
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
),
|
|
204
|
+
};
|