@bearmenu/ui 0.8.51 → 0.8.53

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 (67) hide show
  1. package/dist/chunk-GIJGVFGC.js +177 -0
  2. package/dist/chunk-H4ORZT2D.js +14 -0
  3. package/dist/chunk-RKNPPYRU.js +86 -0
  4. package/dist/{chunk-S5GDARLW.js → chunk-WY2FP76J.js} +28 -5
  5. package/dist/components/category-deck.d.ts +6 -1
  6. package/dist/components/category-deck.js +39 -35
  7. package/dist/components/event-card.d.ts +7 -0
  8. package/dist/components/event-card.js +9 -0
  9. package/dist/components/event-compact-row.d.ts +6 -0
  10. package/dist/components/event-compact-row.js +5 -3
  11. package/dist/components/event-hub-hero.d.ts +5 -0
  12. package/dist/components/event-hub-hero.js +14 -3
  13. package/dist/components/event-list-row-wide.d.ts +6 -0
  14. package/dist/components/event-list-row-wide.js +5 -3
  15. package/dist/components/event-tonight-strip.d.ts +3 -1
  16. package/dist/components/event-tonight-strip.js +3 -2
  17. package/dist/components/facet-group.d.ts +9 -2
  18. package/dist/components/facet-group.js +108 -10
  19. package/dist/components/featured-rail-item.d.ts +3 -1
  20. package/dist/components/featured-rail-item.js +3 -1
  21. package/dist/components/filter-option-chip.d.ts +15 -0
  22. package/dist/components/filter-option-chip.js +3 -0
  23. package/dist/components/filter-option-group.d.ts +37 -0
  24. package/dist/components/filter-option-group.js +10 -0
  25. package/dist/components/filter-panel-footer.d.ts +15 -0
  26. package/dist/components/filter-panel-footer.js +63 -0
  27. package/dist/components/kitchen-dossier.d.ts +7 -1
  28. package/dist/components/kitchen-dossier.js +2 -1
  29. package/dist/components/menu-hub-hero.d.ts +7 -1
  30. package/dist/components/menu-hub-hero.js +2 -0
  31. package/dist/components/place-card.d.ts +7 -0
  32. package/dist/components/place-card.js +11 -3
  33. package/dist/components/poster-stack.d.ts +2 -0
  34. package/dist/components/poster-stack.js +2 -1
  35. package/dist/components/section-header.d.ts +6 -1
  36. package/dist/components/section-header.js +4 -2
  37. package/dist/components/timeline-day.js +1 -1
  38. package/dist/components/timeline-hour.d.ts +13 -1
  39. package/dist/components/timeline-hour.js +1 -1
  40. package/package.json +6 -6
  41. package/src/components/category-deck.tsx +11 -0
  42. package/src/components/event-card.test.tsx +6 -0
  43. package/src/components/event-card.tsx +17 -0
  44. package/src/components/event-compact-row.tsx +34 -24
  45. package/src/components/event-hub-hero.tsx +15 -1
  46. package/src/components/event-list-row-wide.tsx +25 -15
  47. package/src/components/event-tonight-strip.test.tsx +12 -0
  48. package/src/components/event-tonight-strip.tsx +8 -1
  49. package/src/components/facet-group.tsx +153 -54
  50. package/src/components/featured-rail-item.tsx +5 -0
  51. package/src/components/filter-option-chip.test.tsx +50 -0
  52. package/src/components/filter-option-chip.tsx +81 -0
  53. package/src/components/filter-option-group.stories.tsx +250 -0
  54. package/src/components/filter-option-group.test.tsx +100 -0
  55. package/src/components/filter-option-group.tsx +231 -0
  56. package/src/components/filter-panel-footer.tsx +63 -0
  57. package/src/components/kitchen-dossier.tsx +14 -1
  58. package/src/components/menu-hub-hero.tsx +13 -0
  59. package/src/components/place-card.stories.tsx +10 -0
  60. package/src/components/place-card.test.tsx +31 -0
  61. package/src/components/place-card.tsx +22 -6
  62. package/src/components/poster-stack.tsx +3 -0
  63. package/src/components/section-header.tsx +8 -1
  64. package/src/components/timeline-day.stories.tsx +30 -0
  65. package/src/components/timeline-day.test.tsx +24 -0
  66. package/src/components/timeline-hour.tsx +40 -6
  67. package/src/lib/poster-overlay-frame.tsx +26 -0
