@bearmenu/ui 0.8.4 → 0.8.7

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 (150) 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/card.d.ts +2 -2
  12. package/dist/components/card.js +4 -127
  13. package/dist/components/category-deck.d.ts +50 -0
  14. package/dist/components/category-deck.js +128 -0
  15. package/dist/components/concept-card.d.ts +43 -0
  16. package/dist/components/concept-card.js +81 -0
  17. package/dist/components/coverflow.d.ts +123 -0
  18. package/dist/components/coverflow.js +300 -0
  19. package/dist/components/day-rail-chip.d.ts +82 -0
  20. package/dist/components/day-rail-chip.js +105 -0
  21. package/dist/components/drift-rail.d.ts +28 -0
  22. package/dist/components/drift-rail.js +3 -0
  23. package/dist/components/event-card.d.ts +97 -0
  24. package/dist/components/event-card.js +164 -0
  25. package/dist/components/event-compact-row.d.ts +73 -0
  26. package/dist/components/event-compact-row.js +134 -0
  27. package/dist/components/event-day-header.d.ts +65 -0
  28. package/dist/components/event-day-header.js +95 -0
  29. package/dist/components/event-day-separator.d.ts +46 -0
  30. package/dist/components/event-day-separator.js +50 -0
  31. package/dist/components/event-detail-panel.d.ts +80 -0
  32. package/dist/components/event-detail-panel.js +251 -0
  33. package/dist/components/event-hub-hero.d.ts +34 -0
  34. package/dist/components/event-hub-hero.js +57 -0
  35. package/dist/components/event-index-row.d.ts +68 -0
  36. package/dist/components/event-index-row.js +84 -0
  37. package/dist/components/event-list-row-wide.d.ts +101 -0
  38. package/dist/components/event-list-row-wide.js +218 -0
  39. package/dist/components/event-list-row.d.ts +82 -0
  40. package/dist/components/event-list-row.js +173 -0
  41. package/dist/components/event-poster-fallback.d.ts +20 -0
  42. package/dist/components/event-poster-fallback.js +3 -0
  43. package/dist/components/event-types.d.ts +122 -0
  44. package/dist/components/event-types.js +2 -0
  45. package/dist/components/events-empty-state.d.ts +44 -0
  46. package/dist/components/events-empty-state.js +73 -0
  47. package/dist/components/facet-group.d.ts +93 -0
  48. package/dist/components/facet-group.js +222 -0
  49. package/dist/components/featured-rail-item.d.ts +42 -0
  50. package/dist/components/featured-rail-item.js +88 -0
  51. package/dist/components/hour-band.d.ts +68 -0
  52. package/dist/components/hour-band.js +53 -0
  53. package/dist/components/hour-group.d.ts +67 -0
  54. package/dist/components/hour-group.js +59 -0
  55. package/dist/components/motion-icon-controls.js +3 -379
  56. package/dist/components/multi-select-combobox.js +1 -1
  57. package/dist/components/place-about-tab.js +3 -3
  58. package/dist/components/place-details-mobile.js +3 -3
  59. package/dist/components/poster-stack.d.ts +30 -0
  60. package/dist/components/poster-stack.js +104 -0
  61. package/dist/components/poster-tape.d.ts +36 -0
  62. package/dist/components/poster-tape.js +64 -0
  63. package/dist/components/poster-wall.d.ts +25 -0
  64. package/dist/components/poster-wall.js +183 -0
  65. package/dist/components/quiet-stretch.d.ts +43 -0
  66. package/dist/components/quiet-stretch.js +54 -0
  67. package/dist/components/section-header.d.ts +53 -0
  68. package/dist/components/section-header.js +63 -0
  69. package/dist/components/segmented-nav.d.ts +62 -0
  70. package/dist/components/segmented-nav.js +54 -0
  71. package/dist/components/skeleton-card.js +1 -1
  72. package/dist/components/skeleton.d.ts +17 -0
  73. package/dist/components/skeleton.js +1 -1
  74. package/dist/components/tag-group.js +3 -26
  75. package/dist/components/tag.d.ts +2 -2
  76. package/dist/components/tag.js +1 -1
  77. package/dist/components/timeline-band.d.ts +46 -0
  78. package/dist/components/timeline-band.js +5 -0
  79. package/dist/components/venue-events-block.d.ts +143 -0
  80. package/dist/components/venue-events-block.js +222 -0
  81. package/dist/components/weekend-panel.d.ts +44 -0
  82. package/dist/components/weekend-panel.js +231 -0
  83. package/package.json +1 -1
  84. package/src/components/card.tsx +44 -1
  85. package/src/components/category-deck.stories.tsx +96 -0
  86. package/src/components/category-deck.tsx +227 -0
  87. package/src/components/concept-card.stories.tsx +80 -0
  88. package/src/components/concept-card.tsx +143 -0
  89. package/src/components/coverflow.stories.tsx +240 -0
  90. package/src/components/coverflow.test.tsx +193 -0
  91. package/src/components/coverflow.tsx +550 -0
  92. package/src/components/day-rail-chip.stories.tsx +145 -0
  93. package/src/components/day-rail-chip.tsx +201 -0
  94. package/src/components/drift-rail.stories.tsx +92 -0
  95. package/src/components/drift-rail.tsx +86 -0
  96. package/src/components/event-card.stories.tsx +248 -0
  97. package/src/components/event-card.test.tsx +188 -0
  98. package/src/components/event-card.tsx +329 -0
  99. package/src/components/event-compact-row.stories.tsx +221 -0
  100. package/src/components/event-compact-row.test.tsx +51 -0
  101. package/src/components/event-compact-row.tsx +270 -0
  102. package/src/components/event-day-header.stories.tsx +124 -0
  103. package/src/components/event-day-header.tsx +180 -0
  104. package/src/components/event-day-separator.stories.tsx +70 -0
  105. package/src/components/event-day-separator.tsx +95 -0
  106. package/src/components/event-detail-panel.stories.tsx +191 -0
  107. package/src/components/event-detail-panel.tsx +360 -0
  108. package/src/components/event-hub-hero.stories.tsx +66 -0
  109. package/src/components/event-hub-hero.tsx +110 -0
  110. package/src/components/event-index-row.stories.tsx +116 -0
  111. package/src/components/event-index-row.tsx +181 -0
  112. package/src/components/event-list-row-wide.stories.tsx +204 -0
  113. package/src/components/event-list-row-wide.tsx +386 -0
  114. package/src/components/event-list-row.stories.tsx +159 -0
  115. package/src/components/event-list-row.tsx +335 -0
  116. package/src/components/event-poster-fallback.stories.tsx +52 -0
  117. package/src/components/event-poster-fallback.tsx +31 -0
  118. package/src/components/event-types.ts +137 -0
  119. package/src/components/events-empty-state.stories.tsx +59 -0
  120. package/src/components/events-empty-state.tsx +131 -0
  121. package/src/components/facet-group.stories.tsx +247 -0
  122. package/src/components/facet-group.test.tsx +225 -0
  123. package/src/components/facet-group.tsx +352 -0
  124. package/src/components/featured-rail-item.stories.tsx +86 -0
  125. package/src/components/featured-rail-item.tsx +153 -0
  126. package/src/components/hour-band.stories.tsx +159 -0
  127. package/src/components/hour-band.tsx +126 -0
  128. package/src/components/hour-group.stories.tsx +100 -0
  129. package/src/components/hour-group.tsx +124 -0
  130. package/src/components/poster-stack.stories.tsx +68 -0
  131. package/src/components/poster-stack.tsx +155 -0
  132. package/src/components/poster-tape.stories.tsx +70 -0
  133. package/src/components/poster-tape.tsx +122 -0
  134. package/src/components/poster-wall.stories.tsx +95 -0
  135. package/src/components/poster-wall.tsx +220 -0
  136. package/src/components/quiet-stretch.stories.tsx +62 -0
  137. package/src/components/quiet-stretch.tsx +115 -0
  138. package/src/components/section-header.stories.tsx +70 -0
  139. package/src/components/section-header.tsx +134 -0
  140. package/src/components/segmented-nav.stories.tsx +118 -0
  141. package/src/components/segmented-nav.tsx +113 -0
  142. package/src/components/skeleton.tsx +18 -0
  143. package/src/components/tag.tsx +11 -1
  144. package/src/components/timeline-band.stories.tsx +206 -0
  145. package/src/components/timeline-band.tsx +207 -0
  146. package/src/components/venue-events-block.stories.tsx +216 -0
  147. package/src/components/venue-events-block.tsx +408 -0
  148. package/src/components/weekend-panel.stories.tsx +92 -0
  149. package/src/components/weekend-panel.tsx +322 -0
  150. package/src/globals.css +19 -0
