@bearmenu/ui 0.8.11 → 0.8.12

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 (86) hide show
  1. package/dist/{chunk-H3ZZDX5L.js → chunk-7A42J73Q.js} +1 -1
  2. package/dist/{chunk-5RWTOT5Y.js → chunk-CNJQUHCU.js} +11 -1
  3. package/dist/{chunk-JS3EINZD.js → chunk-VAXOQS7W.js} +12 -0
  4. package/dist/{chunk-AKQ57NXY.js → chunk-ZQ53AGML.js} +2 -2
  5. package/dist/components/alert-dialog.js +1 -1
  6. package/dist/components/alert.d.ts +2 -2
  7. package/dist/components/avatar.d.ts +3 -3
  8. package/dist/components/badge.d.ts +1 -1
  9. package/dist/components/button.d.ts +2 -2
  10. package/dist/components/button.js +1 -1
  11. package/dist/components/calendar.js +1 -1
  12. package/dist/components/card.d.ts +4 -4
  13. package/dist/components/carousel.js +1 -1
  14. package/dist/components/combobox.js +1 -1
  15. package/dist/components/command.d.ts +8 -8
  16. package/dist/components/concept-compare-rail.js +2 -2
  17. package/dist/components/concept-compare.d.ts +1 -1
  18. package/dist/components/concept-compare.js +28 -7
  19. package/dist/components/concept-convergence.js +1 -1
  20. package/dist/components/date-input.d.ts +1 -1
  21. package/dist/components/dialog.d.ts +2 -2
  22. package/dist/components/empty-state.js +1 -1
  23. package/dist/components/event-detail-panel.js +1 -1
  24. package/dist/components/event-list-row-wide.js +1 -1
  25. package/dist/components/haptic-button.js +1 -1
  26. package/dist/components/input-group.d.ts +4 -4
  27. package/dist/components/input.d.ts +2 -2
  28. package/dist/components/item.d.ts +19 -9
  29. package/dist/components/item.js +15 -10
  30. package/dist/components/kbd.d.ts +2 -2
  31. package/dist/components/kitchen-dossier.d.ts +2 -0
  32. package/dist/components/kitchen-dossier.js +25 -18
  33. package/dist/components/link-button.js +1 -1
  34. package/dist/components/menu-family-block.d.ts +15 -9
  35. package/dist/components/menu-family-block.js +62 -59
  36. package/dist/components/menu-family-card.js +1 -1
  37. package/dist/components/menu-family-tiles.d.ts +19 -0
  38. package/dist/components/menu-family-tiles.js +57 -0
  39. package/dist/components/menu-hub-hero.js +8 -6
  40. package/dist/components/menu-index-list.d.ts +31 -12
  41. package/dist/components/menu-index-list.js +68 -31
  42. package/dist/components/menu-intent-hero.d.ts +1 -1
  43. package/dist/components/menu-mocks.d.ts +10 -3
  44. package/dist/components/menu-mocks.js +21 -9
  45. package/dist/components/menu-types.d.ts +33 -10
  46. package/dist/components/multi-select-combobox.js +1 -1
  47. package/dist/components/native-select.d.ts +2 -2
  48. package/dist/components/pagination.js +1 -1
  49. package/dist/components/photo-ground.d.ts +7 -1
  50. package/dist/components/photo-ground.js +1 -1
  51. package/dist/components/searchable-select.js +1 -1
  52. package/dist/components/select.d.ts +1 -1
  53. package/dist/components/sliding-panels.js +1 -1
  54. package/dist/components/snap-rail.d.ts +2 -2
  55. package/dist/components/spinner.d.ts +4 -4
  56. package/dist/components/tag.d.ts +4 -4
  57. package/dist/components/text.d.ts +3 -3
  58. package/dist/components/toggle.d.ts +1 -1
  59. package/dist/components/venue-grade-dimensions.js +1 -1
  60. package/dist/components/weekday-theme-banner.d.ts +24 -3
  61. package/dist/components/weekday-theme-banner.js +62 -14
  62. package/package.json +1 -1
  63. package/src/components/button.stories.tsx +25 -2
  64. package/src/components/button.tsx +12 -0
  65. package/src/components/colour-dictionary.stories.tsx +1 -1
  66. package/src/components/concept-compare.tsx +40 -9
  67. package/src/components/item.tsx +69 -45
  68. package/src/components/kitchen-dossier.tsx +45 -24
  69. package/src/components/menu-family-block.tsx +86 -79
  70. package/src/components/menu-family-tiles.stories.tsx +54 -0
  71. package/src/components/menu-family-tiles.test.tsx +30 -0
  72. package/src/components/menu-family-tiles.tsx +132 -0
  73. package/src/components/menu-hub-hero.tsx +13 -7
  74. package/src/components/menu-index-list.stories.tsx +6 -1
  75. package/src/components/menu-index-list.tsx +101 -54
  76. package/src/components/menu-intent-hero.tsx +1 -1
  77. package/src/components/menu-mocks.ts +32 -9
  78. package/src/components/menu-types.ts +32 -9
  79. package/src/components/photo-ground.tsx +13 -1
  80. package/src/components/venue-grade-dimensions.tsx +9 -2
  81. package/src/components/weekday-theme-banner.stories.tsx +43 -4
  82. package/src/components/weekday-theme-banner.tsx +94 -17
  83. package/dist/components/menu-intent-tiles.d.ts +0 -31
  84. package/dist/components/menu-intent-tiles.js +0 -50
  85. package/src/components/menu-intent-tiles.stories.tsx +0 -54
  86. package/src/components/menu-intent-tiles.tsx +0 -76
