@bearmenu/ui 0.8.4 → 0.8.7

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 (150) hide show
  1. package/dist/chunk-637L7XQ3.js +85 -0
  2. package/dist/chunk-6XMYEZVR.js +26 -0
  3. package/dist/chunk-DNSXMHYX.js +8 -0
  4. package/dist/chunk-FDITZ2VM.js +16 -0
  5. package/dist/chunk-GPH6L4DX.js +55 -0
  6. package/dist/{chunk-N2KNBPAQ.js → chunk-QXHMPJ35.js} +1 -0
  7. package/dist/chunk-TT3WFS6S.js +379 -0
  8. package/dist/chunk-UXWAOISG.js +170 -0
  9. package/dist/{chunk-UZ3PCIE5.js → chunk-XWMRABZW.js} +2 -2
  10. package/dist/{chunk-VBA45GLP.js → chunk-ZDAHKI3A.js} +12 -2
  11. package/dist/components/card.d.ts +2 -2
  12. package/dist/components/card.js +4 -127
  13. package/dist/components/category-deck.d.ts +50 -0
  14. package/dist/components/category-deck.js +128 -0
  15. package/dist/components/concept-card.d.ts +43 -0
  16. package/dist/components/concept-card.js +81 -0
  17. package/dist/components/coverflow.d.ts +123 -0
  18. package/dist/components/coverflow.js +300 -0
  19. package/dist/components/day-rail-chip.d.ts +82 -0
  20. package/dist/components/day-rail-chip.js +105 -0
  21. package/dist/components/drift-rail.d.ts +28 -0
  22. package/dist/components/drift-rail.js +3 -0
  23. package/dist/components/event-card.d.ts +97 -0
  24. package/dist/components/event-card.js +164 -0
  25. package/dist/components/event-compact-row.d.ts +73 -0
  26. package/dist/components/event-compact-row.js +134 -0
  27. package/dist/components/event-day-header.d.ts +65 -0
  28. package/dist/components/event-day-header.js +95 -0
  29. package/dist/components/event-day-separator.d.ts +46 -0
  30. package/dist/components/event-day-separator.js +50 -0
  31. package/dist/components/event-detail-panel.d.ts +80 -0
  32. package/dist/components/event-detail-panel.js +251 -0
  33. package/dist/components/event-hub-hero.d.ts +34 -0
  34. package/dist/components/event-hub-hero.js +57 -0
  35. package/dist/components/event-index-row.d.ts +68 -0
  36. package/dist/components/event-index-row.js +84 -0
  37. package/dist/components/event-list-row-wide.d.ts +101 -0
  38. package/dist/components/event-list-row-wide.js +218 -0
  39. package/dist/components/event-list-row.d.ts +82 -0
  40. package/dist/components/event-list-row.js +173 -0
  41. package/dist/components/event-poster-fallback.d.ts +20 -0
  42. package/dist/components/event-poster-fallback.js +3 -0
  43. package/dist/components/event-types.d.ts +122 -0
  44. package/dist/components/event-types.js +2 -0
  45. package/dist/components/events-empty-state.d.ts +44 -0
  46. package/dist/components/events-empty-state.js +73 -0
  47. package/dist/components/facet-group.d.ts +93 -0
  48. package/dist/components/facet-group.js +222 -0
  49. package/dist/components/featured-rail-item.d.ts +42 -0
  50. package/dist/components/featured-rail-item.js +88 -0
  51. package/dist/components/hour-band.d.ts +68 -0
  52. package/dist/components/hour-band.js +53 -0
  53. package/dist/components/hour-group.d.ts +67 -0
  54. package/dist/components/hour-group.js +59 -0
  55. package/dist/components/motion-icon-controls.js +3 -379
  56. package/dist/components/multi-select-combobox.js +1 -1
  57. package/dist/components/place-about-tab.js +3 -3
  58. package/dist/components/place-details-mobile.js +3 -3
  59. package/dist/components/poster-stack.d.ts +30 -0
  60. package/dist/components/poster-stack.js +104 -0
  61. package/dist/components/poster-tape.d.ts +36 -0
  62. package/dist/components/poster-tape.js +64 -0
  63. package/dist/components/poster-wall.d.ts +25 -0
  64. package/dist/components/poster-wall.js +183 -0
  65. package/dist/components/quiet-stretch.d.ts +43 -0
  66. package/dist/components/quiet-stretch.js +54 -0
  67. package/dist/components/section-header.d.ts +53 -0
  68. package/dist/components/section-header.js +63 -0
  69. package/dist/components/segmented-nav.d.ts +62 -0
  70. package/dist/components/segmented-nav.js +54 -0
  71. package/dist/components/skeleton-card.js +1 -1
  72. package/dist/components/skeleton.d.ts +17 -0
  73. package/dist/components/skeleton.js +1 -1
  74. package/dist/components/tag-group.js +3 -26
  75. package/dist/components/tag.d.ts +2 -2
  76. package/dist/components/tag.js +1 -1
  77. package/dist/components/timeline-band.d.ts +46 -0
  78. package/dist/components/timeline-band.js +5 -0
  79. package/dist/components/venue-events-block.d.ts +143 -0
  80. package/dist/components/venue-events-block.js +222 -0
  81. package/dist/components/weekend-panel.d.ts +44 -0
  82. package/dist/components/weekend-panel.js +231 -0
  83. package/package.json +1 -1
  84. package/src/components/card.tsx +44 -1
  85. package/src/components/category-deck.stories.tsx +96 -0
  86. package/src/components/category-deck.tsx +227 -0
  87. package/src/components/concept-card.stories.tsx +80 -0
  88. package/src/components/concept-card.tsx +143 -0
  89. package/src/components/coverflow.stories.tsx +240 -0
  90. package/src/components/coverflow.test.tsx +193 -0
  91. package/src/components/coverflow.tsx +550 -0
  92. package/src/components/day-rail-chip.stories.tsx +145 -0
  93. package/src/components/day-rail-chip.tsx +201 -0
  94. package/src/components/drift-rail.stories.tsx +92 -0
  95. package/src/components/drift-rail.tsx +86 -0
  96. package/src/components/event-card.stories.tsx +248 -0
  97. package/src/components/event-card.test.tsx +188 -0
  98. package/src/components/event-card.tsx +329 -0
  99. package/src/components/event-compact-row.stories.tsx +221 -0
  100. package/src/components/event-compact-row.test.tsx +51 -0
  101. package/src/components/event-compact-row.tsx +270 -0
  102. package/src/components/event-day-header.stories.tsx +124 -0
  103. package/src/components/event-day-header.tsx +180 -0
  104. package/src/components/event-day-separator.stories.tsx +70 -0
  105. package/src/components/event-day-separator.tsx +95 -0
  106. package/src/components/event-detail-panel.stories.tsx +191 -0
  107. package/src/components/event-detail-panel.tsx +360 -0
  108. package/src/components/event-hub-hero.stories.tsx +66 -0
  109. package/src/components/event-hub-hero.tsx +110 -0
  110. package/src/components/event-index-row.stories.tsx +116 -0
  111. package/src/components/event-index-row.tsx +181 -0
  112. package/src/components/event-list-row-wide.stories.tsx +204 -0
  113. package/src/components/event-list-row-wide.tsx +386 -0
  114. package/src/components/event-list-row.stories.tsx +159 -0
  115. package/src/components/event-list-row.tsx +335 -0
  116. package/src/components/event-poster-fallback.stories.tsx +52 -0
  117. package/src/components/event-poster-fallback.tsx +31 -0
  118. package/src/components/event-types.ts +137 -0
  119. package/src/components/events-empty-state.stories.tsx +59 -0
  120. package/src/components/events-empty-state.tsx +131 -0
  121. package/src/components/facet-group.stories.tsx +247 -0
  122. package/src/components/facet-group.test.tsx +225 -0
  123. package/src/components/facet-group.tsx +352 -0
  124. package/src/components/featured-rail-item.stories.tsx +86 -0
  125. package/src/components/featured-rail-item.tsx +153 -0
  126. package/src/components/hour-band.stories.tsx +159 -0
  127. package/src/components/hour-band.tsx +126 -0
  128. package/src/components/hour-group.stories.tsx +100 -0
  129. package/src/components/hour-group.tsx +124 -0
  130. package/src/components/poster-stack.stories.tsx +68 -0
  131. package/src/components/poster-stack.tsx +155 -0
  132. package/src/components/poster-tape.stories.tsx +70 -0
  133. package/src/components/poster-tape.tsx +122 -0
  134. package/src/components/poster-wall.stories.tsx +95 -0
  135. package/src/components/poster-wall.tsx +220 -0
  136. package/src/components/quiet-stretch.stories.tsx +62 -0
  137. package/src/components/quiet-stretch.tsx +115 -0
  138. package/src/components/section-header.stories.tsx +70 -0
  139. package/src/components/section-header.tsx +134 -0
  140. package/src/components/segmented-nav.stories.tsx +118 -0
  141. package/src/components/segmented-nav.tsx +113 -0
  142. package/src/components/skeleton.tsx +18 -0
  143. package/src/components/tag.tsx +11 -1
  144. package/src/components/timeline-band.stories.tsx +206 -0
  145. package/src/components/timeline-band.tsx +207 -0
  146. package/src/components/venue-events-block.stories.tsx +216 -0
  147. package/src/components/venue-events-block.tsx +408 -0
  148. package/src/components/weekend-panel.stories.tsx +92 -0
  149. package/src/components/weekend-panel.tsx +322 -0
  150. package/src/globals.css +19 -0
@@ -0,0 +1,143 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ /**
5
+ * VenueEventsBlock — "one venue, a whole fortnight": the venue and its
6
+ * upcoming nights on the left, its menu on the right. Every string arrives
7
+ * pre-formatted from the caller (ratings, prices, counts) — this component
8
+ * does no number or currency formatting, same contract as `EventCard`.
9
+ *
10
+ * Three independent link surfaces share one `linkComponent`: the venue
11
+ * header, each night row, and the "all events" chip. Each renders a link only
12
+ * when its own `href` is given, a plain element otherwise — none of them
13
+ * assume the others are interactive.
14
+ *
15
+ * `venue`/`nights`/`dishes` default to an empty venue / `[]` so a caller using
16
+ * only `venueContent`/`nightsContent`/`menuContent` never has to fabricate
17
+ * placeholder data just to satisfy these props.
18
+ */
19
+ type VenueEventsBlockLinkProps = {
20
+ href: string;
21
+ className?: string;
22
+ children: React.ReactNode;
23
+ };
24
+ type VenueEventsBlockVenue = {
25
+ name: string;
26
+ ratingLabel?: string;
27
+ metaLabel?: string;
28
+ thumbnailUrl?: string;
29
+ isOpenNow?: boolean;
30
+ href?: string;
31
+ };
32
+ type VenueEventsBlockNight = {
33
+ id: string;
34
+ dayLabel: string;
35
+ title: string;
36
+ timeLabel: string;
37
+ href?: string;
38
+ };
39
+ type VenueEventsBlockDish = {
40
+ id: string;
41
+ name: string;
42
+ description?: string;
43
+ priceLabel: string;
44
+ quantityLabel?: string;
45
+ mentionsLabel?: string;
46
+ };
47
+ type VenueEventsBlockProps = {
48
+ /**
49
+ * Plain venue data this block formats itself into the header card. Fine
50
+ * for a caller with nothing richer than name/rating/meta — and kept for
51
+ * the existing stories — but a consuming app that already owns a place
52
+ * card (real photo, description, cuisine/price meta) should reach for
53
+ * `venueContent` instead: rebuilding that card here would fork it, and the
54
+ * fork would drift the moment either copy changes.
55
+ */
56
+ venue?: VenueEventsBlockVenue;
57
+ /**
58
+ * Plain night data this block formats itself into a row. Fine for a
59
+ * caller with nothing richer than day/title/time — and kept for the
60
+ * existing stories — but a consuming app that already owns an event row
61
+ * component (poster, description, tags) should reach for `nightsContent`
62
+ * instead, for the same reason.
63
+ */
64
+ nights?: VenueEventsBlockNight[];
65
+ allEventsLabel: string;
66
+ allEventsHref?: string;
67
+ menuHeader?: React.ReactNode;
68
+ /**
69
+ * Plain dish data this block formats itself into a row. Fine for a caller
70
+ * with nothing richer than name/price/description — and kept for the
71
+ * existing stories — but a consuming app that already owns a menu-row
72
+ * component (signature badges, an evidence/mentions control, category
73
+ * marks, an ingredient-hue fallback) should reach for `menuContent`
74
+ * instead: rebuilding that row's markup here would fork it, and the fork
75
+ * would drift the moment either copy changes.
76
+ */
77
+ dishes?: VenueEventsBlockDish[];
78
+ /**
79
+ * Replaces the internally-rendered venue header entirely when given —
80
+ * `venue.name`/`ratingLabel`/`metaLabel`/`thumbnailUrl`/`isOpenNow` are
81
+ * then ignored (`venue.href` still wraps this slot in a link when given —
82
+ * a caller whose card is already a link should omit `venue.href` and let
83
+ * its own card own the anchor). Exists because this block should own the
84
+ * LAYOUT and nothing about the shape of a venue card: the consuming app
85
+ * has its own canonical place card (used on its place list, its search
86
+ * results, its map sheet), and the DS must not maintain a second
87
+ * implementation of it.
88
+ */
89
+ venueContent?: React.ReactNode;
90
+ /**
91
+ * Replaces the internally-rendered night rows entirely when given —
92
+ * `nights` is then ignored. Exists because this block should own the
93
+ * LAYOUT and nothing about the shape of a night row: the consuming app
94
+ * has its own canonical event row (used on its timeline, its event
95
+ * detail's "more from this venue"), and the DS must not maintain a
96
+ * second implementation of it. Render that row component here, one per
97
+ * item, with its own key/link/interaction — this slot only provides the
98
+ * column it sits in.
99
+ */
100
+ nightsContent?: React.ReactNode;
101
+ /**
102
+ * Replaces the internally-rendered dish rows entirely when given —
103
+ * `dishes` is then ignored. Exists because this block should own the
104
+ * LAYOUT (venue, nights, menu column) and nothing about the shape of a
105
+ * menu row: the consuming app has its own canonical row (used on its menu
106
+ * page, its place-details sheet, its must-try and pairings lists), and the
107
+ * DS must not maintain a second implementation of it. Render that row
108
+ * component here, one per item, with its own key/link/interaction — this
109
+ * slot only provides the column it sits in.
110
+ */
111
+ menuContent?: React.ReactNode;
112
+ footnote?: string;
113
+ linkComponent?: React.ComponentType<VenueEventsBlockLinkProps>;
114
+ className?: string;
115
+ };
116
+ declare function VenueEventsBlock({ venue, nights, allEventsLabel, allEventsHref, menuHeader, dishes, venueContent, nightsContent, menuContent, footnote, linkComponent: LinkComponent, className, }: VenueEventsBlockProps): react_jsx_runtime.JSX.Element;
117
+ type VenueEventsBlockSkeletonProps = {
118
+ /**
119
+ * Mirrors `VenueEventsBlockProps.venueContent`: a caller rendering its own
120
+ * place card through that slot should pass its own card-shaped skeleton
121
+ * here too, so the loading state matches what actually mounts instead of
122
+ * the block's built-in thumbnail-and-two-lines placeholder.
123
+ */
124
+ venueContent?: React.ReactNode;
125
+ /**
126
+ * Mirrors `VenueEventsBlockProps.nightsContent`: a caller rendering its own
127
+ * row component through that slot should pass its own row-shaped
128
+ * skeletons here too, so the loading state matches what actually mounts
129
+ * instead of three generic placeholder rows.
130
+ */
131
+ nightsContent?: React.ReactNode;
132
+ /**
133
+ * Mirrors `VenueEventsBlockProps.menuContent`: a caller rendering its own
134
+ * row component through that slot should pass its own row-shaped skeleton
135
+ * here too, so the loading state matches what actually mounts instead of
136
+ * three generic placeholder rows.
137
+ */
138
+ menuContent?: React.ReactNode;
139
+ className?: string;
140
+ };
141
+ declare function VenueEventsBlockSkeleton({ venueContent, nightsContent, menuContent, className, }: VenueEventsBlockSkeletonProps): react_jsx_runtime.JSX.Element;
142
+
143
+ export { VenueEventsBlock, type VenueEventsBlockDish, type VenueEventsBlockLinkProps, type VenueEventsBlockNight, type VenueEventsBlockProps, VenueEventsBlockSkeleton, type VenueEventsBlockSkeletonProps, type VenueEventsBlockVenue };
@@ -0,0 +1,222 @@
1
+ import { Separator } from '../chunk-WSRWFA6K.js';
2
+ import { Card } from '../chunk-UXWAOISG.js';
3
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
4
+ import '../chunk-7BWTYWM3.js';
5
+ import { cn } from '../chunk-FEK5XGZW.js';
6
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
7
+ import * as React from 'react';
8
+ import { Star, Quote } from 'lucide-react';
9
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
+
11
+ function DefaultLink(_a) {
12
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
13
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
14
+ }
15
+ function VenueEventsBlock({
16
+ venue = { name: "" },
17
+ nights = [],
18
+ allEventsLabel,
19
+ allEventsHref,
20
+ menuHeader,
21
+ dishes = [],
22
+ venueContent,
23
+ nightsContent,
24
+ menuContent,
25
+ footnote,
26
+ linkComponent: LinkComponent = DefaultLink,
27
+ className
28
+ }) {
29
+ const [thumbError, setThumbError] = React.useState(false);
30
+ const showThumb = !!venue.thumbnailUrl && !thumbError;
31
+ const venueHeader = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-xl border border-border bg-card p-3", children: [
32
+ /* @__PURE__ */ jsxs("div", { className: "relative h-16 w-16 shrink-0 overflow-hidden rounded-lg bg-muted", children: [
33
+ showThumb && // eslint-disable-next-line @next/next/no-img-element
34
+ /* @__PURE__ */ jsx(
35
+ "img",
36
+ {
37
+ src: venue.thumbnailUrl,
38
+ alt: "",
39
+ loading: "lazy",
40
+ decoding: "async",
41
+ className: "h-full w-full object-cover",
42
+ onError: () => setThumbError(true)
43
+ }
44
+ ),
45
+ venue.isOpenNow && /* @__PURE__ */ jsx(
46
+ "span",
47
+ {
48
+ className: "absolute right-1 top-1 h-1.5 w-1.5 rounded-full bg-success ring-2 ring-background",
49
+ "aria-hidden": true
50
+ }
51
+ )
52
+ ] }),
53
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
54
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
55
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: venue.name }),
56
+ venue.ratingLabel && /* @__PURE__ */ jsxs("span", { className: "ml-auto flex shrink-0 items-center gap-1 text-xs font-medium text-foreground", children: [
57
+ /* @__PURE__ */ jsx(Star, { className: "h-3 w-3 fill-current text-primary", "aria-hidden": true }),
58
+ venue.ratingLabel
59
+ ] })
60
+ ] }),
61
+ venue.metaLabel && /* @__PURE__ */ jsx("div", { className: "mt-0.5 truncate text-xs text-muted-foreground", children: venue.metaLabel })
62
+ ] })
63
+ ] });
64
+ return (
65
+ // `Card variant="elevatedPanel"` rather than a hand-rolled surface. This
66
+ // root WAS `border border-border bg-card` — a `surface` card reimplemented
67
+ // inside the DS, which is how it and the skeleton below drifted apart from
68
+ // the primitive and from each other.
69
+ //
70
+ // The elevation is the point: in light mode `--card` on `--background` is
71
+ // 1.04:1, so a panel this size was defined by a 1.32:1 hairline alone and
72
+ // read as a drawn rectangle rather than a raised thing.
73
+ //
74
+ // `rounded-2xl` is exactly the 18px this used to hardcode
75
+ // (`--radius` 0.625rem + 8px) and now re-scales with the token like every
76
+ // other surface. `overflow-hidden` clips descendants, never the element's
77
+ // own box-shadow, so the right column keeps its radius and the panel keeps
78
+ // its lift.
79
+ /* @__PURE__ */ jsxs(
80
+ Card,
81
+ {
82
+ variant: "elevatedPanel",
83
+ className: cn("grid grid-cols-1 overflow-hidden rounded-2xl md:grid-cols-2", className),
84
+ children: [
85
+ /* @__PURE__ */ jsxs("div", { className: "p-5", children: [
86
+ venue.href ? /* @__PURE__ */ jsx(
87
+ LinkComponent,
88
+ {
89
+ href: venue.href,
90
+ className: "block rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
91
+ children: venueContent != null ? venueContent : venueHeader
92
+ }
93
+ ) : venueContent != null ? venueContent : venueHeader,
94
+ /* @__PURE__ */ jsx("div", { children: nightsContent != null ? nightsContent : nights.map((night) => {
95
+ const row = /* @__PURE__ */ jsxs(Fragment, { children: [
96
+ /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
97
+ /* @__PURE__ */ jsx("span", { className: "w-11 shrink-0 font-mono text-xs text-muted-foreground", children: night.dayLabel }),
98
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 text-[13.5px] font-semibold leading-[1.35]", children: night.title }),
99
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: night.timeLabel })
100
+ ] });
101
+ const rowClasses = "relative flex items-center gap-[11px] py-[9px]";
102
+ return night.href ? /* @__PURE__ */ jsx(
103
+ LinkComponent,
104
+ {
105
+ href: night.href,
106
+ className: cn(
107
+ rowClasses,
108
+ "cursor-pointer transition-colors duration-150 hover:text-primary motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-md"
109
+ ),
110
+ children: row
111
+ },
112
+ night.id
113
+ ) : /* @__PURE__ */ jsx("div", { className: rowClasses, children: row }, night.id);
114
+ }) }),
115
+ allEventsHref ? /* @__PURE__ */ jsxs(
116
+ LinkComponent,
117
+ {
118
+ href: allEventsHref,
119
+ className: "mt-3 inline-flex h-9 items-center rounded-full border border-border px-3 text-xs font-semibold text-foreground transition-colors duration-150 hover:bg-muted motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
120
+ children: [
121
+ allEventsLabel,
122
+ " \u203A"
123
+ ]
124
+ }
125
+ ) : /* @__PURE__ */ jsxs("div", { className: "mt-3 inline-flex h-9 items-center rounded-full border border-border px-3 text-xs font-semibold text-foreground", children: [
126
+ allEventsLabel,
127
+ " \u203A"
128
+ ] })
129
+ ] }),
130
+ /* @__PURE__ */ jsxs("div", { className: "relative bg-muted/50 px-[18px] pb-5 pt-4", children: [
131
+ /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0 md:hidden" }),
132
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "absolute inset-y-0 left-0 hidden md:block" }),
133
+ menuHeader,
134
+ menuContent != null ? menuContent : dishes.map((dish) => /* @__PURE__ */ jsxs(
135
+ "div",
136
+ {
137
+ className: "relative flex items-start gap-3 py-3 first:[&>[data-slot=separator]]:hidden",
138
+ children: [
139
+ /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
140
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
141
+ /* @__PURE__ */ jsx("div", { className: "text-[15px] font-medium leading-[1.3]", children: dish.name }),
142
+ dish.description && /* @__PURE__ */ jsx("div", { className: "mt-[3px] line-clamp-1 text-xs text-muted-foreground", children: dish.description }),
143
+ /* @__PURE__ */ jsxs("div", { className: "mt-1.5 flex items-center gap-2 text-[11px] tabular-nums text-muted-foreground", children: [
144
+ dish.quantityLabel && /* @__PURE__ */ jsx("span", { children: dish.quantityLabel }),
145
+ dish.mentionsLabel && /* @__PURE__ */ jsxs("span", { className: "ml-auto inline-flex items-center gap-1 text-[11.5px] text-muted-foreground", children: [
146
+ /* @__PURE__ */ jsx(Quote, { className: "h-3 w-3", "aria-hidden": true }),
147
+ dish.mentionsLabel
148
+ ] })
149
+ ] })
150
+ ] }),
151
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-bold text-primary", children: dish.priceLabel })
152
+ ]
153
+ },
154
+ dish.id
155
+ )),
156
+ footnote && /* @__PURE__ */ jsx("div", { className: "mt-3 text-xs text-muted-foreground", children: footnote })
157
+ ] })
158
+ ]
159
+ }
160
+ )
161
+ );
162
+ }
163
+ function VenueEventsBlockSkeleton({
164
+ venueContent,
165
+ nightsContent,
166
+ menuContent,
167
+ className
168
+ }) {
169
+ return (
170
+ // Identical root to the component above, deliberately — the two drifted
171
+ // once already by both hand-rolling it, so they now share the variant.
172
+ /* @__PURE__ */ jsxs(
173
+ Card,
174
+ {
175
+ "aria-hidden": "true",
176
+ variant: "elevatedPanel",
177
+ className: cn("grid grid-cols-1 overflow-hidden rounded-2xl md:grid-cols-2", className),
178
+ children: [
179
+ /* @__PURE__ */ jsxs("div", { className: "p-5", children: [
180
+ venueContent != null ? venueContent : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-xl border border-border bg-card p-3", children: [
181
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-16 w-16 shrink-0 rounded-lg" }),
182
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
183
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-2/5" }),
184
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-1/3" })
185
+ ] })
186
+ ] }),
187
+ /* @__PURE__ */ jsx("div", { children: nightsContent != null ? nightsContent : Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-[11px] py-[9px]", children: [
188
+ /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
189
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-11 shrink-0" }),
190
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[13.5px] flex-1" }),
191
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-10 shrink-0" })
192
+ ] }, index)) }),
193
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-3 h-9 w-40 rounded-full" })
194
+ ] }),
195
+ /* @__PURE__ */ jsxs("div", { className: "relative bg-muted/50 px-[18px] pb-5 pt-4", children: [
196
+ /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0 md:hidden" }),
197
+ /* @__PURE__ */ jsx(Separator, { orientation: "vertical", className: "absolute inset-y-0 left-0 hidden md:block" }),
198
+ /* @__PURE__ */ jsx(Skeleton, { className: "mb-2 h-4 w-32" }),
199
+ menuContent != null ? menuContent : Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsxs(
200
+ "div",
201
+ {
202
+ className: "relative flex items-start gap-3 py-3 first:[&>[data-slot=separator]]:hidden",
203
+ children: [
204
+ /* @__PURE__ */ jsx(Separator, { className: "absolute inset-x-0 top-0" }),
205
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
206
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[15px] w-3/5" }),
207
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-[3px] h-3 w-4/5" }),
208
+ /* @__PURE__ */ jsx(Skeleton, { className: "mt-1.5 h-3 w-1/4" })
209
+ ] }),
210
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-12 shrink-0" })
211
+ ]
212
+ },
213
+ index
214
+ ))
215
+ ] })
216
+ ]
217
+ }
218
+ )
219
+ );
220
+ }
221
+
222
+ export { VenueEventsBlock, VenueEventsBlockSkeleton };
@@ -0,0 +1,44 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ type WeekendPanelLinkProps = {
5
+ href: string;
6
+ className?: string;
7
+ children: React.ReactNode;
8
+ };
9
+ /** Accessible names for the pause/play toggle, injected so the DS owns no copy. */
10
+ type WeekendPanelLabels = {
11
+ /** Name announced while the carousel is running (the button pauses it). */
12
+ pause: string;
13
+ /** Name announced while the carousel is paused (the button resumes it). */
14
+ play: string;
15
+ };
16
+ type WeekendPanelProps = {
17
+ dayLabel: string;
18
+ /** Caller-composed, e.g. "23 events". */
19
+ countLabel: string;
20
+ actionLabel: string;
21
+ actionHref?: string;
22
+ linkComponent?: React.ComponentType<WeekendPanelLinkProps>;
23
+ onActionClick?: () => void;
24
+ isHighlighted?: boolean;
25
+ /** Loop duration for the slide/dots animation. Default 7 — sibling panels typically stagger at 8.5 and 10. */
26
+ durationSeconds?: number;
27
+ /**
28
+ * Accessible names for the pause/play toggle. Required — the control is
29
+ * only omitted under `prefers-reduced-motion`, where the labels go unused.
30
+ */
31
+ labels: WeekendPanelLabels;
32
+ /** The event cards. The animation is built around exactly four (two pages of two). */
33
+ children?: React.ReactNode;
34
+ className?: string;
35
+ };
36
+ declare function WeekendPanel({ dayLabel, countLabel, actionLabel, actionHref, linkComponent: LinkComponent, onActionClick, isHighlighted, durationSeconds, labels, children, className, }: WeekendPanelProps): react_jsx_runtime.JSX.Element;
37
+ type WeekendPanelSkeletonProps = {
38
+ isHighlighted?: boolean;
39
+ className?: string;
40
+ };
41
+ /** The panel at rest: the two visible cards of the two-up carousel, no slide. */
42
+ declare function WeekendPanelSkeleton({ isHighlighted, className }: WeekendPanelSkeletonProps): react_jsx_runtime.JSX.Element;
43
+
44
+ export { WeekendPanel, type WeekendPanelLabels, type WeekendPanelLinkProps, type WeekendPanelProps, WeekendPanelSkeleton, type WeekendPanelSkeletonProps };
@@ -0,0 +1,231 @@
1
+ import { MotionPlayPause } from '../chunk-TT3WFS6S.js';
2
+ import '../chunk-2WDJR6K7.js';
3
+ import { Skeleton } from '../chunk-QXHMPJ35.js';
4
+ import { cn } from '../chunk-FEK5XGZW.js';
5
+ import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
6
+ import * as React from 'react';
7
+ import { ChevronRight } from 'lucide-react';
8
+ import { jsxs, jsx } from 'react/jsx-runtime';
9
+
10
+ var WEEKEND_PANEL_TRACK_GAP_PX = 14;
11
+ var WEEKEND_PANEL_DEFAULT_DURATION_SECONDS = 7;
12
+ var WEEKEND_PANEL_SLIDE_KEYFRAMES_ID = "ds-weekend-panel-slide";
13
+ var WEEKEND_PANEL_DOT_A_KEYFRAMES_ID = "ds-weekend-panel-dot-a";
14
+ var WEEKEND_PANEL_DOT_B_KEYFRAMES_ID = "ds-weekend-panel-dot-b";
15
+ function usePrefersReducedMotion() {
16
+ const [reduced, setReduced] = React.useState(false);
17
+ React.useEffect(() => {
18
+ if (typeof window === "undefined" || !window.matchMedia) return;
19
+ const query = window.matchMedia("(prefers-reduced-motion: reduce)");
20
+ setReduced(query.matches);
21
+ const onChange = () => setReduced(query.matches);
22
+ query.addEventListener("change", onChange);
23
+ return () => query.removeEventListener("change", onChange);
24
+ }, []);
25
+ return reduced;
26
+ }
27
+ function useInViewport() {
28
+ const ref = React.useRef(null);
29
+ const [inView, setInView] = React.useState(true);
30
+ React.useEffect(() => {
31
+ const node = ref.current;
32
+ if (!node || typeof IntersectionObserver === "undefined") return;
33
+ const observer = new IntersectionObserver(([entry]) => setInView(entry.isIntersecting), {
34
+ threshold: 0
35
+ });
36
+ observer.observe(node);
37
+ return () => observer.disconnect();
38
+ }, []);
39
+ return [ref, inView];
40
+ }
41
+ function DefaultLink(_a) {
42
+ var _b = _a, { href } = _b, props = __objRest(_b, ["href"]);
43
+ return /* @__PURE__ */ jsx("a", __spreadValues({ href }, props));
44
+ }
45
+ function WeekendPanel({
46
+ dayLabel,
47
+ countLabel,
48
+ actionLabel,
49
+ actionHref,
50
+ linkComponent: LinkComponent = DefaultLink,
51
+ onActionClick,
52
+ isHighlighted,
53
+ durationSeconds = WEEKEND_PANEL_DEFAULT_DURATION_SECONDS,
54
+ labels,
55
+ children,
56
+ className
57
+ }) {
58
+ const prefersReducedMotion = usePrefersReducedMotion();
59
+ const [panelRef, inView] = useInViewport();
60
+ const [isHovered, setIsHovered] = React.useState(false);
61
+ const [isFocused, setIsFocused] = React.useState(false);
62
+ const [isManuallyPaused, setIsManuallyPaused] = React.useState(false);
63
+ const [playPauseReplayToken, setPlayPauseReplayToken] = React.useState(0);
64
+ const isPaused = isManuallyPaused || isHovered || isFocused || !inView;
65
+ const action = /* @__PURE__ */ jsxs("span", { className: "ml-auto flex items-center gap-0.5 text-[12.5px] text-muted-foreground", children: [
66
+ actionLabel,
67
+ /* @__PURE__ */ jsx(ChevronRight, { className: "h-3.5 w-3.5", "aria-hidden": true })
68
+ ] });
69
+ return /* @__PURE__ */ jsxs(
70
+ "div",
71
+ {
72
+ ref: panelRef,
73
+ onMouseEnter: () => setIsHovered(true),
74
+ onMouseLeave: () => setIsHovered(false),
75
+ onFocus: () => setIsFocused(true),
76
+ onBlur: () => setIsFocused(false),
77
+ className: cn(
78
+ "relative overflow-hidden rounded-2xl border bg-card py-4",
79
+ isHighlighted ? "border-primary/40" : "border-border",
80
+ className
81
+ ),
82
+ children: [
83
+ /* @__PURE__ */ jsx("style", { children: `
84
+ @keyframes ${WEEKEND_PANEL_SLIDE_KEYFRAMES_ID} {
85
+ 0%, 45% { transform: translateX(0); }
86
+ 50%, 95% { transform: translateX(calc(-100% - ${WEEKEND_PANEL_TRACK_GAP_PX}px)); }
87
+ 100% { transform: translateX(0); }
88
+ }
89
+ @keyframes ${WEEKEND_PANEL_DOT_A_KEYFRAMES_ID} {
90
+ 0% { background: var(--primary); width: 18px; }
91
+ 45% { background: var(--primary); width: 18px; }
92
+ 50% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
93
+ 100% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
94
+ }
95
+ @keyframes ${WEEKEND_PANEL_DOT_B_KEYFRAMES_ID} {
96
+ 0% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
97
+ 50% { background: color-mix(in oklch, var(--muted-foreground) 35%, transparent); width: 6px; }
98
+ 95% { background: var(--primary); width: 18px; }
99
+ 100% { background: var(--primary); width: 18px; }
100
+ }
101
+ ` }),
102
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-[9px] px-4 pb-3", children: [
103
+ /* @__PURE__ */ jsx(
104
+ "span",
105
+ {
106
+ className: cn(
107
+ "text-[15.5px] font-extrabold tracking-[-0.02em] sm:text-[17px]",
108
+ isHighlighted && "text-primary"
109
+ ),
110
+ children: dayLabel
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsx(
114
+ "span",
115
+ {
116
+ className: cn(
117
+ "font-mono text-xs",
118
+ isHighlighted ? "text-primary" : "text-muted-foreground"
119
+ ),
120
+ children: countLabel
121
+ }
122
+ ),
123
+ actionHref ? /* @__PURE__ */ jsx(LinkComponent, { href: actionHref, className: "ml-auto", children: action }) : onActionClick ? /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ onClick: onActionClick,
128
+ className: "ml-auto cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-md",
129
+ children: action
130
+ }
131
+ ) : action
132
+ ] }),
133
+ prefersReducedMotion ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3.5 px-4", children }) : /* @__PURE__ */ jsx("div", { className: "overflow-hidden px-4", children: /* @__PURE__ */ jsx(
134
+ "div",
135
+ {
136
+ className: "flex gap-3.5",
137
+ style: {
138
+ animationName: WEEKEND_PANEL_SLIDE_KEYFRAMES_ID,
139
+ animationDuration: `${durationSeconds}s`,
140
+ animationTimingFunction: "ease-in-out",
141
+ animationIterationCount: "infinite",
142
+ animationPlayState: isPaused ? "paused" : "running"
143
+ },
144
+ children: React.Children.map(children, (child) => /* @__PURE__ */ jsx("div", { className: "w-[calc((100%-14px)/2)] flex-none", children: child }))
145
+ }
146
+ ) }),
147
+ !prefersReducedMotion && /* @__PURE__ */ jsxs("div", { className: "mt-3.5 grid grid-cols-[1fr_auto_1fr] items-center px-4", children: [
148
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true }),
149
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1.5", "aria-hidden": true, children: [
150
+ /* @__PURE__ */ jsx(
151
+ "span",
152
+ {
153
+ className: "block h-1.5 w-[18px] rounded-full bg-primary",
154
+ style: {
155
+ animationName: WEEKEND_PANEL_DOT_A_KEYFRAMES_ID,
156
+ animationDuration: `${durationSeconds}s`,
157
+ animationTimingFunction: "ease-in-out",
158
+ animationIterationCount: "infinite",
159
+ animationPlayState: isPaused ? "paused" : "running"
160
+ }
161
+ }
162
+ ),
163
+ /* @__PURE__ */ jsx(
164
+ "span",
165
+ {
166
+ className: "block h-1.5 w-1.5 rounded-full bg-muted-foreground/35",
167
+ style: {
168
+ animationName: WEEKEND_PANEL_DOT_B_KEYFRAMES_ID,
169
+ animationDuration: `${durationSeconds}s`,
170
+ animationTimingFunction: "ease-in-out",
171
+ animationIterationCount: "infinite",
172
+ animationPlayState: isPaused ? "paused" : "running"
173
+ }
174
+ }
175
+ )
176
+ ] }),
177
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
178
+ "button",
179
+ {
180
+ type: "button",
181
+ onClick: () => setIsManuallyPaused((current) => !current),
182
+ onPointerDown: () => setPlayPauseReplayToken((n) => n + 1),
183
+ "aria-label": isManuallyPaused ? labels.play : labels.pause,
184
+ "aria-pressed": isManuallyPaused,
185
+ className: "flex h-11 w-11 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors duration-150 hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 motion-reduce:transition-none",
186
+ children: /* @__PURE__ */ jsx(
187
+ MotionPlayPause,
188
+ {
189
+ active: !isManuallyPaused,
190
+ replayToken: playPauseReplayToken,
191
+ className: "size-4"
192
+ }
193
+ )
194
+ }
195
+ ) })
196
+ ] })
197
+ ]
198
+ }
199
+ );
200
+ }
201
+ function WeekendPanelSkeleton({ isHighlighted, className }) {
202
+ return /* @__PURE__ */ jsxs(
203
+ "div",
204
+ {
205
+ "aria-hidden": "true",
206
+ className: cn(
207
+ "relative overflow-hidden rounded-2xl border bg-card py-4",
208
+ isHighlighted ? "border-primary/40" : "border-border",
209
+ className
210
+ ),
211
+ children: [
212
+ /* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-[9px] px-4 pb-3", children: [
213
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[15.5px] w-16 sm:h-[17px]" }),
214
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-14" }),
215
+ /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-3 w-14" })
216
+ ] }),
217
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3.5 px-4", children: Array.from({ length: 2 }, (_, index) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
218
+ /* @__PURE__ */ jsx(Skeleton, { className: "aspect-[4/3] w-full" }),
219
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-[13.5px] w-4/5" }),
220
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3/5" })
221
+ ] }, index)) }),
222
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1.5 pt-3.5", children: [
223
+ /* @__PURE__ */ jsx("span", { className: "block h-1.5 w-[18px] rounded-full bg-muted" }),
224
+ /* @__PURE__ */ jsx("span", { className: "block h-1.5 w-1.5 rounded-full bg-muted" })
225
+ ] })
226
+ ]
227
+ }
228
+ );
229
+ }
230
+
231
+ export { WeekendPanel, WeekendPanelSkeleton };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.8.4",
3
+ "version": "0.8.7",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {