@bearmenu/ui 0.8.3 → 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-2WDJR6K7.js +76 -0
- 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/icon.d.ts +18 -0
- package/dist/components/icon.js +31 -0
- package/dist/components/item.d.ts +2 -2
- package/dist/components/motion-icon-controls.d.ts +26 -0
- package/dist/components/motion-icon-controls.js +4 -0
- package/dist/components/motion-icon-set.d.ts +27 -0
- package/dist/components/motion-icon-set.js +139 -0
- package/dist/components/motion-icon.d.ts +44 -0
- package/dist/components/motion-icon.js +3 -0
- 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/dist/lib/icon-registry.d.ts +58 -0
- package/dist/lib/icon-registry.js +39 -0
- package/package.json +2 -1
- package/src/components/accordion.stories.tsx +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/combobox.stories.tsx +1 -1
- 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/currency-input.stories.tsx +3 -3
- 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/empty-state.stories.tsx +3 -3
- 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/form.stories.tsx +1 -1
- 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/icon.tsx +57 -0
- package/src/components/input-otp.stories.tsx +1 -1
- package/src/components/markdown-renderer.stories.tsx +1 -1
- package/src/components/motion-icon-controls.test.tsx +78 -0
- package/src/components/motion-icon-controls.tsx +552 -0
- package/src/components/motion-icon-set.stories.tsx +185 -0
- package/src/components/motion-icon-set.tsx +219 -0
- package/src/components/motion-icon.test.tsx +159 -0
- package/src/components/motion-icon.tsx +163 -0
- package/src/components/multi-select-combobox.stories.tsx +1 -1
- package/src/components/phone-frame.stories.tsx +1 -1
- 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/rating.stories.tsx +3 -3
- package/src/components/searchable-select.stories.tsx +1 -1
- 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/selection-bar.stories.tsx +1 -1
- package/src/components/skeleton-card.stories.tsx +3 -3
- package/src/components/skeleton.tsx +18 -0
- package/src/components/sliding-panels.stories.tsx +1 -1
- package/src/components/stepper.stories.tsx +1 -1
- package/src/components/sticky-container.stories.tsx +3 -3
- 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/toggle-group.stories.tsx +1 -1
- package/src/components/tooltip.stories.tsx +2 -2
- 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
- package/src/lib/icon-registry.ts +139 -0
- package/src/test/setup.ts +5 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { MotionPlayPause } from '../chunk-TT3WFS6S.js';
|
|
2
|
+
import '../chunk-2WDJR6K7.js';
|
|
3
|
+
import { Skeleton } from '../chunk-QXHMPJ35.js';
|
|
4
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
5
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import { ChevronRight } from 'lucide-react';
|
|
8
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
var WEEKEND_PANEL_TRACK_GAP_PX = 14;
|
|
11
|
+
var WEEKEND_PANEL_DEFAULT_DURATION_SECONDS = 7;
|
|
12
|
+
var WEEKEND_PANEL_SLIDE_KEYFRAMES_ID = "ds-weekend-panel-slide";
|
|
13
|
+
var WEEKEND_PANEL_DOT_A_KEYFRAMES_ID = "ds-weekend-panel-dot-a";
|
|
14
|
+
var WEEKEND_PANEL_DOT_B_KEYFRAMES_ID = "ds-weekend-panel-dot-b";
|
|
15
|
+
function usePrefersReducedMotion() {
|
|
16
|
+
const [reduced, setReduced] = React.useState(false);
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (typeof window === "undefined" || !window.matchMedia) return;
|
|
19
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
20
|
+
setReduced(query.matches);
|
|
21
|
+
const onChange = () => setReduced(query.matches);
|
|
22
|
+
query.addEventListener("change", onChange);
|
|
23
|
+
return () => query.removeEventListener("change", onChange);
|
|
24
|
+
}, []);
|
|
25
|
+
return reduced;
|
|
26
|
+
}
|
|
27
|
+
function useInViewport() {
|
|
28
|
+
const ref = React.useRef(null);
|
|
29
|
+
const [inView, setInView] = React.useState(true);
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
const node = ref.current;
|
|
32
|
+
if (!node || typeof IntersectionObserver === "undefined") return;
|
|
33
|
+
const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), {
|
|
34
|
+
threshold: 0
|
|
35
|
+
});
|
|
36
|
+
observer.observe(node);
|
|
37
|
+
return () => observer.disconnect();
|
|
38
|
+
}, []);
|
|
39
|
+
return [ref, inView];
|
|
40
|
+
}
|
|
41
|
+
function DefaultLink(_a) {
|
|
42
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
43
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
44
|
+
}
|
|
45
|
+
function WeekendPanel({
|
|
46
|
+
dayLabel,
|
|
47
|
+
countLabel,
|
|
48
|
+
actionLabel,
|
|
49
|
+
actionHref,
|
|
50
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
51
|
+
onActionClick,
|
|
52
|
+
isHighlighted,
|
|
53
|
+
durationSeconds = WEEKEND_PANEL_DEFAULT_DURATION_SECONDS,
|
|
54
|
+
labels,
|
|
55
|
+
children,
|
|
56
|
+
className
|
|
57
|
+
}) {
|
|
58
|
+
const prefersReducedMotion = usePrefersReducedMotion();
|
|
59
|
+
const [panelRef, inView] = useInViewport();
|
|
60
|
+
const [isHovered, setIsHovered] = React.useState(false);
|
|
61
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
62
|
+
const [isManuallyPaused, setIsManuallyPaused] = React.useState(false);
|
|
63
|
+
const [playPauseReplayToken, setPlayPauseReplayToken] = React.useState(0);
|
|
64
|
+
const isPaused = isManuallyPaused || isHovered || isFocused || !inView;
|
|
65
|
+
const action = /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-0.5 text-[12.5px] text-muted-foreground", children: [
|
|
66
|
+
actionLabel,
|
|
67
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
68
|
+
] });
|
|
69
|
+
return /* @__PURE__ */ jsxs(
|
|
70
|
+
"div",
|
|
71
|
+
{
|
|
72
|
+
ref: panelRef,
|
|
73
|
+
onMouseEnter: () => setIsHovered(true),
|
|
74
|
+
onMouseLeave: () => setIsHovered(false),
|
|
75
|
+
onFocus: () => setIsFocused(true),
|
|
76
|
+
onBlur: () => setIsFocused(false),
|
|
77
|
+
className: cn(
|
|
78
|
+
"relative overflow-hidden rounded-2xl border bg-card py-4",
|
|
79
|
+
isHighlighted ? "border-primary/40" : "border-border",
|
|
80
|
+
className
|
|
81
|
+
),
|
|
82
|
+
children: [
|
|
83
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
84
|
+
@keyframes ${WEEKEND_PANEL_SLIDE_KEYFRAMES_ID} {
|
|
85
|
+
0%, 45% { transform: translateX(0); }
|
|
86
|
+
50%, 95% { transform: translateX(calc(-100% - ${WEEKEND_PANEL_TRACK_GAP_PX}px)); }
|
|
87
|
+
100% { transform: translateX(0); }
|
|
88
|
+
}
|
|
89
|
+
@keyframes ${WEEKEND_PANEL_DOT_A_KEYFRAMES_ID} {
|
|
90
|
+
0% { background: var(--primary); width: 18px; }
|
|
91
|
+
45% { background: var(--primary); width: 18px; }
|
|
92
|
+
50% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
93
|
+
100% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
94
|
+
}
|
|
95
|
+
@keyframes ${WEEKEND_PANEL_DOT_B_KEYFRAMES_ID} {
|
|
96
|
+
0% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
97
|
+
50% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
|
|
98
|
+
95% { background: var(--primary); width: 18px; }
|
|
99
|
+
100% { background: var(--primary); width: 18px; }
|
|
100
|
+
}
|
|
101
|
+
` }),
|
|
102
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-[9px] px-4 pb-3", children: [
|
|
103
|
+
/* @__PURE__ */ jsx(
|
|
104
|
+
"span",
|
|
105
|
+
{
|
|
106
|
+
className: cn(
|
|
107
|
+
"text-[15.5px] font-extrabold tracking-[-0.02em] sm:text-[17px]",
|
|
108
|
+
isHighlighted && "text-primary"
|
|
109
|
+
),
|
|
110
|
+
children: dayLabel
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
/* @__PURE__ */ jsx(
|
|
114
|
+
"span",
|
|
115
|
+
{
|
|
116
|
+
className: cn(
|
|
117
|
+
"font-mono text-xs",
|
|
118
|
+
isHighlighted ? "text-primary" : "text-muted-foreground"
|
|
119
|
+
),
|
|
120
|
+
children: countLabel
|
|
121
|
+
}
|
|
122
|
+
),
|
|
123
|
+
actionHref ? /* @__PURE__ */ jsx(LinkComponent, { href: actionHref, className: "ml-auto", children: action }) : onActionClick ? /* @__PURE__ */ jsx(
|
|
124
|
+
"button",
|
|
125
|
+
{
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: onActionClick,
|
|
128
|
+
className: "ml-auto cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-md",
|
|
129
|
+
children: action
|
|
130
|
+
}
|
|
131
|
+
) : action
|
|
132
|
+
] }),
|
|
133
|
+
prefersReducedMotion ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3.5 px-4", children }) : /* @__PURE__ */ jsx("div", { className: "overflow-hidden px-4", children: /* @__PURE__ */ jsx(
|
|
134
|
+
"div",
|
|
135
|
+
{
|
|
136
|
+
className: "flex gap-3.5",
|
|
137
|
+
style: {
|
|
138
|
+
animationName: WEEKEND_PANEL_SLIDE_KEYFRAMES_ID,
|
|
139
|
+
animationDuration: `${durationSeconds}s`,
|
|
140
|
+
animationTimingFunction: "ease-in-out",
|
|
141
|
+
animationIterationCount: "infinite",
|
|
142
|
+
animationPlayState: isPaused ? "paused" : "running"
|
|
143
|
+
},
|
|
144
|
+
children: React.Children.map(children, (child) => /* @__PURE__ */ jsx("div", { className: "w-[calc((100%-14px)/2)] flex-none", children: child }))
|
|
145
|
+
}
|
|
146
|
+
) }),
|
|
147
|
+
!prefersReducedMotion && /* @__PURE__ */ jsxs("div", { className: "mt-3.5 grid grid-cols-[1fr_auto_1fr] items-center px-4", children: [
|
|
148
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true }),
|
|
149
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1.5", "aria-hidden": true, children: [
|
|
150
|
+
/* @__PURE__ */ jsx(
|
|
151
|
+
"span",
|
|
152
|
+
{
|
|
153
|
+
className: "block h-1.5 w-[18px] rounded-full bg-primary",
|
|
154
|
+
style: {
|
|
155
|
+
animationName: WEEKEND_PANEL_DOT_A_KEYFRAMES_ID,
|
|
156
|
+
animationDuration: `${durationSeconds}s`,
|
|
157
|
+
animationTimingFunction: "ease-in-out",
|
|
158
|
+
animationIterationCount: "infinite",
|
|
159
|
+
animationPlayState: isPaused ? "paused" : "running"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
),
|
|
163
|
+
/* @__PURE__ */ jsx(
|
|
164
|
+
"span",
|
|
165
|
+
{
|
|
166
|
+
className: "block h-1.5 w-1.5 rounded-full bg-muted-foreground/35",
|
|
167
|
+
style: {
|
|
168
|
+
animationName: WEEKEND_PANEL_DOT_B_KEYFRAMES_ID,
|
|
169
|
+
animationDuration: `${durationSeconds}s`,
|
|
170
|
+
animationTimingFunction: "ease-in-out",
|
|
171
|
+
animationIterationCount: "infinite",
|
|
172
|
+
animationPlayState: isPaused ? "paused" : "running"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] }),
|
|
177
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
178
|
+
"button",
|
|
179
|
+
{
|
|
180
|
+
type: "button",
|
|
181
|
+
onClick: () => setIsManuallyPaused((current) => !current),
|
|
182
|
+
onPointerDown: () => setPlayPauseReplayToken((n) => n + 1),
|
|
183
|
+
"aria-label": isManuallyPaused ? labels.play : labels.pause,
|
|
184
|
+
"aria-pressed": isManuallyPaused,
|
|
185
|
+
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",
|
|
186
|
+
children: /* @__PURE__ */ jsx(
|
|
187
|
+
MotionPlayPause,
|
|
188
|
+
{
|
|
189
|
+
active: !isManuallyPaused,
|
|
190
|
+
replayToken: playPauseReplayToken,
|
|
191
|
+
className: "size-4"
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
) })
|
|
196
|
+
] })
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
function WeekendPanelSkeleton({ isHighlighted, className }) {
|
|
202
|
+
return /* @__PURE__ */ jsxs(
|
|
203
|
+
"div",
|
|
204
|
+
{
|
|
205
|
+
"aria-hidden": "true",
|
|
206
|
+
className: cn(
|
|
207
|
+
"relative overflow-hidden rounded-2xl border bg-card py-4",
|
|
208
|
+
isHighlighted ? "border-primary/40" : "border-border",
|
|
209
|
+
className
|
|
210
|
+
),
|
|
211
|
+
children: [
|
|
212
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-[9px] px-4 pb-3", children: [
|
|
213
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[15.5px] w-16 sm:h-[17px]" }),
|
|
214
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-14" }),
|
|
215
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-3 w-14" })
|
|
216
|
+
] }),
|
|
217
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3.5 px-4", children: Array.from({ length: 2 }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
218
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "aspect-[4/3] w-full" }),
|
|
219
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-[13.5px] w-4/5" }),
|
|
220
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3/5" })
|
|
221
|
+
] }, index)) }),
|
|
222
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1.5 pt-3.5", children: [
|
|
223
|
+
/* @__PURE__ */ jsx("span", { className: "block h-1.5 w-[18px] rounded-full bg-muted" }),
|
|
224
|
+
/* @__PURE__ */ jsx("span", { className: "block h-1.5 w-1.5 rounded-full bg-muted" })
|
|
225
|
+
] })
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export { WeekendPanel, WeekendPanelSkeleton };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { LucideIcon } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The purpose → glyph registry.
|
|
5
|
+
*
|
|
6
|
+
* One place recording which glyph means what. Before this existed, ~10 ad-hoc
|
|
7
|
+
* glyph maps were scattered across the app and the same purpose ended up drawn
|
|
8
|
+
* two different ways: back as both an arrow and a chevron, "open filters" as
|
|
9
|
+
* both a funnel and sliders (one surface used both, chosen by which view you
|
|
10
|
+
* were in), clear and close sharing a bare X.
|
|
11
|
+
*
|
|
12
|
+
* Only GENERIC action vocabulary lives here. Product taxonomy — cuisine
|
|
13
|
+
* shortcuts, facet dimensions, digest categories — stays in the consuming app.
|
|
14
|
+
*
|
|
15
|
+
* ## The rules this encodes
|
|
16
|
+
*
|
|
17
|
+
* 1. **Arrow leaves, chevron stays.** `ArrowRight` means "you are going to a new
|
|
18
|
+
* destination" (see-all links, banner CTAs). `ChevronRight` means "reveal
|
|
19
|
+
* more, in place" and is scoped to exactly two jobs: carousel paging and
|
|
20
|
+
* row-contained drill-in. Collapsing these is what produced a glyph with
|
|
21
|
+
* four meanings.
|
|
22
|
+
* 2. **Sliders, not a funnel.** A funnel reads as "narrow a list" and collides
|
|
23
|
+
* with search and sort iconography at 24px; three sliders read
|
|
24
|
+
* unambiguously as "adjust parameters" and carry a badge count cleanly.
|
|
25
|
+
* 3. **Containment separates clear from close.** A bare `X` leaves a layer. A
|
|
26
|
+
* contained `XCircle` resets a value in place without leaving. In a dense
|
|
27
|
+
* filter row, bare Xs beside each other cannot say which layer they close.
|
|
28
|
+
* 4. **Tappable has a container or a colour state; information is muted and
|
|
29
|
+
* bare.** This is what disambiguates the overloaded glyphs (`MapPin`,
|
|
30
|
+
* `Star`) rather than picking different glyphs for each sense. An
|
|
31
|
+
* interactive instance sits in a chip or button and takes an active colour;
|
|
32
|
+
* a decorative one is `text-muted-foreground`, `aria-hidden`, outside any
|
|
33
|
+
* button, with no hover or active transition.
|
|
34
|
+
* 5. **Loved and want-to-try are two actions, not one.** `Heart` is
|
|
35
|
+
* retrospective ("I have had this and I love it"), `Bookmark` is prospective
|
|
36
|
+
* ("I want to try this"). They feed different lists and are mutually
|
|
37
|
+
* exclusive per item.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
type IconPurpose = "back" | "seeAll" | "drillIn" | "carouselPrev" | "carouselNext" | "directions" | "externalLink" | "close" | "clear" | "expand" | "filter" | "sort" | "search" | "share" | "favorite" | "wantToTry" | "confirm" | "add" | "remove" | "refresh" | "call" | "report" | "info";
|
|
41
|
+
declare const iconFor: Record<IconPurpose, LucideIcon>;
|
|
42
|
+
/**
|
|
43
|
+
* Purposes whose glyph must rotate to show state, and by how much.
|
|
44
|
+
* `expand` is the disclosure chevron: pointing down at rest, up when open.
|
|
45
|
+
*/
|
|
46
|
+
declare const iconRotation: Partial<Record<IconPurpose, number>>;
|
|
47
|
+
/**
|
|
48
|
+
* Retired glyphs and what replaced them. Kept so a reviewer can tell a
|
|
49
|
+
* deliberate choice from a regression, and so lint/codemods have a source.
|
|
50
|
+
*/
|
|
51
|
+
declare const retiredIcons: {
|
|
52
|
+
readonly Filter: "filter → SlidersHorizontal";
|
|
53
|
+
readonly ChevronRightAsSeeAll: "seeAll → ArrowRight";
|
|
54
|
+
readonly ChevronLeftAsBack: "back → ArrowLeft";
|
|
55
|
+
readonly XAsClear: "clear → XCircle";
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export { type IconPurpose, iconFor, iconRotation, retiredIcons };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import '../chunk-3GWWZKX7.js';
|
|
2
|
+
import { Info, Flag, Phone, RefreshCw, Trash2, Plus, Check, Bookmark, Heart, Share2, Search, ArrowUpDown, SlidersHorizontal, ChevronDown, XCircle, X, ExternalLink, Navigation, ChevronRight, ChevronLeft, ArrowRight, ArrowLeft } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
var iconFor = {
|
|
5
|
+
back: ArrowLeft,
|
|
6
|
+
seeAll: ArrowRight,
|
|
7
|
+
drillIn: ChevronRight,
|
|
8
|
+
carouselPrev: ChevronLeft,
|
|
9
|
+
carouselNext: ChevronRight,
|
|
10
|
+
directions: Navigation,
|
|
11
|
+
externalLink: ExternalLink,
|
|
12
|
+
close: X,
|
|
13
|
+
clear: XCircle,
|
|
14
|
+
expand: ChevronDown,
|
|
15
|
+
filter: SlidersHorizontal,
|
|
16
|
+
sort: ArrowUpDown,
|
|
17
|
+
search: Search,
|
|
18
|
+
share: Share2,
|
|
19
|
+
favorite: Heart,
|
|
20
|
+
wantToTry: Bookmark,
|
|
21
|
+
confirm: Check,
|
|
22
|
+
add: Plus,
|
|
23
|
+
remove: Trash2,
|
|
24
|
+
refresh: RefreshCw,
|
|
25
|
+
call: Phone,
|
|
26
|
+
report: Flag,
|
|
27
|
+
info: Info
|
|
28
|
+
};
|
|
29
|
+
var iconRotation = {
|
|
30
|
+
expand: 180
|
|
31
|
+
};
|
|
32
|
+
var retiredIcons = {
|
|
33
|
+
Filter: "filter \u2192 SlidersHorizontal",
|
|
34
|
+
ChevronRightAsSeeAll: "seeAll \u2192 ArrowRight",
|
|
35
|
+
ChevronLeftAsBack: "back \u2192 ArrowLeft",
|
|
36
|
+
XAsClear: "clear \u2192 XCircle"
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { iconFor, iconRotation, retiredIcons };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bearmenu/ui",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
4
4
|
"description": "BearMenu design system — shared UI tokens, primitives, and components",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -102,6 +102,7 @@
|
|
|
102
102
|
"@radix-ui/react-tooltip": "^1.2.8",
|
|
103
103
|
"@storybook/addon-a11y": "^10.3.5",
|
|
104
104
|
"@storybook/addon-docs": "^10.3.5",
|
|
105
|
+
"@storybook/react": "^10.3.5",
|
|
105
106
|
"@storybook/react-vite": "^10.3.5",
|
|
106
107
|
"@tailwindcss/postcss": "^4.2.4",
|
|
107
108
|
"@tailwindcss/vite": "^4.2.4",
|
package/src/components/card.tsx
CHANGED
|
@@ -12,6 +12,29 @@ const cardVariants = cva(
|
|
|
12
12
|
glass: "glass",
|
|
13
13
|
outline: "bg-transparent border border-border",
|
|
14
14
|
muted: "bg-muted border border-transparent",
|
|
15
|
+
// `surface` PLUS elevation. The border is NOT replaced, and that is the
|
|
16
|
+
// whole design of these two.
|
|
17
|
+
//
|
|
18
|
+
// In light mode `--card` is pure white on a 0.986 ground — 1.04:1, a
|
|
19
|
+
// five-in-255 step — so a plain surface is defined by its 1.32:1
|
|
20
|
+
// hairline alone. That is proportionate under a pill and invisible under
|
|
21
|
+
// a panel, which is what "this looks flat" means here: there is no z.
|
|
22
|
+
//
|
|
23
|
+
// But dropping the border to go shadow-only breaks both themes. Every
|
|
24
|
+
// elevation token in this system is downward-offset, so a white
|
|
25
|
+
// shadow-only box has no defined TOP edge on a near-white page and
|
|
26
|
+
// reads as half-drawn. And on the 0.155 dark canvas the shadow is
|
|
27
|
+
// nearly invisible, so `--border` is the only thing left describing the
|
|
28
|
+
// box. The border IS the dark-mode fallback, which is why neither
|
|
29
|
+
// variant needs a `dark:` branch.
|
|
30
|
+
elevated: "bg-card border border-border shadow-[var(--shadow-card)]",
|
|
31
|
+
// The same recipe at panel scale — see `--shadow-panel` for why a
|
|
32
|
+
// bigger box does not simply take the card shadow. Resting elevation
|
|
33
|
+
// only: deliberately absent from `compoundVariants` below, because a
|
|
34
|
+
// several-hundred-pixel slab that translates under the cursor is a
|
|
35
|
+
// wobble rather than a lift. A panel that size delegates interaction to
|
|
36
|
+
// its children.
|
|
37
|
+
elevatedPanel: "bg-card border border-border shadow-[var(--shadow-panel)]",
|
|
15
38
|
},
|
|
16
39
|
size: {
|
|
17
40
|
sm: "[&_[data-slot=card-header]]:p-4 [&_[data-slot=card-content]]:p-4 [&_[data-slot=card-content]]:pt-0 [&_[data-slot=card-footer]]:p-4 [&_[data-slot=card-footer]]:pt-0",
|
|
@@ -21,11 +44,31 @@ const cardVariants = cva(
|
|
|
21
44
|
},
|
|
22
45
|
// Interactive variant follows v1.0 `press-response` archetype:
|
|
23
46
|
// hover lift via shadow + translate (200ms), press scale-down (instant).
|
|
47
|
+
//
|
|
48
|
+
// The hover SHADOW is not here — it lives in `compoundVariants` below, so
|
|
49
|
+
// it can be expressed relative to the resting elevation. `box-shadow` is
|
|
50
|
+
// a single property, so a flat `hover:shadow-md` REPLACES a resting
|
|
51
|
+
// shadow rather than stepping up from it: an `elevated` card would
|
|
52
|
+
// translate upward while its shadow got tighter, which is physically
|
|
53
|
+
// backwards and reads as a flicker.
|
|
24
54
|
interactive: {
|
|
25
|
-
true: "cursor-pointer transition-[transform,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-standard)] hover:border-primary/40 hover:-translate-y-0.5
|
|
55
|
+
true: "cursor-pointer transition-[transform,border-color,box-shadow] duration-[var(--duration-quick)] ease-[var(--ease-standard)] hover:border-primary/40 hover:-translate-y-0.5 active:scale-[0.97] active:duration-[var(--duration-instant)] motion-reduce:hover:translate-y-0 motion-reduce:active:scale-100",
|
|
26
56
|
false: "",
|
|
27
57
|
},
|
|
28
58
|
},
|
|
59
|
+
// The first four reproduce the previous `hover:shadow-md` exactly; only
|
|
60
|
+
// `elevated` is new. `elevatedPanel` is absent on purpose — see its note.
|
|
61
|
+
compoundVariants: [
|
|
62
|
+
{ variant: "surface", interactive: true, class: "hover:shadow-md" },
|
|
63
|
+
{ variant: "glass", interactive: true, class: "hover:shadow-md" },
|
|
64
|
+
{ variant: "outline", interactive: true, class: "hover:shadow-md" },
|
|
65
|
+
{ variant: "muted", interactive: true, class: "hover:shadow-md" },
|
|
66
|
+
{
|
|
67
|
+
variant: "elevated",
|
|
68
|
+
interactive: true,
|
|
69
|
+
class: "hover:shadow-[var(--shadow-card-hover)]",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
29
72
|
defaultVariants: {
|
|
30
73
|
variant: "surface",
|
|
31
74
|
size: "default",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { CategoryDeck, CategoryDeckSkeleton } from "./category-deck";
|
|
3
|
+
|
|
4
|
+
function poster(color: string, label: string) {
|
|
5
|
+
return {
|
|
6
|
+
src:
|
|
7
|
+
"data:image/svg+xml;utf8," +
|
|
8
|
+
encodeURIComponent(
|
|
9
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="240" height="320"><rect width="240" height="320" fill="${color}"/><text x="50%" y="50%" font-family="monospace" font-size="14" fill="#fff" text-anchor="middle">${label}</text></svg>`
|
|
10
|
+
),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Explicitly annotated rather than `satisfies`: the meta-level decorator makes
|
|
15
|
+
// the inferred type reference storybook's internal CSF module, which tsc cannot
|
|
16
|
+
// name portably (TS2742).
|
|
17
|
+
const meta: Meta<typeof CategoryDeck> = {
|
|
18
|
+
title: "Events/CategoryDeck",
|
|
19
|
+
component: CategoryDeck,
|
|
20
|
+
tags: ["autodocs"],
|
|
21
|
+
args: {
|
|
22
|
+
name: "Petreceri",
|
|
23
|
+
meta: "32 events · /petreceri",
|
|
24
|
+
href: "/petreceri",
|
|
25
|
+
posters: [
|
|
26
|
+
poster("#7c5a3a", "1"),
|
|
27
|
+
poster("#5a4a3a", "2"),
|
|
28
|
+
poster("#8a6a4a", "3"),
|
|
29
|
+
poster("#3a2a1a", "4"),
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
decorators: [
|
|
33
|
+
(Story) => (
|
|
34
|
+
<div className="w-[220px] p-8">
|
|
35
|
+
<Story />
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
parameters: {
|
|
40
|
+
docs: {
|
|
41
|
+
description: {
|
|
42
|
+
component:
|
|
43
|
+
"The hub's category doorway — one link, four decorative posters fanned back to front. The fan opens on hover AND keyboard focus (`group-focus-visible`), never on hover alone.",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
type Story = StoryObj<typeof CategoryDeck>;
|
|
51
|
+
|
|
52
|
+
export const Default: Story = {};
|
|
53
|
+
|
|
54
|
+
/** Fewer than four posters supplied — the remaining slots render as placeholders. */
|
|
55
|
+
export const PartialPosters: Story = {
|
|
56
|
+
args: {
|
|
57
|
+
posters: [poster("#7c5a3a", "1"), poster("#5a4a3a", "2")],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** No posters at all — every slot is a placeholder block. */
|
|
62
|
+
export const NoPosters: Story = {
|
|
63
|
+
args: { posters: [] },
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* `content` replaces the `<img>` entirely — for callers whose cards are not
|
|
68
|
+
* photographs. The menu hub fans dish categories this way, drawing a tinted
|
|
69
|
+
* colour field per card instead of a photo it is not allowed to show.
|
|
70
|
+
*/
|
|
71
|
+
export const CustomContent: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
name: "Pizza",
|
|
74
|
+
meta: "214 dishes · from 18 lei",
|
|
75
|
+
posters: ["#8d5524", "#a3663a", "#6f4520", "#b07d4e"].map((background, index) => ({
|
|
76
|
+
content: (
|
|
77
|
+
<div
|
|
78
|
+
className="flex h-full w-full items-end p-2 text-[10px] font-bold text-white/80"
|
|
79
|
+
style={{ background }}
|
|
80
|
+
>
|
|
81
|
+
Pizza {index + 1}
|
|
82
|
+
</div>
|
|
83
|
+
),
|
|
84
|
+
})),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** Rendered as a `<button>` when only `onClick` is given. */
|
|
89
|
+
export const AsButton: Story = {
|
|
90
|
+
args: { href: undefined, onClick: () => alert("Category selected") },
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** The fan at rest — never spread, since the loading state has nothing to hover. */
|
|
94
|
+
export const Skeleton: Story = {
|
|
95
|
+
render: () => <CategoryDeckSkeleton />,
|
|
96
|
+
};
|