@snowcone-app/ui 0.1.43 → 0.2.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.
- package/CHANGELOG.md +32 -0
- package/README.md +18 -4
- package/dist/index.cjs +5 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/package.json +9 -5
- package/src/components/CanvasIsolationBoundary.tsx +202 -0
- package/src/components/LoadingOverlayPrism.tsx +251 -0
- package/src/composed/AddToCart.tsx +229 -0
- package/src/composed/ArtAlignment.tsx +703 -0
- package/src/composed/ArtSelector.tsx +290 -0
- package/src/composed/ArtworkCustomizer.tsx +212 -0
- package/src/composed/CanvasEditor.tsx +79 -0
- package/src/composed/ColorPicker.tsx +111 -0
- package/src/composed/CurrentSelectionDisplay.tsx +86 -0
- package/src/composed/HeroProductImage.tsx +1079 -0
- package/src/composed/Lightbox.index.ts +2 -0
- package/src/composed/Lightbox.tsx +230 -0
- package/src/composed/PlacementClipShapeSelector.tsx +88 -0
- package/src/composed/PlacementTabs.tsx +179 -0
- package/src/composed/ProductCard.tsx +298 -0
- package/src/composed/ProductGallery.tsx +54 -0
- package/src/composed/ProductImage.tsx +129 -0
- package/src/composed/ProductList.tsx +147 -0
- package/src/composed/ProductOptions.tsx +305 -0
- package/src/composed/RealtimeMockup.tsx +121 -0
- package/src/composed/TileCount.tsx +348 -0
- package/src/composed/carousels/HeroCarousel.tsx +240 -0
- package/src/composed/carousels/MobileProductCarousel.tsx +1002 -0
- package/src/composed/carousels/index.ts +11 -0
- package/src/composed/carousels/types.ts +58 -0
- package/src/composed/grids/MasonryGrid.tsx +238 -0
- package/src/composed/grids/index.ts +9 -0
- package/src/composed/search/CurrentRefinements.tsx +80 -0
- package/src/composed/search/Filters.tsx +49 -0
- package/src/composed/search/FiltersButton.tsx +57 -0
- package/src/composed/search/FiltersDrawer.tsx +375 -0
- package/src/composed/search/ProductGrid.tsx +118 -0
- package/src/composed/search/ProductHit.tsx +56 -0
- package/src/composed/search/SearchBox.tsx +109 -0
- package/src/composed/search/SearchProvider.tsx +136 -0
- package/src/composed/search/facetConfig.ts +16 -0
- package/src/composed/search/index.ts +22 -0
- package/src/composed/search/meilisearchAdapter.ts +20 -0
- package/src/composed/search/types.ts +22 -0
- package/src/composed/zoom/EnhancedImageViewer.tsx +505 -0
- package/src/composed/zoom/ResponsiveZoom.tsx +134 -0
- package/src/composed/zoom/ZoomOverlay.tsx +194 -0
- package/src/composed/zoom/index.ts +12 -0
- package/src/composed/zoom/types.ts +12 -0
- package/src/design-system/ColorPalette.tsx +126 -0
- package/src/design-system/ColorSwatch.tsx +49 -0
- package/src/design-system/DesignSystemPage.tsx +130 -0
- package/src/design-system/ThemeSwitcher.tsx +181 -0
- package/src/design-system/TypographyScale.tsx +106 -0
- package/src/design-system/index.ts +5 -0
- package/src/ecommerce/stories/HeroProductImage.stories.tsx +66 -0
- package/src/ecommerce/stories/PDPHeroGallery.stories.tsx +105 -0
- package/src/ecommerce/stories/PDPInfoPanel.stories.tsx +472 -0
- package/src/ecommerce/stories/PDPLayout.stories.tsx +365 -0
- package/src/hooks/useBrand.ts +41 -0
- package/src/hooks/useCanvasContext.ts +127 -0
- package/src/hooks/useDeviceDetection.ts +64 -0
- package/src/hooks/useFocusTrap.ts +70 -0
- package/src/hooks/useImagePreloader.ts +268 -0
- package/src/hooks/useImageTransition.ts +608 -0
- package/src/hooks/usePlacementsProcessor.ts +74 -0
- package/src/hooks/useProductGallery.ts +193 -0
- package/src/hooks/useProductPage.ts +467 -0
- package/src/hooks/useRenderGuard.ts +96 -0
- package/src/hooks/useScrollDirection.ts +196 -0
- package/src/hooks/viewport/index.ts +25 -0
- package/src/hooks/viewport/useContainerWidth.ts +59 -0
- package/src/hooks/viewport/useMediaQuery.ts +52 -0
- package/src/hooks/viewport/useResponsiveImageCap.ts +149 -0
- package/src/hooks/viewport/useViewportDimensions.ts +135 -0
- package/src/hooks/viewport/useWideMonitorMode.ts +150 -0
- package/src/hooks/visibility/index.ts +15 -0
- package/src/hooks/visibility/observerPool.ts +150 -0
- package/src/index.ts +240 -0
- package/src/layouts/hero-zoom/HeroShrinkLayout.tsx +209 -0
- package/src/layouts/hero-zoom/HeroZoomLayout.tsx +351 -0
- package/src/layouts/hero-zoom/index.ts +30 -0
- package/src/layouts/hero-zoom/stories/HeroZoomLayout.stories.tsx +350 -0
- package/src/layouts/hero-zoom/types.ts +113 -0
- package/src/layouts/hero-zoom/useHeroZoomScales.ts +156 -0
- package/src/layouts/index.ts +9 -0
- package/src/layouts/pdp/EdgeBlurBox.tsx +210 -0
- package/src/layouts/pdp/ImageBlurExtension.tsx +215 -0
- package/src/layouts/pdp/ImageEdgeBlur.tsx +215 -0
- package/src/layouts/pdp/PDPLayout.tsx +246 -0
- package/src/layouts/pdp/SimpleImageBlur.tsx +140 -0
- package/src/layouts/pdp/index.ts +40 -0
- package/src/lib/env.ts +15 -0
- package/src/lib/locale.ts +167 -0
- package/src/lib/router.tsx +46 -0
- package/src/lib/utils.ts +6 -0
- package/src/lightbox/README.md +77 -0
- package/src/next/index.tsx +26 -0
- package/src/patterns/MockupPriorityProvider.tsx +1014 -0
- package/src/patterns/Product.tsx +850 -0
- package/src/patterns/ProductPageProvider.tsx +224 -0
- package/src/patterns/RealtimeProvider.tsx +1162 -0
- package/src/patterns/ShopProvider.tsx +603 -0
- package/src/personalization/PersonalizationBridge.tsx +235 -0
- package/src/personalization/PersonalizationContext.ts +29 -0
- package/src/personalization/PersonalizationInputs.tsx +110 -0
- package/src/personalization/PersonalizationProvider.tsx +407 -0
- package/src/personalization/canvas-stub.d.ts +22 -0
- package/src/personalization/index.ts +43 -0
- package/src/personalization/types.ts +48 -0
- package/src/personalization/usePersonalization.ts +32 -0
- package/src/personalization/usePersonalizationShimmer.ts +159 -0
- package/src/personalization/utils.ts +59 -0
- package/src/primitives/BrandLogo.tsx +65 -0
- package/src/primitives/BrandName.tsx +51 -0
- package/src/primitives/Button.tsx +123 -0
- package/src/primitives/ColorSwatch.tsx +221 -0
- package/src/primitives/DragHintAnimation.tsx +190 -0
- package/src/primitives/EdgeSwipeGuards.tsx +60 -0
- package/src/primitives/FloatingActionGroup.tsx +176 -0
- package/src/primitives/ProductPrice.tsx +171 -0
- package/src/primitives/ProgressiveBlur.tsx +295 -0
- package/src/primitives/ThemeToggle.tsx +125 -0
- package/src/primitives/__tests__/story-coverage.test.ts +98 -0
- package/src/primitives/accordion.tsx +280 -0
- package/src/primitives/badge.tsx +137 -0
- package/src/primitives/card.tsx +61 -0
- package/src/primitives/checkbox.tsx +56 -0
- package/src/primitives/collapsible.tsx +51 -0
- package/src/primitives/drawer.tsx +828 -0
- package/src/primitives/dropdown-menu.tsx +197 -0
- package/src/primitives/fieldset.tsx +73 -0
- package/src/primitives/index.ts +138 -0
- package/src/primitives/input.tsx +91 -0
- package/src/primitives/kbd.tsx +130 -0
- package/src/primitives/label.tsx +20 -0
- package/src/primitives/link.tsx +182 -0
- package/src/primitives/popover.tsx +80 -0
- package/src/primitives/radio-group.tsx +79 -0
- package/src/primitives/scroll-fade.tsx +159 -0
- package/src/primitives/select.tsx +170 -0
- package/src/primitives/separator.tsx +25 -0
- package/src/primitives/slider.tsx +221 -0
- package/src/primitives/spinner.tsx +72 -0
- package/src/primitives/stories/Accordion.stories.tsx +121 -0
- package/src/primitives/stories/Badge.stories.tsx +221 -0
- package/src/primitives/stories/Button.stories.tsx +185 -0
- package/src/primitives/stories/Card.stories.tsx +171 -0
- package/src/primitives/stories/Checkbox.stories.tsx +214 -0
- package/src/primitives/stories/Collapsible.stories.tsx +230 -0
- package/src/primitives/stories/Drawer.stories.tsx +378 -0
- package/src/primitives/stories/DropdownMenu.stories.tsx +182 -0
- package/src/primitives/stories/Fieldset.stories.tsx +212 -0
- package/src/primitives/stories/Input.stories.tsx +172 -0
- package/src/primitives/stories/Kbd.stories.tsx +183 -0
- package/src/primitives/stories/Label.stories.tsx +98 -0
- package/src/primitives/stories/Link.stories.tsx +260 -0
- package/src/primitives/stories/Popover.stories.tsx +178 -0
- package/src/primitives/stories/RadioGroup.stories.tsx +205 -0
- package/src/primitives/stories/Select.stories.tsx +222 -0
- package/src/primitives/stories/Separator.stories.tsx +134 -0
- package/src/primitives/stories/Slider.stories.tsx +203 -0
- package/src/primitives/stories/Spinner.stories.tsx +142 -0
- package/src/primitives/stories/Surface.stories.tsx +257 -0
- package/src/primitives/stories/Switch.stories.tsx +131 -0
- package/src/primitives/stories/Tabs.stories.tsx +275 -0
- package/src/primitives/stories/TextField.stories.tsx +139 -0
- package/src/primitives/stories/Textarea.stories.tsx +148 -0
- package/src/primitives/stories/Tooltip.stories.tsx +119 -0
- package/src/primitives/surface.tsx +86 -0
- package/src/primitives/switch.tsx +35 -0
- package/src/primitives/tabs.tsx +206 -0
- package/src/primitives/text-field.tsx +84 -0
- package/src/primitives/textarea.tsx +50 -0
- package/src/primitives/tooltip.tsx +58 -0
- package/src/services/CanvasExportService.ts +518 -0
- package/src/styles/base.css +380 -0
- package/src/styles/defaults.css +280 -0
- package/src/styles/globals.css +1242 -0
- package/src/styles/index.css +17 -0
- package/src/styles/ne-themes.css +4740 -0
- package/src/styles/tailwind.css +11 -0
- package/src/styles/tokens.css +117 -0
- package/src/styles/utilities.css +188 -0
- package/src/themes/apply-theme.ts +449 -0
- package/src/themes/getThemeStyles.ts +454 -0
- package/src/themes/index.ts +48 -0
- package/src/themes/oklch-theme.ts +283 -0
- package/src/themes/presets.ts +989 -0
- package/src/themes/types.ts +386 -0
- package/src/themes/useTheme.tsx +450 -0
- package/src/utils/dev-warnings.ts +161 -0
- package/src/utils/devWarnings.ts +153 -0
- 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'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'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'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'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
|
+
};
|