@bearmenu/ui 0.8.7 → 0.8.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/dist/{chunk-637L7XQ3.js → chunk-3TNPSN4D.js} +28 -9
  2. package/dist/chunk-62ES6N32.js +27 -0
  3. package/dist/chunk-AHL45DD2.js +145 -0
  4. package/dist/chunk-AXV5O2IZ.js +58 -0
  5. package/dist/chunk-IYDH5IU6.js +66 -0
  6. package/dist/chunk-OPVKL4VS.js +18 -0
  7. package/dist/chunk-Q6QH52LJ.js +38 -0
  8. package/dist/chunk-TH6W2PWG.js +213 -0
  9. package/dist/chunk-THOVPZNN.js +37 -0
  10. package/dist/chunk-YS6BHKZO.js +92 -0
  11. package/dist/components/badge.d.ts +1 -1
  12. package/dist/components/button.d.ts +1 -1
  13. package/dist/components/category-deck.js +10 -1
  14. package/dist/components/command.d.ts +7 -7
  15. package/dist/components/concept-compare.d.ts +73 -0
  16. package/dist/components/concept-compare.js +162 -0
  17. package/dist/components/coverflow.d.ts +11 -3
  18. package/dist/components/coverflow.js +34 -51
  19. package/dist/components/date-input.d.ts +1 -1
  20. package/dist/components/drift-rail.d.ts +24 -10
  21. package/dist/components/drift-rail.js +1 -1
  22. package/dist/components/event-card.d.ts +8 -0
  23. package/dist/components/event-card.js +64 -34
  24. package/dist/components/event-compact-row.d.ts +33 -6
  25. package/dist/components/event-compact-row.js +120 -39
  26. package/dist/components/event-day-header.js +3 -1
  27. package/dist/components/event-day-separator.d.ts +7 -1
  28. package/dist/components/event-day-separator.js +3 -2
  29. package/dist/components/event-detail-panel.d.ts +171 -25
  30. package/dist/components/event-detail-panel.js +471 -174
  31. package/dist/components/event-hub-hero.d.ts +25 -4
  32. package/dist/components/event-hub-hero.js +89 -19
  33. package/dist/components/event-list-row-wide.js +1 -1
  34. package/dist/components/event-list-row.d.ts +19 -6
  35. package/dist/components/event-list-row.js +79 -35
  36. package/dist/components/hour-band.d.ts +38 -40
  37. package/dist/components/hour-band.js +55 -27
  38. package/dist/components/hour-group.js +1 -1
  39. package/dist/components/hub-surface.d.ts +27 -0
  40. package/dist/components/hub-surface.js +20 -0
  41. package/dist/components/menu-family-card.d.ts +59 -0
  42. package/dist/components/menu-family-card.js +121 -0
  43. package/dist/components/menu-field.d.ts +57 -0
  44. package/dist/components/menu-field.js +3 -0
  45. package/dist/components/menu-hub-hero.d.ts +65 -0
  46. package/dist/components/menu-hub-hero.js +103 -0
  47. package/dist/components/menu-intent-tiles.d.ts +31 -0
  48. package/dist/components/menu-intent-tiles.js +50 -0
  49. package/dist/components/menu-item.d.ts +127 -0
  50. package/dist/components/menu-item.js +5 -0
  51. package/dist/components/menu-mocks.d.ts +55 -0
  52. package/dist/components/menu-mocks.js +379 -0
  53. package/dist/components/menu-types.d.ts +245 -0
  54. package/dist/components/menu-types.js +1 -0
  55. package/dist/components/motion-icon-set.js +15 -3
  56. package/dist/components/open-now-list.d.ts +40 -0
  57. package/dist/components/open-now-list.js +69 -0
  58. package/dist/components/place-types.d.ts +112 -1
  59. package/dist/components/poster-tape.js +1 -1
  60. package/dist/components/poster-wall.d.ts +3 -3
  61. package/dist/components/poster-wall.js +73 -17
  62. package/dist/components/ranked-venue-list.d.ts +49 -0
  63. package/dist/components/ranked-venue-list.js +87 -0
  64. package/dist/components/section-header.d.ts +16 -1
  65. package/dist/components/section-header.js +26 -7
  66. package/dist/components/signature-chip.d.ts +37 -0
  67. package/dist/components/signature-chip.js +4 -0
  68. package/dist/components/signature-line-list.d.ts +45 -0
  69. package/dist/components/signature-line-list.js +99 -0
  70. package/dist/components/snap-rail.d.ts +54 -0
  71. package/dist/components/snap-rail.js +109 -0
  72. package/dist/components/spinner.d.ts +2 -2
  73. package/dist/components/stat-banner.d.ts +47 -0
  74. package/dist/components/stat-banner.js +61 -0
  75. package/dist/components/text.d.ts +3 -3
  76. package/dist/components/timeline-band.d.ts +20 -2
  77. package/dist/components/timeline-band.js +1 -1
  78. package/dist/components/timeline-playhead.d.ts +26 -0
  79. package/dist/components/timeline-playhead.js +25 -0
  80. package/dist/components/venue-busyness-strip.d.ts +35 -0
  81. package/dist/components/venue-busyness-strip.js +37 -0
  82. package/dist/components/venue-grade-dimensions.d.ts +36 -0
  83. package/dist/components/venue-grade-dimensions.js +3 -0
  84. package/dist/components/venue-quotes.d.ts +32 -0
  85. package/dist/components/venue-quotes.js +3 -0
  86. package/dist/components/venue-signature-block.d.ts +39 -0
  87. package/dist/components/venue-signature-block.js +6 -0
  88. package/dist/components/venue-spotlight.d.ts +74 -0
  89. package/dist/components/venue-spotlight.js +138 -0
  90. package/dist/components/venue-wall.d.ts +41 -0
  91. package/dist/components/venue-wall.js +118 -0
  92. package/dist/components/weekday-theme-banner.d.ts +70 -0
  93. package/dist/components/weekday-theme-banner.js +127 -0
  94. package/dist/components/weekend-panel.d.ts +5 -4
  95. package/dist/components/weekend-panel.js +85 -71
  96. package/dist/hooks/use-drag-swipe.d.ts +44 -0
  97. package/dist/hooks/use-drag-swipe.js +2 -0
  98. package/dist/hooks/use-media-query.d.ts +17 -0
  99. package/dist/hooks/use-media-query.js +2 -0
  100. package/package.json +1 -1
  101. package/src/components/category-deck.tsx +19 -8
  102. package/src/components/colour-dictionary.stories.tsx +227 -0
  103. package/src/components/concept-compare.stories.tsx +104 -0
  104. package/src/components/concept-compare.tsx +276 -0
  105. package/src/components/coverflow.test.tsx +28 -1
  106. package/src/components/coverflow.tsx +37 -43
  107. package/src/components/drift-rail.tsx +167 -42
  108. package/src/components/event-card.tsx +55 -10
  109. package/src/components/event-compact-row.stories.tsx +30 -0
  110. package/src/components/event-compact-row.test.tsx +49 -0
  111. package/src/components/event-compact-row.tsx +162 -38
  112. package/src/components/event-day-header.tsx +3 -1
  113. package/src/components/event-day-separator.tsx +8 -1
  114. package/src/components/event-detail-panel.stories.tsx +205 -97
  115. package/src/components/event-detail-panel.test.tsx +181 -0
  116. package/src/components/event-detail-panel.tsx +703 -185
  117. package/src/components/event-hub-hero.stories.tsx +30 -0
  118. package/src/components/event-hub-hero.tsx +102 -30
  119. package/src/components/event-list-row.stories.tsx +34 -0
  120. package/src/components/event-list-row.test.tsx +53 -0
  121. package/src/components/event-list-row.tsx +135 -87
  122. package/src/components/hour-band.stories.tsx +28 -5
  123. package/src/components/hour-band.tsx +90 -70
  124. package/src/components/hub-surface.stories.tsx +68 -0
  125. package/src/components/hub-surface.tsx +40 -0
  126. package/src/components/menu-family-card.stories.tsx +112 -0
  127. package/src/components/menu-family-card.tsx +211 -0
  128. package/src/components/menu-field.stories.tsx +74 -0
  129. package/src/components/menu-field.tsx +199 -0
  130. package/src/components/menu-hub-hero.stories.tsx +92 -0
  131. package/src/components/menu-hub-hero.tsx +187 -0
  132. package/src/components/menu-intent-tiles.stories.tsx +54 -0
  133. package/src/components/menu-intent-tiles.tsx +76 -0
  134. package/src/components/menu-item.tsx +416 -0
  135. package/src/components/menu-mocks.ts +417 -0
  136. package/src/components/menu-types.ts +259 -0
  137. package/src/components/motion-icon-set.tsx +30 -3
  138. package/src/components/open-now-list.stories.tsx +76 -0
  139. package/src/components/open-now-list.tsx +127 -0
  140. package/src/components/place-types.ts +112 -0
  141. package/src/components/poster-wall.test.tsx +28 -0
  142. package/src/components/poster-wall.tsx +96 -23
  143. package/src/components/ranked-venue-list.stories.tsx +80 -0
  144. package/src/components/ranked-venue-list.tsx +177 -0
  145. package/src/components/section-header.stories.tsx +24 -0
  146. package/src/components/section-header.test.tsx +28 -0
  147. package/src/components/section-header.tsx +45 -7
  148. package/src/components/signature-chip.tsx +77 -0
  149. package/src/components/signature-line-list.stories.tsx +85 -0
  150. package/src/components/signature-line-list.tsx +136 -0
  151. package/src/components/snap-rail.stories.tsx +79 -0
  152. package/src/components/snap-rail.test.tsx +18 -0
  153. package/src/components/snap-rail.tsx +148 -0
  154. package/src/components/stat-banner.stories.tsx +100 -0
  155. package/src/components/stat-banner.tsx +117 -0
  156. package/src/components/timeline-band.tsx +52 -12
  157. package/src/components/timeline-playhead.stories.tsx +30 -0
  158. package/src/components/timeline-playhead.test.tsx +11 -0
  159. package/src/components/timeline-playhead.tsx +46 -0
  160. package/src/components/venue-busyness-strip.stories.tsx +66 -0
  161. package/src/components/venue-busyness-strip.tsx +64 -0
  162. package/src/components/venue-grade-dimensions.stories.tsx +83 -0
  163. package/src/components/venue-grade-dimensions.tsx +91 -0
  164. package/src/components/venue-quotes.stories.tsx +59 -0
  165. package/src/components/venue-quotes.tsx +64 -0
  166. package/src/components/venue-signature-block.stories.tsx +68 -0
  167. package/src/components/venue-signature-block.tsx +71 -0
  168. package/src/components/venue-spotlight.stories.tsx +141 -0
  169. package/src/components/venue-spotlight.tsx +236 -0
  170. package/src/components/venue-wall.stories.tsx +78 -0
  171. package/src/components/venue-wall.tsx +185 -0
  172. package/src/components/weekday-theme-banner.stories.tsx +113 -0
  173. package/src/components/weekday-theme-banner.tsx +231 -0
  174. package/src/components/weekend-panel.test.tsx +43 -0
  175. package/src/components/weekend-panel.tsx +142 -108
  176. package/src/hooks/use-drag-swipe.test.tsx +66 -0
  177. package/src/hooks/use-drag-swipe.ts +105 -0
  178. package/src/hooks/use-media-query.ts +31 -0
  179. package/dist/chunk-GPH6L4DX.js +0 -55
