@bearmenu/ui 0.8.4 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-637L7XQ3.js +85 -0
- package/dist/chunk-6XMYEZVR.js +26 -0
- package/dist/chunk-DNSXMHYX.js +8 -0
- package/dist/chunk-FDITZ2VM.js +16 -0
- package/dist/chunk-GPH6L4DX.js +55 -0
- package/dist/{chunk-N2KNBPAQ.js → chunk-QXHMPJ35.js} +1 -0
- package/dist/chunk-TT3WFS6S.js +379 -0
- package/dist/chunk-UXWAOISG.js +170 -0
- package/dist/{chunk-UZ3PCIE5.js → chunk-XWMRABZW.js} +2 -2
- package/dist/{chunk-VBA45GLP.js → chunk-ZDAHKI3A.js} +12 -2
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/card.d.ts +2 -2
- package/dist/components/card.js +4 -127
- package/dist/components/category-deck.d.ts +50 -0
- package/dist/components/category-deck.js +128 -0
- package/dist/components/command.d.ts +15 -15
- package/dist/components/concept-card.d.ts +43 -0
- package/dist/components/concept-card.js +81 -0
- package/dist/components/coverflow.d.ts +123 -0
- package/dist/components/coverflow.js +300 -0
- package/dist/components/date-input.d.ts +1 -1
- package/dist/components/day-rail-chip.d.ts +82 -0
- package/dist/components/day-rail-chip.js +105 -0
- package/dist/components/drift-rail.d.ts +28 -0
- package/dist/components/drift-rail.js +3 -0
- package/dist/components/event-card.d.ts +97 -0
- package/dist/components/event-card.js +161 -0
- package/dist/components/event-compact-row.d.ts +72 -0
- package/dist/components/event-compact-row.js +133 -0
- package/dist/components/event-day-header.d.ts +65 -0
- package/dist/components/event-day-header.js +95 -0
- package/dist/components/event-day-separator.d.ts +46 -0
- package/dist/components/event-day-separator.js +50 -0
- package/dist/components/event-detail-panel.d.ts +80 -0
- package/dist/components/event-detail-panel.js +251 -0
- package/dist/components/event-hub-hero.d.ts +34 -0
- package/dist/components/event-hub-hero.js +57 -0
- package/dist/components/event-index-row.d.ts +68 -0
- package/dist/components/event-index-row.js +84 -0
- package/dist/components/event-list-row-wide.d.ts +101 -0
- package/dist/components/event-list-row-wide.js +218 -0
- package/dist/components/event-list-row.d.ts +82 -0
- package/dist/components/event-list-row.js +173 -0
- package/dist/components/event-poster-fallback.d.ts +20 -0
- package/dist/components/event-poster-fallback.js +3 -0
- package/dist/components/event-types.d.ts +118 -0
- package/dist/components/event-types.js +2 -0
- package/dist/components/events-empty-state.d.ts +44 -0
- package/dist/components/events-empty-state.js +73 -0
- package/dist/components/facet-group.d.ts +93 -0
- package/dist/components/facet-group.js +222 -0
- package/dist/components/featured-rail-item.d.ts +42 -0
- package/dist/components/featured-rail-item.js +88 -0
- package/dist/components/hour-band.d.ts +68 -0
- package/dist/components/hour-band.js +53 -0
- package/dist/components/hour-group.d.ts +67 -0
- package/dist/components/hour-group.js +59 -0
- package/dist/components/item.d.ts +2 -2
- package/dist/components/motion-icon-controls.js +3 -379
- package/dist/components/multi-select-combobox.js +1 -1
- package/dist/components/place-about-tab.js +3 -3
- package/dist/components/place-details-mobile.js +3 -3
- package/dist/components/poster-stack.d.ts +30 -0
- package/dist/components/poster-stack.js +104 -0
- package/dist/components/poster-tape.d.ts +36 -0
- package/dist/components/poster-tape.js +64 -0
- package/dist/components/poster-wall.d.ts +25 -0
- package/dist/components/poster-wall.js +183 -0
- package/dist/components/quiet-stretch.d.ts +43 -0
- package/dist/components/quiet-stretch.js +54 -0
- package/dist/components/section-header.d.ts +53 -0
- package/dist/components/section-header.js +63 -0
- package/dist/components/segmented-nav.d.ts +62 -0
- package/dist/components/segmented-nav.js +54 -0
- package/dist/components/skeleton-card.js +1 -1
- package/dist/components/skeleton.d.ts +17 -0
- package/dist/components/skeleton.js +1 -1
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/tag-group.js +3 -26
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/tag.js +1 -1
- package/dist/components/text.d.ts +3 -3
- package/dist/components/timeline-band.d.ts +46 -0
- package/dist/components/timeline-band.js +5 -0
- package/dist/components/venue-events-block.d.ts +143 -0
- package/dist/components/venue-events-block.js +222 -0
- package/dist/components/weekend-panel.d.ts +44 -0
- package/dist/components/weekend-panel.js +231 -0
- package/package.json +1 -1
- package/src/components/card.tsx +44 -1
- package/src/components/category-deck.stories.tsx +96 -0
- package/src/components/category-deck.tsx +227 -0
- package/src/components/concept-card.stories.tsx +80 -0
- package/src/components/concept-card.tsx +143 -0
- package/src/components/coverflow.stories.tsx +240 -0
- package/src/components/coverflow.test.tsx +193 -0
- package/src/components/coverflow.tsx +550 -0
- package/src/components/day-rail-chip.stories.tsx +145 -0
- package/src/components/day-rail-chip.tsx +201 -0
- package/src/components/drift-rail.stories.tsx +92 -0
- package/src/components/drift-rail.tsx +86 -0
- package/src/components/event-card.stories.tsx +248 -0
- package/src/components/event-card.test.tsx +177 -0
- package/src/components/event-card.tsx +321 -0
- package/src/components/event-compact-row.stories.tsx +221 -0
- package/src/components/event-compact-row.tsx +268 -0
- package/src/components/event-day-header.stories.tsx +124 -0
- package/src/components/event-day-header.tsx +180 -0
- package/src/components/event-day-separator.stories.tsx +70 -0
- package/src/components/event-day-separator.tsx +95 -0
- package/src/components/event-detail-panel.stories.tsx +191 -0
- package/src/components/event-detail-panel.tsx +360 -0
- package/src/components/event-hub-hero.stories.tsx +66 -0
- package/src/components/event-hub-hero.tsx +110 -0
- package/src/components/event-index-row.stories.tsx +116 -0
- package/src/components/event-index-row.tsx +181 -0
- package/src/components/event-list-row-wide.stories.tsx +204 -0
- package/src/components/event-list-row-wide.tsx +386 -0
- package/src/components/event-list-row.stories.tsx +159 -0
- package/src/components/event-list-row.tsx +335 -0
- package/src/components/event-poster-fallback.stories.tsx +52 -0
- package/src/components/event-poster-fallback.tsx +31 -0
- package/src/components/event-types.ts +133 -0
- package/src/components/events-empty-state.stories.tsx +59 -0
- package/src/components/events-empty-state.tsx +131 -0
- package/src/components/facet-group.stories.tsx +247 -0
- package/src/components/facet-group.test.tsx +225 -0
- package/src/components/facet-group.tsx +352 -0
- package/src/components/featured-rail-item.stories.tsx +86 -0
- package/src/components/featured-rail-item.tsx +153 -0
- package/src/components/hour-band.stories.tsx +159 -0
- package/src/components/hour-band.tsx +126 -0
- package/src/components/hour-group.stories.tsx +100 -0
- package/src/components/hour-group.tsx +124 -0
- package/src/components/poster-stack.stories.tsx +68 -0
- package/src/components/poster-stack.tsx +155 -0
- package/src/components/poster-tape.stories.tsx +70 -0
- package/src/components/poster-tape.tsx +122 -0
- package/src/components/poster-wall.stories.tsx +95 -0
- package/src/components/poster-wall.tsx +220 -0
- package/src/components/quiet-stretch.stories.tsx +62 -0
- package/src/components/quiet-stretch.tsx +115 -0
- package/src/components/section-header.stories.tsx +70 -0
- package/src/components/section-header.tsx +134 -0
- package/src/components/segmented-nav.stories.tsx +118 -0
- package/src/components/segmented-nav.tsx +113 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/tag.tsx +11 -1
- package/src/components/timeline-band.stories.tsx +206 -0
- package/src/components/timeline-band.tsx +207 -0
- package/src/components/venue-events-block.stories.tsx +216 -0
- package/src/components/venue-events-block.tsx +408 -0
- package/src/components/weekend-panel.stories.tsx +92 -0
- package/src/components/weekend-panel.tsx +322 -0
- package/src/globals.css +19 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronRight } from "lucide-react";
|
|
5
|
+
import { MotionPlayPause } from "./motion-icon-controls";
|
|
6
|
+
import { cn } from "../lib/utils";
|
|
7
|
+
import { Skeleton } from "./skeleton";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* WeekendPanel — one day panel in the weekend section: a header row over an
|
|
11
|
+
* auto-advancing two-up carousel of the day's event cards.
|
|
12
|
+
*
|
|
13
|
+
* The carousel assumes exactly two "pages" of two cards (four `children` in
|
|
14
|
+
* total) — that's what the design's slide keyframes measure against
|
|
15
|
+
* (`translateX(calc(-100% - gap))` moves the track exactly one page). Pass a
|
|
16
|
+
* different count and the maths still animate, but the pause points won't
|
|
17
|
+
* line up with a full page.
|
|
18
|
+
*
|
|
19
|
+
* ACCESSIBILITY: satisfies WCAG 2.2.2 (Pause, Stop, Hide) with a visible
|
|
20
|
+
* pause/play toggle next to the progress dots. Hover/focus/off-screen pausing
|
|
21
|
+
* remain as convenience behaviour, but the button is the actual mechanism —
|
|
22
|
+
* it is a latch (`isManuallyPaused`), not a hold, so a touch or low-vision
|
|
23
|
+
* user can stop the carousel and keep it stopped while they read. Toggling it
|
|
24
|
+
* flips `animationPlayState` for the track AND both dot keyframes together.
|
|
25
|
+
* Under `prefers-reduced-motion` the panel already renders a static grid with
|
|
26
|
+
* nothing animating, so the control would be meaningless and is not rendered.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const WEEKEND_PANEL_TRACK_GAP_PX = 14;
|
|
30
|
+
const WEEKEND_PANEL_DEFAULT_DURATION_SECONDS = 7;
|
|
31
|
+
const WEEKEND_PANEL_SLIDE_KEYFRAMES_ID = "ds-weekend-panel-slide";
|
|
32
|
+
const WEEKEND_PANEL_DOT_A_KEYFRAMES_ID = "ds-weekend-panel-dot-a";
|
|
33
|
+
const WEEKEND_PANEL_DOT_B_KEYFRAMES_ID = "ds-weekend-panel-dot-b";
|
|
34
|
+
|
|
35
|
+
function usePrefersReducedMotion(): boolean {
|
|
36
|
+
const [reduced, setReduced] = React.useState(false);
|
|
37
|
+
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
40
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
41
|
+
setReduced(query.matches);
|
|
42
|
+
const onChange = () => setReduced(query.matches);
|
|
43
|
+
query.addEventListener("change", onChange);
|
|
44
|
+
return () => query.removeEventListener("change", onChange);
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
return reduced;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function useInViewport<T extends Element>(): [React.RefObject<T | null>, boolean] {
|
|
51
|
+
const ref = React.useRef<T | null>(null);
|
|
52
|
+
const [inView, setInView] = React.useState(true);
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
const node = ref.current;
|
|
56
|
+
if (!node || typeof IntersectionObserver === "undefined") return;
|
|
57
|
+
const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), {
|
|
58
|
+
threshold: 0,
|
|
59
|
+
});
|
|
60
|
+
observer.observe(node);
|
|
61
|
+
return () => observer.disconnect();
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
return [ref, inView];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type WeekendPanelLinkProps = {
|
|
68
|
+
href: string;
|
|
69
|
+
className?: string;
|
|
70
|
+
children: React.ReactNode;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function DefaultLink({ href, ...props }: WeekendPanelLinkProps) {
|
|
74
|
+
return <a href={href} {...props} />;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Accessible names for the pause/play toggle, injected so the DS owns no copy. */
|
|
78
|
+
export type WeekendPanelLabels = {
|
|
79
|
+
/** Name announced while the carousel is running (the button pauses it). */
|
|
80
|
+
pause: string;
|
|
81
|
+
/** Name announced while the carousel is paused (the button resumes it). */
|
|
82
|
+
play: string;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type WeekendPanelProps = {
|
|
86
|
+
dayLabel: string;
|
|
87
|
+
/** Caller-composed, e.g. "23 events". */
|
|
88
|
+
countLabel: string;
|
|
89
|
+
actionLabel: string;
|
|
90
|
+
actionHref?: string;
|
|
91
|
+
linkComponent?: React.ComponentType<WeekendPanelLinkProps>;
|
|
92
|
+
onActionClick?: () => void;
|
|
93
|
+
isHighlighted?: boolean;
|
|
94
|
+
/** Loop duration for the slide/dots animation. Default 7 — sibling panels typically stagger at 8.5 and 10. */
|
|
95
|
+
durationSeconds?: number;
|
|
96
|
+
/**
|
|
97
|
+
* Accessible names for the pause/play toggle. Required — the control is
|
|
98
|
+
* only omitted under `prefers-reduced-motion`, where the labels go unused.
|
|
99
|
+
*/
|
|
100
|
+
labels: WeekendPanelLabels;
|
|
101
|
+
/** The event cards. The animation is built around exactly four (two pages of two). */
|
|
102
|
+
children?: React.ReactNode;
|
|
103
|
+
className?: string;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export function WeekendPanel({
|
|
107
|
+
dayLabel,
|
|
108
|
+
countLabel,
|
|
109
|
+
actionLabel,
|
|
110
|
+
actionHref,
|
|
111
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
112
|
+
onActionClick,
|
|
113
|
+
isHighlighted,
|
|
114
|
+
durationSeconds = WEEKEND_PANEL_DEFAULT_DURATION_SECONDS,
|
|
115
|
+
labels,
|
|
116
|
+
children,
|
|
117
|
+
className,
|
|
118
|
+
}: WeekendPanelProps) {
|
|
119
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
120
|
+
const [panelRef, inView] = useInViewport<HTMLDivElement>();
|
|
121
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
122
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
123
|
+
const [isManuallyPaused, setIsManuallyPaused] = React.useState(false);
|
|
124
|
+
const [playPauseReplayToken, setPlayPauseReplayToken] = React.useState(0);
|
|
125
|
+
const isPaused = isManuallyPaused || isHovered || isFocused || !inView;
|
|
126
|
+
|
|
127
|
+
const action = (
|
|
128
|
+
<span className="ml-auto flex items-center gap-0.5 text-[12.5px] text-muted-foreground">
|
|
129
|
+
{actionLabel}
|
|
130
|
+
<ChevronRight className="h-3.5 w-3.5" aria-hidden />
|
|
131
|
+
</span>
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
ref={panelRef}
|
|
137
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
138
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
139
|
+
onFocus={() => setIsFocused(true)}
|
|
140
|
+
onBlur={() => setIsFocused(false)}
|
|
141
|
+
className={cn(
|
|
142
|
+
"relative overflow-hidden rounded-2xl border bg-card py-4",
|
|
143
|
+
isHighlighted ? "border-primary/40" : "border-border",
|
|
144
|
+
className
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<style>{`
|
|
148
|
+
@keyframes ${WEEKEND_PANEL_SLIDE_KEYFRAMES_ID} {
|
|
149
|
+
0%, 45% { transform: translateX(0); }
|
|
150
|
+
50%, 95% { transform: translateX(calc(-100% - ${WEEKEND_PANEL_TRACK_GAP_PX}px)); }
|
|
151
|
+
100% { transform: translateX(0); }
|
|
152
|
+
}
|
|
153
|
+
@keyframes ${WEEKEND_PANEL_DOT_A_KEYFRAMES_ID} {
|
|
154
|
+
0% { background: var(--primary); width: 18px; }
|
|
155
|
+
45% { background: var(--primary); width: 18px; }
|
|
156
|
+
50% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
157
|
+
100% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
158
|
+
}
|
|
159
|
+
@keyframes ${WEEKEND_PANEL_DOT_B_KEYFRAMES_ID} {
|
|
160
|
+
0% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
161
|
+
50% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
162
|
+
95% { background: var(--primary); width: 18px; }
|
|
163
|
+
100% { background: var(--primary); width: 18px; }
|
|
164
|
+
}
|
|
165
|
+
`}</style>
|
|
166
|
+
|
|
167
|
+
<div className="flex items-baseline gap-[9px] px-4 pb-3">
|
|
168
|
+
<span
|
|
169
|
+
className={cn(
|
|
170
|
+
"text-[15.5px] font-extrabold tracking-[-0.02em] sm:text-[17px]",
|
|
171
|
+
isHighlighted && "text-primary"
|
|
172
|
+
)}
|
|
173
|
+
>
|
|
174
|
+
{dayLabel}
|
|
175
|
+
</span>
|
|
176
|
+
<span
|
|
177
|
+
className={cn(
|
|
178
|
+
"font-mono text-xs",
|
|
179
|
+
isHighlighted ? "text-primary" : "text-muted-foreground"
|
|
180
|
+
)}
|
|
181
|
+
>
|
|
182
|
+
{countLabel}
|
|
183
|
+
</span>
|
|
184
|
+
|
|
185
|
+
{actionHref ? (
|
|
186
|
+
<LinkComponent href={actionHref} className="ml-auto">
|
|
187
|
+
{action}
|
|
188
|
+
</LinkComponent>
|
|
189
|
+
) : onActionClick ? (
|
|
190
|
+
<button
|
|
191
|
+
type="button"
|
|
192
|
+
onClick={onActionClick}
|
|
193
|
+
className="ml-auto cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-md"
|
|
194
|
+
>
|
|
195
|
+
{action}
|
|
196
|
+
</button>
|
|
197
|
+
) : (
|
|
198
|
+
action
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{prefersReducedMotion ? (
|
|
203
|
+
<div className="grid grid-cols-2 gap-3.5 px-4">{children}</div>
|
|
204
|
+
) : (
|
|
205
|
+
<div className="overflow-hidden px-4">
|
|
206
|
+
<div
|
|
207
|
+
className="flex gap-3.5"
|
|
208
|
+
style={{
|
|
209
|
+
animationName: WEEKEND_PANEL_SLIDE_KEYFRAMES_ID,
|
|
210
|
+
animationDuration: `${durationSeconds}s`,
|
|
211
|
+
animationTimingFunction: "ease-in-out",
|
|
212
|
+
animationIterationCount: "infinite",
|
|
213
|
+
animationPlayState: isPaused ? "paused" : "running",
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
{React.Children.map(children, (child) => (
|
|
217
|
+
<div className="w-[calc((100%-14px)/2)] flex-none">{child}</div>
|
|
218
|
+
))}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{!prefersReducedMotion && (
|
|
224
|
+
<div className="mt-3.5 grid grid-cols-[1fr_auto_1fr] items-center px-4">
|
|
225
|
+
<span aria-hidden />
|
|
226
|
+
|
|
227
|
+
<div className="flex items-center justify-center gap-1.5" aria-hidden>
|
|
228
|
+
<span
|
|
229
|
+
className="block h-1.5 w-[18px] rounded-full bg-primary"
|
|
230
|
+
style={{
|
|
231
|
+
animationName: WEEKEND_PANEL_DOT_A_KEYFRAMES_ID,
|
|
232
|
+
animationDuration: `${durationSeconds}s`,
|
|
233
|
+
animationTimingFunction: "ease-in-out",
|
|
234
|
+
animationIterationCount: "infinite",
|
|
235
|
+
animationPlayState: isPaused ? "paused" : "running",
|
|
236
|
+
}}
|
|
237
|
+
/>
|
|
238
|
+
<span
|
|
239
|
+
className="block h-1.5 w-1.5 rounded-full bg-muted-foreground/35"
|
|
240
|
+
style={{
|
|
241
|
+
animationName: WEEKEND_PANEL_DOT_B_KEYFRAMES_ID,
|
|
242
|
+
animationDuration: `${durationSeconds}s`,
|
|
243
|
+
animationTimingFunction: "ease-in-out",
|
|
244
|
+
animationIterationCount: "infinite",
|
|
245
|
+
animationPlayState: isPaused ? "paused" : "running",
|
|
246
|
+
}}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div className="flex justify-end">
|
|
251
|
+
{/* `MotionPlayPause`, not two swapped lucide glyphs. The triangle
|
|
252
|
+
folding into two bars IS the state change, so the control shows
|
|
253
|
+
what it did instead of cutting to a different icon — which is
|
|
254
|
+
the whole reason that gesture exists in the set.
|
|
255
|
+
|
|
256
|
+
`active` drives the HELD state and the bars are the active
|
|
257
|
+
variant, so it is `!isManuallyPaused`: while the carousel plays,
|
|
258
|
+
the button offers pause. `replayToken` bumps on pointer down so
|
|
259
|
+
the gesture answers the touch rather than its consequence.
|
|
260
|
+
|
|
261
|
+
`motion-reduce` is handled inside `MotionIcon`; the button keeps
|
|
262
|
+
its own for the background transition. */}
|
|
263
|
+
<button
|
|
264
|
+
type="button"
|
|
265
|
+
onClick={() => setIsManuallyPaused((current) => !current)}
|
|
266
|
+
onPointerDown={() => setPlayPauseReplayToken((n) => n + 1)}
|
|
267
|
+
aria-label={isManuallyPaused ? labels.play : labels.pause}
|
|
268
|
+
aria-pressed={isManuallyPaused}
|
|
269
|
+
className="flex h-11 w-11 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 motion-reduce:transition-none"
|
|
270
|
+
>
|
|
271
|
+
<MotionPlayPause
|
|
272
|
+
active={!isManuallyPaused}
|
|
273
|
+
replayToken={playPauseReplayToken}
|
|
274
|
+
className="size-4"
|
|
275
|
+
/>
|
|
276
|
+
</button>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export type WeekendPanelSkeletonProps = {
|
|
285
|
+
isHighlighted?: boolean;
|
|
286
|
+
className?: string;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
/** The panel at rest: the two visible cards of the two-up carousel, no slide. */
|
|
290
|
+
export function WeekendPanelSkeleton({ isHighlighted, className }: WeekendPanelSkeletonProps) {
|
|
291
|
+
return (
|
|
292
|
+
<div
|
|
293
|
+
aria-hidden="true"
|
|
294
|
+
className={cn(
|
|
295
|
+
"relative overflow-hidden rounded-2xl border bg-card py-4",
|
|
296
|
+
isHighlighted ? "border-primary/40" : "border-border",
|
|
297
|
+
className
|
|
298
|
+
)}
|
|
299
|
+
>
|
|
300
|
+
<div className="flex items-baseline gap-[9px] px-4 pb-3">
|
|
301
|
+
<Skeleton className="h-[15.5px] w-16 sm:h-[17px]" />
|
|
302
|
+
<Skeleton className="h-3 w-14" />
|
|
303
|
+
<Skeleton className="ml-auto h-3 w-14" />
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div className="grid grid-cols-2 gap-3.5 px-4">
|
|
307
|
+
{Array.from({ length: 2 }, (_, index) => (
|
|
308
|
+
<div key={index} className="flex flex-col gap-1.5">
|
|
309
|
+
<Skeleton className="aspect-[4/3] w-full" />
|
|
310
|
+
<Skeleton className="h-[13.5px] w-4/5" />
|
|
311
|
+
<Skeleton className="h-3 w-3/5" />
|
|
312
|
+
</div>
|
|
313
|
+
))}
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<div className="flex items-center justify-center gap-1.5 pt-3.5">
|
|
317
|
+
<span className="block h-1.5 w-[18px] rounded-full bg-muted" />
|
|
318
|
+
<span className="block h-1.5 w-1.5 rounded-full bg-muted" />
|
|
319
|
+
</div>
|
|
320
|
+
</div>
|
|
321
|
+
);
|
|
322
|
+
}
|
package/src/globals.css
CHANGED
|
@@ -137,6 +137,17 @@ h6 {
|
|
|
137
137
|
0 2px 4px -1px oklch(0.52 0 0 / 0.07), 0 10px 24px -6px oklch(0.52 0 0 / 0.16);
|
|
138
138
|
--shadow-overlay:
|
|
139
139
|
0 4px 12px -2px oklch(0.52 0 0 / 0.1), 0 16px 48px -12px oklch(0.52 0 0 / 0.2);
|
|
140
|
+
/* Panel elevation — the same apparent height as --shadow-card under a much
|
|
141
|
+
larger object. Elevation is a function of area: a 6px penumbra under a
|
|
142
|
+
700px panel reads as a print bevel, not a lift, so the blur widens and the
|
|
143
|
+
peak alpha drops and the total ink scales with the object instead of
|
|
144
|
+
stacking. Deliberately stays BELOW --shadow-card-hover in weight — a
|
|
145
|
+
RESTING panel that out-shadows a card's hover makes hover stop meaning
|
|
146
|
+
anything anywhere. And not --shadow-overlay, which is plane 2 (sheets,
|
|
147
|
+
dialogs); this sits in page flow. Chroma 0, like its neighbours: a 16px
|
|
148
|
+
blur across a full-width panel is the largest soft area on the page and
|
|
149
|
+
the least attributable place for a colour cast. */
|
|
150
|
+
--shadow-panel: 0 1px 2px oklch(0.52 0 0 / 0.04), 0 4px 16px -4px oklch(0.52 0 0 / 0.07);
|
|
140
151
|
|
|
141
152
|
/* Brown variant — replaces hardcoded #482E1F across button/badge */
|
|
142
153
|
--brown: oklch(0.31 0.05 45);
|
|
@@ -205,6 +216,13 @@ h6 {
|
|
|
205
216
|
--ease-spring-smooth: cubic-bezier(0.22, 1, 0.36, 1);
|
|
206
217
|
--ease-spring-soft: cubic-bezier(0.16, 1, 0.3, 1);
|
|
207
218
|
|
|
219
|
+
/* Timeline geometry — shared by `TimelineBand`, which lays the columns out,
|
|
220
|
+
and `HourGroup`, whose spine puck positions itself against that gutter from
|
|
221
|
+
inside the content column. Two components deriving the same offset from two
|
|
222
|
+
hardcoded numbers is how they drift apart. */
|
|
223
|
+
--timeline-lane: 396px;
|
|
224
|
+
--timeline-spine: 6rem;
|
|
225
|
+
|
|
208
226
|
/* Z-index scale */
|
|
209
227
|
--z-base: 0;
|
|
210
228
|
--z-sticky: 100;
|
|
@@ -265,6 +283,7 @@ h6 {
|
|
|
265
283
|
--shadow-card: 0 1px 3px oklch(0 0 0 / 0.2), 0 1px 2px oklch(0 0 0 / 0.15);
|
|
266
284
|
--shadow-card-hover: 0 4px 16px oklch(0 0 0 / 0.3), 0 1px 4px oklch(0 0 0 / 0.2);
|
|
267
285
|
--shadow-overlay: 0 8px 32px oklch(0 0 0 / 0.4), 0 2px 8px oklch(0 0 0 / 0.25);
|
|
286
|
+
--shadow-panel: 0 1px 3px oklch(0 0 0 / 0.24), 0 8px 24px -8px oklch(0 0 0 / 0.34);
|
|
268
287
|
--sidebar: oklch(0.205 0 0);
|
|
269
288
|
--sidebar-foreground: oklch(0.985 0 0);
|
|
270
289
|
--sidebar-primary: oklch(0.488 0.243 264.376);
|