@bearmenu/ui 0.1.1 → 0.5.1

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 (118) hide show
  1. package/dist/{chunk-2CUFS5LF.js → chunk-2BMJRXQX.js} +1 -1
  2. package/dist/chunk-HLQ2IRE7.js +279 -0
  3. package/dist/chunk-IQASCH2V.js +25 -0
  4. package/dist/{chunk-UXAYXRFN.js → chunk-QJ54Z7TC.js} +15 -1
  5. package/dist/{chunk-X6GATUI2.js → chunk-UISA4CIG.js} +1 -0
  6. package/dist/components/alert-dialog.js +1 -1
  7. package/dist/components/alert.d.ts +1 -1
  8. package/dist/components/badge.d.ts +1 -1
  9. package/dist/components/button.d.ts +1 -1
  10. package/dist/components/button.js +1 -1
  11. package/dist/components/calendar.d.ts +9 -7
  12. package/dist/components/calendar.js +153 -39
  13. package/dist/components/card.d.ts +2 -1
  14. package/dist/components/card.js +1 -1
  15. package/dist/components/carousel.js +1 -1
  16. package/dist/components/checkbox.js +3 -25
  17. package/dist/components/command.d.ts +13 -13
  18. package/dist/components/dropdown-menu.js +1 -1
  19. package/dist/components/empty-state.js +1 -1
  20. package/dist/components/haptic-button.js +1 -1
  21. package/dist/components/link-button.js +1 -1
  22. package/dist/components/multi-select-combobox.js +1 -1
  23. package/dist/components/multi-select-menu.d.ts +26 -0
  24. package/dist/components/multi-select-menu.js +71 -0
  25. package/dist/components/pagination.js +1 -1
  26. package/dist/components/phone-frame.d.ts +17 -0
  27. package/dist/components/phone-frame.js +41 -0
  28. package/dist/components/place-about-tab.d.ts +17 -0
  29. package/dist/components/place-about-tab.js +6 -0
  30. package/dist/components/place-card.d.ts +17 -0
  31. package/dist/components/place-card.js +145 -0
  32. package/dist/components/place-details-mobile.d.ts +18 -0
  33. package/dist/components/place-details-mobile.js +187 -0
  34. package/dist/components/place-types.d.ts +145 -0
  35. package/dist/components/place-types.js +1 -0
  36. package/dist/components/search-bar.js +1 -1
  37. package/dist/components/searchable-select.js +1 -1
  38. package/dist/components/skeleton-card.js +1 -1
  39. package/dist/components/skeleton.js +1 -1
  40. package/dist/components/sliding-panels.js +1 -1
  41. package/dist/components/sonner.js +11 -2
  42. package/dist/components/tabs.js +1 -1
  43. package/dist/components/text.d.ts +3 -3
  44. package/dist/components/toaster.js +1 -1
  45. package/package.json +28 -9
  46. package/src/components/accordion.stories.tsx +51 -0
  47. package/src/components/alert-dialog.stories.tsx +45 -0
  48. package/src/components/alert.stories.tsx +85 -0
  49. package/src/components/aspect-ratio.stories.tsx +47 -0
  50. package/src/components/avatar.stories.tsx +66 -0
  51. package/src/components/badge.stories.tsx +60 -0
  52. package/src/components/breadcrumb.stories.tsx +63 -0
  53. package/src/components/button.stories.tsx +115 -0
  54. package/src/components/button.tsx +1 -1
  55. package/src/components/calendar.stories.tsx +47 -0
  56. package/src/components/calendar.tsx +160 -40
  57. package/src/components/card.stories.tsx +72 -0
  58. package/src/components/card.tsx +13 -1
  59. package/src/components/carousel.stories.tsx +62 -0
  60. package/src/components/checkbox.stories.tsx +54 -0
  61. package/src/components/checkbox.tsx +1 -1
  62. package/src/components/collapsible.stories.tsx +34 -0
  63. package/src/components/command.stories.tsx +64 -0
  64. package/src/components/currency-input.stories.tsx +50 -0
  65. package/src/components/dialog.stories.tsx +75 -0
  66. package/src/components/drawer.stories.tsx +47 -0
  67. package/src/components/dropdown-menu.stories.tsx +64 -0
  68. package/src/components/dropdown-menu.tsx +1 -1
  69. package/src/components/empty-state.stories.tsx +67 -0
  70. package/src/components/form.stories.tsx +73 -0
  71. package/src/components/haptic-button.stories.tsx +41 -0
  72. package/src/components/input-otp.stories.tsx +42 -0
  73. package/src/components/input.stories.tsx +41 -0
  74. package/src/components/label.stories.tsx +35 -0
  75. package/src/components/markdown-renderer.stories.tsx +45 -0
  76. package/src/components/multi-select-combobox.stories.tsx +78 -0
  77. package/src/components/multi-select-menu.tsx +96 -0
  78. package/src/components/pagination.stories.tsx +48 -0
  79. package/src/components/phone-frame.stories.tsx +51 -0
  80. package/src/components/phone-frame.test.tsx +82 -0
  81. package/src/components/phone-frame.tsx +59 -0
  82. package/src/components/place-about-tab.stories.tsx +164 -0
  83. package/src/components/place-about-tab.test.tsx +325 -0
  84. package/src/components/place-about-tab.tsx +474 -0
  85. package/src/components/place-card.stories.tsx +120 -0
  86. package/src/components/place-card.test.tsx +187 -0
  87. package/src/components/place-card.tsx +187 -0
  88. package/src/components/place-details-mobile.tsx +251 -0
  89. package/src/components/place-types.ts +127 -0
  90. package/src/components/popover.stories.tsx +42 -0
  91. package/src/components/progress.stories.tsx +35 -0
  92. package/src/components/radio-group.stories.tsx +46 -0
  93. package/src/components/rating.stories.tsx +45 -0
  94. package/src/components/scroll-area.stories.tsx +48 -0
  95. package/src/components/search-bar.stories.tsx +92 -0
  96. package/src/components/searchable-select.stories.tsx +55 -0
  97. package/src/components/select.stories.tsx +92 -0
  98. package/src/components/separator.stories.tsx +33 -0
  99. package/src/components/sheet.stories.tsx +95 -0
  100. package/src/components/skeleton-card.stories.tsx +53 -0
  101. package/src/components/skeleton.stories.tsx +42 -0
  102. package/src/components/skeleton.tsx +1 -0
  103. package/src/components/slider.stories.tsx +27 -0
  104. package/src/components/sliding-panels.stories.tsx +68 -0
  105. package/src/components/sonner.tsx +17 -2
  106. package/src/components/sticky-container.stories.tsx +33 -0
  107. package/src/components/switch.stories.tsx +35 -0
  108. package/src/components/table.stories.tsx +60 -0
  109. package/src/components/tabs.stories.tsx +53 -0
  110. package/src/components/tabs.tsx +1 -1
  111. package/src/components/text.stories.tsx +82 -0
  112. package/src/components/textarea.stories.tsx +35 -0
  113. package/src/components/toast.stories.tsx +63 -0
  114. package/src/components/toggle-group.stories.tsx +60 -0
  115. package/src/components/toggle.stories.tsx +48 -0
  116. package/src/components/tooltip.stories.tsx +71 -0
  117. package/src/globals.css +21 -2
  118. package/src/test/setup.ts +9 -0