@@ -1,9 +1,9 @@
1
- import { DIMENSION_ORDER, VenueGradeDimensions } from '../chunk-AKQ57NXY.js';
1
+ import { DIMENSION_ORDER, VenueGradeDimensions } from '../chunk-ZQ53AGML.js';
2
2
  import { VenueQuotes } from '../chunk-43RC7MHY.js';
3
3
  import { StoryProvider, StorySegments, StoryPanel } from '../chunk-F6FMZJDG.js';
4
4
  import { GlassGround } from '../chunk-WCTHZKU7.js';
5
5
  import { Separator } from '../chunk-WSRWFA6K.js';
6
- import { PhotoGround, PHOTO_GROUND } from '../chunk-5RWTOT5Y.js';
6
+ import { PhotoGround, PHOTO_GROUND } from '../chunk-CNJQUHCU.js';
7
7
  import { cn } from '../chunk-FEK5XGZW.js';
8
8
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
9
9
  import { MessageSquareQuote, Star } from 'lucide-react';
@@ -85,21 +85,18 @@ function KitchenDossier({
85
85
  var _a2, _b2;
86
86
  return ((_a2 = dimensions[b]) != null ? _a2 : 0) - ((_b2 = dimensions[a]) != null ? _b2 : 0);
87
87
  }).slice(0, DIMENSION_LIMIT) : [];
88
- if (grade && (grade.overall || strongest.length > 0)) {
88
+ if (dimensions && strongest.length > 0) {
89
89
  parts.push(
90
- /* @__PURE__ */ jsx(Part, { kicker: labels.theGrade, children: /* @__PURE__ */ jsxs("div", { className: "flex items-end gap-4", children: [
91
- grade.overall && /* @__PURE__ */ jsx("span", { className: "text-[34px] font-extrabold leading-none tracking-[-0.03em] tabular-nums text-[oklch(0.82_0.15_85)]", children: grade.overall }),
92
- dimensions && strongest.length > 0 && /* @__PURE__ */ jsx(
93
- VenueGradeDimensions,
94
- {
95
- className: "min-w-0 flex-1",
96
- dimensions,
97
- labels: labels.dimensions,
98
- order: strongest,
99
- formatScore
100
- }
101
- )
102
- ] }) }, "grade")
90
+ /* @__PURE__ */ jsx(Part, { kicker: labels.theGrade, children: /* @__PURE__ */ jsx(
91
+ VenueGradeDimensions,
92
+ {
93
+ className: "min-w-0",
94
+ dimensions,
95
+ labels: labels.dimensions,
96
+ order: strongest,
97
+ formatScore
98
+ }
99
+ ) }, "grade")
103
100
  );
104
101
  }
105
102
  return /* @__PURE__ */ jsxs(
@@ -153,12 +150,22 @@ function KitchenDossier({
153
150
  /* @__PURE__ */ jsxs("div", { className: "[text-shadow:0_1px_12px_oklch(0_0_0/0.55)]", children: [
154
151
  /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-4", children: [
155
152
  /* @__PURE__ */ jsx("h3", { className: "min-w-0 font-serif text-[26px] font-semibold leading-tight tracking-[-0.02em] text-white lg:text-[32px]", children: /* @__PURE__ */ jsx(LinkComponent, { href, className: "focus-visible:outline-none focus-visible:underline", children: place.name }) }),
156
- rating !== void 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-[18px] font-extrabold leading-none tabular-nums text-white lg:text-[22px]", children: [
153
+ (grade == null ? void 0 : grade.overall) ? /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-baseline gap-1 text-[22px] font-extrabold leading-none tabular-nums text-[oklch(0.82_0.15_85)] lg:text-[26px]", children: [
154
+ grade.overall,
155
+ labels.gradeUnit && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-bold text-white/70", children: labels.gradeUnit })
156
+ ] }) : rating !== void 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-[18px] font-extrabold leading-none tabular-nums text-white lg:text-[22px]", children: [
157
157
  rating.toFixed(1),
158
158
  /* @__PURE__ */ jsx(Star, { className: "h-3.5 w-3.5 lg:h-4 lg:w-4", "aria-hidden": true })
159
159
  ] })
160
160
  ] }),
161
- (meta || labels.reviewCount) && /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-[12.5px] text-white/70", children: [meta, labels.reviewCount].filter(Boolean).join(" \xB7 ") })
161
+ (meta || labels.reviewCount || (grade == null ? void 0 : grade.overall) && rating !== void 0) && /* @__PURE__ */ jsxs("p", { className: "mt-1 flex min-w-0 items-center gap-1 text-[12.5px] text-white/70", children: [
162
+ (meta || labels.reviewCount) && /* @__PURE__ */ jsx("span", { className: "truncate", children: [meta, labels.reviewCount].filter(Boolean).join(" \xB7 ") }),
163
+ (grade == null ? void 0 : grade.overall) && rating !== void 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 tabular-nums", children: [
164
+ (meta || labels.reviewCount) && /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\xB7" }),
165
+ rating.toFixed(1),
166
+ /* @__PURE__ */ jsx(Star, { className: "h-3 w-3", "aria-hidden": true })
167
+ ] })
168
+ ] })
162
169
  ] }),
