@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.
Files changed (157) hide show
  1. package/dist/chunk-637L7XQ3.js +85 -0
  2. package/dist/chunk-6XMYEZVR.js +26 -0
  3. package/dist/chunk-DNSXMHYX.js +8 -0
  4. package/dist/chunk-FDITZ2VM.js +16 -0
  5. package/dist/chunk-GPH6L4DX.js +55 -0
  6. package/dist/{chunk-N2KNBPAQ.js → chunk-QXHMPJ35.js} +1 -0
  7. package/dist/chunk-TT3WFS6S.js +379 -0
  8. package/dist/chunk-UXWAOISG.js +170 -0
  9. package/dist/{chunk-UZ3PCIE5.js → chunk-XWMRABZW.js} +2 -2
  10. package/dist/{chunk-VBA45GLP.js → chunk-ZDAHKI3A.js} +12 -2
  11. package/dist/components/alert.d.ts +2 -2
  12. package/dist/components/badge.d.ts +1 -1
  13. package/dist/components/button.d.ts +1 -1
  14. package/dist/components/card.d.ts +2 -2
  15. package/dist/components/card.js +4 -127
  16. package/dist/components/category-deck.d.ts +50 -0
  17. package/dist/components/category-deck.js +128 -0
  18. package/dist/components/command.d.ts +15 -15
  19. package/dist/components/concept-card.d.ts +43 -0
  20. package/dist/components/concept-card.js +81 -0
  21. package/dist/components/coverflow.d.ts +123 -0
  22. package/dist/components/coverflow.js +300 -0
  23. package/dist/components/date-input.d.ts +1 -1
  24. package/dist/components/day-rail-chip.d.ts +82 -0
  25. package/dist/components/day-rail-chip.js +105 -0
  26. package/dist/components/drift-rail.d.ts +28 -0
  27. package/dist/components/drift-rail.js +3 -0
  28. package/dist/components/event-card.d.ts +97 -0
  29. package/dist/components/event-card.js +161 -0
  30. package/dist/components/event-compact-row.d.ts +72 -0
  31. package/dist/components/event-compact-row.js +133 -0
  32. package/dist/components/event-day-header.d.ts +65 -0
  33. package/dist/components/event-day-header.js +95 -0
  34. package/dist/components/event-day-separator.d.ts +46 -0
  35. package/dist/components/event-day-separator.js +50 -0
  36. package/dist/components/event-detail-panel.d.ts +80 -0
  37. package/dist/components/event-detail-panel.js +251 -0
  38. package/dist/components/event-hub-hero.d.ts +34 -0
  39. package/dist/components/event-hub-hero.js +57 -0
  40. package/dist/components/event-index-row.d.ts +68 -0
  41. package/dist/components/event-index-row.js +84 -0
  42. package/dist/components/event-list-row-wide.d.ts +101 -0
  43. package/dist/components/event-list-row-wide.js +218 -0
  44. package/dist/components/event-list-row.d.ts +82 -0
  45. package/dist/components/event-list-row.js +173 -0
  46. package/dist/components/event-poster-fallback.d.ts +20 -0
  47. package/dist/components/event-poster-fallback.js +3 -0
  48. package/dist/components/event-types.d.ts +118 -0
  49. package/dist/components/event-types.js +2 -0
  50. package/dist/components/events-empty-state.d.ts +44 -0
  51. package/dist/components/events-empty-state.js +73 -0
  52. package/dist/components/facet-group.d.ts +93 -0
  53. package/dist/components/facet-group.js +222 -0
  54. package/dist/components/featured-rail-item.d.ts +42 -0
  55. package/dist/components/featured-rail-item.js +88 -0
  56. package/dist/components/hour-band.d.ts +68 -0
  57. package/dist/components/hour-band.js +53 -0
  58. package/dist/components/hour-group.d.ts +67 -0
  59. package/dist/components/hour-group.js +59 -0
  60. package/dist/components/item.d.ts +2 -2
  61. package/dist/components/motion-icon-controls.js +3 -379
  62. package/dist/components/multi-select-combobox.js +1 -1
  63. package/dist/components/place-about-tab.js +3 -3
  64. package/dist/components/place-details-mobile.js +3 -3
  65. package/dist/components/poster-stack.d.ts +30 -0
  66. package/dist/components/poster-stack.js +104 -0
  67. package/dist/components/poster-tape.d.ts +36 -0
  68. package/dist/components/poster-tape.js +64 -0
  69. package/dist/components/poster-wall.d.ts +25 -0
  70. package/dist/components/poster-wall.js +183 -0
  71. package/dist/components/quiet-stretch.d.ts +43 -0
  72. package/dist/components/quiet-stretch.js +54 -0
  73. package/dist/components/section-header.d.ts +53 -0
  74. package/dist/components/section-header.js +63 -0
  75. package/dist/components/segmented-nav.d.ts +62 -0
  76. package/dist/components/segmented-nav.js +54 -0
  77. package/dist/components/skeleton-card.js +1 -1
  78. package/dist/components/skeleton.d.ts +17 -0
  79. package/dist/components/skeleton.js +1 -1
  80. package/dist/components/spinner.d.ts +2 -2
  81. package/dist/components/tag-group.js +3 -26
  82. package/dist/components/tag.d.ts +2 -2
  83. package/dist/components/tag.js +1 -1
  84. package/dist/components/text.d.ts +3 -3
  85. package/dist/components/timeline-band.d.ts +46 -0
  86. package/dist/components/timeline-band.js +5 -0
  87. package/dist/components/venue-events-block.d.ts +143 -0
  88. package/dist/components/venue-events-block.js +222 -0
  89. package/dist/components/weekend-panel.d.ts +44 -0
  90. package/dist/components/weekend-panel.js +231 -0
  91. package/package.json +1 -1
  92. package/src/components/card.tsx +44 -1
  93. package/src/components/category-deck.stories.tsx +96 -0
  94. package/src/components/category-deck.tsx +227 -0
  95. package/src/components/concept-card.stories.tsx +80 -0
  96. package/src/components/concept-card.tsx +143 -0
  97. package/src/components/coverflow.stories.tsx +240 -0
  98. package/src/components/coverflow.test.tsx +193 -0
  99. package/src/components/coverflow.tsx +550 -0
  100. package/src/components/day-rail-chip.stories.tsx +145 -0
  101. package/src/components/day-rail-chip.tsx +201 -0
  102. package/src/components/drift-rail.stories.tsx +92 -0
  103. package/src/components/drift-rail.tsx +86 -0
  104. package/src/components/event-card.stories.tsx +248 -0
  105. package/src/components/event-card.test.tsx +177 -0
  106. package/src/components/event-card.tsx +321 -0
  107. package/src/components/event-compact-row.stories.tsx +221 -0
  108. package/src/components/event-compact-row.tsx +268 -0
  109. package/src/components/event-day-header.stories.tsx +124 -0
  110. package/src/components/event-day-header.tsx +180 -0
  111. package/src/components/event-day-separator.stories.tsx +70 -0
  112. package/src/components/event-day-separator.tsx +95 -0
  113. package/src/components/event-detail-panel.stories.tsx +191 -0
  114. package/src/components/event-detail-panel.tsx +360 -0
  115. package/src/components/event-hub-hero.stories.tsx +66 -0
  116. package/src/components/event-hub-hero.tsx +110 -0
  117. package/src/components/event-index-row.stories.tsx +116 -0
  118. package/src/components/event-index-row.tsx +181 -0
  119. package/src/components/event-list-row-wide.stories.tsx +204 -0
  120. package/src/components/event-list-row-wide.tsx +386 -0
  121. package/src/components/event-list-row.stories.tsx +159 -0
  122. package/src/components/event-list-row.tsx +335 -0
  123. package/src/components/event-poster-fallback.stories.tsx +52 -0
  124. package/src/components/event-poster-fallback.tsx +31 -0
  125. package/src/components/event-types.ts +133 -0
  126. package/src/components/events-empty-state.stories.tsx +59 -0
  127. package/src/components/events-empty-state.tsx +131 -0
  128. package/src/components/facet-group.stories.tsx +247 -0
  129. package/src/components/facet-group.test.tsx +225 -0
  130. package/src/components/facet-group.tsx +352 -0
  131. package/src/components/featured-rail-item.stories.tsx +86 -0
  132. package/src/components/featured-rail-item.tsx +153 -0
  133. package/src/components/hour-band.stories.tsx +159 -0
  134. package/src/components/hour-band.tsx +126 -0
  135. package/src/components/hour-group.stories.tsx +100 -0
  136. package/src/components/hour-group.tsx +124 -0
  137. package/src/components/poster-stack.stories.tsx +68 -0
  138. package/src/components/poster-stack.tsx +155 -0
  139. package/src/components/poster-tape.stories.tsx +70 -0
  140. package/src/components/poster-tape.tsx +122 -0
  141. package/src/components/poster-wall.stories.tsx +95 -0
  142. package/src/components/poster-wall.tsx +220 -0
  143. package/src/components/quiet-stretch.stories.tsx +62 -0
  144. package/src/components/quiet-stretch.tsx +115 -0
  145. package/src/components/section-header.stories.tsx +70 -0
  146. package/src/components/section-header.tsx +134 -0
  147. package/src/components/segmented-nav.stories.tsx +118 -0
  148. package/src/components/segmented-nav.tsx +113 -0
  149. package/src/components/skeleton.tsx +18 -0
  150. package/src/components/tag.tsx +11 -1
  151. package/src/components/timeline-band.stories.tsx +206 -0
  152. package/src/components/timeline-band.tsx +207 -0
  153. package/src/components/venue-events-block.stories.tsx +216 -0
  154. package/src/components/venue-events-block.tsx +408 -0
  155. package/src/components/weekend-panel.stories.tsx +92 -0
  156. package/src/components/weekend-panel.tsx +322 -0
  157. package/src/globals.css +19 -0