@@ -0,0 +1,187 @@
1
+ import { PlaceAboutTab } from '../chunk-HLQ2IRE7.js';
2
+ import '../chunk-QJ54Z7TC.js';
3
+ import { Text } from '../chunk-4KOMHDZD.js';
4
+ import { Badge } from '../chunk-NKHH23YK.js';
5
+ import { cn } from '../chunk-FEK5XGZW.js';
6
+ import '../chunk-3GWWZKX7.js';
7
+ import { useState } from 'react';
8
+ import { UtensilsCrossed, Star, DollarSign, Clock, Info, MessageSquare, CalendarDays } from 'lucide-react';
9
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
10
+
11
+ function formatHour(h) {
12
+ return `${h.toString().padStart(2, "0")}:00`;
13
+ }
14
+ function formatRating(rating) {
15
+ return (rating == null ? void 0 : rating.rating) ? rating.rating.toFixed(1) : null;
16
+ }
17
+ function priceLevelFromRanges(place) {
18
+ var _a, _b, _c, _d, _e, _f, _g, _h;
19
+ const dish = (_d = (_c = (_a = place.price_dishes) == null ? void 0 : _a.price_range_high) != null ? _c : (_b = place.price_dishes) == null ? void 0 : _b.price_range_low) != null ? _d : 0;
20
+ const drink = (_h = (_g = (_e = place.price_drinks) == null ? void 0 : _e.price_range_high) != null ? _g : (_f = place.price_drinks) == null ? void 0 : _f.price_range_low) != null ? _h : 0;
21
+ if (dish === 0 && drink === 0) return null;
22
+ const avg = dish > 0 && drink > 0 ? Math.round((dish + drink) / 2) : dish || drink;
23
+ return Math.max(1, Math.min(5, avg));
24
+ }
25
+ function getClosingHour(hours, currentHour) {
26
+ if (!hours.length) return null;
27
+ const active = hours.find((s) => s.opens_at <= currentHour && s.closes_at > currentHour);
28
+ if (active) return active.closes_at;
29
+ const last = hours[hours.length - 1];
30
+ return last ? last.closes_at : null;
31
+ }
32
+ function getNextOpeningToday(hours, currentHour) {
33
+ if (!hours.length) return null;
34
+ const later = hours.find((s) => s.opens_at > currentHour);
35
+ return later ? later.opens_at : null;
36
+ }
37
+ function PlaceDetailsMobile({
38
+ place,
39
+ labels,
40
+ coverImageUrl,
41
+ className
42
+ }) {
43
+ var _a, _b, _c;
44
+ const [imgError, setImgError] = useState(false);
45
+ const ratingDisplay = formatRating(place.rating);
46
+ const reviewCount = (_b = place.review_count) != null ? _b : (_a = place.rating) == null ? void 0 : _a.count;
47
+ const priceLevel = priceLevelFromRanges(place);
48
+ const hours = (_c = place.hours) != null ? _c : [];
49
+ const currentHour = (/* @__PURE__ */ new Date()).getHours();
50
+ const closingHour = place.open_now ? getClosingHour(hours, currentHour) : null;
51
+ const nextOpening = !place.open_now ? getNextOpeningToday(hours, currentHour) : null;
52
+ const showCover = !!coverImageUrl && !imgError;
53
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col bg-background", className), children: [
54
+ /* @__PURE__ */ jsxs("div", { className: "px-4 pt-4", children: [
55
+ /* @__PURE__ */ jsx("div", { className: "rounded-[22px] overflow-hidden relative", children: /* @__PURE__ */ jsxs("div", { className: "h-44 flex items-center justify-center relative overflow-hidden", children: [
56
+ /* @__PURE__ */ jsxs("div", { className: "absolute bottom-2 right-2 z-20 flex items-center gap-1.5 bg-surface border border-border px-2.5 h-7 rounded-[12px] text-[11px] font-medium text-foreground", children: [
57
+ /* @__PURE__ */ jsx(
58
+ "span",
59
+ {
60
+ className: cn(
61
+ "w-1.5 h-1.5 rounded-full flex-shrink-0",
62
+ place.open_now ? "bg-green-500" : "bg-muted-foreground"
63
+ )
64
+ }
65
+ ),
66
+ /* @__PURE__ */ jsxs("span", { children: [
67
+ place.open_now ? labels.statusBadge.openNow : labels.statusBadge.closed,
68
+ place.open_now && closingHour != null && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
69
+ " \xB7 ",
70
+ labels.statusBadge.closesAt,
71
+ " ",
72
+ formatHour(closingHour)
73
+ ] }),
74
+ !place.open_now && nextOpening != null && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
75
+ " \xB7 ",
76
+ labels.statusBadge.opensAt,
77
+ " ",
78
+ formatHour(nextOpening)
79
+ ] }),
80
+ !place.open_now && nextOpening == null && hours[0] && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
81
+ " \xB7 ",
82
+ labels.statusBadge.opensTomorrow,
83
+ " ",
84
+ formatHour(hours[0].opens_at)
85
+ ] })
86
+ ] })
87
+ ] }),
88
+ showCover ? (
89
+ // eslint-disable-next-line @next/next/no-img-element
90
+ /* @__PURE__ */ jsx(
91
+ "img",
92
+ {
93
+ src: coverImageUrl,
94
+ alt: place.name,
95
+ className: "absolute inset-0 w-full h-full object-cover",
96
+ onError: () => setImgError(true)
97
+ }
98
+ )
99
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
100
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-br from-primary/20 via-primary/10 to-primary/5" }),
101
+ /* @__PURE__ */ jsxs("div", { className: "relative z-10 flex flex-col items-center justify-center gap-2 h-full", children: [
102
+ /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-primary/20 flex items-center justify-center", children: /* @__PURE__ */ jsx(
103
+ UtensilsCrossed,
104
+ {
105
+ className: "w-8 h-8 text-primary",
106
+ strokeWidth: 1.5,
107
+ "aria-hidden": true
108
+ }
109
+ ) }),
110
+ /* @__PURE__ */ jsx(Text, { as: "p", variant: "caption", color: "muted", weight: "medium", children: labels.noImageAvailable })
111
+ ] })
112
+ ] })
113
+ ] }) }),
114
+ /* @__PURE__ */ jsxs("div", { className: "pt-4 px-1", children: [
115
+ /* @__PURE__ */ jsx("h1", { className: "text-[26px] leading-[1.05] font-bold tracking-tight text-foreground break-words", children: place.name }),
116
+ /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap items-center gap-x-4 gap-y-1.5 text-[12px] text-muted-foreground", children: [
117
+ ratingDisplay && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
118
+ /* @__PURE__ */ jsx(Star, { className: "w-3 h-3 text-primary fill-primary", "aria-hidden": true }),
119
+ /* @__PURE__ */ jsx("span", { className: "text-foreground font-semibold", children: ratingDisplay }),
120
+ reviewCount != null && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: labels.reviewCount(reviewCount) })
121
+ ] }),
122
+ priceLevel != null && /* @__PURE__ */ jsx("div", { className: "flex items-center -space-x-0.5", "aria-label": `Price level ${priceLevel} of 5`, children: Array.from({ length: 5 }, (_, i) => /* @__PURE__ */ jsx(
123
+ DollarSign,
124
+ {
125
+ className: cn(
126
+ "w-3 h-3",
127
+ i < priceLevel ? "text-foreground" : "text-muted-foreground/30"
128
+ ),
129
+ strokeWidth: 2.5,
130
+ "aria-hidden": true
131
+ },
132
+ i
133
+ )) }),
134
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
135
+ /* @__PURE__ */ jsx(Clock, { className: "w-3.5 h-3.5", "aria-hidden": true }),
136
+ /* @__PURE__ */ jsx(
137
+ "span",
138
+ {
139
+ className: cn(
140
+ place.open_now ? "text-green-600 dark:text-green-400" : "text-muted-foreground"
141
+ ),
142
+ children: place.open_now ? labels.statusBadge.openNow : labels.statusBadge.closed
143
+ }
144
+ )
145
+ ] })
146
+ ] }),
147
+ place.cuisines && place.cuisines.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-3 flex flex-wrap gap-1.5", children: place.cuisines.map((cuisine) => /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "capitalize text-[11px]", children: cuisine.replace(/-/g, " ") }, cuisine)) })
148
+ ] })
149
+ ] }),
150
+ /* @__PURE__ */ jsx("div", { className: "mt-5 px-4", children: /* @__PURE__ */ jsx("div", { className: "border-b border-border", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full", children: [
151
+ /* @__PURE__ */ jsx(MobileTabPlaceholder, { icon: /* @__PURE__ */ jsx(UtensilsCrossed, { className: "w-3.5 h-3.5" }), label: labels.tabs.menu }),
152
+ /* @__PURE__ */ jsx(
153
+ MobileTabPlaceholder,
154
+ {
155
+ icon: /* @__PURE__ */ jsx(Info, { className: "w-3.5 h-3.5" }),
156
+ label: labels.tabs.about,
157
+ active: true
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsx(MobileTabPlaceholder, { icon: /* @__PURE__ */ jsx(MessageSquare, { className: "w-3.5 h-3.5" }), label: labels.tabs.reviews }),
161
+ /* @__PURE__ */ jsx(MobileTabPlaceholder, { icon: /* @__PURE__ */ jsx(CalendarDays, { className: "w-3.5 h-3.5" }), label: labels.tabs.events })
162
+ ] }) }) }),
163
+ /* @__PURE__ */ jsx("div", { className: "pb-6", children: /* @__PURE__ */ jsx(PlaceAboutTab, { place, labels }) })
164
+ ] });
165
+ }
166
+ function MobileTabPlaceholder({
167
+ icon,
168
+ label,
169
+ active
170
+ }) {
171
+ return /* @__PURE__ */ jsxs(
172
+ "div",
173
+ {
174
+ className: cn(
175
+ "flex flex-1 min-w-0 items-center justify-center gap-1 px-1 py-2.5 text-[12px] font-medium border-b-2 -mb-px",
176
+ active ? "border-primary text-foreground" : "border-transparent text-muted-foreground"
177
+ ),
178
+ "aria-hidden": true,
179
+ children: [
180
+ /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", "aria-hidden": true, children: icon }),
181
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
182
+ ]
183
+ }
184
+ );
185
+ }
186
+
187
+ export { PlaceDetailsMobile };
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Structural mirror of the openapi `Place` schema, decoupled from any one app's
3
+ * generated types. Both FE and admin map their domain shapes into this type
4
+ * before passing data to `<PlaceCard>` / `<PlaceAboutTab>`.
5
+ */
6
+ type PlacePreview = {
7
+ id: string;
8
+ slug: string;
9
+ name: string;
10
+ description?: string;
11
+ generative_summary?: string;
12
+ review_summary?: string;
13
+ open_now?: boolean;
14
+ rating?: {
15
+ rating: number;
16
+ count: number;
17
+ };
18
+ review_count?: number;
19
+ hours?: PlaceHourBlock[];
20
+ today_schedule?: PlaceHourBlock[];
21
+ location?: {
22
+ address?: string;
23
+ geolocation?: {
24
+ lat: number | string;
25
+ lon: number | string;
26
+ };
27
+ distance?: {
28
+ meters?: number;
29
+ };
30
+ };
31
+ price_dishes?: PlacePriceRange;
32
+ price_drinks?: PlacePriceRange;
33
+ cuisines?: string[];
34
+ amenities?: string[];
35
+ payment_options?: string[];
36
+ accessibility_features?: string[];
37
+ parking_options?: string[];
38
+ landmarks?: Array<{
39
+ name: string;
40
+ distance_meters?: number;
41
+ }>;
42
+ areas?: Array<{
43
+ name: string;
44
+ }>;
45
+ busyness?: Array<{
46
+ hour: number;
47
+ score: number;
48
+ }>;
49
+ type?: string;
50
+ business_status?: 'operational' | 'closed_temporarily' | 'closed_permanently';
51
+ phone?: string;
52
+ website_url?: string;
53
+ socials?: {
54
+ instagram_url?: string;
55
+ facebook_url?: string;
56
+ };
57
+ /**
58
+ * Pre-resolved thumbnail URL. Callers compute this themselves
59
+ * (FE: `https://cdn.bear.menu/places/${slug}-thumbnail.jpg`,
60
+ * admin: form's primary photo URL or blob URL).
61
+ */
62
+ thumbnail_url?: string;
63
+ };
64
+ type PlaceHourBlock = {
65
+ opens_at: number;
66
+ closes_at: number;
67
+ };
68
+ type PlacePriceRange = {
69
+ price_range_low?: number;
70
+ price_range_high?: number;
71
+ };
72
+ /** Labels passed to `<PlaceCard>`. All translation happens in the caller. */
73
+ type PlaceCardLabels = {
74
+ open: string;
75
+ closed: string;
76
+ until: string;
77
+ opensAt: string;
78
+ currency: string;
79
+ };
80
+ /**
81
+ * Labels passed to `<PlaceAboutTab>`. All translation happens in the caller.
82
+ *
83
+ * Dynamic suffix maps (`amenityLabels`, etc.) translate API enum values like
84
+ * `wheelchair_accessible_entrance` into human strings. Missing keys fall back
85
+ * to the raw enum value (with underscores → spaces).
86
+ */
87
+ type PlaceAboutTabLabels = {
88
+ overview: string;
89
+ description: string;
90
+ whatPeopleSay: string;
91
+ contactAndLocation: string;
92
+ address: string;
93
+ instagram: string;
94
+ facebook: string;
95
+ todayHours: string;
96
+ statusOpen: string;
97
+ statusClosed: string;
98
+ typicalBusyness: string;
99
+ cuisines: string;
100
+ amenities: string;
101
+ paymentOptions: string;
102
+ accessibility: string;
103
+ parking: string;
104
+ nearbyLandmarks: string;
105
+ neighborhood: string;
106
+ businessInformation: string;
107
+ type: string;
108
+ status: string;
109
+ amenityLabels: Record<string, string>;
110
+ paymentLabels: Record<string, string>;
111
+ accessibilityLabels: Record<string, string>;
112
+ parkingLabels: Record<string, string>;
113
+ typeLabels: Record<string, string>;
114
+ statusLabels: Record<string, string>;
115
+ };
116
+ /** Discriminator for `<PlaceAboutTab>`'s `onLinkClick` callback. */
117
+ type PlaceLinkKind = 'phone' | 'website' | 'instagram' | 'facebook' | 'address';
118
+ /**
119
+ * Labels passed to `<PlaceDetailsMobile>`. Extends `PlaceAboutTabLabels` with
120
+ * the header/tab-strip strings unique to the mobile place-details surface.
121
+ */
122
+ type PlaceDetailsMobileLabels = PlaceAboutTabLabels & {
123
+ /** Tab strip — "About" is highlighted, the rest are visual placeholders. */
124
+ tabs: {
125
+ menu: string;
126
+ about: string;
127
+ reviews: string;
128
+ events: string;
129
+ };
130
+ /** Status badge over the hero image and the inline open/closed pill in the info row. */
131
+ statusBadge: {
132
+ openNow: string;
133
+ closed: string;
134
+ /** Inserted with surrounding " · " separators. e.g. "Closes at" */
135
+ closesAt: string;
136
+ opensAt: string;
137
+ opensTomorrow: string;
138
+ };
139
+ /** Review count formatter. Pass a translator that handles ICU pluralization. */
140
+ reviewCount: (count: number) => string;
141
+ /** "No image available" fallback shown when no thumbnail is set. */
142
+ noImageAvailable: string;
143
+ };
144
+
145
+ export type { PlaceAboutTabLabels, PlaceCardLabels, PlaceDetailsMobileLabels, PlaceHourBlock, PlaceLinkKind, PlacePreview, PlacePriceRange };
@@ -0,0 +1 @@
1
+
@@ -1,4 +1,4 @@
1
- import { Card } from '../chunk-UXAYXRFN.js';
1
+ import { Card } from '../chunk-QJ54Z7TC.js';
2
2
  import '../chunk-4KOMHDZD.js';
