@bearmenu/ui 0.1.1 → 0.5.0

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 (113) 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/button.js +1 -1
  8. package/dist/components/calendar.d.ts +9 -7
  9. package/dist/components/calendar.js +153 -39
  10. package/dist/components/card.d.ts +2 -1
  11. package/dist/components/card.js +1 -1
  12. package/dist/components/carousel.js +1 -1
  13. package/dist/components/checkbox.js +3 -25
  14. package/dist/components/dropdown-menu.js +1 -1
  15. package/dist/components/empty-state.js +1 -1
  16. package/dist/components/haptic-button.js +1 -1
  17. package/dist/components/link-button.js +1 -1
  18. package/dist/components/multi-select-combobox.js +1 -1
  19. package/dist/components/multi-select-menu.d.ts +26 -0
  20. package/dist/components/multi-select-menu.js +71 -0
  21. package/dist/components/pagination.js +1 -1
  22. package/dist/components/phone-frame.d.ts +17 -0
  23. package/dist/components/phone-frame.js +41 -0
  24. package/dist/components/place-about-tab.d.ts +17 -0
  25. package/dist/components/place-about-tab.js +6 -0
  26. package/dist/components/place-card.d.ts +17 -0
  27. package/dist/components/place-card.js +145 -0
  28. package/dist/components/place-details-mobile.d.ts +18 -0
  29. package/dist/components/place-details-mobile.js +187 -0
  30. package/dist/components/place-types.d.ts +145 -0
  31. package/dist/components/place-types.js +1 -0
  32. package/dist/components/search-bar.js +1 -1
  33. package/dist/components/searchable-select.js +1 -1
  34. package/dist/components/skeleton-card.js +1 -1
  35. package/dist/components/skeleton.js +1 -1
  36. package/dist/components/sliding-panels.js +1 -1
  37. package/dist/components/sonner.js +11 -2
  38. package/dist/components/tabs.js +1 -1
  39. package/dist/components/toaster.js +1 -1
  40. package/package.json +28 -9
  41. package/src/components/accordion.stories.tsx +51 -0
  42. package/src/components/alert-dialog.stories.tsx +45 -0
  43. package/src/components/alert.stories.tsx +85 -0
  44. package/src/components/aspect-ratio.stories.tsx +47 -0
  45. package/src/components/avatar.stories.tsx +66 -0
  46. package/src/components/badge.stories.tsx +60 -0
  47. package/src/components/breadcrumb.stories.tsx +63 -0
  48. package/src/components/button.stories.tsx +115 -0
  49. package/src/components/button.tsx +1 -1
  50. package/src/components/calendar.stories.tsx +47 -0
  51. package/src/components/calendar.tsx +160 -40
  52. package/src/components/card.stories.tsx +72 -0
  53. package/src/components/card.tsx +13 -1
  54. package/src/components/carousel.stories.tsx +62 -0
  55. package/src/components/checkbox.stories.tsx +54 -0
  56. package/src/components/checkbox.tsx +1 -1
  57. package/src/components/collapsible.stories.tsx +34 -0
  58. package/src/components/command.stories.tsx +64 -0
  59. package/src/components/currency-input.stories.tsx +50 -0
  60. package/src/components/dialog.stories.tsx +75 -0
  61. package/src/components/drawer.stories.tsx +47 -0
  62. package/src/components/dropdown-menu.stories.tsx +64 -0
  63. package/src/components/dropdown-menu.tsx +1 -1
  64. package/src/components/empty-state.stories.tsx +67 -0
  65. package/src/components/form.stories.tsx +73 -0
  66. package/src/components/haptic-button.stories.tsx +41 -0
  67. package/src/components/input-otp.stories.tsx +42 -0
  68. package/src/components/input.stories.tsx +41 -0
  69. package/src/components/label.stories.tsx +35 -0
  70. package/src/components/markdown-renderer.stories.tsx +45 -0
  71. package/src/components/multi-select-combobox.stories.tsx +78 -0
  72. package/src/components/multi-select-menu.tsx +96 -0
  73. package/src/components/pagination.stories.tsx +48 -0
  74. package/src/components/phone-frame.stories.tsx +51 -0
  75. package/src/components/phone-frame.test.tsx +82 -0
  76. package/src/components/phone-frame.tsx +59 -0
  77. package/src/components/place-about-tab.stories.tsx +164 -0
  78. package/src/components/place-about-tab.test.tsx +325 -0
  79. package/src/components/place-about-tab.tsx +474 -0
  80. package/src/components/place-card.stories.tsx +120 -0
  81. package/src/components/place-card.test.tsx +187 -0
  82. package/src/components/place-card.tsx +187 -0
  83. package/src/components/place-details-mobile.tsx +251 -0
  84. package/src/components/place-types.ts +127 -0
  85. package/src/components/popover.stories.tsx +42 -0
  86. package/src/components/progress.stories.tsx +35 -0
  87. package/src/components/radio-group.stories.tsx +46 -0
  88. package/src/components/rating.stories.tsx +45 -0
  89. package/src/components/scroll-area.stories.tsx +48 -0
  90. package/src/components/search-bar.stories.tsx +92 -0
  91. package/src/components/searchable-select.stories.tsx +55 -0
  92. package/src/components/select.stories.tsx +92 -0
  93. package/src/components/separator.stories.tsx +33 -0
  94. package/src/components/sheet.stories.tsx +95 -0
  95. package/src/components/skeleton-card.stories.tsx +53 -0
  96. package/src/components/skeleton.stories.tsx +42 -0
  97. package/src/components/skeleton.tsx +1 -0
  98. package/src/components/slider.stories.tsx +27 -0
  99. package/src/components/sliding-panels.stories.tsx +68 -0
  100. package/src/components/sonner.tsx +17 -2
  101. package/src/components/sticky-container.stories.tsx +33 -0
  102. package/src/components/switch.stories.tsx +35 -0
  103. package/src/components/table.stories.tsx +60 -0
  104. package/src/components/tabs.stories.tsx +53 -0
  105. package/src/components/tabs.tsx +1 -1
  106. package/src/components/text.stories.tsx +82 -0
  107. package/src/components/textarea.stories.tsx +35 -0
  108. package/src/components/toast.stories.tsx +63 -0
  109. package/src/components/toggle-group.stories.tsx +60 -0
  110. package/src/components/toggle.stories.tsx +48 -0
  111. package/src/components/tooltip.stories.tsx +71 -0
  112. package/src/globals.css +14 -2
  113. package/src/test/setup.ts +9 -0
@@ -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), {
@@ -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.0",
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
+ };
@@ -0,0 +1,85 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Alert, AlertTitle, AlertDescription } from "./alert";
3
+ import { AlertCircle, CheckCircle, Info, AlertTriangle } from "lucide-react";
4
+
5
+ const meta = {
6
+ title: "Components/Alert",
7
+ component: Alert,
8
+ tags: ["autodocs"],
9
+ argTypes: {
10
+ variant: {
11
+ control: "select",
12
+ options: ["default", "destructive", "warning", "success"],
13
+ },
14
+ },
15
+ } satisfies Meta<typeof Alert>;
16
+
17
+ export default meta;
18
+ type Story = StoryObj<typeof meta>;
19
+
20
+ export const Default: Story = {
21
+ render: () => (
22
+ <Alert>
23
+ <Info className="h-4 w-4" />
24
+ <AlertTitle>Information</AlertTitle>
25
+ <AlertDescription>This is a default informational alert.</AlertDescription>
26
+ </Alert>
27
+ ),
28
+ };
29
+
30
+ export const Destructive: Story = {
31
+ render: () => (
32
+ <Alert variant="destructive">
33
+ <AlertCircle className="h-4 w-4" />
34
+ <AlertTitle>Error</AlertTitle>
35
+ <AlertDescription>Something went wrong. Please try again.</AlertDescription>
36
+ </Alert>
37
+ ),
38
+ };
39
+
40
+ export const Warning: Story = {
41
+ render: () => (
42
+ <Alert variant="warning">
43
+ <AlertTriangle className="h-4 w-4" />
44
+ <AlertTitle>Warning</AlertTitle>
45
+ <AlertDescription>Your session is about to expire.</AlertDescription>
46
+ </Alert>
47
+ ),
48
+ };
49
+
50
+ export const Success: Story = {
51
+ render: () => (
52
+ <Alert variant="success">
53
+ <CheckCircle className="h-4 w-4" />
54
+ <AlertTitle>Success</AlertTitle>
55
+ <AlertDescription>Your changes have been saved.</AlertDescription>
56
+ </Alert>
57
+ ),
58
+ };
59
+
60
+ export const AllVariants: Story = {
61
+ render: () => (
62
+ <div className="flex flex-col gap-4 w-[450px]">
63
+ <Alert>
64
+ <Info className="h-4 w-4" />
65
+ <AlertTitle>Default</AlertTitle>
66
+ <AlertDescription>Informational message.</AlertDescription>
67
+ </Alert>
68
+ <Alert variant="destructive">
69
+ <AlertCircle className="h-4 w-4" />
70
+ <AlertTitle>Destructive</AlertTitle>
71
+ <AlertDescription>Error message.</AlertDescription>
72
+ </Alert>
73
+ <Alert variant="warning">
74
+ <AlertTriangle className="h-4 w-4" />
75
+ <AlertTitle>Warning</AlertTitle>
76
+ <AlertDescription>Warning message.</AlertDescription>
77
+ </Alert>
78
+ <Alert variant="success">
79
+ <CheckCircle className="h-4 w-4" />
80
+ <AlertTitle>Success</AlertTitle>
81
+ <AlertDescription>Success message.</AlertDescription>
82
+ </Alert>
83
+ </div>
84
+ ),
85
+ };
@@ -0,0 +1,47 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { AspectRatio } from "./aspect-ratio";
3
+
4
+ const meta = {
5
+ title: "Components/AspectRatio",
6
+ component: AspectRatio,
7
+ tags: ["autodocs"],
8
+ } satisfies Meta<typeof AspectRatio>;
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const Default: Story = {
14
+ render: () => (
15
+ <div className="w-[450px]">
16
+ <AspectRatio ratio={16 / 9} className="bg-muted rounded-md overflow-hidden">
17
+ <div className="flex items-center justify-center h-full text-muted-foreground">
18
+ 16:9
19
+ </div>
20
+ </AspectRatio>
21
+ </div>
22
+ ),
23
+ };
24
+
25
+ export const Square: Story = {
26
+ render: () => (
27
+ <div className="w-[300px]">
28
+ <AspectRatio ratio={1} className="bg-muted rounded-md overflow-hidden">
29
+ <div className="flex items-center justify-center h-full text-muted-foreground">
30
+ 1:1
31
+ </div>
32
+ </AspectRatio>
33
+ </div>
34
+ ),
35
+ };
36
+
37
+ export const Portrait: Story = {
38
+ render: () => (
39
+ <div className="w-[200px]">
40
+ <AspectRatio ratio={3 / 4} className="bg-muted rounded-md overflow-hidden">
41
+ <div className="flex items-center justify-center h-full text-muted-foreground">
42
+ 3:4
43
+ </div>
44
+ </AspectRatio>
45
+ </div>
46
+ ),
47
+ };
@@ -0,0 +1,66 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { Avatar, AvatarFallback, AvatarImage } from "./avatar";
3
+
4
+ const meta = {
5
+ title: "Components/Avatar",
6
+ component: Avatar,
7
+ tags: ["autodocs"],
8
+ } satisfies Meta<typeof Avatar>;
9
+
10
+ export default meta;
11
+ type Story = StoryObj<typeof meta>;
12
+
13
+ export const WithImage: Story = {
14
+ render: () => (
15
+ <Avatar>
16
+ <AvatarImage src="https://github.com/shadcn.png" alt="User" />
17
+ <AvatarFallback>CN</AvatarFallback>
18
+ </Avatar>
19
+ ),
20
+ };
21
+
22
+ export const Fallback: Story = {
23
+ render: () => (
24
+ <Avatar>
25
+ <AvatarFallback>JD</AvatarFallback>
26
+ </Avatar>
27
+ ),
28
+ };
29
+
30
+ export const Sizes: Story = {
31
+ render: () => (
32
+ <div className="flex items-center gap-4">
33
+ <Avatar className="h-8 w-8">
34
+ <AvatarFallback className="text-xs">S</AvatarFallback>
35
+ </Avatar>
36
+ <Avatar>
37
+ <AvatarFallback>MD</AvatarFallback>
38
+ </Avatar>
39
+ <Avatar className="h-14 w-14">
40
+ <AvatarFallback className="text-lg">LG</AvatarFallback>
41
+ </Avatar>
42
+ <Avatar className="h-20 w-20">
43
+ <AvatarFallback className="text-xl">XL</AvatarFallback>
44
+ </Avatar>
45
+ </div>
46
+ ),
47
+ };
48
+
49
+ export const Group: Story = {
50
+ render: () => (
51
+ <div className="flex -space-x-3">
52
+ <Avatar className="border-2 border-background">
53
+ <AvatarFallback>A</AvatarFallback>
54
+ </Avatar>
55
+ <Avatar className="border-2 border-background">
56
+ <AvatarFallback>B</AvatarFallback>
57
+ </Avatar>
58
+ <Avatar className="border-2 border-background">
59
+ <AvatarFallback>C</AvatarFallback>
60
+ </Avatar>
61
+ <Avatar className="border-2 border-background">
62
+ <AvatarFallback className="text-xs">+3</AvatarFallback>
63
+ </Avatar>
64
+ </div>
65
+ ),
66
+ };