@bearmenu/ui 0.8.13 → 0.8.14
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-66MAPLXS.js → chunk-2ADCZXZJ.js} +1 -1
- package/dist/{chunk-2DEIXTBN.js → chunk-2ZXIXQIU.js} +1 -1
- package/dist/{chunk-YOKHBAIH.js → chunk-4OF4T4AI.js} +12 -11
- package/dist/{chunk-MZL7CGCL.js → chunk-4XDJXMLF.js} +2 -2
- package/dist/{chunk-YYGB4G4Q.js → chunk-CZK2MRS2.js} +1 -1
- package/dist/chunk-DJD3EMPK.js +126 -0
- package/dist/chunk-EZG2NELX.js +5 -0
- package/dist/{chunk-VAXOQS7W.js → chunk-F76NCVVR.js} +1 -1
- package/dist/{chunk-VG3PND3D.js → chunk-FMTJGO76.js} +1 -1
- package/dist/{chunk-UXWAOISG.js → chunk-FVSKGCK3.js} +2 -2
- package/dist/{chunk-2XCZJWTV.js → chunk-GNCWJWFE.js} +1 -1
- package/dist/{chunk-X3WMRKOW.js → chunk-HDR7V6IZ.js} +1 -1
- package/dist/{chunk-HSIYYA2E.js → chunk-HQU6QVAN.js} +5 -5
- package/dist/chunk-I5V446BX.js +27 -0
- package/dist/{chunk-WCTHZKU7.js → chunk-ICOU5QHT.js} +1 -1
- package/dist/{chunk-G7IFL4Z4.js → chunk-L3VEMP5A.js} +57 -9
- package/dist/chunk-LQWGZIYA.js +5 -0
- package/dist/{chunk-XNA2INVQ.js → chunk-MCOHLWRS.js} +2 -2
- package/dist/{chunk-3GWWZKX7.js → chunk-MMUBH76A.js} +21 -1
- package/dist/{chunk-ZDAHKI3A.js → chunk-MWB7S54O.js} +1 -1
- package/dist/{chunk-D5QHEHXJ.js → chunk-NMEVJT5Q.js} +1 -1
- package/dist/{chunk-3TNPSN4D.js → chunk-NSNEANOQ.js} +2 -2
- package/dist/{chunk-WSRWFA6K.js → chunk-NWZGSLPU.js} +1 -1
- package/dist/{chunk-LEAYBICI.js → chunk-OIV7B44M.js} +1 -1
- package/dist/{chunk-2CXU5WZO.js → chunk-P43B3ONY.js} +1 -1
- package/dist/{chunk-FRPPIXMY.js → chunk-PDTPO6NG.js} +1 -1
- package/dist/{chunk-6XMYEZVR.js → chunk-PX6RICS5.js} +1 -1
- package/dist/{chunk-6TEEY5YA.js → chunk-S33K4RC5.js} +1 -1
- package/dist/{chunk-3VMDCCCX.js → chunk-SC2VUTUI.js} +1 -1
- package/dist/{chunk-3NMG3KNA.js → chunk-SXQZ7DLH.js} +3 -3
- package/dist/{chunk-UDTG3RSE.js → chunk-T6S2ZBV6.js} +2 -2
- package/dist/{chunk-FKNBZF5T.js → chunk-WYTJR2YU.js} +1 -1
- package/dist/{chunk-QXHMPJ35.js → chunk-XBBEX4SF.js} +1 -1
- package/dist/chunk-XCB3F6SF.js +16 -0
- package/dist/{chunk-7BWTYWM3.js → chunk-ZLMRVBD7.js} +1 -1
- package/dist/{chunk-P7XTW2CG.js → chunk-ZLPWEHZO.js} +1 -1
- package/dist/components/accordion.js +1 -1
- package/dist/components/alert-dialog.js +2 -2
- package/dist/components/alert.js +2 -2
- package/dist/components/aspect-ratio.js +2 -2
- package/dist/components/avatar.js +1 -1
- package/dist/components/badge.js +2 -2
- package/dist/components/breadcrumb.js +1 -1
- package/dist/components/button-group.js +1 -1
- package/dist/components/button.js +2 -2
- package/dist/components/calendar.js +2 -2
- package/dist/components/card.js +3 -3
- package/dist/components/carousel.js +2 -2
- package/dist/components/category-deck.js +2 -2
- package/dist/components/checkbox.js +2 -2
- package/dist/components/circular-progress.js +1 -1
- package/dist/components/collapsible.js +1 -1
- package/dist/components/combobox.js +5 -5
- package/dist/components/command.js +3 -3
- package/dist/components/concept-card.js +2 -2
- package/dist/components/concept-compare-rail.js +3 -3
- package/dist/components/concept-compare.d.ts +2 -1
- package/dist/components/concept-compare.js +5 -5
- package/dist/components/concept-convergence.js +4 -4
- package/dist/components/convergence.js +3 -3
- package/dist/components/coverflow.js +1 -1
- package/dist/components/currency-input.js +2 -2
- package/dist/components/date-input.js +2 -2
- package/dist/components/day-rail-chip.js +2 -2
- package/dist/components/description-list.js +1 -1
- package/dist/components/dialog.js +2 -2
- package/dist/components/doorway-tiles.d.ts +57 -0
- package/dist/components/doorway-tiles.js +86 -0
- package/dist/components/drawer.js +1 -1
- package/dist/components/drift-rail.js +1 -1
- package/dist/components/dropdown-menu.js +1 -1
- package/dist/components/empty-state.js +3 -3
- package/dist/components/event-card.js +4 -4
- package/dist/components/event-compact-row.js +4 -4
- package/dist/components/event-day-header.js +2 -2
- package/dist/components/event-day-separator.js +3 -3
- package/dist/components/event-detail-panel.js +4 -4
- package/dist/components/event-hub-hero.js +2 -2
- package/dist/components/event-index-row.js +4 -4
- package/dist/components/event-list-row-wide.js +5 -5
- package/dist/components/event-list-row.js +5 -5
- package/dist/components/event-poster-fallback.js +1 -1
- package/dist/components/event-types.js +1 -1
- package/dist/components/events-empty-state.js +1 -1
- package/dist/components/explainer-steps.js +1 -1
- package/dist/components/facet-group.js +2 -2
- package/dist/components/featured-rail-item.js +2 -2
- package/dist/components/field.js +2 -2
- package/dist/components/filter-category-row.js +3 -3
- package/dist/components/form.js +2 -2
- package/dist/components/glass-ground.js +2 -2
- package/dist/components/grade-atom.d.ts +3 -0
- package/dist/components/grade-atom.js +4 -0
- package/dist/components/haptic-button.js +2 -2
- package/dist/components/hour-band.js +4 -4
- package/dist/components/hour-group.js +4 -4
- package/dist/components/hub-surface.js +1 -1
- package/dist/components/hue-chip.js +1 -1
- package/dist/components/icon.js +1 -1
- package/dist/components/input-group.js +1 -1
- package/dist/components/input-otp.js +1 -1
- package/dist/components/input.js +2 -2
- package/dist/components/item.js +1 -1
- package/dist/components/kbd.js +1 -1
- package/dist/components/kitchen-dossier.d.ts +2 -1
- package/dist/components/kitchen-dossier.js +3 -3
- package/dist/components/label.js +2 -2
- package/dist/components/lazy-markdown.js +1 -1
- package/dist/components/link-button.js +2 -2
- package/dist/components/markdown-renderer.js +1 -1
- package/dist/components/menu-dead-end.d.ts +2 -1
- package/dist/components/menu-dead-end.js +1 -1
- package/dist/components/menu-family-block.d.ts +2 -1
- package/dist/components/menu-family-block.js +14 -8
- package/dist/components/menu-family-card.d.ts +2 -1
- package/dist/components/menu-family-card.js +1 -1
- package/dist/components/menu-field.js +1 -1
- package/dist/components/menu-filter-chips.d.ts +2 -1
- package/dist/components/menu-filter-chips.js +3 -3
- package/dist/components/menu-group-rows.d.ts +2 -1
- package/dist/components/menu-group-rows.js +2 -2
- package/dist/components/menu-hub-hero.js +1 -1
- package/dist/components/menu-index-header.d.ts +2 -1
- package/dist/components/menu-index-header.js +2 -2
- package/dist/components/menu-index-list.d.ts +2 -1
- package/dist/components/menu-index-list.js +4 -4
- package/dist/components/menu-intent-hero.d.ts +3 -2
- package/dist/components/menu-intent-hero.js +1 -1
- package/dist/components/menu-item.d.ts +2 -1
- package/dist/components/menu-item.js +4 -4
- package/dist/components/menu-kitchen-list.d.ts +2 -1
- package/dist/components/menu-kitchen-list.js +4 -4
- package/dist/components/menu-mocks.d.ts +16 -5
- package/dist/components/menu-mocks.js +23 -2
- package/dist/components/menu-types.d.ts +21 -34
- package/dist/components/motion-icon-actions.d.ts +27 -0
- package/dist/components/motion-icon-actions.js +336 -0
- package/dist/components/motion-icon-controls.js +3 -3
- package/dist/components/motion-icon-set.d.ts +13 -1
- package/dist/components/motion-icon-set.js +197 -6
- package/dist/components/motion-icon.d.ts +15 -1
- package/dist/components/motion-icon.js +2 -2
- package/dist/components/multi-select-combobox.js +7 -7
- package/dist/components/multi-select-menu.js +2 -2
- package/dist/components/native-select.js +1 -1
- package/dist/components/open-now-list.d.ts +2 -1
- package/dist/components/open-now-list.js +2 -2
- package/dist/components/pagination.js +2 -2
- package/dist/components/phone-frame.js +1 -1
- package/dist/components/photo-ground.js +1 -1
- package/dist/components/place-about-tab.d.ts +2 -1
- package/dist/components/place-about-tab.js +6 -6
- package/dist/components/place-card.d.ts +94 -12
- package/dist/components/place-card.js +203 -128
- package/dist/components/place-details-mobile.d.ts +2 -1
- package/dist/components/place-details-mobile.js +7 -7
- package/dist/components/place-schedule-week.d.ts +2 -1
- package/dist/components/place-schedule-week.js +3 -3
- package/dist/components/place-types.d.ts +3 -265
- package/dist/components/popover.js +2 -2
- package/dist/components/poster-stack.js +1 -1
- package/dist/components/poster-tape.js +1 -1
- package/dist/components/poster-wall.d.ts +22 -6
- package/dist/components/poster-wall.js +197 -12
- package/dist/components/progress.js +1 -1
- package/dist/components/qa-block.d.ts +2 -1
- package/dist/components/qa-block.js +1 -1
- package/dist/components/quiet-stretch.js +1 -1
- package/dist/components/radio-group.js +1 -1
- package/dist/components/rating.js +1 -1
- package/dist/components/scroll-area.js +1 -1
- package/dist/components/scroll-fade.js +2 -2
- package/dist/components/search-bar.js +1 -1
- package/dist/components/searchable-select.js +5 -5
- package/dist/components/section-header.js +2 -2
- package/dist/components/segmented-nav.d.ts +24 -4
- package/dist/components/segmented-nav.js +11 -2
- package/dist/components/select.js +1 -1
- package/dist/components/selection-bar.js +1 -1
- package/dist/components/separator.js +2 -2
- package/dist/components/sheet.js +1 -1
- package/dist/components/signature-chip.js +3 -3
- package/dist/components/skeleton-card.js +3 -3
- package/dist/components/skeleton.js +2 -2
- package/dist/components/slider.js +1 -1
- package/dist/components/sliding-panels.js +2 -2
- package/dist/components/snap-rail.js +2 -2
- package/dist/components/sonner.js +1 -1
- package/dist/components/sort-option-list.js +1 -1
- package/dist/components/spinner.js +1 -1
- package/dist/components/stat-banner.js +1 -1
- package/dist/components/stepper.js +1 -1
- package/dist/components/sticky-container.js +1 -1
- package/dist/components/story.js +1 -1
- package/dist/components/switch.js +1 -1
- package/dist/components/table.js +1 -1
- package/dist/components/tabs.js +2 -2
- package/dist/components/tag-group.js +2 -2
- package/dist/components/tag.js +2 -2
- package/dist/components/text.js +2 -2
- package/dist/components/textarea.js +1 -1
- package/dist/components/timeline-band.js +4 -4
- package/dist/components/timeline-playhead.js +4 -4
- package/dist/components/toggle-group.js +2 -2
- package/dist/components/toggle.js +2 -2
- package/dist/components/tooltip.js +1 -1
- package/dist/components/top-comparison.d.ts +2 -1
- package/dist/components/top-comparison.js +1 -1
- package/dist/components/top-cross-link.d.ts +2 -1
- package/dist/components/top-cross-link.js +1 -1
- package/dist/components/top-ranking-entry.d.ts +2 -1
- package/dist/components/top-ranking-entry.js +6 -6
- package/dist/components/top-ranking-hero.d.ts +2 -1
- package/dist/components/top-ranking-hero.js +1 -1
- package/dist/components/top-ranking-legend.js +1 -1
- package/dist/components/top-types.d.ts +3 -1
- package/dist/components/use-mobile.js +1 -1
- package/dist/components/venue-events-block.js +5 -5
- package/dist/components/venue-grade-dimensions.d.ts +2 -1
- package/dist/components/venue-grade-dimensions.js +1 -1
- package/dist/components/venue-quotes.d.ts +2 -1
- package/dist/components/venue-quotes.js +1 -1
- package/dist/components/venue-wall.d.ts +2 -1
- package/dist/components/venue-wall.js +1 -1
- package/dist/components/weekday-theme-banner.js +1 -1
- package/dist/components/weekend-panel.js +4 -4
- package/dist/hooks/use-drag-swipe.js +1 -1
- package/dist/hooks/use-haptic.js +1 -1
- package/dist/hooks/use-media-query.js +1 -1
- package/dist/hooks/use-mobile.js +1 -1
- package/dist/hooks/use-mounted-theme.js +1 -1
- package/dist/hooks/use-sticky.js +1 -1
- package/dist/lib/card-hover.js +2 -18
- package/dist/lib/grade.d.ts +54 -0
- package/dist/lib/grade.js +2 -0
- package/dist/lib/icon-depth.d.ts +35 -0
- package/dist/lib/icon-depth.js +2 -0
- package/dist/lib/icon-registry.js +1 -1
- package/dist/lib/motion.js +2 -2
- package/dist/lib/overlay-pill.d.ts +30 -0
- package/dist/lib/overlay-pill.js +2 -0
- package/dist/lib/utils.js +1 -1
- package/dist/place-types-BHdeMMiG.d.ts +317 -0
- package/package.json +1 -1
- package/src/components/colour-dictionary.stories.tsx +268 -82
- package/src/components/doorway-tiles.stories.tsx +83 -0
- package/src/components/doorway-tiles.test.tsx +51 -0
- package/src/components/doorway-tiles.tsx +225 -0
- package/src/components/grade-atom.stories.tsx +81 -0
- package/src/components/grade-atom.tsx +256 -0
- package/src/components/hue-chip.stories.tsx +2 -2
- package/src/components/menu-family-block.stories.tsx +29 -0
- package/src/components/menu-family-block.test.tsx +51 -0
- package/src/components/menu-family-block.tsx +20 -4
- package/src/components/menu-intent-hero.tsx +1 -1
- package/src/components/menu-mocks.ts +39 -3
- package/src/components/menu-types.ts +17 -33
- package/src/components/motion-icon-actions.test.tsx +115 -0
- package/src/components/motion-icon-actions.tsx +580 -0
- package/src/components/motion-icon-controls.test.tsx +1 -0
- package/src/components/motion-icon-controls.tsx +18 -13
- package/src/components/motion-icon-set.stories.tsx +62 -3
- package/src/components/motion-icon-set.tsx +349 -2
- package/src/components/motion-icon.test.tsx +180 -1
- package/src/components/motion-icon.tsx +97 -8
- package/src/components/place-card.stories.tsx +66 -76
- package/src/components/place-card.test.tsx +188 -100
- package/src/components/place-card.tsx +356 -148
- package/src/components/place-types.ts +17 -4
- package/src/components/poster-wall.stories.tsx +84 -0
- package/src/components/poster-wall.test.tsx +94 -3
- package/src/components/poster-wall.tsx +257 -9
- package/src/components/segmented-nav.stories.tsx +72 -1
- package/src/components/segmented-nav.test.tsx +96 -0
- package/src/components/segmented-nav.tsx +36 -4
- package/src/globals.css +26 -0
- package/src/lib/grade.test.ts +82 -0
- package/src/lib/grade.ts +76 -0
- package/src/lib/icon-depth.ts +35 -0
- package/src/lib/overlay-pill.ts +29 -0
- package/dist/components/menu-family-tiles.d.ts +0 -19
- package/dist/components/menu-family-tiles.js +0 -57
- package/src/components/menu-family-tiles.stories.tsx +0 -54
- package/src/components/menu-family-tiles.test.tsx +0 -30
- package/src/components/menu-family-tiles.tsx +0 -132
|
@@ -32,7 +32,13 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import * as React from "react";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
motion,
|
|
37
|
+
useAnimationControls,
|
|
38
|
+
useReducedMotion,
|
|
39
|
+
type Transition,
|
|
40
|
+
type Variants,
|
|
41
|
+
} from "framer-motion";
|
|
36
42
|
|
|
37
43
|
import { cn } from "../lib/utils";
|
|
38
44
|
|
|
@@ -107,50 +113,133 @@ const rootVariants: Variants = {
|
|
|
107
113
|
play: { strokeWidth: 2.25, transition: { duration: 0.12 } },
|
|
108
114
|
};
|
|
109
115
|
|
|
116
|
+
/** The longest gesture in the set, with a little room: a play started this recently is still on screen. */
|
|
117
|
+
const GESTURE_MS = 700;
|
|
118
|
+
/**
|
|
119
|
+
* How long a performed-but-not-yet-active glyph keeps its end pose before
|
|
120
|
+
* snapping back to rest — the route the tap triggered usually confirms within
|
|
121
|
+
* this, and a pour that cuts to empty at 620ms only to refill at 900ms is
|
|
122
|
+
* worse than one that waits.
|
|
123
|
+
*/
|
|
124
|
+
const HOLD_GRACE_MS = 900;
|
|
125
|
+
|
|
110
126
|
/**
|
|
111
127
|
* Mount poses without playing; pointer down plays; arriving plays unless the
|
|
112
128
|
* pointer already started it; leaving returns.
|
|
113
129
|
*
|
|
114
130
|
* Both effects compare against a ref rather than trusting the dependency array,
|
|
115
131
|
* so they fire once per actual change and not once per render.
|
|
132
|
+
*
|
|
133
|
+
* A gesture's last keyframe draws the same PICTURE as the held pose, but not
|
|
134
|
+
* always with the same VALUES: the grid's tiles end one slot around the
|
|
135
|
+
* board, the calendar's reel ends four rows up. Left there, the next `rest`
|
|
136
|
+
* tween would perform a second, backwards gesture on leave. So once `play`
|
|
137
|
+
* resolves the values snap — invisibly — to the pose they depict: `active`
|
|
138
|
+
* straight away if the state has arrived, else `rest` after a grace period
|
|
139
|
+
* that a late-arriving `active` cancels. Leaving while a play is in flight
|
|
140
|
+
* defers to that same snap rather than springing the parts back mid-gesture.
|
|
116
141
|
*/
|
|
117
142
|
export function useIconGesture(active: boolean, replayToken: number) {
|
|
118
143
|
const controls = useAnimationControls();
|
|
144
|
+
const reducedMotion = useReducedMotion() === true;
|
|
119
145
|
const lastPlayAt = React.useRef(0);
|
|
120
146
|
const seenToken = React.useRef(0);
|
|
121
147
|
// `null` until mounted, so the pose effect can tell a real change from the
|
|
122
148
|
// first run and never performs the gesture on page load.
|
|
123
149
|
const seenActive = React.useRef<boolean | null>(null);
|
|
150
|
+
const playCount = React.useRef(0);
|
|
151
|
+
const mounted = React.useRef(false);
|
|
152
|
+
const restTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
153
|
+
|
|
154
|
+
const clearRestTimer = React.useCallback(() => {
|
|
155
|
+
if (restTimer.current !== null) clearTimeout(restTimer.current);
|
|
156
|
+
restTimer.current = null;
|
|
157
|
+
}, []);
|
|
124
158
|
|
|
125
159
|
React.useEffect(() => {
|
|
160
|
+
mounted.current = true;
|
|
126
161
|
controls.set(active ? "active" : "rest");
|
|
127
162
|
seenActive.current = active;
|
|
163
|
+
return () => {
|
|
164
|
+
mounted.current = false;
|
|
165
|
+
clearRestTimer();
|
|
166
|
+
};
|
|
128
167
|
// Mount only — the two effects below own everything after that.
|
|
129
168
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
130
169
|
}, []);
|
|
131
170
|
|
|
171
|
+
// Guarded on a counter, so a replay that started mid-gesture is not cut
|
|
172
|
+
// short by the first one finishing, and on `mounted`, so a sheet that closes
|
|
173
|
+
// under its own close button never reaches `controls.set` after unmount.
|
|
174
|
+
//
|
|
175
|
+
// Reduced motion means less movement, not less feedback: the gesture is
|
|
176
|
+
// skipped and the icon goes straight to the pose it would have landed on,
|
|
177
|
+
// so a hold glyph still changes state and a return glyph stays put.
|
|
178
|
+
const perform = React.useCallback(async () => {
|
|
179
|
+
const id = ++playCount.current;
|
|
180
|
+
lastPlayAt.current = Date.now();
|
|
181
|
+
clearRestTimer();
|
|
182
|
+
if (reducedMotion) {
|
|
183
|
+
if (seenActive.current) controls.set("active");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
await controls.start("play");
|
|
187
|
+
if (!mounted.current || playCount.current !== id) return;
|
|
188
|
+
if (seenActive.current) {
|
|
189
|
+
controls.set("active");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
restTimer.current = setTimeout(() => {
|
|
193
|
+
restTimer.current = null;
|
|
194
|
+
if (mounted.current && !seenActive.current) controls.set("rest");
|
|
195
|
+
}, HOLD_GRACE_MS);
|
|
196
|
+
}, [controls, clearRestTimer, reducedMotion]);
|
|
197
|
+
|
|
132
198
|
React.useEffect(() => {
|
|
133
199
|
if (replayToken === seenToken.current) return;
|
|
134
200
|
seenToken.current = replayToken;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}, [replayToken, controls]);
|
|
201
|
+
void perform();
|
|
202
|
+
}, [replayToken, perform]);
|
|
138
203
|
|
|
139
204
|
React.useEffect(() => {
|
|
140
205
|
if (seenActive.current === null || seenActive.current === active) return;
|
|
141
206
|
seenActive.current = active;
|
|
207
|
+
if (reducedMotion) {
|
|
208
|
+
// No performance to wait for or to double: the pose is the feedback.
|
|
209
|
+
clearRestTimer();
|
|
210
|
+
controls.set(active ? "active" : "rest");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const sincePlay = Date.now() - lastPlayAt.current;
|
|
142
214
|
if (!active) {
|
|
215
|
+
// Mid-gesture: let it land; the settle above snaps to `rest` for us.
|
|
216
|
+
if (sincePlay < GESTURE_MS) return;
|
|
217
|
+
clearRestTimer();
|
|
143
218
|
void controls.start("rest");
|
|
144
219
|
return;
|
|
145
220
|
}
|
|
146
|
-
// The pointer that caused this state change already
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
221
|
+
// The pointer that caused this state change already performed the
|
|
222
|
+
// gesture — in flight, or resolved and waiting in its end pose. Take the
|
|
223
|
+
// held pose rather than performing it twice.
|
|
224
|
+
if (sincePlay < GESTURE_MS + HOLD_GRACE_MS) {
|
|
225
|
+
clearRestTimer();
|
|
226
|
+
if (sincePlay >= GESTURE_MS) controls.set("active");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
void perform();
|
|
230
|
+
}, [active, controls, perform, clearRestTimer, reducedMotion]);
|
|
150
231
|
|
|
151
232
|
return controls;
|
|
152
233
|
}
|
|
153
234
|
|
|
235
|
+
/**
|
|
236
|
+
* An id safe inside `url(#…)`: React 18's `useId` contains colons, React 19's
|
|
237
|
+
* may contain guillemets, and neither survives a fragment reference.
|
|
238
|
+
*/
|
|
239
|
+
export function useSvgId(prefix: string): string {
|
|
240
|
+
return `${prefix}-${React.useId().replace(/[^\w-]/g, "")}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
154
243
|
export function MotionIcon({
|
|
155
244
|
active = false,
|
|
156
245
|
replayToken = 0,
|
|
@@ -1,28 +1,40 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
-
import {
|
|
2
|
+
import { Flame } from "lucide-react";
|
|
3
|
+
import { PlaceCard, PlaceCardSkeleton } from "./place-card";
|
|
4
|
+
import { Badge } from "./badge";
|
|
5
|
+
import { Skeleton as SkeletonBlock } from "./skeleton";
|
|
6
|
+
import { OVERLAY_ICON, OVERLAY_PILL } from "../lib/overlay-pill";
|
|
3
7
|
import type { PlaceCardLabels, PlacePreview } from "./place-types";
|
|
4
8
|
|
|
5
9
|
const labels: PlaceCardLabels = {
|
|
6
|
-
open: "Open",
|
|
7
10
|
closed: "Closed",
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
viewDetails: (name) => `View details for ${name}`,
|
|
12
|
+
priceLevel: (level) => `Price level ${level} of 4`,
|
|
13
|
+
grade: {
|
|
14
|
+
soft: "Worth exploring",
|
|
15
|
+
bands: {
|
|
16
|
+
exceptional: "Exceptional",
|
|
17
|
+
great: "Great",
|
|
18
|
+
good: "Good",
|
|
19
|
+
decent: "Decent",
|
|
20
|
+
weak: "Weak",
|
|
21
|
+
},
|
|
22
|
+
aria: (value) => `Grade ${value} out of 10`,
|
|
23
|
+
capped: "Capped for low confidence",
|
|
24
|
+
},
|
|
11
25
|
};
|
|
12
26
|
|
|
13
27
|
const basePlace: PlacePreview = {
|
|
14
28
|
id: "1",
|
|
15
29
|
slug: "casa-gri",
|
|
16
30
|
name: "Casa Gri",
|
|
17
|
-
description:
|
|
18
|
-
"A modern bistro serving thoughtful European plates with a Mediterranean accent. Open daily until late.",
|
|
19
31
|
open_now: true,
|
|
20
32
|
rating: { rating: 4.6, count: 342 },
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
cuisines: ["european", "mediterranean"],
|
|
34
|
+
areas: [{ name: "Centru" }],
|
|
35
|
+
price_dishes: { price_range_low: 35, price_range_high: 90, price_score: 2 },
|
|
36
|
+
reviews_digest: { grade: { overall: 8.4 } },
|
|
37
|
+
cover_url: "https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&q=80",
|
|
26
38
|
};
|
|
27
39
|
|
|
28
40
|
const meta = {
|
|
@@ -30,91 +42,69 @@ const meta = {
|
|
|
30
42
|
component: PlaceCard,
|
|
31
43
|
tags: ["autodocs"],
|
|
32
44
|
parameters: { layout: "centered" },
|
|
33
|
-
args: {
|
|
34
|
-
labels,
|
|
35
|
-
place: basePlace,
|
|
36
|
-
onClick: undefined,
|
|
37
|
-
},
|
|
38
|
-
} satisfies Meta<typeof PlaceCard>;
|
|
39
|
-
|
|
40
|
-
export default meta;
|
|
41
|
-
type Story = StoryObj<typeof meta>;
|
|
42
|
-
|
|
43
|
-
export const Default: Story = {
|
|
45
|
+
args: { labels, place: basePlace },
|
|
44
46
|
decorators: [
|
|
45
47
|
(Story) => (
|
|
46
|
-
<div className="w-[
|
|
48
|
+
<div className="w-[280px]">
|
|
47
49
|
<Story />
|
|
48
50
|
</div>
|
|
49
51
|
),
|
|
50
52
|
],
|
|
51
|
-
}
|
|
53
|
+
} satisfies Meta<typeof PlaceCard>;
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
type Story = StoryObj<typeof meta>;
|
|
57
|
+
|
|
58
|
+
export const Default: Story = {};
|
|
52
59
|
|
|
53
60
|
export const Closed: Story = {
|
|
54
|
-
args: {
|
|
55
|
-
place: { ...basePlace, open_now: false },
|
|
56
|
-
},
|
|
57
|
-
decorators: [
|
|
58
|
-
(Story) => (
|
|
59
|
-
<div className="w-[360px]">
|
|
60
|
-
<Story />
|
|
61
|
-
</div>
|
|
62
|
-
),
|
|
63
|
-
],
|
|
61
|
+
args: { place: { ...basePlace, open_now: false } },
|
|
64
62
|
};
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
/** Ungraded places fall back to the Google star in the metric slot. */
|
|
65
|
+
export const StarFallback: Story = {
|
|
66
|
+
args: { place: { ...basePlace, reviews_digest: undefined } },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** Once geolocation is granted, distance replaces the neighbourhood. */
|
|
70
|
+
export const WithDistance: Story = {
|
|
71
|
+
args: { place: { ...basePlace, location: { distance: { meters: 1240 } } } },
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const NoCover: Story = {
|
|
75
|
+
args: { place: { ...basePlace, cover_url: undefined } },
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/** A crawlable anchor; a plain click calls `onOpen`, a modified click navigates. */
|
|
79
|
+
export const Linked: Story = {
|
|
67
80
|
args: {
|
|
68
|
-
|
|
81
|
+
href: "/ro/cluj-napoca/places/casa-gri",
|
|
82
|
+
onOpen: (place) => alert(`Open drawer: ${place.name}`),
|
|
69
83
|
},
|
|
70
|
-
decorators: [
|
|
71
|
-
(Story) => (
|
|
72
|
-
<div className="w-[360px]">
|
|
73
|
-
<Story />
|
|
74
|
-
</div>
|
|
75
|
-
),
|
|
76
|
-
],
|
|
77
84
|
};
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
/** The caller's marker in the top-left corner — a sibling of the card's root, so it may be interactive. */
|
|
87
|
+
export const WithPhotoOverlay: Story = {
|
|
80
88
|
args: {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<Story />
|
|
87
|
-
</div>
|
|
89
|
+
photoOverlay: (
|
|
90
|
+
<Badge variant="overlay" className={OVERLAY_PILL}>
|
|
91
|
+
<Flame className={OVERLAY_ICON} aria-hidden />
|
|
92
|
+
Trending
|
|
93
|
+
</Badge>
|
|
88
94
|
),
|
|
89
|
-
|
|
95
|
+
},
|
|
90
96
|
};
|
|
91
97
|
|
|
92
|
-
export const
|
|
98
|
+
export const WithFooter: Story = {
|
|
93
99
|
args: {
|
|
94
|
-
|
|
100
|
+
footer: <span className="text-xs text-muted-foreground">Served as Margherita · 45 RON · 350g</span>,
|
|
95
101
|
},
|
|
96
|
-
decorators: [
|
|
97
|
-
(Story) => (
|
|
98
|
-
<div className="w-[360px]">
|
|
99
|
-
<Story />
|
|
100
|
-
</div>
|
|
101
|
-
),
|
|
102
|
-
],
|
|
103
102
|
};
|
|
104
103
|
|
|
105
104
|
export const Minimal: Story = {
|
|
106
|
-
args: {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
decorators: [
|
|
114
|
-
(Story) => (
|
|
115
|
-
<div className="w-[360px]">
|
|
116
|
-
<Story />
|
|
117
|
-
</div>
|
|
118
|
-
),
|
|
119
|
-
],
|
|
105
|
+
args: { place: { id: "2", slug: "tiny", name: "The Tiny Spot" } },
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const Skeleton: Story = {
|
|
109
|
+
render: () => <PlaceCardSkeleton footer={<SkeletonBlock className="h-3 w-1/2" />} />,
|
|
120
110
|
};
|