@snowcone-app/ui 0.1.42 → 0.2.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 (192) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +18 -4
  3. package/package.json +9 -5
  4. package/src/components/CanvasIsolationBoundary.tsx +202 -0
  5. package/src/components/LoadingOverlayPrism.tsx +251 -0
  6. package/src/composed/AddToCart.tsx +229 -0
  7. package/src/composed/ArtAlignment.tsx +703 -0
  8. package/src/composed/ArtSelector.tsx +290 -0
  9. package/src/composed/ArtworkCustomizer.tsx +212 -0
  10. package/src/composed/CanvasEditor.tsx +79 -0
  11. package/src/composed/ColorPicker.tsx +111 -0
  12. package/src/composed/CurrentSelectionDisplay.tsx +86 -0
  13. package/src/composed/HeroProductImage.tsx +1071 -0
  14. package/src/composed/Lightbox.index.ts +2 -0
  15. package/src/composed/Lightbox.tsx +230 -0
  16. package/src/composed/PlacementClipShapeSelector.tsx +88 -0
  17. package/src/composed/PlacementTabs.tsx +179 -0
  18. package/src/composed/ProductCard.tsx +298 -0
  19. package/src/composed/ProductGallery.tsx +54 -0
  20. package/src/composed/ProductImage.tsx +129 -0
  21. package/src/composed/ProductList.tsx +147 -0
  22. package/src/composed/ProductOptions.tsx +305 -0
  23. package/src/composed/RealtimeMockup.tsx +121 -0
  24. package/src/composed/TileCount.tsx +348 -0
  25. package/src/composed/carousels/HeroCarousel.tsx +240 -0
  26. package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
  27. package/src/composed/carousels/index.ts +11 -0
  28. package/src/composed/carousels/types.ts +58 -0
  29. package/src/composed/grids/MasonryGrid.tsx +238 -0
  30. package/src/composed/grids/index.ts +9 -0
  31. package/src/composed/search/CurrentRefinements.tsx +80 -0
  32. package/src/composed/search/Filters.tsx +49 -0
  33. package/src/composed/search/FiltersButton.tsx +57 -0
  34. package/src/composed/search/FiltersDrawer.tsx +375 -0
  35. package/src/composed/search/ProductGrid.tsx +118 -0
  36. package/src/composed/search/ProductHit.tsx +56 -0
  37. package/src/composed/search/SearchBox.tsx +109 -0
  38. package/src/composed/search/SearchProvider.tsx +136 -0
  39. package/src/composed/search/facetConfig.ts +16 -0
  40. package/src/composed/search/index.ts +22 -0
  41. package/src/composed/search/meilisearchAdapter.ts +20 -0
  42. package/src/composed/search/types.ts +22 -0
  43. package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
  44. package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
  45. package/src/composed/zoom/ZoomOverlay.tsx +194 -0
  46. package/src/composed/zoom/index.ts +12 -0
  47. package/src/composed/zoom/types.ts +12 -0
  48. package/src/design-system/ColorPalette.tsx +126 -0
  49. package/src/design-system/ColorSwatch.tsx +49 -0
  50. package/src/design-system/DesignSystemPage.tsx +130 -0
  51. package/src/design-system/ThemeSwitcher.tsx +181 -0
  52. package/src/design-system/TypographyScale.tsx +106 -0
  53. package/src/design-system/index.ts +5 -0
  54. package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
  55. package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
  56. package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
  57. package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
  58. package/src/hooks/useBrand.ts +41 -0
  59. package/src/hooks/useCanvasContext.ts +127 -0
  60. package/src/hooks/useDeviceDetection.ts +64 -0
  61. package/src/hooks/useFocusTrap.ts +70 -0
  62. package/src/hooks/useImagePreloader.ts +268 -0
  63. package/src/hooks/useImageTransition.ts +608 -0
  64. package/src/hooks/usePlacementsProcessor.ts +74 -0
  65. package/src/hooks/useProductGallery.ts +193 -0
  66. package/src/hooks/useProductPage.ts +467 -0
  67. package/src/hooks/useRenderGuard.ts +96 -0
  68. package/src/hooks/useScrollDirection.ts +196 -0
  69. package/src/hooks/viewport/index.ts +25 -0
  70. package/src/hooks/viewport/useContainerWidth.ts +59 -0
  71. package/src/hooks/viewport/useMediaQuery.ts +52 -0
  72. package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
  73. package/src/hooks/viewport/useViewportDimensions.ts +135 -0
  74. package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
  75. package/src/hooks/visibility/index.ts +15 -0
  76. package/src/hooks/visibility/observerPool.ts +150 -0
  77. package/src/index.ts +240 -0
  78. package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
  79. package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
  80. package/src/layouts/hero-zoom/index.ts +30 -0
  81. package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
  82. package/src/layouts/hero-zoom/types.ts +113 -0
  83. package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
  84. package/src/layouts/index.ts +9 -0
  85. package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
  86. package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
  87. package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
  88. package/src/layouts/pdp/PDPLayout.tsx +246 -0
  89. package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
  90. package/src/layouts/pdp/index.ts +40 -0
  91. package/src/lib/env.ts +15 -0
  92. package/src/lib/locale.ts +167 -0
  93. package/src/lib/router.tsx +46 -0
  94. package/src/lib/utils.ts +6 -0
  95. package/src/lightbox/README.md +77 -0
  96. package/src/next/index.tsx +26 -0
  97. package/src/patterns/MockupPriorityProvider.tsx +1014 -0
  98. package/src/patterns/Product.tsx +850 -0
  99. package/src/patterns/ProductPageProvider.tsx +224 -0
  100. package/src/patterns/RealtimeProvider.tsx +1162 -0
  101. package/src/patterns/ShopProvider.tsx +603 -0
  102. package/src/personalization/PersonalizationBridge.tsx +235 -0
  103. package/src/personalization/PersonalizationContext.ts +29 -0
  104. package/src/personalization/PersonalizationInputs.tsx +110 -0
  105. package/src/personalization/PersonalizationProvider.tsx +407 -0
  106. package/src/personalization/canvas-stub.d.ts +22 -0
  107. package/src/personalization/index.ts +43 -0
  108. package/src/personalization/types.ts +48 -0
  109. package/src/personalization/usePersonalization.ts +32 -0
  110. package/src/personalization/usePersonalizationShimmer.ts +159 -0
  111. package/src/personalization/utils.ts +59 -0
  112. package/src/primitives/BrandLogo.tsx +65 -0
  113. package/src/primitives/BrandName.tsx +51 -0
  114. package/src/primitives/Button.tsx +123 -0
  115. package/src/primitives/ColorSwatch.tsx +221 -0
  116. package/src/primitives/DragHintAnimation.tsx +190 -0
  117. package/src/primitives/EdgeSwipeGuards.tsx +60 -0
  118. package/src/primitives/FloatingActionGroup.tsx +176 -0
  119. package/src/primitives/ProductPrice.tsx +171 -0
  120. package/src/primitives/ProgressiveBlur.tsx +295 -0
  121. package/src/primitives/ThemeToggle.tsx +125 -0
  122. package/src/primitives/__tests__/story-coverage.test.ts +98 -0
  123. package/src/primitives/accordion.tsx +280 -0
  124. package/src/primitives/badge.tsx +137 -0
  125. package/src/primitives/card.tsx +61 -0
  126. package/src/primitives/checkbox.tsx +56 -0
  127. package/src/primitives/collapsible.tsx +51 -0
  128. package/src/primitives/drawer.tsx +828 -0
  129. package/src/primitives/dropdown-menu.tsx +197 -0
  130. package/src/primitives/fieldset.tsx +73 -0
  131. package/src/primitives/index.ts +138 -0
  132. package/src/primitives/input.tsx +91 -0
  133. package/src/primitives/kbd.tsx +130 -0
  134. package/src/primitives/label.tsx +20 -0
  135. package/src/primitives/link.tsx +182 -0
  136. package/src/primitives/popover.tsx +80 -0
  137. package/src/primitives/radio-group.tsx +79 -0
  138. package/src/primitives/scroll-fade.tsx +159 -0
  139. package/src/primitives/select.tsx +170 -0
  140. package/src/primitives/separator.tsx +25 -0
  141. package/src/primitives/slider.tsx +221 -0
  142. package/src/primitives/spinner.tsx +72 -0
  143. package/src/primitives/stories/Accordion.stories.tsx +121 -0
  144. package/src/primitives/stories/Badge.stories.tsx +221 -0
  145. package/src/primitives/stories/Button.stories.tsx +185 -0
  146. package/src/primitives/stories/Card.stories.tsx +171 -0
  147. package/src/primitives/stories/Checkbox.stories.tsx +214 -0
  148. package/src/primitives/stories/Collapsible.stories.tsx +230 -0
  149. package/src/primitives/stories/Drawer.stories.tsx +378 -0
  150. package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
  151. package/src/primitives/stories/Fieldset.stories.tsx +212 -0
  152. package/src/primitives/stories/Input.stories.tsx +172 -0
  153. package/src/primitives/stories/Kbd.stories.tsx +183 -0
  154. package/src/primitives/stories/Label.stories.tsx +98 -0
  155. package/src/primitives/stories/Link.stories.tsx +260 -0
  156. package/src/primitives/stories/Popover.stories.tsx +178 -0
  157. package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
  158. package/src/primitives/stories/Select.stories.tsx +222 -0
  159. package/src/primitives/stories/Separator.stories.tsx +134 -0
  160. package/src/primitives/stories/Slider.stories.tsx +203 -0
  161. package/src/primitives/stories/Spinner.stories.tsx +142 -0
  162. package/src/primitives/stories/Surface.stories.tsx +257 -0
  163. package/src/primitives/stories/Switch.stories.tsx +131 -0
  164. package/src/primitives/stories/Tabs.stories.tsx +275 -0
  165. package/src/primitives/stories/TextField.stories.tsx +139 -0
  166. package/src/primitives/stories/Textarea.stories.tsx +148 -0
  167. package/src/primitives/stories/Tooltip.stories.tsx +119 -0
  168. package/src/primitives/surface.tsx +86 -0
  169. package/src/primitives/switch.tsx +35 -0
  170. package/src/primitives/tabs.tsx +206 -0
  171. package/src/primitives/text-field.tsx +84 -0
  172. package/src/primitives/textarea.tsx +50 -0
  173. package/src/primitives/tooltip.tsx +58 -0
  174. package/src/services/CanvasExportService.ts +518 -0
  175. package/src/styles/base.css +380 -0
  176. package/src/styles/defaults.css +280 -0
  177. package/src/styles/globals.css +1242 -0
  178. package/src/styles/index.css +17 -0
  179. package/src/styles/ne-themes.css +4740 -0
  180. package/src/styles/tailwind.css +11 -0
  181. package/src/styles/tokens.css +117 -0
  182. package/src/styles/utilities.css +188 -0
  183. package/src/themes/apply-theme.ts +449 -0
  184. package/src/themes/getThemeStyles.ts +454 -0
  185. package/src/themes/index.ts +48 -0
  186. package/src/themes/oklch-theme.ts +283 -0
  187. package/src/themes/presets.ts +989 -0
  188. package/src/themes/types.ts +386 -0
  189. package/src/themes/useTheme.tsx +450 -0
  190. package/src/utils/dev-warnings.ts +161 -0
  191. package/src/utils/devWarnings.ts +153 -0
  192. package/dist/styles.css +0 -1
@@ -0,0 +1,230 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import { Icon } from '@iconify/react';
4
+ import {
5
+ Collapsible,
6
+ CollapsibleTrigger,
7
+ CollapsibleContent,
8
+ Disclosure,
9
+ DisclosureTrigger,
10
+ DisclosureContent,
11
+ } from '../collapsible';
12
+ import { Button } from '../button';
13
+
14
+ const meta: Meta<typeof Collapsible> = {
15
+ title: 'UI/Collapsible',
16
+ component: Collapsible,
17
+ parameters: {
18
+ layout: 'centered',
19
+ },
20
+ tags: ['autodocs'],
21
+ argTypes: {
22
+ open: {
23
+ control: 'boolean',
24
+ description: 'Open state',
25
+ },
26
+ disabled: {
27
+ control: 'boolean',
28
+ description: 'Disable the collapsible',
29
+ },
30
+ onOpenChange: {
31
+ control: false,
32
+ description: 'Callback when open state changes',
33
+ },
34
+ },
35
+ };
36
+
37
+ export default meta;
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Default: Story = {
41
+ render: () => (
42
+ <Collapsible className="w-[350px] space-y-2">
43
+ <div className="flex items-center justify-between space-x-4 px-4">
44
+ <h4 className="text-sm font-label">@radix-ui/collapsible</h4>
45
+ <CollapsibleTrigger asChild>
46
+ <Button variant="ghost" size="sm">
47
+ <Icon icon="gravity-ui:chevron-down" className="size-4" />
48
+ <span className="sr-only">Toggle</span>
49
+ </Button>
50
+ </CollapsibleTrigger>
51
+ </div>
52
+ <div className="rounded-md border border-divider px-4 py-2 text-sm">
53
+ @radix-ui/primitives
54
+ </div>
55
+ <CollapsibleContent className="space-y-2">
56
+ <div className="rounded-md border border-divider px-4 py-2 text-sm">
57
+ @radix-ui/colors
58
+ </div>
59
+ <div className="rounded-md border border-divider px-4 py-2 text-sm">
60
+ @stitches/react
61
+ </div>
62
+ </CollapsibleContent>
63
+ </Collapsible>
64
+ ),
65
+ };
66
+
67
+ export const Controlled: Story = {
68
+ render: () => {
69
+ const [open, setOpen] = useState(false);
70
+ return (
71
+ <div className="space-y-4">
72
+ <Collapsible open={open} onOpenChange={setOpen} className="w-[350px] space-y-2">
73
+ <div className="flex items-center justify-between space-x-4">
74
+ <h4 className="text-sm font-label">Controlled Collapsible</h4>
75
+ <CollapsibleTrigger asChild>
76
+ <Button variant="secondary" size="sm">
77
+ {open ? 'Close' : 'Open'}
78
+ </Button>
79
+ </CollapsibleTrigger>
80
+ </div>
81
+ <CollapsibleContent>
82
+ <div className="rounded-md border border-divider p-4 text-sm">
83
+ This content can be expanded and collapsed.
84
+ </div>
85
+ </CollapsibleContent>
86
+ </Collapsible>
87
+ <p className="text-sm text-muted-foreground">Open: {open.toString()}</p>
88
+ </div>
89
+ );
90
+ },
91
+ };
92
+
93
+ export const UsingDisclosureAlias: Story = {
94
+ render: () => {
95
+ const [open, setOpen] = useState(false);
96
+ return (
97
+ <div className="space-y-4">
98
+ <Disclosure
99
+ open={open}
100
+ onOpenChange={setOpen}
101
+ className="w-[350px] space-y-2"
102
+ >
103
+ <div className="flex items-center justify-between space-x-4">
104
+ <h4 className="text-sm font-label">Using Disclosure Component</h4>
105
+ <DisclosureTrigger asChild>
106
+ <Button variant="ghost" size="sm">
107
+ <Icon
108
+ icon="gravity-ui:chevron-down"
109
+ className={`size-4 transition-transform ${open ? 'rotate-180' : ''}`}
110
+ />
111
+ </Button>
112
+ </DisclosureTrigger>
113
+ </div>
114
+ <DisclosureContent>
115
+ <div className="rounded-md border border-divider p-4 text-sm">
116
+ Disclosure is an alias for Collapsible (same API).
117
+ </div>
118
+ </DisclosureContent>
119
+ </Disclosure>
120
+ <p className="text-sm text-muted-foreground">Open: {open.toString()}</p>
121
+ </div>
122
+ );
123
+ },
124
+ };
125
+
126
+ export const WithAnimation: Story = {
127
+ render: () => (
128
+ <Collapsible defaultOpen className="w-[350px]">
129
+ <CollapsibleTrigger asChild>
130
+ <Button variant="secondary" className="w-full justify-between">
131
+ Animated Content
132
+ <Icon icon="gravity-ui:chevron-down" className="size-4" />
133
+ </Button>
134
+ </CollapsibleTrigger>
135
+ <CollapsibleContent>
136
+ <div className="mt-2 rounded-md border border-divider p-4 space-y-2">
137
+ <p className="text-sm">This content slides in and out smoothly.</p>
138
+ <p className="text-sm text-muted-foreground">
139
+ The animation is defined in globals.css using the collapsible-up
140
+ and collapsible-down keyframes.
141
+ </p>
142
+ </div>
143
+ </CollapsibleContent>
144
+ </Collapsible>
145
+ ),
146
+ };
147
+
148
+ export const NestedCollapsibles: Story = {
149
+ render: () => (
150
+ <Collapsible defaultOpen className="w-[400px] space-y-2">
151
+ <CollapsibleTrigger asChild>
152
+ <Button variant="secondary" className="w-full justify-between">
153
+ Parent Section
154
+ <Icon icon="gravity-ui:chevron-down" className="size-4" />
155
+ </Button>
156
+ </CollapsibleTrigger>
157
+ <CollapsibleContent>
158
+ <div className="ml-4 space-y-2 border-l border-divider pl-4">
159
+ <Collapsible className="space-y-2">
160
+ <CollapsibleTrigger asChild>
161
+ <Button variant="ghost" size="sm" className="w-full justify-between">
162
+ Child Section 1
163
+ <Icon icon="gravity-ui:chevron-down" className="size-4" />
164
+ </Button>
165
+ </CollapsibleTrigger>
166
+ <CollapsibleContent>
167
+ <div className="rounded-md bg-muted p-3 text-sm">
168
+ Nested content 1
169
+ </div>
170
+ </CollapsibleContent>
171
+ </Collapsible>
172
+ <Collapsible className="space-y-2">
173
+ <CollapsibleTrigger asChild>
174
+ <Button variant="ghost" size="sm" className="w-full justify-between">
175
+ Child Section 2
176
+ <Icon icon="gravity-ui:chevron-down" className="size-4" />
177
+ </Button>
178
+ </CollapsibleTrigger>
179
+ <CollapsibleContent>
180
+ <div className="rounded-md bg-muted p-3 text-sm">
181
+ Nested content 2
182
+ </div>
183
+ </CollapsibleContent>
184
+ </Collapsible>
185
+ </div>
186
+ </CollapsibleContent>
187
+ </Collapsible>
188
+ ),
189
+ };
190
+
191
+ export const FAQ: Story = {
192
+ render: () => {
193
+ const faqs = [
194
+ {
195
+ question: 'Is it accessible?',
196
+ answer: 'Yes. It adheres to the WAI-ARIA design pattern.',
197
+ },
198
+ {
199
+ question: 'Is it styled?',
200
+ answer: 'Yes. It comes with default styles that match your theme.',
201
+ },
202
+ {
203
+ question: 'Is it animated?',
204
+ answer: 'Yes. It has smooth expand/collapse animations.',
205
+ },
206
+ ];
207
+
208
+ return (
209
+ <div className="w-[400px] space-y-2">
210
+ <h3 className="text-lg mb-4 font-heading">Frequently Asked Questions</h3>
211
+ {faqs.map((faq, index) => (
212
+ <Collapsible key={index} className="border border-divider rounded-lg">
213
+ <CollapsibleTrigger asChild>
214
+ <Button
215
+ variant="ghost"
216
+ className="w-full justify-between px-4 py-3 h-auto font-label"
217
+ >
218
+ {faq.question}
219
+ <Icon icon="gravity-ui:plus" className="size-4" />
220
+ </Button>
221
+ </CollapsibleTrigger>
222
+ <CollapsibleContent>
223
+ <div className="px-4 pb-3 text-sm text-muted-foreground">{faq.answer}</div>
224
+ </CollapsibleContent>
225
+ </Collapsible>
226
+ ))}
227
+ </div>
228
+ );
229
+ },
230
+ };
@@ -0,0 +1,378 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useState } from 'react';
3
+ import {
4
+ Drawer,
5
+ DrawerTrigger,
6
+ DrawerContent,
7
+ DrawerHeader,
8
+ DrawerFooter,
9
+ DrawerTitle,
10
+ DrawerDescription,
11
+ DrawerClose,
12
+ } from '../drawer';
13
+ import { Button } from '../button';
14
+ import { Input } from '../input';
15
+ import { Textarea } from '../textarea';
16
+ import { Label } from '../label';
17
+
18
+ const meta = {
19
+ title: 'UI/Drawer',
20
+ component: Drawer,
21
+ parameters: {
22
+ layout: 'centered',
23
+ docs: {
24
+ description: {
25
+ component: `
26
+ A mobile-optimized drawer component that works correctly on all platforms including iOS Safari.
27
+
28
+ ## Key Features
29
+ - CSS-based animations for smooth, native-feeling performance
30
+ - Backdrop blur overlay by default for modern iOS 26+ appearance
31
+ - Automatic safe-area handling (no explicit props needed)
32
+ - Native swipe-to-dismiss gesture support
33
+ - Works with scrollable page content
34
+ - No dependency on Vaul or other drawer libraries
35
+
36
+ ## Why This Exists
37
+ The Vaul drawer library causes viewport issues on iOS Safari 26, leaving a white gap at the bottom of the screen after the drawer closes. This component avoids those issues by using proper positioning and CSS animations.
38
+ `,
39
+ },
40
+ },
41
+ },
42
+ tags: ['autodocs'],
43
+ } satisfies Meta<typeof Drawer>;
44
+
45
+ export default meta;
46
+ type Story = StoryObj<typeof Drawer>;
47
+
48
+ export const Default: Story = {
49
+ render: () => (
50
+ <Drawer>
51
+ <DrawerTrigger asChild>
52
+ <Button variant="secondary">Open Drawer</Button>
53
+ </DrawerTrigger>
54
+ <DrawerContent>
55
+ <DrawerHeader>
56
+ <DrawerTitle>Edit Profile</DrawerTitle>
57
+ <DrawerDescription>
58
+ Make changes to your profile here. Click save when you&apos;re done.
59
+ </DrawerDescription>
60
+ </DrawerHeader>
61
+ <div className="p-4 space-y-4">
62
+ <div className="space-y-2">
63
+ <Label htmlFor="name">Name</Label>
64
+ <Input id="name" placeholder="Enter your name" />
65
+ </div>
66
+ <div className="space-y-2">
67
+ <Label htmlFor="username">Username</Label>
68
+ <Input id="username" placeholder="@username" />
69
+ </div>
70
+ </div>
71
+ <DrawerFooter>
72
+ <Button>Save changes</Button>
73
+ <DrawerClose asChild>
74
+ <Button variant="secondary">Cancel</Button>
75
+ </DrawerClose>
76
+ </DrawerFooter>
77
+ </DrawerContent>
78
+ </Drawer>
79
+ ),
80
+ };
81
+
82
+ export const WithScroll: Story = {
83
+ render: () => (
84
+ <Drawer>
85
+ <DrawerTrigger asChild>
86
+ <Button variant="secondary">Terms & Conditions</Button>
87
+ </DrawerTrigger>
88
+ <DrawerContent>
89
+ <DrawerHeader>
90
+ <DrawerTitle>Terms of Service</DrawerTitle>
91
+ <DrawerDescription>Please read our terms carefully.</DrawerDescription>
92
+ </DrawerHeader>
93
+ <div className="p-4">
94
+ <div className="space-y-4 text-sm text-muted-foreground">
95
+ {Array.from({ length: 20 }).map((_, i) => (
96
+ <p key={i}>
97
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
98
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip
99
+ ex ea commodo consequat.
100
+ </p>
101
+ ))}
102
+ </div>
103
+ </div>
104
+ <DrawerFooter>
105
+ <Button>I Accept</Button>
106
+ <DrawerClose asChild>
107
+ <Button variant="secondary">Decline</Button>
108
+ </DrawerClose>
109
+ </DrawerFooter>
110
+ </DrawerContent>
111
+ </Drawer>
112
+ ),
113
+ };
114
+
115
+ export const ContactForm: Story = {
116
+ render: () => (
117
+ <Drawer>
118
+ <DrawerTrigger asChild>
119
+ <Button>Contact Us</Button>
120
+ </DrawerTrigger>
121
+ <DrawerContent>
122
+ <DrawerHeader>
123
+ <DrawerTitle>Get in Touch</DrawerTitle>
124
+ <DrawerDescription>
125
+ Fill out the form below and we&apos;ll get back to you as soon as possible.
126
+ </DrawerDescription>
127
+ </DrawerHeader>
128
+ <div className="p-4 space-y-4">
129
+ <div className="grid grid-cols-2 gap-4">
130
+ <div className="space-y-2">
131
+ <Label htmlFor="firstName">First Name</Label>
132
+ <Input id="firstName" placeholder="John" />
133
+ </div>
134
+ <div className="space-y-2">
135
+ <Label htmlFor="lastName">Last Name</Label>
136
+ <Input id="lastName" placeholder="Doe" />
137
+ </div>
138
+ </div>
139
+ <div className="space-y-2">
140
+ <Label htmlFor="email">Email</Label>
141
+ <Input id="email" type="email" placeholder="john@example.com" />
142
+ </div>
143
+ <div className="space-y-2">
144
+ <Label htmlFor="message">Message</Label>
145
+ <Textarea id="message" placeholder="How can we help?" />
146
+ </div>
147
+ </div>
148
+ <DrawerFooter>
149
+ <Button className="w-full">Send Message</Button>
150
+ </DrawerFooter>
151
+ </DrawerContent>
152
+ </Drawer>
153
+ ),
154
+ };
155
+
156
+ export const SimpleConfirmation: Story = {
157
+ render: () => (
158
+ <Drawer>
159
+ <DrawerTrigger asChild>
160
+ <Button variant="destructive">Delete Account</Button>
161
+ </DrawerTrigger>
162
+ <DrawerContent maxHeight="40dvh">
163
+ <DrawerHeader className="text-center">
164
+ <DrawerTitle>Are you sure?</DrawerTitle>
165
+ <DrawerDescription>
166
+ This action cannot be undone. This will permanently delete your account and remove your data from our
167
+ servers.
168
+ </DrawerDescription>
169
+ </DrawerHeader>
170
+ <DrawerFooter className="flex-row justify-center gap-2">
171
+ <DrawerClose asChild>
172
+ <Button variant="secondary">Cancel</Button>
173
+ </DrawerClose>
174
+ <Button variant="destructive">Delete Account</Button>
175
+ </DrawerFooter>
176
+ </DrawerContent>
177
+ </Drawer>
178
+ ),
179
+ };
180
+
181
+ export const TallDrawer: Story = {
182
+ render: () => (
183
+ <Drawer>
184
+ <DrawerTrigger asChild>
185
+ <Button variant="secondary">Open Tall Drawer (90%)</Button>
186
+ </DrawerTrigger>
187
+ <DrawerContent maxHeight="90dvh">
188
+ <DrawerHeader>
189
+ <DrawerTitle>Full Screen Experience</DrawerTitle>
190
+ <DrawerDescription>This drawer takes up 90% of the viewport height.</DrawerDescription>
191
+ </DrawerHeader>
192
+ <div className="p-4 flex-1">
193
+ <div className="h-full flex items-center justify-center text-muted-foreground">
194
+ Lots of space for content here
195
+ </div>
196
+ </div>
197
+ <DrawerFooter>
198
+ <DrawerClose asChild>
199
+ <Button variant="secondary" className="w-full">
200
+ Close
201
+ </Button>
202
+ </DrawerClose>
203
+ </DrawerFooter>
204
+ </DrawerContent>
205
+ </Drawer>
206
+ ),
207
+ };
208
+
209
+ export const HiddenHandle: Story = {
210
+ render: () => (
211
+ <Drawer>
212
+ <DrawerTrigger asChild>
213
+ <Button variant="secondary">Open (No Handle)</Button>
214
+ </DrawerTrigger>
215
+ <DrawerContent hideHandle>
216
+ <DrawerHeader>
217
+ <DrawerTitle>Clean Look</DrawerTitle>
218
+ <DrawerDescription>
219
+ This drawer has no visible handle. Swipe gestures still work for dismissal.
220
+ </DrawerDescription>
221
+ </DrawerHeader>
222
+ <div className="p-4">
223
+ <p className="text-sm text-muted-foreground">
224
+ You can still swipe down to dismiss this drawer, even though the handle is hidden.
225
+ </p>
226
+ </div>
227
+ <DrawerFooter>
228
+ <DrawerClose asChild>
229
+ <Button className="w-full">Got it</Button>
230
+ </DrawerClose>
231
+ </DrawerFooter>
232
+ </DrawerContent>
233
+ </Drawer>
234
+ ),
235
+ };
236
+
237
+ export const Controlled: Story = {
238
+ render: function ControlledDrawer() {
239
+ const [open, setOpen] = useState(false);
240
+
241
+ return (
242
+ <div className="flex flex-col gap-4 items-center">
243
+ <p className="text-sm text-muted-foreground">Drawer is {open ? 'open' : 'closed'}</p>
244
+ <Drawer open={open} onOpenChange={setOpen}>
245
+ <DrawerTrigger asChild>
246
+ <Button>Open Controlled Drawer</Button>
247
+ </DrawerTrigger>
248
+ <DrawerContent>
249
+ <DrawerHeader>
250
+ <DrawerTitle>Controlled Drawer</DrawerTitle>
251
+ <DrawerDescription>This drawer&apos;s state is controlled externally.</DrawerDescription>
252
+ </DrawerHeader>
253
+ <div className="p-4">
254
+ <p className="text-sm text-muted-foreground">
255
+ The parent component controls whether this drawer is open or closed. This is useful for programmatic
256
+ control.
257
+ </p>
258
+ </div>
259
+ <DrawerFooter>
260
+ <Button onClick={() => setOpen(false)}>Close Programmatically</Button>
261
+ <DrawerClose asChild>
262
+ <Button variant="secondary">Close via Context</Button>
263
+ </DrawerClose>
264
+ </DrawerFooter>
265
+ </DrawerContent>
266
+ </Drawer>
267
+ </div>
268
+ );
269
+ },
270
+ };
271
+
272
+ export const NoOverlay: Story = {
273
+ render: () => (
274
+ <div className="p-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg">
275
+ <Drawer>
276
+ <DrawerTrigger asChild>
277
+ <Button variant="secondary">Open (No Overlay)</Button>
278
+ </DrawerTrigger>
279
+ <DrawerContent showOverlay={false}>
280
+ <DrawerHeader>
281
+ <DrawerTitle>No Overlay</DrawerTitle>
282
+ <DrawerDescription>The background is visible behind this drawer.</DrawerDescription>
283
+ </DrawerHeader>
284
+ <div className="p-4">
285
+ <p className="text-sm text-muted-foreground">
286
+ Notice that there&apos;s no dark overlay. The colorful background is still visible.
287
+ </p>
288
+ </div>
289
+ <DrawerFooter>
290
+ <DrawerClose asChild>
291
+ <Button className="w-full">Close</Button>
292
+ </DrawerClose>
293
+ </DrawerFooter>
294
+ </DrawerContent>
295
+ </Drawer>
296
+ </div>
297
+ ),
298
+ };
299
+
300
+ export const SwipeToTest: Story = {
301
+ render: () => (
302
+ <div className="text-center space-y-4">
303
+ <p className="text-sm text-muted-foreground max-w-sm">
304
+ On touch devices, you can swipe down on the drawer to dismiss it. Try it on iOS Safari!
305
+ </p>
306
+ <Drawer>
307
+ <DrawerTrigger asChild>
308
+ <Button>Test Swipe Gesture</Button>
309
+ </DrawerTrigger>
310
+ <DrawerContent>
311
+ <DrawerHeader>
312
+ <DrawerTitle>Swipe Me Down!</DrawerTitle>
313
+ <DrawerDescription>
314
+ Touch and drag downward anywhere on this drawer to dismiss it.
315
+ </DrawerDescription>
316
+ </DrawerHeader>
317
+ <div className="p-4 space-y-4">
318
+ {Array.from({ length: 8 }).map((_, i) => (
319
+ <div key={i} className="p-4 bg-muted rounded-lg">
320
+ Item {i + 1}
321
+ </div>
322
+ ))}
323
+ </div>
324
+ </DrawerContent>
325
+ </Drawer>
326
+ </div>
327
+ ),
328
+ };
329
+
330
+ export const CustomBackground: Story = {
331
+ render: () => (
332
+ <div className="p-8 bg-gradient-to-br from-blue-400 via-teal-300 to-green-300 rounded-lg min-h-[200px]">
333
+ <div className="text-center space-y-4">
334
+ <p className="text-sm text-white/80 max-w-sm mx-auto">
335
+ Custom colored drawer with blur overlay. The drawer background extends
336
+ naturally into the safe areas - no configuration needed.
337
+ </p>
338
+ <Drawer>
339
+ <DrawerTrigger asChild>
340
+ <Button className="bg-white/90 text-emerald-700 hover:bg-white">
341
+ Open Custom Drawer
342
+ </Button>
343
+ </DrawerTrigger>
344
+ <DrawerContent className="bg-emerald-500" scrollFadeColor="#22c55e">
345
+ <DrawerTitle className="sr-only">Custom Styled Drawer</DrawerTitle>
346
+ <div className="px-6 pb-4">
347
+ <h2 className="text-xl font-bold text-white">Custom Background</h2>
348
+ <p className="text-emerald-100 text-sm mt-1">
349
+ Blur overlay + safe areas work automatically
350
+ </p>
351
+ </div>
352
+ <div className="px-6">
353
+ <div className="bg-emerald-400 border border-emerald-300 rounded-lg p-3 mb-4">
354
+ <p className="text-white text-sm">
355
+ Notice the blurred gradient visible through the overlay above.
356
+ Safe area at bottom matches the drawer background.
357
+ </p>
358
+ </div>
359
+ <div className="space-y-3 pb-4">
360
+ {Array.from({ length: 10 }).map((_, i) => (
361
+ <div
362
+ key={i}
363
+ className="p-4 bg-emerald-400 rounded-lg border border-emerald-300"
364
+ >
365
+ <div className="font-medium text-white">Item {i + 1}</div>
366
+ <div className="text-sm text-emerald-100">
367
+ Content with custom colored background
368
+ </div>
369
+ </div>
370
+ ))}
371
+ </div>
372
+ </div>
373
+ </DrawerContent>
374
+ </Drawer>
375
+ </div>
376
+ </div>
377
+ ),
378
+ };