163
170
  parts.length > 0 && /* @__PURE__ */ jsx(
164
171
  StoryPanel,
@@ -1,4 +1,4 @@
1
- import { Button } from '../chunk-JS3EINZD.js';
1
+ import { Button } from '../chunk-VAXOQS7W.js';
2
2
  import '../chunk-R47NEOSI.js';
3
3
  import '../chunk-FEK5XGZW.js';
4
4
  import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
@@ -5,15 +5,16 @@ import 'react';
5
5
  import './place-types.js';
6
6
 
7
7
  /**
8
- * MenuFamilyBlock — the cuisines index's family card. The mosaic is the ground.
8
+ * MenuFamilyBlock — the cuisines index's family card. One photograph is the ground.
9
9
  *
10
10
  * ## Three layers, and everything floats on them
11
11
  *
12
- * The grid of venue photographs is the background for the ENTIRE card, edge to
13
- * edge, and the facts sit on it. Three layers do the work:
12
+ * One venue photograph is the background for the ENTIRE card, edge to edge,
13
+ * and the facts sit on it. Three layers do the work:
14
14
  *
15
- * 1. **The mosaic**, full-bleed, wearing the family's hue through a
16
- * `mix-blend-color` layer.
15
+ * 1. **The photograph**, full-bleed, wearing the family's hue through a
16
+ * `mix-blend-color` layer. It was a mosaic of six; the design draws one
17
+ * plate, and six tiles under the ink read as texture rather than a room.
17
18
  * 2. **An ink gradient** — absent where the photography is the subject, full
18
19
  * where the rows begin. This is what buys the contrast. There used to be a
19
20
  * masked backdrop-filter under it; it was removed for the measurement
@@ -22,10 +23,9 @@ import './place-types.js';
22
23
  *
23
24
  * On the phone the ink runs top to bottom: the name and the action sit on
24
25
  * clear photography, the chips and the rows on the inked foot. On desktop the
25
- * same layers turn ninety degrees the photo column on the left stays clear,
26
- * the row column on the right is the inked one. The mosaic
27
- * runs 3×2 there rather than 2×2, because at 1110px a four-tile grid makes each
28
- * photograph too big to read as texture.
26
+ * photo column on the left (a third) stays clear, and the row column on the
27
+ * right is opaque paper in the card's own dark three venue rows never sit
28
+ * on a photograph.
29
29
  *
30
30
  * ## The dark scope
31
31
  *
@@ -67,6 +67,12 @@ type MenuFamilyBlockLabels = {
67
67
  priceBand: string;
68
68
  /** Main action, e.g. "Browse Italian". Sits on the photography — keep it short. */
69
69
  browse: string;
70
+ /**
71
+ * The action from `lg`, where the photo column is a third of the card and
72
+ * "Browse Bakeries & patisseries" does not fit — e.g. "Browse". The full
73
+ * `browse` stays the accessible name.
74
+ */
75
+ browseShort?: string;
70
76
  /** Passed through to the venue rows. */
71
77
  kitchens: MenuKitchenListLabels;
72
78
  };
@@ -1,8 +1,10 @@
1
1
  import { MenuKitchenList } from '../chunk-24RFI4RQ.js';
2
2
  import { HueChip } from '../chunk-CLLY6XB3.js';
3
3
  import { Separator } from '../chunk-WSRWFA6K.js';
4
- import { PhotoGround, PHOTO_GROUND } from '../chunk-5RWTOT5Y.js';
4
+ import { PhotoGround, PHOTO_GROUND } from '../chunk-CNJQUHCU.js';
5
5
  import { Skeleton } from '../chunk-QXHMPJ35.js';
6
+ import { buttonVariants } from '../chunk-VAXOQS7W.js';
7
+ import '../chunk-R47NEOSI.js';
6
8
  import { cn } from '../chunk-FEK5XGZW.js';
7
9
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
8
10
  import { Wallet } from 'lucide-react';
@@ -12,10 +14,9 @@ function DefaultLink(_a) {
12
14
  var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
13
15
  return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
14
16
  }
15
- var MOSAIC_TILES = 6;
16
- var PHONE_TILES = 4;
17
17
  var GROUND = PHOTO_GROUND;
