@bearmenu/ui 0.8.8 → 0.8.10

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 (206) hide show
  1. package/dist/chunk-24RFI4RQ.js +167 -0
  2. package/dist/chunk-2XCZJWTV.js +121 -0
  3. package/dist/chunk-5RWTOT5Y.js +72 -0
  4. package/dist/chunk-62ES6N32.js +27 -0
  5. package/dist/chunk-AKQ57NXY.js +58 -0
  6. package/dist/chunk-CLLY6XB3.js +41 -0
  7. package/dist/chunk-DTXNDLSE.js +125 -0
  8. package/dist/chunk-F6FMZJDG.js +113 -0
  9. package/dist/{chunk-2WDJR6K7.js → chunk-G7IFL4Z4.js} +4 -1
  10. package/dist/chunk-MZL7CGCL.js +109 -0
  11. package/dist/chunk-Q6QH52LJ.js +38 -0
  12. package/dist/chunk-SY6RV476.js +48 -0
  13. package/dist/chunk-TH6W2PWG.js +213 -0
  14. package/dist/chunk-UDN7UHJW.js +60 -0
  15. package/dist/{chunk-TT3WFS6S.js → chunk-YOKHBAIH.js} +11 -11
  16. package/dist/components/alert.d.ts +2 -2
  17. package/dist/components/badge.d.ts +1 -1
  18. package/dist/components/concept-compare-rail.d.ts +50 -0
  19. package/dist/components/concept-compare-rail.js +5 -0
  20. package/dist/components/concept-compare.d.ts +110 -0
  21. package/dist/components/concept-compare.js +238 -0
  22. package/dist/components/concept-convergence.d.ts +41 -0
  23. package/dist/components/concept-convergence.js +53 -0
  24. package/dist/components/convergence.d.ts +25 -0
  25. package/dist/components/convergence.js +4 -0
  26. package/dist/components/date-input.d.ts +1 -1
  27. package/dist/components/event-card.js +1 -1
  28. package/dist/components/event-compact-row.d.ts +1 -1
  29. package/dist/components/event-compact-row.js +1 -1
  30. package/dist/components/event-detail-panel.d.ts +171 -25
  31. package/dist/components/event-detail-panel.js +472 -175
  32. package/dist/components/event-index-row.js +1 -1
  33. package/dist/components/event-list-row-wide.js +2 -2
  34. package/dist/components/event-list-row.js +2 -2
  35. package/dist/components/explainer-steps.d.ts +34 -0
  36. package/dist/components/explainer-steps.js +36 -0
  37. package/dist/components/glass-ground.d.ts +66 -0
  38. package/dist/components/glass-ground.js +3 -0
  39. package/dist/components/hub-surface.d.ts +27 -0
  40. package/dist/components/hub-surface.js +30 -0
  41. package/dist/components/hue-chip.d.ts +70 -0
  42. package/dist/components/hue-chip.js +3 -0
  43. package/dist/components/kitchen-dossier.d.ts +88 -0
  44. package/dist/components/kitchen-dossier.js +187 -0
  45. package/dist/components/menu-dead-end.d.ts +56 -0
  46. package/dist/components/menu-dead-end.js +49 -0
  47. package/dist/components/menu-family-block.d.ts +99 -0
  48. package/dist/components/menu-family-block.js +155 -0
  49. package/dist/components/menu-family-card.d.ts +65 -0
  50. package/dist/components/menu-family-card.js +79 -0
  51. package/dist/components/menu-field.d.ts +57 -0
  52. package/dist/components/menu-field.js +93 -0
  53. package/dist/components/menu-filter-chips.d.ts +86 -0
  54. package/dist/components/menu-filter-chips.js +122 -0
  55. package/dist/components/menu-group-rows.d.ts +53 -0
  56. package/dist/components/menu-group-rows.js +67 -0
  57. package/dist/components/menu-hub-hero.d.ts +65 -0
  58. package/dist/components/menu-hub-hero.js +103 -0
  59. package/dist/components/menu-index-header.d.ts +37 -0
  60. package/dist/components/menu-index-header.js +68 -0
  61. package/dist/components/menu-index-list.d.ts +48 -0
  62. package/dist/components/menu-index-list.js +96 -0
  63. package/dist/components/menu-intent-hero.d.ts +69 -0
  64. package/dist/components/menu-intent-hero.js +87 -0
  65. package/dist/components/menu-intent-tiles.d.ts +31 -0
  66. package/dist/components/menu-intent-tiles.js +50 -0
  67. package/dist/components/menu-item.d.ts +127 -0
  68. package/dist/components/menu-item.js +5 -0
  69. package/dist/components/menu-kitchen-list.d.ts +82 -0
  70. package/dist/components/menu-kitchen-list.js +5 -0
  71. package/dist/components/menu-mocks.d.ts +90 -0
  72. package/dist/components/menu-mocks.js +504 -0
  73. package/dist/components/menu-types.d.ts +421 -0
  74. package/dist/components/menu-types.js +1 -0
  75. package/dist/components/motion-icon-controls.js +2 -2
  76. package/dist/components/motion-icon-set.js +21 -9
  77. package/dist/components/motion-icon.d.ts +22 -1
  78. package/dist/components/motion-icon.js +1 -1
  79. package/dist/components/open-now-list.d.ts +40 -0
  80. package/dist/components/open-now-list.js +69 -0
  81. package/dist/components/photo-ground.d.ts +64 -0
  82. package/dist/components/photo-ground.js +3 -0
  83. package/dist/components/place-about-tab.js +2 -2
  84. package/dist/components/place-details-mobile.js +2 -2
  85. package/dist/components/place-types.d.ts +112 -1
  86. package/dist/components/qa-block.d.ts +36 -0
  87. package/dist/components/qa-block.js +24 -0
  88. package/dist/components/scroll-fade.js +3 -121
  89. package/dist/components/signature-chip.d.ts +37 -0
  90. package/dist/components/signature-chip.js +4 -0
  91. package/dist/components/snap-rail.d.ts +48 -5
  92. package/dist/components/snap-rail.js +3 -51
  93. package/dist/components/stat-banner.d.ts +47 -0
  94. package/dist/components/stat-banner.js +61 -0
  95. package/dist/components/story.d.ts +34 -0
  96. package/dist/components/story.js +3 -0
  97. package/dist/components/tag.d.ts +2 -2
  98. package/dist/components/text.d.ts +1 -1
  99. package/dist/components/top-comparison.d.ts +28 -0
  100. package/dist/components/top-comparison.js +76 -0
  101. package/dist/components/top-cross-link.d.ts +34 -0
  102. package/dist/components/top-cross-link.js +101 -0
  103. package/dist/components/top-ranking-entry.d.ts +53 -0
  104. package/dist/components/top-ranking-entry.js +217 -0
  105. package/dist/components/top-ranking-hero.d.ts +61 -0
  106. package/dist/components/top-ranking-hero.js +99 -0
  107. package/dist/components/top-ranking-legend.d.ts +69 -0
  108. package/dist/components/top-ranking-legend.js +124 -0
  109. package/dist/components/top-types.d.ts +109 -0
  110. package/dist/components/top-types.js +1 -0
  111. package/dist/components/venue-grade-dimensions.d.ts +38 -0
  112. package/dist/components/venue-grade-dimensions.js +3 -0
  113. package/dist/components/venue-quotes.d.ts +32 -0
  114. package/dist/components/venue-quotes.js +3 -0
  115. package/dist/components/venue-wall.d.ts +41 -0
  116. package/dist/components/venue-wall.js +118 -0
  117. package/dist/components/weekday-theme-banner.d.ts +70 -0
  118. package/dist/components/weekday-theme-banner.js +127 -0
  119. package/dist/components/weekend-panel.js +2 -2
  120. package/package.json +1 -1
  121. package/src/components/colour-dictionary.stories.tsx +227 -0
  122. package/src/components/concept-compare-rail.tsx +131 -0
  123. package/src/components/concept-compare.stories.tsx +172 -0
  124. package/src/components/concept-compare.tsx +443 -0
  125. package/src/components/concept-convergence.stories.tsx +53 -0
  126. package/src/components/concept-convergence.tsx +90 -0
  127. package/src/components/convergence.stories.tsx +54 -0
  128. package/src/components/convergence.tsx +177 -0
  129. package/src/components/event-compact-row.tsx +1 -1
  130. package/src/components/event-detail-panel.stories.tsx +205 -97
  131. package/src/components/event-detail-panel.test.tsx +181 -0
  132. package/src/components/event-detail-panel.tsx +703 -185
  133. package/src/components/explainer-steps.stories.tsx +46 -0
  134. package/src/components/explainer-steps.tsx +74 -0
  135. package/src/components/glass-ground.tsx +129 -0
  136. package/src/components/hub-surface.stories.tsx +68 -0
  137. package/src/components/hub-surface.tsx +50 -0
  138. package/src/components/hue-chip.stories.tsx +76 -0
  139. package/src/components/hue-chip.test.tsx +62 -0
  140. package/src/components/hue-chip.tsx +99 -0
  141. package/src/components/kitchen-dossier.stories.tsx +108 -0
  142. package/src/components/kitchen-dossier.tsx +322 -0
  143. package/src/components/menu-dead-end.stories.tsx +67 -0
  144. package/src/components/menu-dead-end.tsx +127 -0
  145. package/src/components/menu-family-block.stories.tsx +91 -0
  146. package/src/components/menu-family-block.tsx +314 -0
  147. package/src/components/menu-family-card.stories.tsx +120 -0
  148. package/src/components/menu-family-card.tsx +191 -0
  149. package/src/components/menu-field.stories.tsx +74 -0
  150. package/src/components/menu-field.tsx +199 -0
  151. package/src/components/menu-filter-chips.stories.tsx +107 -0
  152. package/src/components/menu-filter-chips.tsx +238 -0
  153. package/src/components/menu-group-rows.stories.tsx +54 -0
  154. package/src/components/menu-group-rows.tsx +133 -0
  155. package/src/components/menu-hub-hero.stories.tsx +92 -0
  156. package/src/components/menu-hub-hero.tsx +187 -0
  157. package/src/components/menu-index-header.stories.tsx +72 -0
  158. package/src/components/menu-index-header.tsx +125 -0
  159. package/src/components/menu-index-list.stories.tsx +50 -0
  160. package/src/components/menu-index-list.tsx +149 -0
  161. package/src/components/menu-intent-hero.stories.tsx +67 -0
  162. package/src/components/menu-intent-hero.tsx +189 -0
  163. package/src/components/menu-intent-tiles.stories.tsx +54 -0
  164. package/src/components/menu-intent-tiles.tsx +76 -0
  165. package/src/components/menu-item.tsx +416 -0
  166. package/src/components/menu-kitchen-list.stories.tsx +67 -0
  167. package/src/components/menu-kitchen-list.test.tsx +78 -0
  168. package/src/components/menu-kitchen-list.tsx +284 -0
  169. package/src/components/menu-mocks.ts +592 -0
  170. package/src/components/menu-types.ts +442 -0
  171. package/src/components/motion-icon-controls.tsx +11 -11
  172. package/src/components/motion-icon-set.tsx +36 -8
  173. package/src/components/motion-icon.test.tsx +23 -1
  174. package/src/components/motion-icon.tsx +20 -0
  175. package/src/components/open-now-list.stories.tsx +76 -0
  176. package/src/components/open-now-list.tsx +127 -0
  177. package/src/components/photo-ground.tsx +147 -0
  178. package/src/components/place-types.ts +112 -0
  179. package/src/components/qa-block.stories.tsx +43 -0
  180. package/src/components/qa-block.tsx +60 -0
  181. package/src/components/signature-chip.tsx +77 -0
  182. package/src/components/snap-rail.tsx +133 -9
  183. package/src/components/stat-banner.stories.tsx +100 -0
  184. package/src/components/stat-banner.tsx +117 -0
  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.stories.tsx +83 -0
  199. package/src/components/venue-grade-dimensions.tsx +91 -0
  200. package/src/components/venue-quotes.stories.tsx +59 -0
  201. package/src/components/venue-quotes.tsx +64 -0
  202. package/src/components/venue-wall.stories.tsx +78 -0
  203. package/src/components/venue-wall.tsx +185 -0
  204. package/src/components/weekday-theme-banner.stories.tsx +113 -0
  205. package/src/components/weekday-theme-banner.tsx +231 -0
  206. package/dist/{chunk-XWMRABZW.js → chunk-HSIYYA2E.js} +1 -1