@@ -0,0 +1,222 @@
1
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
2
+ import { cn } from '../chunk-FEK5XGZW.js';
3
+ import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
4
+ import { useId, useState } from 'react';
5
+ import { ChevronDown, Check } from 'lucide-react';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ var ROW_SIZE = {
9
+ comfortable: "-mx-2 min-h-11 rounded-lg px-2",
10
+ compact: "-mx-2 rounded-lg px-2 py-1"
11
+ };
12
+ var ROW_GAP = {
13
+ comfortable: "gap-[9px]",
14
+ compact: "gap-[2px]"
15
+ };
16
+ function FacetGroup(_a) {
17
+ var _b = _a, {
18
+ title,
19
+ icon: Icon,
20
+ options,
21
+ selection = "multiple",
22
+ name,
23
+ size = "comfortable",
24
+ collapsible = false,
25
+ defaultOpen = true,
26
+ onToggle,
27
+ moreLabel,
28
+ onMore,
29
+ className
30
+ } = _b, rest = __objRest(_b, [
31
+ "title",
32
+ "icon",
33
+ "options",
34
+ "selection",
35
+ "name",
36
+ "size",
37
+ "collapsible",
38
+ "defaultOpen",
39
+ "onToggle",
40
+ "moreLabel",
41
+ "onMore",
42
+ "className"
43
+ ]);
44
+ const isSingle = selection === "single";
45
+ const regionId = useId();
46
+ const [isOpen, setIsOpen] = useState(defaultOpen);
47
+ const open = collapsible ? isOpen : true;
48
+ const setOpen = setIsOpen;
49
+ const fallbackName = useId();
50
+ const groupName = name != null ? name : fallbackName;
51
+ return (
52
+ // NO SEPARATOR OF ITS OWN. This drew `<Separator className="absolute
53
+ // inset-x-0 top-0" />`, which looked like a rule above each group and was
54
+ // not: a `<legend>` is laid out in the fieldset's border box, so the
55
+ // padding box — and therefore `top-0` — begins BELOW it. The rule landed
56
+ // between a group's own label and its own options, cutting each group in
57
+ // half rather than dividing one from the next, and the first group got one
58
+ // above nothing.
59
+ //
60
+ // A divider between siblings belongs to whatever owns the order, and that
61
+ // is the caller — it alone knows which group is first and which groups
62
+ // rendered at all. So the caller interleaves `<Separator />` between
63
+ // groups; this component draws none.
64
+ /* @__PURE__ */ jsxs("fieldset", __spreadProps(__spreadValues({ className: cn("m-0 border-0 p-0 py-3", className) }, rest), { children: [
65
+ /* @__PURE__ */ jsx(
66
+ "legend",
67
+ {
68
+ className: cn(
69
+ "block w-full p-0 text-[11.5px] font-extrabold tracking-[0.1em] text-muted-foreground uppercase",
70
+ collapsible ? "mb-0" : "mb-[11px]"
71
+ ),
72
+ children: collapsible ? (
73
+ // The legend becomes the toggle. It stays inside `<legend>` so the
74
+ // fieldset keeps its accessible name and screen readers still
75
+ // announce the group per option; `aria-expanded` and the region id
76
+ // carry the disclosure on top of that rather than replacing it.
77
+ /* @__PURE__ */ jsxs(
78
+ "button",
79
+ {
80
+ type: "button",
81
+ onClick: () => setOpen((previous) => !previous),
82
+ "aria-expanded": open,
83
+ "aria-controls": regionId,
84
+ className: cn(
85
+ "-mx-2 flex w-[calc(100%+1rem)] cursor-pointer items-center gap-1.5 rounded-lg px-2 py-1.5 text-left uppercase",
86
+ "transition-[margin,background-color] duration-200 ease-out hover:bg-foreground/5 motion-reduce:transition-none",
87
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
88
+ // The 11px exists to separate this label from ITS OPTIONS, so a
89
+ // collapsed group must not keep it: with the panel at zero
90
+ // height it becomes dead space below the label, and the group
91
+ // reads as `12px | label | 23px` between the caller's dividers.
92
+ // That 11px asymmetry is why an evenly-spaced stack of collapsed
93
+ // groups looked like the rules were mis-centred — they were
94
+ // centred, the labels were not. Transitioned alongside the panel
95
+ // so the two move as one gesture rather than the margin snapping.
96
+ open ? "mb-[11px]" : "mb-0"
97
+ ),
98
+ children: [
99
+ Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3.5 shrink-0", strokeWidth: 2.25 }),
100
+ title,
101
+ /* @__PURE__ */ jsx(
102
+ ChevronDown,
103
+ {
104
+ "aria-hidden": true,
105
+ className: cn(
106
+ "ml-auto size-3.5 shrink-0 transition-transform duration-150 motion-reduce:transition-none",
107
+ open ? "rotate-180" : "rotate-0"
108
+ )
109
+ }
110
+ )
111
+ ]
112
+ }
113
+ )
114
+ ) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
115
+ Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3.5 shrink-0", strokeWidth: 2.25 }),
116
+ title
117
+ ] })
118
+ }
119
+ ),
120
+ /* @__PURE__ */ jsx(
121
+ "div",
122
+ {
123
+ className: cn(
124
+ "grid transition-[grid-template-rows] duration-200 ease-out motion-reduce:transition-none",
125
+ open ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
126
+ ),
127
+ children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx(
128
+ "div",
129
+ {
130
+ id: regionId,
131
+ inert: !open,
132
+ "aria-hidden": !open || void 0,
133
+ className: cn("flex flex-col", ROW_GAP[size]),
134
+ children: options.map((option) => {
135
+ const checked = !!option.checked;
136
+ return /* @__PURE__ */ jsxs(
137
+ "label",
138
+ {
139
+ className: cn(
140
+ "flex cursor-pointer items-center gap-[9px] text-[13.5px] select-none",
141
+ "transition-colors duration-150 hover:bg-muted/60 motion-reduce:transition-none",
142
+ ROW_SIZE[size]
143
+ ),
144
+ children: [
145
+ /* @__PURE__ */ jsx(
146
+ "input",
147
+ {
148
+ type: isSingle ? "radio" : "checkbox",
149
+ name: isSingle ? groupName : void 0,
150
+ className: "peer sr-only",
151
+ checked,
152
+ onChange: (event) => onToggle == null ? void 0 : onToggle(option.id, event.target.checked)
153
+ }
154
+ ),
155
+ /* @__PURE__ */ jsx(
156
+ "span",
157
+ {
158
+ "aria-hidden": true,
159
+ className: cn(
160
+ "flex h-4 w-4 shrink-0 items-center justify-center border-[1.5px] transition-colors duration-150 motion-reduce:transition-none",
161
+ isSingle ? "rounded-full" : "rounded-[5px]",
162
+ "peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
163
+ checked ? "border-primary bg-primary" : "border-border bg-background"
164
+ ),
165
+ children: checked && (isSingle ? /* @__PURE__ */ jsx("span", { className: "size-[7px] rounded-full bg-primary-foreground" }) : /* @__PURE__ */ jsx(Check, { className: "h-[11px] w-[11px] text-primary-foreground", strokeWidth: 3 }))
166
+ }
167
+ ),
168
+ /* @__PURE__ */ jsx(
169
+ "span",
170
+ {
171
+ title: option.label,
172
+ className: cn(
173
+ "min-w-0 truncate",
174
+ checked ? "font-bold text-primary" : "font-normal text-foreground"
175
+ ),
176
+ children: option.label
177
+ }
178
+ ),
179
+ /* @__PURE__ */ jsx("span", { className: "ml-auto shrink-0 font-mono text-[11.5px] tabular-nums text-muted-foreground", children: option.count })
180
+ ]
181
+ },
182
+ option.id
183
+ );
184
+ })
185
+ }
186
+ ) })
187
+ }
188
+ ),
189
+ moreLabel && open && /* @__PURE__ */ jsx(
190
+ "button",
191
+ {
192
+ type: "button",
193
+ onClick: onMore,
194
+ className: "cursor-pointer pt-[11px] text-[12.5px] font-bold text-primary transition-colors duration-150 hover:text-primary/80 motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:rounded-sm",
195
+ children: moreLabel
196
+ }
197
+ )
198
+ ] }))
199
+ );
200
+ }
201
+ function FacetGroupSkeleton({
202
+ optionCount = 4,
203
+ showMore,
204
+ size = "comfortable",
205
+ className
206
+ }) {
207
+ return (
208
+ // Matches the live group: no rule of its own, `py-3`, and the row gap keyed
209
+ // to the same density — or the panel shifts on load by the difference.
210
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: cn("py-3", className), children: [
211
+ /* @__PURE__ */ jsx(Skeleton, { className: "mb-[11px] h-3 w-20" }),
212
+ /* @__PURE__ */ jsx("div", { className: cn("flex flex-col", ROW_GAP[size]), children: Array.from({ length: optionCount }).map((_, index) => /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-[9px]", ROW_SIZE[size]), children: [
213
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-[5px]" }),
214
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-24" }),
215
+ /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-3 w-5" })
216
+ ] }, index)) }),
217
+ showMore && /* @__PURE__ */ jsx(Skeleton, { className: "mt-[11px] h-3 w-16" })
218
+ ] })
219
+ );
220
+ }
221
+
222
+ export { FacetGroup, FacetGroupSkeleton };
@@ -0,0 +1,42 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ /**
5
+ * FeaturedRailItem — the card inside the featured rail (a `<DriftRail>` or a
6
+ * plain scroll snap row). Narrower than `<EventCard>`'s 4:3 base card and
7
+ * built for a single overlay pill rather than the badge-plus-fee pair, so it
8
+ * stays legible at 150–196px wide.
9
+ *
10
+ * The only motion here is the interactive hover/press lift shared by every DS
11
+ * card (see `card.tsx`) — the source design's continuous drift animation
12
+ * belongs to the rail this card sits inside, not the card itself.
13
+ */
14
+ type FeaturedRailItemLinkProps = {
15
+ href: string;
16
+ className?: string;
17
+ children: React.ReactNode;
18
+ onClick?: (mouseEvent: React.MouseEvent) => void;
19
+ };
20
+ type FeaturedRailItemProps = {
21
+ title: string;
22
+ venue: string;
23
+ /** Caller-composed day/time line, e.g. "Sat 5 · 20:00". */
24
+ note: string;
25
+ /** Top-left overlay pill. Include its icon. */
26
+ tagLabel?: React.ReactNode;
27
+ posterUrl?: string;
28
+ href?: string;
29
+ /** Defaults to a plain `<a>`. Pass `next/link` in a Next app. */
30
+ linkComponent?: React.ComponentType<FeaturedRailItemLinkProps>;
31
+ onClick?: (mouseEvent: React.MouseEvent) => void;
32
+ className?: string;
33
+ /** Rendered instead of the poster when it's absent or failed to load. Defaults to `<EventPosterFallback />`. */
34
+ fallback?: ReactNode;
35
+ };
36
+ declare function FeaturedRailItem({ title, venue, note, tagLabel, posterUrl, href, linkComponent: LinkComponent, onClick, className, fallback, }: FeaturedRailItemProps): react_jsx_runtime.JSX.Element;
37
+ type FeaturedRailItemSkeletonProps = {
38
+ className?: string;
39
+ };
40
+ declare function FeaturedRailItemSkeleton({ className }: FeaturedRailItemSkeletonProps): react_jsx_runtime.JSX.Element;
41
+
42
+ export { FeaturedRailItem, type FeaturedRailItemLinkProps, type FeaturedRailItemProps, FeaturedRailItemSkeleton, type FeaturedRailItemSkeletonProps };
@@ -0,0 +1,88 @@
1
+ import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
2
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
3
+ import { cn } from '../chunk-FEK5XGZW.js';
4
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
5
+ import { useState } from 'react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ function DefaultLink(_a) {
9
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
10
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
11
+ }
12
+ function FeaturedRailItem({
13
+ title,
14
+ venue,
15
+ note,
16
+ tagLabel,
17
+ posterUrl,
18
+ href,
19
+ linkComponent: LinkComponent = DefaultLink,
20
+ onClick,
21
+ className,
22
+ fallback = /* @__PURE__ */ jsx(EventPosterFallback, {})
23
+ }) {
24
+ const [imgError, setImgError] = useState(false);
25
+ const showPoster = !!posterUrl && !imgError;
26
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
27
+ /* @__PURE__ */ jsxs("div", { className: "relative aspect-[4/3] overflow-hidden rounded-xl bg-muted", children: [
28
+ showPoster ? (
29
+ // eslint-disable-next-line @next/next/no-img-element
30
+ /* @__PURE__ */ jsx(
31
+ "img",
32
+ {
33
+ src: posterUrl,
34
+ alt: title,
35
+ title,
36
+ loading: "lazy",
37
+ decoding: "async",
38
+ className: "h-full w-full object-cover",
39
+ onError: () => setImgError(true)
40
+ }
41
+ )
42
+ ) : fallback,
43
+ tagLabel && /* @__PURE__ */ jsx("div", { className: "absolute inset-x-2 top-2 flex items-start", children: /* @__PURE__ */ jsx("span", { className: "inline-flex h-6 max-w-full items-center gap-1 rounded-full bg-black/55 px-2 text-[11px] font-semibold text-white", children: tagLabel }) })
44
+ ] }),
45
+ /* @__PURE__ */ jsxs("div", { className: "px-0.5 pt-2.5", children: [
46
+ /* @__PURE__ */ jsx("h3", { className: "text-[13.5px] font-bold leading-[1.3]", children: title }),
47
+ /* @__PURE__ */ jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: venue }),
48
+ /* @__PURE__ */ jsx("div", { className: "mt-[3px] truncate text-[11.5px] font-bold text-primary", children: note })
49
+ ] })
50
+ ] });
51
+ const isInteractive = !!(href || onClick);
52
+ const rootClasses = cn(
53
+ "flex w-[150px] shrink-0 flex-col text-left sm:w-[196px]",
54
+ isInteractive && cn(
55
+ "cursor-pointer rounded-xl transition-transform duration-200",
56
+ "hover:-translate-y-0.5 active:scale-[0.99]",
57
+ "motion-reduce:transition-none motion-reduce:hover:translate-y-0",
58
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
59
+ ),
60
+ className
61
+ );
62
+ if (href) {
63
+ return /* @__PURE__ */ jsx(LinkComponent, { href, className: rootClasses, onClick, children: body });
64
+ }
65
+ if (onClick) {
66
+ return /* @__PURE__ */ jsx("button", { type: "button", className: rootClasses, onClick, children: body });
67
+ }
68
+ return /* @__PURE__ */ jsx("div", { className: rootClasses, children: body });
69
+ }
70
+ function FeaturedRailItemSkeleton({ className }) {
71
+ return /* @__PURE__ */ jsxs(
72
+ "div",
73
+ {
74
+ "aria-hidden": "true",
75
+ className: cn("flex w-[150px] shrink-0 flex-col sm:w-[196px]", className),
76
+ children: [
77
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[4/3] w-full rounded-xl" }),
78
+ /* @__PURE__ */ jsxs("div", { className: "px-0.5 pt-2.5", children: [
79
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[13.5px] w-4/5" }),
80
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1 h-3 w-3/5" }),
81
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-[11.5px] w-2/5" })
82
+ ] })
83
+ ]
84
+ }
85
+ );
86
+ }
87
+
88
+ export { FeaturedRailItem, FeaturedRailItemSkeleton };
@@ -0,0 +1,68 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * HourBand — one hour on the timeline's axis, with that hour's events beside it.
6
+ *
7
+ * The design's mobile timeline is deliberately plain: a 52px gutter carrying a
8
+ * hairline and an hour puck, and full-width cards to its right. Nothing else.
9
+ * The cards inside carry no timestamp of their own — this band IS the
10
+ * timestamp, which is why `EventCard` has `hideWhen`.
11
+ *
12
+ * The puck is the shared `TimelineHourPuck`, sitting ON the line with a
13
+ * background fill punching through it so the hairline reads as continuous
14
+ * behind the label. It used to be a second, hand-rolled copy of the desktop
15
+ * band's puck, and the two had drifted apart — this one borderless and
16
+ * `rounded-md px-1.5`, the desktop one bordered and `rounded-[7px] px-[7px]`.
17
+ * Sharing the object changes this gutter's pixels: the mobile puck now carries
18
+ * a border, which is what makes it read as clipped onto the rail rather than
19
+ * text floating over it, and matches the station on the desktop spine.
20
+ *
21
+ * `isLive` turns the puck green-bordered and reveals the NOW label beneath it,
22
+ * which is why live bands need extra top padding on the content column — the
23
+ * label occupies space the cards would otherwise start in.
24
+ *
25
+ * ── Why the gap is 24px and not 12px ──────────────────────────────────────
26
+ * THE PUCK IS WIDER THAN THE GUTTER IT SITS IN, and the gap is what absorbs
27
+ * the difference. A 12-hour locale renders "09:00 AM" — 8 monospace glyphs at
28
+ * 12.5px (~60px) plus `px-[7px]` twice and a border twice, so ~76px of puck
29
+ * centred on a 52px gutter, overflowing ~12px on each side. At the original
30
+ * `gap-3` that right-hand overflow exactly consumed the gap and the puck's
31
+ * edge landed ON the content column, which read as the hairline colliding
32
+ * with the rows beside it.
33
+ *
34
+ * 24px leaves ~12px of real clearance at the widest label. Do not tighten it
35
+ * back without re-deriving against a 12-hour locale: a 24-hour one ("09:00")
36
+ * is ~30px narrower and hides the problem completely.
37
+ */
38
+ type HourBandProps = {
39
+ /** The hour, already formatted and localised — "20:00". */
40
+ time: string;
41
+ /** Marks this as the hour currently in progress. */
42
+ isLive?: boolean;
43
+ /** Shown under the puck when live, e.g. "NOW ON". Required to render it. */
44
+ liveLabel?: string;
45
+ /** The hour's events. */
46
+ children: React.ReactNode;
47
+ className?: string;
48
+ };
49
+ declare function HourBand({ time, isLive, liveLabel, children, className }: HourBandProps): react_jsx_runtime.JSX.Element;
50
+ type HourBandSkeletonProps = {
51
+ /** Cards to reserve space for. The design's loading frame shows two. */
52
+ cardCount?: number;
53
+ className?: string;
54
+ };
55
+ /**
56
+ * The mobile timeline's loading state, mirroring `HourBand` exactly — same 52px
57
+ * gutter, same hairline, same content gap — so nothing jumps when data lands.
58
+ *
59
+ * The puck is omitted rather than blocked out: the hour is not known until the
60
+ * data arrives, and a grey lozenge on the axis reads as a real, unreadable
61
+ * timestamp. The hairline alone still says "this is a time axis".
62
+ *
63
+ * Card footprints come from the design's own loading frame: a 220px poster with
64
+ * an 16px title bar and a 12px meta bar, then a 170px poster with a 15px title.
65
+ */
66
+ declare function HourBandSkeleton({ cardCount, className }: HourBandSkeletonProps): react_jsx_runtime.JSX.Element;
67
+
68
+ export { HourBand, type HourBandProps, HourBandSkeleton, type HourBandSkeletonProps };
@@ -0,0 +1,53 @@
1
+ import { TimelineHourPuck } from '../chunk-637L7XQ3.js';
2
+ import { Separator } from '../chunk-WSRWFA6K.js';
3
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
4
+ import { cn } from '../chunk-FEK5XGZW.js';
5
+ import '../chunk-3GWWZKX7.js';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ function HourBand({ time, isLive, liveLabel, children, className }) {
9
+ const showLiveLabel = isLive && !!liveLabel;
10
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-6 pt-[18px]", className), children: [
11
+ /* @__PURE__ */ jsxs("div", { className: "relative flex w-[52px] shrink-0 justify-center", children: [
12
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-full" }),
13
+ /* @__PURE__ */ jsx(
14
+ TimelineHourPuck,
15
+ {
16
+ time,
17
+ isLive,
18
+ liveLabel,
19
+ className: "absolute left-1/2 top-0 w-0"
20
+ }
21
+ )
22
+ ] }),
23
+ /* @__PURE__ */ jsx(
24
+ "div",
25
+ {
26
+ className: cn(
27
+ "flex min-w-0 flex-1 flex-col gap-5 pb-2",
28
+ showLiveLabel ? "pt-[34px]" : "pt-1"
29
+ ),
30
+ children
31
+ }
32
+ )
33
+ ] });
34
+ }
35
+ function HourBandSkeleton({ cardCount = 2, className }) {
36
+ const cards = [
37
+ { poster: 220, title: "80%", meta: "50%" },
38
+ { poster: 170, title: "70%", meta: null }
39
+ ];
40
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-6 pt-[18px]", className), "aria-hidden": "true", children: [
41
+ /* @__PURE__ */ jsx("div", { className: "relative flex w-[52px] shrink-0 justify-center", children: /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "h-full" }) }),
42
+ /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 flex-col gap-5 pb-2 pt-1", children: Array.from({ length: cardCount }).map((_, index) => {
43
+ const card = cards[index % cards.length];
44
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5", children: [
45
+ /* @__PURE__ */ jsx(Skeleton, { className: "w-full", style: { height: card.poster } }),
46
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4", style: { width: card.title } }),
47
+ card.meta && /* @__PURE__ */ jsx(Skeleton, { className: "h-3", style: { width: card.meta } })
48
+ ] }, index);
49
+ }) })
50
+ ] });
51
+ }
52
+
53
+ export { HourBand, HourBandSkeleton };
@@ -0,0 +1,67 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * HourGroup — an hour heading inside `<TimelineBand>`'s content column: a
6
+ * time, a rule, and a count, with that hour's rows beneath and an optional
7
+ * trailing note (e.g. "3 more not shown").
8
+ *
9
+ * ── `spine` — the desktop default ────────────────────────────────────────
10
+ * Inside a `<TimelineBand>`, pass `spine` and the hour renders as a
11
+ * `TimelineHourPuck` out ON the axis, centred on this heading's own rule,
12
+ * instead of as inline text at the head of the content column. Every hour of
13
+ * the day then reads as a station on the same rail, which is the entire point
14
+ * of the rail; the band itself no longer draws any hour, so there is nothing
15
+ * left to collide with.
16
+ *
17
+ * The offset is `calc(var(--timeline-spine) * -0.5)` — half the gutter, back
18
+ * from the content column's left edge, which lands on the hairline. Written
19
+ * with `*-0.5` rather than `/2` because a slash inside a Tailwind arbitrary
20
+ * value collides with the opacity-modifier parse. The `w-0` wrapper plus the
21
+ * puck's own `justify-center` is what centres it, so no `-translate-x-1/2` is
22
+ * needed and the live label centres on the same line for free.
23
+ *
24
+ * `spine` implies the inline time is suppressed — no caller should have to
25
+ * pass `spine hideTime` to avoid printing the hour twice.
26
+ *
27
+ * Keep the rule and the count. The rule is the only thing tying a puck to the
28
+ * rows it labels across the empty gutter; without it the puck reads as a
29
+ * sticker rather than a station on a track. The count carries information
30
+ * present nowhere else and terminates the rule so it doesn't dissolve.
31
+ *
32
+ * Where a group's rows already carry their own timestamps, `hideTime` drops the
33
+ * time and keeps the rule and count, so the hour is stated exactly once.
34
+ *
35
+ * `size="sm"` is the mobile variant, shrinking the type a half-step and
36
+ * matching `<HourBand>`'s own top offset (18px) rather than the desktop
37
+ * default (22px).
38
+ */
39
+ type HourGroupProps = {
40
+ /** The hour, already formatted and localised — "20:00". */
41
+ time: string;
42
+ /**
43
+ * Render the hour as a puck out on `<TimelineBand>`'s spine instead of as
44
+ * inline text. The desktop timeline's default; implies `hideTime`.
45
+ */
46
+ spine?: boolean;
47
+ /** Marks this hour as the one currently in progress. `spine` only. */
48
+ isLive?: boolean;
49
+ /** Shown under the puck when live, e.g. "NOW". `spine` only. */
50
+ liveLabel?: string;
51
+ /**
52
+ * Drop the leading time, keeping the rule and count. For groups whose rows
53
+ * state their own time.
54
+ */
55
+ hideTime?: boolean;
56
+ /** Caller-composed, e.g. "2 events" — mirrors `EventDayHeader`'s `meta`. */
57
+ countLabel: string;
58
+ /** Trailing note shown after the rows, e.g. "3 more not shown". */
59
+ note?: string;
60
+ /** The hour's rows. */
61
+ children?: React.ReactNode;
62
+ size?: "sm" | "default";
63
+ className?: string;
64
+ };
65
+ declare function HourGroup({ time, spine, isLive, liveLabel, hideTime, countLabel, note, children, size, className, }: HourGroupProps): react_jsx_runtime.JSX.Element;
66
+
67
+ export { HourGroup, type HourGroupProps };
@@ -0,0 +1,59 @@
1
+ import { TimelineHourPuck } from '../chunk-637L7XQ3.js';
2
+ import { Separator } from '../chunk-WSRWFA6K.js';
3
+ import '../chunk-QXHMPJ35.js';
4
+ import { cn } from '../chunk-FEK5XGZW.js';
5
+ import '../chunk-3GWWZKX7.js';
6
+ import { jsxs, jsx } from 'react/jsx-runtime';
7
+
8
+ function HourGroup({
9
+ time,
10
+ spine,
11
+ isLive,
12
+ liveLabel,
13
+ hideTime,
14
+ countLabel,
15
+ note,
16
+ children,
17
+ size = "default",
18
+ className
19
+ }) {
20
+ const isSm = size === "sm";
21
+ return (
22
+ // The group's top padding lives HERE, not on the heading row, and that is
23
+ // load-bearing for `spine`: `top-1/2` resolves against the heading row's
24
+ // PADDING box while `items-center` centres the rule in its CONTENT box, so
25
+ // with `pt-[22px]` on the row the puck landed 19.25px down and the rule at
26
+ // 30.25px — 11px apart, the puck visibly riding above the line it is
27
+ // supposed to sit on. Unpadded, both resolve against the same box and the
28
+ // puck's centre, the rule and the spine hairline are one line.
29
+ /* @__PURE__ */ jsxs("div", { className: cn(isSm ? "pt-[18px]" : "pt-[22px]", className), children: [
30
+ /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-2.5", spine && "relative"), children: [
31
+ spine && /* @__PURE__ */ jsx(
32
+ TimelineHourPuck,
33
+ {
34
+ time,
35
+ isLive,
36
+ liveLabel,
37
+ className: "absolute left-[calc(var(--timeline-spine)*-0.5)] top-1/2 w-0 -translate-y-1/2"
38
+ }
39
+ ),
40
+ !hideTime && !spine && /* @__PURE__ */ jsx(
41
+ "span",
42
+ {
43
+ className: cn(
44
+ "font-mono text-muted-foreground tabular-nums",
45
+ isSm ? "text-[11.5px]" : "text-xs"
46
+ ),
47
+ children: time
48
+ }
49
+ ),
50
+ /* @__PURE__ */ jsx(Separator, { className: "flex-1" }),
51
+ /* @__PURE__ */ jsx("span", { className: cn("text-muted-foreground/70", isSm ? "text-[10.5px]" : "text-[11px]"), children: countLabel })
52
+ ] }),
53
+ children,
54
+ note && /* @__PURE__ */ jsx("p", { className: "pt-[9px] text-[11.5px] text-muted-foreground", children: note })
55
+ ] })
56
+ );
57
+ }
58
+
59
+ export { HourGroup };
@@ -3,12 +3,12 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const itemVariants: (props?: ({
6
- variant?: "default" | "outline" | "muted" | null | undefined;
6
+ variant?: "default" | "muted" | "outline" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  interactive?: boolean | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare const Item: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & VariantProps<(props?: ({
11
- variant?: "default" | "outline" | "muted" | null | undefined;
11
+ variant?: "default" | "muted" | "outline" | null | undefined;
12
12
  size?: "default" | "sm" | "lg" | null | undefined;
13
13
  interactive?: boolean | null | undefined;
14
14
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {