@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
|
@@ -152,6 +152,8 @@ export function MenuFamilyBlock({
|
|
|
152
152
|
// see — the photograph is one of them, not a venue on its own.
|
|
153
153
|
const venues = family.venues ?? [];
|
|
154
154
|
const overflow = Math.max(family.venueCount - venues.length, 0);
|
|
155
|
+
// The corner pill: the caller's badge when it has one, else the overflow.
|
|
156
|
+
const pill = family.badge ?? (overflow > 0 ? labels.more(overflow) : undefined);
|
|
155
157
|
const initial = (family.name.match(/\p{L}/u)?.[0] ?? "").toLocaleUpperCase();
|
|
156
158
|
|
|
157
159
|
return (
|
|
@@ -182,19 +184,33 @@ export function MenuFamilyBlock({
|
|
|
182
184
|
TEXT_SCRIM
|
|
183
185
|
)}
|
|
184
186
|
>
|
|
185
|
-
{
|
|
187
|
+
{pill && (
|
|
186
188
|
<span className="absolute right-3.5 top-3.5 rounded-full bg-[oklch(0.16_0.012_55_/_0.78)] px-2.5 py-1 text-[11px] font-semibold text-white lg:static lg:mb-3 lg:self-start">
|
|
187
|
-
{
|
|
189
|
+
{pill}
|
|
188
190
|
</span>
|
|
189
191
|
)}
|
|
190
192
|
|
|
193
|
+
{family.kicker && (
|
|
194
|
+
<span className="mb-1 text-[11px] font-semibold uppercase tracking-[0.14em] text-white/70">
|
|
195
|
+
{family.kicker}
|
|
196
|
+
</span>
|
|
197
|
+
)}
|
|
191
198
|
<h3 className="text-balance font-serif text-[26px] font-semibold leading-tight tracking-[-0.02em] text-white lg:text-[30px]">
|
|
192
199
|
{family.name}
|
|
193
200
|
</h3>
|
|
194
|
-
{/* The count and the band on one line:
|
|
195
|
-
read together — "96 kitchens, 14–46 lei"
|
|
201
|
+
{/* The count, the facts and the band on one line: a few facts about
|
|
202
|
+
the family, read together — "96 kitchens, 14–46 lei" or
|
|
203
|
+
"70 kitchens · 50 names · 8–44 lei". */}
|
|
196
204
|
<span className="mt-1 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-[13px] font-medium tabular-nums text-white/70">
|
|
197
205
|
{labels.venueUnit(family.venueCount)}
|
|
206
|
+
{family.facts?.map((fact) => (
|
|
207
|
+
<span key={fact} className="inline-flex items-center gap-2.5">
|
|
208
|
+
<span aria-hidden className="text-white/40">
|
|
209
|
+
·
|
|
210
|
+
</span>
|
|
211
|
+
{fact}
|
|
212
|
+
</span>
|
|
213
|
+
))}
|
|
198
214
|
{family.priceBandLabel && (
|
|
199
215
|
<span
|
|
200
216
|
className="inline-flex items-center gap-1"
|
|
@@ -55,7 +55,7 @@ export type MenuIntentHeroProps = {
|
|
|
55
55
|
* Finished CSS gradient for the ground, e.g. `linear-gradient(140deg, …)`.
|
|
56
56
|
*
|
|
57
57
|
* A gradient rather than a token name, for the same reason `<MenuField>` and
|
|
58
|
-
* `<
|
|
58
|
+
* `<DoorwayTiles>` take one: the hue tables are app-owned, and a component
|
|
59
59
|
* referencing `var(--h-pizza)` renders an unstyled panel in any consumer that
|
|
60
60
|
* does not define it. Missing, the panel falls back to the foreground colour
|
|
61
61
|
* — visibly unstyled rather than white text on white.
|
|
@@ -25,13 +25,13 @@ import type {
|
|
|
25
25
|
MenuFilterChip,
|
|
26
26
|
MenuIndexEntry,
|
|
27
27
|
MenuIntentFigure,
|
|
28
|
-
MenuFamilyTile,
|
|
29
28
|
MenuItemPreview,
|
|
30
29
|
MenuKitchenPreview,
|
|
31
30
|
MenuLoosenOption,
|
|
32
31
|
MenuVenuePreview,
|
|
33
32
|
QaItem,
|
|
34
33
|
} from "./menu-types";
|
|
34
|
+
import type { DoorwayTile } from "./doorway-tiles";
|
|
35
35
|
import type { PlacePreview } from "./place-types";
|
|
36
36
|
|
|
37
37
|
export const mockGradients = {
|
|
@@ -387,7 +387,7 @@ const familyPhoto = (hue: number) =>
|
|
|
387
387
|
* venue's, never a plate's; the last one ships without, so the story shows
|
|
388
388
|
* the initial fallback beside six real tiles.
|
|
389
389
|
*/
|
|
390
|
-
export const mockFamilyTiles:
|
|
390
|
+
export const mockFamilyTiles: DoorwayTile[] = [
|
|
391
391
|
{ slug: "italian", label: "Italian", countLabel: "66", href: "/menu/cuisines/italian", gradient: familyGradient(38), photoUrl: familyPhoto(30) },
|
|
392
392
|
{ slug: "romanian", label: "Romanian", countLabel: "64", href: "/menu/cuisines/romanian", gradient: familyGradient(25), photoUrl: familyPhoto(10) },
|
|
393
393
|
{ slug: "breakfast-brunch", label: "Breakfast & brunch", countLabel: "64", href: "/menu/cuisines/breakfast-brunch", gradient: familyGradient(82), photoUrl: familyPhoto(45) },
|
|
@@ -398,7 +398,7 @@ export const mockFamilyTiles: MenuFamilyTile[] = [
|
|
|
398
398
|
];
|
|
399
399
|
|
|
400
400
|
/** The closing tile: stone, so it reads as the index and not as an eighth cuisine. */
|
|
401
|
-
export const mockFamilyTilesAll:
|
|
401
|
+
export const mockFamilyTilesAll: DoorwayTile = {
|
|
402
402
|
slug: "all",
|
|
403
403
|
label: "All 38 cuisines",
|
|
404
404
|
countLabel: "The full index",
|
|
@@ -407,6 +407,42 @@ export const mockFamilyTilesAll: MenuFamilyTile = {
|
|
|
407
407
|
photoUrl: familyPhoto(60),
|
|
408
408
|
};
|
|
409
409
|
|
|
410
|
+
/** An occasion's ground: a mood hue over a room photograph. */
|
|
411
|
+
const occasionGradient = (l: number, c: number, h: number) =>
|
|
412
|
+
`linear-gradient(150deg, oklch(${l} ${c} ${h}), color-mix(in oklab, oklch(${l} ${c} ${h}) 82%, black))`;
|
|
413
|
+
|
|
414
|
+
/** A room photograph stand-in with the mid-tone structure multiply needs. */
|
|
415
|
+
const occasionPhoto = (hue: number) =>
|
|
416
|
+
"data:image/svg+xml;utf8," +
|
|
417
|
+
encodeURIComponent(
|
|
418
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="640" height="320"><defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="hsl(${hue} 20% 88%)"/><stop offset="1" stop-color="hsl(${hue} 18% 52%)"/></linearGradient></defs><rect width="640" height="320" fill="url(#g)"/><circle cx="420" cy="120" r="70" fill="white" opacity=".6"/><rect x="60" y="200" width="220" height="80" fill="hsl(${hue} 15% 40%)" opacity=".5"/></svg>`
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The homepage's seven occasions in the day's order, ground treatment: the
|
|
423
|
+
* mood's hue multiplied over a room. The last one ships without a photograph
|
|
424
|
+
* so the story shows the initial on the bare hue.
|
|
425
|
+
*/
|
|
426
|
+
export const mockOccasionTiles: DoorwayTile[] = [
|
|
427
|
+
{ slug: "brunch", label: "Brunch", countLabel: "314 places", href: "/menu/cuisines/breakfast-brunch", gradient: occasionGradient(0.62, 0.14, 82), photoUrl: occasionPhoto(40) },
|
|
428
|
+
{ slug: "laptop-and-coffee", label: "Laptop and coffee", countLabel: "52 places", href: "/occasions/laptop-and-coffee", gradient: occasionGradient(0.44, 0.1, 52), photoUrl: occasionPhoto(30) },
|
|
429
|
+
{ slug: "quiet", label: "Somewhere quiet", countLabel: "108 places", href: "/occasions/quiet", gradient: occasionGradient(0.38, 0.09, 255), photoUrl: occasionPhoto(210) },
|
|
430
|
+
{ slug: "something-sweet", label: "Something sweet", countLabel: "120 places", href: "/occasions/something-sweet", gradient: occasionGradient(0.5, 0.08, 335), photoUrl: occasionPhoto(330) },
|
|
431
|
+
{ slug: "with-friends", label: "With friends", countLabel: "158 places", href: "/occasions/with-friends", gradient: occasionGradient(0.6, 0.14, 358), photoUrl: occasionPhoto(10) },
|
|
432
|
+
{ slug: "date-night", label: "Date night", countLabel: "53 places", href: "/occasions/date-night", gradient: occasionGradient(0.44, 0.15, 18), photoUrl: occasionPhoto(0) },
|
|
433
|
+
{ slug: "late-night", label: "Late night", countLabel: "59 places", href: "/occasions/late-night", gradient: occasionGradient(0.34, 0.1, 300) },
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
/** The closing tile: stone, so it reads as the index and not as an eighth mood. */
|
|
437
|
+
export const mockOccasionTilesAll: DoorwayTile = {
|
|
438
|
+
slug: "all",
|
|
439
|
+
label: "All occasions",
|
|
440
|
+
countLabel: "14 sets",
|
|
441
|
+
href: "/occasions",
|
|
442
|
+
gradient: "linear-gradient(150deg, oklch(0.50 0.03 70), oklch(0.40 0.028 64))",
|
|
443
|
+
photoUrl: occasionPhoto(60),
|
|
444
|
+
};
|
|
445
|
+
|
|
410
446
|
/** Formatters the stories pass. Real callers inject locale-aware ones. */
|
|
411
447
|
export const mockFormatters = {
|
|
412
448
|
price: (value: number) => `${value} lei`,
|
|
@@ -216,38 +216,6 @@ export type MenuVenueSignature = {
|
|
|
216
216
|
isSignature?: boolean
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
/**
|
|
220
|
-
* A family tile — a cuisine on the dishes tab, a going-out family on the
|
|
221
|
-
* drinks tab — as the hub's doorway grid draws it: a hue, a name, a venue
|
|
222
|
-
* count and one venue's photograph.
|
|
223
|
-
*
|
|
224
|
-
* `href` is required: a tile that cannot resolve to a real destination is not
|
|
225
|
-
* rendered rather than linking nowhere.
|
|
226
|
-
*/
|
|
227
|
-
export type MenuFamilyTile = {
|
|
228
|
-
slug: string
|
|
229
|
-
label: string
|
|
230
|
-
/**
|
|
231
|
-
* The venue count, already formatted by the caller — "64", "1,204" — because
|
|
232
|
-
* thousands separators are locale-dependent and the DS owns no locale.
|
|
233
|
-
*/
|
|
234
|
-
countLabel?: string
|
|
235
|
-
href: string
|
|
236
|
-
/**
|
|
237
|
-
* Finished CSS gradient for the tile's ground, e.g.
|
|
238
|
-
* `linear-gradient(150deg, …)`.
|
|
239
|
-
*
|
|
240
|
-
* A gradient rather than a token name, for the same reason `<MenuField>`
|
|
241
|
-
* takes one: the hue tables are app-owned, and a component referencing
|
|
242
|
-
* `var(--h-pizza)` renders an invisible tile in any consumer that does not
|
|
243
|
-
* happen to define that variable. A missing gradient falls back to `bg-muted`
|
|
244
|
-
* — visible and obviously unstyled, rather than white text on white.
|
|
245
|
-
*/
|
|
246
|
-
gradient?: string
|
|
247
|
-
/** Pre-resolved VENUE photograph. Venue photography is real; item photography is not. */
|
|
248
|
-
photoUrl?: string
|
|
249
|
-
}
|
|
250
|
-
|
|
251
219
|
/**
|
|
252
220
|
* One figure in the intent hero — a number the reader can tap to filter by.
|
|
253
221
|
*
|
|
@@ -380,7 +348,9 @@ export type MenuIndexEntry = {
|
|
|
380
348
|
}
|
|
381
349
|
|
|
382
350
|
/**
|
|
383
|
-
* A family on the cuisines index — the hub's card, opened out sideways
|
|
351
|
+
* A family on the cuisines index — the hub's card, opened out sideways — and,
|
|
352
|
+
* with `kicker` / `facts` / `badge`, a concept on the dishes index: the same
|
|
353
|
+
* card with the unit swapped, as the design draws it.
|
|
384
354
|
*
|
|
385
355
|
* Deliberately thin. The reader here is choosing a cuisine, not a venue, and
|
|
386
356
|
* every field the block does NOT carry is a decision the next page owns.
|
|
@@ -388,7 +358,21 @@ export type MenuIndexEntry = {
|
|
|
388
358
|
export type MenuFamilyBlockPreview = {
|
|
389
359
|
slug: string
|
|
390
360
|
name: string
|
|
361
|
+
/** Small caps over the name, e.g. "DESSERT CONCEPT". Composed by the caller. */
|
|
362
|
+
kicker?: string
|
|
391
363
|
venueCount: number
|
|
364
|
+
/**
|
|
365
|
+
* Further pre-formatted facts on the count line, after the unit and before
|
|
366
|
+
* the price band, e.g. `["50 names"]`. Each is one fact; the block draws the
|
|
367
|
+
* separators.
|
|
368
|
+
*/
|
|
369
|
+
facts?: string[]
|
|
370
|
+
/**
|
|
371
|
+
* The pill in the photo's corner, e.g. "168 mentions". When given it replaces
|
|
372
|
+
* the "+N more" overflow pill, which the concept card has no room for beside
|
|
373
|
+
* a name-chip overflow that already says it.
|
|
374
|
+
*/
|
|
375
|
+
badge?: string
|
|
392
376
|
/** Facet chips. Labels only — the block never prints their counts. */
|
|
393
377
|
chips?: MenuFamilyChip[]
|
|
394
378
|
/** Composed price band, e.g. "22–86 lei". */
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MotionArrow,
|
|
7
|
+
MotionCaret,
|
|
8
|
+
MotionX,
|
|
9
|
+
MotionCircleX,
|
|
10
|
+
MotionExternalLink,
|
|
11
|
+
MotionSend,
|
|
12
|
+
MotionNavigation,
|
|
13
|
+
MotionLocate,
|
|
14
|
+
MotionMaximize,
|
|
15
|
+
MotionPhone,
|
|
16
|
+
MotionMail,
|
|
17
|
+
MotionFlag,
|
|
18
|
+
MotionLogIn,
|
|
19
|
+
MotionLogOut,
|
|
20
|
+
MotionSettings,
|
|
21
|
+
} from "./motion-icon-actions";
|
|
22
|
+
import {
|
|
23
|
+
MotionLayoutGrid,
|
|
24
|
+
MotionWine,
|
|
25
|
+
MotionMap,
|
|
26
|
+
MotionFlame,
|
|
27
|
+
MotionCalendarClock,
|
|
28
|
+
MotionList,
|
|
29
|
+
} from "./motion-icon-set";
|
|
30
|
+
import type { MotionIconProps } from "./motion-icon";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Real framer-motion, no mock: every glyph has to mount, pose and replay
|
|
34
|
+
* without throwing, in both directions where it has one. The gestures
|
|
35
|
+
* themselves are judged in the Storybook gallery at 16px and 22px — nothing
|
|
36
|
+
* here can see whether a fold reads.
|
|
37
|
+
*/
|
|
38
|
+
const GLYPHS: Array<[string, React.ComponentType<MotionIconProps>]> = [
|
|
39
|
+
["Arrow →", (p) => <MotionArrow {...p} />],
|
|
40
|
+
["Arrow ←", (p) => <MotionArrow direction="left" {...p} />],
|
|
41
|
+
["Caret →", (p) => <MotionCaret {...p} />],
|
|
42
|
+
["Caret ←", (p) => <MotionCaret direction="left" {...p} />],
|
|
43
|
+
["X", MotionX],
|
|
44
|
+
["CircleX", MotionCircleX],
|
|
45
|
+
["ExternalLink", MotionExternalLink],
|
|
46
|
+
["Send", MotionSend],
|
|
47
|
+
["Navigation", MotionNavigation],
|
|
48
|
+
["Locate", MotionLocate],
|
|
49
|
+
["Maximize", MotionMaximize],
|
|
50
|
+
["Phone", MotionPhone],
|
|
51
|
+
["Mail", MotionMail],
|
|
52
|
+
["Flag", MotionFlag],
|
|
53
|
+
["LogIn", MotionLogIn],
|
|
54
|
+
["LogOut", MotionLogOut],
|
|
55
|
+
["Settings", MotionSettings],
|
|
56
|
+
["LayoutGrid", MotionLayoutGrid],
|
|
57
|
+
["Wine", MotionWine],
|
|
58
|
+
["Map", MotionMap],
|
|
59
|
+
["Flame", MotionFlame],
|
|
60
|
+
["CalendarClock", MotionCalendarClock],
|
|
61
|
+
["List", MotionList],
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
describe.each(GLYPHS)("%s", (_name, Icon) => {
|
|
65
|
+
it("mounts at rest, holds, and replays without throwing", () => {
|
|
66
|
+
const { container, rerender } = render(<Icon active={false} replayToken={0} />);
|
|
67
|
+
const svg = container.querySelector("svg");
|
|
68
|
+
expect(svg).not.toBeNull();
|
|
69
|
+
expect(svg).toHaveAttribute("aria-hidden", "true");
|
|
70
|
+
expect(svg).toHaveAttribute("viewBox", "0 0 24 24");
|
|
71
|
+
|
|
72
|
+
rerender(<Icon active replayToken={1} />);
|
|
73
|
+
rerender(<Icon active={false} replayToken={2} />);
|
|
74
|
+
expect(container.querySelector("svg")).not.toBeNull();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("MotionArrow / MotionCaret", () => {
|
|
79
|
+
it("vendor the mirrored lucide paths rather than flipping the right-hand glyph", () => {
|
|
80
|
+
const right = render(<MotionArrow />).container.innerHTML;
|
|
81
|
+
const left = render(<MotionArrow direction="left" />).container.innerHTML;
|
|
82
|
+
expect(right).toContain('d="M5 12h14"');
|
|
83
|
+
expect(left).toContain('d="M19 12H5"');
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("MotionCalendarClock", () => {
|
|
88
|
+
it("draws the elapsed wedge with butt caps so it stays a pie, not a blob", () => {
|
|
89
|
+
const { container } = render(<MotionCalendarClock />);
|
|
90
|
+
const wedge = container.querySelector('circle[r="2"]');
|
|
91
|
+
expect(wedge).toHaveAttribute("stroke-linecap", "butt");
|
|
92
|
+
expect(wedge).toHaveAttribute("stroke-width", "4");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("is empty on first paint — the frame before the pose effect runs", () => {
|
|
96
|
+
// Without these two static attributes an un-dashed r=2 / width-4 circle
|
|
97
|
+
// is a solid disc on the face for one frame on every mount.
|
|
98
|
+
const { container } = render(<MotionCalendarClock />);
|
|
99
|
+
const wedge = container.querySelector('circle[r="2"]');
|
|
100
|
+
expect(wedge).toHaveAttribute("pathLength", "1");
|
|
101
|
+
expect(wedge).toHaveAttribute("stroke-dasharray", "0 1");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("MotionLogIn / MotionLogOut", () => {
|
|
106
|
+
it("clip the reel so the arriving arrow never draws over the label beside the button", () => {
|
|
107
|
+
const { container: inn } = render(<MotionLogIn />);
|
|
108
|
+
const { container: out } = render(<MotionLogOut />);
|
|
109
|
+
expect(inn.querySelector("clipPath rect")).toHaveAttribute("width", "17");
|
|
110
|
+
expect(out.querySelector("clipPath rect")).toHaveAttribute("x", "8");
|
|
111
|
+
// Two arrows each: the departing one and the one that takes its place.
|
|
112
|
+
expect(inn.querySelectorAll('path[d="m10 17 5-5-5-5"]')).toHaveLength(2);
|
|
113
|
+
expect(out.querySelectorAll('path[d="m16 17 5-5-5-5"]')).toHaveLength(2);
|
|
114
|
+
});
|
|
115
|
+
});
|