@@ -0,0 +1,183 @@
1
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
2
+ import { cn } from '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
4
+ import * as React from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var POSTER_WALL_DESKTOP_TILE_COUNT = 48;
8
+ var POSTER_WALL_COMPACT_TILE_COUNT = 36;
9
+ var POSTER_WALL_SWEEP_KEYFRAMES_ID = "ds-poster-wall-sweep-keyframes";
10
+ function usePrefersReducedMotion() {
11
+ const [reduced, setReduced] = React.useState(false);
12
+ React.useEffect(() => {
13
+ if (typeof window === "undefined" || !window.matchMedia) return;
14
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
15
+ setReduced(query.matches);
16
+ const onChange = () => setReduced(query.matches);
17
+ query.addEventListener("change", onChange);
18
+ return () => query.removeEventListener("change", onChange);
19
+ }, []);
20
+ return reduced;
21
+ }
22
+ function useInViewport() {
23
+ const ref = React.useRef(null);
24
+ const [inView, setInView] = React.useState(true);
25
+ React.useEffect(() => {
26
+ const node = ref.current;
27
+ if (!node || typeof IntersectionObserver === "undefined") return;
28
+ const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), {
29
+ threshold: 0
30
+ });
31
+ observer.observe(node);
32
+ return () => observer.disconnect();
33
+ }, []);
34
+ return [ref, inView];
35
+ }
36
+ function PosterWall({ tiles, variant = "default", children, className }) {
37
+ const isCompact = variant === "compact";
38
+ const tileCount = isCompact ? POSTER_WALL_COMPACT_TILE_COUNT : POSTER_WALL_DESKTOP_TILE_COUNT;
39
+ const slots = Array.from({ length: tileCount }, (_, index) => {
40
+ var _a;
41
+ return (_a = tiles == null ? void 0 : tiles[index]) != null ? _a : {};
42
+ });
43
+ const prefersReducedMotion = usePrefersReducedMotion();
44
+ const [wallRef, inView] = useInViewport();
45
+ const isSweepPaused = prefersReducedMotion || !inView;
46
+ return /* @__PURE__ */ jsxs(
47
+ "div",
48
+ {
49
+ ref: wallRef,
50
+ className: cn(
51
+ "relative overflow-hidden rounded-[18px] bg-[oklch(0.22_0.012_60)]",
52
+ isCompact ? "h-[330px]" : "h-[420px]",
53
+ className
54
+ ),
55
+ children: [
56
+ /* @__PURE__ */ jsx("style", { children: `
57
+ @keyframes ${POSTER_WALL_SWEEP_KEYFRAMES_ID} {
58
+ 0% { transform: translateX(-30%); }
59
+ 100% { transform: translateX(30%); }
60
+ }
61
+ ` }),
62
+ /* @__PURE__ */ jsx(
63
+ "div",
64
+ {
65
+ className: cn(
66
+ "absolute inset-0 grid auto-rows-fr gap-[2px] opacity-50",
67
+ isCompact ? "grid-cols-6" : "grid-cols-12"
68
+ ),
69
+ "aria-hidden": true,
70
+ children: slots.map(
71
+ (tile, index) => tile.src ? (
72
+ // eslint-disable-next-line @next/next/no-img-element
73
+ /* @__PURE__ */ jsx(
74
+ "img",
75
+ {
76
+ src: tile.src,
77
+ alt: "",
78
+ loading: "lazy",
79
+ decoding: "async",
80
+ className: "h-full w-full object-cover"
81
+ },
82
+ index
83
+ )
84
+ ) : /* @__PURE__ */ jsx(
85
+ "div",
86
+ {
87
+ className: "h-full w-full bg-[repeating-linear-gradient(45deg,color-mix(in_oklch,white_10%,transparent)_0px,color-mix(in_oklch,white_10%,transparent)_2px,transparent_2px,transparent_8px)]"
88
+ },
89
+ index
90
+ )
91
+ )
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsx(
95
+ "div",
96
+ {
97
+ className: "absolute -inset-[20%]",
98
+ style: {
99
+ background: "radial-gradient(circle at 30% 50%, oklch(1 0 0 / 0.34) 0%, transparent 42%)",
100
+ animationName: POSTER_WALL_SWEEP_KEYFRAMES_ID,
101
+ animationDuration: "18s",
102
+ animationTimingFunction: "linear",
103
+ animationIterationCount: "infinite",
104
+ animationPlayState: isSweepPaused ? "paused" : "running"
105
+ },
106
+ "aria-hidden": true
107
+ }
108
+ ),
109
+ /* @__PURE__ */ jsx(
110
+ "div",
111
+ {
112
+ className: "absolute inset-0",
113
+ style: {
114
+ background: isCompact ? "linear-gradient(to top, oklch(0.2 0.01 60 / 0.93) 0%, oklch(0.2 0.01 60 / 0.7) 55%, oklch(0.2 0.01 60 / 0.35) 100%)" : "linear-gradient(90deg, oklch(0.2 0.01 60 / 0.88) 0%, oklch(0.2 0.01 60 / 0.55) 55%, oklch(0.2 0.01 60 / 0.2) 100%)"
115
+ },
116
+ "aria-hidden": true
117
+ }
118
+ ),
119
+ /* @__PURE__ */ jsx(
120
+ "div",
121
+ {
122
+ className: cn(
123
+ "relative flex h-full min-w-0 flex-col text-white",
124
+ isCompact ? "justify-end gap-[11px] p-5" : "justify-center gap-[15px] px-12"
125
+ ),
126
+ children
127
+ }
128
+ )
129
+ ]
130
+ }
131
+ );
132
+ }
133
+ function PosterWallSkeleton({ variant = "default", className }) {
134
+ const isCompact = variant === "compact";
135
+ const tileCount = isCompact ? POSTER_WALL_COMPACT_TILE_COUNT : POSTER_WALL_DESKTOP_TILE_COUNT;
136
+ return /* @__PURE__ */ jsxs(
137
+ "div",
138
+ {
139
+ "aria-hidden": "true",
140
+ className: cn(
141
+ "relative overflow-hidden rounded-[18px] bg-[oklch(0.22_0.012_60)]",
142
+ isCompact ? "h-[330px]" : "h-[420px]",
143
+ className
144
+ ),
145
+ children: [
146
+ /* @__PURE__ */ jsx(
147
+ "div",
148
+ {
149
+ className: cn(
150
+ "absolute inset-0 grid auto-rows-fr gap-[2px] opacity-50",
151
+ isCompact ? "grid-cols-6" : "grid-cols-12"
152
+ ),
153
+ children: Array.from({ length: tileCount }, (_, index) => /* @__PURE__ */ jsx(Skeleton, { className: "h-full w-full rounded-none bg-white/10" }, index))
154
+ }
155
+ ),
156
+ /* @__PURE__ */ jsx(
157
+ "div",
158
+ {
159
+ className: "absolute inset-0",
160
+ style: {
161
+ background: isCompact ? "linear-gradient(to top, oklch(0.2 0.01 60 / 0.93) 0%, oklch(0.2 0.01 60 / 0.7) 55%, oklch(0.2 0.01 60 / 0.35) 100%)" : "linear-gradient(90deg, oklch(0.2 0.01 60 / 0.88) 0%, oklch(0.2 0.01 60 / 0.55) 55%, oklch(0.2 0.01 60 / 0.2) 100%)"
162
+ }
163
+ }
164
+ ),
165
+ /* @__PURE__ */ jsxs(
166
+ "div",
167
+ {
168
+ className: cn(
169
+ "relative flex h-full min-w-0 flex-col",
170
+ isCompact ? "justify-end gap-[11px] p-5" : "justify-center gap-[15px] px-12"
171
+ ),
172
+ children: [
173
+ /* @__PURE__ */ jsx(Skeleton, { className: cn("bg-white/15", isCompact ? "h-[11px] w-40" : "h-3 w-52") }),
174
+ /* @__PURE__ */ jsx(Skeleton, { className: cn("bg-white/20", isCompact ? "h-7 w-64" : "h-10 w-96") })
175
+ ]
176
+ }
177
+ )
178
+ ]
179
+ }
180
+ );
181
+ }
182
+
183
+ export { PosterWall, PosterWallSkeleton };
@@ -0,0 +1,43 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * QuietStretch — the collapsed run of quiet days on the timeline, summarising
6
+ * a run of near-empty days into one row so the reader doesn't scroll past
7
+ * dozens of "nothing on" headers to reach the next thing worth seeing.
8
+ *
9
+ * Responsive rather than a separate mobile component: past `sm` the label,
10
+ * days and action sit on one row; below it they stack, and the days list
11
+ * wraps at a tighter gap (14px vs. the desktop 22px).
12
+ *
13
+ * The action renders as a link whenever `href` is given, a button when only
14
+ * `onAction` is — same anchor rule as the rest of the events surface.
15
+ */
16
+ type QuietStretchLinkProps = {
17
+ href: string;
18
+ className?: string;
19
+ children: React.ReactNode;
20
+ onClick?: (mouseEvent: React.MouseEvent) => void;
21
+ };
22
+ type QuietStretchDay = {
23
+ id: string;
24
+ /** Already formatted and localised, e.g. "Mon 7". */
25
+ dayLabel: string;
26
+ count: number;
27
+ };
28
+ type QuietStretchProps = {
29
+ /** e.g. "QUIET STRETCH". */
30
+ label: string;
31
+ days: QuietStretchDay[];
32
+ /** Shown in place of a zero count, e.g. "nothing yet". */
33
+ emptyLabel: string;
34
+ /** e.g. "Expand". */
35
+ actionLabel: string;
36
+ href?: string;
37
+ linkComponent?: React.ComponentType<QuietStretchLinkProps>;
38
+ onAction?: (mouseEvent: React.MouseEvent) => void;
39
+ className?: string;
40
+ };
41
+ declare function QuietStretch({ label, days, emptyLabel, actionLabel, href, linkComponent: LinkComponent, onAction, className, }: QuietStretchProps): react_jsx_runtime.JSX.Element;
42
+
43
+ export { QuietStretch, type QuietStretchDay, type QuietStretchLinkProps, type QuietStretchProps };
@@ -0,0 +1,54 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { jsxs, jsx, Fragment } 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
+ function QuietStretch({
11
+ label,
12
+ days,
13
+ emptyLabel,
14
+ actionLabel,
15
+ href,
16
+ linkComponent: LinkComponent = DefaultLink,
17
+ onAction,
18
+ className
19
+ }) {
20
+ const isInteractive = !!href || !!onAction;
21
+ const actionClasses = cn(
22
+ "inline-flex items-center gap-1 whitespace-nowrap rounded-sm text-[13px] font-bold text-primary sm:ml-auto",
23
+ isInteractive && cn(
24
+ "-m-1 cursor-pointer p-1",
25
+ "transition-colors duration-150 hover:text-primary/80 motion-reduce:transition-none",
26
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
27
+ )
28
+ );
29
+ const action = /* @__PURE__ */ jsxs(Fragment, { children: [
30
+ actionLabel,
31
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5", "aria-hidden": true })
32
+ ] });
33
+ return /* @__PURE__ */ jsxs(
34
+ "div",
35
+ {
36
+ className: cn(
37
+ "my-2 flex flex-col gap-3 rounded-xl bg-muted px-[22px] py-5",
38
+ "sm:flex-row sm:items-center sm:gap-4",
39
+ className
40
+ ),
41
+ children: [
42
+ /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap text-xs font-extrabold tracking-widest text-muted-foreground", children: label }),
43
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3.5 text-[13px] sm:gap-[22px]", children: days.map((day) => /* @__PURE__ */ jsxs("span", { className: "whitespace-nowrap", children: [
44
+ /* @__PURE__ */ jsx("span", { className: "font-bold", children: day.dayLabel }),
45
+ " ",
46
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: day.count > 0 ? day.count : emptyLabel })
47
+ ] }, day.id)) }),
48
+ href ? /* @__PURE__ */ jsx(LinkComponent, { href, className: actionClasses, onClick: onAction, children: action }) : onAction ? /* @__PURE__ */ jsx("button", { type: "button", className: actionClasses, onClick: onAction, children: action }) : /* @__PURE__ */ jsx("span", { className: actionClasses, children: action })
49
+ ]
50
+ }
51
+ );
52
+ }
53
+
54
+ export { QuietStretch };
@@ -0,0 +1,53 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * SectionHeader — the title row every rail, list and hub section opens with:
6
+ * title (+ optional count), an optional trailing action, and an optional
7
+ * subtitle line beneath.
8
+ *
9
+ * Renders a link when `actionHref` is given, a button when only `onAction`
10
+ * is, and no action at all otherwise — same href/onClick contract as
11
+ * `EventCard`. No app schema, no i18n, no app hooks.
12
+ */
13
+ type SectionHeaderLinkProps = {
14
+ href: string;
15
+ className?: string;
16
+ children: React.ReactNode;
17
+ onClick?: (mouseEvent: React.MouseEvent) => void;
18
+ };
19
+ type SectionHeaderProps = {
20
+ title: string;
21
+ /** Renders inline after the title in muted text, e.g. "12". */
22
+ count?: number;
23
+ /** Sub-text below the title row. */
24
+ subtitle?: string;
25
+ /** Trailing action label, e.g. "See all". */
26
+ action?: string;
27
+ actionHref?: string;
28
+ /** Defaults to a plain `<a>`. Pass `next/link` in a Next app. */
29
+ linkComponent?: React.ComponentType<SectionHeaderLinkProps>;
30
+ onAction?: (mouseEvent: React.MouseEvent) => void;
31
+ /**
32
+ * Heading level for `title`, or `"p"` / `"div"` for a section opener that
33
+ * should NOT enter the document outline — e.g. an index whose own group
34
+ * headings are the page's semantic `h2`s, where a generic opener above them
35
+ * would dilute the headings that matter. Defaults to `"h2"`.
36
+ */
37
+ as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "div";
38
+ className?: string;
39
+ };
40
+ declare function SectionHeader({ title, count, subtitle, action, actionHref, linkComponent: LinkComponent, onAction, as: Heading, className, }: SectionHeaderProps): react_jsx_runtime.JSX.Element;
41
+ type SectionHeaderSkeletonProps = {
42
+ /** Shows the inline count placeholder, matching `SectionHeaderProps["count"]`. */
43
+ showCount?: boolean;
44
+ /** Shows the trailing action placeholder, matching `SectionHeaderProps["action"]`. */
45
+ showAction?: boolean;
46
+ /** Shows the subtitle placeholder, matching `SectionHeaderProps["subtitle"]`. */
47
+ showSubtitle?: boolean;
48
+ className?: string;
49
+ };
50
+ /** Mirrors `<SectionHeader>`'s geometry exactly so nothing shifts when data lands. */
51
+ declare function SectionHeaderSkeleton({ showCount, showAction, showSubtitle, className, }: SectionHeaderSkeletonProps): react_jsx_runtime.JSX.Element;
52
+
53
+ export { SectionHeader, type SectionHeaderLinkProps, type SectionHeaderProps, SectionHeaderSkeleton, type SectionHeaderSkeletonProps };
@@ -0,0 +1,63 @@
1
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
2
+ import { cn } from '../chunk-FEK5XGZW.js';
3
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
4
+ import { ChevronRight } from 'lucide-react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function DefaultLink(_a) {
8
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
9
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
10
+ }
11
+ var ACTION_CLASSES = cn(
12
+ "inline-flex shrink-0 items-center gap-0.5 rounded-sm text-[14px] text-muted-foreground",
13
+ "transition-colors duration-150 hover:text-foreground motion-reduce:transition-none",
14
+ "cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
15
+ );
16
+ function SectionHeader({
17
+ title,
18
+ count,
19
+ subtitle,
20
+ action,
21
+ actionHref,
22
+ linkComponent: LinkComponent = DefaultLink,
23
+ onAction,
24
+ as: Heading = "h2",
25
+ className
26
+ }) {
27
+ const actionIcon = /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5", "aria-hidden": true });
28
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col mb-3.5", className), children: [
29
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
30
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline", children: [
31
+ /* @__PURE__ */ jsx(Heading, { className: "truncate text-[17px] font-bold tracking-[-0.01em] text-foreground md:text-[18px]", children: title }),
32
+ count !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-0.5 shrink-0 text-[12px] text-muted-foreground", children: count })
33
+ ] }),
34
+ action && (actionHref ? /* @__PURE__ */ jsxs(LinkComponent, { href: actionHref, className: ACTION_CLASSES, onClick: onAction, children: [
35
+ action,
36
+ actionIcon
37
+ ] }) : onAction ? /* @__PURE__ */ jsxs("button", { type: "button", className: ACTION_CLASSES, onClick: onAction, children: [
38
+ action,
39
+ actionIcon
40
+ ] }) : null)
41
+ ] }),
42
+ subtitle && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[13px] text-muted-foreground md:text-[14px]", children: subtitle })
43
+ ] });
44
+ }
45
+ function SectionHeaderSkeleton({
46
+ showCount,
47
+ showAction,
48
+ showSubtitle,
49
+ className
50
+ }) {
51
+ return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: cn("flex flex-col mb-3.5", className), children: [
52
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
53
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-baseline gap-1", children: [
54
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[17px] w-32 md:h-[18px]" }),
55
+ showCount && /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-4" })
56
+ ] }),
57
+ showAction && /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-16" })
58
+ ] }),
59
+ showSubtitle && /* @__PURE__ */ jsx(Skeleton, { className: "mt-0.5 h-[13px] w-48 md:h-3.5" })
60
+ ] });
61
+ }
62
+
63
+ export { SectionHeader, SectionHeaderSkeleton };
@@ -0,0 +1,62 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * SegmentedNav — a segmented control whose segments are LINKS, not buttons.
6
+ *
7
+ * Use this when the segments are separate routes. For segments that swap
8
+ * content inside one page, use `<Tabs>` (Radix tab-panel semantics) or
9
+ * `<ToggleGroup>` (button semantics) instead — a nav landmark with
10
+ * `aria-current="page"` is only correct when each segment is a destination.
11
+ *
12
+ * FRAMEWORK-AGNOSTIC BY DESIGN. This renders a plain `<a>` unless the caller
13
+ * passes `linkComponent`. It deliberately does NOT import `next/link` the way
14
+ * `link-button.tsx` does — that import is what quietly makes a component
15
+ * Next-only, and this one has to work in admin (Vite) too.
16
+ *
17
+ * Segments stretch to fill the row on mobile and shrink to their content from
18
+ * `lg` up, matching the `.mtog` / `.tog` split in the source design.
19
+ *
20
+ * The `w-full` below `lg` is what makes that sentence TRUE, and it is not
21
+ * redundant with the segments' own `flex-1`. `flex-1` only distributes the
22
+ * width the nav already has; the nav itself sizes to content whenever its
23
+ * parent is a flex row, so a caller placing this beside other controls got
24
+ * three segments hugging their labels with dead space alongside — the
25
+ * component quietly not doing the thing its own docblock claimed. Callers that
26
+ * genuinely want it to hug on mobile can pass `w-auto` through `className`,
27
+ * which wins via `cn`'s merge.
28
+ */
29
+ type SegmentedNavItem = {
30
+ /** Stable identity, compared against `activeId`. */
31
+ id: string;
32
+ href: string;
33
+ label: string;
34
+ /** Rendered before the label. Size it at the call site (the design uses 15–16px). */
35
+ icon?: React.ReactNode;
36
+ };
37
+ /**
38
+ * The shape `linkComponent` must accept. `next/link` satisfies it structurally,
39
+ * so callers can pass it directly.
40
+ */
41
+ type SegmentedNavLinkProps = {
42
+ href: string;
43
+ className?: string;
44
+ children: React.ReactNode;
45
+ "aria-current"?: "page";
46
+ onClick?: () => void;
47
+ };
48
+ type SegmentedNavProps = {
49
+ items: SegmentedNavItem[];
50
+ /** `id` of the segment for the current route. */
51
+ activeId: string;
52
+ /** Accessible name for the nav landmark. Translated by the caller. */
53
+ label: string;
54
+ /** Defaults to a plain `<a>`. Pass `next/link` in a Next app. */
55
+ linkComponent?: React.ComponentType<SegmentedNavLinkProps>;
56
+ /** Fired with the segment id on click — analytics, haptics. Never navigation. */
57
+ onSelect?: (id: string) => void;
58
+ className?: string;
59
+ };
60
+ declare function SegmentedNav({ items, activeId, label, linkComponent: LinkComponent, onSelect, className, }: SegmentedNavProps): react_jsx_runtime.JSX.Element;
61
+
62
+ export { SegmentedNav, type SegmentedNavItem, type SegmentedNavLinkProps, type SegmentedNavProps };
@@ -0,0 +1,54 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ function DefaultLink(_a) {
6
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
7
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
8
+ }
9
+ function SegmentedNav({
10
+ items,
11
+ activeId,
12
+ label,
13
+ linkComponent: LinkComponent = DefaultLink,
14
+ onSelect,
15
+ className
16
+ }) {
17
+ return /* @__PURE__ */ jsx(
18
+ "nav",
19
+ {
20
+ "aria-label": label,
21
+ className: cn(
22
+ "flex w-full items-center gap-0.5 rounded-lg border border-border bg-card p-0.5",
23
+ "lg:inline-flex lg:w-fit",
24
+ className
25
+ ),
26
+ children: items.map((item) => {
27
+ const isActive = item.id === activeId;
28
+ return /* @__PURE__ */ jsxs(
29
+ LinkComponent,
30
+ {
31
+ href: item.href,
32
+ "aria-current": isActive ? "page" : void 0,
33
+ onClick: onSelect ? () => onSelect(item.id) : void 0,
34
+ className: cn(
35
+ "inline-flex h-9 flex-1 items-center justify-center gap-1.5 rounded-md px-3",
36
+ "text-[13px] font-semibold lg:flex-none lg:text-sm",
37
+ "transition-colors duration-150 motion-reduce:transition-none",
38
+ "cursor-pointer touch-manipulation select-none",
39
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
40
+ isActive ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"
41
+ ),
42
+ children: [
43
+ item.icon,
44
+ item.label
45
+ ]
46
+ },
47
+ item.id
48
+ );
49
+ })
50
+ }
51
+ );
52
+ }
53
+
54
+ export { SegmentedNav };
@@ -1,4 +1,4 @@
1
- import { Skeleton } from '../chunk-N2KNBPAQ.js';
1
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
2
2
  import { AspectRatio } from '../chunk-3VMDCCCX.js';