@@ -0,0 +1,187 @@
1
+ import { cn } from "../lib/utils";
2
+
3
+ /**
4
+ * MenuHubHero — the claim that opens a menu hub, proven by live figures.
5
+ *
6
+ * A statement rather than a count ("Nobody should have to walk in blind"),
7
+ * over a dark ground, with the catalogue's real numbers under it and one proof
8
+ * line that demonstrates what the catalogue can do that a single menu cannot.
9
+ *
10
+ * ## Why the ground is a texture and not a photograph
11
+ *
12
+ * Menu-item photography is AI-generated and never shown, and one venue
13
+ * photograph behind a claim about every venue in the city argues the wrong
14
+ * thing. The tile grid reads as "many menus" without being any of them.
15
+ * Callers that DO have a real, licensed image for the slot can pass
16
+ * `backgroundUrl` — venue photography, never a plate.
17
+ *
18
+ * ## Figures
19
+ *
20
+ * `figures` is a list, not four fixed slots: the drinks tab has different ones
21
+ * and the count changes when a number turns out to have nothing behind it.
22
+ * Every value is a pre-formatted string because thousands separators are
23
+ * locale-dependent and the DS owns no locale.
24
+ *
25
+ * Server-safe.
26
+ */
27
+
28
+ export type MenuHubHeroFigure = {
29
+ /** Pre-formatted, e.g. "19,062". */
30
+ value: string;
31
+ label: string;
32
+ };
33
+
34
+ export type MenuHubHeroProps = {
35
+ /** Small caps line above the headline, e.g. "397 MENUS IN CLUJ-NAPOCA, READ IN FULL". */
36
+ kicker?: string;
37
+ /** Icon for the kicker, resolved by the caller. */
38
+ kickerIcon?: React.ReactNode;
39
+ headline: string;
40
+ body?: string;
41
+ figures?: MenuHubHeroFigure[];
42
+ /**
43
+ * The proof line — one demonstrated fact, e.g. "A margherita: 87 kitchens
44
+ * make one, 10 to 120 lei". Composed by the caller; clause order is
45
+ * locale-dependent.
46
+ */
47
+ proof?: React.ReactNode;
48
+ proofIcon?: React.ReactNode;
49
+ /** The action beside the proof line. Pass a rendered link or button. */
50
+ action?: React.ReactNode;
51
+ /** Real, licensed photograph for the ground. Venue imagery only — see above. */
52
+ backgroundUrl?: string;
53
+ /**
54
+ * Tiles for the mosaic ground — generic, cinematic, licensed stills, served
55
+ * from the consumer's own origin.
56
+ *
57
+ * A mosaic rather than one photograph because the hero's claim is about EVERY
58
+ * kitchen in the city: a single image makes it about one of them. Given fewer
59
+ * tiles than cells the list repeats, so a caller can ship six and still fill
60
+ * a 1440px hero.
61
+ */
62
+ tileUrls?: string[];
63
+ className?: string;
64
+ };
65
+
66
+ /** Enough cells to fill the widest hero without the grid reading as a pattern. */
67
+ const TEXTURE_CELLS = 28;
68
+
69
+ export function MenuHubHero({
70
+ kicker,
71
+ kickerIcon,
72
+ headline,
73
+ body,
74
+ figures,
75
+ proof,
76
+ proofIcon,
77
+ action,
78
+ backgroundUrl,
79
+ tileUrls,
80
+ className,
81
+ }: MenuHubHeroProps) {
82
+ return (
83
+ <div
84
+ className={cn(
85
+ "relative flex min-h-[260px] overflow-hidden rounded-[20px] bg-[oklch(0.21_0.012_55)] lg:min-h-[440px]",
86
+ className
87
+ )}
88
+ >
89
+ {backgroundUrl ? (
90
+ // eslint-disable-next-line @next/next/no-img-element
91
+ <img
92
+ src={backgroundUrl}
93
+ alt=""
94
+ aria-hidden
95
+ className="absolute inset-0 h-full w-full object-cover opacity-[0.42]"
96
+ />
97
+ ) : (
98
+ <div
99
+ aria-hidden
100
+ className="absolute inset-0 grid auto-rows-fr grid-cols-4 gap-0.5 lg:grid-cols-7"
101
+ >
102
+ {Array.from({ length: TEXTURE_CELLS }, (_, cell) => {
103
+ const url = tileUrls?.length ? tileUrls[cell % tileUrls.length] : undefined;
104
+
105
+ return url ? (
106
+ // eslint-disable-next-line @next/next/no-img-element
107
+ <img
108
+ key={cell}
109
+ src={url}
110
+ alt=""
111
+ loading="lazy"
112
+ decoding="async"
113
+ className="h-full w-full object-cover"
114
+ />
115
+ ) : (
116
+ <div
117
+ key={cell}
118
+ className="bg-[repeating-linear-gradient(135deg,oklch(0.6_0.02_55)_0_9px,oklch(0.68_0.016_58)_9px_18px)] opacity-[0.42]"
119
+ />
120
+ );
121
+ })}
122
+ </div>
123
+ )}
124
+
125
+ {/*
126
+ Heavier over a photo mosaic than it was over flat texture: twelve
127
+ cinematic stills at full strength turn a claim into a collage and the
128
+ headline stops being the loudest thing on the banner. This keeps the
129
+ depth and gives the type its contrast back.
130
+ */}
131
+ <div
132
+ aria-hidden
133
+ className={cn(
134
+ "absolute inset-0",
135
+ tileUrls?.length || backgroundUrl
136
+ ? "bg-[linear-gradient(100deg,oklch(0.16_0.012_55_/_0.96)_0%,oklch(0.16_0.012_55_/_0.88)_52%,oklch(0.16_0.012_55_/_0.72)_100%)]"
137
+ : "bg-[linear-gradient(100deg,oklch(0.18_0.012_55_/_0.95)_0%,oklch(0.18_0.012_55_/_0.72)_52%,oklch(0.18_0.012_55_/_0.3)_100%)]"
138
+ )}
139
+ />
140
+
141
+ <div className="relative z-[1] flex w-full flex-col justify-center gap-3 p-5 text-white lg:gap-4 lg:px-12 lg:py-11">
142
+ {kicker && (
143
+ <span className="inline-flex w-max items-center gap-[7px] rounded-full bg-white/15 px-3 py-1.5 text-[11px] font-extrabold tracking-[0.11em]">
144
+ {kickerIcon}
145
+ {kicker}
146
+ </span>
147
+ )}
148
+
149
+ <h1 className="max-w-[640px] font-serif text-[32px] font-semibold leading-[1.04] tracking-[-0.025em] lg:text-[50px]">
150
+ {headline}
151
+ </h1>
152
+
153
+ {body && (
154
+ <p className="hidden max-w-[540px] text-sm leading-[1.6] opacity-[0.84] lg:block lg:text-[15.5px]">
155
+ {body}
156
+ </p>
157
+ )}
158
+
159
+ {figures && figures.length > 0 && (
160
+ // Two by two on the phone: four figures in one row wrapped to three and an orphan.
161
+ <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">
162
+ {figures.map((figure) => (
163
+ <div key={figure.label}>
164
+ <div className="text-2xl font-black leading-none tracking-[-0.03em] tabular-nums lg:text-[32px]">
165
+ {figure.value}
166
+ </div>
167
+ <div className="mt-[5px] text-xs opacity-[0.72]">{figure.label}</div>
168
+ </div>
169
+ ))}
170
+ </div>
171
+ )}
172
+
173
+ {(proof || action) && (
174
+ <div className="mt-auto flex flex-wrap items-center gap-3 pt-5">
175
+ {proof && (
176
+ <span className="inline-flex min-h-11 items-center gap-2.5 rounded-full bg-white/10 px-4 py-2 text-[13px] backdrop-blur-[6px]">
177
+ {proofIcon}
178
+ {proof}
179
+ </span>
180
+ )}
181
+ {action}
182
+ </div>
183
+ )}
184
+ </div>
185
+ </div>
186
+ );
187
+ }
@@ -0,0 +1,54 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Beef, Coffee, Leaf, MapPin, Pizza, UtensilsCrossed } from "lucide-react";
3
+ import { MenuIntentTiles } from "./menu-intent-tiles";
4
+ import { mockIntentTiles } from "./menu-mocks";
5
+
6
+ const icons = [Pizza, Beef, Coffee, UtensilsCrossed, Leaf, MapPin];
7
+ const tiles = mockIntentTiles.map((tile, index) => {
8
+ const Icon = icons[index];
9
+ return { ...tile, icon: <Icon /> };
10
+ });
11
+
12
+ const meta: Meta<typeof MenuIntentTiles> = {
13
+ title: "Menu/MenuIntentTiles",
14
+ component: MenuIntentTiles,
15
+ tags: ["autodocs"],
16
+ decorators: [
17
+ (Story) => (
18
+ <div className="w-[1100px] bg-background p-4">
19
+ <Story />
20
+ </div>
21
+ ),
22
+ ],
23
+ parameters: {
24
+ docs: {
25
+ description: {
26
+ component:
27
+ "The sentence people arrive with, as six coloured tiles. An intent is a family plus a constraint — Italian *tonight*, burgers, coffee — and each tile is a destination, so `href` is required on the view model: a category page that does not exist is filtered out by the caller rather than rendered as a tile linking nowhere. Roughly thirty-two of thirty-eight cuisines have a page, so that is the normal case, not the edge one.",
28
+ },
29
+ },
30
+ },
31
+ };
32
+
33
+ export default meta;
34
+ type Story = StoryObj<typeof MenuIntentTiles>;
35
+
36
+ export const Default: Story = { args: { tiles } };
37
+
38
+ /** Two-up on a phone, six-up from `lg`. */
39
+ export const Mobile: Story = {
40
+ args: { tiles },
41
+ decorators: [
42
+ (Story) => (
43
+ <div className="w-[390px] bg-background p-3">
44
+ <Story />
45
+ </div>
46
+ ),
47
+ ],
48
+ };
49
+
50
+ /** Without icons the tiles still read — the watermark is recognition, not information. */
51
+ export const WithoutIcons: Story = { args: { tiles: mockIntentTiles } };
52
+
53
+ /** Fewer tiles than a full row: the grid does not stretch them. */
54
+ export const Partial: Story = { args: { tiles: tiles.slice(0, 3) } };
@@ -0,0 +1,76 @@
1
+ import { ChevronRight } from "lucide-react";
2
+ import { cn } from "../lib/utils";
3
+ import type { MenuIntentTile, MenuLinkProps, MenuLinkComponent } from "./menu-types";
4
+
5
+ /**
6
+ * MenuIntentTiles — the sentence people arrive with, as six coloured tiles.
7
+ *
8
+ * An intent is a family plus a constraint: Italian *tonight*, burgers, coffee.
9
+ * Each tile is a destination, so `href` is required on the view model — a
10
+ * category page that does not exist is filtered out by the caller rather than
11
+ * rendered as a tile that links nowhere. Roughly thirty-two of thirty-eight
12
+ * cuisines have a page, so that case is normal, not exceptional.
13
+ *
14
+ * The icon is a watermark bleeding off the bottom-right corner at low opacity,
15
+ * not a badge: it gives the tile a silhouette to be recognised by in a rail
16
+ * without competing with the label. It is `aria-hidden` — the label already
17
+ * says what the tile is.
18
+ *
19
+ * Server-safe.
20
+ */
21
+
22
+ /** @deprecated Alias of `MenuLinkProps`; kept so existing imports keep working. */
23
+ export type MenuIntentTilesLinkProps = MenuLinkProps;
24
+
25
+ function DefaultLink({ href, ...props }: MenuIntentTilesLinkProps) {
26
+ return <a href={href} {...props} />;
27
+ }
28
+
29
+ export type MenuIntentTilesProps = {
30
+ tiles: MenuIntentTile[];
31
+ linkComponent?: MenuLinkComponent;
32
+ className?: string;
33
+ };
34
+
35
+ export function MenuIntentTiles({
36
+ tiles,
37
+ linkComponent: LinkComponent = DefaultLink,
38
+ className,
39
+ }: MenuIntentTilesProps) {
40
+ return (
41
+ <div
42
+ className={cn(
43
+ "grid grid-cols-2 gap-3 lg:grid-cols-6 lg:gap-3.5",
44
+ className
45
+ )}
46
+ >
47
+ {tiles.map((tile) => (
48
+ <LinkComponent
49
+ key={tile.slug}
50
+ href={tile.href}
51
+ className="relative flex min-h-[110px] flex-col overflow-hidden rounded-2xl bg-muted p-3.5 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 lg:min-h-[124px] lg:px-3.5 lg:pb-3.5 lg:pt-[15px]"
52
+ style={tile.gradient ? { backgroundImage: tile.gradient } : undefined}
53
+ >
54
+ {tile.icon && (
55
+ <span
56
+ aria-hidden
57
+ className="pointer-events-none absolute -bottom-2.5 -right-1.5 z-0 opacity-[0.22] [&>svg]:h-[62px] [&>svg]:w-[62px]"
58
+ >
59
+ {tile.icon}
60
+ </span>
61
+ )}
62
+
63
+ <span className="relative z-[1] flex h-full flex-col">
64
+ <span className="text-[14.5px] font-extrabold leading-[1.2] tracking-[-0.015em]">
65
+ {tile.label}
66
+ </span>
67
+ {tile.caption && (
68
+ <span className="mt-[3px] text-[11.5px] leading-[1.35]">{tile.caption}</span>
69
+ )}
70
+ <ChevronRight className="mt-auto h-4 w-4 opacity-85" aria-hidden />
71
+ </span>
72
+ </LinkComponent>
73
+ ))}
74
+ </div>
75
+ );
76
+ }