@bearmenu/ui 0.8.13 → 0.8.15

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 (294) hide show
  1. package/README.md +0 -1
  2. package/dist/{chunk-66MAPLXS.js → chunk-2ADCZXZJ.js} +1 -1
  3. package/dist/{chunk-2DEIXTBN.js → chunk-2ZXIXQIU.js} +1 -1
  4. package/dist/{chunk-YOKHBAIH.js → chunk-4OF4T4AI.js} +12 -11
  5. package/dist/{chunk-MZL7CGCL.js → chunk-4XDJXMLF.js} +2 -2
  6. package/dist/{chunk-VAXOQS7W.js → chunk-5FCC5L5F.js} +3 -3
  7. package/dist/{chunk-YYGB4G4Q.js → chunk-CZK2MRS2.js} +1 -1
  8. package/dist/chunk-DJD3EMPK.js +126 -0
  9. package/dist/chunk-EZG2NELX.js +5 -0
  10. package/dist/{chunk-VG3PND3D.js → chunk-FMTJGO76.js} +1 -1
  11. package/dist/{chunk-UXWAOISG.js → chunk-FVSKGCK3.js} +2 -2
  12. package/dist/{chunk-2XCZJWTV.js → chunk-GNCWJWFE.js} +1 -1
  13. package/dist/{chunk-X3WMRKOW.js → chunk-HDR7V6IZ.js} +1 -1
  14. package/dist/{chunk-HSIYYA2E.js → chunk-HQU6QVAN.js} +5 -5
  15. package/dist/chunk-I5V446BX.js +27 -0
  16. package/dist/{chunk-WCTHZKU7.js → chunk-ICOU5QHT.js} +1 -1
  17. package/dist/{chunk-G7IFL4Z4.js → chunk-L3VEMP5A.js} +57 -9
  18. package/dist/chunk-LQWGZIYA.js +5 -0
  19. package/dist/{chunk-XNA2INVQ.js → chunk-MCOHLWRS.js} +2 -2
  20. package/dist/{chunk-3GWWZKX7.js → chunk-MMUBH76A.js} +21 -1
  21. package/dist/{chunk-ZDAHKI3A.js → chunk-MWB7S54O.js} +1 -1
  22. package/dist/{chunk-D5QHEHXJ.js → chunk-NMEVJT5Q.js} +1 -1
  23. package/dist/{chunk-3TNPSN4D.js → chunk-NSNEANOQ.js} +2 -2
  24. package/dist/{chunk-WSRWFA6K.js → chunk-NWZGSLPU.js} +1 -1
  25. package/dist/{chunk-LEAYBICI.js → chunk-OIV7B44M.js} +1 -1
  26. package/dist/{chunk-2CXU5WZO.js → chunk-P43B3ONY.js} +1 -1
  27. package/dist/{chunk-FRPPIXMY.js → chunk-PDTPO6NG.js} +1 -1
  28. package/dist/{chunk-6XMYEZVR.js → chunk-PX6RICS5.js} +1 -1
  29. package/dist/{chunk-6TEEY5YA.js → chunk-S33K4RC5.js} +1 -1
  30. package/dist/{chunk-3VMDCCCX.js → chunk-SC2VUTUI.js} +1 -1
  31. package/dist/{chunk-3NMG3KNA.js → chunk-SXQZ7DLH.js} +3 -3
  32. package/dist/{chunk-UDTG3RSE.js → chunk-T6S2ZBV6.js} +2 -2
  33. package/dist/{chunk-R47NEOSI.js → chunk-V4E333QS.js} +29 -4
  34. package/dist/{chunk-FKNBZF5T.js → chunk-WYTJR2YU.js} +1 -1
  35. package/dist/{chunk-QXHMPJ35.js → chunk-XBBEX4SF.js} +1 -1
  36. package/dist/chunk-XCB3F6SF.js +16 -0
  37. package/dist/{chunk-7BWTYWM3.js → chunk-ZLMRVBD7.js} +1 -1
  38. package/dist/{chunk-P7XTW2CG.js → chunk-ZLPWEHZO.js} +1 -1
  39. package/dist/components/accordion.js +1 -1
  40. package/dist/components/alert-dialog.js +3 -3
  41. package/dist/components/alert.js +2 -2
  42. package/dist/components/aspect-ratio.js +2 -2
  43. package/dist/components/avatar.js +1 -1
  44. package/dist/components/badge.js +2 -2
  45. package/dist/components/breadcrumb.js +1 -1
  46. package/dist/components/button-group.js +1 -1
  47. package/dist/components/button.d.ts +4 -0
  48. package/dist/components/button.js +3 -3
  49. package/dist/components/calendar.js +3 -3
  50. package/dist/components/card.js +3 -3
  51. package/dist/components/carousel.js +3 -3
  52. package/dist/components/category-deck.js +2 -2
  53. package/dist/components/checkbox.js +2 -2
  54. package/dist/components/circular-progress.js +1 -1
  55. package/dist/components/collapsible.js +1 -1
  56. package/dist/components/combobox.js +6 -6
  57. package/dist/components/command.js +3 -3
  58. package/dist/components/concept-card.js +2 -2
  59. package/dist/components/concept-compare-rail.js +3 -3
  60. package/dist/components/concept-compare.d.ts +2 -1
  61. package/dist/components/concept-compare.js +5 -5
  62. package/dist/components/concept-convergence.js +4 -4
  63. package/dist/components/convergence.js +3 -3
  64. package/dist/components/coverflow.js +1 -1
  65. package/dist/components/currency-input.js +2 -2
  66. package/dist/components/date-input.js +2 -2
  67. package/dist/components/day-rail-chip.js +2 -2
  68. package/dist/components/description-list.js +1 -1
  69. package/dist/components/dialog.js +2 -2
  70. package/dist/components/doorway-tiles.d.ts +57 -0
  71. package/dist/components/doorway-tiles.js +86 -0
  72. package/dist/components/drawer.js +1 -1
  73. package/dist/components/drift-rail.js +1 -1
  74. package/dist/components/dropdown-menu.js +1 -1
  75. package/dist/components/empty-state.js +4 -4
  76. package/dist/components/event-card.js +4 -4
  77. package/dist/components/event-compact-row.js +4 -4
  78. package/dist/components/event-day-header.js +2 -2
  79. package/dist/components/event-day-separator.js +3 -3
  80. package/dist/components/event-detail-panel.js +5 -5
  81. package/dist/components/event-hub-hero.js +2 -2
  82. package/dist/components/event-index-row.js +4 -4
  83. package/dist/components/event-list-row-wide.js +6 -6
  84. package/dist/components/event-list-row.js +5 -5
  85. package/dist/components/event-poster-fallback.js +1 -1
  86. package/dist/components/event-types.js +1 -1
  87. package/dist/components/events-empty-state.js +1 -1
  88. package/dist/components/explainer-steps.js +1 -1
  89. package/dist/components/facet-group.js +2 -2
  90. package/dist/components/featured-rail-item.js +2 -2
  91. package/dist/components/field.js +2 -2
  92. package/dist/components/filter-category-row.js +3 -3
  93. package/dist/components/form.js +2 -2
  94. package/dist/components/glass-ground.js +2 -2
  95. package/dist/components/grade-atom.d.ts +3 -0
  96. package/dist/components/grade-atom.js +4 -0
  97. package/dist/components/hour-band.js +4 -4
  98. package/dist/components/hour-group.js +4 -4
  99. package/dist/components/hub-surface.js +1 -1
  100. package/dist/components/hue-chip.js +1 -1
  101. package/dist/components/icon.js +1 -1
  102. package/dist/components/input-group.js +1 -1
  103. package/dist/components/input-otp.js +1 -1
  104. package/dist/components/input.js +2 -2
  105. package/dist/components/item.js +1 -1
  106. package/dist/components/kbd.js +1 -1
  107. package/dist/components/kitchen-dossier.d.ts +2 -1
  108. package/dist/components/kitchen-dossier.js +4 -4
  109. package/dist/components/label.js +2 -2
  110. package/dist/components/lazy-markdown.js +1 -1
  111. package/dist/components/link-button.js +3 -3
  112. package/dist/components/markdown-renderer.js +1 -1
  113. package/dist/components/menu-dead-end.d.ts +2 -1
  114. package/dist/components/menu-dead-end.js +1 -1
  115. package/dist/components/menu-family-block.d.ts +2 -1
  116. package/dist/components/menu-family-block.js +15 -9
  117. package/dist/components/menu-family-card.d.ts +2 -1
  118. package/dist/components/menu-family-card.js +1 -1
  119. package/dist/components/menu-field.js +1 -1
  120. package/dist/components/menu-filter-chips.d.ts +2 -1
  121. package/dist/components/menu-filter-chips.js +3 -3
  122. package/dist/components/menu-group-rows.d.ts +2 -1
  123. package/dist/components/menu-group-rows.js +2 -2
  124. package/dist/components/menu-hub-hero.js +1 -1
  125. package/dist/components/menu-index-header.d.ts +2 -1
  126. package/dist/components/menu-index-header.js +2 -2
  127. package/dist/components/menu-index-list.d.ts +2 -1
  128. package/dist/components/menu-index-list.js +5 -5
  129. package/dist/components/menu-intent-hero.d.ts +3 -2
  130. package/dist/components/menu-intent-hero.js +1 -1
  131. package/dist/components/menu-item.d.ts +2 -1
  132. package/dist/components/menu-item.js +4 -4
  133. package/dist/components/menu-kitchen-list.d.ts +2 -1
  134. package/dist/components/menu-kitchen-list.js +4 -4
  135. package/dist/components/menu-mocks.d.ts +16 -5
  136. package/dist/components/menu-mocks.js +23 -2
  137. package/dist/components/menu-types.d.ts +21 -34
  138. package/dist/components/motion-icon-actions.d.ts +27 -0
  139. package/dist/components/motion-icon-actions.js +336 -0
  140. package/dist/components/motion-icon-controls.js +3 -3
  141. package/dist/components/motion-icon-set.d.ts +13 -1
  142. package/dist/components/motion-icon-set.js +197 -6
  143. package/dist/components/motion-icon.d.ts +15 -1
  144. package/dist/components/motion-icon.js +2 -2
  145. package/dist/components/multi-select-combobox.js +8 -8
  146. package/dist/components/multi-select-menu.js +2 -2
  147. package/dist/components/native-select.js +1 -1
  148. package/dist/components/open-now-list.d.ts +2 -1
  149. package/dist/components/open-now-list.js +2 -2
  150. package/dist/components/pagination.js +3 -3
  151. package/dist/components/phone-frame.js +1 -1
  152. package/dist/components/photo-ground.js +1 -1
  153. package/dist/components/place-about-tab.d.ts +2 -1
  154. package/dist/components/place-about-tab.js +6 -6
  155. package/dist/components/place-card.d.ts +94 -12
  156. package/dist/components/place-card.js +203 -128
  157. package/dist/components/place-details-mobile.d.ts +2 -1
  158. package/dist/components/place-details-mobile.js +7 -7
  159. package/dist/components/place-schedule-week.d.ts +2 -1
  160. package/dist/components/place-schedule-week.js +3 -3
  161. package/dist/components/place-types.d.ts +3 -265
  162. package/dist/components/popover.js +2 -2
  163. package/dist/components/poster-stack.js +1 -1
  164. package/dist/components/poster-tape.js +1 -1
  165. package/dist/components/poster-wall.d.ts +22 -6
  166. package/dist/components/poster-wall.js +197 -12
  167. package/dist/components/progress.js +1 -1
  168. package/dist/components/qa-block.d.ts +2 -1
  169. package/dist/components/qa-block.js +1 -1
  170. package/dist/components/quiet-stretch.js +1 -1
  171. package/dist/components/radio-group.js +1 -1
  172. package/dist/components/rating.js +1 -1
  173. package/dist/components/scroll-area.js +1 -1
  174. package/dist/components/scroll-fade.js +2 -2
  175. package/dist/components/search-bar.js +1 -1
  176. package/dist/components/searchable-select.js +6 -6
  177. package/dist/components/section-header.js +2 -2
  178. package/dist/components/segmented-nav.d.ts +24 -4
  179. package/dist/components/segmented-nav.js +11 -2
  180. package/dist/components/select.js +1 -1
  181. package/dist/components/selection-bar.js +1 -1
  182. package/dist/components/separator.js +2 -2
  183. package/dist/components/sheet.js +1 -1
  184. package/dist/components/signature-chip.js +3 -3
  185. package/dist/components/skeleton-card.js +3 -3
  186. package/dist/components/skeleton.js +2 -2
  187. package/dist/components/slider.js +1 -1
  188. package/dist/components/sliding-panels.js +3 -3
  189. package/dist/components/snap-rail.js +2 -2
  190. package/dist/components/sonner.js +1 -1
  191. package/dist/components/sort-option-list.js +1 -1
  192. package/dist/components/spinner.js +1 -1
  193. package/dist/components/stat-banner.js +1 -1
  194. package/dist/components/stepper.js +1 -1
  195. package/dist/components/sticky-container.js +1 -1
  196. package/dist/components/story.js +1 -1
  197. package/dist/components/switch.js +1 -1
  198. package/dist/components/table.js +1 -1
  199. package/dist/components/tabs.js +2 -2
  200. package/dist/components/tag-group.js +2 -2
  201. package/dist/components/tag.js +2 -2
  202. package/dist/components/text.js +2 -2
  203. package/dist/components/textarea.js +1 -1
  204. package/dist/components/timeline-band.js +4 -4
  205. package/dist/components/timeline-playhead.js +4 -4
  206. package/dist/components/toggle-group.js +2 -2
  207. package/dist/components/toggle.js +2 -2
  208. package/dist/components/tooltip.js +1 -1
  209. package/dist/components/top-comparison.d.ts +2 -1
  210. package/dist/components/top-comparison.js +1 -1
  211. package/dist/components/top-cross-link.d.ts +2 -1
  212. package/dist/components/top-cross-link.js +1 -1
  213. package/dist/components/top-ranking-entry.d.ts +2 -1
  214. package/dist/components/top-ranking-entry.js +6 -6
  215. package/dist/components/top-ranking-hero.d.ts +2 -1
  216. package/dist/components/top-ranking-hero.js +1 -1
  217. package/dist/components/top-ranking-legend.js +1 -1
  218. package/dist/components/top-types.d.ts +3 -1
  219. package/dist/components/use-mobile.js +1 -1
  220. package/dist/components/venue-events-block.js +5 -5
  221. package/dist/components/venue-grade-dimensions.d.ts +2 -1
  222. package/dist/components/venue-grade-dimensions.js +1 -1
  223. package/dist/components/venue-quotes.d.ts +2 -1
  224. package/dist/components/venue-quotes.js +1 -1
  225. package/dist/components/venue-wall.d.ts +2 -1
  226. package/dist/components/venue-wall.js +1 -1
  227. package/dist/components/weekday-theme-banner.js +1 -1
  228. package/dist/components/weekend-panel.js +4 -4
  229. package/dist/hooks/use-drag-swipe.js +1 -1
  230. package/dist/hooks/use-haptic.js +2 -2
  231. package/dist/hooks/use-media-query.js +1 -1
  232. package/dist/hooks/use-mobile.js +1 -1
  233. package/dist/hooks/use-mounted-theme.js +1 -1
  234. package/dist/hooks/use-sticky.js +1 -1
  235. package/dist/lib/card-hover.js +2 -18
  236. package/dist/lib/grade.d.ts +54 -0
  237. package/dist/lib/grade.js +2 -0
  238. package/dist/lib/icon-depth.d.ts +35 -0
  239. package/dist/lib/icon-depth.js +2 -0
  240. package/dist/lib/icon-registry.js +1 -1
  241. package/dist/lib/motion.js +2 -2
  242. package/dist/lib/overlay-pill.d.ts +30 -0
  243. package/dist/lib/overlay-pill.js +2 -0
  244. package/dist/lib/utils.js +1 -1
  245. package/dist/place-types-BHdeMMiG.d.ts +317 -0
  246. package/package.json +1 -1
  247. package/src/components/button.tsx +5 -1
  248. package/src/components/colour-dictionary.stories.tsx +268 -82
  249. package/src/components/doorway-tiles.stories.tsx +83 -0
  250. package/src/components/doorway-tiles.test.tsx +51 -0
  251. package/src/components/doorway-tiles.tsx +225 -0
  252. package/src/components/grade-atom.stories.tsx +81 -0
  253. package/src/components/grade-atom.tsx +256 -0
  254. package/src/components/hue-chip.stories.tsx +2 -2
  255. package/src/components/menu-family-block.stories.tsx +29 -0
  256. package/src/components/menu-family-block.test.tsx +51 -0
  257. package/src/components/menu-family-block.tsx +20 -4
  258. package/src/components/menu-intent-hero.tsx +1 -1
  259. package/src/components/menu-mocks.ts +39 -3
  260. package/src/components/menu-types.ts +17 -33
  261. package/src/components/motion-icon-actions.test.tsx +115 -0
  262. package/src/components/motion-icon-actions.tsx +580 -0
  263. package/src/components/motion-icon-controls.test.tsx +1 -0
  264. package/src/components/motion-icon-controls.tsx +18 -13
  265. package/src/components/motion-icon-set.stories.tsx +62 -3
  266. package/src/components/motion-icon-set.tsx +349 -2
  267. package/src/components/motion-icon.test.tsx +180 -1
  268. package/src/components/motion-icon.tsx +97 -8
  269. package/src/components/place-card.stories.tsx +66 -76
  270. package/src/components/place-card.test.tsx +188 -100
  271. package/src/components/place-card.tsx +356 -148
  272. package/src/components/place-types.ts +17 -4
  273. package/src/components/poster-wall.stories.tsx +84 -0
  274. package/src/components/poster-wall.test.tsx +94 -3
  275. package/src/components/poster-wall.tsx +257 -9
  276. package/src/components/segmented-nav.stories.tsx +72 -1
  277. package/src/components/segmented-nav.test.tsx +96 -0
  278. package/src/components/segmented-nav.tsx +36 -4
  279. package/src/globals.css +26 -0
  280. package/src/hooks/use-haptic.test.tsx +80 -0
  281. package/src/hooks/use-haptic.ts +45 -4
  282. package/src/lib/grade.test.ts +82 -0
  283. package/src/lib/grade.ts +76 -0
  284. package/src/lib/icon-depth.ts +35 -0
  285. package/src/lib/overlay-pill.ts +29 -0
  286. package/dist/components/haptic-button.d.ts +0 -12
  287. package/dist/components/haptic-button.js +0 -17
  288. package/dist/components/menu-family-tiles.d.ts +0 -19
  289. package/dist/components/menu-family-tiles.js +0 -57
  290. package/src/components/haptic-button.stories.tsx +0 -41
  291. package/src/components/haptic-button.tsx +0 -19
  292. package/src/components/menu-family-tiles.stories.tsx +0 -54
  293. package/src/components/menu-family-tiles.test.tsx +0 -30
  294. package/src/components/menu-family-tiles.tsx +0 -132