18
- var INK = "bg-[linear-gradient(to_bottom,oklch(0.16_0.012_55_/_0.42)_0%,oklch(0.16_0.012_55_/_0.6)_30%,oklch(0.16_0.012_55_/_0.9)_48%,oklch(0.16_0.012_55_/_0.96)_100%)] lg:bg-[linear-gradient(to_right,oklch(0.16_0.012_55_/_0.38)_0%,oklch(0.16_0.012_55_/_0.6)_30%,oklch(0.16_0.012_55_/_0.92)_46%,oklch(0.16_0.012_55_/_0.96)_100%)]";
18
+ var INK = "bg-[linear-gradient(to_bottom,oklch(0.16_0.012_55_/_0.12)_0%,oklch(0.16_0.012_55_/_0.55)_38%,oklch(0.16_0.012_55_/_0.92)_62%,oklch(0.16_0.012_55_/_0.96)_100%)] lg:bg-[linear-gradient(to_right,oklch(0.16_0.012_55_/_0.38)_0%,oklch(0.16_0.012_55_/_0.6)_30%,oklch(0.16_0.012_55_/_0.92)_46%,oklch(0.16_0.012_55_/_0.96)_100%)]";
19
+ var PAPER = "lg:bg-[oklch(0.21_0.012_55)]";
19
20
  var TEXT_SCRIM = (
20
21
  /* `isolate` + `before:-z-10`: a positioned pseudo-element paints ABOVE the
21
22
  column's in-flow text unless the column is its own stacking context and
@@ -32,11 +33,11 @@ function MenuFamilyBlock({
32
33
  linkComponent: LinkComponent = DefaultLink,
33
34
  className
34
35
  }) {
35
- var _a, _b, _c, _d, _e;
36
- const photos = ((_a = family.photoUrls) != null ? _a : []).slice(0, MOSAIC_TILES);
37
- const overflow = Math.max(family.venueCount - photos.length, 0);
38
- const initial = ((_c = (_b = family.name.match(new RegExp("\\p{L}", "u"))) == null ? void 0 : _b[0]) != null ? _c : "").toLocaleUpperCase();
39
- const venues = (_d = family.venues) != null ? _d : [];
36
+ var _a, _b, _c, _d;
37
+ const [photo] = (_a = family.photoUrls) != null ? _a : [];
38
+ const venues = (_b = family.venues) != null ? _b : [];
39
+ const overflow = Math.max(family.venueCount - venues.length, 0);
40
+ const initial = ((_d = (_c = family.name.match(new RegExp("\\p{L}", "u"))) == null ? void 0 : _c[0]) != null ? _d : "").toLocaleUpperCase();
40
41
  return /* @__PURE__ */ jsxs(
41
42
  "article",
42
43
  {
@@ -48,54 +49,56 @@ function MenuFamilyBlock({
48
49
  className
49
50
  ),
50
51
  children: [
51
- /* @__PURE__ */ jsx(
52
- PhotoGround,
53
- {
54
- photos: photos.length > 0 ? photos : [void 0],
55
- initials: initial,
56
- mosaicClassName: photos.length > 0 ? "grid-cols-2 grid-rows-2 lg:grid-cols-3" : "grid-cols-1",
57
- phoneTiles: PHONE_TILES,
58
- gradient
59
- }
60
- ),
52
+ /* @__PURE__ */ jsx(PhotoGround, { photos: [photo], initials: initial, mode: "stack", gradient }),
61
53
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn("absolute inset-0", INK) }),
62
- /* @__PURE__ */ jsxs("div", { className: "relative grid grid-cols-1 lg:grid-cols-[38%_1fr]", children: [
63
- /* @__PURE__ */ jsxs("div", { className: cn("relative flex flex-col p-3.5 lg:min-h-[320px] lg:justify-end lg:p-5", TEXT_SCRIM), children: [
64
- overflow > 0 && /* @__PURE__ */ jsx("span", { className: "mb-16 self-end rounded-full bg-[oklch(0.16_0.012_55_/_0.78)] px-2.5 py-1 text-[11px] font-semibold text-white lg:mb-3 lg:self-start", children: labels.more(overflow) }),
65
- /* @__PURE__ */ jsx("h3", { className: "text-balance font-serif text-[26px] font-semibold leading-tight tracking-[-0.02em] text-white lg:text-[30px]", children: family.name }),
66
- /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-[13px] font-medium tabular-nums text-white/70", children: labels.venueUnit(family.venueCount) }),
67
- /* @__PURE__ */ jsx(
68
- LinkComponent,
69
- {
70
- href: family.href,
71
- className: "mt-3.5 inline-flex h-10 w-max items-center rounded-full border border-white/20 bg-white/[0.16] px-4 text-[13.5px] font-bold text-white transition-colors hover:bg-white/25 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white motion-reduce:transition-none",
72
- children: labels.browse
73
- }
74
- )
75
- ] }),
76
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col px-3.5 pb-3.5 lg:p-5", children: [
77
- /* @__PURE__ */ jsxs("ul", { className: "flex flex-wrap items-center gap-1.5", children: [
78
- family.priceBandLabel && /* No hue: a fact about money, not about the family. In the dark
79
- scope `HueChip`'s `--muted` fallback is the dark pill the design
80
- draws. */
81
- /* @__PURE__ */ jsx(
82
- HueChip,
83
- {
84
- as: "li",
85
- className: "font-bold tabular-nums",
86
- "aria-label": `${labels.priceBand}: ${family.priceBandLabel}`,
87
- icon: /* @__PURE__ */ jsx(Wallet, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
88
- children: family.priceBandLabel
89
- }
54
+ /* @__PURE__ */ jsxs("div", { className: "relative grid grid-cols-1 lg:grid-cols-[34%_1fr]", children: [
55
+ /* @__PURE__ */ jsxs(
56
+ "div",
57
+ {
58
+ className: cn(
59
+ "relative flex min-h-[220px] flex-col justify-end p-3.5 lg:min-h-[320px] lg:p-5",
60
+ TEXT_SCRIM
90
61
  ),
91
- (_e = family.chips) == null ? void 0 : _e.map((chip) => (
92
- /* Label only. The counts are VENUES beside a dish group's name,
93
- which every reader takes for dishes. */
94
- /* @__PURE__ */ jsx(HueChip, { as: "li", hue, children: chip.label }, chip.slug)
95
- ))
96
- ] }),
62
+ children: [
63
+ overflow > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-3.5 top-3.5 rounded-full bg-[oklch(0.16_0.012_55_/_0.78)] px-2.5 py-1 text-[11px] font-semibold text-white lg:static lg:mb-3 lg:self-start", children: labels.more(overflow) }),
64
+ /* @__PURE__ */ jsx("h3", { className: "text-balance font-serif text-[26px] font-semibold leading-tight tracking-[-0.02em] text-white lg:text-[30px]", children: family.name }),
65
+ /* @__PURE__ */ jsxs("span", { className: "mt-1 flex flex-wrap items-center gap-x-2.5 gap-y-1 text-[13px] font-medium tabular-nums text-white/70", children: [
66
+ labels.venueUnit(family.venueCount),
67
+ family.priceBandLabel && /* @__PURE__ */ jsxs(
68
+ "span",
69
+ {
70
+ className: "inline-flex items-center gap-1",
71
+ "aria-label": `${labels.priceBand}: ${family.priceBandLabel}`,
72
+ children: [
73
+ /* @__PURE__ */ jsx(Wallet, { className: "h-3.5 w-3.5 shrink-0 text-white/60", "aria-hidden": true }),
74
+ family.priceBandLabel
75
+ ]
76
+ }
77
+ )
78
+ ] }),
79
+ /* @__PURE__ */ jsx(
80
+ LinkComponent,
81
+ {
82
+ href: family.href,
83
+ "aria-label": labels.browse,
84
+ className: cn(buttonVariants({ variant: "ink", size: "pill" }), "mt-3.5 w-max max-w-full"),
85
+ children: labels.browseShort ? /* @__PURE__ */ jsxs(Fragment, { children: [
86
+ /* @__PURE__ */ jsx("span", { className: "lg:hidden", children: labels.browse }),
87
+ /* @__PURE__ */ jsx("span", { className: "hidden lg:inline", children: labels.browseShort })
88
+ ] }) : labels.browse
89
+ }
90
+ )
91
+ ]
92
+ }
93
+ ),
94
+ /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 flex-col px-3.5 pb-3.5 lg:p-5", PAPER), children: [
95
+ family.chips && family.chips.length > 0 && /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap items-center gap-1.5 lg:justify-end", children: family.chips.map((chip) => (
96
+ /* Label only. The counts are VENUES beside a dish group's name,
97
+ which every reader takes for dishes. */
98
+ /* @__PURE__ */ jsx(HueChip, { as: "li", hue, children: chip.label }, chip.slug)
99
+ )) }),
97
100
  venues.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
98
- /* @__PURE__ */ jsx(Separator, { className: "my-3" }),
101
+ /* @__PURE__ */ jsx(Separator, { className: "my-3 via-white/15" }),
99
102
  /* @__PURE__ */ jsx(
100
103
  MenuKitchenList,
101
104
  {
@@ -120,12 +123,12 @@ function MenuFamilyBlockSkeleton({
120
123
  {
121
124
  className: cn("dark relative overflow-hidden rounded-[14px]", GROUND, className),
122
125
  "aria-hidden": true,
123
- children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-[38%_1fr]", children: [
124
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col p-3.5 lg:min-h-[320px] lg:justify-end lg:p-5", children: [
125
- /* @__PURE__ */ jsx(Skeleton, { className: "mb-16 h-6 w-[72px] self-end rounded-full lg:mb-3 lg:self-start" }),
126
+ children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-[34%_1fr]", children: [
127
+ /* @__PURE__ */ jsxs("div", { className: "relative flex min-h-[220px] flex-col justify-end p-3.5 lg:min-h-[320px] lg:p-5", children: [
128
+ /* @__PURE__ */ jsx(Skeleton, { className: "absolute right-3.5 top-3.5 h-6 w-[72px] rounded-full lg:static lg:mb-3" }),
126
129
  /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-[60%]" }),
127
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-[90px]" }),
128
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-3.5 h-10 w-[160px] rounded-full" })
130
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-2 h-3.5 w-[150px]" }),
131
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-3.5 h-11 w-[160px] rounded-full" })
129
132
  ] }),
130
133
  /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col px-3.5 pb-3.5 lg:p-5", children: [
131
134
  /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
@@ -1,5 +1,5 @@
1
1
  import { HueChip } from '../chunk-CLLY6XB3.js';
2
- import { PhotoGround, PHOTO_GROUND } from '../chunk-5RWTOT5Y.js';
2
+ import { PhotoGround, PHOTO_GROUND } from '../chunk-CNJQUHCU.js';
3
3
  import { cn } from '../chunk-FEK5XGZW.js';
4
4
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
5
5
  import { Wallet } from 'lucide-react';
@@ -0,0 +1,19 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuFamilyTile, MenuLinkComponent } from './menu-types.js';
3
+ import 'react';
4
+ import './place-types.js';
5
+
6
+ type MenuFamilyTilesProps = {
7
+ tiles: MenuFamilyTile[];
8
+ /**
9
+ * The closing tile: the index behind the seven, drawn exactly like them —
10
+ * its own hue and photograph, `countLabel` as the caption. Only its place in
11
+ * the grid says it is the index. Omit when there is none.
12
+ */
13
+ all?: MenuFamilyTile;
14
+ linkComponent?: MenuLinkComponent;
15
+ className?: string;
16
+ };
17
+ declare function MenuFamilyTiles({ tiles, all, linkComponent: LinkComponent, className, }: MenuFamilyTilesProps): react_jsx_runtime.JSX.Element;
18
+
19
+ export { MenuFamilyTiles, type MenuFamilyTilesProps };
@@ -0,0 +1,57 @@
1
+ import { PhotoGround } from '../chunk-CNJQUHCU.js';
2
+ import { cn } from '../chunk-FEK5XGZW.js';
3
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
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
+ var TILE = "group relative isolate flex aspect-[16/10] min-h-[108px] flex-col justify-end overflow-hidden rounded-2xl bg-muted p-3.5 text-white lg:p-4 transition-[transform,box-shadow] duration-200 hover:-translate-y-0.5 hover:shadow-[var(--shadow-card-hover)] active:translate-y-0 active:scale-[0.99] motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
11
+ var POLAROID = "absolute -right-[4%] -top-[8%] z-0 block aspect-square h-[82%] overflow-hidden rounded-[10%] rotate-[8deg] shadow-[0_10px_22px_-6px_oklch(0.16_0.012_55_/_0.65)] transition-transform duration-300 group-hover:rotate-[5deg] motion-reduce:transition-none";
12
+ var SCRIM = "after:pointer-events-none after:absolute after:inset-x-0 after:bottom-0 after:z-0 after:h-3/5 after:bg-gradient-to-t after:from-[oklch(0.16_0.012_55_/_0.55)] after:to-transparent after:content-['']";
13
+ function MenuFamilyTiles({
14
+ tiles,
15
+ all,
16
+ linkComponent: LinkComponent = DefaultLink,
17
+ className
18
+ }) {
19
+ const cells = all ? [...tiles, all] : tiles;
20
+ return /* @__PURE__ */ jsx("div", { className: cn("grid grid-cols-2 gap-3 lg:grid-cols-4 lg:gap-3.5", className), children: cells.map((tile) => /* @__PURE__ */ jsxs(
21
+ LinkComponent,
22
+ {
23
+ href: tile.href,
24
+ className: cn(TILE, SCRIM),
25
+ style: tile.gradient ? { backgroundImage: tile.gradient } : void 0,
26
+ children: [
27
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: POLAROID, children: tile.photoUrl ? (
28
+ /* Not lazy: the grid sits directly under the hero, and these
29
+ photographs are the first thing the reader looks at. */
30
+ // eslint-disable-next-line @next/next/no-img-element
31
+ /* @__PURE__ */ jsx(
32
+ "img",
33
+ {
34
+ src: tile.photoUrl,
35
+ alt: "",
36
+ decoding: "async",
37
+ className: "absolute inset-0 h-full w-full object-cover"
38
+ }
39
+ )
40
+ ) : /* @__PURE__ */ jsx(
41
+ PhotoGround,
42
+ {
43
+ photos: [void 0],
44
+ initials: tile.label.charAt(0),
45
+ mode: "stack",
46
+ glyphClassName: "text-[44px]"
47
+ }
48
+ ) }),
49
+ /* @__PURE__ */ jsx("span", { className: "relative z-[1] line-clamp-2 max-w-[55%] text-[14.5px] font-extrabold leading-[1.2] tracking-[-0.015em] lg:text-[16px]", children: tile.label }),
50
+ tile.countLabel && /* @__PURE__ */ jsx("span", { className: "relative z-[1] mt-0.5 max-w-[55%] truncate text-[12px] font-medium leading-[1.35] tabular-nums text-white/80 lg:text-[13px]", children: tile.countLabel })
51
+ ]
52
+ },
53
+ tile.slug
54
+ )) });
55
+ }
56
+
57
+ export { MenuFamilyTiles };
@@ -85,14 +85,16 @@ function MenuHubHero({
85
85
  kickerIcon,
86
86
  kicker
87
87
  ] }),
88
- /* @__PURE__ */ jsx("h1", { className: "max-w-[640px] font-serif text-[32px] font-semibold leading-[1.04] tracking-[-0.025em] lg:text-[50px]", children: headline }),
88
+ /* @__PURE__ */ jsx("h1", { className: "max-w-[640px] font-serif text-[28px] font-semibold leading-[1.06] tracking-[-0.025em] lg:text-[50px] lg:leading-[1.04]", children: headline }),
89
89
  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 }),
90
- figures && figures.length > 0 && // Two by two on the phone: four figures in one row wrapped to three and an orphan.
91
- /* @__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: [
92
- /* @__PURE__ */ jsx("div", { className: "text-2xl font-black leading-none tracking-[-0.03em] tabular-nums lg:text-[32px]", children: figure.value }),
93
- /* @__PURE__ */ jsx("div", { className: "mt-[5px] text-xs opacity-[0.72]", children: figure.label })
90
+ figures && figures.length > 0 && // One row of four on the phone. It was two by two, which was the
91
+ // taller half of a 500px hero; at 20px the widest figure ("19,090")
92
+ // is ~68px and four of them share a 358px column with room.
93
+ /* @__PURE__ */ jsx("div", { className: "flex justify-between gap-x-3 pt-2 lg:flex-wrap lg:justify-start lg:gap-x-8 lg:gap-y-4 lg:pt-1.5", children: figures.map((figure) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
94
+ /* @__PURE__ */ jsx("div", { className: "text-xl font-black leading-none tracking-[-0.02em] tabular-nums lg:text-[32px] lg:tracking-[-0.03em]", children: figure.value }),
95
+ /* @__PURE__ */ jsx("div", { className: "mt-1 text-[11px] leading-tight opacity-[0.72] lg:mt-[5px] lg:text-xs", children: figure.label })
94
96
  ] }, figure.label)) }),
95
- (proof || action) && /* @__PURE__ */ jsxs("div", { className: "mt-auto flex flex-wrap items-center gap-3 pt-5", children: [
97
+ (proof || action) && /* @__PURE__ */ jsxs("div", { className: "mt-auto flex flex-wrap items-center gap-3 pt-4 lg:pt-5", children: [
96
98
  proof && /* @__PURE__ */ jsxs("span", { className: "inline-flex min-h-11 items-center gap-2.5 rounded-full bg-white/[0.14] px-4 py-2 text-[13px]", children: [
97
99
  proofIcon,
98
100
  proof
@@ -4,19 +4,24 @@ import 'react';
4
4
  import './place-types.js';
5
5
 
6
6
  /**
7
- * MenuIndexList — the complete set, four across.
7
+ * MenuIndexList — the complete set: one column on a phone, three from `lg`.
8
8
  *
9
9
  * ## This block is why the page can afford to be progressive
10
10
  *
11
11
  * It carries a crawlable link to EVERY destination the city has — all
12
- * thirty-eight cuisines, not the ten the page draws in full below. That makes
13
- * it the page's entire link-equity job, which in turn means the rich blocks
14
- * underneath are free to load ten at a time behind a "show more": nothing a
15
- * crawler needs is hidden behind that control, because it is all up here.
12
+ * thirty-eight cuisines, not the eight the page draws in full above. That
13
+ * makes it the page's entire link-equity job, which in turn means the rich
14
+ * blocks are free to load eight at a time behind a "show more": nothing a
15
+ * crawler needs is hidden behind that control, because it is all here.
16
16
  *
17
- * Get this backwards put the whole set behind the disclosure and only ten
18
- * links in the markup and the page silently stops linking to twenty-eight
19
- * pages it is supposed to be the index for.
17
+ * The phone's own "show all" is CSS, not markup: every row is in the DOM and
18
+ * the ones past `collapsedCount` are hidden below `lg` until the button is
19
+ * pressed. A list that mounted its tail on tap would have taken twenty-six
20
+ * links out of the page for the crawler that never taps.
21
+ *
22
+ * Get this backwards — put the whole set behind the disclosure and only eight
23
+ * links in the markup — and the page silently stops linking to thirty pages
24
+ * it is supposed to be the index for.
20
25
  *
21
26
  * ## Ordered by count, and the counts overlap
22
27
  *
@@ -24,6 +29,10 @@ import './place-types.js';
24
29
  * places, because a trattoria is Italian AND pizza AND international. So this
25
30
  * is a ranked list of ways in, never a partition, and no copy near it may total
26
31
  * the numbers or call them a breakdown.
32
+ *
33
+ * A client component for the one piece of state the expander needs; the
34
+ * `linkComponent` a server caller hands in must therefore be a client
35
+ * reference (`ClientLink`), not `next/link` itself.
27
36
  */
28
37
  /** @deprecated Alias of `MenuLinkProps`; kept for symmetry with the other menu surfaces. */
29
38
  type MenuIndexListLinkProps = MenuLinkProps;
@@ -33,13 +42,23 @@ type MenuIndexListProps = {
33
42
  title: string;
34
43
  /** One line under it, e.g. "Ordered by kitchens — every one has its own page". */
35
44
  subtitle?: string;
36
- /** Locale-aware integer formatter. Defaults to `String`. */
37
- formatCount?: (value: number) => string;
45
+ /**
46
+ * Rows shown on a phone before the expander; the rest are hidden below `lg`
47
+ * until it is pressed. Omit to show every row at every width.
48
+ */
49
+ collapsedCount?: number;
50
+ /** The expander's labels, e.g. "Show all 38 cuisines" / "Show fewer". */
51
+ expandLabels?: {
52
+ showAll: string;
53
+ showFewer: string;
54
+ };
55
+ /** The colour mark beside each name, keyed by slug. Falls back to the border colour. */
56
+ hues?: Record<string, string>;
38
57
  linkComponent?: MenuLinkComponent;
39
58
  className?: string;
40
59
  };
41
- declare function MenuIndexList({ entries, title, subtitle, formatCount, linkComponent: LinkComponent, className, }: MenuIndexListProps): react_jsx_runtime.JSX.Element;
42
- /** Two columns on a phone, four from `lg` — the grid the list itself renders. */
60
+ declare function MenuIndexList({ entries, title, subtitle, collapsedCount, expandLabels, hues, linkComponent: LinkComponent, className, }: MenuIndexListProps): react_jsx_runtime.JSX.Element;
61
+ /** One column on a phone, three from `lg` — the grid the list itself renders. */
43
62
  declare function MenuIndexListSkeleton({ rows, className, }: {
44
63
  rows?: number;
45
64
  className?: string;
@@ -1,8 +1,11 @@
1
1
  import { Separator } from '../chunk-WSRWFA6K.js';
2
2
  import { Skeleton } from '../chunk-QXHMPJ35.js';
3
+ import { Button } from '../chunk-VAXOQS7W.js';
4
+ import '../chunk-R47NEOSI.js';
3
5
  import { cn } from '../chunk-FEK5XGZW.js';
4
6
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
5
- import { ChevronRight } from 'lucide-react';
7
+ import { useState } from 'react';
8
+ import { ChevronRight, ChevronDown } from 'lucide-react';
6
9
  import { jsxs, jsx } from 'react/jsx-runtime';
7
10
 
8
11
  function DefaultLink(_a) {
@@ -13,10 +16,14 @@ function MenuIndexList({
13
16
  entries,
14
17
  title,
15
18
  subtitle,
16
- formatCount = String,
19
+ collapsedCount,
20
+ expandLabels,
21
+ hues,
17
22
  linkComponent: LinkComponent = DefaultLink,
18
23
  className
19
24
  }) {
25
+ const [expanded, setExpanded] = useState(false);
26
+ const collapsible = collapsedCount !== void 0 && expandLabels !== void 0 && entries.length > collapsedCount;
20
27
  return /* @__PURE__ */ jsxs(
21
28
  "section",
22
29
  {
@@ -29,44 +36,74 @@ function MenuIndexList({
29
36
  /* @__PURE__ */ jsx("h2", { className: "text-[15px] font-bold tracking-[-0.01em]", children: title }),
30
37
  subtitle && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[12.5px] text-muted-foreground", children: subtitle })
31
38
  ] }),
32
- /* @__PURE__ */ jsx("ul", { className: "grid grid-cols-2 gap-x-5 lg:grid-cols-4 lg:gap-x-8", children: entries.map((entry) => /* @__PURE__ */ jsxs("li", { children: [
33
- /* @__PURE__ */ jsx(Separator, {}),
34
- /* @__PURE__ */ jsxs(
35
- LinkComponent,
39
+ /* @__PURE__ */ jsx("ul", { className: "grid grid-cols-1 lg:grid-cols-3 lg:gap-x-8", children: entries.map((entry, index) => {
40
+ var _a;
41
+ const hue = hues == null ? void 0 : hues[entry.slug];
42
+ return /* @__PURE__ */ jsxs(
43
+ "li",
36
44
  {
37
- href: entry.href,
38
- className: "group flex items-center gap-2.5 py-2.5 transition-colors hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring motion-reduce:transition-none",
45
+ className: cn(
46
+ collapsible && !expanded && index >= collapsedCount && "max-lg:hidden"
47
+ ),
39
48
  children: [
40
- /* @__PURE__ */ jsx(
41
- "span",
42
- {
43
- "aria-hidden": true,
44
- className: cn(
45
- "h-2.5 w-2.5 shrink-0 rounded-[3px]",
46
- !entry.gradient && "bg-border"
47
- ),
48
- style: entry.gradient ? { backgroundImage: entry.gradient } : void 0
49
- }
50
- ),
51
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-[13.5px]", children: entry.name }),
52
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[13px] tabular-nums text-muted-foreground", children: formatCount(entry.count) }),
53
- /* @__PURE__ */ jsx(
54
- ChevronRight,
49
+ /* @__PURE__ */ jsx(Separator, {}),
50
+ /* @__PURE__ */ jsxs(
51
+ LinkComponent,
55
52
  {
56
- className: "h-3.5 w-3.5 shrink-0 text-muted-foreground transition-colors group-hover:text-accent motion-reduce:transition-none",
57
- "aria-hidden": true
53
+ href: entry.href,
54
+ className: "group flex items-center gap-2.5 py-2.5 transition-colors hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring motion-reduce:transition-none",
55
+ children: [
56
+ /* @__PURE__ */ jsx(
57
+ "span",
58
+ {
59
+ "aria-hidden": true,
60
+ className: cn("h-2 w-2 shrink-0 rounded-full", !hue && !entry.gradient && "bg-border"),
61
+ style: hue ? { backgroundColor: hue } : entry.gradient ? { backgroundImage: entry.gradient } : void 0
62
+ }
63
+ ),
64
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: entry.name }),
65
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[13px] tabular-nums text-muted-foreground", children: (_a = entry.countLabel) != null ? _a : String(entry.count) }),
66
+ /* @__PURE__ */ jsx(
67
+ ChevronRight,
68
+ {
69
+ className: "h-3.5 w-3.5 shrink-0 text-muted-foreground transition-colors group-hover:text-accent motion-reduce:transition-none",
70
+ "aria-hidden": true
71
+ }
72
+ )
73
+ ]
58
74
  }
59
75
  )
60
76
  ]
61
- }
62
- )
63
- ] }, entry.slug)) })
77
+ },
78
+ entry.slug
79
+ );
80
+ }) }),
81
+ collapsible && /* @__PURE__ */ jsxs(
82
+ Button,
83
+ {
84
+ variant: "outline",
85
+ fullWidth: true,
86
+ className: "mt-2 lg:hidden",
87
+ "aria-expanded": expanded,
88
+ onClick: () => setExpanded((open) => !open),
89
+ children: [
90
+ expanded ? expandLabels.showFewer : expandLabels.showAll,
91
+ /* @__PURE__ */ jsx(
92
+ ChevronDown,
93
+ {
94
+ className: cn("transition-transform motion-reduce:transition-none", expanded && "rotate-180"),
95
+ "aria-hidden": true
96
+ }
97
+ )
98
+ ]
99
+ }
100
+ )
64
101
  ]
65
102
  }
66
103
  );
67
104
  }
68
105
  function MenuIndexListSkeleton({
69
- rows = 24,
106
+ rows = 12,
70
107
  className
71
108
  }) {
72
109
  return /* @__PURE__ */ jsxs(
@@ -80,10 +117,10 @@ function MenuIndexListSkeleton({
80
117
  children: [
81
118
  /* @__PURE__ */ jsx(Skeleton, { className: "h-[15px] w-[140px]" }),
82
119
  /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-[240px]" }),
83
- /* @__PURE__ */ jsx("ul", { className: "mt-2 grid grid-cols-2 gap-x-5 lg:grid-cols-4 lg:gap-x-8", children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("li", { children: [
120
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 grid grid-cols-1 lg:grid-cols-3 lg:gap-x-8", children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("li", { children: [
84
121
  /* @__PURE__ */ jsx(Separator, {}),
85
122
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 py-2.5", children: [
86
- /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-2.5 rounded-[3px]" }),
123
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-2 w-2 rounded-full" }),
87
124
  /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 flex-1" }),
88
125
  /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-6" })
89
126
  ] })