@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.
Files changed (113) hide show
  1. package/dist/chunk-62ES6N32.js +27 -0
  2. package/dist/chunk-AXV5O2IZ.js +58 -0
  3. package/dist/chunk-Q6QH52LJ.js +38 -0
  4. package/dist/chunk-TH6W2PWG.js +213 -0
  5. package/dist/chunk-THOVPZNN.js +37 -0
  6. package/dist/chunk-YS6BHKZO.js +92 -0
  7. package/dist/components/alert.d.ts +2 -2
  8. package/dist/components/badge.d.ts +1 -1
  9. package/dist/components/button.d.ts +1 -1
  10. package/dist/components/command.d.ts +7 -7
  11. package/dist/components/concept-compare.d.ts +73 -0
  12. package/dist/components/concept-compare.js +162 -0
  13. package/dist/components/event-card.js +1 -1
  14. package/dist/components/event-compact-row.d.ts +1 -1
  15. package/dist/components/event-compact-row.js +1 -1
  16. package/dist/components/event-detail-panel.d.ts +171 -25
  17. package/dist/components/event-detail-panel.js +471 -174
  18. package/dist/components/event-list-row-wide.js +1 -1
  19. package/dist/components/event-list-row.js +1 -1
  20. package/dist/components/hub-surface.d.ts +27 -0
  21. package/dist/components/hub-surface.js +20 -0
  22. package/dist/components/menu-family-card.d.ts +59 -0
  23. package/dist/components/menu-family-card.js +121 -0
  24. package/dist/components/menu-field.d.ts +57 -0
  25. package/dist/components/menu-field.js +3 -0
  26. package/dist/components/menu-hub-hero.d.ts +65 -0
  27. package/dist/components/menu-hub-hero.js +103 -0
  28. package/dist/components/menu-intent-tiles.d.ts +31 -0
  29. package/dist/components/menu-intent-tiles.js +50 -0
  30. package/dist/components/menu-item.d.ts +127 -0
  31. package/dist/components/menu-item.js +5 -0
  32. package/dist/components/menu-mocks.d.ts +55 -0
  33. package/dist/components/menu-mocks.js +379 -0
  34. package/dist/components/menu-types.d.ts +245 -0
  35. package/dist/components/menu-types.js +1 -0
  36. package/dist/components/motion-icon-set.js +15 -3
  37. package/dist/components/open-now-list.d.ts +40 -0
  38. package/dist/components/open-now-list.js +69 -0
  39. package/dist/components/place-types.d.ts +112 -1
  40. package/dist/components/ranked-venue-list.d.ts +49 -0
  41. package/dist/components/ranked-venue-list.js +87 -0
  42. package/dist/components/signature-chip.d.ts +37 -0
  43. package/dist/components/signature-chip.js +4 -0
  44. package/dist/components/signature-line-list.d.ts +45 -0
  45. package/dist/components/signature-line-list.js +99 -0
  46. package/dist/components/snap-rail.d.ts +24 -4
  47. package/dist/components/snap-rail.js +66 -8
  48. package/dist/components/spinner.d.ts +2 -2
  49. package/dist/components/stat-banner.d.ts +47 -0
  50. package/dist/components/stat-banner.js +61 -0
  51. package/dist/components/tag.d.ts +2 -2
  52. package/dist/components/text.d.ts +3 -3
  53. package/dist/components/venue-busyness-strip.d.ts +35 -0
  54. package/dist/components/venue-busyness-strip.js +37 -0
  55. package/dist/components/venue-grade-dimensions.d.ts +36 -0
  56. package/dist/components/venue-grade-dimensions.js +3 -0
  57. package/dist/components/venue-quotes.d.ts +32 -0
  58. package/dist/components/venue-quotes.js +3 -0
  59. package/dist/components/venue-signature-block.d.ts +39 -0
  60. package/dist/components/venue-signature-block.js +6 -0
  61. package/dist/components/venue-spotlight.d.ts +74 -0
  62. package/dist/components/venue-spotlight.js +138 -0
  63. package/dist/components/venue-wall.d.ts +41 -0
  64. package/dist/components/venue-wall.js +118 -0
  65. package/dist/components/weekday-theme-banner.d.ts +70 -0
  66. package/dist/components/weekday-theme-banner.js +127 -0
  67. package/package.json +1 -1
  68. package/src/components/colour-dictionary.stories.tsx +227 -0
  69. package/src/components/concept-compare.stories.tsx +104 -0
  70. package/src/components/concept-compare.tsx +276 -0
  71. package/src/components/event-compact-row.tsx +1 -1
  72. package/src/components/event-detail-panel.stories.tsx +205 -97
  73. package/src/components/event-detail-panel.test.tsx +181 -0
  74. package/src/components/event-detail-panel.tsx +703 -185
  75. package/src/components/hub-surface.stories.tsx +68 -0
  76. package/src/components/hub-surface.tsx +40 -0
  77. package/src/components/menu-family-card.stories.tsx +112 -0
  78. package/src/components/menu-family-card.tsx +211 -0
  79. package/src/components/menu-field.stories.tsx +74 -0
  80. package/src/components/menu-field.tsx +199 -0
  81. package/src/components/menu-hub-hero.stories.tsx +92 -0
  82. package/src/components/menu-hub-hero.tsx +187 -0
  83. package/src/components/menu-intent-tiles.stories.tsx +54 -0
  84. package/src/components/menu-intent-tiles.tsx +76 -0
  85. package/src/components/menu-item.tsx +416 -0
  86. package/src/components/menu-mocks.ts +417 -0
  87. package/src/components/menu-types.ts +259 -0
  88. package/src/components/motion-icon-set.tsx +30 -3
  89. package/src/components/open-now-list.stories.tsx +76 -0
  90. package/src/components/open-now-list.tsx +127 -0
  91. package/src/components/place-types.ts +112 -0
  92. package/src/components/ranked-venue-list.stories.tsx +80 -0
  93. package/src/components/ranked-venue-list.tsx +177 -0
  94. package/src/components/signature-chip.tsx +77 -0
  95. package/src/components/signature-line-list.stories.tsx +85 -0
  96. package/src/components/signature-line-list.tsx +136 -0
  97. package/src/components/snap-rail.tsx +90 -9
  98. package/src/components/stat-banner.stories.tsx +100 -0
  99. package/src/components/stat-banner.tsx +117 -0
  100. package/src/components/venue-busyness-strip.stories.tsx +66 -0
  101. package/src/components/venue-busyness-strip.tsx +64 -0
  102. package/src/components/venue-grade-dimensions.stories.tsx +83 -0
  103. package/src/components/venue-grade-dimensions.tsx +91 -0
  104. package/src/components/venue-quotes.stories.tsx +59 -0
  105. package/src/components/venue-quotes.tsx +64 -0
  106. package/src/components/venue-signature-block.stories.tsx +68 -0
  107. package/src/components/venue-signature-block.tsx +71 -0
  108. package/src/components/venue-spotlight.stories.tsx +141 -0
  109. package/src/components/venue-spotlight.tsx +236 -0
  110. package/src/components/venue-wall.stories.tsx +78 -0
  111. package/src/components/venue-wall.tsx +185 -0
  112. package/src/components/weekday-theme-banner.stories.tsx +113 -0
  113. package/src/components/weekday-theme-banner.tsx +231 -0