@@ -1,19 +0,0 @@
1
- "use client";
2
-
3
- import { Button, type ButtonProps } from "./button";
4
- import { useHaptic } from "../hooks/use-haptic";
5
-
6
- interface HapticButtonProps extends ButtonProps {
7
- hapticStyle?: "light" | "medium" | "heavy";
8
- }
9
-
10
- export function HapticButton({ hapticStyle = "light", onClick, ...props }: HapticButtonProps) {
11
- const { trigger } = useHaptic();
12
-
13
- const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
14
- trigger(hapticStyle);
15
- onClick?.(e);
16
- };
17
-
18
- return <Button {...props} onClick={handleClick} haptic={false} />;
19
- }
@@ -1,54 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react";
2
- import { MenuFamilyTiles } from "./menu-family-tiles";
3
- import { mockFamilyTiles, mockFamilyTilesAll } from "./menu-mocks";
4
-
5
- const meta: Meta<typeof MenuFamilyTiles> = {
6
- title: "Menu/MenuFamilyTiles",
7
- component: MenuFamilyTiles,
8
- tags: ["autodocs"],
9
- decorators: [
10
- (Story) => (
11
- <div className="w-[1100px] bg-background p-4">
12
- <Story />
13
- </div>
14
- ),
15
- ],
16
- parameters: {
17
- docs: {
18
- description: {
19
- component:
20
- "The hub's doorways: every family the city browses by, as a grid of coloured tiles — the family's name, its venue count and one venue's photograph tilted off the corner — closed by one tile that opens the whole index. Two to a row on a phone, four from `lg`, so seven families and the index make two rows at every width.",
21
- },
22
- },
23
- },
24
- };
25
-
26
- export default meta;
27
- type Story = StoryObj<typeof MenuFamilyTiles>;
28
-
29
- export const Default: Story = { args: { tiles: mockFamilyTiles, all: mockFamilyTilesAll } };
30
-
31
- /** Two-up on a phone — seven families and the index in four rows. */
32
- export const Mobile: Story = {
33
- args: { tiles: mockFamilyTiles, all: mockFamilyTilesAll },
34
- // The grid's columns follow the viewport, not the wrapper.
35
- parameters: { viewport: { defaultViewport: "mobile1" } },
36
- decorators: [
37
- (Story) => (
38
- <div className="w-[390px] bg-background p-3">
39
- <Story />
40
- </div>
41
- ),
42
- ],
43
- };
44
-
45
- /** No photographs anywhere: every polaroid holds the family's initial. */
46
- export const WithoutPhotos: Story = {
47
- args: {
48
- tiles: mockFamilyTiles.map(({ photoUrl: _photoUrl, ...tile }) => tile),
49
- all: { ...mockFamilyTilesAll, photoUrl: undefined },
50
- },
51
- };
52
-
53
- /** The drinks tab's five going-out families, no index tile. */
54
- export const Partial: Story = { args: { tiles: mockFamilyTiles.slice(0, 5) } };
@@ -1,30 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { render, screen } from "@testing-library/react";
3
- import { MenuFamilyTiles } from "./menu-family-tiles";
4
- import { mockFamilyTiles, mockFamilyTilesAll } from "./menu-mocks";
5
-
6
- describe("MenuFamilyTiles", () => {
7
- it("renders one link per family plus the index tile, in order", () => {
8
- render(<MenuFamilyTiles tiles={mockFamilyTiles} all={mockFamilyTilesAll} />);
9
- const links = screen.getAllByRole("link");
10
- expect(links).toHaveLength(mockFamilyTiles.length + 1);
11
- expect(links[0]).toHaveAttribute("href", mockFamilyTiles[0].href);
12
- expect(links.at(-1)).toHaveAttribute("href", mockFamilyTilesAll.href);
13
- expect(links.at(-1)).toHaveTextContent(mockFamilyTilesAll.label);
14
- });
15
-
16
- // A family without a photograph keeps the polaroid — the initial fills it —
17
- // so a row of seven never has one tile with a different silhouette.
18
- it("draws the initial where a family has no photograph", () => {
19
- const [withPhoto, withoutPhoto] = [mockFamilyTiles[0], mockFamilyTiles.at(-1)!];
20
- expect(withoutPhoto.photoUrl).toBeUndefined();
21
- render(<MenuFamilyTiles tiles={[withPhoto, withoutPhoto]} />);
22
- expect(document.querySelectorAll("img")).toHaveLength(1);
23
- expect(screen.getByText(withoutPhoto.label.charAt(0))).toBeInTheDocument();
24
- });
25
-
26
- it("omits the index tile when no index is given", () => {
27
- render(<MenuFamilyTiles tiles={mockFamilyTiles.slice(0, 3)} />);
28
- expect(screen.getAllByRole("link")).toHaveLength(3);
29
- });
30
- });
@@ -1,132 +0,0 @@
1
- import { cn } from "../lib/utils";
2
- import type { MenuFamilyTile, MenuLinkComponent, MenuLinkProps } from "./menu-types";
3
- import { PhotoGround } from "./photo-ground";
4
-
5
- /**
6
- * MenuFamilyTiles — the hub's doorways: every family the city browses by, as
7
- * a grid of coloured tiles, closed by one tile that opens the whole index.
8
- *
9
- * ## A tile, not a card
10
- *
11
- * The family card this replaces was a full-width mosaic with chips, a price
12
- * band and a browse pill — a summary of the family, in a rail the reader had
13
- * to swipe through one family at a time. The tile is the family's NAME, its
14
- * venue count and one photograph on the family's hue, two to a row, so seven
15
- * families and the index fit on one phone screen. The reader is choosing a
16
- * family here, not learning about one; the summary lives on the page the tile
17
- * opens.
18
- *
19
- * ## The photograph is a polaroid, not a ground
20
- *
21
- * One square photograph, tilted, running off the tile's top and right edges
22
- * — the tile's `overflow-hidden` crops it, so it reads as a print dropped on
23
- * the tile rather than a thumbnail placed in it — with the name and the count
24
- * in the bottom-left, under the print's lower edge. Not a ground: white type
25
- * on a photograph needs a scrim heavy enough to hide the photograph, and the
26
- * hue is what tells seven tiles apart at a glance. The photograph is the
27
- * FAMILY's — a plate, a room, a glass — served from the consumer's own origin,
28
- * never a venue's (a venue on a tile about every venue reads as an
29
- * endorsement). A family with no photograph gets the same tilted square
30
- * holding `PhotoGround`'s serif initial, so the silhouette never changes.
31
- *
32
- * Every measure on the polaroid is a percentage of the tile, so the tile and
33
- * the design stay in proportion from a 170px phone cell to a 260px desktop
34
- * one: 82% of the height, 8% above the top, 4% past the right, 8° over —
35
- * measured off the design frame.
36
- *
37
- * The bottom scrim is mandatory. The honey and terrace hues sit around
38
- * `oklch(0.6…)`, where white at 14.5px is about 3:1 — under the 4.5:1 the
39
- * label needs. The scrim takes the label's corner down to where it passes on
40
- * every hue in the dictionary.
41
- *
42
- * Server-safe.
43
- */
44
-
45
- function DefaultLink({ href, ...props }: MenuLinkProps) {
46
- return <a href={href} {...props} />;
47
- }
48
-
49
- export type MenuFamilyTilesProps = {
50
- tiles: MenuFamilyTile[];
51
- /**
52
- * The closing tile: the index behind the seven, drawn exactly like them —
53
- * its own hue and photograph, `countLabel` as the caption. Only its place in
54
- * the grid says it is the index. Omit when there is none.
55
- */
56
- all?: MenuFamilyTile;
57
- linkComponent?: MenuLinkComponent;
58
- className?: string;
59
- };
60
-
61
- /**
62
- * The tile: the design's 16:10, never shorter than two lines of name plus the
63
- * count need.
64
- */
65
- const TILE =
66
- "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 " +
67
- "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 " +
68
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
69
-
70
- /**
71
- * The polaroid. Same literal family as `PHOTO_GROUND` for the shadow, never a
72
- * black `rgba` — the shadow is the ground the card family already stands on.
73
- */
74
- const POLAROID =
75
- "absolute -right-[4%] -top-[8%] z-0 block aspect-square h-[82%] overflow-hidden rounded-[10%] rotate-[8deg] " +
76
- "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";
77
-
78
- const SCRIM =
79
- "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-['']";
80
-
81
- export function MenuFamilyTiles({
82
- tiles,
83
- all,
84
- linkComponent: LinkComponent = DefaultLink,
85
- className,
86
- }: MenuFamilyTilesProps) {
87
- const cells = all ? [...tiles, all] : tiles;
88
-
89
- return (
90
- <div className={cn("grid grid-cols-2 gap-3 lg:grid-cols-4 lg:gap-3.5", className)}>
91
- {cells.map((tile) => (
92
- <LinkComponent
93
- key={tile.slug}
94
- href={tile.href}
95
- className={cn(TILE, SCRIM)}
96
- style={tile.gradient ? { backgroundImage: tile.gradient } : undefined}
97
- >
98
- <span aria-hidden className={POLAROID}>
99
- {tile.photoUrl ? (
100
- /* Not lazy: the grid sits directly under the hero, and these
101
- photographs are the first thing the reader looks at. */
102
- // eslint-disable-next-line @next/next/no-img-element
103
- <img
104
- src={tile.photoUrl}
105
- alt=""
106
- decoding="async"
107
- className="absolute inset-0 h-full w-full object-cover"
108
- />
109
- ) : (
110
- <PhotoGround
111
- photos={[undefined]}
112
- initials={tile.label.charAt(0)}
113
- mode="stack"
114
- glyphClassName="text-[44px]"
115
- />
116
- )}
117
- </span>
118
-
119
- {/* Both lines keep to the left 55%: the polaroid owns the right. */}
120
- <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]">
121
- {tile.label}
122
- </span>
123
- {tile.countLabel && (
124
- <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]">
125
- {tile.countLabel}
126
- </span>
127
- )}
128
- </LinkComponent>
129
- ))}
130
- </div>
131
- );
132
- }