3
3
  import { cn } from '../chunk-FEK5XGZW.js';
4
4
  import '../chunk-3GWWZKX7.js';
@@ -1,7 +1,7 @@
1
1
  import { Popover, PopoverTrigger, PopoverContent } from '../chunk-4UO73SKC.js';
2
2
  import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from '../chunk-7H54PTVH.js';
3
3
  import '../chunk-4AVS6CV4.js';
4
- import { Button } from '../chunk-2CUFS5LF.js';
4
+ import { Button } from '../chunk-2BMJRXQX.js';
5
5
  import '../chunk-R47NEOSI.js';
6
6
  import { cn } from '../chunk-FEK5XGZW.js';
7
7
  import '../chunk-3GWWZKX7.js';
@@ -1,4 +1,4 @@
1
- import { Skeleton } from '../chunk-X6GATUI2.js';
1
+ import { Skeleton } from '../chunk-UISA4CIG.js';
2
2
  import { AspectRatio } from '../chunk-3VMDCCCX.js';
3
3
  import { cn } from '../chunk-FEK5XGZW.js';
4
4
  import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
@@ -1,3 +1,3 @@
1
- export { Skeleton } from '../chunk-X6GATUI2.js';
1
+ export { Skeleton } from '../chunk-UISA4CIG.js';
2
2
  import '../chunk-FEK5XGZW.js';