3
3
  import { cn } from '../chunk-FEK5XGZW.js';
4
4
  import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
@@ -1,6 +1,23 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React__default from 'react';
3
3
 
4
+ /**
5
+ * The shimmer stops under `prefers-reduced-motion: reduce`.
6
+ *
7
+ * It did not, and this is the one animation in the package that is guaranteed
8
+ * to be on screen during every slow load, on every surface, in multiples — a
9
+ * list of twelve skeleton rows is twelve sweeping gradients. A reader who has
10
+ * asked the OS for less motion is exactly the reader least served by that.
11
+ *
12
+ * `motion-reduce:` on the `before:` pseudo-element rather than a media query in
13
+ * a style block, because the animation is declared as a utility class here and
14
+ * a class beats a class predictably; the components that animate via an inline
15
+ * `animation-play-state` need the media-query form instead, and say so.
16
+ *
17
+ * The skeleton still reads as a skeleton without the sweep — it keeps its muted
18
+ * fill and its geometry, which is what actually communicates "content is coming
19
+ * and it will be this shape".
20
+ */
4
21
  declare function Skeleton({ className, ...props }: React__default.HTMLAttributes<HTMLDivElement>): react_jsx_runtime.JSX.Element;
5
22
 
6
23
  export { Skeleton };
@@ -1,3 +1,3 @@
1
- export { Skeleton } from '../chunk-N2KNBPAQ.js';
1
+ export { Skeleton } from '../chunk-QXHMPJ35.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-3GWWZKX7.js';
@@ -1,26 +1,3 @@
1
- import { cn } from '../chunk-FEK5XGZW.js';
2
- import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
3
- import * as React from 'react';
4
- import { jsxs, jsx } from 'react/jsx-runtime';
5
-
6
- var TagGroup = React.forwardRef((_a, ref) => {
7
- var _b = _a, { className, label, srLabel = true, children } = _b, props = __objRest(_b, ["className", "label", "srLabel", "children"]);
8
- return /* @__PURE__ */ jsxs(
9
- "div",
10
- __spreadProps(__spreadValues({
11
- ref,
12
- role: "group",
13
- "aria-label": label,
14
- "data-slot": "tag-group",
15
- className: cn("flex flex-wrap items-center gap-1.5", className)
16
- }, props), {
17
- children: [
18
- label && !srLabel && /* @__PURE__ */ jsx("span", { className: "mr-1 text-xs font-semibold text-muted-foreground", children: label }),
19
- children
20
- ]
21
- })
22
- );
23
- });
24
- TagGroup.displayName = "TagGroup";
25
-
26
- export { TagGroup };
1
+ export { TagGroup } from '../chunk-6XMYEZVR.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-3GWWZKX7.js';
@@ -3,11 +3,11 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const tagVariants: (props?: ({
6
- variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | null | undefined;
6
+ variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | "quiet" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare const Tag: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
10
- variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | null | undefined;
10
+ variant?: "default" | "destructive" | "outline" | "success" | "warning" | "primary" | "quiet" | null | undefined;
11
11
  size?: "default" | "sm" | "lg" | null | undefined;
12
12
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
13
13
  onRemove?: () => void;
@@ -1,3 +1,3 @@
1
- export { Tag, tagVariants } from '../chunk-VBA45GLP.js';
1
+ export { Tag, tagVariants } from '../chunk-ZDAHKI3A.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,46 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ type TimelineHourPuckProps = {
5
+ /** The hour, already formatted and localised — or an em dash for a quiet day. */
6
+ time: string;
7
+ /** Marks this as the hour currently in progress. */
8
+ isLive?: boolean;
9
+ /** Shown under the puck when live, e.g. "NOW ON". Required to render it. */
10
+ liveLabel?: string;
11
+ className?: string;
12
+ };
13
+ /**
14
+ * The station on the timeline's axis — ONE object, positioned by its host:
15
+ * `HourGroup` centres it on its own heading rule out in the desktop spine,
16
+ * `HourBand` pins it to the top of the mobile gutter.
17
+ *
18
+ * Extracted because it was hand-rolled twice and the copies had already
19
+ * drifted — this one bordered and `rounded-[7px] px-[7px]`, `hour-band.tsx`'s
20
+ * borderless and `rounded-md px-1.5`. The `bg-background` fill punches through
21
+ * the hairline so the rail reads as continuous behind the label rather than
22
+ * butting into it.
23
+ *
24
+ * The live label is absolutely positioned rather than a flex sibling: hosts
25
+ * centre this puck with `-translate-y-1/2`, and a two-item column would centre
26
+ * the STACK, lifting the puck off the line it exists to sit on.
27
+ */
28
+ declare function TimelineHourPuck({ time, isLive, liveLabel, className }: TimelineHourPuckProps): react_jsx_runtime.JSX.Element;
29
+ type TimelineBandProps = {
30
+ /** The lane column — usually a poster card, or empty for a quiet hour. */
31
+ lane: React.ReactNode;
32
+ /** The content column, normally one `<HourGroup spine>` per hour. */
33
+ children: React.ReactNode;
34
+ className?: string;
35
+ };
36
+ declare function TimelineBand({ lane, children, className }: TimelineBandProps): react_jsx_runtime.JSX.Element;
37
+ type TimelineBandSkeletonProps = {
38
+ className?: string;
39
+ };
40
+ /**
41
+ * Holds the lane/spine/content geometry so nothing jumps when data lands.
42
+ * The spine renders its hairline with no puck — there's no hour to show yet.
43
+ */
44
+ declare function TimelineBandSkeleton({ className }: TimelineBandSkeletonProps): react_jsx_runtime.JSX.Element;
45
+
46
+ export { TimelineBand, type TimelineBandProps, TimelineBandSkeleton, type TimelineBandSkeletonProps, TimelineHourPuck, type TimelineHourPuckProps };
@@ -0,0 +1,5 @@
1
+ export { TimelineBand, TimelineBandSkeleton, TimelineHourPuck } from '../chunk-637L7XQ3.js';
2
+ import '../chunk-WSRWFA6K.js';
3
+ import '../chunk-QXHMPJ35.js';
4
+ import '../chunk-FEK5XGZW.js';
5
+ import '../chunk-3GWWZKX7.js';