@bearmenu/ui 0.8.3 → 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-2WDJR6K7.js +76 -0
- 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/icon.d.ts +18 -0
- package/dist/components/icon.js +31 -0
- package/dist/components/item.d.ts +2 -2
- package/dist/components/motion-icon-controls.d.ts +26 -0
- package/dist/components/motion-icon-controls.js +4 -0
- package/dist/components/motion-icon-set.d.ts +27 -0
- package/dist/components/motion-icon-set.js +139 -0
- package/dist/components/motion-icon.d.ts +44 -0
- package/dist/components/motion-icon.js +3 -0
- 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/dist/lib/icon-registry.d.ts +58 -0
- package/dist/lib/icon-registry.js +39 -0
- package/package.json +2 -1
- package/src/components/accordion.stories.tsx +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/combobox.stories.tsx +1 -1
- 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/currency-input.stories.tsx +3 -3
- 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/empty-state.stories.tsx +3 -3
- 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/form.stories.tsx +1 -1
- 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/icon.tsx +57 -0
- package/src/components/input-otp.stories.tsx +1 -1
- package/src/components/markdown-renderer.stories.tsx +1 -1
- package/src/components/motion-icon-controls.test.tsx +78 -0
- package/src/components/motion-icon-controls.tsx +552 -0
- package/src/components/motion-icon-set.stories.tsx +185 -0
- package/src/components/motion-icon-set.tsx +219 -0
- package/src/components/motion-icon.test.tsx +159 -0
- package/src/components/motion-icon.tsx +163 -0
- package/src/components/multi-select-combobox.stories.tsx +1 -1
- package/src/components/phone-frame.stories.tsx +1 -1
- 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/rating.stories.tsx +3 -3
- package/src/components/searchable-select.stories.tsx +1 -1
- 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/selection-bar.stories.tsx +1 -1
- package/src/components/skeleton-card.stories.tsx +3 -3
- package/src/components/skeleton.tsx +18 -0
- package/src/components/sliding-panels.stories.tsx +1 -1
- package/src/components/stepper.stories.tsx +1 -1
- package/src/components/sticky-container.stories.tsx +3 -3
- 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/toggle-group.stories.tsx +1 -1
- package/src/components/tooltip.stories.tsx +2 -2
- 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
- package/src/lib/icon-registry.ts +139 -0
- package/src/test/setup.ts +5 -1
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { Separator } from '../chunk-WSRWFA6K.js';
|
|
2
|
+
import { TagGroup } from '../chunk-6XMYEZVR.js';
|
|
3
|
+
import { Tag } from '../chunk-ZDAHKI3A.js';
|
|
4
|
+
import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
|
|
5
|
+
import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
|
|
6
|
+
import { Skeleton } from '../chunk-QXHMPJ35.js';
|
|
7
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
8
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
9
|
+
import { memo, useState } from 'react';
|
|
10
|
+
import { MapPin, Radio } from 'lucide-react';
|
|
11
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
12
|
+
|
|
13
|
+
function DefaultLink(_a) {
|
|
14
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
15
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
16
|
+
}
|
|
17
|
+
var EventListRow = memo(function EventListRow2({
|
|
18
|
+
event,
|
|
19
|
+
labels,
|
|
20
|
+
formatters,
|
|
21
|
+
isLive,
|
|
22
|
+
isSoldOut,
|
|
23
|
+
href,
|
|
24
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
25
|
+
onClick,
|
|
26
|
+
className,
|
|
27
|
+
priority,
|
|
28
|
+
tags,
|
|
29
|
+
fallback = /* @__PURE__ */ jsx(EventPosterFallback, {}),
|
|
30
|
+
hideRule
|
|
31
|
+
}) {
|
|
32
|
+
var _a;
|
|
33
|
+
const [imgError, setImgError] = useState(false);
|
|
34
|
+
const fee = resolveEventEntryFee(event);
|
|
35
|
+
const showPoster = !!event.poster_url && !imgError;
|
|
36
|
+
const venue = (_a = event.place_name) != null ? _a : event.location;
|
|
37
|
+
const priceText = fee.kind === "free" ? labels.free : fee.kind === "paid" ? formatters.price(fee.amountRon) : void 0;
|
|
38
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
39
|
+
!hideRule && /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
|
|
40
|
+
/* @__PURE__ */ jsxs(
|
|
41
|
+
"div",
|
|
42
|
+
{
|
|
43
|
+
className: cn("w-[42px] shrink-0 rounded-[9px] py-1.5 text-center", isLive && "bg-success/10"),
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ jsx(
|
|
46
|
+
"div",
|
|
47
|
+
{
|
|
48
|
+
className: cn(
|
|
49
|
+
"text-sm font-semibold leading-none tabular-nums",
|
|
50
|
+
isLive ? "text-success" : "text-foreground"
|
|
51
|
+
),
|
|
52
|
+
children: formatters.dayNumber(event.starts_at)
|
|
53
|
+
}
|
|
54
|
+
),
|
|
55
|
+
/* @__PURE__ */ jsx(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
className: cn(
|
|
59
|
+
"mt-[3px] text-[10px] font-semibold tracking-[0.06em]",
|
|
60
|
+
isLive ? "text-success" : "text-muted-foreground"
|
|
61
|
+
),
|
|
62
|
+
children: formatters.monthShort(event.starts_at)
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-square w-[72px] shrink-0 overflow-hidden rounded-[9px] bg-muted", children: showPoster ? (
|
|
69
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
70
|
+
/* @__PURE__ */ jsx(
|
|
71
|
+
"img",
|
|
72
|
+
{
|
|
73
|
+
src: event.poster_url,
|
|
74
|
+
alt: "",
|
|
75
|
+
loading: priority ? "eager" : "lazy",
|
|
76
|
+
decoding: "async",
|
|
77
|
+
className: "h-full w-full object-cover",
|
|
78
|
+
onError: () => setImgError(true)
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
) : fallback }),
|
|
82
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "text-[15px] font-semibold leading-[1.3] tracking-[-0.01em]", children: event.title }),
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
85
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums text-foreground", children: formatters.time(event.starts_at) }),
|
|
86
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 items-center gap-1", children: [
|
|
87
|
+
/* @__PURE__ */ jsx(MapPin, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
|
|
88
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: venue })
|
|
89
|
+
] }),
|
|
90
|
+
isSoldOut ? /* @__PURE__ */ jsx(Tag, { size: "sm", variant: "quiet", className: "shrink-0 whitespace-nowrap", children: labels.soldOut }) : priceText && /* @__PURE__ */ jsx(
|
|
91
|
+
"span",
|
|
92
|
+
{
|
|
93
|
+
className: cn(
|
|
94
|
+
"shrink-0 tabular-nums",
|
|
95
|
+
fee.kind === "free" && "font-semibold text-success"
|
|
96
|
+
),
|
|
97
|
+
children: priceText
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
] }),
|
|
101
|
+
(isLive || tags && tags.length > 0) && /* @__PURE__ */ jsxs(TagGroup, { className: "mt-1.5 h-[22px] overflow-hidden", children: [
|
|
102
|
+
isLive && /* @__PURE__ */ jsxs(Tag, { size: "sm", variant: "success", className: "shrink-0 whitespace-nowrap", children: [
|
|
103
|
+
/* @__PURE__ */ jsx(Radio, { className: "h-2.5 w-2.5", "aria-hidden": true }),
|
|
104
|
+
labels.liveNow
|
|
105
|
+
] }),
|
|
106
|
+
tags == null ? void 0 : tags.map((tag) => /* @__PURE__ */ jsx(Tag, { size: "sm", variant: "quiet", className: "shrink-0 whitespace-nowrap", children: tag }, tag))
|
|
107
|
+
] })
|
|
108
|
+
] })
|
|
109
|
+
] });
|
|
110
|
+
const rootClasses = cn(
|
|
111
|
+
// `py-3`, not `py-3.5`: the chip line adds a third line back, and 8px of
|
|
112
|
+
// padding pays for most of it. Rows with a one-line title and no chips are
|
|
113
|
+
// still poster-driven and end up SHORTER than before.
|
|
114
|
+
"relative flex w-full items-start gap-3 py-3 text-left",
|
|
115
|
+
isSoldOut && "opacity-60",
|
|
116
|
+
(href || onClick) && "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
117
|
+
className
|
|
118
|
+
);
|
|
119
|
+
if (href) {
|
|
120
|
+
return /* @__PURE__ */ jsx(
|
|
121
|
+
LinkComponent,
|
|
122
|
+
{
|
|
123
|
+
href,
|
|
124
|
+
className: rootClasses,
|
|
125
|
+
onClick: onClick ? (mouseEvent) => onClick(event, mouseEvent) : void 0,
|
|
126
|
+
children: body
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
if (onClick) {
|
|
131
|
+
return /* @__PURE__ */ jsx("button", { type: "button", className: rootClasses, onClick: (e) => onClick(event, e), children: body });
|
|
132
|
+
}
|
|
133
|
+
return /* @__PURE__ */ jsx("div", { className: rootClasses, children: body });
|
|
134
|
+
});
|
|
135
|
+
function EventListRowSkeleton({
|
|
136
|
+
hideRule,
|
|
137
|
+
showTags = true,
|
|
138
|
+
className
|
|
139
|
+
}) {
|
|
140
|
+
return /* @__PURE__ */ jsxs(
|
|
141
|
+
"div",
|
|
142
|
+
{
|
|
143
|
+
"aria-hidden": "true",
|
|
144
|
+
className: cn("relative flex w-full items-start gap-3 py-3 text-left", className),
|
|
145
|
+
children: [
|
|
146
|
+
!hideRule && /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
|
|
147
|
+
/* @__PURE__ */ jsxs("div", { className: "w-[42px] shrink-0 rounded-[9px] py-1.5 text-center", children: [
|
|
148
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mx-auto h-4 w-5" }),
|
|
149
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "mx-auto mt-[3px] h-[10px] w-6" })
|
|
150
|
+
] }),
|
|
151
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "aspect-square w-[72px] shrink-0 rounded-[9px]" }),
|
|
152
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
153
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
154
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[15px] w-full" }),
|
|
155
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[15px] w-2/3" })
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center gap-2", children: [
|
|
158
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-10" }),
|
|
159
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3 shrink-0 rounded-full" }),
|
|
160
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-24" }),
|
|
161
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-3 w-12" })
|
|
162
|
+
] }),
|
|
163
|
+
showTags && /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex items-center gap-1.5", children: [
|
|
164
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[22px] w-14 rounded-full" }),
|
|
165
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[22px] w-11 rounded-full" })
|
|
166
|
+
] })
|
|
167
|
+
] })
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export { EventListRow, EventListRowSkeleton };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EventPosterFallback — what every events surface renders in place of a
|
|
6
|
+
* missing (or failed) poster image.
|
|
7
|
+
*
|
|
8
|
+
* Follows the DS image-fallback rule: never a bare icon on a flat background.
|
|
9
|
+
* `--gradient-primary` is warm in both themes, so the drop-shadowed icon
|
|
10
|
+
* always sits on a tinted, on-brand surface rather than the cold, zero-chroma
|
|
11
|
+
* `bg-muted` block a poster box would otherwise show.
|
|
12
|
+
*/
|
|
13
|
+
type EventPosterFallbackProps = {
|
|
14
|
+
/** Defaults to a calendar glyph. Pass a tag-derived icon (e.g. `getEventFallbackIcon`) for a closer match to the event's category. */
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
};
|
|
18
|
+
declare function EventPosterFallback({ icon, className }: EventPosterFallbackProps): react_jsx_runtime.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { EventPosterFallback, type EventPosterFallbackProps };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural mirror of the openapi `Event` schema, decoupled from any one app's
|
|
3
|
+
* generated types. Both FE and admin map their domain shapes into this type
|
|
4
|
+
* before passing data to `<EventCard>` / `<EventListRow>` / `<EventDaySection>`.
|
|
5
|
+
*
|
|
6
|
+
* Same contract as `place-types.ts`: no app schemas, no app hooks, no i18n.
|
|
7
|
+
* Every user-facing string arrives through a `*Labels` object, and every
|
|
8
|
+
* locale-sensitive format arrives as an injected formatter — the DS never owns
|
|
9
|
+
* a translation key or an `Intl` locale.
|
|
10
|
+
*/
|
|
11
|
+
type EventPreview = {
|
|
12
|
+
id: string;
|
|
13
|
+
title: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
/** Short teaser (≤79 chars) for tight surfaces. Absent for events without one. */
|
|
16
|
+
short_description?: string;
|
|
17
|
+
/** Free-text venue/location line as returned by the API. */
|
|
18
|
+
location: string;
|
|
19
|
+
/** ISO 8601 date-time. */
|
|
20
|
+
starts_at: string;
|
|
21
|
+
/** ISO 8601 date-time. Absent for events with no published end. */
|
|
22
|
+
ends_at?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Entry fee in RON. `0` means explicitly free; `undefined` means unknown.
|
|
25
|
+
* The distinction is load-bearing — see `resolveEventEntryFee`.
|
|
26
|
+
*/
|
|
27
|
+
entry_fee?: number;
|
|
28
|
+
place_name?: string;
|
|
29
|
+
place_slug?: string;
|
|
30
|
+
/** 0–100 social-buzz score. `0` for events with no engagement signal. */
|
|
31
|
+
hype_score?: number;
|
|
32
|
+
performers?: EventPerformerRef[];
|
|
33
|
+
tags?: EventTagRef[];
|
|
34
|
+
event_concept_slug?: string;
|
|
35
|
+
event_concept_title?: string;
|
|
36
|
+
/** Booking URL, when the host takes bookings through a link. */
|
|
37
|
+
bookings?: string;
|
|
38
|
+
/** Ticketing URL, when the event is ticketed. */
|
|
39
|
+
tickets?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Pre-resolved poster URL. Callers compute this themselves — the API returns
|
|
42
|
+
* a `Photo.key`, and CDN base plus transform differ per app.
|
|
43
|
+
*/
|
|
44
|
+
poster_url?: string;
|
|
45
|
+
};
|
|
46
|
+
/** A taxonomy tag. `slug` is only unique within its `axis_slug`. */
|
|
47
|
+
type EventTagRef = {
|
|
48
|
+
slug: string;
|
|
49
|
+
axis_slug: string;
|
|
50
|
+
};
|
|
51
|
+
type EventPerformerRef = {
|
|
52
|
+
name: string;
|
|
53
|
+
kind: "person" | "group";
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Entry-fee state. `unknown` is a real third case, not a missing `paid` — an
|
|
57
|
+
* event with no published fee must never render as free.
|
|
58
|
+
*/
|
|
59
|
+
type EventEntryFee = {
|
|
60
|
+
kind: "free";
|
|
61
|
+
} | {
|
|
62
|
+
kind: "paid";
|
|
63
|
+
amountRon: number;
|
|
64
|
+
} | {
|
|
65
|
+
kind: "unknown";
|
|
66
|
+
};
|
|
67
|
+
/** Structural, not editorial — safe to derive inside the DS. */
|
|
68
|
+
declare function resolveEventEntryFee(event: EventPreview): EventEntryFee;
|
|
69
|
+
/**
|
|
70
|
+
* Locale-sensitive formatters, injected by the caller. The DS holds no `Intl`
|
|
71
|
+
* locale and no currency of its own.
|
|
72
|
+
*/
|
|
73
|
+
type EventFormatters = {
|
|
74
|
+
/** e.g. `"20:00"` */
|
|
75
|
+
time: (isoDateTime: string) => string;
|
|
76
|
+
/** e.g. `"Sat, Sep 5"` — used when a card appears outside its own day section. */
|
|
77
|
+
day: (isoDateTime: string) => string;
|
|
78
|
+
/** e.g. `50 => "50 RON"` */
|
|
79
|
+
price: (amountRon: number) => string;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The extra formatters a date badge needs, split out rather than added to
|
|
83
|
+
* `EventFormatters` so `<EventCard>` keeps asking for only what it uses.
|
|
84
|
+
* `<EventListRow>` and the timeline's day headers need the parts separately.
|
|
85
|
+
*/
|
|
86
|
+
type EventDateBadgeFormatters = EventFormatters & {
|
|
87
|
+
/** Day of month as displayed, e.g. `"05"`. */
|
|
88
|
+
dayNumber: (isoDateTime: string) => string;
|
|
89
|
+
/** Short month, e.g. `"SEP"`. */
|
|
90
|
+
monthShort: (isoDateTime: string) => string;
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Labels passed to `<EventCard>`. All translation happens in the caller.
|
|
94
|
+
*
|
|
95
|
+
* There is no `priceUnknown` here on purpose. An unknown fee is the commonest
|
|
96
|
+
* case in this feed, and every surface that used to render a placeholder for
|
|
97
|
+
* it now renders nothing — see the long note in `event-list-row-wide.tsx`.
|
|
98
|
+
*/
|
|
99
|
+
type EventCardLabels = {
|
|
100
|
+
free: string;
|
|
101
|
+
/** Badge shown while the event is currently running. */
|
|
102
|
+
liveNow: string;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Labels passed to `<EventListRow>`. Extends the card's with the sold-out
|
|
106
|
+
* chip; the row's ACTION labels live on `EventListRowWideLabels`, since the
|
|
107
|
+
* phone row has no action to label.
|
|
108
|
+
*/
|
|
109
|
+
type EventListRowLabels = EventCardLabels & {
|
|
110
|
+
soldOut: string;
|
|
111
|
+
};
|
|
112
|
+
/** Labels passed to `<DayRailChip>`. */
|
|
113
|
+
type DayRailChipLabels = {
|
|
114
|
+
/** Screen-reader text for a day with no events, rendered where the count sits. */
|
|
115
|
+
noEvents: string;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export { type DayRailChipLabels, type EventCardLabels, type EventDateBadgeFormatters, type EventEntryFee, type EventFormatters, type EventListRowLabels, type EventPerformerRef, type EventPreview, type EventTagRef, resolveEventEntryFee };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* EventsEmptyState — "nothing on in this city".
|
|
6
|
+
*
|
|
7
|
+
* BUILT FRESH, NOT COMPOSED FROM `empty-state.tsx`: that component's actions
|
|
8
|
+
* only take `onClick` (no `href`, no per-action icon, no link-component
|
|
9
|
+
* injection), and its buttons render through the shared `<Button>` variants,
|
|
10
|
+
* none of which produce this design's `bg-foreground`/`text-background`
|
|
11
|
+
* primary chip. Both actions here need to render either an `<a>` (via the
|
|
12
|
+
* same injected `linkComponent` contract as the rest of `Events/*`) or a
|
|
13
|
+
* `<button>`, which `empty-state.tsx` has no path for. Reusing it would mean
|
|
14
|
+
* bolting a second, incompatible action API onto it — the "compose it if it
|
|
15
|
+
* fits" bar isn't met here.
|
|
16
|
+
*
|
|
17
|
+
* Illustration follows the DS image-fallback rule: a real photo when
|
|
18
|
+
* `illustrationUrl` is given, otherwise a tinted gradient circle with a
|
|
19
|
+
* drop-shadowed icon — never a bare icon on a flat background.
|
|
20
|
+
*/
|
|
21
|
+
type EventsEmptyStateLinkProps = {
|
|
22
|
+
href: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
onClick?: (mouseEvent: React.MouseEvent) => void;
|
|
26
|
+
};
|
|
27
|
+
type EventsEmptyStateAction = {
|
|
28
|
+
label: string;
|
|
29
|
+
icon?: ReactNode;
|
|
30
|
+
href?: string;
|
|
31
|
+
onClick?: () => void;
|
|
32
|
+
};
|
|
33
|
+
type EventsEmptyStateProps = {
|
|
34
|
+
title: string;
|
|
35
|
+
body: string;
|
|
36
|
+
illustrationUrl?: string;
|
|
37
|
+
primaryAction?: EventsEmptyStateAction;
|
|
38
|
+
secondaryAction?: EventsEmptyStateAction;
|
|
39
|
+
linkComponent?: React.ComponentType<EventsEmptyStateLinkProps>;
|
|
40
|
+
className?: string;
|
|
41
|
+
};
|
|
42
|
+
declare function EventsEmptyState({ title, body, illustrationUrl, primaryAction, secondaryAction, linkComponent: LinkComponent, className, }: EventsEmptyStateProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
|
|
44
|
+
export { EventsEmptyState, type EventsEmptyStateAction, type EventsEmptyStateLinkProps, type EventsEmptyStateProps };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
2
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
3
|
+
import { CalendarOff } from 'lucide-react';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
function DefaultLink(_a) {
|
|
7
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
8
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
9
|
+
}
|
|
10
|
+
var ACTION_BASE = "inline-flex h-11 w-full items-center justify-center gap-2 rounded-xl border px-5 text-sm font-semibold transition-colors duration-150 motion-reduce:transition-none cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 sm:w-auto";
|
|
11
|
+
var PRIMARY_ACTION_CLASSES = cn(
|
|
12
|
+
ACTION_BASE,
|
|
13
|
+
"border-foreground bg-foreground text-background hover:bg-foreground/90"
|
|
14
|
+
);
|
|
15
|
+
var SECONDARY_ACTION_CLASSES = cn(
|
|
16
|
+
ACTION_BASE,
|
|
17
|
+
"border-border bg-background text-foreground hover:bg-muted"
|
|
18
|
+
);
|
|
19
|
+
function renderAction(action, classes, LinkComponent) {
|
|
20
|
+
if (!action) return null;
|
|
21
|
+
if (action.href) {
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
LinkComponent,
|
|
24
|
+
{
|
|
25
|
+
href: action.href,
|
|
26
|
+
className: classes,
|
|
27
|
+
onClick: action.onClick ? () => {
|
|
28
|
+
var _a;
|
|
29
|
+
return (_a = action.onClick) == null ? void 0 : _a.call(action);
|
|
30
|
+
} : void 0,
|
|
31
|
+
children: [
|
|
32
|
+
action.icon,
|
|
33
|
+
action.label
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return /* @__PURE__ */ jsxs("button", { type: "button", className: classes, onClick: action.onClick, children: [
|
|
39
|
+
action.icon,
|
|
40
|
+
action.label
|
|
41
|
+
] });
|
|
42
|
+
}
|
|
43
|
+
function EventsEmptyState({
|
|
44
|
+
title,
|
|
45
|
+
body,
|
|
46
|
+
illustrationUrl,
|
|
47
|
+
primaryAction,
|
|
48
|
+
secondaryAction,
|
|
49
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
50
|
+
className
|
|
51
|
+
}) {
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
53
|
+
"div",
|
|
54
|
+
{
|
|
55
|
+
role: "status",
|
|
56
|
+
className: cn("flex flex-col items-center gap-[14px] pt-[44px] pb-[30px] text-center", className),
|
|
57
|
+
children: [
|
|
58
|
+
/* @__PURE__ */ jsx("div", { className: "h-24 w-24 shrink-0 overflow-hidden rounded-full sm:h-[120px] sm:w-[120px]", children: illustrationUrl ? (
|
|
59
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
60
|
+
/* @__PURE__ */ jsx("img", { src: illustrationUrl, alt: "", className: "h-full w-full object-cover" })
|
|
61
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br from-primary/15 to-primary/5", children: /* @__PURE__ */ jsx(CalendarOff, { className: "h-8 w-8 text-primary drop-shadow-sm sm:h-10 sm:w-10", "aria-hidden": true }) }) }),
|
|
62
|
+
/* @__PURE__ */ jsx("h3", { className: "text-[18px] font-extrabold tracking-[-0.02em] sm:text-[20px]", children: title }),
|
|
63
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-[460px] text-[13px] leading-[1.6] text-muted-foreground sm:text-[13.5px]", children: body }),
|
|
64
|
+
(primaryAction || secondaryAction) && /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-[9px] pt-1 sm:w-auto sm:flex-row sm:gap-[10px]", children: [
|
|
65
|
+
renderAction(primaryAction, PRIMARY_ACTION_CLASSES, LinkComponent),
|
|
66
|
+
renderAction(secondaryAction, SECONDARY_ACTION_CLASSES, LinkComponent)
|
|
67
|
+
] })
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { EventsEmptyState };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* FacetGroup — one filter group in the events sidebar or the mobile filter sheet.
|
|
7
|
+
*
|
|
8
|
+
* REAL INPUTS, NOT STYLED DIVS: the DS's `<Checkbox>` (`checkbox.tsx`) is
|
|
9
|
+
* a Radix primitive whose check glyph is fixed at 14px inside a 20px box with
|
|
10
|
+
* no prop to shrink either — this design's 16px box with an 11px glyph can't
|
|
11
|
+
* be reached by styling it from the outside, and this file isn't allowed to
|
|
12
|
+
* edit `checkbox.tsx` itself. So each row is a visually-hidden native
|
|
13
|
+
* `<input>` — real semantics, real keyboard/Enter/Space/arrow behaviour, a
|
|
14
|
+
* real accessibility-tree checked state — paired with a decorative box that
|
|
15
|
+
* carries the visual design. `peer`/`peer-focus-visible` puts a visible focus
|
|
16
|
+
* ring on the decorative box when the hidden input has focus.
|
|
17
|
+
*
|
|
18
|
+
* SELECTION MODE IS PART OF THE CONTRACT: `selection="single"` renders real
|
|
19
|
+
* radios sharing one `name`, so the browser and the accessibility tree both
|
|
20
|
+
* know only one value can be live. Rendering checkboxes for a single-valued
|
|
21
|
+
* facet (the events list's WHEN scope, for one) makes the control lie: ticking
|
|
22
|
+
* one option silently unticks another, and a screen reader announces N
|
|
23
|
+
* independent checkboxes one of which is inexplicably always checked.
|
|
24
|
+
*
|
|
25
|
+
* The group itself is a `<fieldset>`/`<legend>`, the correct native pairing
|
|
26
|
+
* for "here is a caption for this set of options" — screen readers announce
|
|
27
|
+
* the group title once per option instead of the title being lost as a bare
|
|
28
|
+
* visual label.
|
|
29
|
+
*/
|
|
30
|
+
type FacetOption = {
|
|
31
|
+
id: string;
|
|
32
|
+
label: string;
|
|
33
|
+
count: number;
|
|
34
|
+
checked?: boolean;
|
|
35
|
+
};
|
|
36
|
+
type FacetGroupSelection = "multiple" | "single";
|
|
37
|
+
/**
|
|
38
|
+
* Row density. `comfortable` is the default because this component also renders
|
|
39
|
+
* inside the mobile filter sheet, where a row has to clear the 44px WCAG 2.5.5
|
|
40
|
+
* target floor. `compact` is opt-in, for a dense pointer-driven sidebar.
|
|
41
|
+
*/
|
|
42
|
+
type FacetGroupSize = "compact" | "comfortable";
|
|
43
|
+
type FacetGroupProps = Omit<ComponentPropsWithoutRef<"fieldset">, "title" | "children" | "onToggle"> & {
|
|
44
|
+
title: string;
|
|
45
|
+
/** Decorative glyph in the legend, before the title. The title stays the accessible name. */
|
|
46
|
+
icon?: LucideIcon;
|
|
47
|
+
options: FacetOption[];
|
|
48
|
+
/**
|
|
49
|
+
* `"multiple"` (default) renders checkboxes. `"single"` renders radios sharing
|
|
50
|
+
* one `name` — use it for any facet whose model holds exactly one value.
|
|
51
|
+
*/
|
|
52
|
+
selection?: FacetGroupSelection;
|
|
53
|
+
/**
|
|
54
|
+
* The radio group's shared `name` when `selection="single"`. Optional: a
|
|
55
|
+
* per-instance name is derived from `useId()` when omitted, so radios can
|
|
56
|
+
* never leak into a neighbouring group. Pass one when the fieldset is
|
|
57
|
+
* submitted as part of a native form and the name has to be stable.
|
|
58
|
+
*/
|
|
59
|
+
name?: string;
|
|
60
|
+
size?: FacetGroupSize;
|
|
61
|
+
/**
|
|
62
|
+
* Turns the legend into a disclosure toggle for this group's options.
|
|
63
|
+
*
|
|
64
|
+
* Off by default: a sidebar of permanently-open groups is the right default
|
|
65
|
+
* for a facet panel, because seeing which dimensions exist is most of what
|
|
66
|
+
* the panel is for. Turn it on where several groups are long enough that
|
|
67
|
+
* scanning them costs more than opening one — the events list's five, with
|
|
68
|
+
* venues running to dozens of options.
|
|
69
|
+
*/
|
|
70
|
+
collapsible?: boolean;
|
|
71
|
+
/** Only meaningful with `collapsible`. Groups open by default. */
|
|
72
|
+
defaultOpen?: boolean;
|
|
73
|
+
/** Fires with `(id, true)` for the newly picked radio under `selection="single"`. */
|
|
74
|
+
onToggle?: (id: string, checked: boolean) => void;
|
|
75
|
+
/** e.g. "Show all". Omit to hide the trailing action. */
|
|
76
|
+
moreLabel?: string;
|
|
77
|
+
onMore?: () => void;
|
|
78
|
+
className?: string;
|
|
79
|
+
};
|
|
80
|
+
declare function FacetGroup({ title, icon: Icon, options, selection, name, size, collapsible, defaultOpen, onToggle, moreLabel, onMore, className, ...rest }: FacetGroupProps): react_jsx_runtime.JSX.Element;
|
|
81
|
+
type FacetGroupSkeletonProps = {
|
|
82
|
+
/** How many option rows to draw — match the expected/last-known option count. */
|
|
83
|
+
optionCount?: number;
|
|
84
|
+
/** Shows the trailing "more" placeholder, matching `FacetGroupProps["moreLabel"]`. */
|
|
85
|
+
showMore?: boolean;
|
|
86
|
+
/** Must match the `<FacetGroup>` it stands in for, or rows shift when data lands. */
|
|
87
|
+
size?: FacetGroupSize;
|
|
88
|
+
className?: string;
|
|
89
|
+
};
|
|
90
|
+
/** Mirrors `<FacetGroup>`'s geometry exactly so nothing shifts when data lands. */
|
|
91
|
+
declare function FacetGroupSkeleton({ optionCount, showMore, size, className, }: FacetGroupSkeletonProps): react_jsx_runtime.JSX.Element;
|
|
92
|
+
|
|
93
|
+
export { FacetGroup, type FacetGroupProps, type FacetGroupSelection, type FacetGroupSize, FacetGroupSkeleton, type FacetGroupSkeletonProps, type FacetOption };
|