@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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Separator } from '../chunk-WSRWFA6K.js';
|
|
2
|
-
import { Tag } from '../chunk-ZDAHKI3A.js';
|
|
3
2
|
import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
|
|
3
|
+
import { Tag } from '../chunk-ZDAHKI3A.js';
|
|
4
4
|
import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
|
|
5
5
|
import { Skeleton } from '../chunk-QXHMPJ35.js';
|
|
6
6
|
import { buttonVariants } from '../chunk-H3BV4VAG.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Separator } from '../chunk-WSRWFA6K.js';
|
|
2
2
|
import { TagGroup } from '../chunk-6XMYEZVR.js';
|
|
3
|
-
import { Tag } from '../chunk-ZDAHKI3A.js';
|
|
4
3
|
import { resolveEventEntryFee } from '../chunk-DNSXMHYX.js';
|
|
4
|
+
import { Tag } from '../chunk-ZDAHKI3A.js';
|
|
5
5
|
import { EventPosterFallback } from '../chunk-FDITZ2VM.js';
|
|
6
6
|
import { Skeleton } from '../chunk-QXHMPJ35.js';
|
|
7
7
|
import { cn } from '../chunk-FEK5XGZW.js';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* HubSurface — the surface a hub section sits on BELOW `lg`.
|
|
5
|
+
*
|
|
6
|
+
* On a phone a hub is an app screen, not a page: each section is a white card
|
|
7
|
+
* on the tinted ground, 18px radius, one card shadow and no border, with 14px
|
|
8
|
+
* of inset. Hairlines live inside a card, never across the page. From `lg` up
|
|
9
|
+
* the card dissolves — no fill, no radius, no inset — and the sections sit flat
|
|
10
|
+
* the way the desktop design draws them. One element, two looks; the section
|
|
11
|
+
* markup inside is identical at every width.
|
|
12
|
+
*
|
|
13
|
+
* `flush` drops the inset so a section whose content bleeds to the card edge —
|
|
14
|
+
* a snap rail, a full-width photograph — can pad its own header and let the
|
|
15
|
+
* rest run.
|
|
16
|
+
*
|
|
17
|
+
* Generalised out of the events hub's `EventsHubCard`, which had the same job
|
|
18
|
+
* and lived in the app. Nothing here is menu- or event-shaped.
|
|
19
|
+
*/
|
|
20
|
+
type HubSurfaceProps = {
|
|
21
|
+
flush?: boolean;
|
|
22
|
+
className?: string;
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
};
|
|
25
|
+
declare function HubSurface({ flush, className, children }: HubSurfaceProps): react_jsx_runtime.JSX.Element;
|
|
26
|
+
|
|
27
|
+
export { HubSurface, type HubSurfaceProps };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
2
|
+
import '../chunk-3GWWZKX7.js';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function HubSurface({ flush, className, children }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
"section",
|
|
8
|
+
{
|
|
9
|
+
className: cn(
|
|
10
|
+
"rounded-[18px] border border-border bg-card shadow-none",
|
|
11
|
+
flush ? "overflow-hidden" : "px-3.5 py-4",
|
|
12
|
+
"lg:overflow-visible lg:rounded-none lg:bg-transparent lg:p-0 lg:shadow-none",
|
|
13
|
+
className
|
|
14
|
+
),
|
|
15
|
+
children
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { HubSurface };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { MenuItemLabels } from './menu-item.js';
|
|
3
|
+
import { MenuFamilyPreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
4
|
+
import 'react';
|
|
5
|
+
import './place-types.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* MenuFamilyCard — a browsable grouping of venues: a cuisine on the dishes tab,
|
|
9
|
+
* a drink family on the drinks tab.
|
|
10
|
+
*
|
|
11
|
+
* ## The count does the scanning, the item answers the question
|
|
12
|
+
*
|
|
13
|
+
* The card is two halves with two jobs. The dark panel is for scanning a row of
|
|
14
|
+
* four: one number, one name, and the groups the family consists of, over a
|
|
15
|
+
* mosaic of its venues. The white half underneath answers what you would
|
|
16
|
+
* actually order — the production `<MenuItem>`, with its price, portion and
|
|
17
|
+
* mention count, so the recommendation is the review census rather than an
|
|
18
|
+
* editorial pick.
|
|
19
|
+
*
|
|
20
|
+
* ## Both halves count VENUES
|
|
21
|
+
*
|
|
22
|
+
* Cuisine exists on places and nowhere else — no listing endpoint will tell you
|
|
23
|
+
* how many dishes are Italian — so a card counting dishes cannot be built and
|
|
24
|
+
* this one does not try. The groups line is a plain list without counts: at
|
|
25
|
+
* four cards across, four chips each carrying a number turned the panel into a
|
|
26
|
+
* table, and the numbers were kitchens, which nobody reads a chip as.
|
|
27
|
+
*
|
|
28
|
+
* The mosaic holds VENUE photographs, which are real. It dims them hard — this
|
|
29
|
+
* is a ground for a number, not a gallery, and at this size a legible photo
|
|
30
|
+
* competes with the count for the same glance.
|
|
31
|
+
*/
|
|
32
|
+
/** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
|
|
33
|
+
type MenuFamilyCardLinkProps = MenuLinkProps;
|
|
34
|
+
type MenuFamilyCardLabels = {
|
|
35
|
+
/** Unit under the count, e.g. "KITCHENS". */
|
|
36
|
+
venueUnit: string;
|
|
37
|
+
/** Kicker over the featured item, e.g. "MOST NAMED". */
|
|
38
|
+
mostNamed: string;
|
|
39
|
+
/** Footer caption, e.g. "kitchen price band". */
|
|
40
|
+
priceBand: string;
|
|
41
|
+
};
|
|
42
|
+
type MenuFamilyCardProps = {
|
|
43
|
+
family: MenuFamilyPreview;
|
|
44
|
+
labels: MenuFamilyCardLabels;
|
|
45
|
+
/** Passed through to the featured `<MenuItem>`. */
|
|
46
|
+
itemLabels: MenuItemLabels;
|
|
47
|
+
formatPrice: (amount: number) => string;
|
|
48
|
+
formatCount?: (value: number) => string;
|
|
49
|
+
href: string;
|
|
50
|
+
/** Finished CSS colour for the featured item's category mark. */
|
|
51
|
+
hue?: string;
|
|
52
|
+
/** Composed price band, e.g. "32–84 lei". */
|
|
53
|
+
priceBandLabel?: string;
|
|
54
|
+
linkComponent?: MenuLinkComponent;
|
|
55
|
+
className?: string;
|
|
56
|
+
};
|
|
57
|
+
declare function MenuFamilyCard({ family, labels, itemLabels, formatPrice, formatCount, href, hue, priceBandLabel, linkComponent: LinkComponent, className, }: MenuFamilyCardProps): react_jsx_runtime.JSX.Element;
|
|
58
|
+
|
|
59
|
+
export { MenuFamilyCard, type MenuFamilyCardLabels, type MenuFamilyCardLinkProps, type MenuFamilyCardProps };
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { MenuItem } from '../chunk-TH6W2PWG.js';
|
|
2
|
+
import '../chunk-Q6QH52LJ.js';
|
|
3
|
+
import '../chunk-UKBNQO6Q.js';
|
|
4
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
5
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
6
|
+
import { Wallet } from 'lucide-react';
|
|
7
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
function DefaultLink(_a) {
|
|
10
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
11
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
12
|
+
}
|
|
13
|
+
var MOSAIC_CELLS = 4;
|
|
14
|
+
function MenuFamilyCard({
|
|
15
|
+
family,
|
|
16
|
+
labels,
|
|
17
|
+
itemLabels,
|
|
18
|
+
formatPrice,
|
|
19
|
+
formatCount,
|
|
20
|
+
href,
|
|
21
|
+
hue,
|
|
22
|
+
priceBandLabel,
|
|
23
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
24
|
+
className
|
|
25
|
+
}) {
|
|
26
|
+
var _a, _b, _c;
|
|
27
|
+
const photos = (_a = family.venue_photo_urls) != null ? _a : [];
|
|
28
|
+
const groups = ((_b = family.chips) != null ? _b : []).map((chip) => chip.label);
|
|
29
|
+
return /* @__PURE__ */ jsxs(
|
|
30
|
+
"div",
|
|
31
|
+
{
|
|
32
|
+
className: cn(
|
|
33
|
+
"flex h-full flex-col overflow-hidden rounded-[14px] border border-border bg-card",
|
|
34
|
+
className
|
|
35
|
+
),
|
|
36
|
+
children: [
|
|
37
|
+
/* @__PURE__ */ jsxs(
|
|
38
|
+
LinkComponent,
|
|
39
|
+
{
|
|
40
|
+
href,
|
|
41
|
+
className: "relative block overflow-hidden bg-[oklch(0.21_0.012_55)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
|
|
42
|
+
children: [
|
|
43
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "absolute inset-0 grid grid-cols-2 grid-rows-2 gap-px", children: Array.from(
|
|
44
|
+
{ length: MOSAIC_CELLS },
|
|
45
|
+
(_, cell) => photos.length ? (
|
|
46
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
"img",
|
|
49
|
+
{
|
|
50
|
+
src: photos[cell % photos.length],
|
|
51
|
+
alt: "",
|
|
52
|
+
loading: "lazy",
|
|
53
|
+
decoding: "async",
|
|
54
|
+
className: "h-full w-full object-cover"
|
|
55
|
+
},
|
|
56
|
+
cell
|
|
57
|
+
)
|
|
58
|
+
) : /* @__PURE__ */ jsx(
|
|
59
|
+
"span",
|
|
60
|
+
{
|
|
61
|
+
className: "bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.18]"
|
|
62
|
+
},
|
|
63
|
+
cell
|
|
64
|
+
)
|
|
65
|
+
) }),
|
|
66
|
+
hue && /* @__PURE__ */ jsx(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
"aria-hidden": true,
|
|
70
|
+
className: "absolute inset-0 opacity-90 mix-blend-color",
|
|
71
|
+
style: { backgroundColor: hue }
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
/* @__PURE__ */ jsx(
|
|
75
|
+
"span",
|
|
76
|
+
{
|
|
77
|
+
"aria-hidden": true,
|
|
78
|
+
className: "absolute inset-0 bg-[linear-gradient(180deg,oklch(0.16_0.012_55_/_0.9)_0%,oklch(0.16_0.012_55_/_0.62)_45%,oklch(0.16_0.012_55_/_0.94)_100%)]"
|
|
79
|
+
}
|
|
80
|
+
),
|
|
81
|
+
/* @__PURE__ */ jsxs("span", { className: "relative flex min-h-[260px] flex-col justify-between p-3.5 text-white", children: [
|
|
82
|
+
/* @__PURE__ */ jsxs("span", { className: "block", children: [
|
|
83
|
+
/* @__PURE__ */ jsx("b", { className: "block text-[34px] font-extrabold leading-none tracking-[-0.03em] tabular-nums", children: family.venue_count }),
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "mt-1 block text-[9.5px] font-bold tracking-[0.12em] opacity-80", children: labels.venueUnit })
|
|
85
|
+
] }),
|
|
86
|
+
/* @__PURE__ */ jsxs("span", { className: "block", children: [
|
|
87
|
+
/* @__PURE__ */ jsx("span", { className: "block text-[17px] font-bold tracking-[-0.01em]", children: family.name }),
|
|
88
|
+
groups.length > 0 && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate text-[11.5px] opacity-75", children: groups.join(" \xB7 ") }),
|
|
89
|
+
priceBandLabel && /* @__PURE__ */ jsxs("span", { className: "mt-2 inline-flex items-center gap-1.5 rounded-full bg-white/15 px-2.5 py-1 text-[11px] backdrop-blur-[2px]", children: [
|
|
90
|
+
/* @__PURE__ */ jsx(Wallet, { className: "h-3 w-3 shrink-0", "aria-hidden": true }),
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-80", children: labels.priceBand }),
|
|
92
|
+
/* @__PURE__ */ jsx("b", { className: "font-semibold tabular-nums", children: priceBandLabel })
|
|
93
|
+
] })
|
|
94
|
+
] })
|
|
95
|
+
] })
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
family.signature && /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col px-2 pb-2 pt-3", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: "px-2 text-[9.5px] font-bold uppercase tracking-[0.09em] text-muted-foreground", children: labels.mostNamed }),
|
|
101
|
+
/* @__PURE__ */ jsx(
|
|
102
|
+
MenuItem,
|
|
103
|
+
{
|
|
104
|
+
className: "min-h-0",
|
|
105
|
+
item: family.signature.item,
|
|
106
|
+
labels: itemLabels,
|
|
107
|
+
formatPrice,
|
|
108
|
+
formatCount,
|
|
109
|
+
markGradient: hue,
|
|
110
|
+
subtitle: family.signature.why,
|
|
111
|
+
href: (_c = family.signature.href) != null ? _c : href,
|
|
112
|
+
linkComponent: LinkComponent
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
] })
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export { MenuFamilyCard };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MenuField — the photoless cover surface for a dish or drink.
|
|
5
|
+
*
|
|
6
|
+
* Menu-item photography is AI-generated and never shown, so the item's own name
|
|
7
|
+
* repeats across a shallow diagonal over a gradient. The type IS the cover.
|
|
8
|
+
*
|
|
9
|
+
* The rows fade SYMMETRICALLY out from a single strong row at the centre. That
|
|
10
|
+
* shape is what keeps the type reading as texture in the colour rather than as
|
|
11
|
+
* a watermark stamped on top of it: the eye lands on one legible line and the
|
|
12
|
+
* rest falls away in both directions. A uniform wash — or a random per-row
|
|
13
|
+
* alpha — reads as noise at every alpha high enough to see.
|
|
14
|
+
*
|
|
15
|
+
* The alpha curve is IDENTICAL for every hue. Scaling it per hue to even out
|
|
16
|
+
* how white type reads over different grounds also moves where rows fall below
|
|
17
|
+
* visibility, so a damped hue lost rows and one card showed five bands where
|
|
18
|
+
* its neighbour showed three. Correct contrast in the `gradient` you pass, not
|
|
19
|
+
* in the curve.
|
|
20
|
+
*
|
|
21
|
+
* ## Why `gradient` is a prop
|
|
22
|
+
*
|
|
23
|
+
* Resolving a category slug to a hue needs the app's category enums, which the
|
|
24
|
+
* DS does not take. Callers pass a finished CSS gradient — fe from its own
|
|
25
|
+
* category-colour table, post-generator from whatever it is filming. The DS
|
|
26
|
+
* owns the typography and the falloff; the consumer owns the palette.
|
|
27
|
+
*
|
|
28
|
+
* Server-safe: no state, no effects. The row geometry is a 13-iteration loop
|
|
29
|
+
* computed on every render, which is cheaper than the hook that would let it be
|
|
30
|
+
* skipped.
|
|
31
|
+
*/
|
|
32
|
+
type MenuFieldProps = {
|
|
33
|
+
/** Item name — rendered as the repeating ghost. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** CSS `background-image` value. See the note above on why this is injected. */
|
|
36
|
+
gradient: string;
|
|
37
|
+
/**
|
|
38
|
+
* Deterministic stagger seed. Pass the PARENT CONCEPT SLUG so one dish breaks
|
|
39
|
+
* its lines identically at every venue and across re-sorts. Defaults to
|
|
40
|
+
* `name`, which is stable but shared by every venue's version of a dish.
|
|
41
|
+
*/
|
|
42
|
+
seedKey?: string;
|
|
43
|
+
/**
|
|
44
|
+
* `repeat` (default) — the name as texture, thirteen fading rows. Right at
|
|
45
|
+
* swatch and thumbnail sizes, where any single line would be unreadable.
|
|
46
|
+
*
|
|
47
|
+
* `display` — the name ONCE, set large and bled off the edges. Right when the
|
|
48
|
+
* tile is the primary object on its row and the name is the thing being sold;
|
|
49
|
+
* the repeat treatment at that size reads as wallpaper rather than as a dish.
|
|
50
|
+
* Long names clip on purpose — the caption underneath carries the full text.
|
|
51
|
+
*/
|
|
52
|
+
variant?: "repeat" | "display";
|
|
53
|
+
className?: string;
|
|
54
|
+
};
|
|
55
|
+
declare function MenuField({ name, gradient, seedKey, variant, className, }: MenuFieldProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
export { MenuField, type MenuFieldProps };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MenuHubHero — the claim that opens a menu hub, proven by live figures.
|
|
5
|
+
*
|
|
6
|
+
* A statement rather than a count ("Nobody should have to walk in blind"),
|
|
7
|
+
* over a dark ground, with the catalogue's real numbers under it and one proof
|
|
8
|
+
* line that demonstrates what the catalogue can do that a single menu cannot.
|
|
9
|
+
*
|
|
10
|
+
* ## Why the ground is a texture and not a photograph
|
|
11
|
+
*
|
|
12
|
+
* Menu-item photography is AI-generated and never shown, and one venue
|
|
13
|
+
* photograph behind a claim about every venue in the city argues the wrong
|
|
14
|
+
* thing. The tile grid reads as "many menus" without being any of them.
|
|
15
|
+
* Callers that DO have a real, licensed image for the slot can pass
|
|
16
|
+
* `backgroundUrl` — venue photography, never a plate.
|
|
17
|
+
*
|
|
18
|
+
* ## Figures
|
|
19
|
+
*
|
|
20
|
+
* `figures` is a list, not four fixed slots: the drinks tab has different ones
|
|
21
|
+
* and the count changes when a number turns out to have nothing behind it.
|
|
22
|
+
* Every value is a pre-formatted string because thousands separators are
|
|
23
|
+
* locale-dependent and the DS owns no locale.
|
|
24
|
+
*
|
|
25
|
+
* Server-safe.
|
|
26
|
+
*/
|
|
27
|
+
type MenuHubHeroFigure = {
|
|
28
|
+
/** Pre-formatted, e.g. "19,062". */
|
|
29
|
+
value: string;
|
|
30
|
+
label: string;
|
|
31
|
+
};
|
|
32
|
+
type MenuHubHeroProps = {
|
|
33
|
+
/** Small caps line above the headline, e.g. "397 MENUS IN CLUJ-NAPOCA, READ IN FULL". */
|
|
34
|
+
kicker?: string;
|
|
35
|
+
/** Icon for the kicker, resolved by the caller. */
|
|
36
|
+
kickerIcon?: React.ReactNode;
|
|
37
|
+
headline: string;
|
|
38
|
+
body?: string;
|
|
39
|
+
figures?: MenuHubHeroFigure[];
|
|
40
|
+
/**
|
|
41
|
+
* The proof line — one demonstrated fact, e.g. "A margherita: 87 kitchens
|
|
42
|
+
* make one, 10 to 120 lei". Composed by the caller; clause order is
|
|
43
|
+
* locale-dependent.
|
|
44
|
+
*/
|
|
45
|
+
proof?: React.ReactNode;
|
|
46
|
+
proofIcon?: React.ReactNode;
|
|
47
|
+
/** The action beside the proof line. Pass a rendered link or button. */
|
|
48
|
+
action?: React.ReactNode;
|
|
49
|
+
/** Real, licensed photograph for the ground. Venue imagery only — see above. */
|
|
50
|
+
backgroundUrl?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Tiles for the mosaic ground — generic, cinematic, licensed stills, served
|
|
53
|
+
* from the consumer's own origin.
|
|
54
|
+
*
|
|
55
|
+
* A mosaic rather than one photograph because the hero's claim is about EVERY
|
|
56
|
+
* kitchen in the city: a single image makes it about one of them. Given fewer
|
|
57
|
+
* tiles than cells the list repeats, so a caller can ship six and still fill
|
|
58
|
+
* a 1440px hero.
|
|
59
|
+
*/
|
|
60
|
+
tileUrls?: string[];
|
|
61
|
+
className?: string;
|
|
62
|
+
};
|
|
63
|
+
declare function MenuHubHero({ kicker, kickerIcon, headline, body, figures, proof, proofIcon, action, backgroundUrl, tileUrls, className, }: MenuHubHeroProps): react_jsx_runtime.JSX.Element;
|
|
64
|
+
|
|
65
|
+
export { MenuHubHero, type MenuHubHeroFigure, type MenuHubHeroProps };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
2
|
+
import '../chunk-3GWWZKX7.js';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
var TEXTURE_CELLS = 28;
|
|
6
|
+
function MenuHubHero({
|
|
7
|
+
kicker,
|
|
8
|
+
kickerIcon,
|
|
9
|
+
headline,
|
|
10
|
+
body,
|
|
11
|
+
figures,
|
|
12
|
+
proof,
|
|
13
|
+
proofIcon,
|
|
14
|
+
action,
|
|
15
|
+
backgroundUrl,
|
|
16
|
+
tileUrls,
|
|
17
|
+
className
|
|
18
|
+
}) {
|
|
19
|
+
return /* @__PURE__ */ jsxs(
|
|
20
|
+
"div",
|
|
21
|
+
{
|
|
22
|
+
className: cn(
|
|
23
|
+
"relative flex min-h-[260px] overflow-hidden rounded-[20px] bg-[oklch(0.21_0.012_55)] lg:min-h-[440px]",
|
|
24
|
+
className
|
|
25
|
+
),
|
|
26
|
+
children: [
|
|
27
|
+
backgroundUrl ? (
|
|
28
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
29
|
+
/* @__PURE__ */ jsx(
|
|
30
|
+
"img",
|
|
31
|
+
{
|
|
32
|
+
src: backgroundUrl,
|
|
33
|
+
alt: "",
|
|
34
|
+
"aria-hidden": true,
|
|
35
|
+
className: "absolute inset-0 h-full w-full object-cover opacity-[0.42]"
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
) : /* @__PURE__ */ jsx(
|
|
39
|
+
"div",
|
|
40
|
+
{
|
|
41
|
+
"aria-hidden": true,
|
|
42
|
+
className: "absolute inset-0 grid auto-rows-fr grid-cols-4 gap-0.5 lg:grid-cols-7",
|
|
43
|
+
children: Array.from({ length: TEXTURE_CELLS }, (_, cell) => {
|
|
44
|
+
const url = (tileUrls == null ? void 0 : tileUrls.length) ? tileUrls[cell % tileUrls.length] : void 0;
|
|
45
|
+
return url ? (
|
|
46
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
"img",
|
|
49
|
+
{
|
|
50
|
+
src: url,
|
|
51
|
+
alt: "",
|
|
52
|
+
loading: "lazy",
|
|
53
|
+
decoding: "async",
|
|
54
|
+
className: "h-full w-full object-cover"
|
|
55
|
+
},
|
|
56
|
+
cell
|
|
57
|
+
)
|
|
58
|
+
) : /* @__PURE__ */ jsx(
|
|
59
|
+
"div",
|
|
60
|
+
{
|
|
61
|
+
className: "bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.42]"
|
|
62
|
+
},
|
|
63
|
+
cell
|
|
64
|
+
);
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
"aria-hidden": true,
|
|
72
|
+
className: cn(
|
|
73
|
+
"absolute inset-0",
|
|
74
|
+
(tileUrls == null ? void 0 : tileUrls.length) || backgroundUrl ? "bg-[linear-gradient(100deg,oklch(0.16_0.012_55_/_0.96)_0%,oklch(0.16_0.012_55_/_0.88)_52%,oklch(0.16_0.012_55_/_0.72)_100%)]" : "bg-[linear-gradient(100deg,oklch(0.18_0.012_55_/_0.95)_0%,oklch(0.18_0.012_55_/_0.72)_52%,oklch(0.18_0.012_55_/_0.3)_100%)]"
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex w-full flex-col justify-center gap-3 p-5 text-white lg:gap-4 lg:px-12 lg:py-11", children: [
|
|
79
|
+
kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex w-max items-center gap-[7px] rounded-full bg-white/15 px-3 py-1.5 text-[11px] font-extrabold tracking-[0.11em]", children: [
|
|
80
|
+
kickerIcon,
|
|
81
|
+
kicker
|
|
82
|
+
] }),
|
|
83
|
+
/* @__PURE__ */ jsx("h1", { className: "max-w-[640px] font-serif text-[32px] font-semibold leading-[1.04] tracking-[-0.025em] lg:text-[50px]", children: headline }),
|
|
84
|
+
body && /* @__PURE__ */ jsx("p", { className: "hidden max-w-[540px] text-sm leading-[1.6] opacity-[0.84] lg:block lg:text-[15.5px]", children: body }),
|
|
85
|
+
figures && figures.length > 0 && // Two by two on the phone: four figures in one row wrapped to three and an orphan.
|
|
86
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-x-6 gap-y-3 pt-3 lg:flex lg:flex-wrap lg:gap-x-8 lg:gap-y-4 lg:pt-1.5", children: figures.map((figure) => /* @__PURE__ */ jsxs("div", { children: [
|
|
87
|
+
/* @__PURE__ */ jsx("div", { className: "text-2xl font-black leading-none tracking-[-0.03em] tabular-nums lg:text-[32px]", children: figure.value }),
|
|
88
|
+
/* @__PURE__ */ jsx("div", { className: "mt-[5px] text-xs opacity-[0.72]", children: figure.label })
|
|
89
|
+
] }, figure.label)) }),
|
|
90
|
+
(proof || action) && /* @__PURE__ */ jsxs("div", { className: "mt-auto flex flex-wrap items-center gap-3 pt-5", children: [
|
|
91
|
+
proof && /* @__PURE__ */ jsxs("span", { className: "inline-flex min-h-11 items-center gap-2.5 rounded-full bg-white/10 px-4 py-2 text-[13px] backdrop-blur-[6px]", children: [
|
|
92
|
+
proofIcon,
|
|
93
|
+
proof
|
|
94
|
+
] }),
|
|
95
|
+
action
|
|
96
|
+
] })
|
|
97
|
+
] })
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export { MenuHubHero };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { MenuIntentTile, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
|
|
3
|
+
import 'react';
|
|
4
|
+
import './place-types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* MenuIntentTiles — the sentence people arrive with, as six coloured tiles.
|
|
8
|
+
*
|
|
9
|
+
* An intent is a family plus a constraint: Italian *tonight*, burgers, coffee.
|
|
10
|
+
* Each tile is a destination, so `href` is required on the view model — a
|
|
11
|
+
* category page that does not exist is filtered out by the caller rather than
|
|
12
|
+
* rendered as a tile that links nowhere. Roughly thirty-two of thirty-eight
|
|
13
|
+
* cuisines have a page, so that case is normal, not exceptional.
|
|
14
|
+
*
|
|
15
|
+
* The icon is a watermark bleeding off the bottom-right corner at low opacity,
|
|
16
|
+
* not a badge: it gives the tile a silhouette to be recognised by in a rail
|
|
17
|
+
* without competing with the label. It is `aria-hidden` — the label already
|
|
18
|
+
* says what the tile is.
|
|
19
|
+
*
|
|
20
|
+
* Server-safe.
|
|
21
|
+
*/
|
|
22
|
+
/** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
|
|
23
|
+
type MenuIntentTilesLinkProps = MenuLinkProps;
|
|
24
|
+
type MenuIntentTilesProps = {
|
|
25
|
+
tiles: MenuIntentTile[];
|
|
26
|
+
linkComponent?: MenuLinkComponent;
|
|
27
|
+
className?: string;
|
|
28
|
+
};
|
|
29
|
+
declare function MenuIntentTiles({ tiles, linkComponent: LinkComponent, className, }: MenuIntentTilesProps): react_jsx_runtime.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { MenuIntentTiles, type MenuIntentTilesLinkProps, type MenuIntentTilesProps };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { cn } from '../chunk-FEK5XGZW.js';
|
|
2
|
+
import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
|
|
3
|
+
import { ChevronRight } from 'lucide-react';
|
|
4
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
function DefaultLink(_a) {
|
|
7
|
+
var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
|
|
8
|
+
return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
|
|
9
|
+
}
|
|
10
|
+
function MenuIntentTiles({
|
|
11
|
+
tiles,
|
|
12
|
+
linkComponent: LinkComponent = DefaultLink,
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
className: cn(
|
|
19
|
+
"grid grid-cols-2 gap-3 lg:grid-cols-6 lg:gap-3.5",
|
|
20
|
+
className
|
|
21
|
+
),
|
|
22
|
+
children: tiles.map((tile) => /* @__PURE__ */ jsxs(
|
|
23
|
+
LinkComponent,
|
|
24
|
+
{
|
|
25
|
+
href: tile.href,
|
|
26
|
+
className: "relative flex min-h-[110px] flex-col overflow-hidden rounded-2xl bg-muted p-3.5 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 lg:min-h-[124px] lg:px-3.5 lg:pb-3.5 lg:pt-[15px]",
|
|
27
|
+
style: tile.gradient ? { backgroundImage: tile.gradient } : void 0,
|
|
28
|
+
children: [
|
|
29
|
+
tile.icon && /* @__PURE__ */ jsx(
|
|
30
|
+
"span",
|
|
31
|
+
{
|
|
32
|
+
"aria-hidden": true,
|
|
33
|
+
className: "pointer-events-none absolute -bottom-2.5 -right-1.5 z-0 opacity-[0.22] [&>svg]:h-[62px] [&>svg]:w-[62px]",
|
|
34
|
+
children: tile.icon
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
/* @__PURE__ */ jsxs("span", { className: "relative z-[1] flex h-full flex-col", children: [
|
|
38
|
+
/* @__PURE__ */ jsx("span", { className: "text-[14.5px] font-extrabold leading-[1.2] tracking-[-0.015em]", children: tile.label }),
|
|
39
|
+
tile.caption && /* @__PURE__ */ jsx("span", { className: "mt-[3px] text-[11.5px] leading-[1.35]", children: tile.caption }),
|
|
40
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "mt-auto h-4 w-4 opacity-85", "aria-hidden": true })
|
|
41
|
+
] })
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
tile.slug
|
|
45
|
+
))
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { MenuIntentTiles };
|