@bearmenu/ui 0.8.9 → 0.8.11

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 (225) hide show
  1. package/dist/chunk-24RFI4RQ.js +167 -0
  2. package/dist/{chunk-UKBNQO6Q.js → chunk-2CXU5WZO.js} +1 -1
  3. package/dist/chunk-2XCZJWTV.js +121 -0
  4. package/dist/{chunk-62ES6N32.js → chunk-43RC7MHY.js} +1 -1
  5. package/dist/chunk-5RWTOT5Y.js +72 -0
  6. package/dist/{chunk-Q6QH52LJ.js → chunk-6TEEY5YA.js} +2 -2
  7. package/dist/{chunk-AXV5O2IZ.js → chunk-AKQ57NXY.js} +3 -3
  8. package/dist/chunk-CLLY6XB3.js +41 -0
  9. package/dist/chunk-DTXNDLSE.js +125 -0
  10. package/dist/chunk-F6FMZJDG.js +113 -0
  11. package/dist/{chunk-2WDJR6K7.js → chunk-G7IFL4Z4.js} +4 -1
  12. package/dist/chunk-H3ZZDX5L.js +61 -0
  13. package/dist/{chunk-H3BV4VAG.js → chunk-JS3EINZD.js} +2 -2
  14. package/dist/chunk-MZL7CGCL.js +109 -0
  15. package/dist/{chunk-TH6W2PWG.js → chunk-UDTG3RSE.js} +1 -1
  16. package/dist/chunk-WCTHZKU7.js +37 -0
  17. package/dist/{chunk-TT3WFS6S.js → chunk-YOKHBAIH.js} +11 -11
  18. package/dist/components/alert-dialog.js +1 -1
  19. package/dist/components/alert.d.ts +2 -2
  20. package/dist/components/avatar.d.ts +3 -3
  21. package/dist/components/badge.d.ts +1 -1
  22. package/dist/components/badge.js +1 -1
  23. package/dist/components/button.d.ts +2 -2
  24. package/dist/components/button.js +1 -1
  25. package/dist/components/calendar.js +1 -1
  26. package/dist/components/card.d.ts +4 -4
  27. package/dist/components/carousel.js +1 -1
  28. package/dist/components/combobox.js +1 -1
  29. package/dist/components/command.d.ts +2 -2
  30. package/dist/components/concept-compare-rail.d.ts +50 -0
  31. package/dist/components/concept-compare-rail.js +5 -0
  32. package/dist/components/concept-compare.d.ts +75 -38
  33. package/dist/components/concept-compare.js +209 -133
  34. package/dist/components/concept-convergence.d.ts +41 -0
  35. package/dist/components/concept-convergence.js +53 -0
  36. package/dist/components/convergence.d.ts +25 -0
  37. package/dist/components/convergence.js +4 -0
  38. package/dist/components/dialog.d.ts +2 -2
  39. package/dist/components/empty-state.js +1 -1
  40. package/dist/components/event-card.js +1 -1
  41. package/dist/components/event-compact-row.js +1 -1
  42. package/dist/components/event-detail-panel.js +2 -2
  43. package/dist/components/event-index-row.js +1 -1
  44. package/dist/components/event-list-row-wide.js +2 -2
  45. package/dist/components/event-list-row.js +2 -2
  46. package/dist/components/explainer-steps.d.ts +34 -0
  47. package/dist/components/explainer-steps.js +36 -0
  48. package/dist/components/filter-category-row.js +1 -1
  49. package/dist/components/glass-ground.d.ts +61 -0
  50. package/dist/components/glass-ground.js +3 -0
  51. package/dist/components/haptic-button.js +1 -1
  52. package/dist/components/hub-surface.js +11 -1
  53. package/dist/components/hue-chip.d.ts +70 -0
  54. package/dist/components/hue-chip.js +3 -0
  55. package/dist/components/input-group.d.ts +2 -2
  56. package/dist/components/input.d.ts +1 -1
  57. package/dist/components/item.d.ts +4 -4
  58. package/dist/components/kbd.d.ts +2 -2
  59. package/dist/components/kitchen-dossier.d.ts +88 -0
  60. package/dist/components/kitchen-dossier.js +186 -0
  61. package/dist/components/link-button.js +1 -1
  62. package/dist/components/menu-dead-end.d.ts +56 -0
  63. package/dist/components/menu-dead-end.js +49 -0
  64. package/dist/components/menu-family-block.d.ts +98 -0
  65. package/dist/components/menu-family-block.js +153 -0
  66. package/dist/components/menu-family-card.d.ts +33 -27
  67. package/dist/components/menu-family-card.js +41 -85
  68. package/dist/components/menu-field.js +92 -2
  69. package/dist/components/menu-filter-chips.d.ts +86 -0
  70. package/dist/components/menu-filter-chips.js +122 -0
  71. package/dist/components/menu-group-rows.d.ts +53 -0
  72. package/dist/components/menu-group-rows.js +67 -0
  73. package/dist/components/menu-hub-hero.js +10 -5
  74. package/dist/components/menu-index-header.d.ts +37 -0
  75. package/dist/components/menu-index-header.js +68 -0
  76. package/dist/components/menu-index-list.d.ts +48 -0
  77. package/dist/components/menu-index-list.js +96 -0
  78. package/dist/components/menu-intent-hero.d.ts +69 -0
  79. package/dist/components/menu-intent-hero.js +87 -0
  80. package/dist/components/menu-item.js +3 -3
  81. package/dist/components/menu-kitchen-list.d.ts +82 -0
  82. package/dist/components/menu-kitchen-list.js +5 -0
  83. package/dist/components/menu-mocks.d.ts +37 -2
  84. package/dist/components/menu-mocks.js +152 -27
  85. package/dist/components/menu-types.d.ts +182 -6
  86. package/dist/components/motion-icon-controls.js +2 -2
  87. package/dist/components/motion-icon-set.js +6 -6
  88. package/dist/components/motion-icon.d.ts +22 -1
  89. package/dist/components/motion-icon.js +1 -1
  90. package/dist/components/multi-select-combobox.js +2 -2
  91. package/dist/components/native-select.d.ts +2 -2
  92. package/dist/components/pagination.js +1 -1
  93. package/dist/components/photo-ground.d.ts +64 -0
  94. package/dist/components/photo-ground.js +3 -0
  95. package/dist/components/place-about-tab.js +2 -2
  96. package/dist/components/place-details-mobile.js +3 -3
  97. package/dist/components/place-types.d.ts +2 -2
  98. package/dist/components/qa-block.d.ts +36 -0
  99. package/dist/components/qa-block.js +24 -0
  100. package/dist/components/scroll-fade.js +3 -121
  101. package/dist/components/searchable-select.js +1 -1
  102. package/dist/components/select.d.ts +1 -1
  103. package/dist/components/signature-chip.d.ts +2 -2
  104. package/dist/components/signature-chip.js +2 -2
  105. package/dist/components/sliding-panels.js +1 -1
  106. package/dist/components/snap-rail.d.ts +26 -3
  107. package/dist/components/snap-rail.js +3 -109
  108. package/dist/components/spinner.d.ts +4 -4
  109. package/dist/components/story.d.ts +34 -0
  110. package/dist/components/story.js +3 -0
  111. package/dist/components/tag.d.ts +4 -4
  112. package/dist/components/text.d.ts +5 -5
  113. package/dist/components/toggle.d.ts +1 -1
  114. package/dist/components/top-comparison.d.ts +28 -0
  115. package/dist/components/top-comparison.js +76 -0
  116. package/dist/components/top-cross-link.d.ts +34 -0
  117. package/dist/components/top-cross-link.js +101 -0
  118. package/dist/components/top-ranking-entry.d.ts +53 -0
  119. package/dist/components/top-ranking-entry.js +217 -0
  120. package/dist/components/top-ranking-hero.d.ts +61 -0
  121. package/dist/components/top-ranking-hero.js +99 -0
  122. package/dist/components/top-ranking-legend.d.ts +69 -0
  123. package/dist/components/top-ranking-legend.js +124 -0
  124. package/dist/components/top-types.d.ts +109 -0
  125. package/dist/components/top-types.js +1 -0
  126. package/dist/components/venue-grade-dimensions.d.ts +3 -1
  127. package/dist/components/venue-grade-dimensions.js +1 -1
  128. package/dist/components/venue-quotes.d.ts +1 -1
  129. package/dist/components/venue-quotes.js +1 -1
  130. package/dist/components/venue-wall.js +5 -3
  131. package/dist/components/weekday-theme-banner.js +1 -1
  132. package/dist/components/weekend-panel.js +2 -2
  133. package/package.json +1 -1
  134. package/src/components/badge.tsx +1 -1
  135. package/src/components/button.tsx +2 -2
  136. package/src/components/concept-compare-rail.tsx +132 -0
  137. package/src/components/concept-compare.stories.tsx +122 -54
  138. package/src/components/concept-compare.tsx +368 -196
  139. package/src/components/concept-convergence.stories.tsx +53 -0
  140. package/src/components/concept-convergence.tsx +90 -0
  141. package/src/components/convergence.stories.tsx +54 -0
  142. package/src/components/convergence.tsx +177 -0
  143. package/src/components/explainer-steps.stories.tsx +46 -0
  144. package/src/components/explainer-steps.tsx +74 -0
  145. package/src/components/glass-ground.tsx +104 -0
  146. package/src/components/hub-surface.tsx +11 -1
  147. package/src/components/hue-chip.stories.tsx +76 -0
  148. package/src/components/hue-chip.test.tsx +62 -0
  149. package/src/components/hue-chip.tsx +99 -0
  150. package/src/components/kitchen-dossier.stories.tsx +108 -0
  151. package/src/components/kitchen-dossier.tsx +321 -0
  152. package/src/components/menu-dead-end.stories.tsx +67 -0
  153. package/src/components/menu-dead-end.tsx +127 -0
  154. package/src/components/menu-family-block.stories.tsx +91 -0
  155. package/src/components/menu-family-block.tsx +303 -0
  156. package/src/components/menu-family-card.stories.tsx +43 -35
  157. package/src/components/menu-family-card.tsx +115 -142
  158. package/src/components/menu-filter-chips.stories.tsx +107 -0
  159. package/src/components/menu-filter-chips.tsx +238 -0
  160. package/src/components/menu-group-rows.stories.tsx +54 -0
  161. package/src/components/menu-group-rows.tsx +133 -0
  162. package/src/components/menu-hub-hero.test.tsx +39 -0
  163. package/src/components/menu-hub-hero.tsx +17 -5
  164. package/src/components/menu-index-header.stories.tsx +72 -0
  165. package/src/components/menu-index-header.tsx +125 -0
  166. package/src/components/menu-index-list.stories.tsx +50 -0
  167. package/src/components/menu-index-list.tsx +149 -0
  168. package/src/components/menu-intent-hero.stories.tsx +67 -0
  169. package/src/components/menu-intent-hero.tsx +189 -0
  170. package/src/components/menu-kitchen-list.stories.tsx +67 -0
  171. package/src/components/menu-kitchen-list.test.tsx +78 -0
  172. package/src/components/menu-kitchen-list.tsx +284 -0
  173. package/src/components/menu-mocks.ts +198 -23
  174. package/src/components/menu-types.ts +189 -6
  175. package/src/components/motion-icon-controls.tsx +11 -11
  176. package/src/components/motion-icon-set.tsx +6 -5
  177. package/src/components/motion-icon.test.tsx +23 -1
  178. package/src/components/motion-icon.tsx +20 -0
  179. package/src/components/photo-ground.tsx +147 -0
  180. package/src/components/place-types.ts +2 -2
  181. package/src/components/qa-block.stories.tsx +43 -0
  182. package/src/components/qa-block.tsx +60 -0
  183. package/src/components/signature-chip.tsx +3 -3
  184. package/src/components/snap-rail.tsx +58 -15
  185. package/src/components/story.tsx +225 -0
  186. package/src/components/top-comparison.stories.tsx +78 -0
  187. package/src/components/top-comparison.tsx +151 -0
  188. package/src/components/top-cross-link.stories.tsx +84 -0
  189. package/src/components/top-cross-link.tsx +171 -0
  190. package/src/components/top-ranking-entry.stories.tsx +171 -0
  191. package/src/components/top-ranking-entry.test.tsx +105 -0
  192. package/src/components/top-ranking-entry.tsx +425 -0
  193. package/src/components/top-ranking-hero.stories.tsx +76 -0
  194. package/src/components/top-ranking-hero.tsx +218 -0
  195. package/src/components/top-ranking-legend.stories.tsx +78 -0
  196. package/src/components/top-ranking-legend.tsx +239 -0
  197. package/src/components/top-types.ts +110 -0
  198. package/src/components/venue-grade-dimensions.tsx +2 -2
  199. package/src/components/venue-quotes.stories.tsx +1 -1
  200. package/src/components/venue-quotes.tsx +4 -2
  201. package/src/components/venue-wall.tsx +9 -4
  202. package/src/components/weekday-theme-banner.tsx +1 -1
  203. package/dist/chunk-THOVPZNN.js +0 -37
  204. package/dist/chunk-YS6BHKZO.js +0 -92
  205. package/dist/components/ranked-venue-list.d.ts +0 -49
  206. package/dist/components/ranked-venue-list.js +0 -87
  207. package/dist/components/signature-line-list.d.ts +0 -45
  208. package/dist/components/signature-line-list.js +0 -99
  209. package/dist/components/venue-busyness-strip.d.ts +0 -35
  210. package/dist/components/venue-busyness-strip.js +0 -37
  211. package/dist/components/venue-signature-block.d.ts +0 -39
  212. package/dist/components/venue-signature-block.js +0 -6
  213. package/dist/components/venue-spotlight.d.ts +0 -74
  214. package/dist/components/venue-spotlight.js +0 -138
  215. package/src/components/ranked-venue-list.stories.tsx +0 -80
  216. package/src/components/ranked-venue-list.tsx +0 -177
  217. package/src/components/signature-line-list.stories.tsx +0 -85
  218. package/src/components/signature-line-list.tsx +0 -136
  219. package/src/components/venue-busyness-strip.stories.tsx +0 -66
  220. package/src/components/venue-busyness-strip.tsx +0 -64
  221. package/src/components/venue-signature-block.stories.tsx +0 -68
  222. package/src/components/venue-signature-block.tsx +0 -71
  223. package/src/components/venue-spotlight.stories.tsx +0 -141
  224. package/src/components/venue-spotlight.tsx +0 -236
  225. package/dist/{chunk-XWMRABZW.js → chunk-HSIYYA2E.js} +1 -1