@@ -0,0 +1,284 @@
1
+ import { Check, ChevronRight, Clock, MessageSquare, Star } from "lucide-react";
2
+ import { cn } from "../lib/utils";
3
+ import { Separator } from "./separator";
4
+ import { Skeleton } from "./skeleton";
5
+ import type { MenuKitchenPreview, MenuLinkComponent, MenuLinkProps } from "./menu-types";
6
+
7
+ /**
8
+ * MenuKitchenList — the intent page's spine.
9
+ *
10
+ * Venues ranked by how often reviews name their dishes, each row carrying the
11
+ * dish that earned it its place. Not a grid of dishes: a shortlist of rooms you
12
+ * could walk into.
13
+ *
14
+ * ## Two targets, and why this is not `<RankedVenueList>`
15
+ *
16
+ * The row goes to the venue. The dish name opens the item drawer — the same
17
+ * drawer the dish tiles open, listing every kitchen that makes it and for how
18
+ * much. Those are two different destinations inside one row, and the hub's
19
+ * `<RankedVenueList>` wraps its ENTIRE row in a single `LinkComponent`, so it
20
+ * structurally cannot express a second one. That is the reason this exists
21
+ * rather than a variant flag on that component.
22
+ *
23
+ * The mechanism is a stretched overlay, as in `<MenuItem>`: the venue link is
24
+ * an absolutely-positioned sibling at `z-[1]`, and the dish link sits above it
25
+ * at `z-[2]`. Nesting one anchor inside another is invalid HTML and breaks
26
+ * middle-click on both.
27
+ *
28
+ * The dish name is underlined so it reads as a target before it is pressed. The
29
+ * chevron belongs to the venue and stays on the right, away from it — an
30
+ * unmarked second target inside a row that already navigates is a trap.
31
+ *
32
+ * ## The filtered state changes the row's reason for being there
33
+ *
34
+ * With a dish or dietary filter on, each row swaps its most-named dish for the
35
+ * one that actually matched, and the kicker changes with it ("MOST NAMED HERE"
36
+ * → "WHY IT MATCHES"). A filtered list whose rows still advertise their
37
+ * most-named dish is claiming the filter matched something it did not.
38
+ *
39
+ * ## Header
40
+ *
41
+ * Deliberately none. The page composes `<SectionHeader>` above it, the same way
42
+ * the events hub does, because the heading here carries a count and a "see the
43
+ * full ranking" link into `/top` that the list knows nothing about.
44
+ */
45
+
46
+ /** @deprecated Alias of `MenuLinkProps`; kept for symmetry with the other menu surfaces. */
47
+ export type MenuKitchenListLinkProps = MenuLinkProps;
48
+
49
+ function DefaultLink({ href, ...props }: MenuKitchenListLinkProps) {
50
+ return <a href={href} {...props} />;
51
+ }
52
+
53
+ export type MenuKitchenListLabels = {
54
+ /** Accessible prefix for the row's venue link, e.g. "Open". */
55
+ openVenue: (name: string) => string;
56
+ /** Accessible name for the dish link, e.g. "See Truffle Tagliatelle". */
57
+ openDish: (name: string) => string;
58
+ /** Accessible name for the open-now dot. */
59
+ openNow: string;
60
+ /** Reveals the next page of rows, e.g. "Show 10 more". */
61
+ showMore?: string;
62
+ };
63
+
64
+ export type MenuKitchenListProps = {
65
+ kitchens: MenuKitchenPreview[];
66
+ labels: MenuKitchenListLabels;
67
+ /**
68
+ * Reveals more rows IN PLACE. The intent page pages ten at a time and never
69
+ * navigates for the eleventh — the list is the page.
70
+ */
71
+ onShowMore?: () => void;
72
+ linkComponent?: MenuLinkComponent;
73
+ className?: string;
74
+ };
75
+
76
+ export function MenuKitchenList({
77
+ kitchens,
78
+ labels,
79
+ onShowMore,
80
+ linkComponent = DefaultLink,
81
+ className,
82
+ }: MenuKitchenListProps) {
83
+ return (
84
+ <div className={cn("flex flex-col", className)}>
85
+ <ol>
86
+ {kitchens.map((kitchen, index) => (
87
+ <li key={kitchen.place.slug} className="[&:first-child>div]:pt-0">
88
+ {/* The DS `Separator`, drawn BETWEEN rows rather than a `border-t`
89
+ with a `first:` reset on every row. It lives inside the `<li>`
90
+ because `<ol>` admits only list items, so a sibling element
91
+ between them would be invalid markup. */}
92
+ {index > 0 && <Separator />}
93
+ <KitchenRow kitchen={kitchen} labels={labels} linkComponent={linkComponent} />
94
+ </li>
95
+ ))}
96
+ </ol>
97
+
98
+ {onShowMore && labels.showMore && (
99
+ <button
100
+ type="button"
101
+ onClick={onShowMore}
102
+ className="mt-2 self-start rounded-md py-2 text-[13px] font-bold text-accent transition-colors hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none"
103
+ >
104
+ {labels.showMore}
105
+ </button>
106
+ )}
107
+ </div>
108
+ );
109
+ }
110
+
111
+ function KitchenRow({
112
+ kitchen,
113
+ labels,
114
+ linkComponent: LinkComponent,
115
+ }: {
116
+ kitchen: MenuKitchenPreview;
117
+ labels: MenuKitchenListLabels;
118
+ linkComponent: MenuLinkComponent;
119
+ }) {
120
+ const { place, dish } = kitchen;
121
+ const openNow = place.open_now === true;
122
+
123
+ return (
124
+ <div className="relative flex flex-wrap items-center gap-[11px] py-3 transition-colors hover:bg-muted/30 motion-reduce:transition-none lg:flex-nowrap lg:gap-4 lg:py-3.5">
125
+ {/* The venue link, stretched. `z-[1]` — the dish link sits above it. */}
126
+ <LinkComponent
127
+ href={kitchen.href}
128
+ aria-label={labels.openVenue(place.name)}
129
+ className="absolute inset-0 z-[1] rounded-xl focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
130
+ />
131
+
132
+ <span className="relative h-[46px] w-[46px] shrink-0 overflow-hidden rounded-[11px] bg-[repeating-linear-gradient(135deg,oklch(0.78_0.018_55)_0_8px,oklch(0.84_0.012_58)_8px_16px)] lg:h-[58px] lg:w-[58px] lg:rounded-[13px]">
133
+ {place.thumbnail_url && (
134
+ // eslint-disable-next-line @next/next/no-img-element
135
+ <img
136
+ src={place.thumbnail_url}
137
+ alt=""
138
+ loading="lazy"
139
+ decoding="async"
140
+ className="h-full w-full object-cover"
141
+ />
142
+ )}
143
+ <span
144
+ title={openNow ? labels.openNow : undefined}
145
+ className={cn(
146
+ "absolute right-[5px] top-[5px] h-[7px] w-[7px] rounded-full ring-2 ring-white/85",
147
+ openNow ? "bg-[var(--free,oklch(0.55_0.13_155))]" : "bg-[oklch(0.8_0.01_60)]"
148
+ )}
149
+ />
150
+ </span>
151
+
152
+ <div className="flex min-w-0 flex-1 flex-col gap-0.5 lg:w-[226px] lg:flex-none">
153
+ <span className="truncate text-[14.5px] font-bold tracking-[-0.01em] lg:text-[15.5px]">
154
+ {place.name}
155
+ </span>
156
+ <span className="flex items-center gap-1 truncate text-xs text-muted-foreground">
157
+ {place.rating && (
158
+ <>
159
+ <Star className="h-[11px] w-[11px] shrink-0" aria-hidden />
160
+ {place.rating.rating}
161
+ {kitchen.metaLabel && " · "}
162
+ </>
163
+ )}
164
+ {kitchen.metaLabel}
165
+ </span>
166
+ {kitchen.hoursLabel && (
167
+ <span
168
+ className={cn(
169
+ "mt-[3px] inline-flex h-[21px] w-max items-center gap-[5px] rounded-md px-2 text-[10.5px] font-bold",
170
+ openNow
171
+ ? "bg-[oklch(0.96_0.02_155)] text-[var(--free,oklch(0.45_0.13_155))]"
172
+ : "bg-muted text-muted-foreground"
173
+ )}
174
+ >
175
+ <Clock className="h-2.5 w-2.5 shrink-0" aria-hidden />
176
+ {kitchen.hoursLabel}
177
+ </span>
178
+ )}
179
+ </div>
180
+
181
+ {dish && (
182
+ /* DESKTOP ONLY, where it is a column of its own beside the venue.
183
+ On the phone it wrapped to a third line under the row — kicker, dish,
184
+ mention count — which turned a four-line venue row into a seven-line
185
+ block and, ten rows down, buried the venue names the list exists to
186
+ be scanned for. The dish is not what a reader is choosing here; it is
187
+ one tap away on the venue's own page, with room to state it.
188
+
189
+ `order-5` puts it last in the visual order without moving it in the
190
+ DOM, where it belongs to the venue. */
191
+ <div className="hidden min-w-0 basis-full pl-[57px] lg:order-none lg:block lg:flex-1 lg:basis-auto lg:pl-0">
192
+ <span
193
+ className={cn(
194
+ "flex items-center gap-1.5 text-[9px] font-extrabold uppercase tracking-[0.1em]",
195
+ dish.matched ? "text-accent" : "text-[oklch(0.72_0.01_60)]"
196
+ )}
197
+ >
198
+ {dish.kicker}
199
+ {dish.matched && dish.matchLabel && (
200
+ <span className="inline-flex items-center gap-1 rounded-[5px] bg-accent-subtle px-1.5 py-px text-[10.5px] font-bold normal-case tracking-normal text-accent">
201
+ <Check className="h-2.5 w-2.5 shrink-0" aria-hidden />
202
+ {dish.matchLabel}
203
+ </span>
204
+ )}
205
+ </span>
206
+
207
+ {dish.href ? (
208
+ <LinkComponent
209
+ href={dish.href}
210
+ aria-label={labels.openDish(dish.name)}
211
+ className="relative z-[2] mt-0.5 block truncate font-serif text-[13.5px] italic underline decoration-[oklch(0.84_0.01_60)] decoration-1 underline-offset-[3px] transition-colors hover:text-accent hover:decoration-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none lg:text-[15px]"
212
+ >
213
+ {dish.name}
214
+ </LinkComponent>
215
+ ) : (
216
+ <span className="mt-0.5 block truncate font-serif text-[13.5px] italic lg:text-[15px]">
217
+ {dish.name}
218
+ </span>
219
+ )}
220
+
221
+ {dish.meta && (
222
+ <span className="mt-[3px] flex items-center gap-1 text-[11.5px] text-muted-foreground">
223
+ {!dish.matched && <MessageSquare className="h-2.5 w-2.5 shrink-0" aria-hidden />}
224
+ {dish.meta}
225
+ </span>
226
+ )}
227
+ </div>
228
+ )}
229
+
230
+ <div className="ml-auto flex shrink-0 items-center gap-[11px] lg:ml-0">
231
+ {kitchen.priceLabel && (
232
+ <span className="text-base font-extrabold tabular-nums text-accent">
233
+ {kitchen.priceLabel}
234
+ </span>
235
+ )}
236
+ <ChevronRight className="h-[15px] w-[15px] shrink-0 text-muted-foreground" aria-hidden />
237
+ </div>
238
+ </div>
239
+ );
240
+ }
241
+
242
+ // ─── Skeleton ───────────────────────────────────────────────────────────────
243
+
244
+ /**
245
+ * The list's own loading state, at the row's own footprint.
246
+ *
247
+ * It lives HERE, beside the component, and that is the whole point: a skeleton
248
+ * kept in the consuming app drifts the moment the row's padding or thumbnail
249
+ * size changes, and the drift is invisible until a page reflows on load. The
250
+ * measurements below are the same literals the row above uses — change one and
251
+ * the diff shows you the other.
252
+ */
253
+ export function MenuKitchenListSkeleton({
254
+ rows = 10,
255
+ className,
256
+ }: {
257
+ rows?: number;
258
+ className?: string;
259
+ }) {
260
+ return (
261
+ <div className={cn("flex flex-col", className)} aria-hidden>
262
+ {Array.from({ length: rows }, (_, index) => (
263
+ <div key={index}>
264
+ {index > 0 && <Separator />}
265
+ <div className="flex flex-wrap items-center gap-[11px] py-3 lg:flex-nowrap lg:gap-4 lg:py-3.5">
266
+ <Skeleton className="h-[46px] w-[46px] rounded-[11px] lg:h-[58px] lg:w-[58px] lg:rounded-[13px]" />
267
+ <div className="flex min-w-0 flex-1 flex-col gap-1.5 lg:w-[226px] lg:flex-none">
268
+ <Skeleton className="h-4 w-[60%]" />
269
+ <Skeleton className="h-3 w-[45%]" />
270
+ <Skeleton className="h-[21px] w-[120px] rounded-md" />
271
+ </div>
272
+ {/* Desktop only, as the real row's dish column now is. */}
273
+ <div className="hidden min-w-0 basis-full pl-[57px] lg:order-none lg:block lg:flex-1 lg:basis-auto lg:pl-0">
274
+ <Skeleton className="h-2.5 w-[90px]" />
275
+ <Skeleton className="mt-1.5 h-4 w-[70%]" />
276
+ <Skeleton className="mt-1.5 h-3 w-[40%]" />
277
+ </div>
278
+ <Skeleton className="ml-auto h-5 w-[56px] lg:ml-0" />
279
+ </div>
280
+ </div>
281
+ ))}
282
+ </div>
283
+ );
284
+ }