@bearmenu/ui 0.8.52 → 0.8.54

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 (44) hide show
  1. package/dist/{chunk-4WW3HPIW.js → chunk-6Z6MBILC.js} +8 -4
  2. package/dist/chunk-GIJGVFGC.js +177 -0
  3. package/dist/chunk-RKNPPYRU.js +86 -0
  4. package/dist/{chunk-2ADCZXZJ.js → chunk-RXXD3UYP.js} +3 -1
  5. package/dist/components/alert.d.ts +2 -2
  6. package/dist/components/badge.d.ts +1 -1
  7. package/dist/components/button.d.ts +2 -2
  8. package/dist/components/combobox.js +1 -1
  9. package/dist/components/event-detail-panel.d.ts +17 -6
  10. package/dist/components/event-detail-panel.js +58 -8
  11. package/dist/components/facet-group.d.ts +9 -2
  12. package/dist/components/facet-group.js +108 -10
  13. package/dist/components/filter-option-chip.d.ts +15 -0
  14. package/dist/components/filter-option-chip.js +3 -0
  15. package/dist/components/filter-option-group.d.ts +37 -0
  16. package/dist/components/filter-option-group.js +10 -0
  17. package/dist/components/filter-panel-footer.d.ts +15 -0
  18. package/dist/components/filter-panel-footer.js +63 -0
  19. package/dist/components/menu-item.d.ts +24 -3
  20. package/dist/components/menu-item.js +1 -1
  21. package/dist/components/multi-select-combobox.js +1 -1
  22. package/dist/components/place-card.js +1 -1
  23. package/dist/components/popover.js +1 -1
  24. package/dist/components/searchable-select.js +1 -1
  25. package/dist/components/spinner.d.ts +2 -2
  26. package/dist/components/tag.d.ts +2 -2
  27. package/dist/components/text.d.ts +1 -1
  28. package/dist/components/toggle.d.ts +1 -1
  29. package/dist/components/top-ranking-entry.js +1 -1
  30. package/package.json +11 -8
  31. package/src/components/event-detail-panel.stories.tsx +16 -0
  32. package/src/components/event-detail-panel.test.tsx +36 -0
  33. package/src/components/event-detail-panel.tsx +86 -14
  34. package/src/components/facet-group.tsx +153 -54
  35. package/src/components/filter-option-chip.test.tsx +50 -0
  36. package/src/components/filter-option-chip.tsx +81 -0
  37. package/src/components/filter-option-group.stories.tsx +250 -0
  38. package/src/components/filter-option-group.test.tsx +100 -0
  39. package/src/components/filter-option-group.tsx +231 -0
  40. package/src/components/filter-panel-footer.tsx +63 -0
  41. package/src/components/menu-item.stories.tsx +82 -0
  42. package/src/components/menu-item.test.tsx +55 -1
  43. package/src/components/menu-item.tsx +31 -4
  44. package/src/components/popover.tsx +3 -1
@@ -1,9 +1,17 @@
1
+ import { FilterOptionGroup } from '../chunk-GIJGVFGC.js';
2
+ import '../chunk-RKNPPYRU.js';
3
+ import '../chunk-NWZGSLPU.js';
1
4
  import { Skeleton } from '../chunk-XBBEX4SF.js';
5
+ import { Button } from '../chunk-GHKTVHQG.js';
6
+ import '../chunk-RIH2IS7X.js';
7
+ import '../chunk-5YNIQ2BL.js';
8
+ import '../chunk-ZLMRVBD7.js';
9
+ import '../chunk-RYPPN2GZ.js';
2
10
  import { cn } from '../chunk-FEK5XGZW.js';
3
11
  import { __objRest, __spreadProps, __spreadValues } from '../chunk-MMUBH76A.js';
4
12
  import { useId, useState } from 'react';
5
13
  import { ChevronDown, Check } from 'lucide-react';
6
- import { jsxs, jsx } from 'react/jsx-runtime';
14
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
15
 
8
16
  var ROW_SIZE = {
9
17
  comfortable: "-mx-2 min-h-11 rounded-lg px-2",
@@ -21,6 +29,10 @@ function FacetGroup(_a) {
21
29
  selection = "multiple",
22
30
  name,
23
31
  size = "comfortable",
32
+ layout = "rows",
33
+ visibleLimit,
34
+ showAllLabel,
35
+ showLessLabel,
24
36
  collapsible = false,
25
37
  defaultOpen = true,
26
38
  onToggle,
@@ -35,6 +47,10 @@ function FacetGroup(_a) {
35
47
  "selection",
36
48
  "name",
37
49
  "size",
50
+ "layout",
51
+ "visibleLimit",
52
+ "showAllLabel",
53
+ "showLessLabel",
38
54
  "collapsible",
39
55
  "defaultOpen",
40
56
  "onToggle",
@@ -50,6 +66,48 @@ function FacetGroup(_a) {
50
66
  const setOpen = setIsOpen;
51
67
  const fallbackName = useId();
52
68
  const groupName = name != null ? name : fallbackName;
69
+ if (layout === "chips") {
70
+ const value = options.filter((option) => option.checked).map((option) => option.id);
71
+ return /* @__PURE__ */ jsx(
72
+ FilterOptionGroup,
73
+ {
74
+ title,
75
+ options: options.map((option) => __spreadProps(__spreadValues({}, option), { value: option.id })),
76
+ value,
77
+ selection,
78
+ name: groupName,
79
+ defaultOpen,
80
+ collapsible,
81
+ divider: false,
82
+ visibleLimit,
83
+ showAllLabel,
84
+ showLessLabel,
85
+ disabled: rest.disabled,
86
+ className,
87
+ onValueChange: (next) => {
88
+ if (selection === "single") {
89
+ if (next[0] != null) onToggle == null ? void 0 : onToggle(String(next[0]), true);
90
+ return;
91
+ }
92
+ for (const option of options) {
93
+ const checked = next.includes(option.id);
94
+ if (checked !== !!option.checked) onToggle == null ? void 0 : onToggle(option.id, checked);
95
+ }
96
+ },
97
+ footer: moreLabel && /* @__PURE__ */ jsx(
98
+ Button,
99
+ {
100
+ type: "button",
101
+ variant: "link",
102
+ className: "min-h-11 px-0",
103
+ onClick: onMore,
104
+ "aria-expanded": moreExpanded,
105
+ children: moreLabel
106
+ }
107
+ )
108
+ }
109
+ );
110
+ }
53
111
  return (
54
112
  // NO SEPARATOR OF ITS OWN. This drew `<Separator className="absolute
55
113
  // inset-x-0 top-0" />`, which looked like a rule above each group and was
@@ -98,7 +156,14 @@ function FacetGroup(_a) {
98
156
  open ? "mb-[11px]" : "mb-0"
99
157
  ),
100
158
  children: [
101
- Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3.5 shrink-0", strokeWidth: 2.25 }),
159
+ Icon && /* @__PURE__ */ jsx(
160
+ Icon,
161
+ {
162
+ "aria-hidden": true,
163
+ className: "size-3.5 shrink-0",
164
+ strokeWidth: 2.25
165
+ }
166
+ ),
102
167
  title,
103
168
  /* @__PURE__ */ jsx(
104
169
  ChevronDown,
@@ -114,7 +179,14 @@ function FacetGroup(_a) {
114
179
  }
115
180
  )
116
181
  ) : /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
117
- Icon && /* @__PURE__ */ jsx(Icon, { "aria-hidden": true, className: "size-3.5 shrink-0", strokeWidth: 2.25 }),
182
+ Icon && /* @__PURE__ */ jsx(
183
+ Icon,
184
+ {
185
+ "aria-hidden": true,
186
+ className: "size-3.5 shrink-0",
187
+ strokeWidth: 2.25
188
+ }
189
+ ),
118
190
  title
119
191
  ] })
120
192
  }
@@ -164,7 +236,13 @@ function FacetGroup(_a) {
164
236
  "peer-focus-visible:ring-2 peer-focus-visible:ring-ring peer-focus-visible:ring-offset-2",
165
237
  checked ? "border-primary bg-primary" : "border-border bg-background"
166
238
  ),
167
- children: checked && (isSingle ? /* @__PURE__ */ jsx("span", { className: "size-[7px] rounded-full bg-primary-foreground" }) : /* @__PURE__ */ jsx(Check, { className: "h-[11px] w-[11px] text-primary-foreground", strokeWidth: 3 }))
239
+ children: checked && (isSingle ? /* @__PURE__ */ jsx("span", { className: "size-[7px] rounded-full bg-primary-foreground" }) : /* @__PURE__ */ jsx(
240
+ Check,
241
+ {
242
+ className: "h-[11px] w-[11px] text-primary-foreground",
243
+ strokeWidth: 3
244
+ }
245
+ ))
168
246
  }
169
247
  ),
170
248
  /* @__PURE__ */ jsx(
@@ -205,18 +283,38 @@ function FacetGroupSkeleton({
205
283
  optionCount = 4,
206
284
  showMore,
207
285
  size = "comfortable",
208
- className
286
+ className,
287
+ layout = "rows"
209
288
  }) {
289
+ if (layout === "chips")
290
+ return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: cn("py-3", className), children: [
291
+ /* @__PURE__ */ jsx(Skeleton, { className: "mb-4 h-4 w-24" }),
292
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: Array.from({ length: optionCount }, (_, index) => /* @__PURE__ */ jsx(
293
+ Skeleton,
294
+ {
295
+ className: cn("h-11 rounded-2xl", index % 2 ? "w-28" : "w-24")
296
+ },
297
+ index
298
+ )) }),
299
+ showMore && /* @__PURE__ */ jsx(Skeleton, { className: "mt-3 h-4 w-20" })
300
+ ] });
210
301
  return (
211
302
  // Matches the live group: no rule of its own, `py-3`, and the row gap keyed
212
303
  // to the same density — or the panel shifts on load by the difference.
213
304
  /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: cn("py-3", className), children: [
214
305
  /* @__PURE__ */ jsx(Skeleton, { className: "mb-[11px] h-3 w-20" }),
215
- /* @__PURE__ */ jsx("div", { className: cn("flex flex-col", ROW_GAP[size]), children: Array.from({ length: optionCount }).map((_, index) => /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-[9px]", ROW_SIZE[size]), children: [
216
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-[5px]" }),
217
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-24" }),
218
- /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-3 w-5" })
219
- ] }, index)) }),
306
+ /* @__PURE__ */ jsx("div", { className: cn("flex flex-col", ROW_GAP[size]), children: Array.from({ length: optionCount }).map((_, index) => /* @__PURE__ */ jsxs(
307
+ "div",
308
+ {
309
+ className: cn("flex items-center gap-[9px]", ROW_SIZE[size]),
310
+ children: [
311
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-4 rounded-[5px]" }),
312
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-24" }),
313
+ /* @__PURE__ */ jsx(Skeleton, { className: "ml-auto h-3 w-5" })
314
+ ]
315
+ },
316
+ index
317
+ )) }),
220
318
  showMore && /* @__PURE__ */ jsx(Skeleton, { className: "mt-[11px] h-3 w-16" })
221
319
  ] })
