@bearmenu/ui 0.8.16 → 0.8.17

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 (87) hide show
  1. package/dist/{chunk-ZLPWEHZO.js → chunk-54FQQZZJ.js} +1 -1
  2. package/dist/{chunk-36KEHCET.js → chunk-5LTILG3L.js} +15 -4
  3. package/dist/{chunk-P43B3ONY.js → chunk-5XREWOBB.js} +3 -1
  4. package/dist/{chunk-CLLY6XB3.js → chunk-6VOWT4YR.js} +6 -3
  5. package/dist/{chunk-S33K4RC5.js → chunk-BKKQ7GCZ.js} +1 -1
  6. package/dist/chunk-QY66ME5B.js +207 -0
  7. package/dist/{chunk-T6S2ZBV6.js → chunk-SDWN6L4O.js} +1 -1
  8. package/dist/{chunk-CNJQUHCU.js → chunk-SWD6CJ35.js} +2 -1
  9. package/dist/{chunk-HDR7V6IZ.js → chunk-TIQNM4XD.js} +8 -1
  10. package/dist/components/alert-dialog.js +1 -1
  11. package/dist/components/badge.d.ts +1 -1
  12. package/dist/components/badge.js +1 -1
  13. package/dist/components/button.d.ts +1 -1
  14. package/dist/components/button.js +1 -1
  15. package/dist/components/calendar.js +1 -1
  16. package/dist/components/carousel.js +1 -1
  17. package/dist/components/coach-marks.d.ts +63 -0
  18. package/dist/components/coach-marks.js +162 -0
  19. package/dist/components/combobox.js +1 -1
  20. package/dist/components/concept-compare-rail.js +2 -2
  21. package/dist/components/concept-compare.js +2 -2
  22. package/dist/components/concept-convergence.js +2 -2
  23. package/dist/components/doorway-tiles.d.ts +11 -0
  24. package/dist/components/doorway-tiles.js +5 -3
  25. package/dist/components/empty-state.js +1 -1
  26. package/dist/components/event-detail-panel.js +2 -2
  27. package/dist/components/event-index-row.js +1 -1
  28. package/dist/components/event-list-row-wide.js +2 -2
  29. package/dist/components/event-list-row.js +1 -1
  30. package/dist/components/facet-group.d.ts +8 -1
  31. package/dist/components/facet-group.js +3 -0
  32. package/dist/components/filter-category-row.js +1 -1
  33. package/dist/components/hue-chip.js +1 -1
  34. package/dist/components/item.js +4 -1
  35. package/dist/components/kitchen-dossier.js +1 -1
  36. package/dist/components/link-button.js +1 -1
  37. package/dist/components/menu-family-block.d.ts +8 -1
  38. package/dist/components/menu-family-block.js +101 -68
  39. package/dist/components/menu-family-card.js +2 -2
  40. package/dist/components/menu-filter-chips.d.ts +12 -3
  41. package/dist/components/menu-filter-chips.js +22 -4
  42. package/dist/components/menu-index-list.js +1 -1
  43. package/dist/components/menu-item.js +3 -3
  44. package/dist/components/menu-kitchen-list.d.ts +14 -2
  45. package/dist/components/menu-kitchen-list.js +1 -1
  46. package/dist/components/multi-select-combobox.js +2 -2
  47. package/dist/components/pagination.js +1 -1
  48. package/dist/components/photo-ground.d.ts +7 -1
  49. package/dist/components/photo-ground.js +1 -1
  50. package/dist/components/place-card.js +1 -1
  51. package/dist/components/place-details-mobile.js +1 -1
  52. package/dist/components/searchable-select.js +1 -1
  53. package/dist/components/section-header.d.ts +7 -1
  54. package/dist/components/section-header.js +11 -7
  55. package/dist/components/signature-chip.js +2 -2
  56. package/dist/components/sliding-panels.js +1 -1
  57. package/dist/components/toggle-group.js +9 -4
  58. package/dist/components/toggle.d.ts +1 -1
  59. package/dist/components/toggle.js +1 -1
  60. package/dist/components/top-ranking-entry.js +3 -3
  61. package/package.json +1 -1
  62. package/src/components/badge.stories.tsx +31 -1
  63. package/src/components/badge.tsx +2 -0
  64. package/src/components/button.stories.tsx +5 -4
  65. package/src/components/button.test.tsx +11 -1
  66. package/src/components/button.tsx +16 -5
  67. package/src/components/coach-marks.stories.tsx +79 -0
  68. package/src/components/coach-marks.test.tsx +140 -0
  69. package/src/components/coach-marks.tsx +252 -0
  70. package/src/components/doorway-tiles.stories.tsx +34 -0
  71. package/src/components/doorway-tiles.test.tsx +27 -0
  72. package/src/components/doorway-tiles.tsx +30 -2
  73. package/src/components/facet-group.tsx +9 -0
  74. package/src/components/hue-chip.test.tsx +6 -3
  75. package/src/components/hue-chip.tsx +5 -2
  76. package/src/components/item.tsx +4 -1
  77. package/src/components/menu-family-block.stories.tsx +12 -0
  78. package/src/components/menu-family-block.tsx +53 -20
  79. package/src/components/menu-filter-chips.tsx +32 -4
  80. package/src/components/menu-kitchen-list.tsx +45 -6
  81. package/src/components/photo-ground.tsx +7 -0
  82. package/src/components/section-header.test.tsx +15 -0
  83. package/src/components/section-header.tsx +26 -12
  84. package/src/components/toggle-group.stories.tsx +37 -1
  85. package/src/components/toggle-group.tsx +11 -6
  86. package/src/components/toggle.tsx +8 -0
  87. package/dist/chunk-SXQZ7DLH.js +0 -167
