@bearmenu/ui 0.8.7 → 0.8.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-637L7XQ3.js → chunk-3TNPSN4D.js} +28 -9
- package/dist/chunk-62ES6N32.js +27 -0
- package/dist/chunk-AHL45DD2.js +145 -0
- package/dist/chunk-AXV5O2IZ.js +58 -0
- package/dist/chunk-IYDH5IU6.js +66 -0
- package/dist/chunk-OPVKL4VS.js +18 -0
- package/dist/chunk-Q6QH52LJ.js +38 -0
- package/dist/chunk-TH6W2PWG.js +213 -0
- package/dist/chunk-THOVPZNN.js +37 -0
- package/dist/chunk-YS6BHKZO.js +92 -0
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -1
- package/dist/components/category-deck.js +10 -1
- package/dist/components/command.d.ts +7 -7
- package/dist/components/concept-compare.d.ts +73 -0
- package/dist/components/concept-compare.js +162 -0
- package/dist/components/coverflow.d.ts +11 -3
- package/dist/components/coverflow.js +34 -51
- package/dist/components/date-input.d.ts +1 -1
- package/dist/components/drift-rail.d.ts +24 -10
- package/dist/components/drift-rail.js +1 -1
- package/dist/components/event-card.d.ts +8 -0
- package/dist/components/event-card.js +64 -34
- package/dist/components/event-compact-row.d.ts +33 -6
- package/dist/components/event-compact-row.js +120 -39
- package/dist/components/event-day-header.js +3 -1
- package/dist/components/event-day-separator.d.ts +7 -1
- package/dist/components/event-day-separator.js +3 -2
- package/dist/components/event-detail-panel.d.ts +171 -25
- package/dist/components/event-detail-panel.js +471 -174
- package/dist/components/event-hub-hero.d.ts +25 -4
- package/dist/components/event-hub-hero.js +89 -19
- package/dist/components/event-list-row-wide.js +1 -1
- package/dist/components/event-list-row.d.ts +19 -6
- package/dist/components/event-list-row.js +79 -35
- package/dist/components/hour-band.d.ts +38 -40
- package/dist/components/hour-band.js +55 -27
- package/dist/components/hour-group.js +1 -1
- package/dist/components/hub-surface.d.ts +27 -0
- package/dist/components/hub-surface.js +20 -0
- package/dist/components/menu-family-card.d.ts +59 -0
- package/dist/components/menu-family-card.js +121 -0
- package/dist/components/menu-field.d.ts +57 -0
- package/dist/components/menu-field.js +3 -0
- package/dist/components/menu-hub-hero.d.ts +65 -0
- package/dist/components/menu-hub-hero.js +103 -0
- package/dist/components/menu-intent-tiles.d.ts +31 -0
- package/dist/components/menu-intent-tiles.js +50 -0
- package/dist/components/menu-item.d.ts +127 -0
- package/dist/components/menu-item.js +5 -0
- package/dist/components/menu-mocks.d.ts +55 -0
- package/dist/components/menu-mocks.js +379 -0
- package/dist/components/menu-types.d.ts +245 -0
- package/dist/components/menu-types.js +1 -0
- package/dist/components/motion-icon-set.js +15 -3
- package/dist/components/open-now-list.d.ts +40 -0
- package/dist/components/open-now-list.js +69 -0
- package/dist/components/place-types.d.ts +112 -1
- package/dist/components/poster-tape.js +1 -1
- package/dist/components/poster-wall.d.ts +3 -3
- package/dist/components/poster-wall.js +73 -17
- package/dist/components/ranked-venue-list.d.ts +49 -0
- package/dist/components/ranked-venue-list.js +87 -0
- package/dist/components/section-header.d.ts +16 -1
- package/dist/components/section-header.js +26 -7
- package/dist/components/signature-chip.d.ts +37 -0
- package/dist/components/signature-chip.js +4 -0
- package/dist/components/signature-line-list.d.ts +45 -0
- package/dist/components/signature-line-list.js +99 -0
- package/dist/components/snap-rail.d.ts +54 -0
- package/dist/components/snap-rail.js +109 -0
- package/dist/components/spinner.d.ts +2 -2
- package/dist/components/stat-banner.d.ts +47 -0
- package/dist/components/stat-banner.js +61 -0
- package/dist/components/text.d.ts +3 -3
- package/dist/components/timeline-band.d.ts +20 -2
- package/dist/components/timeline-band.js +1 -1
- package/dist/components/timeline-playhead.d.ts +26 -0
- package/dist/components/timeline-playhead.js +25 -0
- package/dist/components/venue-busyness-strip.d.ts +35 -0
- package/dist/components/venue-busyness-strip.js +37 -0
- package/dist/components/venue-grade-dimensions.d.ts +36 -0
- package/dist/components/venue-grade-dimensions.js +3 -0
- package/dist/components/venue-quotes.d.ts +32 -0
- package/dist/components/venue-quotes.js +3 -0
- package/dist/components/venue-signature-block.d.ts +39 -0
- package/dist/components/venue-signature-block.js +6 -0
- package/dist/components/venue-spotlight.d.ts +74 -0
- package/dist/components/venue-spotlight.js +138 -0
- package/dist/components/venue-wall.d.ts +41 -0
- package/dist/components/venue-wall.js +118 -0
- package/dist/components/weekday-theme-banner.d.ts +70 -0
- package/dist/components/weekday-theme-banner.js +127 -0
- package/dist/components/weekend-panel.d.ts +5 -4
- package/dist/components/weekend-panel.js +85 -71
- package/dist/hooks/use-drag-swipe.d.ts +44 -0
- package/dist/hooks/use-drag-swipe.js +2 -0
- package/dist/hooks/use-media-query.d.ts +17 -0
- package/dist/hooks/use-media-query.js +2 -0
- package/package.json +1 -1
- package/src/components/category-deck.tsx +19 -8
- package/src/components/colour-dictionary.stories.tsx +227 -0
- package/src/components/concept-compare.stories.tsx +104 -0
- package/src/components/concept-compare.tsx +276 -0
- package/src/components/coverflow.test.tsx +28 -1
- package/src/components/coverflow.tsx +37 -43
- package/src/components/drift-rail.tsx +167 -42
- package/src/components/event-card.tsx +55 -10
- package/src/components/event-compact-row.stories.tsx +30 -0
- package/src/components/event-compact-row.test.tsx +49 -0
- package/src/components/event-compact-row.tsx +162 -38
- package/src/components/event-day-header.tsx +3 -1
- package/src/components/event-day-separator.tsx +8 -1
- package/src/components/event-detail-panel.stories.tsx +205 -97
- package/src/components/event-detail-panel.test.tsx +181 -0
- package/src/components/event-detail-panel.tsx +703 -185
- package/src/components/event-hub-hero.stories.tsx +30 -0
- package/src/components/event-hub-hero.tsx +102 -30
- package/src/components/event-list-row.stories.tsx +34 -0
- package/src/components/event-list-row.test.tsx +53 -0
- package/src/components/event-list-row.tsx +135 -87
- package/src/components/hour-band.stories.tsx +28 -5
- package/src/components/hour-band.tsx +90 -70
- package/src/components/hub-surface.stories.tsx +68 -0
- package/src/components/hub-surface.tsx +40 -0
- package/src/components/menu-family-card.stories.tsx +112 -0
- package/src/components/menu-family-card.tsx +211 -0
- package/src/components/menu-field.stories.tsx +74 -0
- package/src/components/menu-field.tsx +199 -0
- package/src/components/menu-hub-hero.stories.tsx +92 -0
- package/src/components/menu-hub-hero.tsx +187 -0
- package/src/components/menu-intent-tiles.stories.tsx +54 -0
- package/src/components/menu-intent-tiles.tsx +76 -0
- package/src/components/menu-item.tsx +416 -0
- package/src/components/menu-mocks.ts +417 -0
- package/src/components/menu-types.ts +259 -0
- package/src/components/motion-icon-set.tsx +30 -3
- package/src/components/open-now-list.stories.tsx +76 -0
- package/src/components/open-now-list.tsx +127 -0
- package/src/components/place-types.ts +112 -0
- package/src/components/poster-wall.test.tsx +28 -0
- package/src/components/poster-wall.tsx +96 -23
- package/src/components/ranked-venue-list.stories.tsx +80 -0
- package/src/components/ranked-venue-list.tsx +177 -0
- package/src/components/section-header.stories.tsx +24 -0
- package/src/components/section-header.test.tsx +28 -0
- package/src/components/section-header.tsx +45 -7
- package/src/components/signature-chip.tsx +77 -0
- package/src/components/signature-line-list.stories.tsx +85 -0
- package/src/components/signature-line-list.tsx +136 -0
- package/src/components/snap-rail.stories.tsx +79 -0
- package/src/components/snap-rail.test.tsx +18 -0
- package/src/components/snap-rail.tsx +148 -0
- package/src/components/stat-banner.stories.tsx +100 -0
- package/src/components/stat-banner.tsx +117 -0
- package/src/components/timeline-band.tsx +52 -12
- package/src/components/timeline-playhead.stories.tsx +30 -0
- package/src/components/timeline-playhead.test.tsx +11 -0
- package/src/components/timeline-playhead.tsx +46 -0
- package/src/components/venue-busyness-strip.stories.tsx +66 -0
- package/src/components/venue-busyness-strip.tsx +64 -0
- package/src/components/venue-grade-dimensions.stories.tsx +83 -0
- package/src/components/venue-grade-dimensions.tsx +91 -0
- package/src/components/venue-quotes.stories.tsx +59 -0
- package/src/components/venue-quotes.tsx +64 -0
- package/src/components/venue-signature-block.stories.tsx +68 -0
- package/src/components/venue-signature-block.tsx +71 -0
- package/src/components/venue-spotlight.stories.tsx +141 -0
- package/src/components/venue-spotlight.tsx +236 -0
- package/src/components/venue-wall.stories.tsx +78 -0
- package/src/components/venue-wall.tsx +185 -0
- package/src/components/weekday-theme-banner.stories.tsx +113 -0
- package/src/components/weekday-theme-banner.tsx +231 -0
- package/src/components/weekend-panel.test.tsx +43 -0
- package/src/components/weekend-panel.tsx +142 -108
- package/src/hooks/use-drag-swipe.test.tsx +66 -0
- package/src/hooks/use-drag-swipe.ts +105 -0
- package/src/hooks/use-media-query.ts +31 -0
- package/dist/chunk-GPH6L4DX.js +0 -55
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
3
|
+
import { MenuItemPreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
4
|
+
import './place-types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MenuItem — THE menu row.
|
|
8
|
+
*
|
|
9
|
+
* A venue's page (its menu list, its signature picks, the "pairs well with"
|
|
10
|
+
* list inside the entity drawer) and the cross-venue surfaces (the city menu,
|
|
11
|
+
* the hub's "known for" list, the daily editorial buckets) all render this one
|
|
12
|
+
* component, so an item is visibly the same object wherever it is met.
|
|
13
|
+
*
|
|
14
|
+
* Hoisted out of the app, where it had grown a second and a third lookalike
|
|
15
|
+
* beside it. It carries the app version's design decisions verbatim; only the
|
|
16
|
+
* couplings changed — the locale, the translation keys and the category-colour
|
|
17
|
+
* table are injected, because the DS owns none of those.
|
|
18
|
+
*
|
|
19
|
+
* ## The left mark
|
|
20
|
+
*
|
|
21
|
+
* An 8px rounded tab in the section's CATEGORY colour, flat against the row's
|
|
22
|
+
* left edge and bulging right. It replaced an earlier monogram chip, and the
|
|
23
|
+
* trade was deliberate:
|
|
24
|
+
*
|
|
25
|
+
* + The text column widens. At 390px the monogram costs 50px (38 + gap) and
|
|
26
|
+
* the tab costs 16px, so the text column goes 308px → 342px: ~11% more room
|
|
27
|
+
* for names and descriptions, which means fewer wrapped names and more
|
|
28
|
+
* items per screen.
|
|
29
|
+
* + The hue answers "which section am I in" rather than "what is this made
|
|
30
|
+
* of". Every row in a category draws the same mark, so it survives the
|
|
31
|
+
* category header scrolling out of view — which a per-item ingredient hue
|
|
32
|
+
* cannot do, since it changes for reasons the reader cannot see.
|
|
33
|
+
* − COLOUR-BLINDNESS. The monogram gave a reader who cannot separate the hues
|
|
34
|
+
* a LETTER; a rule collapses to the same grey bar for everyone. Both marks
|
|
35
|
+
* are `aria-hidden` and neither carries meaning the text lacks, so this is
|
|
36
|
+
* not a WCAG 1.4.1 failure — but it is a real loss of scannability for ~8%
|
|
37
|
+
* of men, and it has not been bought back yet.
|
|
38
|
+
*
|
|
39
|
+
* `markGradient` is passed IN, never derived: inside a menu the colour depends
|
|
40
|
+
* on the neighbouring sections, which a single row cannot see.
|
|
41
|
+
*
|
|
42
|
+
* ## No chevron
|
|
43
|
+
*
|
|
44
|
+
* The row is already `role="button"`, so an arrow would only restate that, and
|
|
45
|
+
* the right edge belongs to the mention count, which the arrow would collide
|
|
46
|
+
* with.
|
|
47
|
+
*/
|
|
48
|
+
/** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
|
|
49
|
+
type MenuItemLinkProps = MenuLinkProps;
|
|
50
|
+
type MenuItemLabels = {
|
|
51
|
+
/** Currency unit beside the price, e.g. "RON". */
|
|
52
|
+
currency: string;
|
|
53
|
+
/** Fully-worded mention count — the accessible name for the glyph-and-numeral pair. */
|
|
54
|
+
mentions: (count: number) => string;
|
|
55
|
+
/** Chip text for a `signature`-strength item. */
|
|
56
|
+
signature: string;
|
|
57
|
+
/** Chip text for a `weak` (merely mentioned) item. */
|
|
58
|
+
mentioned: string;
|
|
59
|
+
};
|
|
60
|
+
type MenuItemProps = {
|
|
61
|
+
item: MenuItemPreview;
|
|
62
|
+
labels: MenuItemLabels;
|
|
63
|
+
/** Locale-aware money formatter. The DS owns no `Intl` locale. */
|
|
64
|
+
formatPrice: (amount: number) => string;
|
|
65
|
+
/** Locale-aware integer formatter for the mention count. Defaults to `String`. */
|
|
66
|
+
formatCount?: (value: number) => string;
|
|
67
|
+
/**
|
|
68
|
+
* Opens the item's own sheet. Ignored when `href` is set.
|
|
69
|
+
*
|
|
70
|
+
* Neither this nor `href` → an INERT row: same geometry, mark, type and meta
|
|
71
|
+
* line, but no `role="button"`, no tab stop and no hover or press states.
|
|
72
|
+
* That is the must-try mention with no menu row behind it — it has nothing to
|
|
73
|
+
* open, so it renders as content rather than as a control that goes nowhere.
|
|
74
|
+
*/
|
|
75
|
+
onOpen?: () => void;
|
|
76
|
+
showSignatureBadge?: boolean;
|
|
77
|
+
signatureTier?: "strong" | "weak";
|
|
78
|
+
onSignatureEvidence?: () => void;
|
|
79
|
+
/** Opens the reviews behind the mention count. */
|
|
80
|
+
onMentions?: () => void;
|
|
81
|
+
/** The mark's colour, as a CSS `background-image` value. */
|
|
82
|
+
markGradient?: string;
|
|
83
|
+
/**
|
|
84
|
+
* The venue this item belongs to — an attribution line under the name.
|
|
85
|
+
*
|
|
86
|
+
* Set it ONLY on cross-venue surfaces, where ten near-identical burgers come
|
|
87
|
+
* from ten kitchens and the venue IS the tap decision. A venue's own menu
|
|
88
|
+
* page must never pass it: the page already said whose menu this is. Passed
|
|
89
|
+
* in rather than read off `item.place` so the redundant case cannot be the
|
|
90
|
+
* default.
|
|
91
|
+
*/
|
|
92
|
+
venueName?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Explicit subtitle, in italic, in place of the description. Used where the
|
|
95
|
+
* reviewer's reason IS the row's content rather than the payoff for tapping.
|
|
96
|
+
*/
|
|
97
|
+
subtitle?: string;
|
|
98
|
+
/**
|
|
99
|
+
* Render the row as a LINK instead of a `role="button"` div — for surfaces
|
|
100
|
+
* where it navigates rather than opening a sheet, so it stays
|
|
101
|
+
* middle-clickable and shareable. Implemented as a stretched overlay rather
|
|
102
|
+
* than by wrapping the row: the mention count is a control in its own right
|
|
103
|
+
* and a button inside an anchor is invalid.
|
|
104
|
+
*/
|
|
105
|
+
href?: string;
|
|
106
|
+
/** Accessible name for the link overlay. Defaults to the item's name. */
|
|
107
|
+
linkLabel?: string;
|
|
108
|
+
linkComponent?: MenuLinkComponent;
|
|
109
|
+
disabled?: boolean;
|
|
110
|
+
className?: string;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* The mention count: a filled quote glyph and the numeral, pinned right.
|
|
114
|
+
*
|
|
115
|
+
* Deliberately NOT a badge — a chip would add a filled shape to a row that has
|
|
116
|
+
* none, and would read as a status label the venue was given rather than a
|
|
117
|
+
* count drawn from the same reviews.
|
|
118
|
+
*/
|
|
119
|
+
declare function MentionSignal({ label, count, onMentions, formatCount, }: {
|
|
120
|
+
label: string;
|
|
121
|
+
count: number;
|
|
122
|
+
onMentions?: () => void;
|
|
123
|
+
formatCount?: (value: number) => string;
|
|
124
|
+
}): react_jsx_runtime.JSX.Element;
|
|
125
|
+
declare const MenuItem: React.NamedExoticComponent<MenuItemProps>;
|
|
126
|
+
|
|
127
|
+
export { MentionSignal, MenuItem, type MenuItemLabels, type MenuItemLinkProps, type MenuItemProps };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { MenuConceptPreview, MenuFamilyPreview, MenuIntentTile, MenuItemPreview, MenuVenuePreview } from './menu-types.js';
|
|
2
|
+
import { PlacePreview } from './place-types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fixtures for the menu-hub surfaces — used by their stories, and shipped so
|
|
7
|
+
* that consumers rendering these components outside an app (post-generator's
|
|
8
|
+
* Remotion frames, admin previews) do not each rebuild a plausible view model.
|
|
9
|
+
*
|
|
10
|
+
* ## Every venue here is invented
|
|
11
|
+
*
|
|
12
|
+
* The names, ratings and quotes are fabricated. Real venues must never appear
|
|
13
|
+
* in marketing or promotional renders — a real kitchen's rating in a promo
|
|
14
|
+
* frame is a claim about that business, made without its involvement, and it
|
|
15
|
+
* goes stale the moment the data moves. Anything filmed from these fixtures is
|
|
16
|
+
* safe to publish; anything filmed from live data is not.
|
|
17
|
+
*
|
|
18
|
+
* The SHAPES are real, though: the gaps are the gaps the API actually has, so
|
|
19
|
+
* a layout that survives these fixtures survives production. `mentions` is
|
|
20
|
+
* populated everywhere because it is populated everywhere upstream; one venue
|
|
21
|
+
* has no `must_try`, one has no photograph, and one grade is missing the
|
|
22
|
+
* dimensions a coffee bar never gets.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
declare const mockGradients: {
|
|
26
|
+
readonly pasta: "linear-gradient(150deg, oklch(0.555 0.105 80), oklch(0.455 0.10 70))";
|
|
27
|
+
readonly meat: "linear-gradient(150deg, oklch(0.49 0.13 28), oklch(0.41 0.12 22))";
|
|
28
|
+
readonly greens: "linear-gradient(150deg, oklch(0.505 0.115 148), oklch(0.415 0.11 142))";
|
|
29
|
+
readonly sweet: "linear-gradient(150deg, oklch(0.505 0.14 50), oklch(0.405 0.13 42))";
|
|
30
|
+
readonly cocktail: "linear-gradient(150deg, oklch(0.485 0.095 205), oklch(0.405 0.09 212))";
|
|
31
|
+
};
|
|
32
|
+
declare const mockPlaces: PlacePreview[];
|
|
33
|
+
declare const mockVenues: MenuVenuePreview[];
|
|
34
|
+
declare const mockItems: MenuItemPreview[];
|
|
35
|
+
declare const mockConcept: MenuConceptPreview;
|
|
36
|
+
declare const mockFamily: MenuFamilyPreview;
|
|
37
|
+
/** The drinks counterpart, and the reason `chip.count` is optional. */
|
|
38
|
+
declare const mockDrinkFamily: MenuFamilyPreview;
|
|
39
|
+
declare const mockIntentTiles: MenuIntentTile[];
|
|
40
|
+
/** Formatters the stories pass. Real callers inject locale-aware ones. */
|
|
41
|
+
declare const mockFormatters: {
|
|
42
|
+
price: (value: number) => string;
|
|
43
|
+
number: (value: number) => string;
|
|
44
|
+
score: (value: number) => string;
|
|
45
|
+
};
|
|
46
|
+
declare const mockHubLabels: {
|
|
47
|
+
mentions: (count: number) => string;
|
|
48
|
+
venues: (count: number) => string;
|
|
49
|
+
seeAll: string;
|
|
50
|
+
signature: string;
|
|
51
|
+
openNow: string;
|
|
52
|
+
until: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export { mockConcept, mockDrinkFamily, mockFamily, mockFormatters, mockGradients, mockHubLabels, mockIntentTiles, mockItems, mockPlaces, mockVenues };
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import '../chunk-3GWWZKX7.js';
|
|
2
|
+
|
|
3
|
+
// src/components/menu-mocks.ts
|
|
4
|
+
var mockGradients = {
|
|
5
|
+
// The forest dictionary (docs/colour-dictionary.md, Foundations/Colour dictionary):
|
|
6
|
+
// wheat, ember, pine, maple, lake water. Post-generator films these, so they
|
|
7
|
+
// must be the same values the app paints.
|
|
8
|
+
pasta: "linear-gradient(150deg, oklch(0.555 0.105 80), oklch(0.455 0.10 70))",
|
|
9
|
+
meat: "linear-gradient(150deg, oklch(0.49 0.13 28), oklch(0.41 0.12 22))",
|
|
10
|
+
greens: "linear-gradient(150deg, oklch(0.505 0.115 148), oklch(0.415 0.11 142))",
|
|
11
|
+
sweet: "linear-gradient(150deg, oklch(0.505 0.14 50), oklch(0.405 0.13 42))",
|
|
12
|
+
cocktail: "linear-gradient(150deg, oklch(0.485 0.095 205), oklch(0.405 0.09 212))"
|
|
13
|
+
};
|
|
14
|
+
var mockPlaces = [
|
|
15
|
+
{
|
|
16
|
+
id: "p1",
|
|
17
|
+
slug: "the-copper-yard",
|
|
18
|
+
name: "The Copper Yard",
|
|
19
|
+
open_now: true,
|
|
20
|
+
rating: { rating: 4.6, count: 318 },
|
|
21
|
+
hours: [{ opens_at: 12, closes_at: 23 }],
|
|
22
|
+
price_dishes: { price_range_low: 19, price_range_high: 62, median_price: 28 },
|
|
23
|
+
cuisines: ["italian", "pizza"],
|
|
24
|
+
type: "pub",
|
|
25
|
+
busyness: [
|
|
26
|
+
{ hour: 12, score: 22 },
|
|
27
|
+
{ hour: 13, score: 26 },
|
|
28
|
+
{ hour: 14, score: 30 },
|
|
29
|
+
{ hour: 15, score: 38 },
|
|
30
|
+
{ hour: 16, score: 52 },
|
|
31
|
+
{ hour: 17, score: 74 },
|
|
32
|
+
{ hour: 18, score: 92 },
|
|
33
|
+
{ hour: 19, score: 100 },
|
|
34
|
+
{ hour: 20, score: 86 },
|
|
35
|
+
{ hour: 21, score: 64 }
|
|
36
|
+
],
|
|
37
|
+
reviews_digest: {
|
|
38
|
+
review_count: 318,
|
|
39
|
+
grade: {
|
|
40
|
+
overall: 8.4,
|
|
41
|
+
show_number: true,
|
|
42
|
+
descriptor: "Foarte bun",
|
|
43
|
+
confidence: "high",
|
|
44
|
+
dimensions: {
|
|
45
|
+
food: 89,
|
|
46
|
+
service: 82,
|
|
47
|
+
value: 86,
|
|
48
|
+
ambiance: 78,
|
|
49
|
+
portion: 91,
|
|
50
|
+
consistency: 84
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
value: { value: "fair" },
|
|
54
|
+
tags: [{ value: "casual" }, { value: "cozy" }, { value: "family_friendly" }],
|
|
55
|
+
highlights: [
|
|
56
|
+
{
|
|
57
|
+
text: "The wings are the reason to sit at the bar \u2014 hot honey, and they keep going until the kitchen shuts.",
|
|
58
|
+
review_ids: ["r1", "r2", "r3"]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
text: "Last kitchen open on this street. We came at half eleven and still ate properly.",
|
|
62
|
+
review_ids: ["r4", "r5"]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
must_try: [
|
|
66
|
+
{
|
|
67
|
+
name: "Chicken wings, 6 pc",
|
|
68
|
+
why: "Buttermilk brine and hot honey, fried to order until the kitchen shuts.",
|
|
69
|
+
strength: "signature",
|
|
70
|
+
slug: "chicken-wings",
|
|
71
|
+
mentions: 23,
|
|
72
|
+
sizes_and_prices: [{ size: 320, price: 32 }],
|
|
73
|
+
review_ids: ["r1", "r2", "r3"]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "p2",
|
|
80
|
+
slug: "salt-and-stone",
|
|
81
|
+
name: "Salt & Stone",
|
|
82
|
+
open_now: true,
|
|
83
|
+
rating: { rating: 4.8, count: 204 },
|
|
84
|
+
hours: [{ opens_at: 11, closes_at: 22 }],
|
|
85
|
+
price_dishes: { price_range_low: 38, price_range_high: 84, median_price: 49 },
|
|
86
|
+
cuisines: ["italian"],
|
|
87
|
+
type: "restaurant",
|
|
88
|
+
reviews_digest: {
|
|
89
|
+
review_count: 204,
|
|
90
|
+
grade: {
|
|
91
|
+
overall: 8.9,
|
|
92
|
+
show_number: true,
|
|
93
|
+
dimensions: { food: 93, service: 88, value: 74, ambiance: 85 }
|
|
94
|
+
},
|
|
95
|
+
must_try: [
|
|
96
|
+
{
|
|
97
|
+
name: "Truffle Tagliatelle",
|
|
98
|
+
why: "Made in-house each morning with black truffle and parmesan.",
|
|
99
|
+
strength: "signature",
|
|
100
|
+
slug: "truffle-tagliatelle",
|
|
101
|
+
mentions: 17,
|
|
102
|
+
sizes_and_prices: [{ size: 320, price: 62 }],
|
|
103
|
+
review_ids: ["r6", "r7"]
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: "p3",
|
|
110
|
+
slug: "the-quiet-hours",
|
|
111
|
+
name: "The Quiet Hours",
|
|
112
|
+
open_now: true,
|
|
113
|
+
rating: { rating: 4.5, count: 96 },
|
|
114
|
+
hours: [{ opens_at: 8, closes_at: 20 }],
|
|
115
|
+
price_dishes: { price_range_low: 8, price_range_high: 26, median_price: 14 },
|
|
116
|
+
type: "coffee_shop",
|
|
117
|
+
reviews_digest: {
|
|
118
|
+
review_count: 96,
|
|
119
|
+
// A coffee bar is graded on coffee_quality and never on food — the
|
|
120
|
+
// dimension row must survive the absence rather than draw a zero.
|
|
121
|
+
grade: {
|
|
122
|
+
overall: 8.6,
|
|
123
|
+
show_number: true,
|
|
124
|
+
dimensions: { coffee_quality: 91, service: 86, value: 80, ambiance: 88 }
|
|
125
|
+
},
|
|
126
|
+
must_try: [
|
|
127
|
+
{
|
|
128
|
+
name: "Flat white",
|
|
129
|
+
strength: "frequent",
|
|
130
|
+
slug: "flat-white",
|
|
131
|
+
mentions: 28,
|
|
132
|
+
sizes_and_prices: [{ size: 220, price: 14 }]
|
|
133
|
+
}
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "p4",
|
|
139
|
+
slug: "green-room-canteen",
|
|
140
|
+
name: "Green Room Canteen",
|
|
141
|
+
open_now: false,
|
|
142
|
+
rating: { rating: 4.7, count: 141 },
|
|
143
|
+
price_dishes: { price_range_low: 22, price_range_high: 51, median_price: 31 },
|
|
144
|
+
type: "restaurant"
|
|
145
|
+
// No digest at all: a place the census has not reached. Every venue surface
|
|
146
|
+
// has to render this without collapsing.
|
|
147
|
+
}
|
|
148
|
+
];
|
|
149
|
+
var placeBySlug = (slug) => mockPlaces.find((place) => place.slug === slug);
|
|
150
|
+
var mockVenues = [
|
|
151
|
+
{
|
|
152
|
+
place: placeBySlug("the-copper-yard"),
|
|
153
|
+
href: "/places/the-copper-yard",
|
|
154
|
+
closes_at_label: "23:00",
|
|
155
|
+
signature: {
|
|
156
|
+
item: {
|
|
157
|
+
id: "s1",
|
|
158
|
+
slug: "chicken-wings",
|
|
159
|
+
name: "Chicken wings, 6 pc",
|
|
160
|
+
kind: "dish",
|
|
161
|
+
mentions: 23,
|
|
162
|
+
sizes_and_prices: [{ size: 320, price: 32 }]
|
|
163
|
+
},
|
|
164
|
+
why: "Buttermilk brine and hot honey, fried to order until the kitchen shuts.",
|
|
165
|
+
isSignature: true,
|
|
166
|
+
href: "/menu/dishes/chicken-wings"
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
place: placeBySlug("salt-and-stone"),
|
|
171
|
+
href: "/places/salt-and-stone",
|
|
172
|
+
closes_at_label: "22:00",
|
|
173
|
+
signature: {
|
|
174
|
+
item: {
|
|
175
|
+
id: "s2",
|
|
176
|
+
slug: "truffle-tagliatelle",
|
|
177
|
+
name: "Truffle Tagliatelle",
|
|
178
|
+
kind: "dish",
|
|
179
|
+
mentions: 17,
|
|
180
|
+
sizes_and_prices: [{ size: 320, price: 62 }]
|
|
181
|
+
},
|
|
182
|
+
why: "Made in-house each morning with black truffle and parmesan.",
|
|
183
|
+
isSignature: true,
|
|
184
|
+
href: "/menu/dishes/truffle-tagliatelle"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
place: placeBySlug("the-quiet-hours"),
|
|
189
|
+
href: "/places/the-quiet-hours",
|
|
190
|
+
closes_at_label: "20:00",
|
|
191
|
+
signature: {
|
|
192
|
+
item: {
|
|
193
|
+
id: "s3",
|
|
194
|
+
slug: "flat-white",
|
|
195
|
+
name: "Flat white",
|
|
196
|
+
kind: "drink",
|
|
197
|
+
mentions: 28,
|
|
198
|
+
sizes_and_prices: [{ size: 220, price: 14 }]
|
|
199
|
+
},
|
|
200
|
+
href: "/menu/drinks/flat-white"
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
place: placeBySlug("green-room-canteen"),
|
|
205
|
+
href: "/places/green-room-canteen"
|
|
206
|
+
// No signature: the row must still read as a venue.
|
|
207
|
+
}
|
|
208
|
+
];
|
|
209
|
+
var mockItems = [
|
|
210
|
+
{
|
|
211
|
+
id: "i1",
|
|
212
|
+
slug: "truffle-tagliatelle",
|
|
213
|
+
name: "Truffle Tagliatelle",
|
|
214
|
+
kind: "dish",
|
|
215
|
+
category_slug: "pasta",
|
|
216
|
+
mentions: 17,
|
|
217
|
+
sizes_and_prices: [{ size: 320, price: 62 }],
|
|
218
|
+
place: { slug: "salt-and-stone", name: "Salt & Stone" }
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "i2",
|
|
222
|
+
slug: "chicken-wings",
|
|
223
|
+
name: "Chicken wings, 6 pc",
|
|
224
|
+
kind: "dish",
|
|
225
|
+
category_slug: "chicken-dish",
|
|
226
|
+
mentions: 23,
|
|
227
|
+
sizes_and_prices: [{ size: 320, price: 32 }],
|
|
228
|
+
place: { slug: "the-copper-yard", name: "The Copper Yard" }
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
id: "i3",
|
|
232
|
+
slug: "burrata-heirloom-tomato",
|
|
233
|
+
name: "Burrata & heirloom tomato",
|
|
234
|
+
kind: "dish",
|
|
235
|
+
category_slug: "salad",
|
|
236
|
+
mentions: 14,
|
|
237
|
+
sizes_and_prices: [{ size: 300, price: 49 }],
|
|
238
|
+
place: { slug: "green-room-canteen", name: "Green Room Canteen" }
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
id: "i4",
|
|
242
|
+
slug: "flat-white",
|
|
243
|
+
name: "Flat white",
|
|
244
|
+
kind: "drink",
|
|
245
|
+
category_slug: "non-alcoholic",
|
|
246
|
+
mentions: 28,
|
|
247
|
+
sizes_and_prices: [{ size: 220, price: 14 }],
|
|
248
|
+
place: { slug: "the-quiet-hours", name: "The Quiet Hours" }
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "i5",
|
|
252
|
+
slug: "house-negroni",
|
|
253
|
+
name: "House negroni",
|
|
254
|
+
kind: "drink",
|
|
255
|
+
category_slug: "cocktail",
|
|
256
|
+
// Deliberately unmentioned: the badge must not render "0".
|
|
257
|
+
mentions: 0,
|
|
258
|
+
sizes_and_prices: [{ size: 150, price: 32 }],
|
|
259
|
+
place: { slug: "the-copper-yard", name: "The Copper Yard" }
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
var mockConcept = {
|
|
263
|
+
slug: "margherita",
|
|
264
|
+
title: "Margherita",
|
|
265
|
+
kind: "dish",
|
|
266
|
+
venue_count: 87,
|
|
267
|
+
price_low: 10,
|
|
268
|
+
price_high: 120,
|
|
269
|
+
alias_count: 12,
|
|
270
|
+
mentions: 214,
|
|
271
|
+
rows: [
|
|
272
|
+
{
|
|
273
|
+
place_slug: "the-copper-yard",
|
|
274
|
+
place_name: "The Copper Yard",
|
|
275
|
+
alias: "Pizza Margherita",
|
|
276
|
+
price: 34,
|
|
277
|
+
mentions: 23,
|
|
278
|
+
rating: 4.6,
|
|
279
|
+
place_type: "pub",
|
|
280
|
+
featured: true
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
place_slug: "salt-and-stone",
|
|
284
|
+
place_name: "Salt & Stone",
|
|
285
|
+
alias: "Margherita D.O.P.",
|
|
286
|
+
price: 46,
|
|
287
|
+
mentions: 17,
|
|
288
|
+
rating: 4.8,
|
|
289
|
+
place_type: "restaurant"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
place_slug: "green-room-canteen",
|
|
293
|
+
place_name: "Green Room Canteen",
|
|
294
|
+
alias: "Marinara Bianca",
|
|
295
|
+
price: 52,
|
|
296
|
+
mentions: 9,
|
|
297
|
+
rating: 4.7,
|
|
298
|
+
place_type: "canteen"
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
};
|
|
302
|
+
var mockFamily = {
|
|
303
|
+
slug: "italian",
|
|
304
|
+
name: "Italian",
|
|
305
|
+
venue_count: 247,
|
|
306
|
+
chips: [
|
|
307
|
+
{ slug: "pizza", label: "Pizza", count: 120 },
|
|
308
|
+
{ slug: "pasta", label: "Pasta", count: 96 },
|
|
309
|
+
{ slug: "salad", label: "Salad", count: 88 },
|
|
310
|
+
{ slug: "chicken-dish", label: "Chicken", count: 77 }
|
|
311
|
+
],
|
|
312
|
+
price_low: 32,
|
|
313
|
+
price_high: 84,
|
|
314
|
+
price_median: 49,
|
|
315
|
+
venue_photo_urls: [],
|
|
316
|
+
signature: {
|
|
317
|
+
item: {
|
|
318
|
+
id: "f1",
|
|
319
|
+
slug: "truffle-tagliatelle",
|
|
320
|
+
name: "Truffle Tagliatelle",
|
|
321
|
+
kind: "dish",
|
|
322
|
+
mentions: 17,
|
|
323
|
+
sizes_and_prices: [{ size: 320, price: 62 }]
|
|
324
|
+
},
|
|
325
|
+
why: "Salt & Stone",
|
|
326
|
+
href: "/menu/dishes/truffle-tagliatelle"
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
var mockDrinkFamily = {
|
|
330
|
+
slug: "cocktail",
|
|
331
|
+
name: "Cocktails",
|
|
332
|
+
venue_count: 21,
|
|
333
|
+
// No counts: there is no drink-group facet on places, so these ship as bare
|
|
334
|
+
// labels rather than with an invented number.
|
|
335
|
+
chips: [
|
|
336
|
+
{ slug: "spritz", label: "Spritz" },
|
|
337
|
+
{ slug: "negroni", label: "Negroni" },
|
|
338
|
+
{ slug: "sour", label: "Sour" }
|
|
339
|
+
],
|
|
340
|
+
price_low: 18,
|
|
341
|
+
price_high: 54,
|
|
342
|
+
price_median: 28,
|
|
343
|
+
venue_photo_urls: [],
|
|
344
|
+
signature: {
|
|
345
|
+
item: {
|
|
346
|
+
id: "f2",
|
|
347
|
+
slug: "house-negroni",
|
|
348
|
+
name: "House negroni",
|
|
349
|
+
kind: "drink",
|
|
350
|
+
mentions: 16,
|
|
351
|
+
sizes_and_prices: [{ size: 150, price: 32 }]
|
|
352
|
+
},
|
|
353
|
+
why: "The Copper Yard"
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
var intentGradient = (hue) => `linear-gradient(150deg, oklch(0.52 0.14 ${hue}), oklch(0.43 0.13 ${hue - 4}))`;
|
|
357
|
+
var mockIntentTiles = [
|
|
358
|
+
{ slug: "italian", label: "Italian tonight", caption: "247 kitchens", href: "/top/italian?open=now", gradient: intentGradient(40) },
|
|
359
|
+
{ slug: "burgeri", label: "Burgers", caption: "229 kitchens", href: "/top/burgeri", gradient: intentGradient(25) },
|
|
360
|
+
{ slug: "restaurante", label: "Open right now", caption: "by the hour", href: "/top/restaurante?open=now", gradient: intentGradient(60) },
|
|
361
|
+
{ slug: "brunch", label: "Brunch", caption: "314 kitchens", href: "/top/brunch", gradient: intentGradient(75) },
|
|
362
|
+
{ slug: "vegan", label: "Vegan", caption: "108 kitchens", href: "/top/vegan", gradient: intentGradient(148) },
|
|
363
|
+
{ slug: "centru", label: "In Centru", caption: "by neighbourhood", href: "/top/centru", gradient: intentGradient(300) }
|
|
364
|
+
];
|
|
365
|
+
var mockFormatters = {
|
|
366
|
+
price: (value) => `${value} lei`,
|
|
367
|
+
number: (value) => value.toLocaleString("en-US"),
|
|
368
|
+
score: (value) => (value / 10).toFixed(1)
|
|
369
|
+
};
|
|
370
|
+
var mockHubLabels = {
|
|
371
|
+
mentions: (count) => `${count} mentions`,
|
|
372
|
+
venues: (count) => `${count} kitchens`,
|
|
373
|
+
seeAll: "See all",
|
|
374
|
+
signature: "SIGNATURE",
|
|
375
|
+
openNow: "Open now",
|
|
376
|
+
until: "until"
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
export { mockConcept, mockDrinkFamily, mockFamily, mockFormatters, mockGradients, mockHubLabels, mockIntentTiles, mockItems, mockPlaces, mockVenues };
|