222
320
  );
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
3
+
4
+ type FilterOptionChipProps = Omit<ComponentPropsWithoutRef<"input">, "type" | "children" | "size"> & {
5
+ label: string;
6
+ /** Radios in one group must share a name. */
7
+ type?: "checkbox" | "radio";
8
+ /** Omit while counts are unresolved. Zero is a valid, selectable count. */
9
+ count?: number | null;
10
+ icon?: ReactNode;
11
+ };
12
+ /** A wrapping filter option with native selection and keyboard semantics. */
13
+ declare function FilterOptionChip({ label, type, count, icon, className, id, disabled, ...input }: FilterOptionChipProps): react_jsx_runtime.JSX.Element;
14
+
15
+ export { FilterOptionChip, type FilterOptionChipProps };
@@ -0,0 +1,3 @@
1
+ export { FilterOptionChip } from '../chunk-RKNPPYRU.js';
2
+ import '../chunk-FEK5XGZW.js';
3
+ import '../chunk-MMUBH76A.js';
@@ -0,0 +1,37 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type FilterOptionValue = string | number;
5
+ type FilterOptionGroupProps = {
6
+ title: string;
7
+ options: {
8
+ value: FilterOptionValue;
9
+ label: string;
10
+ count?: number | null;
11
+ icon?: ReactNode;
12
+ section?: string;
13
+ }[];
14
+ value: FilterOptionValue[];
15
+ onValueChange: (value: FilterOptionValue[]) => void;
16
+ selection?: "multiple" | "single";
17
+ name?: string;
18
+ defaultOpen?: boolean;
19
+ collapsible?: boolean;
20
+ visibleLimit?: number;
21
+ showAllLabel?: string | ((count: number) => string);
22
+ showLessLabel?: string;
23
+ /** Extra controls or a selection explanation, before the options. */
24
+ children?: ReactNode;
25
+ footer?: ReactNode;
26
+ clearLabel?: string;
27
+ onClear?: () => void;
28
+ /** Omit neutral choices from the active summary without changing radio selection. */
29
+ summaryValues?: FilterOptionValue[];
30
+ className?: string;
31
+ divider?: boolean;
32
+ disabled?: boolean;
33
+ };
34
+ /** Presentation only: callers own filter rules, URL state, counts and translations. */
35
+ declare function FilterOptionGroup({ title, options, value, onValueChange, selection, name, defaultOpen, collapsible, visibleLimit, showAllLabel, showLessLabel, children, footer, clearLabel, onClear, summaryValues, className, divider, disabled, }: FilterOptionGroupProps): react_jsx_runtime.JSX.Element;
36
+
37
+ export { FilterOptionGroup, type FilterOptionGroupProps, type FilterOptionValue };
@@ -0,0 +1,10 @@
1
+ export { FilterOptionGroup } from '../chunk-GIJGVFGC.js';
2
+ import '../chunk-RKNPPYRU.js';
3
+ import '../chunk-NWZGSLPU.js';
4
+ import '../chunk-GHKTVHQG.js';
5
+ import '../chunk-RIH2IS7X.js';
6
+ import '../chunk-5YNIQ2BL.js';
7
+ import '../chunk-ZLMRVBD7.js';
8
+ import '../chunk-RYPPN2GZ.js';
9
+ import '../chunk-FEK5XGZW.js';
10
+ import '../chunk-MMUBH76A.js';
@@ -0,0 +1,15 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type FilterPanelFooterProps = {
4
+ resetLabel: string;
5
+ resultsLabel: string;
6
+ onReset: () => void;
7
+ onResults: () => void;
8
+ canReset: boolean;
9
+ pending?: boolean;
10
+ resetHint?: string;
11
+ };
12
+ /** Persistent actions belong outside the panel's scroll container. */
13
+ declare function FilterPanelFooter({ resetLabel, resultsLabel, onReset, onResults, canReset, pending, resetHint, }: FilterPanelFooterProps): react_jsx_runtime.JSX.Element;
14
+
15
+ export { FilterPanelFooter, type FilterPanelFooterProps };
@@ -0,0 +1,63 @@
1
+ import { Button } from '../chunk-GHKTVHQG.js';
2
+ import '../chunk-RIH2IS7X.js';
3
+ import '../chunk-5YNIQ2BL.js';
4
+ import { Text } from '../chunk-ZLMRVBD7.js';
5
+ import '../chunk-FEK5XGZW.js';
6
+ import '../chunk-MMUBH76A.js';
7
+ import { jsxs, jsx } from 'react/jsx-runtime';
8
+
9
+ function FilterPanelFooter({
10
+ resetLabel,
11
+ resultsLabel,
12
+ onReset,
13
+ onResults,
14
+ canReset,
15
+ pending,
16
+ resetHint
17
+ }) {
18
+ return /* @__PURE__ */ jsxs(
19
+ "footer",
20
+ {
21
+ className: "shrink-0 border-t border-border bg-card px-4 pt-3 pb-[max(1rem,env(safe-area-inset-bottom))]",
22
+ "data-slot": "filter-panel-footer",
23
+ children: [
24
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
25
+ /* @__PURE__ */ jsx(
26
+ Button,
27
+ {
28
+ type: "button",
29
+ variant: "outline",
30
+ className: "min-h-11 h-auto flex-1 whitespace-normal py-2",
31
+ disabled: !canReset,
32
+ onClick: onReset,
33
+ children: resetLabel
34
+ }
35
+ ),
36
+ /* @__PURE__ */ jsx(
37
+ Button,
38
+ {
39
+ type: "button",
40
+ className: "min-h-11 h-auto flex-[2] whitespace-normal py-2",
41
+ disabled: pending,
42
+ "aria-busy": pending || void 0,
43
+ onClick: onResults,
44
+ children: resultsLabel
45
+ }
46
+ )
47
+ ] }),
48
+ resetHint && /* @__PURE__ */ jsx(
49
+ Text,
50
+ {
51
+ as: "p",
52
+ variant: "caption",
53
+ color: "muted",
54
+ className: "mt-2 leading-relaxed",
55
+ children: resetHint
56
+ }
57
+ )
58
+ ]
59
+ }
60
+ );
61
+ }
62
+
63
+ export { FilterPanelFooter };
@@ -64,6 +64,18 @@ type MenuItemLabels = {
64
64
  */
65
65
  externalRating?: (score: string) => string;
66
66
  };
