@bearmenu/ui 0.8.8 → 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-62ES6N32.js +27 -0
- package/dist/chunk-AXV5O2IZ.js +58 -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/alert.d.ts +2 -2
- package/dist/components/badge.d.ts +1 -1
- package/dist/components/button.d.ts +1 -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/event-card.js +1 -1
- package/dist/components/event-compact-row.d.ts +1 -1
- package/dist/components/event-compact-row.js +1 -1
- package/dist/components/event-detail-panel.d.ts +171 -25
- package/dist/components/event-detail-panel.js +471 -174
- package/dist/components/event-list-row-wide.js +1 -1
- package/dist/components/event-list-row.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/ranked-venue-list.d.ts +49 -0
- package/dist/components/ranked-venue-list.js +87 -0
- 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 +24 -4
- package/dist/components/snap-rail.js +66 -8
- 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/tag.d.ts +2 -2
- package/dist/components/text.d.ts +3 -3
- 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/package.json +1 -1
- 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/event-compact-row.tsx +1 -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/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/ranked-venue-list.stories.tsx +80 -0
- package/src/components/ranked-venue-list.tsx +177 -0
- 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.tsx +90 -9
- package/src/components/stat-banner.stories.tsx +100 -0
- package/src/components/stat-banner.tsx +117 -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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { ComponentType, CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { MenuSizeAndPrice, PlacePreview } from './place-types.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Structural mirror of the openapi `Dish` / `Drink` schemas, decoupled from any
|
|
6
|
+
* one app's generated types. FE, admin and post-generator map their domain
|
|
7
|
+
* shapes into these before passing data to the menu-hub surfaces.
|
|
8
|
+
*
|
|
9
|
+
* Same contract as `place-types.ts` and `event-types.ts`: no app schemas, no
|
|
10
|
+
* app hooks, no i18n. Every user-facing string arrives through a `*Labels`
|
|
11
|
+
* object, every destination through an `href`, and every locale-sensitive
|
|
12
|
+
* format through an injected formatter — the DS never owns a translation key
|
|
13
|
+
* or an `Intl` locale.
|
|
14
|
+
*
|
|
15
|
+
* ## One set of components, two tabs
|
|
16
|
+
*
|
|
17
|
+
* Dishes and drinks are the same shape. `MenuItemPreview.kind` is the only
|
|
18
|
+
* discriminator, and nothing in this file branches on it — a drinks hub is a
|
|
19
|
+
* different view model and a different `*Labels` object, never a second set of
|
|
20
|
+
* components. Anything that would need a `if (kind === 'drink')` inside a
|
|
21
|
+
* component belongs in the caller's mapper instead.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The shape every menu surface's `linkComponent` must accept.
|
|
26
|
+
*
|
|
27
|
+
* One type rather than a clone per component: these surfaces nest each other —
|
|
28
|
+
* a family card renders a `<MenuItem>`, a spotlight renders a signature block —
|
|
29
|
+
* and a caller passes ONE `next/link` down through all of them. Subtly
|
|
30
|
+
* different clones made those hand-offs fail to typecheck for no design reason.
|
|
31
|
+
*/
|
|
32
|
+
type MenuLinkProps = {
|
|
33
|
+
href: string;
|
|
34
|
+
className?: string;
|
|
35
|
+
'aria-label'?: string;
|
|
36
|
+
'aria-current'?: 'page';
|
|
37
|
+
/** Intent tiles paint their hue on the link element itself. */
|
|
38
|
+
style?: CSSProperties;
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
};
|
|
41
|
+
type MenuLinkComponent = ComponentType<MenuLinkProps>;
|
|
42
|
+
/**
|
|
43
|
+
* A dish or a drink as every hub surface consumes it.
|
|
44
|
+
*
|
|
45
|
+
* `mentions` is the census count of review mentions and is populated on every
|
|
46
|
+
* item in the catalogue — it is the ranking signal the hub is built on. Do not
|
|
47
|
+
* confuse it with `review_count`, which counts full review objects and is zero
|
|
48
|
+
* for most items; a surface that ranks on `review_count` renders an empty page.
|
|
49
|
+
*/
|
|
50
|
+
type MenuItemPreview = {
|
|
51
|
+
id: string;
|
|
52
|
+
slug: string;
|
|
53
|
+
name: string;
|
|
54
|
+
kind: 'dish' | 'drink';
|
|
55
|
+
description?: string;
|
|
56
|
+
/** API category code (pizza, pasta, cocktail, non-alcoholic, …). Localised by the caller. */
|
|
57
|
+
category_slug?: string;
|
|
58
|
+
sizes_and_prices?: MenuSizeAndPrice[];
|
|
59
|
+
/** Censused review mentions. The hub's ranking signal. */
|
|
60
|
+
mentions?: number;
|
|
61
|
+
/** Full review objects citing this item. Sparse — not a ranking signal. */
|
|
62
|
+
review_count?: number;
|
|
63
|
+
/** 0–100 recommendation score. */
|
|
64
|
+
score?: number;
|
|
65
|
+
/** The venue serving it. Menu items are always venue-scoped. */
|
|
66
|
+
place?: MenuItemPlaceRef;
|
|
67
|
+
/**
|
|
68
|
+
* Pre-resolved photo URL. Menu-item photography is AI-generated and hidden in
|
|
69
|
+
* production, so hub surfaces draw a typographic `<MenuFieldTile>` instead
|
|
70
|
+
* and leave this unset. It exists for the surfaces that legitimately show one
|
|
71
|
+
* (concept detail) and for consumers outside the consumer app.
|
|
72
|
+
*/
|
|
73
|
+
photo_url?: string;
|
|
74
|
+
};
|
|
75
|
+
/** The venue attribution carried on a menu item. */
|
|
76
|
+
type MenuItemPlaceRef = {
|
|
77
|
+
slug: string;
|
|
78
|
+
name: string;
|
|
79
|
+
/** Pre-resolved venue thumbnail. Venue photography is real; item photography is not. */
|
|
80
|
+
thumbnail_url?: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* A dish or drink concept — one item across every kitchen that makes it, under
|
|
84
|
+
* whatever name each of them uses. The page's keystone, and the one thing the
|
|
85
|
+
* catalogue can say that a single menu cannot.
|
|
86
|
+
*/
|
|
87
|
+
type MenuConceptPreview = {
|
|
88
|
+
slug: string;
|
|
89
|
+
/** Canonical concept name, e.g. "Margherita". */
|
|
90
|
+
title: string;
|
|
91
|
+
kind: 'dish' | 'drink';
|
|
92
|
+
/** Venues serving it. */
|
|
93
|
+
venue_count?: number;
|
|
94
|
+
price_low?: number;
|
|
95
|
+
price_high?: number;
|
|
96
|
+
/** Distinct names it appears under across those menus. */
|
|
97
|
+
alias_count?: number;
|
|
98
|
+
/** Summed censused mentions across every venue's version. */
|
|
99
|
+
mentions?: number;
|
|
100
|
+
rows?: MenuConceptRow[];
|
|
101
|
+
};
|
|
102
|
+
/** One venue's version of a concept, as drawn by `<ConceptCompare>`. */
|
|
103
|
+
type MenuConceptRow = {
|
|
104
|
+
place_slug: string;
|
|
105
|
+
place_name: string;
|
|
106
|
+
/** What this kitchen calls it on its own menu. */
|
|
107
|
+
alias: string;
|
|
108
|
+
price: number;
|
|
109
|
+
mentions?: number;
|
|
110
|
+
rating?: number;
|
|
111
|
+
/** Venue type line, e.g. "pub". Localised by the caller. */
|
|
112
|
+
place_type?: string;
|
|
113
|
+
/** Neighbourhood, e.g. "Centru". */
|
|
114
|
+
place_area?: string;
|
|
115
|
+
/**
|
|
116
|
+
* Pre-resolved VENUE photograph.
|
|
117
|
+
*
|
|
118
|
+
* The row is place-led: five restaurants that disagree about a name, not five
|
|
119
|
+
* bars of similar length. A price bar was drawn here first and it measured
|
|
120
|
+
* the one thing already written beside it in words, while the venue — the
|
|
121
|
+
* thing you are actually choosing between — had no image at all.
|
|
122
|
+
*/
|
|
123
|
+
place_thumbnail_url?: string;
|
|
124
|
+
open_now?: boolean;
|
|
125
|
+
/** Marks the row the surface should lead with (cheapest, or most named). */
|
|
126
|
+
featured?: boolean;
|
|
127
|
+
/** Portion, already formatted with its unit, e.g. "260g". */
|
|
128
|
+
portion_label?: string;
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* A browsable grouping of venues — a cuisine on the dishes tab, a drink family
|
|
132
|
+
* on the drinks tab. Both are sets of VENUES, never sets of items: cuisine
|
|
133
|
+
* exists only on places, so a card counting dishes cannot be built.
|
|
134
|
+
*/
|
|
135
|
+
type MenuFamilyPreview = {
|
|
136
|
+
slug: string;
|
|
137
|
+
/** Display name, already localised by the caller. */
|
|
138
|
+
name: string;
|
|
139
|
+
/** Venues in this family. */
|
|
140
|
+
venue_count: number;
|
|
141
|
+
/**
|
|
142
|
+
* Facet chips. Counts are VENUES, not items — they come from the places
|
|
143
|
+
* facet narrowed by this family. The drinks tab has no equivalent facet, so
|
|
144
|
+
* its chips ship without counts; `count` is optional for that reason.
|
|
145
|
+
*/
|
|
146
|
+
chips?: MenuFamilyChip[];
|
|
147
|
+
/** Price band across the family's venues. */
|
|
148
|
+
price_low?: number;
|
|
149
|
+
price_high?: number;
|
|
150
|
+
price_median?: number;
|
|
151
|
+
/** Pre-resolved venue photos for the fanned deck. Venue photography only. */
|
|
152
|
+
venue_photo_urls?: string[];
|
|
153
|
+
/** The item this family is most named for. */
|
|
154
|
+
signature?: MenuFamilySignature;
|
|
155
|
+
};
|
|
156
|
+
type MenuFamilyChip = {
|
|
157
|
+
slug: string;
|
|
158
|
+
label: string;
|
|
159
|
+
/** Venue count. Absent where no facet backs the chip. */
|
|
160
|
+
count?: number;
|
|
161
|
+
};
|
|
162
|
+
/** A family card's featured item. Same shape as a venue's, for the same reason. */
|
|
163
|
+
type MenuFamilySignature = MenuVenueSignature;
|
|
164
|
+
/**
|
|
165
|
+
* A venue as the hub's venue-scoped surfaces consume it: the place itself plus
|
|
166
|
+
* the pre-resolved bits a hub row needs and `PlacePreview` does not carry —
|
|
167
|
+
* which signature to lead with, and where the row points.
|
|
168
|
+
*/
|
|
169
|
+
type MenuVenuePreview = {
|
|
170
|
+
place: PlacePreview;
|
|
171
|
+
href: string;
|
|
172
|
+
/**
|
|
173
|
+
* The signature to draw. Callers pick it from `reviews_digest.must_try`
|
|
174
|
+
* rather than the surface re-deriving the rule on every row.
|
|
175
|
+
*/
|
|
176
|
+
signature?: MenuVenueSignature;
|
|
177
|
+
/** Closing time as an already-formatted wall clock, e.g. "23:00". */
|
|
178
|
+
closes_at_label?: string;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* The resolved signature on a venue row.
|
|
182
|
+
*
|
|
183
|
+
* It carries a whole `MenuItemPreview` rather than a flattened name and price,
|
|
184
|
+
* because every surface that draws one renders `<MenuItem>` — the same row a
|
|
185
|
+
* venue's own menu uses — and that component takes an item. Flattening it here
|
|
186
|
+
* was what let the hub's version drift into a different-looking row.
|
|
187
|
+
*/
|
|
188
|
+
type MenuVenueSignature = {
|
|
189
|
+
item: MenuItemPreview;
|
|
190
|
+
/** The reviewers' reason, from the digest. Rendered in place of the description. */
|
|
191
|
+
why?: string;
|
|
192
|
+
href?: string;
|
|
193
|
+
/** The digest called this the venue's signature rather than merely frequent. */
|
|
194
|
+
isSignature?: boolean;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* An intent tile — a family plus a constraint, resolving to a category page.
|
|
198
|
+
*
|
|
199
|
+
* Only about thirty-two of the thirty-eight cuisines have a page, so `href` is
|
|
200
|
+
* required: a tile that cannot resolve to a real destination is not rendered
|
|
201
|
+
* rather than linking nowhere.
|
|
202
|
+
*/
|
|
203
|
+
type MenuIntentTile = {
|
|
204
|
+
slug: string;
|
|
205
|
+
label: string;
|
|
206
|
+
/** Supporting line, e.g. "247 kitchens". Already formatted by the caller. */
|
|
207
|
+
caption?: string;
|
|
208
|
+
href: string;
|
|
209
|
+
/**
|
|
210
|
+
* Finished CSS gradient for the tile's ground, e.g.
|
|
211
|
+
* `linear-gradient(150deg, …)`.
|
|
212
|
+
*
|
|
213
|
+
* A gradient rather than a token name, for the same reason `<MenuField>`
|
|
214
|
+
* takes one: the hue tables are app-owned, and a component referencing
|
|
215
|
+
* `var(--h-pizza)` renders an invisible tile in any consumer that does not
|
|
216
|
+
* happen to define that variable. A missing gradient falls back to `bg-muted`
|
|
217
|
+
* — visible and obviously unstyled, rather than white text on white.
|
|
218
|
+
*/
|
|
219
|
+
gradient?: string;
|
|
220
|
+
/** Icon node, resolved by the caller — the DS does not pick icons for it. */
|
|
221
|
+
icon?: ReactNode;
|
|
222
|
+
};
|
|
223
|
+
/** Formatters injected into every menu surface. The DS owns no `Intl` locale. */
|
|
224
|
+
type MenuFormatters = {
|
|
225
|
+
/** 24 → "24 lei". */
|
|
226
|
+
price: (value: number) => string;
|
|
227
|
+
/** 1717 → "1,717". */
|
|
228
|
+
number: (value: number) => string;
|
|
229
|
+
};
|
|
230
|
+
/** Strings shared by every menu-hub surface. */
|
|
231
|
+
type MenuHubLabels = {
|
|
232
|
+
/** Mention-count badge. Pass a translator that handles ICU pluralization. */
|
|
233
|
+
mentions: (count: number) => string;
|
|
234
|
+
/** Venue-count line. Pass a translator that handles ICU pluralization. */
|
|
235
|
+
venues: (count: number) => string;
|
|
236
|
+
/** "See all" affordance on a section header. */
|
|
237
|
+
seeAll: string;
|
|
238
|
+
/** Badge on a must-try row the digest called `signature`. */
|
|
239
|
+
signature: string;
|
|
240
|
+
openNow: string;
|
|
241
|
+
/** Prefixes a closing time, e.g. "until". */
|
|
242
|
+
until: string;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export { type MenuConceptPreview, type MenuConceptRow, type MenuFamilyChip, type MenuFamilyPreview, type MenuFamilySignature, type MenuFormatters, type MenuHubLabels, type MenuIntentTile, type MenuItemPlaceRef, type MenuItemPreview, type MenuLinkComponent, type MenuLinkProps, MenuSizeAndPrice, type MenuVenuePreview, type MenuVenueSignature };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -40,12 +40,14 @@ function MotionHouse(props) {
|
|
|
40
40
|
}
|
|
41
41
|
var UTENSILS_TIMES = [0, 0.18, 0.72, 1];
|
|
42
42
|
var SEPARATION = 5;
|
|
43
|
+
var FORK_LIFT = -1.5;
|
|
43
44
|
var fork = {
|
|
44
|
-
rest: { rotate: 0, x: 0, transition: returnTransition },
|
|
45
|
-
active: { rotate: -44, x: -SEPARATION, transition: returnTransition },
|
|
45
|
+
rest: { rotate: 0, x: 0, y: 0, transition: returnTransition },
|
|
46
|
+
active: { rotate: -44, x: -SEPARATION, y: FORK_LIFT, transition: returnTransition },
|
|
46
47
|
play: {
|
|
47
48
|
rotate: [0, 6, -50, -44],
|
|
48
49
|
x: [0, 0.8, -6, -SEPARATION],
|
|
50
|
+
y: [0, 0, FORK_LIFT - 0.3, FORK_LIFT],
|
|
49
51
|
transition: beats(UTENSILS_TIMES, 0.6)
|
|
50
52
|
}
|
|
51
53
|
};
|
|
@@ -59,6 +61,16 @@ var knife = {
|
|
|
59
61
|
}
|
|
60
62
|
};
|
|
61
63
|
var CROSSING = { originX: "12px", originY: "12px" };
|
|
64
|
+
var HANDLE_CROSSED = "M2.1 21.8 8.5 15.4";
|
|
65
|
+
var HANDLE_CLEAR = "M2.1 21.8 12 12";
|
|
66
|
+
var forkHandle = {
|
|
67
|
+
rest: { d: HANDLE_CROSSED, transition: returnTransition },
|
|
68
|
+
active: { d: HANDLE_CLEAR, transition: returnTransition },
|
|
69
|
+
play: {
|
|
70
|
+
d: [HANDLE_CROSSED, HANDLE_CROSSED, HANDLE_CLEAR, HANDLE_CLEAR],
|
|
71
|
+
transition: beats(UTENSILS_TIMES, 0.6)
|
|
72
|
+
}
|
|
73
|
+
};
|
|
62
74
|
function MotionUtensils(props) {
|
|
63
75
|
return /* @__PURE__ */ jsxs(MotionIcon, __spreadProps(__spreadValues({}, props), { children: [
|
|
64
76
|
/* @__PURE__ */ jsx(
|
|
@@ -71,7 +83,7 @@ function MotionUtensils(props) {
|
|
|
71
83
|
),
|
|
72
84
|
/* @__PURE__ */ jsxs(motion.g, { variants: fork, style: CROSSING, children: [
|
|
73
85
|
/* @__PURE__ */ jsx("path", { d: "m16 2-2.3 2.3a3 3 0 0 0 0 4.2l1.8 1.8a3 3 0 0 0 4.2 0L22 8" }),
|
|
74
|
-
/* @__PURE__ */ jsx(
|
|
86
|
+
/* @__PURE__ */ jsx(motion.path, { variants: forkHandle, d: HANDLE_CROSSED }),
|
|
75
87
|
/* @__PURE__ */ jsx("path", { d: "m19 5-7 7" })
|
|
76
88
|
] })
|
|
77
89
|
] }));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { MenuVenuePreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './place-types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OpenNowList — who is still serving, and until when.
|
|
8
|
+
*
|
|
9
|
+
* At half past ten this beats every other sort, which is why it is its own
|
|
10
|
+
* surface rather than a filter chip: the question is not "what is good" but
|
|
11
|
+
* "what is possible", and every other module on a hub answers the first one.
|
|
12
|
+
*
|
|
13
|
+
* The closing time arrives pre-formatted on the view model (`closes_at_label`)
|
|
14
|
+
* because the venue's own trading day can cross midnight — a bar closing at
|
|
15
|
+
* 04:00 closes on the following date — and resolving that needs the venue's
|
|
16
|
+
* timezone and the reader's clock, neither of which the DS has.
|
|
17
|
+
*
|
|
18
|
+
* The live dot is never the only signal that a venue is open: the closing pill
|
|
19
|
+
* carries the same fact in words, so the row reads without colour.
|
|
20
|
+
*
|
|
21
|
+
* Server-safe.
|
|
22
|
+
*/
|
|
23
|
+
/** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
|
|
24
|
+
type OpenNowListLinkProps = MenuLinkProps;
|
|
25
|
+
type OpenNowListProps = {
|
|
26
|
+
venues: MenuVenuePreview[];
|
|
27
|
+
/** Prefix on the closing pill, e.g. "until". */
|
|
28
|
+
untilLabel: string;
|
|
29
|
+
/** Composed meta line per venue, keyed by place slug, e.g. "pub · Centru". */
|
|
30
|
+
metaLabels?: Record<string, string>;
|
|
31
|
+
/** What the kitchen is still sending out, keyed by place slug. */
|
|
32
|
+
summaryLabels?: Record<string, string>;
|
|
33
|
+
/** Composed price band per venue, keyed by place slug, e.g. "19—62 lei". */
|
|
34
|
+
priceBandLabels?: Record<string, string>;
|
|
35
|
+
linkComponent?: MenuLinkComponent;
|
|
36
|
+
className?: string;
|
|
37
|
+
};
|
|
38
|
+
declare function OpenNowList({ venues, untilLabel, metaLabels, summaryLabels, priceBandLabels, linkComponent: LinkComponent, className, }: OpenNowListProps): react_jsx_runtime.JSX.Element;
|
|
39
|
+
|
|
40
|
+
export { OpenNowList, type OpenNowListLinkProps, type OpenNowListProps };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Separator } from '../chunk-WSRWFA6K.js';
|
|
2
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
3
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
4
|
+
import { Clock, ChevronRight } from 'lucide-react';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function DefaultLink(_a) {
|
|
8
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
9
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
10
|
+
}
|
|
11
|
+
function OpenNowList({
|
|
12
|
+
venues,
|
|
13
|
+
untilLabel,
|
|
14
|
+
metaLabels,
|
|
15
|
+
summaryLabels,
|
|
16
|
+
priceBandLabels,
|
|
17
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
18
|
+
className
|
|
19
|
+
}) {
|
|
20
|
+
return /* @__PURE__ */ jsx("ul", { className: cn("flex flex-col", className), children: venues.map((venue, index) => {
|
|
21
|
+
const { place } = venue;
|
|
22
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
23
|
+
index > 0 && /* @__PURE__ */ jsx(Separator, {}),
|
|
24
|
+
/* @__PURE__ */ jsxs(
|
|
25
|
+
LinkComponent,
|
|
26
|
+
{
|
|
27
|
+
href: venue.href,
|
|
28
|
+
className: "flex flex-wrap items-center gap-x-3 gap-y-1.5 py-3 transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsxs("span", { className: "relative h-14 w-14 shrink-0 overflow-hidden rounded-xl bg-[repeating-linear-gradient(135deg,oklch(0.78_0.018_55)_0_8px,oklch(0.84_0.012_58)_8px_16px)]", children: [
|
|
31
|
+
place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
|
|
32
|
+
/* @__PURE__ */ jsx(
|
|
33
|
+
"img",
|
|
34
|
+
{
|
|
35
|
+
src: place.thumbnail_url,
|
|
36
|
+
alt: "",
|
|
37
|
+
className: "h-full w-full object-cover",
|
|
38
|
+
loading: "lazy"
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
/* @__PURE__ */ jsx(
|
|
42
|
+
"i",
|
|
43
|
+
{
|
|
44
|
+
"aria-hidden": true,
|
|
45
|
+
className: "absolute right-1 top-1 h-[7px] w-[7px] rounded-full bg-[var(--free,currentColor)] ring-2 ring-white/80"
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
50
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm font-bold leading-snug tracking-[-0.01em]", children: place.name }),
|
|
51
|
+
(metaLabels == null ? void 0 : metaLabels[place.slug]) && /* @__PURE__ */ jsx("span", { className: "mt-px block truncate text-[11.5px] text-muted-foreground", children: metaLabels[place.slug] })
|
|
52
|
+
] }),
|
|
53
|
+
(summaryLabels == null ? void 0 : summaryLabels[place.slug]) && /* @__PURE__ */ jsx("span", { className: "order-last w-full truncate text-[12.5px] text-muted-foreground lg:order-none lg:w-auto lg:flex-1", children: summaryLabels[place.slug] }),
|
|
54
|
+
(venue.closes_at_label || (priceBandLabels == null ? void 0 : priceBandLabels[place.slug])) && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 flex-col items-end gap-1 lg:flex-row lg:items-center lg:gap-3", children: [
|
|
55
|
+
(priceBandLabels == null ? void 0 : priceBandLabels[place.slug]) && /* @__PURE__ */ jsx("span", { className: "text-[12.5px] tabular-nums text-muted-foreground", children: priceBandLabels[place.slug] }),
|
|
56
|
+
venue.closes_at_label && /* @__PURE__ */ jsxs("span", { className: "inline-flex h-6 items-center gap-1 rounded-full bg-[var(--free-subtle,var(--accent-subtle))] px-2.5 text-[11.5px] font-semibold tabular-nums", children: [
|
|
57
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-[11px] w-[11px]", "aria-hidden": true }),
|
|
58
|
+
`${untilLabel} ${venue.closes_at_label}`
|
|
59
|
+
] })
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
] }, place.slug);
|
|
66
|
+
}) });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { OpenNowList };
|
|
@@ -47,6 +47,13 @@ type PlacePreview = {
|
|
|
47
47
|
hour: number;
|
|
48
48
|
score: number;
|
|
49
49
|
}>;
|
|
50
|
+
/**
|
|
51
|
+
* The review distillation. Drives every menu-hub surface that speaks for a
|
|
52
|
+
* venue — `<VenueSpotlight>`, `<RankedVenueList>`, `<SignatureLineList>` —
|
|
53
|
+
* because the signature dish, the pull-quotes and the graded dimensions all
|
|
54
|
+
* live here rather than on the menu itself.
|
|
55
|
+
*/
|
|
56
|
+
reviews_digest?: PlaceReviewsDigest;
|
|
50
57
|
type?: string;
|
|
51
58
|
business_status?: 'operational' | 'closed_temporarily' | 'closed_permanently';
|
|
52
59
|
phone?: string;
|
|
@@ -71,6 +78,110 @@ type PlaceSchedule = Record<WeekdayKey, PlaceHourBlock[]>;
|
|
|
71
78
|
type PlacePriceRange = {
|
|
72
79
|
price_range_low?: number;
|
|
73
80
|
price_range_high?: number;
|
|
81
|
+
/** Median price across the place's menu, in the local currency. */
|
|
82
|
+
median_price?: number;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Menu items are priced per size; a single-size item still ships one entry.
|
|
86
|
+
*
|
|
87
|
+
* Defined here rather than in `menu-types.ts` because `PlaceMustTry` carries
|
|
88
|
+
* it, and `menu-types.ts` already imports from this file — keeping the
|
|
89
|
+
* definition here leaves that import one-directional. `menu-types.ts`
|
|
90
|
+
* re-exports it, so menu code never reaches into place types for it.
|
|
91
|
+
*/
|
|
92
|
+
type MenuSizeAndPrice = {
|
|
93
|
+
/** Grams or millilitres. */
|
|
94
|
+
size: number;
|
|
95
|
+
price: number;
|
|
96
|
+
discounted_price?: number;
|
|
97
|
+
discount_percentage?: number;
|
|
98
|
+
};
|
|
99
|
+
/** A digest string with the reviews that back it. `review_ids.length` is the mention count. */
|
|
100
|
+
type CitedText = {
|
|
101
|
+
text: string;
|
|
102
|
+
review_ids?: string[];
|
|
103
|
+
};
|
|
104
|
+
/** A digest enum value with the reviews that back it. The value is a language-neutral code. */
|
|
105
|
+
type CitedValue = {
|
|
106
|
+
value: string;
|
|
107
|
+
review_ids?: string[];
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* One dish or drink the reviews single out.
|
|
111
|
+
*
|
|
112
|
+
* `mentions` and `score` come from the item census and are only served by the
|
|
113
|
+
* place DETAIL endpoint — a listing carries the item without them. Surfaces
|
|
114
|
+
* that need a count from a list fall back to `review_ids.length`, which is
|
|
115
|
+
* present in both. `strength`, `why` and `review_ids` are absent for census
|
|
116
|
+
* items the prose digest never mentioned, so none of them can be assumed.
|
|
117
|
+
*/
|
|
118
|
+
type PlaceMustTry = {
|
|
119
|
+
name: string;
|
|
120
|
+
why?: string;
|
|
121
|
+
strength?: 'signature' | 'frequent' | 'occasional';
|
|
122
|
+
/** Deep-link slug. Present only when the item matched a live menu row. */
|
|
123
|
+
slug?: string;
|
|
124
|
+
sizes_and_prices?: MenuSizeAndPrice[];
|
|
125
|
+
review_ids?: string[];
|
|
126
|
+
/** 0–100 census sentiment. Detail endpoint only; 3+ mentions required. */
|
|
127
|
+
score?: number;
|
|
128
|
+
/** Censused review mentions of this item. Detail endpoint only. */
|
|
129
|
+
mentions?: number;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Per-dimension 0–100 review scores.
|
|
133
|
+
*
|
|
134
|
+
* A dimension the reviews never mention is ABSENT, not zero — so a surface
|
|
135
|
+
* drawing these must render what it is given rather than a fixed row of seven.
|
|
136
|
+
* Exactly one of `food` / `coffee_quality` / `drinks_quality` is scored per
|
|
137
|
+
* place: a coffee bar has no food score and a restaurant has no coffee one.
|
|
138
|
+
*/
|
|
139
|
+
type PlaceGradeDimensions = {
|
|
140
|
+
food?: number;
|
|
141
|
+
coffee_quality?: number;
|
|
142
|
+
drinks_quality?: number;
|
|
143
|
+
value?: number;
|
|
144
|
+
service?: number;
|
|
145
|
+
cleanliness?: number;
|
|
146
|
+
ambiance?: number;
|
|
147
|
+
portion?: number;
|
|
148
|
+
consistency?: number;
|
|
149
|
+
};
|
|
150
|
+
type PlaceGrade = {
|
|
151
|
+
/** 1.0–10.0. */
|
|
152
|
+
overall?: number;
|
|
153
|
+
/**
|
|
154
|
+
* Backend recommendation to show the number at all. When false the caller
|
|
155
|
+
* should draw a "building confidence" state instead — the DS renders what it
|
|
156
|
+
* is told, it does not apply the rule.
|
|
157
|
+
*/
|
|
158
|
+
show_number?: boolean;
|
|
159
|
+
/** Locale-resolved band label, e.g. "Foarte bun". Already translated upstream. */
|
|
160
|
+
descriptor?: string;
|
|
161
|
+
confidence?: 'low' | 'medium' | 'high';
|
|
162
|
+
capped?: boolean;
|
|
163
|
+
dimensions?: PlaceGradeDimensions;
|
|
164
|
+
};
|
|
165
|
+
type PlaceReviewsDigest = {
|
|
166
|
+
grade?: PlaceGrade;
|
|
167
|
+
grade_note?: string;
|
|
168
|
+
tagline?: CitedText;
|
|
169
|
+
verdict?: CitedText;
|
|
170
|
+
caveat?: CitedText;
|
|
171
|
+
/** 1–3 standout positives. The pull-quotes on `<VenueSpotlight>`. */
|
|
172
|
+
highlights?: CitedText[];
|
|
173
|
+
must_try?: PlaceMustTry[];
|
|
174
|
+
/** Vibe codes (cozy, quiet, family_friendly, …). Localised by the caller. */
|
|
175
|
+
tags?: CitedValue[];
|
|
176
|
+
dietary?: CitedValue[];
|
|
177
|
+
/** small | moderate | generous */
|
|
178
|
+
portion_size?: CitedValue;
|
|
179
|
+
/** cheap | fair | mid_range | splurge */
|
|
180
|
+
value?: CitedValue;
|
|
181
|
+
/** quiet | moderate | loud */
|
|
182
|
+
noise_level?: CitedValue;
|
|
183
|
+
/** Text reviews behind the digest. */
|
|
184
|
+
review_count?: number;
|
|
74
185
|
};
|
|
75
186
|
/** Labels passed to `<PlaceCard>`. All translation happens in the caller. */
|
|
76
187
|
type PlaceCardLabels = {
|
|
@@ -151,4 +262,4 @@ type PlaceDetailsMobileLabels = PlaceAboutTabLabels & {
|
|
|
151
262
|
noImageAvailable: string;
|
|
152
263
|
};
|
|
153
264
|
|
|
154
|
-
export type { PlaceAboutTabLabels, PlaceCardLabels, PlaceDetailsMobileLabels, PlaceHourBlock, PlaceLinkKind, PlacePreview, PlacePriceRange, PlaceSchedule, PlaceScheduleWeekLabels, WeekdayKey };
|
|
265
|
+
export type { CitedText, CitedValue, MenuSizeAndPrice, PlaceAboutTabLabels, PlaceCardLabels, PlaceDetailsMobileLabels, PlaceGrade, PlaceGradeDimensions, PlaceHourBlock, PlaceLinkKind, PlaceMustTry, PlacePreview, PlacePriceRange, PlaceReviewsDigest, PlaceSchedule, PlaceScheduleWeekLabels, WeekdayKey };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { MenuVenuePreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './place-types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* RankedVenueList — venues ordered by how often reviews name their dishes.
|
|
8
|
+
*
|
|
9
|
+
* The answer to "best Italian in Cluj", and the reason it is defensible is the
|
|
10
|
+
* mention count sitting on every row: the order is evidence, not opinion. A
|
|
11
|
+
* caller that cannot supply mentions should use a different surface rather than
|
|
12
|
+
* this one with the column blank — an unexplained ranking is worse than none.
|
|
13
|
+
*
|
|
14
|
+
* The tab strip re-ranks in place and never navigates, so `activeTab` and
|
|
15
|
+
* `onTabChange` are controlled by the caller. Omit `tabs` entirely and the
|
|
16
|
+
* header is just a heading.
|
|
17
|
+
*
|
|
18
|
+
* No directive: without `tabs` the list renders on the server, which is the
|
|
19
|
+
* common case on a hub.
|
|
20
|
+
*/
|
|
21
|
+
/** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
|
|
22
|
+
type RankedVenueListLinkProps = MenuLinkProps;
|
|
23
|
+
type RankedVenueListTab = {
|
|
24
|
+
slug: string;
|
|
25
|
+
label: string;
|
|
26
|
+
};
|
|
27
|
+
type RankedVenueListProps = {
|
|
28
|
+
venues: MenuVenuePreview[];
|
|
29
|
+
/** Section heading, e.g. "The kitchens Cluj rates for Italian". */
|
|
30
|
+
heading?: string;
|
|
31
|
+
/** The basis of the order, e.g. "by how often reviews name their dishes". */
|
|
32
|
+
caption?: string;
|
|
33
|
+
tabs?: RankedVenueListTab[];
|
|
34
|
+
activeTab?: string;
|
|
35
|
+
onTabChange?: (slug: string) => void;
|
|
36
|
+
/** Composed price band per venue, keyed by place slug, e.g. "38—84". */
|
|
37
|
+
priceBandLabels?: Record<string, string>;
|
|
38
|
+
/**
|
|
39
|
+
* The app's own grade per venue slug, already formatted ("8.7"). Shown in
|
|
40
|
+
* place of the star rating when present — the star is the third-party
|
|
41
|
+
* fallback for a venue the reviews have not graded yet.
|
|
42
|
+
*/
|
|
43
|
+
gradeLabels?: Record<string, string>;
|
|
44
|
+
linkComponent?: MenuLinkComponent;
|
|
45
|
+
className?: string;
|
|
46
|
+
};
|
|
47
|
+
declare function RankedVenueList({ venues, heading, caption, tabs, activeTab, onTabChange, priceBandLabels, gradeLabels, linkComponent: LinkComponent, className, }: RankedVenueListProps): react_jsx_runtime.JSX.Element;
|
|
48
|
+
|
|
49
|
+
export { RankedVenueList, type RankedVenueListLinkProps, type RankedVenueListProps, type RankedVenueListTab };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Separator } from '../chunk-WSRWFA6K.js';
|
|
2
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
3
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
4
|
+
import { MessageSquare, Star, ChevronRight } from 'lucide-react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function DefaultLink(_a) {
|
|
8
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
9
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
10
|
+
}
|
|
11
|
+
function RankedVenueList({
|
|
12
|
+
venues,
|
|
13
|
+
heading,
|
|
14
|
+
caption,
|
|
15
|
+
tabs,
|
|
16
|
+
activeTab,
|
|
17
|
+
onTabChange,
|
|
18
|
+
priceBandLabels,
|
|
19
|
+
gradeLabels,
|
|
20
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
21
|
+
className
|
|
22
|
+
}) {
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("overflow-hidden rounded-[14px] border border-border bg-card", className), children: [
|
|
24
|
+
(heading || tabs) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border p-3.5", children: [
|
|
25
|
+
heading && /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-bold leading-tight tracking-[-0.01em]", children: heading }),
|
|
26
|
+
caption && /* @__PURE__ */ jsx("span", { className: "text-[12.5px] text-muted-foreground", children: caption }),
|
|
27
|
+
tabs && tabs.length > 0 && /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap gap-1.5", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
28
|
+
"button",
|
|
29
|
+
{
|
|
30
|
+
type: "button",
|
|
31
|
+
"aria-pressed": tab.slug === activeTab,
|
|
32
|
+
onClick: () => onTabChange == null ? void 0 : onTabChange(tab.slug),
|
|
33
|
+
className: cn(
|
|
34
|
+
"inline-flex h-7 items-center rounded-full border px-2.5 text-xs font-semibold transition-colors",
|
|
35
|
+
tab.slug === activeTab ? "border-accent bg-accent-subtle text-accent" : "border-border text-muted-foreground hover:bg-muted/50"
|
|
36
|
+
),
|
|
37
|
+
children: tab.label
|
|
38
|
+
},
|
|
39
|
+
tab.slug
|
|
40
|
+
)) })
|
|
41
|
+
] }),
|
|
42
|
+
/* @__PURE__ */ jsx("ol", { children: venues.map((venue, index) => {
|
|
43
|
+
var _a;
|
|
44
|
+
const { place, signature } = venue;
|
|
45
|
+
const mentions = (_a = signature == null ? void 0 : signature.item.mentions) != null ? _a : 0;
|
|
46
|
+
return /* @__PURE__ */ jsxs("li", { children: [
|
|
47
|
+
index > 0 && /* @__PURE__ */ jsx(Separator, {}),
|
|
48
|
+
/* @__PURE__ */ jsxs(
|
|
49
|
+
LinkComponent,
|
|
50
|
+
{
|
|
51
|
+
href: venue.href,
|
|
52
|
+
className: "flex items-center gap-3 p-3 transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsx("span", { className: "w-4 shrink-0 text-center text-[13px] font-extrabold tabular-nums text-accent", children: index + 1 }),
|
|
55
|
+
/* @__PURE__ */ jsx("span", { className: "h-12 w-12 shrink-0 overflow-hidden rounded-xl bg-[repeating-linear-gradient(135deg,oklch(0.78_0.018_55)_0_8px,oklch(0.84_0.012_58)_8px_16px)]", children: place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
"img",
|
|
58
|
+
{
|
|
59
|
+
src: place.thumbnail_url,
|
|
60
|
+
alt: "",
|
|
61
|
+
className: "h-full w-full object-cover",
|
|
62
|
+
loading: "lazy"
|
|
63
|
+
}
|
|
64
|
+
) }),
|
|
65
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
66
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm font-bold leading-snug tracking-[-0.01em]", children: place.name }),
|
|
67
|
+
(signature == null ? void 0 : signature.item.name) && /* @__PURE__ */ jsx("span", { className: "mt-px block truncate font-serif text-xs italic text-muted-foreground", children: `\u201C${signature.item.name}\u201D` })
|
|
68
|
+
] }),
|
|
69
|
+
mentions > 0 && /* @__PURE__ */ jsxs("span", { className: "hidden shrink-0 items-center gap-1 text-[11.5px] tabular-nums text-muted-foreground sm:inline-flex", children: [
|
|
70
|
+
/* @__PURE__ */ jsx(MessageSquare, { className: "h-[11px] w-[11px]", "aria-hidden": true }),
|
|
71
|
+
mentions
|
|
72
|
+
] }),
|
|
73
|
+
(gradeLabels == null ? void 0 : gradeLabels[place.slug]) ? /* @__PURE__ */ jsx("span", { className: "inline-flex h-6 shrink-0 items-center rounded-md bg-foreground px-1.5 text-[12px] font-extrabold tabular-nums text-background", children: gradeLabels[place.slug] }) : place.rating && /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-[12.5px] font-semibold tabular-nums", children: [
|
|
74
|
+
/* @__PURE__ */ jsx(Star, { className: "h-3 w-3", "aria-hidden": true }),
|
|
75
|
+
place.rating.rating
|
|
76
|
+
] }),
|
|
77
|
+
(priceBandLabels == null ? void 0 : priceBandLabels[place.slug]) && /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-[12.5px] tabular-nums text-muted-foreground sm:block", children: priceBandLabels[place.slug] }),
|
|
78
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true })
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
] }, place.slug);
|
|
83
|
+
}) })
|
|
84
|
+
] });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { RankedVenueList };
|