@bearmenu/ui 0.8.7 → 0.8.8
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 → chunk-3TNPSN4D.js} +28 -9
- package/dist/chunk-AHL45DD2.js +145 -0
- package/dist/chunk-IYDH5IU6.js +66 -0
- package/dist/chunk-OPVKL4VS.js +18 -0
- package/dist/components/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/category-deck.js +10 -1
- package/dist/components/coverflow.d.ts +11 -3
- package/dist/components/coverflow.js +34 -51
- package/dist/components/date-input.d.ts +1 -1
- package/dist/components/drift-rail.d.ts +24 -10
- package/dist/components/drift-rail.js +1 -1
- package/dist/components/event-card.d.ts +8 -0
- package/dist/components/event-card.js +63 -33
- package/dist/components/event-compact-row.d.ts +32 -5
- package/dist/components/event-compact-row.js +119 -38
- package/dist/components/event-day-header.js +3 -1
- package/dist/components/event-day-separator.d.ts +7 -1
- package/dist/components/event-day-separator.js +3 -2
- package/dist/components/event-hub-hero.d.ts +25 -4
- package/dist/components/event-hub-hero.js +89 -19
- package/dist/components/event-list-row.d.ts +19 -6
- package/dist/components/event-list-row.js +78 -34
- package/dist/components/hour-band.d.ts +38 -40
- package/dist/components/hour-band.js +55 -27
- package/dist/components/hour-group.js +1 -1
- package/dist/components/poster-tape.js +1 -1
- package/dist/components/poster-wall.d.ts +3 -3
- package/dist/components/poster-wall.js +73 -17
- package/dist/components/section-header.d.ts +16 -1
- package/dist/components/section-header.js +26 -7
- package/dist/components/snap-rail.d.ts +34 -0
- package/dist/components/snap-rail.js +51 -0
- package/dist/components/tag.d.ts +2 -2
- package/dist/components/text.d.ts +1 -1
- package/dist/components/timeline-band.d.ts +20 -2
- package/dist/components/timeline-band.js +1 -1
- package/dist/components/timeline-playhead.d.ts +26 -0
- package/dist/components/timeline-playhead.js +25 -0
- package/dist/components/weekend-panel.d.ts +5 -4
- package/dist/components/weekend-panel.js +85 -71
- package/dist/hooks/use-drag-swipe.d.ts +44 -0
- package/dist/hooks/use-drag-swipe.js +2 -0
- package/dist/hooks/use-media-query.d.ts +17 -0
- package/dist/hooks/use-media-query.js +2 -0
- package/package.json +1 -1
- package/src/components/category-deck.tsx +19 -8
- package/src/components/coverflow.test.tsx +28 -1
- package/src/components/coverflow.tsx +37 -43
- package/src/components/drift-rail.tsx +167 -42
- package/src/components/event-card.tsx +55 -10
- package/src/components/event-compact-row.stories.tsx +30 -0
- package/src/components/event-compact-row.test.tsx +49 -0
- package/src/components/event-compact-row.tsx +161 -37
- package/src/components/event-day-header.tsx +3 -1
- package/src/components/event-day-separator.tsx +8 -1
- package/src/components/event-hub-hero.stories.tsx +30 -0
- package/src/components/event-hub-hero.tsx +102 -30
- package/src/components/event-list-row.stories.tsx +34 -0
- package/src/components/event-list-row.test.tsx +53 -0
- package/src/components/event-list-row.tsx +135 -87
- package/src/components/hour-band.stories.tsx +28 -5
- package/src/components/hour-band.tsx +90 -70
- package/src/components/poster-wall.test.tsx +28 -0
- package/src/components/poster-wall.tsx +96 -23
- package/src/components/section-header.stories.tsx +24 -0
- package/src/components/section-header.test.tsx +28 -0
- package/src/components/section-header.tsx +45 -7
- package/src/components/snap-rail.stories.tsx +79 -0
- package/src/components/snap-rail.test.tsx +18 -0
- package/src/components/snap-rail.tsx +67 -0
- package/src/components/timeline-band.tsx +52 -12
- package/src/components/timeline-playhead.stories.tsx +30 -0
- package/src/components/timeline-playhead.test.tsx +11 -0
- package/src/components/timeline-playhead.tsx +46 -0
- package/src/components/weekend-panel.test.tsx +43 -0
- package/src/components/weekend-panel.tsx +142 -108
- package/src/hooks/use-drag-swipe.test.tsx +66 -0
- package/src/hooks/use-drag-swipe.ts +105 -0
- package/src/hooks/use-media-query.ts +31 -0
- package/dist/chunk-GPH6L4DX.js +0 -55
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { Separator } from './chunk-WSRWFA6K.js';
|
|
2
2
|
import { Skeleton } from './chunk-QXHMPJ35.js';
|
|
3
3
|
import { cn } from './chunk-FEK5XGZW.js';
|
|
4
|
+
import { Radio } from 'lucide-react';
|
|
4
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
6
|
|
|
6
7
|
var LIVE_PUCK_PULSE_CLASS = "bm-timeline-live-puck-pulse";
|
|
7
|
-
|
|
8
|
+
var TIMELINE_MOBILE_GUTTER_CLASS = "w-[58px]";
|
|
9
|
+
function TimelineHourPuck({
|
|
10
|
+
time,
|
|
11
|
+
isLive,
|
|
12
|
+
liveLabel,
|
|
13
|
+
variant = "station",
|
|
14
|
+
className
|
|
15
|
+
}) {
|
|
16
|
+
const isChip = variant === "chip";
|
|
8
17
|
return /* @__PURE__ */ jsxs("span", { className: cn("relative flex justify-center", className), children: [
|
|
9
18
|
isLive && /* @__PURE__ */ jsx("style", { children: `
|
|
10
19
|
@keyframes ${LIVE_PUCK_PULSE_CLASS} {
|
|
@@ -18,23 +27,33 @@ function TimelineHourPuck({ time, isLive, liveLabel, className }) {
|
|
|
18
27
|
.${LIVE_PUCK_PULSE_CLASS} { animation: none; }
|
|
19
28
|
}
|
|
20
29
|
` }),
|
|
21
|
-
/* @__PURE__ */
|
|
30
|
+
/* @__PURE__ */ jsxs(
|
|
22
31
|
"span",
|
|
23
32
|
{
|
|
24
33
|
className: cn(
|
|
25
|
-
"whitespace-nowrap
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
"whitespace-nowrap font-mono tabular-nums",
|
|
35
|
+
isChip ? cn(
|
|
36
|
+
"inline-flex h-6 min-w-[58px] items-center justify-center gap-1 rounded-[7px] px-1 text-[12px] font-medium",
|
|
37
|
+
isLive ? cn("bg-success/10 text-success", LIVE_PUCK_PULSE_CLASS) : "bg-muted text-foreground"
|
|
38
|
+
) : cn(
|
|
39
|
+
"rounded-[7px] border bg-background px-[7px] py-[3px] text-[12.5px] font-medium",
|
|
40
|
+
isLive ? cn("border-success/40 text-success", LIVE_PUCK_PULSE_CLASS) : "border-border text-foreground"
|
|
41
|
+
)
|
|
28
42
|
),
|
|
29
|
-
children:
|
|
43
|
+
children: [
|
|
44
|
+
isChip && isLive && /* @__PURE__ */ jsx(Radio, { className: "h-2.5 w-2.5 shrink-0", "aria-hidden": true }),
|
|
45
|
+
time
|
|
46
|
+
]
|
|
30
47
|
}
|
|
31
48
|
),
|
|
32
49
|
isLive && !!liveLabel && /* @__PURE__ */ jsx(
|
|
33
50
|
"span",
|
|
34
51
|
{
|
|
35
52
|
className: cn(
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
isChip ? "sr-only" : cn(
|
|
54
|
+
"absolute top-[calc(100%+6px)] whitespace-nowrap bg-background px-1",
|
|
55
|
+
"text-[9.5px] font-extrabold tracking-[0.08em] text-success"
|
|
56
|
+
)
|
|
38
57
|
),
|
|
39
58
|
children: liveLabel
|
|
40
59
|
}
|
|
@@ -82,4 +101,4 @@ function TimelineBandSkeleton({ className }) {
|
|
|
82
101
|
] });
|
|
83
102
|
}
|
|
84
103
|
|
|
85
|
-
export { TimelineBand, TimelineBandSkeleton, TimelineHourPuck };
|
|
104
|
+
export { TIMELINE_MOBILE_GUTTER_CLASS, TimelineBand, TimelineBandSkeleton, TimelineHourPuck };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { cn } from './chunk-FEK5XGZW.js';
|
|
2
|
+
import { useRef, useState, useEffect } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var DRIFT_RAIL_DEFAULT_DURATION_SECONDS = 42;
|
|
6
|
+
var DRIFT_RAIL_RESUME_DELAY_MS = 3e3;
|
|
7
|
+
function DriftRail({
|
|
8
|
+
children,
|
|
9
|
+
reverse = false,
|
|
10
|
+
durationSeconds = DRIFT_RAIL_DEFAULT_DURATION_SECONDS,
|
|
11
|
+
pauseOnHover = true,
|
|
12
|
+
className
|
|
13
|
+
}) {
|
|
14
|
+
const scrollerRef = useRef(null);
|
|
15
|
+
const trackRef = useRef(null);
|
|
16
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
17
|
+
const [inView, setInView] = useState(true);
|
|
18
|
+
const [reducedMotion, setReducedMotion] = useState(false);
|
|
19
|
+
const heldUntil = useRef(0);
|
|
20
|
+
const mouseDrag = useRef(null);
|
|
21
|
+
const position = useRef(0);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
24
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
25
|
+
setReducedMotion(query.matches);
|
|
26
|
+
const onChange = () => setReducedMotion(query.matches);
|
|
27
|
+
query.addEventListener("change", onChange);
|
|
28
|
+
return () => query.removeEventListener("change", onChange);
|
|
29
|
+
}, []);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const node = scrollerRef.current;
|
|
32
|
+
if (!node || typeof IntersectionObserver === "undefined") return void 0;
|
|
33
|
+
const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), {
|
|
34
|
+
threshold: 0
|
|
35
|
+
});
|
|
36
|
+
observer.observe(node);
|
|
37
|
+
return () => observer.disconnect();
|
|
38
|
+
}, []);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
const scroller = scrollerRef.current;
|
|
41
|
+
const track = trackRef.current;
|
|
42
|
+
if (!scroller || !track) return void 0;
|
|
43
|
+
let frame = 0;
|
|
44
|
+
let last = performance.now();
|
|
45
|
+
const drifting = !reducedMotion && inView && !(pauseOnHover && isHovered);
|
|
46
|
+
if (reverse && scroller.scrollLeft === 0) scroller.scrollLeft = track.scrollWidth / 2;
|
|
47
|
+
position.current = scroller.scrollLeft;
|
|
48
|
+
const tick = (now) => {
|
|
49
|
+
const half = track.scrollWidth / 2;
|
|
50
|
+
if (half > 0) {
|
|
51
|
+
const driftingNow = drifting && now >= heldUntil.current;
|
|
52
|
+
if (driftingNow) {
|
|
53
|
+
const perSecond = half / durationSeconds;
|
|
54
|
+
const delta = perSecond * (now - last) / 1e3;
|
|
55
|
+
position.current += reverse ? -delta : delta;
|
|
56
|
+
} else {
|
|
57
|
+
position.current = scroller.scrollLeft;
|
|
58
|
+
}
|
|
59
|
+
let wrapped = false;
|
|
60
|
+
if (position.current >= half) {
|
|
61
|
+
position.current -= half;
|
|
62
|
+
wrapped = true;
|
|
63
|
+
} else if (position.current < 0) {
|
|
64
|
+
position.current += half;
|
|
65
|
+
wrapped = true;
|
|
66
|
+
}
|
|
67
|
+
if (driftingNow || wrapped) scroller.scrollLeft = position.current;
|
|
68
|
+
}
|
|
69
|
+
last = now;
|
|
70
|
+
frame = requestAnimationFrame(tick);
|
|
71
|
+
};
|
|
72
|
+
frame = requestAnimationFrame(tick);
|
|
73
|
+
return () => cancelAnimationFrame(frame);
|
|
74
|
+
}, [durationSeconds, inView, isHovered, pauseOnHover, reducedMotion, reverse]);
|
|
75
|
+
const hold = () => {
|
|
76
|
+
heldUntil.current = Number.POSITIVE_INFINITY;
|
|
77
|
+
};
|
|
78
|
+
const release = () => {
|
|
79
|
+
heldUntil.current = performance.now() + DRIFT_RAIL_RESUME_DELAY_MS;
|
|
80
|
+
};
|
|
81
|
+
const onPointerDown = (event) => {
|
|
82
|
+
hold();
|
|
83
|
+
if (event.pointerType !== "mouse" || event.button !== 0 || !scrollerRef.current) return;
|
|
84
|
+
mouseDrag.current = {
|
|
85
|
+
startX: event.clientX,
|
|
86
|
+
startScroll: scrollerRef.current.scrollLeft,
|
|
87
|
+
pointerId: event.pointerId
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
const onPointerMove = (event) => {
|
|
91
|
+
var _a, _b, _c, _d;
|
|
92
|
+
const drag = mouseDrag.current;
|
|
93
|
+
if (!drag || event.pointerId !== drag.pointerId || !scrollerRef.current) return;
|
|
94
|
+
const dx = event.clientX - drag.startX;
|
|
95
|
+
if (Math.abs(dx) > 4 && !((_b = (_a = event.currentTarget).hasPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId))) {
|
|
96
|
+
(_d = (_c = event.currentTarget).setPointerCapture) == null ? void 0 : _d.call(_c, event.pointerId);
|
|
97
|
+
event.currentTarget.dataset.dragging = "true";
|
|
98
|
+
}
|
|
99
|
+
scrollerRef.current.scrollLeft = drag.startScroll - dx;
|
|
100
|
+
};
|
|
101
|
+
const onPointerEnd = (event) => {
|
|
102
|
+
var _a, _b;
|
|
103
|
+
release();
|
|
104
|
+
const drag = mouseDrag.current;
|
|
105
|
+
mouseDrag.current = null;
|
|
106
|
+
if (drag && ((_b = (_a = event.currentTarget).hasPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId))) {
|
|
107
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
108
|
+
}
|
|
109
|
+
delete event.currentTarget.dataset.dragging;
|
|
110
|
+
};
|
|
111
|
+
const onClickCapture = (event) => {
|
|
112
|
+
if (event.currentTarget.dataset.dragging === "true") {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
return /* @__PURE__ */ jsx(
|
|
118
|
+
"div",
|
|
119
|
+
{
|
|
120
|
+
ref: scrollerRef,
|
|
121
|
+
onMouseEnter: () => setIsHovered(true),
|
|
122
|
+
onMouseLeave: () => setIsHovered(false),
|
|
123
|
+
onPointerDown,
|
|
124
|
+
onPointerMove,
|
|
125
|
+
onPointerUp: onPointerEnd,
|
|
126
|
+
onPointerCancel: onPointerEnd,
|
|
127
|
+
onWheel: () => {
|
|
128
|
+
release();
|
|
129
|
+
},
|
|
130
|
+
onDragStart: (event) => event.preventDefault(),
|
|
131
|
+
onClickCapture,
|
|
132
|
+
className: cn(
|
|
133
|
+
"overflow-x-auto overflow-y-hidden py-1.5 scrollbar-hide [touch-action:pan-x]",
|
|
134
|
+
"cursor-grab select-none data-[dragging=true]:cursor-grabbing",
|
|
135
|
+
className
|
|
136
|
+
),
|
|
137
|
+
children: /* @__PURE__ */ jsxs("div", { ref: trackRef, className: "flex w-max items-stretch gap-3.5", children: [
|
|
138
|
+
children,
|
|
139
|
+
/* @__PURE__ */ jsx("div", { className: "contents", "aria-hidden": "true", children })
|
|
140
|
+
] })
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { DriftRail };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useState, useRef, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
function useDragSwipe({ threshold = 40, onSwipe, disabled }) {
|
|
4
|
+
const [dragX, setDragX] = useState(0);
|
|
5
|
+
const startX = useRef(null);
|
|
6
|
+
const pointerId = useRef(null);
|
|
7
|
+
const didDrag = useRef(false);
|
|
8
|
+
const onPointerDown = useCallback(
|
|
9
|
+
(event) => {
|
|
10
|
+
if (disabled || event.button !== 0) return;
|
|
11
|
+
startX.current = event.clientX;
|
|
12
|
+
pointerId.current = event.pointerId;
|
|
13
|
+
didDrag.current = false;
|
|
14
|
+
},
|
|
15
|
+
[disabled]
|
|
16
|
+
);
|
|
17
|
+
const onPointerMove = useCallback((event) => {
|
|
18
|
+
var _a, _b;
|
|
19
|
+
if (startX.current === null || event.pointerId !== pointerId.current) return;
|
|
20
|
+
const dx = event.clientX - startX.current;
|
|
21
|
+
if (!didDrag.current && Math.abs(dx) > 6) {
|
|
22
|
+
didDrag.current = true;
|
|
23
|
+
(_b = (_a = event.currentTarget).setPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId);
|
|
24
|
+
}
|
|
25
|
+
if (didDrag.current) setDragX(dx);
|
|
26
|
+
}, []);
|
|
27
|
+
const end = useCallback(
|
|
28
|
+
(event, cancelled) => {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
if (startX.current === null || event.pointerId !== pointerId.current) return;
|
|
31
|
+
const dx = event.clientX - startX.current;
|
|
32
|
+
startX.current = null;
|
|
33
|
+
pointerId.current = null;
|
|
34
|
+
if ((_b = (_a = event.currentTarget).hasPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId)) {
|
|
35
|
+
event.currentTarget.releasePointerCapture(event.pointerId);
|
|
36
|
+
}
|
|
37
|
+
setDragX(0);
|
|
38
|
+
if (!cancelled && didDrag.current && Math.abs(dx) >= threshold) {
|
|
39
|
+
onSwipe(dx < 0 ? 1 : -1);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
[onSwipe, threshold]
|
|
43
|
+
);
|
|
44
|
+
const onPointerUp = useCallback((event) => end(event, false), [end]);
|
|
45
|
+
const onPointerCancel = useCallback(
|
|
46
|
+
(event) => end(event, true),
|
|
47
|
+
[end]
|
|
48
|
+
);
|
|
49
|
+
const onDragStart = useCallback((event) => {
|
|
50
|
+
event.preventDefault();
|
|
51
|
+
}, []);
|
|
52
|
+
const onClickCapture = useCallback((event) => {
|
|
53
|
+
if (didDrag.current) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
didDrag.current = false;
|
|
57
|
+
}
|
|
58
|
+
}, []);
|
|
59
|
+
return {
|
|
60
|
+
dragX,
|
|
61
|
+
isDragging: dragX !== 0,
|
|
62
|
+
handlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel, onClickCapture, onDragStart }
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { useDragSwipe };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
|
|
3
|
+
function useMediaQuery(query) {
|
|
4
|
+
return useSyncExternalStore(
|
|
5
|
+
(onChange) => {
|
|
6
|
+
if (typeof window.matchMedia !== "function") return () => {
|
|
7
|
+
};
|
|
8
|
+
const mql = window.matchMedia(query);
|
|
9
|
+
mql.addEventListener("change", onChange);
|
|
10
|
+
return () => mql.removeEventListener("change", onChange);
|
|
11
|
+
},
|
|
12
|
+
() => typeof window.matchMedia === "function" ? window.matchMedia(query).matches : false,
|
|
13
|
+
() => false
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
var DESKTOP_MEDIA_QUERY = "(min-width: 1024px)";
|
|
17
|
+
|
|
18
|
+
export { DESKTOP_MEDIA_QUERY, useMediaQuery };
|
|
@@ -3,10 +3,10 @@ import * as React from 'react';
|
|
|
3
3
|
import { VariantProps } from 'class-variance-authority';
|
|
4
4
|
|
|
5
5
|
declare const alertVariants: (props?: ({
|
|
6
|
-
variant?: "
|
|
6
|
+
variant?: "success" | "warning" | "default" | "destructive" | null | undefined;
|
|
7
7
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
8
8
|
declare const Alert: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLDivElement> & VariantProps<(props?: ({
|
|
9
|
-
variant?: "
|
|
9
|
+
variant?: "success" | "warning" | "default" | "destructive" | null | undefined;
|
|
10
10
|
} & class_variance_authority_types.ClassProp) | undefined) => string> & React.RefAttributes<HTMLDivElement>>;
|
|
11
11
|
declare const AlertTitle: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLHeadingElement> & React.RefAttributes<HTMLParagraphElement>>;
|
|
12
12
|
declare const AlertDescription: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLParagraphElement> & React.RefAttributes<HTMLParagraphElement>>;
|
|
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|
|
4
4
|
import { VariantProps } from 'class-variance-authority';
|
|
5
5
|
|
|
6
6
|
declare const badgeVariants: (props?: ({
|
|
7
|
-
variant?: "
|
|
7
|
+
variant?: "success" | "warning" | "default" | "destructive" | "outline" | "secondary" | "glass" | "overlay" | "brown" | null | undefined;
|
|
8
8
|
live?: boolean | null | undefined;
|
|
9
9
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
10
10
|
interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {
|
|
@@ -57,7 +57,10 @@ function CategoryDeck({
|
|
|
57
57
|
{
|
|
58
58
|
className: cn(
|
|
59
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%.
|
|
60
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)]",
|
|
61
64
|
"sm:w-[120px]",
|
|
62
65
|
transform.rest,
|
|
63
66
|
transform.spread,
|
|
@@ -86,7 +89,13 @@ function CategoryDeck({
|
|
|
86
89
|
/* @__PURE__ */ jsx("div", { className: "truncate text-[14.5px] font-extrabold tracking-[-0.015em] sm:text-base", children: name }),
|
|
87
90
|
/* @__PURE__ */ jsx("div", { className: "truncate font-mono text-[11.5px] text-muted-foreground", children: meta })
|
|
88
91
|
] }),
|
|
89
|
-
/* @__PURE__ */ jsx(
|
|
92
|
+
/* @__PURE__ */ jsx(
|
|
93
|
+
ChevronRight,
|
|
94
|
+
{
|
|
95
|
+
className: "ml-auto h-4 w-4 shrink-0 text-muted-foreground",
|
|
96
|
+
"aria-hidden": true
|
|
97
|
+
}
|
|
98
|
+
)
|
|
90
99
|
] })
|
|
91
100
|
] });
|
|
92
101
|
const rootClasses = cn(
|
|
@@ -3,9 +3,17 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
3
3
|
/**
|
|
4
4
|
* Coverflow — the busiest-events browser: a 3D perspective lane with a large
|
|
5
5
|
* lead poster flanked by up to three shrinking, desaturating posters on each
|
|
6
|
-
* side.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* side.
|
|
7
|
+
*
|
|
8
|
+
* NOTHING MOVES ON ITS OWN. The lane used to drift ±40px on a loop; it was
|
|
9
|
+
* removed at product request — a stage that sways is read as a carousel that
|
|
10
|
+
* is about to advance, and the reader waits for it instead of touching it.
|
|
11
|
+
* The stage is driven by the reader: a horizontal drag (touch, pen or mouse)
|
|
12
|
+
* steps the ring one card in the drag's direction and the lane follows the
|
|
13
|
+
* finger a little on the way, so the gesture has weight. The prev/next arrows
|
|
14
|
+
* stay for desktop and keyboard; below `lg` they are hidden so nothing sits
|
|
15
|
+
* over the lead card — the swipe is the control there, and every card is
|
|
16
|
+
* still a focusable, named button for keyboard and screen-reader users.
|
|
9
17
|
*
|
|
10
18
|
* IT IS A RING, NOT A LANE. A card's slot comes from `ringOffset` — the
|
|
11
19
|
* SIGNED SHORTEST WAY ROUND the list, not the raw `index - activeIndex`. With
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { useDragSwipe } from '../chunk-IYDH5IU6.js';
|
|
1
2
|
import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
|
|
2
3
|
import { cn } from '../chunk-FEK5XGZW.js';
|
|
3
|
-
import {
|
|
4
|
-
import { useId,
|
|
4
|
+
import { __spreadProps, __spreadValues, __objRest } from '../chunk-3GWWZKX7.js';
|
|
5
|
+
import { useId, useState } from 'react';
|
|
5
6
|
import { ChevronLeft, ChevronRight, Gift } from 'lucide-react';
|
|
6
7
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
8
|
|
|
@@ -21,10 +22,8 @@ var COVERFLOW_CARD_FOCUS_CLASS = "focus-visible:outline-none focus-visible:ring-
|
|
|
21
22
|
var COVERFLOW_SIDE_SHADOW_CLASS = "shadow-[0_11px_27px_oklch(0.3_0.02_60/0.22)]";
|
|
22
23
|
var COVERFLOW_LEAD_SHADOW_CLASS = "shadow-[0_24px_56px_oklch(0.28_0.03_45/0.32)]";
|
|
23
24
|
var COVERFLOW_PILL_CLASS = "inline-flex h-6 max-w-[85%] items-center gap-1 rounded-full px-2 text-[11px] font-semibold";
|
|
24
|
-
var COVERFLOW_DRIFT_DURATION_SECONDS = 26;
|
|
25
|
-
var COVERFLOW_DRIFT_PIXELS = 40;
|
|
26
25
|
var COVERFLOW_LEAD_Z_INDEX = 6;
|
|
27
|
-
var COVERFLOW_ARROW_CLASS = "absolute top-1/2 z-20
|
|
26
|
+
var COVERFLOW_ARROW_CLASS = "absolute top-1/2 z-20 hidden h-11 w-11 -translate-y-1/2 lg:flex cursor-pointer items-center justify-center rounded-full bg-background/85 text-foreground shadow-[var(--shadow-overlay)] backdrop-blur-sm transition-colors duration-150 hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 motion-reduce:transition-none";
|
|
28
27
|
function wrapIndex(index, length) {
|
|
29
28
|
return (index % length + length) % length;
|
|
30
29
|
}
|
|
@@ -141,7 +140,6 @@ function Coverflow({
|
|
|
141
140
|
}) {
|
|
142
141
|
const scopeId = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
143
142
|
const scopeClass = `coverflow-${scopeId}`;
|
|
144
|
-
const containerRef = useRef(null);
|
|
145
143
|
const length = items.length;
|
|
146
144
|
const active = length > 0 ? wrapIndex(activeIndex, length) : 0;
|
|
147
145
|
const [step, setStep] = useState({ from: active, to: active, length });
|
|
@@ -150,21 +148,13 @@ function Coverflow({
|
|
|
150
148
|
step.length === length ? { from: step.to, to: active, length } : { from: active, to: active, length }
|
|
151
149
|
);
|
|
152
150
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
node.dataset.offscreen = entry.isIntersecting ? "false" : "true";
|
|
159
|
-
},
|
|
160
|
-
{ threshold: 0 }
|
|
161
|
-
);
|
|
162
|
-
observer.observe(node);
|
|
163
|
-
return () => observer.disconnect();
|
|
164
|
-
}, []);
|
|
151
|
+
const goTo = (index) => onActiveIndexChange == null ? void 0 : onActiveIndexChange(wrapIndex(index, length));
|
|
152
|
+
const { dragX, isDragging, handlers } = useDragSwipe({
|
|
153
|
+
onSwipe: (direction) => goTo(active + direction),
|
|
154
|
+
disabled: length < 2
|
|
155
|
+
});
|
|
165
156
|
if (length === 0) return null;
|
|
166
157
|
const activeItem = items[active];
|
|
167
|
-
const goTo = (index) => onActiveIndexChange == null ? void 0 : onActiveIndexChange(wrapIndex(index, length));
|
|
168
158
|
const activeDelta = ringOffset(step.to - step.from, length);
|
|
169
159
|
const visibleCards = items.map((item, index) => {
|
|
170
160
|
const offset = ringOffset(index - active, length);
|
|
@@ -208,28 +198,11 @@ function Coverflow({
|
|
|
208
198
|
--cf-tier-base: 0.5714;
|
|
209
199
|
}
|
|
210
200
|
}
|
|
211
|
-
.${scopeClass} .coverflow-lane {
|
|
212
|
-
animation: ${scopeClass}-drift ${COVERFLOW_DRIFT_DURATION_SECONDS}s ease-in-out infinite alternate;
|
|
213
|
-
}
|
|
214
|
-
.${scopeClass}[data-offscreen="true"] .coverflow-lane {
|
|
215
|
-
animation-play-state: paused;
|
|
216
|
-
}
|
|
217
|
-
@keyframes ${scopeClass}-drift {
|
|
218
|
-
from { transform: translateX(-${COVERFLOW_DRIFT_PIXELS}px); }
|
|
219
|
-
to { transform: translateX(${COVERFLOW_DRIFT_PIXELS}px); }
|
|
220
|
-
}
|
|
221
|
-
@media (prefers-reduced-motion: reduce) {
|
|
222
|
-
.${scopeClass} .coverflow-lane {
|
|
223
|
-
animation: none;
|
|
224
|
-
transform: none;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
201
|
` }),
|
|
228
202
|
/* @__PURE__ */ jsxs(
|
|
229
203
|
"div",
|
|
230
|
-
{
|
|
231
|
-
|
|
232
|
-
className: "relative flex h-[300px] items-center justify-center overflow-x-clip overflow-y-visible [perspective:900px] sm:h-[380px] sm:[perspective:1400px]",
|
|
204
|
+
__spreadProps(__spreadValues({}, handlers), {
|
|
205
|
+
className: "relative flex h-[300px] select-none items-center justify-center overflow-x-clip overflow-y-visible [touch-action:pan-y] [perspective:900px] sm:h-[380px] sm:[perspective:1400px]",
|
|
233
206
|
children: [
|
|
234
207
|
/* @__PURE__ */ jsx(
|
|
235
208
|
"div",
|
|
@@ -267,21 +240,31 @@ function Coverflow({
|
|
|
267
240
|
}
|
|
268
241
|
)
|
|
269
242
|
] }),
|
|
270
|
-
/* @__PURE__ */ jsx(
|
|
271
|
-
|
|
243
|
+
/* @__PURE__ */ jsx(
|
|
244
|
+
"div",
|
|
272
245
|
{
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
246
|
+
className: cn(
|
|
247
|
+
"coverflow-lane relative h-full w-full [transform-style:preserve-3d]",
|
|
248
|
+
!isDragging && "transition-transform duration-300 ease-out motion-reduce:transition-none"
|
|
249
|
+
),
|
|
250
|
+
style: { transform: `translateX(${dragX / 3}px)` },
|
|
251
|
+
children: visibleCards.map(({ item, index, offset, suppressTransition }) => /* @__PURE__ */ jsx(
|
|
252
|
+
CoverflowCard,
|
|
253
|
+
{
|
|
254
|
+
item,
|
|
255
|
+
index,
|
|
256
|
+
offset,
|
|
257
|
+
suppressTransition,
|
|
258
|
+
freeLabel,
|
|
259
|
+
onActivate: goTo,
|
|
260
|
+
linkComponent: LinkComponent
|
|
261
|
+
},
|
|
262
|
+
item.id
|
|
263
|
+
))
|
|
264
|
+
}
|
|
265
|
+
)
|
|
283
266
|
]
|
|
284
|
-
}
|
|
267
|
+
})
|
|
285
268
|
),
|
|
286
269
|
/* @__PURE__ */ jsxs("div", { className: "mt-4 space-y-4 text-center", children: [
|
|
287
270
|
/* @__PURE__ */ jsxs("div", { "aria-live": "polite", children: [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
|
|
3
|
-
declare const DateInput: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "
|
|
3
|
+
declare const DateInput: React.ForwardRefExoticComponent<Omit<React.InputHTMLAttributes<HTMLInputElement>, "size" | "type"> & {
|
|
4
4
|
/** Native input type. Defaults to "date". */
|
|
5
5
|
type?: "date" | "datetime-local" | "time" | "month" | "week";
|
|
6
6
|
size?: "sm" | "default" | "lg";
|
|
@@ -2,22 +2,36 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* DriftRail — an infinite horizontal marquee for logos, tag chips or small
|
|
5
|
-
* poster tiles. The caller passes one set of
|
|
6
|
-
* a second, `aria-hidden` copy right after it
|
|
7
|
-
* seamless. Because the loop depends on the duplicate being
|
|
8
|
-
* to the first pass, keep `children` a fixed, ordered list —
|
|
9
|
-
* randomisation.
|
|
5
|
+
* poster tiles that the reader can take hold of. The caller passes one set of
|
|
6
|
+
* children; the component renders a second, `aria-hidden` copy right after it
|
|
7
|
+
* so the loop is seamless. Because the loop depends on the duplicate being
|
|
8
|
+
* pixel-identical to the first pass, keep `children` a fixed, ordered list —
|
|
9
|
+
* no per-render randomisation.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* IT IS A SCROLLER THAT DRIFTS, NOT AN ANIMATION. It used to be a CSS
|
|
12
|
+
* `translateX` keyframe, which is cheap but cannot be grabbed: a finger on it
|
|
13
|
+
* did nothing, and a marquee that ignores the hand on it reads as a broken
|
|
14
|
+
* scroller. Now the rail is a real horizontal scroll container with the
|
|
15
|
+
* scrollbar hidden, and a `requestAnimationFrame` loop advances `scrollLeft`
|
|
16
|
+
* at `width / durationSeconds` per second. When the scroll position passes
|
|
17
|
+
* the first copy it is wrapped back by exactly one copy's width — the two
|
|
18
|
+
* copies are identical, so the jump is invisible — and the same wrap holds
|
|
19
|
+
* while the reader scrolls, in either direction, so there is no end to hit.
|
|
20
|
+
*
|
|
21
|
+
* THE READER WINS. A touch scrolls natively (momentum and all); a mouse drag
|
|
22
|
+
* scrolls the rail by the drag distance; a wheel works because it is a
|
|
23
|
+
* scroller. Any of those stops the drift, and it resumes a few seconds after
|
|
24
|
+
* the last interaction. Hover pauses it too (`pauseOnHover`), and an
|
|
25
|
+
* `IntersectionObserver` stops the loop off-screen so the page is not doing
|
|
26
|
+
* work nobody can see. `prefers-reduced-motion` removes the drift entirely;
|
|
27
|
+
* the rail stays scrollable by hand, which is more than the static strip it
|
|
28
|
+
* used to collapse to.
|
|
16
29
|
*/
|
|
17
30
|
type DriftRailProps = {
|
|
18
31
|
children: React.ReactNode;
|
|
19
32
|
/** Runs the loop right-to-left instead of left-to-right. */
|
|
20
33
|
reverse?: boolean;
|
|
34
|
+
/** Seconds for one full pass of the children. */
|
|
21
35
|
durationSeconds?: number;
|
|
22
36
|
/** Pauses the loop while the pointer hovers the rail. */
|
|
23
37
|
pauseOnHover?: boolean;
|
|
@@ -61,6 +61,14 @@ type EventCardProps = {
|
|
|
61
61
|
liveStatus?: string;
|
|
62
62
|
/** Hide the day/time line — the timeline supplies its own hour heading. */
|
|
63
63
|
hideWhen?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Already over — dims the poster, mutes the title and shows the `endedLabel`
|
|
66
|
+
* chip. Never row-level opacity, which composites the text below AA. Same
|
|
67
|
+
* contract as `EventCompactRow`.
|
|
68
|
+
*/
|
|
69
|
+
isEnded?: boolean;
|
|
70
|
+
/** Chip text for an ended event, e.g. "Ended". Required to render the chip. */
|
|
71
|
+
endedLabel?: string;
|
|
64
72
|
/** Overrides the day label when the card appears outside its own day section. */
|
|
65
73
|
dayLabel?: string;
|
|
66
74
|
/** Append the entry fee to the meta line. Off by default, matching the design's base card. */
|