@@ -6,7 +6,7 @@ import type { CitedText } from "./place-types";
6
6
  *
7
7
  * These are `reviews_digest.highlights`: one to three short positives already
8
8
  * resolved into the request locale. They are the only place on a hub where a
9
- * reviewer speaks in their own register, so they are set in serif italic
9
+ * reviewer speaks in their own register, so they are set in quoted serif
10
10
  * against the sans everything else uses.
11
11
  *
12
12
  * `onCite` turns a quote into a button that opens the reviews behind it —
@@ -39,8 +39,10 @@ export function VenueQuotes({ quotes, onCite, citeLabel, limit, className }: Ven
39
39
  const citations = quote.review_ids?.length ?? 0;
40
40
  const canCite = !!onCite && citations > 0;
41
41
 
42
+ // Upright, quoted: the italic face was the only reason the consumer's
43
+ // menu page loaded 281 KB of Fraunces italic at navigation time.
42
44
  const text = (
43
- <blockquote className="border-l-2 border-border pl-3 font-serif text-[13px] italic leading-[1.45] text-muted-foreground">
45
+ <blockquote className="border-l-2 border-border pl-3 font-serif text-[13px] leading-[1.45] text-muted-foreground">
44
46
  {`“${quote.text}”`}
45
47
  </blockquote>
46
48
  );
@@ -42,6 +42,9 @@ export type VenueWallProps = {
42
42
  linkComponent?: MenuLinkComponent;
43
43
  };
44
44
 
45
+ const WALL_CELLS = 18;
46
+ const PHONE_CELLS = 6;
47
+
45
48
  export function VenueWall({
46
49
  places,
47
50
  hrefs,
@@ -64,8 +67,10 @@ export function VenueWall({
64
67
  >
65
68
  {tileUrls?.length ? (
66
69
  <>
67
- <div aria-hidden className="absolute inset-0 grid auto-rows-fr grid-cols-4 gap-0.5 lg:grid-cols-6">
68
- {Array.from({ length: 18 }, (_, cell) => (
70
+ <div aria-hidden className="absolute inset-0 grid auto-rows-fr grid-cols-3 gap-0.5 lg:grid-cols-6">
71
+ {/* Six cells on the phone, eighteen from `lg` — the hero's rule:
72
+ `hidden` keeps the lazy stills past the sixth from loading. */}
73
+ {Array.from({ length: WALL_CELLS }, (_, cell) => (
69
74
  // eslint-disable-next-line @next/next/no-img-element
70
75
  <img
71
76
  key={cell}
@@ -73,7 +78,7 @@ export function VenueWall({
73
78
  alt=""
74
79
  loading="lazy"
75
80
  decoding="async"
76
- className="h-full w-full object-cover"
81
+ className={cn("h-full w-full object-cover", cell >= PHONE_CELLS && "hidden lg:block")}
77
82
  />
78
83
  ))}
79
84
  </div>
@@ -151,7 +156,7 @@ export function VenueWall({
151
156
  );
152
157
 
153
158
  const chipClasses =
154
- "inline-flex max-w-[220px] items-center gap-2 rounded-full bg-white/10 py-1 pl-1 pr-3.5 backdrop-blur-[6px]";
159
+ "inline-flex max-w-[220px] items-center gap-2 rounded-full bg-white/[0.14] py-1 pl-1 pr-3.5";
155
160
 
156
161
  return (
157
162
  <li key={place.slug}>
@@ -159,7 +159,7 @@ export function WeekdayThemeBanner({
159
159
  "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",
160
160
  // Over a photograph the rail needs its own ground, or seven lines of
161
161
  // small type land on whatever the plate happens to be.
162
- imageUrl && "bg-black/45 backdrop-blur-[2px]"
162
+ imageUrl && "bg-black/55"
163
163
  )}
164
164
  >
165
165
  {weekLabel && (
@@ -1,37 +0,0 @@
1
- import { MenuItem } from './chunk-TH6W2PWG.js';
2
- import { cn } from './chunk-FEK5XGZW.js';
3
- import { jsxs, jsx } from 'react/jsx-runtime';
4
-
5
- function VenueSignatureBlock({
6
- item,
7
- labels,
8
- formatPrice,
9
- formatCount,
10
- kicker,
11
- markGradient,
12
- subtitle,
13
- href,
14
- linkComponent,
15
- className
16
- }) {
17
- return /* @__PURE__ */ jsxs("div", { className: cn("overflow-hidden rounded-xl bg-accent-subtle py-1", className), children: [
18
- /* @__PURE__ */ jsx("span", { className: "block px-4 pt-2 text-[9.5px] font-bold uppercase tracking-[0.09em] text-muted-foreground", children: kicker }),
19
- /* @__PURE__ */ jsx(
20
- MenuItem,
21
- {
22
- item,
23
- labels,
24
- formatPrice,
25
- formatCount,
26
- markGradient,
27
- subtitle,
28
- showSignatureBadge: false,
29
- href,
30
- linkComponent,
31
- className: "min-h-0 md:rounded-none md:hover:bg-transparent md:hover:shadow-none"
32
- }
33
- )
34
- ] });
35
- }
36
-
37
- export { VenueSignatureBlock };
@@ -1,92 +0,0 @@
1
- import { cn } from './chunk-FEK5XGZW.js';
2
- import { jsx } from 'react/jsx-runtime';
3
-
4
- var GHOST_ROWS = 13;
5
- var GHOST_CENTRE = (GHOST_ROWS - 1) / 2;
6
- var ALPHA_PEAK = 0.44;
7
- var ALPHA_EDGE = 0;
8
- var FALLOFF_EXPONENT = 3.4;
9
- var GHOST_LINE_CHARS = 56;
10
- function hashString(input) {
11
- let hash = 5381;
12
- for (let i = 0; i < input.length; i++) hash = hash * 33 ^ input.charCodeAt(i);
13
- return Math.abs(hash);
14
- }
15
- function MenuField({
16
- name,
17
- gradient,
18
- seedKey,
19
- variant = "repeat",
20
- className
21
- }) {
22
- if (variant === "display") {
23
- const words = name.trim().split(/\s+/).filter(Boolean);
24
- const longestWord = Math.max(1, ...words.map((word) => word.length));
25
- const lineCount = Math.max(1, words.length);
26
- const byWidth = (96 / (longestWord * 0.62)).toFixed(1);
27
- const byHeight = (90 / (lineCount * 0.9)).toFixed(1);
28
- const fontSize = `clamp(9cqw, min(19cqw, ${byWidth}cqw, ${byHeight}cqh), 19cqw)`;
29
- return /* @__PURE__ */ jsx(
30
- "div",
31
- {
32
- className: cn("relative isolate overflow-hidden", className),
33
- style: { backgroundImage: gradient, containerType: "size" },
34
- children: /* @__PURE__ */ jsx(
35
- "span",
36
- {
37
- "aria-hidden": true,
38
- className: "pointer-events-none absolute inset-0 flex select-none items-center justify-center p-[4cqw] text-center font-black uppercase leading-[0.9] tracking-[-0.03em] text-white/[0.26]",
39
- style: { fontSize },
40
- children: name
41
- }
42
- )
43
- }
44
- );
45
- }
46
- const unit = `${name} \xB7 `;
47
- const repeats = Math.max(2, Math.ceil(GHOST_LINE_CHARS / Math.max(unit.length, 1)));
48
- const ghostLine = unit.repeat(repeats);
49
- const seed = hashString(seedKey != null ? seedKey : name);
50
- const rows = Array.from({ length: GHOST_ROWS }, (_, row) => {
51
- const distance = Math.abs(row - GHOST_CENTRE) / GHOST_CENTRE;
52
- const alpha = ALPHA_EDGE + (ALPHA_PEAK - ALPHA_EDGE) * Math.pow(1 - distance, FALLOFF_EXPONENT);
53
- return { row, alpha, offset: (seed >> row % 12) % 90 - 45 };
54
- });
55
- return /* @__PURE__ */ jsx(
56
- "div",
57
- {
58
- className: cn("relative isolate overflow-hidden", className),
59
- style: { backgroundImage: gradient },
60
- children: /* @__PURE__ */ jsx(
61
- "div",
62
- {
63
- "aria-hidden": true,
64
- className: "pointer-events-none absolute left-1/2 top-1/2 flex h-[180%] w-[180%] -translate-x-1/2 -translate-y-1/2 -rotate-[11deg] select-none flex-col justify-center",
65
- children: rows.map(({ row, alpha, offset }) => /* @__PURE__ */ jsx(
66
- "span",
67
- {
68
- className: "block whitespace-nowrap text-center text-[15px] font-bold leading-[1.45] tracking-[-0.01em]",
69
- style: {
70
- color: `oklch(1 0 0 / ${alpha.toFixed(3)})`,
71
- // Emboss, both edges scaled by this row's alpha: a dark edge below
72
- // cuts the letterform into the field, a light edge above lifts its
73
- // top. The dark edge is kept well under the fill — at parity it
74
- // ringed every glyph and dragged the type back toward grey, which
75
- // is the opposite of what the fill is doing.
76
- textShadow: [
77
- `0 1px 1px oklch(0 0 0 / ${(alpha * 0.4).toFixed(3)})`,
78
- `0 -1px 0 oklch(1 0 0 / ${(alpha * 0.4).toFixed(3)})`
79
- ].join(", "),
80
- transform: `translateX(${offset}px)`
81
- },
82
- children: ghostLine
83
- },
84
- row
85
- ))
86
- }
87
- )
88
- }
89
- );
90
- }
91
-
92
- export { MenuField };
@@ -1,49 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { MenuVenuePreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
3
- import 'react';
4
- import './place-types.js';
5
-
6
- /**
7
- * RankedVenueList — venues ordered by how often reviews name their dishes.
8
- *
9
- * The answer to "best Italian in Cluj", and the reason it is defensible is the
10
- * mention count sitting on every row: the order is evidence, not opinion. A
11
- * caller that cannot supply mentions should use a different surface rather than
12
- * this one with the column blank — an unexplained ranking is worse than none.
13
- *
14
- * The tab strip re-ranks in place and never navigates, so `activeTab` and
15
- * `onTabChange` are controlled by the caller. Omit `tabs` entirely and the
16
- * header is just a heading.
17
- *
18
- * No directive: without `tabs` the list renders on the server, which is the
19
- * common case on a hub.
20
- */
21
- /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
22
- type RankedVenueListLinkProps = MenuLinkProps;
23
- type RankedVenueListTab = {
24
- slug: string;
25
- label: string;
26
- };
27
- type RankedVenueListProps = {
28
- venues: MenuVenuePreview[];
29
- /** Section heading, e.g. "The kitchens Cluj rates for Italian". */
30
- heading?: string;
31
- /** The basis of the order, e.g. "by how often reviews name their dishes". */
32
- caption?: string;
33
- tabs?: RankedVenueListTab[];
34
- activeTab?: string;
35
- onTabChange?: (slug: string) => void;
36
- /** Composed price band per venue, keyed by place slug, e.g. "38—84". */
37
- priceBandLabels?: Record<string, string>;
38
- /**
39
- * The app's own grade per venue slug, already formatted ("8.7"). Shown in
40
- * place of the star rating when present — the star is the third-party
41
- * fallback for a venue the reviews have not graded yet.
42
- */
43
- gradeLabels?: Record<string, string>;
44
- linkComponent?: MenuLinkComponent;
45
- className?: string;
46
- };
47
- declare function RankedVenueList({ venues, heading, caption, tabs, activeTab, onTabChange, priceBandLabels, gradeLabels, linkComponent: LinkComponent, className, }: RankedVenueListProps): react_jsx_runtime.JSX.Element;
48
-
49
- export { RankedVenueList, type RankedVenueListLinkProps, type RankedVenueListProps, type RankedVenueListTab };
@@ -1,87 +0,0 @@
1
- import { Separator } from '../chunk-WSRWFA6K.js';
2
- import { cn } from '../chunk-FEK5XGZW.js';
3
- import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
4
- import { MessageSquare, Star, ChevronRight } from 'lucide-react';
5
- import { jsxs, jsx } from 'react/jsx-runtime';
6
-
7
- function DefaultLink(_a) {
8
- var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
9
- return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
10
- }
11
- function RankedVenueList({
12
- venues,
13
- heading,
14
- caption,
15
- tabs,
16
- activeTab,
17
- onTabChange,
18
- priceBandLabels,
19
- gradeLabels,
20
- linkComponent: LinkComponent = DefaultLink,
21
- className
22
- }) {
23
- return /* @__PURE__ */ jsxs("div", { className: cn("overflow-hidden rounded-[14px] border border-border bg-card", className), children: [
24
- (heading || tabs) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-border p-3.5", children: [
25
- heading && /* @__PURE__ */ jsx("h3", { className: "text-[15px] font-bold leading-tight tracking-[-0.01em]", children: heading }),
26
- caption && /* @__PURE__ */ jsx("span", { className: "text-[12.5px] text-muted-foreground", children: caption }),
27
- tabs && tabs.length > 0 && /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap gap-1.5", children: tabs.map((tab) => /* @__PURE__ */ jsx(
28
- "button",
29
- {
30
- type: "button",
31
- "aria-pressed": tab.slug === activeTab,
32
- onClick: () => onTabChange == null ? void 0 : onTabChange(tab.slug),
33
- className: cn(
34
- "inline-flex h-7 items-center rounded-full border px-2.5 text-xs font-semibold transition-colors",
35
- tab.slug === activeTab ? "border-accent bg-accent-subtle text-accent" : "border-border text-muted-foreground hover:bg-muted/50"
36
- ),
37
- children: tab.label
38
- },
39
- tab.slug
40
- )) })
41
- ] }),
42
- /* @__PURE__ */ jsx("ol", { children: venues.map((venue, index) => {
43
- var _a;
44
- const { place, signature } = venue;
45
- const mentions = (_a = signature == null ? void 0 : signature.item.mentions) != null ? _a : 0;
46
- return /* @__PURE__ */ jsxs("li", { children: [
47
- index > 0 && /* @__PURE__ */ jsx(Separator, {}),
48
- /* @__PURE__ */ jsxs(
49
- LinkComponent,
50
- {
51
- href: venue.href,
52
- className: "flex items-center gap-3 p-3 transition-colors hover:bg-muted/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",
53
- children: [
54
- /* @__PURE__ */ jsx("span", { className: "w-4 shrink-0 text-center text-[13px] font-extrabold tabular-nums text-accent", children: index + 1 }),
55
- /* @__PURE__ */ jsx("span", { className: "h-12 w-12 shrink-0 overflow-hidden rounded-xl bg-[repeating-linear-gradient(135deg,oklch(0.78_0.018_55)_0_8px,oklch(0.84_0.012_58)_8px_16px)]", children: place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
56
- /* @__PURE__ */ jsx(
57
- "img",
58
- {
59
- src: place.thumbnail_url,
60
- alt: "",
61
- className: "h-full w-full object-cover",
62
- loading: "lazy"
63
- }
64
- ) }),
65
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
66
- /* @__PURE__ */ jsx("span", { className: "block text-sm font-bold leading-snug tracking-[-0.01em]", children: place.name }),
67
- (signature == null ? void 0 : signature.item.name) && /* @__PURE__ */ jsx("span", { className: "mt-px block truncate font-serif text-xs italic text-muted-foreground", children: `\u201C${signature.item.name}\u201D` })
68
- ] }),
69
- mentions > 0 && /* @__PURE__ */ jsxs("span", { className: "hidden shrink-0 items-center gap-1 text-[11.5px] tabular-nums text-muted-foreground sm:inline-flex", children: [
70
- /* @__PURE__ */ jsx(MessageSquare, { className: "h-[11px] w-[11px]", "aria-hidden": true }),
71
- mentions
72
- ] }),
73
- (gradeLabels == null ? void 0 : gradeLabels[place.slug]) ? /* @__PURE__ */ jsx("span", { className: "inline-flex h-6 shrink-0 items-center rounded-md bg-foreground px-1.5 text-[12px] font-extrabold tabular-nums text-background", children: gradeLabels[place.slug] }) : place.rating && /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1 text-[12.5px] font-semibold tabular-nums", children: [
74
- /* @__PURE__ */ jsx(Star, { className: "h-3 w-3", "aria-hidden": true }),
75
- place.rating.rating
76
- ] }),
77
- (priceBandLabels == null ? void 0 : priceBandLabels[place.slug]) && /* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-[12.5px] tabular-nums text-muted-foreground sm:block", children: priceBandLabels[place.slug] }),
78
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 shrink-0 text-muted-foreground", "aria-hidden": true })
79
- ]
80
- }
81
- )
82
- ] }, place.slug);
83
- }) })
84
- ] });
85
- }
86
-
87
- export { RankedVenueList };
@@ -1,45 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { MenuItemLabels } from './menu-item.js';
3
- import { MenuVenuePreview, MenuLinkComponent, MenuLinkProps } from './menu-types.js';
4
- import 'react';
5
- import './place-types.js';
6
-
7
- /**
8
- * SignatureLineList — family, venue, dish, price and reputation on one line.
9
- *
10
- * The densest surface on the hub, and the one that most nearly justifies the
11
- * whole catalogue: every row ties a kitchen to the single thing its reviewers
12
- * keep naming, with the price and the count that back it.
13
- *
14
- * ## Two targets per row, deliberately
15
- *
16
- * The venue line goes to the venue; the item row goes to the item across every
17
- * kitchen that makes it. Those are genuinely different destinations, and
18
- * collapsing them into one tap loses the comparison that makes the item worth
19
- * showing. They are separate anchors rather than one anchor with a nested
20
- * button, which would be invalid markup.
21
- *
22
- * Below `lg` the row stacks: venue line first, then the full `<MenuItem>`
23
- * underneath. The desktop layout puts them side by side. Same markup, one
24
- * breakpoint — no second component.
25
- *
26
- * Server-safe.
27
- */
28
- /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
29
- type SignatureLineListLinkProps = MenuLinkProps;
30
- type SignatureLineListProps = {
31
- venues: MenuVenuePreview[];
32
- /** Passed through to each `<MenuItem>`. */
33
- labels: MenuItemLabels;
34
- formatPrice: (amount: number) => string;
35
- formatCount?: (value: number) => string;
36
- /** Composed meta line per venue, keyed by place slug, e.g. "romanian · Centru". */
37
- metaLabels?: Record<string, string>;
38
- /** The row's category mark per venue, keyed by place slug. */
39
- hues?: Record<string, string>;
40
- linkComponent?: MenuLinkComponent;
41
- className?: string;
42
- };
43
- declare function SignatureLineList({ venues, labels, formatPrice, formatCount, metaLabels, hues, linkComponent: LinkComponent, className, }: SignatureLineListProps): react_jsx_runtime.JSX.Element;
44
-
45
- export { SignatureLineList, type SignatureLineListLinkProps, type SignatureLineListProps };
@@ -1,99 +0,0 @@
1
- import { Separator } from '../chunk-WSRWFA6K.js';
2
- import { MenuItem } from '../chunk-TH6W2PWG.js';
3
- import '../chunk-Q6QH52LJ.js';
4
- import '../chunk-UKBNQO6Q.js';
5
- import { cn } from '../chunk-FEK5XGZW.js';
6
- import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
7
- import { Star, ChevronRight } from 'lucide-react';
8
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
-
10
- function DefaultLink(_a) {
11
- var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
12
- return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
13
- }
14
- function SignatureLineList({
15
- venues,
16
- labels,
17
- formatPrice,
18
- formatCount,
19
- metaLabels,
20
- hues,
21
- linkComponent: LinkComponent = DefaultLink,
22
- className
23
- }) {
24
- return /* @__PURE__ */ jsx("ul", { className: cn("flex flex-col", className), children: venues.map((venue, index) => {
25
- const { place, signature } = venue;
26
- return /* @__PURE__ */ jsxs(
27
- "li",
28
- {
29
- className: "grid gap-x-4 py-3.5 lg:grid-cols-[minmax(0,260px)_minmax(0,1fr)] lg:items-center",
30
- children: [
31
- index > 0 && /* @__PURE__ */ jsx(Separator, { className: "mb-3.5 lg:col-span-2" }),
32
- /* @__PURE__ */ jsxs(
33
- LinkComponent,
34
- {
35
- href: venue.href,
36
- className: "flex items-center gap-3 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
37
- children: [
38
- /* @__PURE__ */ jsxs("span", { className: "relative h-12 w-12 shrink-0 overflow-hidden rounded-xl bg-[repeating-linear-gradient(135deg,oklch(0.78_0.018_55)_0_8px,oklch(0.84_0.012_58)_8px_16px)] lg:h-16 lg:w-16", children: [
39
- place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
40
- /* @__PURE__ */ jsx(
41
- "img",
42
- {
43
- src: place.thumbnail_url,
44
- alt: "",
45
- className: "h-full w-full object-cover",
46
- loading: "lazy"
47
- }
48
- ),
49
- place.open_now && /* @__PURE__ */ jsx(
50
- "i",
51
- {
52
- "aria-hidden": true,
53
- className: "absolute right-1 top-1 h-[7px] w-[7px] rounded-full bg-[var(--free,currentColor)] ring-2 ring-white/80"
54
- }
55
- )
56
- ] }),
57
- /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
58
- /* @__PURE__ */ jsx("span", { className: "block truncate text-[15px] font-bold tracking-[-0.01em]", children: place.name }),
59
- /* @__PURE__ */ jsxs("span", { className: "mt-0.5 flex items-center gap-1.5 text-xs text-muted-foreground", children: [
60
- place.rating && /* @__PURE__ */ jsxs(Fragment, { children: [
61
- /* @__PURE__ */ jsx(Star, { className: "h-[11px] w-[11px]", "aria-hidden": true }),
62
- /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: place.rating.rating })
63
- ] }),
64
- (metaLabels == null ? void 0 : metaLabels[place.slug]) && /* @__PURE__ */ jsx("span", { className: "truncate", children: metaLabels[place.slug] })
65
- ] })
66
- ] }),
67
- /* @__PURE__ */ jsx(
68
- ChevronRight,
69
- {
70
- className: "h-4 w-4 shrink-0 text-muted-foreground lg:hidden",
71
- "aria-hidden": true
72
- }
73
- )
74
- ]
75
- }
76
- ),
77
- signature && /* @__PURE__ */ jsx(
78
- MenuItem,
79
- {
80
- item: signature.item,
81
- labels,
82
- formatPrice,
83
- formatCount,
84
- markGradient: hues == null ? void 0 : hues[place.slug],
85
- subtitle: signature.why,
86
- showSignatureBadge: signature.isSignature,
87
- href: signature.href,
88
- linkComponent: LinkComponent,
89
- className: "min-w-0"
90
- }
91
- )
92
- ]
93
- },
94
- place.slug
95
- );
96
- }) });
97
- }
98
-
99
- export { SignatureLineList };
@@ -1,35 +0,0 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
-
3
- /**
4
- * VenueBusynessStrip — when a venue actually fills up, as a bar per hour.
5
- *
6
- * The API serves one score per hour of the venue's own trading day, so the
7
- * strip has no fixed length: a bakery closing at three and a club open until
8
- * four draw different numbers of bars, and padding either to 24 would put empty
9
- * columns under hours the venue is not open.
10
- *
11
- * The rush is derived rather than passed: bars at or above `peakRatio` of the
12
- * day's own maximum take the accent. A fixed score threshold would mark every
13
- * hour at a busy venue and none at a quiet one — busyness is only meaningful
14
- * relative to that venue's own day.
15
- *
16
- * `caption` is composed by the caller ("Usually not busy until 18:00 — the rush
17
- * is nine to eleven"), because the sentence needs the venue's clock, its locale
18
- * and a plural rule the DS does not own.
19
- *
20
- * Server-safe.
21
- */
22
- type VenueBusynessStripProps = {
23
- busyness: Array<{
24
- hour: number;
25
- score: number;
26
- }>;
27
- /** Composed sentence beside the bars. */
28
- caption?: string;
29
- /** Share of the day's own peak at which an hour counts as the rush. */
30
- peakRatio?: number;
31
- className?: string;
32
- };
33
- declare function VenueBusynessStrip({ busyness, caption, peakRatio, className, }: VenueBusynessStripProps): react_jsx_runtime.JSX.Element | null;
34
-
35
- export { VenueBusynessStrip, type VenueBusynessStripProps };
@@ -1,37 +0,0 @@
1
- import { cn } from '../chunk-FEK5XGZW.js';
2
- import '../chunk-3GWWZKX7.js';
3
- import { jsxs, jsx } from 'react/jsx-runtime';
4
-
5
- var BAR_MAX_HEIGHT = 22;
6
- function VenueBusynessStrip({
7
- busyness,
8
- caption,
9
- peakRatio = 0.8,
10
- className
11
- }) {
12
- if (busyness.length === 0) return null;
13
- const peak = Math.max(...busyness.map((entry) => entry.score));
14
- if (peak <= 0) return null;
15
- return /* @__PURE__ */ jsxs(
16
- "div",
17
- {
18
- className: cn("flex items-center gap-2.5 text-[12.5px] text-muted-foreground", className),
19
- children: [
20
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "flex h-[22px] items-end gap-[3px]", children: busyness.map(({ hour, score }) => /* @__PURE__ */ jsx(
21
- "i",
22
- {
23
- className: cn(
24
- "w-[7px] rounded-t-sm bg-[oklch(0.88_0.02_45)]",
25
- score >= peak * peakRatio && "bg-[var(--accent,currentColor)]"
26
- ),
27
- style: { height: Math.max(2, Math.round(score / peak * BAR_MAX_HEIGHT)) }
28
- },
29
- hour
30
- )) }),
31
- caption
32
- ]
33
- }
34
- );
35
- }
36
-
37
- export { VenueBusynessStrip };
@@ -1,39 +0,0 @@
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 };
@@ -1,6 +0,0 @@
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';