67
+ /**
68
+ * What the count beside the row counts.
69
+ *
70
+ * - `"mentions"`: every review that named the item, complaints included. Drawn
71
+ * with the quote glyph.
72
+ * - `"praise"`: only the reviews that praised it. Drawn with a thumbs-up.
73
+ *
74
+ * The caller decides, because only the caller knows which number it passed in
75
+ * `item.mentions`. A thumbs-up must never sit on a count that includes
76
+ * complaints, so `"mentions"` is the default.
77
+ */
78
+ type MenuItemCountKind = "praise" | "mentions";
67
79
  type MenuItemProps = {
68
80
  item: MenuItemPreview;
69
81
  labels: MenuItemLabels;
@@ -85,6 +97,12 @@ type MenuItemProps = {
85
97
  onSignatureEvidence?: () => void;
86
98
  /** Opens the reviews behind the mention count. */
87
99
  onMentions?: () => void;
100
+ /**
101
+ * Whether the count is praise or every mention. Sets the glyph before the
102
+ * numeral: a thumbs-up for `"praise"`, the quote glyph for `"mentions"`.
103
+ * Defaults to `"mentions"`. Keep `labels.mentions` in step with it.
104
+ */
105
+ countKind?: MenuItemCountKind;
88
106
  /**
89
107
  * An outside aggregate score for the item (a wine's Vivino rating, a beer's
90
108
  * Untappd one), shown as a star and the score beside the mention count.
@@ -128,17 +146,20 @@ type MenuItemProps = {
128
146
  className?: string;
129
147
  };
130
148
  /**
131
- * The mention count: a filled quote glyph and the numeral, pinned right.
149
+ * The mention count: a glyph and the numeral, pinned right. The glyph is the
150
+ * quote glyph for every mention, or a thumbs-up when the count is praise only
151
+ * (see `MenuItemCountKind`).
132
152
  *
133
153
  * Deliberately NOT a badge — a chip would add a filled shape to a row that has
134
154
  * none, and would read as a status label the venue was given rather than a
135
155
  * count drawn from the same reviews.
136
156
  */
137
- declare function MentionSignal({ label, count, onMentions, formatCount, }: {
157
+ declare function MentionSignal({ label, count, onMentions, formatCount, countKind, }: {
138
158
  label: string;
139
159
  count: number;
140
160
  onMentions?: () => void;
141
161
  formatCount?: (value: number) => string;
162
+ countKind?: MenuItemCountKind;
142
163
  }): react_jsx_runtime.JSX.Element;
143
164
  /**
144
165
  * An external aggregate score: a star and the numeral, in the neutral muted
@@ -152,4 +173,4 @@ declare function ExternalRatingSignal({ label, value }: {
152
173
  }): react_jsx_runtime.JSX.Element;
153
174
  declare const MenuItem: React.NamedExoticComponent<MenuItemProps>;
154
175
 
155
- export { ExternalRatingSignal, MentionSignal, MenuItem, type MenuItemLabels, type MenuItemLinkProps, type MenuItemProps };
176
+ export { ExternalRatingSignal, MentionSignal, MenuItem, type MenuItemCountKind, type MenuItemLabels, type MenuItemLinkProps, type MenuItemProps };
@@ -1,4 +1,4 @@
1
- export { ExternalRatingSignal, MentionSignal, MenuItem } from '../chunk-4WW3HPIW.js';
1
+ export { ExternalRatingSignal, MentionSignal, MenuItem } from '../chunk-6Z6MBILC.js';
2
2
  import '../chunk-6PQINSAA.js';
3
3
  import '../chunk-RYPPN2GZ.js';
4
4
  import '../chunk-FEK5XGZW.js';
@@ -1,5 +1,5 @@
1
1
  import { Checkbox } from '../chunk-L3CDXVEP.js';
2
- import { Popover, PopoverTrigger, PopoverContent } from '../chunk-2ADCZXZJ.js';
2
+ import { Popover, PopoverTrigger, PopoverContent } from '../chunk-RXXD3UYP.js';
3
3
  import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from '../chunk-PGQXVJXW.js';
4
4
  import '../chunk-IQIJCMG2.js';
5
5
  import { Button } from '../chunk-GHKTVHQG.js';
@@ -1,6 +1,6 @@
1
+ import { HOVER_IMAGE_MOTION, HOVER_IMAGE_WRAPPER, HOVER_MOTION, HOVER_LIFT_GRID_GROUP } from '../chunk-XCB3F6SF.js';
1
2
  import { NEUTRAL_ICON_FILTER } from '../chunk-LQWGZIYA.js';
2
3
  import { OVERLAY_PILL } from '../chunk-EZG2NELX.js';
3
- import { HOVER_IMAGE_MOTION, HOVER_IMAGE_WRAPPER, HOVER_MOTION, HOVER_LIFT_GRID_GROUP } from '../chunk-XCB3F6SF.js';
4
4
  import { GradeAtom } from '../chunk-YE4ZQUTI.js';
5
5
  import { showsGradeAtom } from '../chunk-I5V446BX.js';
6
6
  import { Separator } from '../chunk-NWZGSLPU.js';
@@ -1,3 +1,3 @@
1
- export { Popover, PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from '../chunk-2ADCZXZJ.js';
1
+ export { Popover, PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from '../chunk-RXXD3UYP.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-MMUBH76A.js';
@@ -1,4 +1,4 @@
1
- import { Popover, PopoverTrigger, PopoverContent } from '../chunk-2ADCZXZJ.js';
1
+ import { Popover, PopoverTrigger, PopoverContent } from '../chunk-RXXD3UYP.js';
2
2
  import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from '../chunk-PGQXVJXW.js';
3
3
  import '../chunk-IQIJCMG2.js';
4
4
  import { Button } from '../chunk-GHKTVHQG.js';
@@ -4,11 +4,11 @@ import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const spinnerVariants: (props?: ({
6
6
  size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
7
- tone?: "primary" | "default" | "inherit" | "destructive" | null | undefined;
7
+ tone?: "primary" | "inherit" | "default" | "destructive" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare const Spinner: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
10
10
  size?: "default" | "xs" | "sm" | "lg" | "xl" | null | undefined;
11
- tone?: "primary" | "default" | "inherit" | "destructive" | null | undefined;
11
+ tone?: "primary" | "inherit" | "default" | "destructive" | null | undefined;
12
12
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
13
13
  /** Visually-hidden label for screen readers. Defaults to "Loading". */
14
14
  label?: string;
@@ -3,11 +3,11 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const tagVariants: (props?: ({
6
- variant?: "primary" | "default" | "success" | "warning" | "destructive" | "outline" | "quiet" | null | undefined;
6
+ variant?: "primary" | "success" | "warning" | "default" | "destructive" | "outline" | "quiet" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare const Tag: React.ForwardRefExoticComponent<React.HTMLAttributes<HTMLSpanElement> & VariantProps<(props?: ({
10
- variant?: "primary" | "default" | "success" | "warning" | "destructive" | "outline" | "quiet" | null | undefined;
10
+ variant?: "primary" | "success" | "warning" | "default" | "destructive" | "outline" | "quiet" | null | undefined;
11
11
  size?: "default" | "sm" | "lg" | null | undefined;
12
12
  } & class_variance_authority_types.ClassProp) | undefined) => string> & {
13
13
  onRemove?: () => void;
@@ -23,7 +23,7 @@ import { VariantProps } from 'class-variance-authority';
23
23
  */
24
24
  declare const textVariants: (props?: ({
25
25
  variant?: "small" | "body" | "display" | "h2" | "h3" | "label" | "caption" | "h1" | "h4" | "body-lg" | "body-sm" | "overline" | null | undefined;
26
- color?: "primary" | "default" | "inherit" | "destructive" | "muted" | "primary-foreground" | null | undefined;
26
+ color?: "primary" | "inherit" | "default" | "destructive" | "muted" | "primary-foreground" | null | undefined;
27
27
  weight?: "bold" | "medium" | "normal" | "semibold" | null | undefined;
28
28
  leading?: "none" | "normal" | "tight" | "snug" | "relaxed" | "loose" | null | undefined;
29
29
  tracking?: "normal" | "tight" | "wide" | "wider" | null | undefined;
@@ -11,7 +11,7 @@ import { VariantProps } from 'class-variance-authority';
11
11
  * `data-[state=on]:` utilities sort after `hover:` and still win while on.
12
12
  */
13
13
  declare const toggleVariants: (props?: ({
14
- variant?: "default" | "tab" | "outline" | "chip" | "segmented" | null | undefined;
14
+ variant?: "tab" | "default" | "outline" | "chip" | "segmented" | null | undefined;
15
15
  size?: "default" | "sm" | "lg" | null | undefined;
16
16
  } & class_variance_authority_types.ClassProp) | undefined) => string;
17
17
  /**
@@ -1,4 +1,4 @@
1
- import { MenuItem } from '../chunk-4WW3HPIW.js';
1
+ import { MenuItem } from '../chunk-6Z6MBILC.js';
2
2
  import '../chunk-6PQINSAA.js';
3
3
  import { Separator } from '../chunk-NWZGSLPU.js';
4
4
  import { Skeleton } from '../chunk-XBBEX4SF.js';
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.8.52",
3
+ "version": "0.8.54",
4
4
  "packageManager": "pnpm@10.28.1",
5
- "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
+ "description": "BearMenu design system \u2014 shared UI tokens, primitives, and components",
6
6
  "license": "UNLICENSED",
7
7
  "repository": {
8
8
  "type": "git",
@@ -204,7 +204,10 @@
204
204
  "./lib/icon-registry": "./src/lib/icon-registry.ts",
205
205
  "./lib/motion": "./src/lib/motion.ts",
206
206
  "./lib/overlay-pill": "./src/lib/overlay-pill.ts",
207
- "./lib/utils": "./src/lib/utils.ts"
207
+ "./lib/utils": "./src/lib/utils.ts",
208
+ "./components/filter-option-chip": "./src/components/filter-option-chip.tsx",
209
+ "./components/filter-option-group": "./src/components/filter-option-group.tsx",
210
+ "./components/filter-panel-footer": "./src/components/filter-panel-footer.tsx"
208
211
  },
209
212
  "publishConfig": {
210
213
  "exports": {
@@ -304,10 +307,10 @@
304
307
  "@radix-ui/react-toggle": "^1.1.10",
305
308
  "@radix-ui/react-toggle-group": "^1.1.11",
306
309
  "@radix-ui/react-tooltip": "^1.2.8",
307
- "@storybook/addon-a11y": "^10.3.5",
308
- "@storybook/addon-docs": "^10.3.5",
309
- "@storybook/react": "^10.3.5",
310
- "@storybook/react-vite": "^10.3.5",
310
+ "@storybook/addon-a11y": "^10.6.5",
311
+ "@storybook/addon-docs": "^10.6.5",
312
+ "@storybook/react": "^10.6.5",
313
+ "@storybook/react-vite": "^10.6.5",
311
314
  "@tailwindcss/postcss": "^4.2.4",
312
315
  "@tailwindcss/vite": "^4.2.4",
313
316
  "@testing-library/jest-dom": "^6.9.1",
@@ -333,7 +336,7 @@
333
336
  "react-markdown": "^10.1.0",
334
337
  "remark-gfm": "^4.0.1",
335
338
  "sonner": "^2.0.7",
336
- "storybook": "^10.3.5",
339
+ "storybook": "^10.6.5",
337
340
  "tailwind-merge": "^3.5.0",
338
341
  "tailwindcss": "^4.2.4",
339
342
  "tsup": "^8.5.0",
@@ -283,6 +283,22 @@ export const Loading: Story = {
283
283
  decorators: SideSheet.decorators,
284
284
  };
285
285
 
286
+ export const EntryAndBrowseActions: Story = {
287
+ args: {
288
+ tags: [{ label: "Party", href: "/events?tags=party" }],
289
+ entryDetails: {
290
+ label: "Entry and tickets",
291
+ description: "Check availability with the organizer.",
292
+ action: (
293
+ <Button asChild fullWidth>
294
+ <a href="https://tickets.example.com">Get tickets</a>
295
+ </Button>
296
+ ),
297
+ },
298
+ },
299
+ decorators: SideSheet.decorators,
300
+ };
301
+
286
302
  export const Error: Story = {
287
303
  render: () => (
288
304
  <EventDetailPanelError
@@ -37,6 +37,42 @@ const event: EventPreview = {
37
37
  const base = { event, labels, formatters, dateLine: "Tuesday · 17:30" };
38
38
 
39
39
  describe("EventDetailPanel", () => {
40
+ it("opens entry information and exposes a separate booking link", () => {
41
+ render(
42
+ <EventDetailPanel
43
+ {...base}
44
+ entryDetails={{
45
+ label: "Entry and tickets",
46
+ description: "Book with the organizer.",
47
+ action: <a href="https://tickets.example.com">Get tickets</a>,
48
+ }}
49
+ />,
50
+ );
51
+ expect(
52
+ screen.queryByRole("link", { name: "Get tickets" }),
53
+ ).not.toBeInTheDocument();
54
+ fireEvent.click(screen.getByRole("button", { name: "Entry and tickets" }));
55
+ expect(screen.getByText("Book with the organizer.")).toBeVisible();
56
+ expect(screen.getByRole("link", { name: "Get tickets" })).toHaveAttribute(
57
+ "href",
58
+ "https://tickets.example.com",
59
+ );
60
+ });
61
+
62
+ it("gives browse tags destinations while keeping descriptive tags out of the tab order", () => {
63
+ render(
64
+ <EventDetailPanel
65
+ {...base}
66
+ tags={["Descriptive", { label: "Party", href: "/events?tags=party" }]}
67
+ />,
68
+ );
69
+ expect(screen.getByRole("link", { name: "Party" })).toHaveAttribute(
70
+ "href",
71
+ "/events?tags=party",
72
+ );
73
+ expect(screen.getByText("Descriptive").tagName).toBe("SPAN");
74
+ });
75
+
40
76
  it("shows the status chip for live, soon and ended, and none when upcoming", () => {
41
77
  const { rerender } = render(<EventDetailPanel {...base} />);
42
78
  expect(screen.queryByText("Now")).not.toBeInTheDocument();