3
3
  import '../chunk-3GWWZKX7.js';
@@ -1,4 +1,4 @@
1
- import { Button } from '../chunk-2CUFS5LF.js';
1
+ import { Button } from '../chunk-2BMJRXQX.js';
2
2
  import '../chunk-R47NEOSI.js';
3
3
  import { cn } from '../chunk-FEK5XGZW.js';
4
4
  import '../chunk-3GWWZKX7.js';
@@ -1,4 +1,5 @@
1
1
  import { __objRest, __spreadValues } from '../chunk-3GWWZKX7.js';
2
+ import { Loader2Icon, OctagonXIcon, TriangleAlertIcon, InfoIcon, CircleCheckIcon } from 'lucide-react';
2
3
  import { useTheme } from 'next-themes';
3
4
  import { Toaster as Toaster$1 } from 'sonner';
4
5
  import { jsx } from 'react/jsx-runtime';
@@ -9,12 +10,20 @@ var Toaster = (_a) => {
9
10
  return /* @__PURE__ */ jsx(
10
11
  Toaster$1,
11
12
  __spreadValues({
12
- theme,
13
+ theme: theme != null ? theme : "system",
13
14
  className: "toaster group",
15
+ icons: {
16
+ success: /* @__PURE__ */ jsx(CircleCheckIcon, { className: "size-4" }),
17
+ info: /* @__PURE__ */ jsx(InfoIcon, { className: "size-4" }),
18
+ warning: /* @__PURE__ */ jsx(TriangleAlertIcon, { className: "size-4" }),
19
+ error: /* @__PURE__ */ jsx(OctagonXIcon, { className: "size-4" }),
20
+ loading: /* @__PURE__ */ jsx(Loader2Icon, { className: "size-4 animate-spin" })
21
+ },
14
22
  style: {
15
23
  "--normal-bg": "var(--popover)",
16
24
  "--normal-text": "var(--popover-foreground)",
17
- "--normal-border": "var(--border)"
25
+ "--normal-border": "var(--border)",
26
+ "--border-radius": "var(--radius)"
18
27
  }
19
28
  }, props)
20
29
  );
@@ -13,7 +13,7 @@ var TabsList = React.forwardRef((_a, ref) => {
13
13
  __spreadProps(__spreadValues({
14
14
  ref,
15
15
  className: cn(
16
- variant === "pill" ? "inline-flex h-12 items-center justify-center gap-2 rounded-3xl glass p-1 text-muted-foreground border border-border/50 shadow-lg" : "inline-flex h-auto items-center gap-0 rounded-none bg-transparent border-b border-border p-0 shadow-none text-muted-foreground",
16
+ variant === "pill" ? "inline-flex h-12 items-center justify-center gap-2 rounded-3xl bg-sidebar p-1 text-muted-foreground border border-border/50 shadow-lg" : "inline-flex h-auto items-center gap-0 rounded-none bg-transparent border-b border-border p-0 shadow-none text-muted-foreground",
17
17
  className
18
18
  )
19
19
  }, props), {
@@ -4,9 +4,9 @@ import * as React from 'react';
4
4
  import { VariantProps } from 'class-variance-authority';
5
5
 
6
6
  declare const textVariants: (props?: ({
7
- variant?: "display" | "h1" | "h2" | "h3" | "h4" | "body-lg" | "body" | "body-sm" | "caption" | "label" | "overline" | null | undefined;
8
- color?: "default" | "destructive" | "inherit" | "muted" | "primary" | "primary-foreground" | null | undefined;
9
- weight?: "bold" | "normal" | "medium" | "semibold" | null | undefined;
7
+ variant?: "h2" | "h3" | "label" | "body" | "caption" | "h1" | "h4" | "display" | "body-lg" | "body-sm" | "overline" | null | undefined;
8
+ color?: "inherit" | "default" | "destructive" | "muted" | "primary" | "primary-foreground" | null | undefined;
9
+ weight?: "bold" | "medium" | "normal" | "semibold" | null | undefined;
10
10
  leading?: "none" | "normal" | "tight" | "snug" | "relaxed" | "loose" | null | undefined;
11
11
  tracking?: "normal" | "tight" | "wide" | "wider" | null | undefined;
12
12
  wrap?: "balance" | "pretty" | "nowrap" | null | undefined;
@@ -1,5 +1,5 @@
1
- import { useToast } from '../chunk-EBAMCZDW.js';
2
1
  import { ToastProvider, Toast, ToastTitle, ToastDescription, ToastClose, ToastViewport } from '../chunk-ERMIA7NP.js';
2
+ import { useToast } from '../chunk-EBAMCZDW.js';
3
3
  import '../chunk-FEK5XGZW.js';
4
4
  import { __objRest, __spreadProps, __spreadValues } from '../chunk-3GWWZKX7.js';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bearmenu/ui",
3
- "version": "0.1.1",
3
+ "version": "0.5.1",
4
4
  "description": "BearMenu design system — shared UI tokens, primitives, and components",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -45,7 +45,13 @@
45
45
  "./components/link-button": "./src/components/link-button.tsx",
46
46
  "./components/markdown-renderer": "./src/components/markdown-renderer.tsx",
47
47
  "./components/multi-select-combobox": "./src/components/multi-select-combobox.tsx",
48
+ "./components/multi-select-menu": "./src/components/multi-select-menu.tsx",
48
49
  "./components/pagination": "./src/components/pagination.tsx",
50
+ "./components/phone-frame": "./src/components/phone-frame.tsx",
51
+ "./components/place-about-tab": "./src/components/place-about-tab.tsx",
52
+ "./components/place-card": "./src/components/place-card.tsx",
53
+ "./components/place-details-mobile": "./src/components/place-details-mobile.tsx",
54
+ "./components/place-types": "./src/components/place-types.ts",
49
55
  "./components/popover": "./src/components/popover.tsx",
50
56
  "./components/progress": "./src/components/progress.tsx",
51
57
  "./components/radio-group": "./src/components/radio-group.tsx",
@@ -98,6 +104,8 @@
98
104
  }
99
105
  },
100
106
  "scripts": {
107
+ "storybook": "storybook dev -p 6006",
108
+ "build-storybook": "storybook build",
101
109
  "build": "tsup",
102
110
  "dev": "tsup --watch",
103
111
  "typecheck": "tsc --noEmit",
@@ -105,7 +113,7 @@
105
113
  "prepublishOnly": "pnpm run build"
106
114
  },
107
115
  "peerDependencies": {
108
- "@hookform/resolvers": "^3.0.0",
116
+ "@hookform/resolvers": "^3.0.0 || ^5.0.0",
109
117
  "@radix-ui/react-accordion": "*",
110
118
  "@radix-ui/react-alert-dialog": "*",
111
119
  "@radix-ui/react-aspect-ratio": "*",
@@ -135,7 +143,7 @@
135
143
  "embla-carousel-react": "*",
136
144
  "framer-motion": "^12.0.0",
137
145
  "input-otp": "*",
138
- "lucide-react": "^0.400.0",
146
+ "lucide-react": ">=0.400.0",
139
147
  "next": ">=14.0.0",
140
148
  "next-themes": "^0.4.0",
141
149
  "react": "^18.0.0 || ^19.0.0",
@@ -144,10 +152,10 @@
144
152
  "react-hook-form": "^7.0.0",
145
153
  "react-markdown": "^10.0.0",
146
154
  "remark-gfm": "^4.0.0",
147
- "sonner": "^1.0.0",
148
- "tailwind-merge": "^2.0.0",
155
+ "sonner": "^1.0.0 || ^2.0.0",
156
+ "tailwind-merge": "^2.0.0 || ^3.0.0",
149
157
  "vaul": "^1.0.0",
150
- "zod": "^3.0.0"
158
+ "zod": "^3.0.0 || ^4.0.0"
151
159
  },
152
160
  "devDependencies": {
153
161
  "@hookform/resolvers": "^3.10.0",
@@ -174,15 +182,24 @@
174
182
  "@radix-ui/react-toggle": "latest",
175
183
  "@radix-ui/react-toggle-group": "latest",
176
184
  "@radix-ui/react-tooltip": "latest",
185
+ "@storybook/addon-a11y": "^10.3.5",
186
+ "@storybook/addon-docs": "^10.3.5",
187
+ "@storybook/react-vite": "^10.3.5",
188
+ "@tailwindcss/postcss": "^4.2.4",
189
+ "@tailwindcss/vite": "^4.2.4",
190
+ "@testing-library/jest-dom": "^6.9.1",
191
+ "@testing-library/react": "^16.3.2",
177
192
  "@types/react": "^19",
178
193
  "@types/react-dom": "^19",
194
+ "@vitejs/plugin-react": "^6.0.1",
179
195
  "class-variance-authority": "^0.7.1",
180
196
  "clsx": "^2.1.1",
181
197
  "cmdk": "latest",
182
198
  "embla-carousel-react": "^8.6.0",
183
199
  "framer-motion": "^12.23.22",
184
200
  "input-otp": "^1.4.2",
185
- "lucide-react": "^0.577.0",
201
+ "jsdom": "^29.1.0",
202
+ "lucide-react": "^1.0.1",
186
203
  "next": "^16.1.7",
187
204
  "next-themes": "^0.4.6",
188
205
  "react": "19.2.4",
@@ -191,13 +208,15 @@
191
208
  "react-hook-form": "7.72.0",
192
209
  "react-markdown": "^10.1.0",
193
210
  "remark-gfm": "^4.0.1",
194
- "sonner": "^1.7.4",
195
- "tailwind-merge": "^2.5.5",
211
+ "sonner": "^2.0.7",
212
+ "storybook": "^10.3.5",
213
+ "tailwind-merge": "^3.5.0",
196
214
  "tailwindcss": "^4.2.4",
197
215
  "tsup": "^8.5.0",
198
216
  "tw-animate-css": "^1.4.0",
199
217
  "typescript": "^5",
200
218
  "vaul": "^1.1.2",
219
+ "vitest": "^4.1.5",
201
220
  "zod": "^3.25.67"
202
221
  }
203
222
  }
@@ -0,0 +1,51 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./accordion";
3
+
4
+ const meta = {
5
+ title: "Components/Accordion",
6
+ component: Accordion,
7
+ tags: ["autodocs"],
8
+ } satisfies Meta<typeof Accordion>;
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const Default: Story = {
14
+ render: () => (
15
+ <Accordion type="single" collapsible className="w-[400px]">
16
+ <AccordionItem value="item-1">
17
+ <AccordionTrigger>Is it accessible?</AccordionTrigger>
18
+ <AccordionContent>Yes. It adheres to the WAI-ARIA design pattern.</AccordionContent>
19
+ </AccordionItem>
20
+ <AccordionItem value="item-2">
21
+ <AccordionTrigger>Is it styled?</AccordionTrigger>
22
+ <AccordionContent>
23
+ Yes. It comes with default styles that match the design system.
24
+ </AccordionContent>
25
+ </AccordionItem>
26
+ <AccordionItem value="item-3">
27
+ <AccordionTrigger>Is it animated?</AccordionTrigger>
28
+ <AccordionContent>Yes. It uses CSS animations for smooth transitions.</AccordionContent>
29
+ </AccordionItem>
30
+ </Accordion>
31
+ ),
32
+ };
33
+
34
+ export const Multiple: Story = {
35
+ render: () => (
36
+ <Accordion type="multiple" className="w-[400px]">
37
+ <AccordionItem value="item-1">
38
+ <AccordionTrigger>Section One</AccordionTrigger>
39
+ <AccordionContent>Content for section one.</AccordionContent>
40
+ </AccordionItem>
41
+ <AccordionItem value="item-2">
42
+ <AccordionTrigger>Section Two</AccordionTrigger>
43
+ <AccordionContent>Content for section two.</AccordionContent>
44
+ </AccordionItem>
45
+ <AccordionItem value="item-3">
46
+ <AccordionTrigger>Section Three</AccordionTrigger>
47
+ <AccordionContent>Content for section three.</AccordionContent>
48
+ </AccordionItem>
49
+ </Accordion>
50
+ ),
51
+ };
@@ -0,0 +1,45 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import {
3
+ AlertDialog,
4
+ AlertDialogAction,
5
+ AlertDialogCancel,
6
+ AlertDialogContent,
7
+ AlertDialogDescription,
8
+ AlertDialogFooter,
9
+ AlertDialogHeader,
10
+ AlertDialogTitle,
11
+ AlertDialogTrigger,
12
+ } from "./alert-dialog";
13
+ import { Button } from "./button";
14
+
15
+ const meta = {
16
+ title: "Components/AlertDialog",
17
+ component: AlertDialog,
18
+ tags: ["autodocs"],
19
+ } satisfies Meta<typeof AlertDialog>;
20
+
21
+ export default meta;
22
+ type Story = StoryObj<typeof meta>;
23
+
24
+ export const Default: Story = {
25
+ render: () => (
26
+ <AlertDialog>
27
+ <AlertDialogTrigger asChild>
28
+ <Button variant="destructive">Delete Account</Button>
29
+ </AlertDialogTrigger>
30
+ <AlertDialogContent>
31
+ <AlertDialogHeader>
32
+ <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
33
+ <AlertDialogDescription>
34
+ This action cannot be undone. This will permanently delete your account and remove your
35
+ data from our servers.
36
+ </AlertDialogDescription>
37
+ </AlertDialogHeader>
38
+ <AlertDialogFooter>
39
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
40
+ <AlertDialogAction>Continue</AlertDialogAction>
41
+ </AlertDialogFooter>
42
+ </AlertDialogContent>
43
+ </AlertDialog>
44
+ ),
45
+ };