@@ -0,0 +1,177 @@
1
+ import { FilterOptionChip } from './chunk-RKNPPYRU.js';
2
+ import { Separator } from './chunk-NWZGSLPU.js';
3
+ import { Button } from './chunk-GHKTVHQG.js';
4
+ import { Text } from './chunk-ZLMRVBD7.js';
5
+ import { Badge } from './chunk-RYPPN2GZ.js';
6
+ import { cn } from './chunk-FEK5XGZW.js';
7
+ import { useId, useState } from 'react';
8
+ import { ChevronDown } from 'lucide-react';
9
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
10
+
11
+ function FilterOptionGroup({
12
+ title,
13
+ options,
14
+ value,
15
+ onValueChange,
16
+ selection = "multiple",
17
+ name,
18
+ defaultOpen = false,
19
+ collapsible = true,
20
+ visibleLimit = 6,
21
+ showAllLabel,
22
+ showLessLabel,
23
+ children,
24
+ footer,
25
+ clearLabel,
26
+ onClear,
27
+ summaryValues = value,
28
+ className,
29
+ divider = true,
30
+ disabled
31
+ }) {
32
+ const id = useId();
33
+ const [isOpen, setOpen] = useState(defaultOpen);
34
+ const open = !collapsible || isOpen;
35
+ const [expanded, setExpanded] = useState([]);
36
+ const selected = options.filter(
37
+ (option) => summaryValues.includes(option.value)
38
+ );
39
+ const sections = [];
40
+ for (const option of options) {
41
+ const last = sections[sections.length - 1];
42
+ if (!last || last.label !== option.section)
43
+ sections.push({ label: option.section, options: [option] });
44
+ else last.options.push(option);
45
+ }
46
+ const heading = /* @__PURE__ */ jsxs(Fragment, { children: [
47
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
48
+ /* @__PURE__ */ jsx(
49
+ Text,
50
+ {
51
+ as: "span",
52
+ id: `${id}-title`,
53
+ className: "block",
54
+ variant: "body-sm",
55
+ weight: "semibold",
56
+ children: title
57
+ }
58
+ ),
59
+ !open && selected.length > 0 && /* @__PURE__ */ jsx(
60
+ Text,
61
+ {
62
+ as: "span",
63
+ variant: "caption",
64
+ color: "muted",
65
+ className: "mt-1 block leading-relaxed [overflow-wrap:anywhere]",
66
+ children: selected.map((option) => option.label).join(" \xB7 ")
67
+ }
68
+ )
69
+ ] }),
70
+ selected.length > 0 && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "tabular-nums", children: selected.length }),
71
+ collapsible && /* @__PURE__ */ jsx(
72
+ ChevronDown,
73
+ {
74
+ "aria-hidden": true,
75
+ className: cn(
76
+ "size-4 shrink-0 text-muted-foreground transition-transform motion-reduce:transition-none",
77
+ open && "rotate-180"
78
+ )
79
+ }
80
+ )
81
+ ] });
82
+ return /* @__PURE__ */ jsxs("section", { className: cn("min-w-0", className), children: [
83
+ /* @__PURE__ */ jsxs("div", { className: "py-2", children: [
84
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
85
+ collapsible ? /* @__PURE__ */ jsx(
86
+ "button",
87
+ {
88
+ type: "button",
89
+ onClick: () => setOpen((previous) => !previous),
90
+ "aria-expanded": open,
91
+ "aria-controls": id,
92
+ className: "flex min-h-11 min-w-0 flex-1 items-center gap-3 rounded-lg py-2 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
93
+ children: heading
94
+ }
95
+ ) : /* @__PURE__ */ jsx("div", { className: "flex min-h-11 min-w-0 flex-1 items-center gap-3 py-2", children: heading }),
96
+ onClear && clearLabel && summaryValues.length > 0 && /* @__PURE__ */ jsx(
97
+ Button,
98
+ {
99
+ type: "button",
100
+ variant: "ghost",
101
+ className: "min-h-11 shrink-0 px-2 text-xs",
102
+ onClick: onClear,
103
+ children: clearLabel
104
+ }
105
+ )
106
+ ] }),
107
+ /* @__PURE__ */ jsxs("div", { id, hidden: !open, children: [
108
+ children,
109
+ sections.map((section, index) => {
110
+ var _a;
111
+ const sectionId = `${id}-section-${index}`;
112
+ const key = (_a = section.label) != null ? _a : "";
113
+ const isExpanded = expanded.includes(key);
114
+ const limit = showAllLabel ? visibleLimit : Infinity;
115
+ const visible = isExpanded ? section.options : section.options.filter(
116
+ (option, i) => i < limit || value.includes(option.value)
117
+ );
118
+ return /* @__PURE__ */ jsxs("div", { children: [
119
+ section.label && /* @__PURE__ */ jsx(
120
+ Text,
121
+ {
122
+ as: "h4",
123
+ id: sectionId,
124
+ variant: "caption",
125
+ weight: "semibold",
126
+ color: "muted",
127
+ className: "mt-3 mb-1",
128
+ children: section.label
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx(
132
+ "fieldset",
133
+ {
134
+ "aria-labelledby": `${id}-title${section.label ? ` ${sectionId}` : ""}`,
135
+ disabled,
136
+ className: "m-0 flex min-w-0 flex-wrap gap-2 border-0 px-0 pt-2 pb-3",
137
+ children: visible.map((option) => /* @__PURE__ */ jsx(
138
+ FilterOptionChip,
139
+ {
140
+ label: option.label,
141
+ count: option.count,
142
+ icon: option.icon,
143
+ type: selection === "single" ? "radio" : "checkbox",
144
+ name: selection === "single" ? name != null ? name : id : void 0,
145
+ value: option.value,
146
+ checked: value.includes(option.value),
147
+ onChange: (event) => onValueChange(
148
+ selection === "single" ? [option.value] : event.target.checked ? [...value, option.value] : value.filter((item) => item !== option.value)
149
+ )
150
+ },
151
+ option.value
152
+ ))
153
+ }
154
+ ),
155
+ section.options.length > limit && /* @__PURE__ */ jsx(
156
+ Button,
157
+ {
158
+ type: "button",
159
+ variant: "link",
160
+ "aria-expanded": isExpanded,
161
+ onClick: () => setExpanded(
162
+ (previous) => isExpanded ? previous.filter((item) => item !== key) : [...previous, key]
163
+ ),
164
+ className: "mb-1 min-h-11 px-0",
165
+ children: isExpanded ? showLessLabel : typeof showAllLabel === "function" ? showAllLabel(section.options.length) : showAllLabel
166
+ }
167
+ )
168
+ ] }, key);
169
+ }),
170
+ footer
171
+ ] })
172
+ ] }),
173
+ divider && /* @__PURE__ */ jsx(Separator, {})
174
+ ] });
175
+ }
176
+
177
+ export { FilterOptionGroup };
@@ -0,0 +1,14 @@
1
+ import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
+
3
+ function PosterOverlayFrame({
4
+ overlay,
5
+ children
6
+ }) {
7
+ if (!overlay) return /* @__PURE__ */ jsx(Fragment, { children });
8
+ return /* @__PURE__ */ jsxs("div", { className: "relative shrink-0", children: [
9
+ children,
10
+ /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-[1]", "aria-hidden": "true", children: overlay })
11
+ ] });
12
+ }
13
+
14
+ export { PosterOverlayFrame };
@@ -0,0 +1,86 @@
1
+ import { cn } from './chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadProps, __spreadValues } from './chunk-MMUBH76A.js';
3
+ import { useId } from 'react';
4
+ import { Check } from 'lucide-react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function FilterOptionChip(_a) {
8
+ var _b = _a, {
9
+ label,
10
+ type = "checkbox",
11
+ count,
12
+ icon,
13
+ className,
14
+ id,
15
+ disabled
16
+ } = _b, input = __objRest(_b, [
17
+ "label",
18
+ "type",
19
+ "count",
20
+ "icon",
21
+ "className",
22
+ "id",
23
+ "disabled"
24
+ ]);
25
+ var _a2;
26
+ const generatedId = useId();
27
+ const inputId = id != null ? id : generatedId;
28
+ return /* @__PURE__ */ jsxs(
29
+ "label",
30
+ {
31
+ htmlFor: inputId,
32
+ className: cn(
33
+ "relative inline-flex max-w-full min-w-0",
34
+ disabled && "cursor-not-allowed opacity-50",
35
+ className
36
+ ),
37
+ children: [
38
+ /* @__PURE__ */ jsx(
39
+ "input",
40
+ __spreadProps(__spreadValues({}, input), {
41
+ id: inputId,
42
+ type,
43
+ disabled,
44
+ "aria-label": (_a2 = input["aria-label"]) != null ? _a2 : label,
45
+ className: "peer sr-only"
46
+ })
47
+ ),
48
+ /* @__PURE__ */ jsxs(
49
+ "span",
50
+ {
51
+ className: cn(
52
+ "flex min-h-11 min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-2xl border border-border bg-background px-3 py-2 text-sm text-foreground",
53
+ "transition-colors hover:bg-muted motion-reduce:transition-none",
54
+ "peer-checked:border-primary peer-checked:bg-primary/10 peer-checked:shadow-[inset_0_0_0_1px_var(--primary)] peer-checked:text-primary",
55
+ "peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ring",
56
+ "peer-disabled:pointer-events-none peer-disabled:cursor-not-allowed",
57
+ "[&_.filter-check]:hidden peer-checked:[&_.filter-check]:block peer-checked:[&_.filter-icon]:hidden"
58
+ ),
59
+ children: [
60
+ /* @__PURE__ */ jsx(
61
+ Check,
62
+ {
63
+ "aria-hidden": true,
64
+ className: "filter-check size-4 shrink-0",
65
+ strokeWidth: 2.5
66
+ }
67
+ ),
68
+ icon && /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "filter-icon shrink-0 [&>svg]:size-4", children: icon }),
69
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 [overflow-wrap:anywhere]", children: label }),
70
+ count != null && /* @__PURE__ */ jsx(
71
+ "span",
72
+ {
73
+ "aria-hidden": true,
74
+ className: "shrink-0 text-xs tabular-nums text-muted-foreground",
75
+ children: count
76
+ }
77
+ )
78
+ ]
79
+ }
80
+ )
81
+ ]
82
+ }
83
+ );
84
+ }
85
+
86
+ export { FilterOptionChip };
@@ -16,6 +16,7 @@ function ruleFromLg(items) {
16
16
  function TimelineHour({
17
17
  time,
18
18
  role = "hour",
19
+ tone = "default",
19
20
  isLive,
20
21
  liveLabel,
21
22
  countLabel,
@@ -28,6 +29,7 @@ function TimelineHour({
28
29
  const isHero = role === "hero";
29
30
  const isContinuation = role === "continuation";
30
31
  const isUnscheduled = role === "unscheduled";
32
+ const isLiveGroup = isUnscheduled && tone === "live";
31
33
  const hasStation = !isUnscheduled;
32
34
  const isCards = layout === "cards";
33
35
  const items = React.Children.toArray(children);
@@ -36,10 +38,12 @@ function TimelineHour({
36
38
  "section",
37
39
  {
38
40
  "data-timeline-hour": role,
41
+ "data-tone": isLiveGroup ? "live" : void 0,
39
42
  className: cn(
40
43
  "relative flex flex-col pt-2.5",
41
44
  // The phone gutter: the puck's column plus the 12px gap `HourBand` had.
42
- hasStation && "pl-[70px] lg:pl-0",
45
+ // A live group keeps it so its cards align with the hours' cards.
46
+ (hasStation || isLiveGroup) && "pl-[70px] lg:pl-0",
43
47
  "lg:pt-[22px]",
44
48
  isHero ? cn(
45
49
  // The lane. Placed by the grid, not by source order; `self-start`
@@ -88,12 +92,31 @@ function TimelineHour({
88
92
  className: cn(
89
93
  "items-center gap-2.5",
90
94
  isUnscheduled ? "flex h-4" : "hidden lg:flex lg:h-4",
91
- isHero && "lg:hidden"
95
+ isHero && "lg:hidden",
96
+ // The heading spans the gutter; only the cards sit inset.
97
+ isLiveGroup && "-ml-[70px] lg:ml-0"
92
98
  ),
93
99
  children: [
94
- isUnscheduled && /* @__PURE__ */ jsx("span", { className: "font-mono text-[11.5px] text-muted-foreground tabular-nums lg:text-xs", children: time }),
95
- /* @__PURE__ */ jsx(Separator, { className: "flex-1" }),
96
- countLabel && /* @__PURE__ */ jsx("span", { className: "text-[10.5px] text-muted-foreground/70 lg:text-[11px]", children: countLabel })
100
+ isLiveGroup ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-semibold text-success", children: [
101
+ /* @__PURE__ */ jsx(
102
+ "span",
103
+ {
104
+ "aria-hidden": "true",
105
+ className: "size-1.5 shrink-0 animate-pulse rounded-full bg-success motion-reduce:animate-none"
106
+ }
107
+ ),
108
+ time
109
+ ] }) : isUnscheduled && /* @__PURE__ */ jsx("span", { className: "font-mono text-[11.5px] text-muted-foreground tabular-nums lg:text-xs", children: time }),
110
+ /* @__PURE__ */ jsx(Separator, { className: cn("flex-1", isLiveGroup && "bg-success/30") }),
111
+ countLabel && /* @__PURE__ */ jsx(
112
+ "span",
113
+ {
114
+ className: cn(
115
+ isLiveGroup ? "text-xs text-muted-foreground" : "text-[10.5px] text-muted-foreground/70 lg:text-[11px]"
116
+ ),
117
+ children: countLabel
118
+ }
119
+ )
97
120
  ]
98
121
  }
99
122
  ),
@@ -38,9 +38,14 @@ type CategoryDeckProps = {
38
38
  /** Defaults to a plain `<a>`. Pass `next/link` in a Next app. */
39
39
  linkComponent?: React.ComponentType<CategoryDeckLinkProps>;
40
40
  onClick?: (mouseEvent: React.MouseEvent) => void;
41
+ /**
42
+ * Decoration laid over the fan's box, unclipped (it may cross the box's
43
+ * edge), above the cards. Absolutely positioned, `aria-hidden`, no layout.
44
+ */
45
+ overlay?: React.ReactNode;
41
46
  className?: string;
42
47
  };
43
- declare function CategoryDeck({ name, meta, posters, href, linkComponent: LinkComponent, onClick, className, }: CategoryDeckProps): react_jsx_runtime.JSX.Element;
48
+ declare function CategoryDeck({ name, meta, posters, href, linkComponent: LinkComponent, onClick, overlay, className, }: CategoryDeckProps): react_jsx_runtime.JSX.Element;
44
49
  type CategoryDeckSkeletonProps = {
45
50
  className?: string;
46
51
  };
@@ -39,6 +39,7 @@ function CategoryDeck({
39
39
  href,
40
40
  linkComponent: LinkComponent = DefaultLink,
41
41
  onClick,
42
+ overlay,
42
43
  className
43
44
  }) {
44
45
  const slots = Array.from(
@@ -49,41 +50,44 @@ function CategoryDeck({
49
50
  }
50
51
  );
51
52
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
52
- /* @__PURE__ */ jsx("div", { className: "relative h-[150px] sm:h-[186px]", "aria-hidden": true, children: slots.map((poster, index) => {
53
- var _a;
54
- const transform = DECK_CARD_TRANSFORMS[index];
55
- return /* @__PURE__ */ jsx(
56
- "div",
57
- {
58
- className: cn(
59
- "absolute left-1/2 top-0 aspect-[4/5] w-24 overflow-hidden rounded-[10px] bg-muted",
60
- // Keyed to --foreground so the shadow is ink in light mode; in
61
- // dark mode that same 18% is a light halo, so it drops to 7%.
62
- "shadow-[0_10px_26px_color-mix(in_oklch,var(--foreground)_18%,transparent)]",
63
- "dark:shadow-[0_10px_26px_color-mix(in_oklch,var(--foreground)_7%,transparent)]",
64
- "sm:w-[120px]",
65
- transform.rest,
66
- transform.spread,
67
- transform.z,
68
- DECK_TRANSITION
69
- ),
70
- children: poster.content ? poster.content : poster.src ? (
71
- // eslint-disable-next-line @next/next/no-img-element
72
- /* @__PURE__ */ jsx(
73
- "img",
74
- {
75
- src: poster.src,
76
- alt: (_a = poster.alt) != null ? _a : "",
77
- loading: "lazy",
78
- decoding: "async",
79
- className: "h-full w-full object-cover"
80
- }
81
- )
82
- ) : /* @__PURE__ */ jsx("div", { className: "h-full w-full bg-muted" })
83
- },
84
- index
85
- );
86
- }) }),
53
+ /* @__PURE__ */ jsxs("div", { className: "relative h-[150px] sm:h-[186px]", "aria-hidden": true, children: [
54
+ slots.map((poster, index) => {
55
+ var _a;
56
+ const transform = DECK_CARD_TRANSFORMS[index];
57
+ return /* @__PURE__ */ jsx(
58
+ "div",
59
+ {
60
+ className: cn(
61
+ "absolute left-1/2 top-0 aspect-[4/5] w-24 overflow-hidden rounded-[10px] bg-muted",
62
+ // Keyed to --foreground so the shadow is ink in light mode; in
63
+ // dark mode that same 18% is a light halo, so it drops to 7%.
64
+ "shadow-[0_10px_26px_color-mix(in_oklch,var(--foreground)_18%,transparent)]",
65
+ "dark:shadow-[0_10px_26px_color-mix(in_oklch,var(--foreground)_7%,transparent)]",
66
+ "sm:w-[120px]",
67
+ transform.rest,
68
+ transform.spread,
69
+ transform.z,
70
+ DECK_TRANSITION
71
+ ),
72
+ children: poster.content ? poster.content : poster.src ? (
73
+ // eslint-disable-next-line @next/next/no-img-element
74
+ /* @__PURE__ */ jsx(
75
+ "img",
76
+ {
77
+ src: poster.src,
78
+ alt: (_a = poster.alt) != null ? _a : "",
79
+ loading: "lazy",
80
+ decoding: "async",
81
+ className: "h-full w-full object-cover"
82
+ }
83
+ )
84
+ ) : /* @__PURE__ */ jsx("div", { className: "h-full w-full bg-muted" })
85
+ },
86
+ index
87
+ );
88
+ }),
89
+ overlay && /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute inset-0 z-[3] block", "aria-hidden": true, children: overlay })
90
+ ] }),
87
91
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[9px] pt-3.5", children: [
88
92
  /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
89
93
  /* @__PURE__ */ jsx("div", { className: "truncate text-[14.5px] font-extrabold tracking-[-0.015em] sm:text-base", children: name }),
@@ -57,6 +57,13 @@ type EventCardProps = {
57
57
  badge?: React.ReactNode;
58
58
  /** Bottom-left overlay pill naming the category. Include its icon. */
59
59
  tagLabel?: React.ReactNode;
60
+ /**
61
+ * Decorative overlay inside the poster box (which is `relative` and clips),
62
+ * for a caller's absolutely positioned ornament. Rendered after the pills,
63
+ * hidden on the compact row's 64px poster. Must be `aria-hidden` and
64
+ * pointer-transparent; it never takes layout.
65
+ */
66
+ posterOverlay?: React.ReactNode;
60
67
  /**
61
68
  * Caller-supplied display labels, rendered as small chips in the text area
62
69
  * beneath the venue/meta line. `EventPreview.tags` is taxonomy coordinates
@@ -23,6 +23,7 @@ var EventCard = memo(function EventCard2({
23
23
  density = "card",
24
24
  badge,
25
25
  tagLabel,
26
+ posterOverlay,
26
27
  tags,
27
28
  liveStatus,
28
29
  hideWhen,
@@ -106,6 +107,14 @@ var EventCard = memo(function EventCard2({
106
107
  ),
107
108
  children: /* @__PURE__ */ jsx("span", { className: cn(PILL_BASE, "bg-black/55 text-white"), children: tagLabel })
108
109
  }
110
+ ),
111
+ posterOverlay && /* @__PURE__ */ jsx(
112
+ "div",
113
+ {
114
+ className: cn("pointer-events-none absolute inset-0", isRowLg && "lg:hidden"),
115
+ "aria-hidden": "true",
116
+ children: posterOverlay
117
+ }
109
118
  )
110
119
  ]
111
120
  }
@@ -87,6 +87,12 @@ type EventCompactRowProps = {
87
87
  priority?: boolean;
88
88
  /** Rendered in place of the poster when it is absent or fails to load. Defaults to `<EventPosterFallback />`. */
89
89
  fallback?: React.ReactNode;
90
+ /**
91
+ * Decoration laid over the poster's box, unclipped (it may cross the
92
+ * poster's edge), above the row. Absolutely positioned, `aria-hidden`,
93
+ * pointer-transparent; it takes no layout.
94
+ */
95
+ posterOverlay?: React.ReactNode;
90
96
  };
91
97
  declare const EventCompactRow: React$1.NamedExoticComponent<EventCompactRowProps>;
92
98
  type EventCompactRowSkeletonProps = {
@@ -1,4 +1,5 @@
1
1
  import { TagGroup } from '../chunk-PX6RICS5.js';
2
+ import { PosterOverlayFrame } from '../chunk-H4ORZT2D.js';
2
3
  import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
3
4
  import { Tag } from '../chunk-MWB7S54O.js';
4
5
  import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
@@ -30,7 +31,8 @@ var EventCompactRow = memo(function EventCompactRow2({
30
31
  onClick,
31
32
  className,
32
33
  priority,
33
- fallback = /* @__PURE__ */ jsx(EventPosterFallback, {})
34
+ fallback = /* @__PURE__ */ jsx(EventPosterFallback, {}),
35
+ posterOverlay
34
36
  }) {
35
37
  var _a;
36
38
  const [imgError, setImgError] = useState(false);
@@ -43,7 +45,7 @@ var EventCompactRow = memo(function EventCompactRow2({
43
45
  const showEnded = isEnded && !!endedLabel;
44
46
  const separator = /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-border", children: "\xB7" });
45
47
  const body = /* @__PURE__ */ jsxs(Fragment, { children: [
46
- /* @__PURE__ */ jsx(
48
+ /* @__PURE__ */ jsx(PosterOverlayFrame, { overlay: posterOverlay, children: /* @__PURE__ */ jsx(
47
49
  "div",
48
50
  {
49
51
  className: cn(
@@ -71,7 +73,7 @@ var EventCompactRow = memo(function EventCompactRow2({
71
73
  fallback
72
74
  )
73
75
  }
74
- ),
76
+ ) }),
75
77
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
76
78
  /* @__PURE__ */ jsx(
77
79
  "div",
@@ -25,6 +25,11 @@ type EventHubHeroStat = {
25
25
  label: string;
26
26
  /** `live` marks the figure that is happening right now. */
27
27
  tone?: "default" | "live";
28
+ /**
29
+ * Decoration beside the figure, absolutely positioned just past its right
30
+ * edge: no layout. Decorative and `aria-hidden`.
31
+ */
32
+ adornment?: React.ReactNode;
28
33
  };
29
34
  type EventHubHeroProps = {
30
35
  /** Small caps-style label above the title, e.g. "Updated today". */
@@ -68,15 +68,26 @@ function EventHubHero({
68
68
  "lg:min-w-0 lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0"
69
69
  ),
70
70
  children: [
71
- /* @__PURE__ */ jsx(
71
+ /* @__PURE__ */ jsxs(
72
72
  "span",
73
73
  {
74
74
  className: cn(
75
75
  "text-[19px] font-black leading-none tracking-[-0.02em] tabular-nums",
76
76
  isLive ? "text-success" : "text-foreground",
77
- "lg:text-[30px] lg:tracking-[-0.03em]"
77
+ "lg:text-[30px] lg:tracking-[-0.03em]",
78
+ stat.adornment && "relative w-fit"
78
79
  ),
79
- children: stat.value
80
+ children: [
81
+ stat.value,
82
+ stat.adornment && /* @__PURE__ */ jsx(
83
+ "span",
84
+ {
85
+ className: "pointer-events-none absolute left-full top-1/2 ml-1.5 -translate-y-1/2",
86
+ "aria-hidden": true,
87
+ children: stat.adornment
88
+ }
89
+ )
90
+ ]
80
91
  }
81
92
  ),
82
93
  /* @__PURE__ */ jsx("span", { className: "mt-0.5 text-[11px] text-muted-foreground lg:mt-[5px] lg:text-[12.5px]", children: stat.label })
@@ -86,6 +86,12 @@ type EventListRowWideProps = {
86
86
  * line. Same prop and same reason as `EventIndexRow`.
87
87
  */
88
88
  hideRule?: boolean;
89
+ /**
90
+ * Decoration laid over the thumbnail's box, unclipped (it may cross the
91
+ * thumbnail's edge). Absolutely positioned, `aria-hidden`,
92
+ * pointer-transparent; it takes no layout.
93
+ */
94
+ posterOverlay?: ReactNode;
89
95
  };
90
96
  declare const EventListRowWide: React$1.NamedExoticComponent<EventListRowWideProps>;
91
97
  type EventListRowWideSkeletonProps = {
@@ -1,3 +1,4 @@
1
+ import { PosterOverlayFrame } from '../chunk-H4ORZT2D.js';
1
2
  import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
2
3
  import { Tag } from '../chunk-MWB7S54O.js';
3
4
  import { Separator } from '../chunk-NWZGSLPU.js';
@@ -30,7 +31,8 @@ var EventListRowWide = memo(function EventListRowWide2({
30
31
  className,
31
32
  priority,
32
33
  fallback = /* @__PURE__ */ jsx(EventPosterFallback, {}),
33
- hideRule
34
+ hideRule,
35
+ posterOverlay
34
36
  }) {
35
37
  var _a, _b;
36
38
  const [imgError, setImgError] = useState(false);
@@ -69,7 +71,7 @@ var EventListRowWide = memo(function EventListRowWide2({
69
71
  ]
70
72
  }
71
73
  ),
72
- /* @__PURE__ */ jsx("div", { className: "aspect-square w-[104px] shrink-0 overflow-hidden rounded-[10px] bg-muted", children: showPoster ? (
74
+ /* @__PURE__ */ jsx(PosterOverlayFrame, { overlay: posterOverlay, children: /* @__PURE__ */ jsx("div", { className: "aspect-square w-[104px] shrink-0 overflow-hidden rounded-[10px] bg-muted", children: showPoster ? (
73
75
  // eslint-disable-next-line @next/next/no-img-element
74
76
  /* @__PURE__ */ jsx(
75
77
  "img",
@@ -82,7 +84,7 @@ var EventListRowWide = memo(function EventListRowWide2({
82
84
  onError: () => setImgError(true)
83
85
  }
84
86
  )
85
- ) : fallback }),
87
+ ) : fallback }) }),
86
88
  /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
87
89
  /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
88
90
  href ? /* @__PURE__ */ jsx(
@@ -40,9 +40,11 @@ type EventTonightStripProps = {
40
40
  href?: string;
41
41
  /** Defaults to a plain `<a>`. Pass `next/link` in a Next app. */
42
42
  linkComponent?: React.ComponentType<EventTonightStripLinkProps>;
43
+ /** Replaces the pulsing dot of the `live` tone, in the dot's own slot. Decorative. */
44
+ liveIndicator?: React.ReactNode;
43
45
  className?: string;
44
46
  };
45
- declare function EventTonightStrip({ tone, lead, details, actionLabel, href, linkComponent: LinkComponent, className, }: EventTonightStripProps): react_jsx_runtime.JSX.Element;
47
+ declare function EventTonightStrip({ tone, lead, details, actionLabel, href, linkComponent: LinkComponent, liveIndicator, className, }: EventTonightStripProps): react_jsx_runtime.JSX.Element;
46
48
  declare function EventTonightStripSkeleton({ className }: {
47
49
  className?: string;
48
50
  }): react_jsx_runtime.JSX.Element;