@@ -59,11 +59,19 @@ export type MenuFilterChipsProps = {
59
59
  onMorePointerDown?: () => void;
60
60
  /** Locale-aware integer formatter for the counts. Defaults to `String`. */
61
61
  formatCount?: (value: number) => string;
62
+ /**
63
+ * `sm` (36px) is the row under a page header. `lg` (44px) is for a row
64
+ * inside a sticky bar, where the chips are the only thing the thumb can
65
+ * reach and 36px sits under the target floor.
66
+ */
67
+ chipSize?: MenuFilterChipSize;
62
68
  /** Accessible name for the row. */
63
69
  ariaLabel?: string;
64
70
  className?: string;
65
71
  };
66
72
 
73
+ export type MenuFilterChipSize = "sm" | "lg";
74
+
67
75
  export function MenuFilterChips({
68
76
  chips,
69
77
  onToggle,
@@ -73,6 +81,7 @@ export function MenuFilterChips({
73
81
  moreIcon,
74
82
  onMorePointerDown,
75
83
  formatCount = String,
84
+ chipSize = "sm",
76
85
  ariaLabel,
77
86
  className,
78
87
  }: MenuFilterChipsProps) {
@@ -99,6 +108,7 @@ export function MenuFilterChips({
99
108
  <Chip
100
109
  key={chip.key}
101
110
  chip={chip}
111
+ size={chipSize}
102
112
  onToggle={() => onToggle(chip.key)}
103
113
  formatCount={formatCount}
104
114
  />
@@ -120,7 +130,11 @@ export function MenuFilterChips({
120
130
  onPointerDown={onMorePointerDown}
121
131
  aria-label={moreLabel}
122
132
  aria-haspopup="dialog"
123
- className="inline-flex size-10 shrink-0 items-center justify-center rounded-full border border-border text-accent transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none lg:hidden"
133
+ className={cn(
134
+ "inline-flex shrink-0 items-center justify-center rounded-full border border-border text-accent transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none lg:hidden",
135
+ // The one control that never scrolls keeps the chips' height.
136
+ chipSize === "lg" ? "size-11" : "size-10"
137
+ )}
124
138
  >
125
139
  {moreIcon}
126
140
  </button>
@@ -143,10 +157,12 @@ export function MenuFilterChips({
143
157
 
144
158
  function Chip({
145
159
  chip,
160
+ size,
146
161
  onToggle,
147
162
  formatCount,
148
163
  }: {
149
164
  chip: MenuFilterChip;
165
+ size: MenuFilterChipSize;
150
166
  onToggle: () => void;
151
167
  formatCount: (value: number) => string;
152
168
  }) {
@@ -156,7 +172,8 @@ function Chip({
156
172
  onClick={onToggle}
157
173
  aria-pressed={chip.active}
158
174
  className={cn(
159
- "inline-flex h-9 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full border px-4 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
175
+ "inline-flex shrink-0 items-center gap-1.5 whitespace-nowrap rounded-full border px-4 text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring motion-reduce:transition-none",
176
+ size === "lg" ? "h-11" : "h-9",
160
177
  chip.active
161
178
  ? "border-accent font-semibold text-accent shadow-[0_1px_2px_oklch(0.51_0.095_45_/_0.08)]"
162
179
  : "border-border bg-card text-foreground hover:border-accent/40",
@@ -226,12 +243,23 @@ export function MenuFilterSummary({
226
243
  */
227
244
  const CHIP_WIDTHS = [128, 104, 96, 148, 112, 88];
228
245
 
229
- export function MenuFilterChipsSkeleton({ className }: { className?: string }) {
246
+ export function MenuFilterChipsSkeleton({
247
+ className,
248
+ chipSize = "sm",
249
+ }: {
250
+ className?: string;
251
+ /** Match the row it stands in for, so the chips do not jump 8px on load. */
252
+ chipSize?: MenuFilterChipSize;
253
+ }) {
230
254
  return (
231
255
  <div className={cn("flex items-center gap-2 overflow-hidden", className)} aria-hidden>
232
256
  <Skeleton className="h-3 w-[92px] shrink-0" />
233
257
  {CHIP_WIDTHS.map((width, index) => (
234
- <Skeleton key={index} className="h-9 shrink-0 rounded-full" style={{ width }} />
258
+ <Skeleton
259
+ key={index}
260
+ className={cn("shrink-0 rounded-full", chipSize === "lg" ? "h-11" : "h-9")}
261
+ style={{ width }}
262
+ />
235
263
  ))}
236
264
  </div>
237
265
  );
@@ -61,6 +61,18 @@ export type MenuKitchenListLabels = {
61
61
  showMore?: string;
62
62
  };
63
63
 
64
+ /**
65
+ * `wide` (default): the intent page's full-width list — from `lg` the venue
66
+ * takes a fixed 226px column and the dish stands beside it in a column of its
67
+ * own. `stacked`: the list sits in a narrow column (a concept card's row
68
+ * half), so the venue keeps the row and the dish takes a SECOND LINE under it
69
+ * from `lg`; in a half-width card the dish column had ~100px and its kicker,
70
+ * name and meta wrapped into a stack of single words. `phone`: the phone row
71
+ * at every width — no dish line, the venue and its price only — for a card
72
+ * that keeps its phone form on a desktop rail.
73
+ */
74
+ export type MenuKitchenListLayout = "wide" | "stacked" | "phone";
75
+
64
76
  export type MenuKitchenListProps = {
65
77
  kitchens: MenuKitchenPreview[];
66
78
  labels: MenuKitchenListLabels;
@@ -69,6 +81,7 @@ export type MenuKitchenListProps = {
69
81
  * navigates for the eleventh — the list is the page.
70
82
  */
71
83
  onShowMore?: () => void;
84
+ layout?: MenuKitchenListLayout;
72
85
  linkComponent?: MenuLinkComponent;
73
86
  className?: string;
74
87
  };
@@ -77,6 +90,7 @@ export function MenuKitchenList({
77
90
  kitchens,
78
91
  labels,
79
92
  onShowMore,
93
+ layout = "wide",
80
94
  linkComponent = DefaultLink,
81
95
  className,
82
96
  }: MenuKitchenListProps) {
@@ -90,7 +104,12 @@ export function MenuKitchenList({
90
104
  because `<ol>` admits only list items, so a sibling element
91
105
  between them would be invalid markup. */}
92
106
  {index > 0 && <Separator />}
93
- <KitchenRow kitchen={kitchen} labels={labels} linkComponent={linkComponent} />
107
+ <KitchenRow
108
+ kitchen={kitchen}
109
+ labels={labels}
110
+ layout={layout}
111
+ linkComponent={linkComponent}
112
+ />
94
113
  </li>
95
114
  ))}
96
115
  </ol>
@@ -111,17 +130,25 @@ export function MenuKitchenList({
111
130
  function KitchenRow({
112
131
  kitchen,
113
132
  labels,
133
+ layout,
114
134
  linkComponent: LinkComponent,
115
135
  }: {
116
136
  kitchen: MenuKitchenPreview;
117
137
  labels: MenuKitchenListLabels;
138
+ layout: MenuKitchenListLayout;
118
139
  linkComponent: MenuLinkComponent;
119
140
  }) {
120
141
  const { place, dish } = kitchen;
121
142
  const openNow = place.open_now === true;
143
+ const wide = layout === "wide";
122
144
 
123
145
  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">
146
+ <div
147
+ className={cn(
148
+ "relative flex flex-wrap items-center gap-[11px] py-3 transition-colors hover:bg-muted/30 motion-reduce:transition-none lg:gap-4 lg:py-3.5",
149
+ wide && "lg:flex-nowrap"
150
+ )}
151
+ >
125
152
  {/* The venue link, stretched. `z-[1]` — the dish link sits above it. */}
126
153
  <LinkComponent
127
154
  href={kitchen.href}
@@ -149,7 +176,12 @@ function KitchenRow({
149
176
  />
150
177
  </span>
151
178
 
152
- <div className="flex min-w-0 flex-1 flex-col gap-0.5 lg:w-[226px] lg:flex-none">
179
+ <div
180
+ className={cn(
181
+ "flex min-w-0 flex-1 flex-col gap-0.5",
182
+ wide && "lg:w-[226px] lg:flex-none"
183
+ )}
184
+ >
153
185
  {/* One line with an ellipsis on a phone, where the row is the whole
154
186
  width; two lines from `lg`, where the column is 226px and
155
187
  "ADRIATICA GELATERIA ITALIANA" would otherwise lose its half. */}
@@ -190,8 +222,15 @@ function KitchenRow({
190
222
  one tap away on the venue's own page, with room to state it.
191
223
 
192
224
  `order-5` puts it last in the visual order without moving it in the
193
- DOM, where it belongs to the venue. */
194
- <div className="hidden min-w-0 basis-full pl-[57px] lg:order-none lg:block lg:flex-1 lg:basis-auto lg:pl-0">
225
+ DOM, where it belongs to the venue. In the `stacked` layout it is a
226
+ second LINE under the venue, indented past the thumbnail. */
227
+ <div
228
+ className={cn(
229
+ "hidden min-w-0 basis-full pl-[57px]",
230
+ layout !== "phone" && "lg:block",
231
+ wide ? "lg:order-none lg:flex-1 lg:basis-auto lg:pl-0" : "lg:order-5 lg:pl-[74px]"
232
+ )}
233
+ >
195
234
  <span
196
235
  className={cn(
197
236
  "flex items-center gap-1.5 text-[9px] font-extrabold uppercase tracking-[0.1em]",
@@ -230,7 +269,7 @@ function KitchenRow({
230
269
  </div>
231
270
  )}
232
271
 
233
- <div className="ml-auto flex shrink-0 items-center gap-[11px] lg:ml-0">
272
+ <div className={cn("ml-auto flex shrink-0 items-center gap-[11px]", wide && "lg:ml-0")}>
234
273
  {kitchen.priceLabel && (
235
274
  <span className="text-base font-extrabold tabular-nums text-accent">
236
275
  {kitchen.priceLabel}
@@ -38,6 +38,13 @@ import { cn } from "../lib/utils";
38
38
  /** The ground under the photography. Fixed, and deliberately not a token. */
39
39
  export const PHOTO_GROUND = "bg-[oklch(0.21_0.012_55)]";
40
40
 
41
+ /**
42
+ * The shadow a glyph wears when it stands on `PHOTO_GROUND` in a photograph's
43
+ * place — the card family's fallback treatment: a ground and a lifted glyph,
44
+ * never a bare icon on a flat fill. Same literal family as the ground.
45
+ */
46
+ export const PHOTO_GLYPH_SHADOW = "drop-shadow-[0_2px_6px_oklch(0.16_0.012_55_/_0.6)]";
47
+
41
48
  /** The uniform dark overlay a card may put over every photograph. */
42
49
  const OVERLAY = "absolute inset-0 bg-[oklch(0.16_0.012_55_/_0.5)]";
43
50
 
@@ -25,4 +25,19 @@ describe("SectionHeader", () => {
25
25
  expect(screen.getByText("Still to start").className).toContain("hidden");
26
26
  expect(screen.getByText("Still to start").className).toContain("lg:block");
27
27
  });
28
+
29
+ it("holds trailing controls at the row's end, beside the action", () => {
30
+ render(
31
+ <SectionHeader
32
+ title="Concepts"
33
+ action="See all"
34
+ actionHref="/concepts"
35
+ trailing={<button type="button">Next</button>}
36
+ />
37
+ );
38
+ const next = screen.getByRole("button", { name: "Next" });
39
+ const action = screen.getByRole("link", { name: "See all" });
40
+ expect(next.parentElement).toBe(action.parentElement);
41
+ expect(next.parentElement).not.toContainElement(screen.getByText("Concepts"));
42
+ });
28
43
  });
@@ -60,6 +60,12 @@ export type SectionHeaderProps = {
60
60
  * would dilute the headings that matter. Defaults to `"h2"`.
61
61
  */
62
62
  as?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "p" | "div";
63
+ /**
64
+ * Controls at the end of the title row, after `action` — a rail's
65
+ * previous / next pair on desktop. The header owns none of their
66
+ * behaviour; it only holds the slot.
67
+ */
68
+ trailing?: React.ReactNode;
63
69
  className?: string;
64
70
  };
65
71
 
@@ -91,6 +97,7 @@ export function SectionHeader({
91
97
  linkComponent: LinkComponent = DefaultLink,
92
98
  onAction,
93
99
  as: Heading = "h2",
100
+ trailing,
94
101
  className,
95
102
  }: SectionHeaderProps) {
96
103
  const actionIcon = <ChevronRight className="h-3.5 w-3.5" aria-hidden />;
@@ -110,18 +117,25 @@ export function SectionHeader({
110
117
  )}
111
118
  </div>
112
119
 
113
- {action &&
114
- (actionHref ? (
115
- <LinkComponent href={actionHref} className={actionClasses} onClick={onAction}>
116
- {action}
117
- {actionIcon}
118
- </LinkComponent>
119
- ) : onAction ? (
120
- <button type="button" className={actionClasses} onClick={onAction}>
121
- {action}
122
- {actionIcon}
123
- </button>
124
- ) : null)}
120
+ {/* The action and the trailing controls share the row's end, so the
121
+ action never floats to the middle when both are present. */}
122
+ {(action || trailing) && (
123
+ <div className="flex shrink-0 items-center gap-3">
124
+ {action &&
125
+ (actionHref ? (
126
+ <LinkComponent href={actionHref} className={actionClasses} onClick={onAction}>
127
+ {action}
128
+ {actionIcon}
129
+ </LinkComponent>
130
+ ) : onAction ? (
131
+ <button type="button" className={actionClasses} onClick={onAction}>
132
+ {action}
133
+ {actionIcon}
134
+ </button>
135
+ ) : null)}
136
+ {trailing}
137
+ </div>
138
+ )}
125
139
  </div>
126
140
 
127
141
  {subtitle && (
@@ -1,6 +1,16 @@
1
1
  import type { Meta, StoryObj } from "@storybook/react";
2
2
  import { ToggleGroup, ToggleGroupItem } from "./toggle-group";
3
- import { Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight } from "lucide-react";
3
+ import {
4
+ Bold,
5
+ Italic,
6
+ Underline,
7
+ AlignLeft,
8
+ AlignCenter,
9
+ AlignRight,
10
+ LayoutGrid,
11
+ List,
12
+ } from "lucide-react";
13
+ import { Button } from "./button";
4
14
 
5
15
  const meta = {
6
16
  title: "Components/ToggleGroup",
@@ -58,3 +68,29 @@ export const Outline: Story = {
58
68
  </ToggleGroup>
59
69
  ),
60
70
  };
71
+
72
+ /**
73
+ * The segmented control: one 44px muted capsule, the on cell a raised card.
74
+ * Sits beside pill buttons at the same height — the browse toolbar's view
75
+ * switch. Single, always one on.
76
+ */
77
+ export const Segmented: Story = {
78
+ render: () => (
79
+ <div className="flex items-center gap-2">
80
+ <Button variant="outline" size="pill">
81
+ Filters
82
+ </Button>
83
+ <Button variant="outline" size="pill">
84
+ Sort
85
+ </Button>
86
+ <ToggleGroup type="single" variant="segmented" defaultValue="list" aria-label="View">
87
+ <ToggleGroupItem value="list" aria-label="Rows">
88
+ <List />
89
+ </ToggleGroupItem>
90
+ <ToggleGroupItem value="grid" aria-label="Cards">
91
+ <LayoutGrid />
92
+ </ToggleGroupItem>
93
+ </ToggleGroup>
94
+ </div>
95
+ ),
96
+ };
@@ -26,13 +26,14 @@ function ToggleGroup({
26
26
  data-size={size}
27
27
  className={cn(
28
28
  "group/toggle-group flex w-fit items-center rounded-md data-[variant=outline]:shadow-xs",
29
+ // The segmented control's capsule: muted ground, the on cell a raised
30
+ // card inside it (see `toggleVariants.segmented`). 44px tall.
31
+ "data-[variant=segmented]:gap-0.5 data-[variant=segmented]:rounded-full data-[variant=segmented]:bg-muted data-[variant=segmented]:p-0.5",
29
32
  className
30
33
  )}
31
34
  {...props}
32
35
  >
33
- <ToggleGroupContext.Provider value={{ variant, size }}>
34
- {children}
35
- </ToggleGroupContext.Provider>
36
+ <ToggleGroupContext.Provider value={{ variant, size }}>{children}</ToggleGroupContext.Provider>
36
37
  </ToggleGroupPrimitive.Root>
37
38
  );
38
39
  }
@@ -45,18 +46,22 @@ function ToggleGroupItem({
45
46
  ...props
46
47
  }: React.ComponentProps<typeof ToggleGroupPrimitive.Item> & VariantProps<typeof toggleVariants>) {
47
48
  const context = React.useContext(ToggleGroupContext);
49
+ const resolved = context.variant || variant;
48
50
 
49
51
  return (
50
52
  <ToggleGroupPrimitive.Item
51
53
  data-slot="toggle-group-item"
52
- data-variant={context.variant || variant}
54
+ data-variant={resolved}
53
55
  data-size={context.size || size}
54
56
  className={cn(
55
57
  toggleVariants({
56
- variant: context.variant || variant,
58
+ variant: resolved,
57
59
  size: context.size || size,
58
60
  }),
59
- "min-w-0 flex-1 shrink-0 rounded-none shadow-none first:rounded-l-md last:rounded-r-md focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
61
+ "min-w-0 flex-1 shrink-0 shadow-none focus:z-10 focus-visible:z-10 data-[variant=outline]:border-l-0 data-[variant=outline]:first:border-l",
62
+ resolved === "segmented"
63
+ ? "rounded-full px-0 data-[state=on]:shadow-[var(--shadow-card)]"
64
+ : "rounded-none first:rounded-l-md last:rounded-r-md",
60
65
  className
61
66
  )}
62
67
  {...props}
@@ -14,6 +14,14 @@ const toggleVariants = cva(
14
14
  default: "bg-transparent",
15
15
  outline:
16
16
  "border border-border bg-transparent hover:bg-accent/50 hover:text-accent-foreground data-[state=on]:border-primary",
17
+ /**
18
+ * One cell of a segmented control: a raised card on the group's
19
+ * muted capsule when on, quiet type when off. The group
20
+ * (`ToggleGroup variant="segmented"`) supplies the capsule; the cell
21
+ * never fills with the accent, which would read as a primary button.
22
+ */
23
+ segmented:
24
+ "h-10 w-11 rounded-full text-muted-foreground hover:bg-transparent hover:text-foreground data-[state=on]:bg-card data-[state=on]:text-foreground data-[state=on]:shadow-[var(--shadow-card)]",
17
25
  },
18
26
  size: {
19
27
  default: "h-9 px-2 min-w-9",
@@ -1,167 +0,0 @@
1
- import { Separator } from './chunk-NWZGSLPU.js';
2
- import { Skeleton } from './chunk-XBBEX4SF.js';
3
- import { cn } from './chunk-FEK5XGZW.js';
4
- import { __objRest, __spreadValues } from './chunk-MMUBH76A.js';
5
- import { Star, Clock, Check, MessageSquare, ChevronRight } from 'lucide-react';
6
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
7
-
8
- function DefaultLink(_a) {
9
- var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
10
- return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
11
- }
12
- function MenuKitchenList({
13
- kitchens,
14
- labels,
15
- onShowMore,
16
- linkComponent = DefaultLink,
17
- className
18
- }) {
19
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col", className), children: [
20
- /* @__PURE__ */ jsx("ol", { children: kitchens.map((kitchen, index) => /* @__PURE__ */ jsxs("li", { className: "[&:first-child>div]:pt-0", children: [
21
- index > 0 && /* @__PURE__ */ jsx(Separator, {}),
22
- /* @__PURE__ */ jsx(KitchenRow, { kitchen, labels, linkComponent })
23
- ] }, kitchen.place.slug)) }),
24
- onShowMore && labels.showMore && /* @__PURE__ */ jsx(
25
- "button",
26
- {
27
- type: "button",
28
- onClick: onShowMore,
29
- 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",
30
- children: labels.showMore
31
- }
32
- )
33
- ] });
34
- }
35
- function KitchenRow({
36
- kitchen,
37
- labels,
38
- linkComponent: LinkComponent
39
- }) {
40
- const { place, dish } = kitchen;
41
- const openNow = place.open_now === true;
42
- return /* @__PURE__ */ jsxs("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", children: [
43
- /* @__PURE__ */ jsx(
44
- LinkComponent,
45
- {
46
- href: kitchen.href,
47
- "aria-label": labels.openVenue(place.name),
48
- className: "absolute inset-0 z-[1] rounded-xl focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
49
- }
50
- ),
51
- /* @__PURE__ */ jsxs("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]", children: [
52
- place.thumbnail_url && // eslint-disable-next-line @next/next/no-img-element
53
- /* @__PURE__ */ jsx(
54
- "img",
55
- {
56
- src: place.thumbnail_url,
57
- alt: "",
58
- loading: "lazy",
59
- decoding: "async",
60
- className: "h-full w-full object-cover"
61
- }
62
- ),
63
- /* @__PURE__ */ jsx(
64
- "span",
65
- {
66
- title: openNow ? labels.openNow : void 0,
67
- className: cn(
68
- "absolute right-[5px] top-[5px] h-[7px] w-[7px] rounded-full ring-2 ring-white/85",
69
- openNow ? "bg-[var(--free,oklch(0.55_0.13_155))]" : "bg-[oklch(0.8_0.01_60)]"
70
- )
71
- }
72
- )
73
- ] }),
74
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-0.5 lg:w-[226px] lg:flex-none", children: [
75
- /* @__PURE__ */ jsx("span", { className: "truncate text-[14.5px] font-bold tracking-[-0.01em] lg:line-clamp-2 lg:whitespace-normal lg:text-[15.5px] lg:leading-tight", children: place.name }),
76
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 truncate text-xs text-muted-foreground", children: [
77
- place.rating && /* @__PURE__ */ jsxs(Fragment, { children: [
78
- /* @__PURE__ */ jsx(Star, { className: "h-[11px] w-[11px] shrink-0", "aria-hidden": true }),
79
- place.rating.rating,
80
- kitchen.metaLabel && " \xB7 "
81
- ] }),
82
- kitchen.metaLabel
83
- ] }),
84
- kitchen.hoursLabel && /* @__PURE__ */ jsxs(
85
- "span",
86
- {
87
- className: cn(
88
- "mt-[3px] inline-flex h-[21px] w-max items-center gap-[5px] rounded-md px-2 text-[10.5px] font-bold",
89
- openNow ? "bg-[oklch(0.96_0.02_155)] text-[var(--free,oklch(0.45_0.13_155))]" : "bg-muted text-muted-foreground"
90
- ),
91
- children: [
92
- /* @__PURE__ */ jsx(Clock, { className: "h-2.5 w-2.5 shrink-0", "aria-hidden": true }),
93
- kitchen.hoursLabel
94
- ]
95
- }
96
- )
97
- ] }),
98
- dish && /* DESKTOP ONLY, where it is a column of its own beside the venue.
99
- On the phone it wrapped to a third line under the row — kicker, dish,
100
- mention count — which turned a four-line venue row into a seven-line
101
- block and, ten rows down, buried the venue names the list exists to
102
- be scanned for. The dish is not what a reader is choosing here; it is
103
- one tap away on the venue's own page, with room to state it.
104
-
105
- `order-5` puts it last in the visual order without moving it in the
106
- DOM, where it belongs to the venue. */
107
- /* @__PURE__ */ jsxs("div", { className: "hidden min-w-0 basis-full pl-[57px] lg:order-none lg:block lg:flex-1 lg:basis-auto lg:pl-0", children: [
108
- /* @__PURE__ */ jsxs(
109
- "span",
110
- {
111
- className: cn(
112
- "flex items-center gap-1.5 text-[9px] font-extrabold uppercase tracking-[0.1em]",
113
- dish.matched ? "text-accent" : "text-[oklch(0.72_0.01_60)]"
114
- ),
115
- children: [
116
- dish.kicker,
117
- dish.matched && dish.matchLabel && /* @__PURE__ */ jsxs("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", children: [
118
- /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5 shrink-0", "aria-hidden": true }),
119
- dish.matchLabel
120
- ] })
121
- ]
122
- }
123
- ),
124
- dish.href ? /* @__PURE__ */ jsx(
125
- LinkComponent,
126
- {
127
- href: dish.href,
128
- "aria-label": labels.openDish(dish.name),
129
- 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]",
130
- children: dish.name
131
- }
132
- ) : /* @__PURE__ */ jsx("span", { className: "mt-0.5 block truncate font-serif text-[13.5px] italic lg:text-[15px]", children: dish.name }),
133
- dish.meta && /* @__PURE__ */ jsxs("span", { className: "mt-[3px] flex items-center gap-1 text-[11.5px] text-muted-foreground", children: [
134
- !dish.matched && /* @__PURE__ */ jsx(MessageSquare, { className: "h-2.5 w-2.5 shrink-0", "aria-hidden": true }),
135
- dish.meta
136
- ] })
137
- ] }),
138
- /* @__PURE__ */ jsxs("div", { className: "ml-auto flex shrink-0 items-center gap-[11px] lg:ml-0", children: [
139
- kitchen.priceLabel && /* @__PURE__ */ jsx("span", { className: "text-base font-extrabold tabular-nums text-accent", children: kitchen.priceLabel }),
140
- /* @__PURE__ */ jsx(ChevronRight, { className: "h-[15px] w-[15px] shrink-0 text-muted-foreground", "aria-hidden": true })
141
- ] })
142
- ] });
143
- }
144
- function MenuKitchenListSkeleton({
145
- rows = 10,
146
- className
147
- }) {
148
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col", className), "aria-hidden": true, children: Array.from({ length: rows }, (_, index) => /* @__PURE__ */ jsxs("div", { children: [
149
- index > 0 && /* @__PURE__ */ jsx(Separator, {}),
150
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-[11px] py-3 lg:flex-nowrap lg:gap-4 lg:py-3.5", children: [
151
- /* @__PURE__ */ jsx(Skeleton, { className: "h-[46px] w-[46px] rounded-[11px] lg:h-[58px] lg:w-[58px] lg:rounded-[13px]" }),
152
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5 lg:w-[226px] lg:flex-none", children: [
153
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-[60%]" }),
154
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-[45%]" }),
155
- /* @__PURE__ */ jsx(Skeleton, { className: "h-[21px] w-[120px] rounded-md" })
156
- ] }),
157
- /* @__PURE__ */ jsxs("div", { className: "hidden min-w-0 basis-full pl-[57px] lg:order-none lg:block lg:flex-1 lg:basis-auto lg:pl-0", children: [
158
- /* @__PURE__ */ jsx(Skeleton, { className: "h-2.5 w-[90px]" }),
159
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-4 w-[70%]" }),
160
- /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-[40%]" })
161
- ] }),
162
- /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-5 w-[56px] lg:ml-0" })
163
- ] })
164
- ] }, index)) });
165
- }
166
-
167
- export { MenuKitchenList, MenuKitchenListSkeleton };