@@ -0,0 +1,39 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuItemLabels, MenuItemLinkProps } from './menu-item.js';
3
+ import { MenuItemPreview } from './menu-types.js';
4
+ import 'react';
5
+ import './place-types.js';
6
+
7
+ /**
8
+ * VenueSignatureBlock — "what it is known for": the one dish or drink a venue's
9
+ * reviews single out, on a tinted band.
10
+ *
11
+ * ## It is a `<MenuItem>` with a kicker
12
+ *
13
+ * It used to be its own layout — swatch, label, name, price, all hand-placed —
14
+ * and it drifted from the row every other surface renders. It is now the
15
+ * production row on an accent ground, so a dish looks like itself here, in the
16
+ * "known for" list, and on the venue's own menu.
17
+ *
18
+ * The kicker sits ABOVE the row rather than inside it. Inline at 9.5px it
19
+ * competed with the dish name for the same line, which is how it ended up
20
+ * stacked one word per line.
21
+ */
22
+ type VenueSignatureBlockProps = {
23
+ item: MenuItemPreview;
24
+ labels: MenuItemLabels;
25
+ formatPrice: (amount: number) => string;
26
+ formatCount?: (value: number) => string;
27
+ /** Small caps kicker, e.g. "WHAT IT IS KNOWN FOR". */
28
+ kicker: string;
29
+ /** The row's category mark, as a CSS `background-image` value. */
30
+ markGradient?: string;
31
+ /** Reviewers' reason, in place of the item description. */
32
+ subtitle?: string;
33
+ href?: string;
34
+ linkComponent?: React.ComponentType<MenuItemLinkProps>;
35
+ className?: string;
36
+ };
37
+ declare function VenueSignatureBlock({ item, labels, formatPrice, formatCount, kicker, markGradient, subtitle, href, linkComponent, className, }: VenueSignatureBlockProps): react_jsx_runtime.JSX.Element;
38
+
39
+ export { VenueSignatureBlock, type VenueSignatureBlockProps };
@@ -0,0 +1,6 @@
1
+ export { VenueSignatureBlock } from '../chunk-THOVPZNN.js';
2
+ import '../chunk-TH6W2PWG.js';
3
+ import '../chunk-Q6QH52LJ.js';
4
+ import '../chunk-UKBNQO6Q.js';
5
+ import '../chunk-FEK5XGZW.js';
6
+ import '../chunk-3GWWZKX7.js';
@@ -0,0 +1,74 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { VenueGradeDimensionKey } from './venue-grade-dimensions.js';
3
+ import { MenuItemLabels } from './menu-item.js';
4
+ import { MenuVenueSignature, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
5
+ import { PlacePreview, CitedText } from './place-types.js';
6
+ import 'react';
7
+
8
+ /**
9
+ * VenueSpotlight — one venue given real space: photograph, what it is known
10
+ * for, what reviewers say, how it grades, and when it fills up.
11
+ *
12
+ * The photograph IS the point of this surface. It is the one module on a menu
13
+ * hub that shows an image, and it can, because venue photography is real and
14
+ * licensed where item photography is generated. Absent a photo it degrades to
15
+ * the same diagonal texture the hero uses, never to a bare icon.
16
+ *
17
+ * ## Composed from exported parts
18
+ *
19
+ * `VenueSignatureBlock`, `VenueQuotes`, `VenueGradeDimensions` and
20
+ * `VenueBusynessStrip` are each exported on their own. This composite is the
21
+ * arrangement the hub wants; a consumer filming a single panel, or building a
22
+ * narrower variant, takes the parts instead. Everything here is layout — no
23
+ * part is reachable only through the composite.
24
+ *
25
+ * No directive: it renders on the server unless `onCite` is passed through to
26
+ * the quotes.
27
+ */
28
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
29
+ type VenueSpotlightLinkProps = MenuLinkProps;
30
+ type VenueSpotlightLabels = {
31
+ /** Overlay on the photograph, e.g. "venue photograph". */
32
+ photoCredit?: string;
33
+ /** Composed open state, e.g. "Kitchen open until 23:00". Absent when closed or unknown. */
34
+ openUntil?: string;
35
+ /** Kicker over the signature, e.g. "WHAT IT IS KNOWN FOR". */
36
+ signatureKicker: string;
37
+ /** Composed review count, e.g. "318 reviews". */
38
+ reviewCount?: string;
39
+ /** Dimension key → translated label. */
40
+ dimensions: Partial<Record<VenueGradeDimensionKey, string>>;
41
+ /** Tag code → translated label. Missing codes render the raw code. */
42
+ tags?: Record<string, string>;
43
+ /** Composed value verdict, e.g. "Good value". */
44
+ valueVerdict?: string;
45
+ /** Composed busyness sentence. */
46
+ busyness?: string;
47
+ /** Accessible name for a quote's cite action. */
48
+ cite?: (reviewCount: number) => string;
49
+ };
50
+ type VenueSpotlightProps = {
51
+ place: PlacePreview;
52
+ href: string;
53
+ labels: VenueSpotlightLabels;
54
+ /** Composed meta line, e.g. "pub · Centru". Clause order is locale-dependent. */
55
+ meta?: string;
56
+ signature?: MenuVenueSignature;
57
+ /** CSS gradient for the signature swatch. */
58
+ signatureGradient?: string;
59
+ /** Passed through to the signature `<MenuItem>`. */
60
+ itemLabels: MenuItemLabels;
61
+ formatPrice: (amount: number) => string;
62
+ formatCount?: (value: number) => string;
63
+ /** 0–100 → display numeral for the dimension bars. */
64
+ formatScore: (value: number) => string;
65
+ /** Opens the reviews behind a quote. */
66
+ onCite?: (quote: CitedText) => void;
67
+ /** The action in the footer — pass a rendered link or button. */
68
+ action?: React.ReactNode;
69
+ linkComponent?: MenuLinkComponent;
70
+ className?: string;
71
+ };
72
+ declare function VenueSpotlight({ place, href, labels, meta, signature, signatureGradient, itemLabels, formatPrice, formatCount, formatScore, onCite, action, linkComponent: LinkComponent, className, }: VenueSpotlightProps): react_jsx_runtime.JSX.Element;
73
+
74
+ export { VenueSpotlight, type VenueSpotlightLabels, type VenueSpotlightLinkProps, type VenueSpotlightProps };
@@ -0,0 +1,138 @@
1
+ import { VenueGradeDimensions } from '../chunk-AXV5O2IZ.js';
2
+ import { VenueQuotes } from '../chunk-62ES6N32.js';
3
+ import { VenueSignatureBlock } from '../chunk-THOVPZNN.js';
4
+ import { Separator } from '../chunk-WSRWFA6K.js';
5
+ import '../chunk-TH6W2PWG.js';
6
+ import '../chunk-Q6QH52LJ.js';
7
+ import '../chunk-UKBNQO6Q.js';
8
+ import { cn } from '../chunk-FEK5XGZW.js';
9
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
10
+ import { Camera, Star } from 'lucide-react';
11
+ import { jsxs, jsx } from 'react/jsx-runtime';
12
+
13
+ function DefaultLink(_a) {
14
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
15
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
16
+ }
17
+ var QUOTE_LIMIT = 2;
18
+ function VenueSpotlight({
19
+ place,
20
+ href,
21
+ labels,
22
+ meta,
23
+ signature,
24
+ signatureGradient,
25
+ itemLabels,
26
+ formatPrice,
27
+ formatCount,
28
+ formatScore,
29
+ onCite,
30
+ action,
31
+ linkComponent: LinkComponent = DefaultLink,
32
+ className
33
+ }) {
34
+ var _a, _b, _c;
35
+ const digest = place.reviews_digest;
36
+ const tags = (_a = digest == null ? void 0 : digest.tags) != null ? _a : [];
37
+ return /* @__PURE__ */ jsxs(
38
+ "div",
39
+ {
40
+ className: cn(
41
+ "grid overflow-hidden rounded-[18px] border border-border bg-card lg:grid-cols-[404px_1fr]",
42
+ className
43
+ ),
44
+ children: [
45
+ /* @__PURE__ */ jsxs("div", { className: "relative min-h-[220px] bg-[repeating-linear-gradient(135deg,oklch(0.76_0.02_55)_0_10px,oklch(0.83_0.013_58)_10px_20px)] lg:min-h-[340px]", children: [
46
+ place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
47
+ /* @__PURE__ */ jsx(
48
+ "img",
49
+ {
50
+ src: place.thumbnail_url,
51
+ alt: place.name,
52
+ className: "absolute inset-0 h-full w-full object-cover"
53
+ }
54
+ ),
55
+ labels.photoCredit && /* @__PURE__ */ jsxs("span", { className: "absolute left-3.5 top-3.5 inline-flex h-7 items-center gap-1.5 rounded-full bg-black/60 px-2.5 text-[11px] font-bold text-white", children: [
56
+ /* @__PURE__ */ jsx(Camera, { className: "h-2.5 w-2.5", "aria-hidden": true }),
57
+ labels.photoCredit
58
+ ] }),
59
+ labels.openUntil && /* @__PURE__ */ jsxs("span", { className: "absolute bottom-3.5 left-3.5 inline-flex h-[30px] items-center gap-[7px] rounded-full bg-background px-3 text-[11.5px] font-bold text-[var(--free,currentColor)]", children: [
60
+ /* @__PURE__ */ jsx("i", { className: "h-[7px] w-[7px] rounded-full bg-current", "aria-hidden": true }),
61
+ labels.openUntil
62
+ ] })
63
+ ] }),
64
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3.5 p-4 lg:p-6", children: [
65
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
66
+ /* @__PURE__ */ jsx("h3", { className: "min-w-0 flex-1 basis-0 font-serif text-xl font-semibold leading-tight tracking-[-0.02em] lg:text-[28px]", children: /* @__PURE__ */ jsx(
67
+ LinkComponent,
68
+ {
69
+ href,
70
+ className: "rounded focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
71
+ children: place.name
72
+ }
73
+ ) }),
74
+ place.rating && /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-baseline gap-1.5", children: [
75
+ /* @__PURE__ */ jsx("b", { className: "text-xl font-extrabold tabular-nums lg:text-2xl", children: place.rating.rating }),
76
+ /* @__PURE__ */ jsx(Star, { className: "h-3 w-3 self-center text-muted-foreground", "aria-hidden": true })
77
+ ] })
78
+ ] }),
79
+ (meta || labels.reviewCount) && /* @__PURE__ */ jsxs("div", { className: "-mt-2 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[12.5px] text-muted-foreground", children: [
80
+ meta && /* @__PURE__ */ jsx("span", { children: meta }),
81
+ meta && labels.reviewCount && /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
82
+ labels.reviewCount && /* @__PURE__ */ jsx("span", { children: labels.reviewCount })
83
+ ] }),
84
+ signature && /* @__PURE__ */ jsx(
85
+ VenueSignatureBlock,
86
+ {
87
+ item: signature.item,
88
+ labels: itemLabels,
89
+ formatPrice,
90
+ formatCount,
91
+ kicker: labels.signatureKicker,
92
+ markGradient: signatureGradient,
93
+ subtitle: signature.why,
94
+ href: signature.href,
95
+ linkComponent: LinkComponent
96
+ }
97
+ ),
98
+ (digest == null ? void 0 : digest.highlights) && /* @__PURE__ */ jsx(
99
+ VenueQuotes,
100
+ {
101
+ quotes: digest.highlights,
102
+ limit: QUOTE_LIMIT,
103
+ onCite,
104
+ citeLabel: labels.cite
105
+ }
106
+ ),
107
+ ((_b = digest == null ? void 0 : digest.grade) == null ? void 0 : _b.dimensions) && /* @__PURE__ */ jsx(Separator, {}),
108
+ ((_c = digest == null ? void 0 : digest.grade) == null ? void 0 : _c.dimensions) && /* @__PURE__ */ jsx(
109
+ VenueGradeDimensions,
110
+ {
111
+ dimensions: digest.grade.dimensions,
112
+ labels: labels.dimensions,
113
+ formatScore,
114
+ className: "mt-1"
115
+ }
116
+ ),
117
+ (labels.valueVerdict || tags.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
118
+ labels.valueVerdict && /* @__PURE__ */ jsx("span", { className: "inline-flex h-[26px] items-center gap-1.5 rounded-full bg-accent-subtle px-2.5 text-[11.5px] font-bold text-accent", children: labels.valueVerdict }),
119
+ tags.map((tag) => {
120
+ var _a2, _b2;
121
+ return /* @__PURE__ */ jsx(
122
+ "span",
123
+ {
124
+ className: "inline-flex h-[26px] items-center rounded-full border border-border px-2.5 text-[11.5px] text-muted-foreground",
125
+ children: (_b2 = (_a2 = labels.tags) == null ? void 0 : _a2[tag.value]) != null ? _b2 : tag.value
126
+ },
127
+ tag.value
128
+ );
129
+ })
130
+ ] }),
131
+ action && /* @__PURE__ */ jsx("div", { className: "mt-auto flex justify-end pt-1", children: action })
132
+ ] })
133
+ ]
134
+ }
135
+ );
136
+ }
137
+
138
+ export { VenueSpotlight };
@@ -0,0 +1,41 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuLinkComponent, MenuLinkProps } from './menu-types.js';
3
+ import { PlacePreview } from './place-types.js';
4
+ import 'react';
5
+
6
+ /**
7
+ * VenueWall — the catalogue's scale, stated as a row of the venues in it.
8
+ *
9
+ * A closing beat rather than a browse surface: the avatars are named and
10
+ * linked, but the module's job is to make "we read every menu in the city"
11
+ * land as something specific. It shows a handful and counts the rest, because
12
+ * a wall long enough to be exhaustive stops reading as places.
13
+ *
14
+ * Server-safe.
15
+ */
16
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
17
+ type VenueWallLinkProps = MenuLinkProps;
18
+ type VenueWallProps = {
19
+ places: PlacePreview[];
20
+ /** Destination per place, keyed by slug. Without one an avatar is inert. */
21
+ hrefs?: Record<string, string>;
22
+ kicker?: string;
23
+ kickerIcon?: React.ReactNode;
24
+ headline?: string;
25
+ body?: string;
26
+ /** Composed overflow chip, e.g. "+393 more". */
27
+ overflowLabel?: string;
28
+ /**
29
+ * Tiles for the mosaic ground — the same licensed stills the hero uses.
30
+ * Without them the wall falls back to the flat diagonal texture, which is
31
+ * the degraded state rather than the design.
32
+ */
33
+ tileUrls?: string[];
34
+ /** The action — pass a rendered link or button. */
35
+ action?: React.ReactNode;
36
+ className?: string;
37
+ linkComponent?: MenuLinkComponent;
38
+ };
39
+ declare function VenueWall({ places, hrefs, kicker, kickerIcon, headline, body, overflowLabel, tileUrls, action, linkComponent: LinkComponent, className, }: VenueWallProps): react_jsx_runtime.JSX.Element;
40
+
41
+ export { VenueWall, type VenueWallLinkProps, type VenueWallProps };
@@ -0,0 +1,118 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
+
5
+ function DefaultLink(_a) {
6
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
7
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
8
+ }
9
+ function VenueWall({
10
+ places,
11
+ hrefs,
12
+ kicker,
13
+ kickerIcon,
14
+ headline,
15
+ body,
16
+ overflowLabel,
17
+ tileUrls,
18
+ action,
19
+ linkComponent: LinkComponent = DefaultLink,
20
+ className
21
+ }) {
22
+ return /* @__PURE__ */ jsxs(
23
+ "div",
24
+ {
25
+ className: cn(
26
+ "relative flex flex-col gap-4 overflow-hidden rounded-[20px] bg-[oklch(0.21_0.012_55)] p-5 text-white lg:p-8",
27
+ className
28
+ ),
29
+ children: [
30
+ (tileUrls == null ? void 0 : tileUrls.length) ? /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsx("div", { "aria-hidden": true, className: "absolute inset-0 grid auto-rows-fr grid-cols-4 gap-0.5 lg:grid-cols-6", children: Array.from({ length: 18 }, (_, cell) => (
32
+ // eslint-disable-next-line @next/next/no-img-element
33
+ /* @__PURE__ */ jsx(
34
+ "img",
35
+ {
36
+ src: tileUrls[cell % tileUrls.length],
37
+ alt: "",
38
+ loading: "lazy",
39
+ decoding: "async",
40
+ className: "h-full w-full object-cover"
41
+ },
42
+ cell
43
+ )
44
+ )) }),
45
+ /* @__PURE__ */ jsx(
46
+ "div",
47
+ {
48
+ "aria-hidden": true,
49
+ className: "absolute inset-0 bg-[oklch(0.16_0.012_55_/_0.9)]"
50
+ }
51
+ )
52
+ ] }) : /* @__PURE__ */ jsx(
53
+ "div",
54
+ {
55
+ "aria-hidden": true,
56
+ className: "absolute inset-0 bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.18]"
57
+ }
58
+ ),
59
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex flex-col gap-3", children: [
60
+ kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex w-max items-center gap-1.5 rounded-full bg-white/15 px-3 py-1.5 text-[11px] font-extrabold tracking-[0.11em]", children: [
61
+ kickerIcon,
62
+ kicker
63
+ ] }),
64
+ headline && /* @__PURE__ */ jsx("h3", { className: "max-w-[640px] font-serif text-[28px] font-semibold leading-[1.1] tracking-[-0.025em] lg:text-[40px]", children: headline }),
65
+ body && /* @__PURE__ */ jsx("p", { className: "hidden max-w-[540px] text-sm leading-[1.55] opacity-85 lg:block", children: body })
66
+ ] }),
67
+ /* @__PURE__ */ jsxs("div", { "aria-hidden": true, className: "relative z-[1] flex items-center gap-3 lg:hidden", children: [
68
+ /* @__PURE__ */ jsx("div", { className: "flex", children: places.slice(0, 5).map((place, index) => /* @__PURE__ */ jsx(
69
+ "span",
70
+ {
71
+ className: cn(
72
+ "h-9 w-9 shrink-0 overflow-hidden rounded-full bg-white/20 ring-2 ring-[oklch(0.21_0.012_55)]",
73
+ index > 0 && "-ml-2.5"
74
+ ),
75
+ children: place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
76
+ /* @__PURE__ */ jsx("img", { src: place.thumbnail_url, alt: "", className: "h-full w-full object-cover", loading: "lazy" })
77
+ },
78
+ place.slug
79
+ )) }),
80
+ overflowLabel && /* @__PURE__ */ jsx("span", { className: "text-[13px] font-medium opacity-85", children: overflowLabel })
81
+ ] }),
82
+ /* @__PURE__ */ jsxs("ul", { className: "relative z-[1] hidden flex-wrap items-center gap-2 lg:flex", children: [
83
+ places.map((place) => {
84
+ const avatar = /* @__PURE__ */ jsxs(Fragment, { children: [
85
+ /* @__PURE__ */ jsx("span", { className: "h-7 w-7 shrink-0 overflow-hidden rounded-full bg-white/20", children: place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
86
+ /* @__PURE__ */ jsx(
87
+ "img",
88
+ {
89
+ src: place.thumbnail_url,
90
+ alt: "",
91
+ className: "h-full w-full object-cover",
92
+ loading: "lazy"
93
+ }
94
+ ) }),
95
+ /* @__PURE__ */ jsx("span", { className: "truncate text-[12.5px] font-medium", children: place.name })
96
+ ] });
97
+ const chipClasses = "inline-flex max-w-[220px] items-center gap-2 rounded-full bg-white/10 py-1 pl-1 pr-3.5 backdrop-blur-[6px]";
98
+ return /* @__PURE__ */ jsx("li", { children: (hrefs == null ? void 0 : hrefs[place.slug]) ? /* @__PURE__ */ jsx(
99
+ LinkComponent,
100
+ {
101
+ href: hrefs[place.slug],
102
+ className: cn(
103
+ chipClasses,
104
+ "transition-colors hover:bg-white/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
105
+ ),
106
+ children: avatar
107
+ }
108
+ ) : /* @__PURE__ */ jsx("span", { className: chipClasses, children: avatar }) }, place.slug);
109
+ }),
110
+ overflowLabel && /* @__PURE__ */ jsx("li", { className: "inline-flex h-9 items-center rounded-full bg-white/10 px-4 text-[12.5px] opacity-80", children: overflowLabel })
111
+ ] }),
112
+ action && /* @__PURE__ */ jsx("div", { className: "relative z-[1] mt-1", children: action })
113
+ ]
114
+ }
115
+ );
116
+ }
117
+
118
+ export { VenueWall };
@@ -0,0 +1,70 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * WeekdayThemeBanner — the day's editorial theme, with the whole week beside it.
5
+ *
6
+ * Showing all seven days is the point: a banner that only says "it is Burger
7
+ * Thursday" is an assertion, while one that shows Wednesday was salads and
8
+ * Friday is pizza reads as a standing rhythm the reader can come back for. The
9
+ * other six days are also the only affordance for browsing to one.
10
+ *
11
+ * ## The active day is the caller's, not the component's
12
+ *
13
+ * `activeDay` is an index into `days`, resolved by the caller from the READER's
14
+ * clock. Resolving it here would use the server's, and a server in UTC and a
15
+ * reader in Bucharest disagree about which day it is for two hours out of every
16
+ * twenty-four.
17
+ *
18
+ * No directive: with `dayHref` the week rail is links and renders on the
19
+ * server; with `onDayChange` it is buttons and needs a client caller.
20
+ */
21
+ type WeekdayThemeBannerLinkProps = {
22
+ href: string;
23
+ className?: string;
24
+ children: React.ReactNode;
25
+ };
26
+ type WeekdayThemeDay = {
27
+ /** Short code, e.g. "THU". Already localised. */
28
+ code: string;
29
+ /** Theme name, e.g. "Burger Thursday". */
30
+ name: string;
31
+ /** Short form for the rail, e.g. "Burger". Falls back to `name`. */
32
+ shortName?: string;
33
+ /** Items under the theme. Omit where there is no count to show. */
34
+ count?: number;
35
+ };
36
+ type WeekdayThemeBannerProps = {
37
+ days: WeekdayThemeDay[];
38
+ /** Index into `days` for the reader's own today. */
39
+ activeDay: number;
40
+ /** Kicker over the title, e.g. "TODAY IS THURSDAY". Composed by the caller. */
41
+ kicker?: string;
42
+ kickerIcon?: React.ReactNode;
43
+ /** The banner's body copy for the active day. */
44
+ body?: string;
45
+ /** Heading over the week rail, e.g. "THE WEEK". */
46
+ weekLabel?: string;
47
+ /** The primary action — pass a rendered button or link. */
48
+ action?: React.ReactNode;
49
+ /** Note beside the action, e.g. "Bruno picks nine, ranked on mentions". */
50
+ actionNote?: string;
51
+ /** Destination per day index. Renders the rail as links. */
52
+ dayHref?: (day: WeekdayThemeDay, index: number) => string;
53
+ /** Selects a day in place. Renders the rail as buttons. */
54
+ onDayChange?: (index: number) => void;
55
+ /**
56
+ * The day's photograph.
57
+ *
58
+ * This is the one editorial slot on a menu hub that carries a plated dish,
59
+ * and it can because these are art-directed assets shipped with the app, not
60
+ * generated item photos pulled off a menu row. Without one the banner falls
61
+ * back to its flat ground and still reads — but the flat version is the
62
+ * degraded state, not the design.
63
+ */
64
+ imageUrl?: string;
65
+ linkComponent?: React.ComponentType<WeekdayThemeBannerLinkProps>;
66
+ className?: string;
67
+ };
68
+ declare function WeekdayThemeBanner({ days, activeDay, kicker, kickerIcon, body, weekLabel, action, actionNote, dayHref, onDayChange, imageUrl, linkComponent: LinkComponent, className, }: WeekdayThemeBannerProps): react_jsx_runtime.JSX.Element | null;
69
+
70
+ export { WeekdayThemeBanner, type WeekdayThemeBannerLinkProps, type WeekdayThemeBannerProps, type WeekdayThemeDay };
@@ -0,0 +1,127 @@
1
+ import { cn } from '../chunk-FEK5XGZW.js';
2
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
+
5
+ function DefaultLink(_a) {
6
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
7
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
8
+ }
9
+ function WeekdayThemeBanner({
10
+ days,
11
+ activeDay,
12
+ kicker,
13
+ kickerIcon,
14
+ body,
15
+ weekLabel,
16
+ action,
17
+ actionNote,
18
+ dayHref,
19
+ onDayChange,
20
+ imageUrl,
21
+ linkComponent: LinkComponent = DefaultLink,
22
+ className
23
+ }) {
24
+ const active = days[activeDay];
25
+ if (!active) return null;
26
+ return /* @__PURE__ */ jsxs(
27
+ "div",
28
+ {
29
+ className: cn(
30
+ "relative grid overflow-hidden rounded-[18px] bg-[linear-gradient(120deg,oklch(0.32_0.06_45),oklch(0.24_0.04_40))] text-white lg:grid-cols-[1fr_auto]",
31
+ className
32
+ ),
33
+ children: [
34
+ imageUrl && /* @__PURE__ */ jsxs(Fragment, { children: [
35
+ /* @__PURE__ */ jsx(
36
+ "img",
37
+ {
38
+ src: imageUrl,
39
+ alt: "",
40
+ "aria-hidden": true,
41
+ className: "absolute inset-0 h-full w-full object-cover"
42
+ }
43
+ ),
44
+ /* @__PURE__ */ jsx(
45
+ "div",
46
+ {
47
+ "aria-hidden": true,
48
+ className: "absolute inset-0 bg-[linear-gradient(100deg,oklch(0.18_0.03_45_/_0.94)_0%,oklch(0.18_0.03_45_/_0.82)_45%,oklch(0.18_0.03_45_/_0.66)_100%)]"
49
+ }
50
+ )
51
+ ] }),
52
+ !imageUrl && /* @__PURE__ */ jsx(
53
+ "span",
54
+ {
55
+ "aria-hidden": true,
56
+ className: "pointer-events-none absolute bottom-0 right-[28%] select-none font-serif text-[140px] font-bold leading-none text-white/[0.06]",
57
+ children: active.code
58
+ }
59
+ ),
60
+ /* @__PURE__ */ jsxs("div", { className: "relative z-[1] flex flex-col gap-2.5 p-4 lg:gap-3 lg:p-6", children: [
61
+ kicker && /* @__PURE__ */ jsxs("span", { className: "inline-flex w-max items-center gap-1.5 rounded-full bg-white/15 px-2.5 py-1 text-[10.5px] font-bold tracking-[0.09em]", children: [
62
+ kickerIcon,
63
+ kicker
64
+ ] }),
65
+ /* @__PURE__ */ jsx("h3", { className: "font-serif text-[24px] font-semibold tracking-[-0.02em] lg:text-[36px]", children: active.name }),
66
+ body && /* @__PURE__ */ jsx("p", { className: "max-w-[520px] text-[13.5px] leading-[1.5] opacity-90", children: body }),
67
+ (action || actionNote) && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-3", children: [
68
+ action,
69
+ actionNote && /* @__PURE__ */ jsx("span", { className: "text-xs opacity-75", children: actionNote })
70
+ ] })
71
+ ] }),
72
+ /* @__PURE__ */ jsxs(
73
+ "div",
74
+ {
75
+ className: cn(
76
+ "relative z-[1] hidden border-t border-white/15 p-4 lg:block lg:min-w-[240px] lg:border-l lg:border-t-0 lg:p-5",
77
+ // Over a photograph the rail needs its own ground, or seven lines of
78
+ // small type land on whatever the plate happens to be.
79
+ imageUrl && "bg-black/45 backdrop-blur-[2px]"
80
+ ),
81
+ children: [
82
+ weekLabel && /* @__PURE__ */ jsx("div", { className: "mb-2 text-[10px] font-bold tracking-[0.12em] opacity-70", children: weekLabel }),
83
+ /* @__PURE__ */ jsx("ul", { className: "grid grid-cols-2 gap-x-4 sm:grid-cols-4 lg:grid-cols-1", children: days.map((day, index) => {
84
+ var _a;
85
+ const isActive = index === activeDay;
86
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
87
+ /* @__PURE__ */ jsx("b", { className: "w-8 shrink-0 font-mono text-[11px] font-medium opacity-70", children: day.code }),
88
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: (_a = day.shortName) != null ? _a : day.name }),
89
+ day.count != null && /* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums opacity-70", children: day.count })
90
+ ] });
91
+ const rowClasses = cn(
92
+ "flex w-full items-center gap-2 rounded transition-opacity",
93
+ isActive ? "-mx-1.5 bg-white/10 px-1.5 py-1.5 text-[14.5px] font-extrabold" : "py-[5px] text-[12.5px] opacity-70 hover:opacity-100"
94
+ );
95
+ return /* @__PURE__ */ jsx("li", { children: dayHref ? /* @__PURE__ */ jsx(
96
+ LinkComponent,
97
+ {
98
+ href: dayHref(day, index),
99
+ className: cn(
100
+ rowClasses,
101
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
102
+ ),
103
+ children: content
104
+ }
105
+ ) : onDayChange ? /* @__PURE__ */ jsx(
106
+ "button",
107
+ {
108
+ type: "button",
109
+ "aria-pressed": isActive,
110
+ onClick: () => onDayChange(index),
111
+ className: cn(
112
+ rowClasses,
113
+ "text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
114
+ ),
115
+ children: content
116
+ }
117
+ ) : /* @__PURE__ */ jsx("span", { className: rowClasses, children: content }) }, day.code);
118
+ }) })
119
+ ]
120
+ }
121
+ )
122
+ ]
123
+ }
124
+ );
125
+ }
126
+
127
+ export { WeekdayThemeBanner };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {