@djangocfg/ui-core 2.1.381 → 2.1.383

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 (78) hide show
  1. package/README.md +85 -21
  2. package/package.json +5 -12
  3. package/src/components/boundary/Boundary.tsx +204 -33
  4. package/src/components/boundary/README.md +249 -0
  5. package/src/components/boundary/index.ts +9 -2
  6. package/src/components/index.ts +9 -2
  7. package/src/components/select/combobox.tsx +47 -19
  8. package/src/hooks/audio/createSoundBus.ts +172 -0
  9. package/src/hooks/audio/index.ts +21 -0
  10. package/src/hooks/audio/useAudioPrefs.ts +91 -0
  11. package/src/hooks/audio/useNotificationSounds.ts +271 -0
  12. package/src/hooks/audio/useSoundEffect.ts +78 -0
  13. package/src/hooks/hotkey/formatHotkey.ts +96 -0
  14. package/src/hooks/hotkey/index.ts +10 -0
  15. package/src/hooks/hotkey/useHotkey.ts +106 -34
  16. package/src/hooks/hotkey/useHotkeyChord.ts +96 -0
  17. package/src/hooks/hotkey/useHotkeyHelp.ts +68 -0
  18. package/src/hooks/index.ts +1 -0
  19. package/src/components/boundary/boundary.story.tsx +0 -109
  20. package/src/components/data/avatar/avatar.story.tsx +0 -115
  21. package/src/components/data/badge/badge.story.tsx +0 -56
  22. package/src/components/data/calendar/calendar.story.tsx +0 -127
  23. package/src/components/data/carousel/carousel.story.tsx +0 -122
  24. package/src/components/data/progress/progress.story.tsx +0 -97
  25. package/src/components/data/table/table.story.tsx +0 -148
  26. package/src/components/data/toggle/toggle.story.tsx +0 -104
  27. package/src/components/data/toggle-group/toggle-group.story.tsx +0 -118
  28. package/src/components/feedback/alert/alert.story.tsx +0 -77
  29. package/src/components/feedback/empty/empty.story.tsx +0 -115
  30. package/src/components/feedback/preloader/preloader.story.tsx +0 -86
  31. package/src/components/feedback/spinner/spinner.story.tsx +0 -66
  32. package/src/components/forms/button/button.story.tsx +0 -116
  33. package/src/components/forms/button-download/button-download.story.tsx +0 -112
  34. package/src/components/forms/button-group/button-group.story.tsx +0 -79
  35. package/src/components/forms/checkbox/checkbox.story.tsx +0 -89
  36. package/src/components/forms/input/input.story.tsx +0 -77
  37. package/src/components/forms/input-group/input-group.story.tsx +0 -119
  38. package/src/components/forms/input-otp/input-otp.story.tsx +0 -105
  39. package/src/components/forms/label/label.story.tsx +0 -52
  40. package/src/components/forms/radio-group/radio-group.story.tsx +0 -113
  41. package/src/components/forms/slider/slider.story.tsx +0 -134
  42. package/src/components/forms/switch/switch.story.tsx +0 -98
  43. package/src/components/forms/textarea/textarea.story.tsx +0 -94
  44. package/src/components/layout/aspect-ratio/aspect-ratio.story.tsx +0 -94
  45. package/src/components/layout/card/card.story.tsx +0 -105
  46. package/src/components/layout/resizable/resizable.story.tsx +0 -119
  47. package/src/components/layout/scroll-area/scroll-area.story.tsx +0 -172
  48. package/src/components/layout/separator/separator.story.tsx +0 -69
  49. package/src/components/layout/skeleton/skeleton.story.tsx +0 -101
  50. package/src/components/navigation/accordion/accordion.story.tsx +0 -110
  51. package/src/components/navigation/collapsible/collapsible.story.tsx +0 -133
  52. package/src/components/navigation/command/command.story.tsx +0 -121
  53. package/src/components/navigation/context-menu/context-menu.story.tsx +0 -125
  54. package/src/components/navigation/dropdown-menu/dropdown-menu.story.tsx +0 -208
  55. package/src/components/navigation/menubar/menubar.story.tsx +0 -152
  56. package/src/components/navigation/navigation-menu/navigation-menu.story.tsx +0 -154
  57. package/src/components/navigation/tabs/tabs.story.tsx +0 -98
  58. package/src/components/overlay/alert-dialog/alert-dialog.story.tsx +0 -104
  59. package/src/components/overlay/dialog/dialog.story.tsx +0 -212
  60. package/src/components/overlay/drawer/drawer.story.tsx +0 -359
  61. package/src/components/overlay/hover-card/hover-card.story.tsx +0 -102
  62. package/src/components/overlay/popover/popover.story.tsx +0 -127
  63. package/src/components/overlay/responsive-sheet/responsive-sheet.story.tsx +0 -117
  64. package/src/components/overlay/sheet/sheet.story.tsx +0 -148
  65. package/src/components/overlay/tooltip/tooltip.story.tsx +0 -139
  66. package/src/components/select/combobox-async.story.tsx +0 -215
  67. package/src/components/select/combobox.story.tsx +0 -226
  68. package/src/components/select/country-select.story.tsx +0 -261
  69. package/src/components/select/language-select.story.tsx +0 -264
  70. package/src/components/select/multi-select.story.tsx +0 -122
  71. package/src/components/select/select.story.tsx +0 -112
  72. package/src/components/specialized/copy/copy.story.tsx +0 -77
  73. package/src/components/specialized/flag/flag.story.tsx +0 -82
  74. package/src/components/specialized/image-with-fallback/image-with-fallback.story.tsx +0 -105
  75. package/src/components/specialized/kbd/kbd.story.tsx +0 -113
  76. package/src/lib/dialog-service/dialog-service.story.tsx +0 -263
  77. package/src/stories/index.ts +0 -28
  78. package/src/styles/theme/theme-tokens.story.tsx +0 -157
@@ -1,104 +0,0 @@
1
- import { defineStory } from '@djangocfg/playground';
2
- import {
3
- AlertDialog,
4
- AlertDialogTrigger,
5
- AlertDialogContent,
6
- AlertDialogHeader,
7
- AlertDialogFooter,
8
- AlertDialogTitle,
9
- AlertDialogDescription,
10
- AlertDialogAction,
11
- AlertDialogCancel,
12
- } from '.';
13
- import { Button } from '../../forms/button';
14
-
15
- export default defineStory({
16
- title: 'Core/Alert Dialog',
17
- component: AlertDialog,
18
- description: 'Modal dialog for important confirmations.',
19
- });
20
-
21
- export const Default = () => (
22
- <AlertDialog>
23
- <AlertDialogTrigger asChild>
24
- <Button variant="outline">Show Dialog</Button>
25
- </AlertDialogTrigger>
26
- <AlertDialogContent>
27
- <AlertDialogHeader>
28
- <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
29
- <AlertDialogDescription>
30
- This action cannot be undone. This will permanently delete your
31
- account and remove your data from our servers.
32
- </AlertDialogDescription>
33
- </AlertDialogHeader>
34
- <AlertDialogFooter>
35
- <AlertDialogCancel>Cancel</AlertDialogCancel>
36
- <AlertDialogAction>Continue</AlertDialogAction>
37
- </AlertDialogFooter>
38
- </AlertDialogContent>
39
- </AlertDialog>
40
- );
41
-
42
- export const Destructive = () => (
43
- <AlertDialog>
44
- <AlertDialogTrigger asChild>
45
- <Button variant="destructive">Delete Account</Button>
46
- </AlertDialogTrigger>
47
- <AlertDialogContent>
48
- <AlertDialogHeader>
49
- <AlertDialogTitle>Delete Account</AlertDialogTitle>
50
- <AlertDialogDescription>
51
- Are you sure you want to delete your account? All of your data will be
52
- permanently removed. This action cannot be undone.
53
- </AlertDialogDescription>
54
- </AlertDialogHeader>
55
- <AlertDialogFooter>
56
- <AlertDialogCancel>Cancel</AlertDialogCancel>
57
- <AlertDialogAction className="bg-destructive text-destructive-foreground hover:bg-destructive/90">
58
- Delete
59
- </AlertDialogAction>
60
- </AlertDialogFooter>
61
- </AlertDialogContent>
62
- </AlertDialog>
63
- );
64
-
65
- export const SaveChanges = () => (
66
- <AlertDialog>
67
- <AlertDialogTrigger asChild>
68
- <Button>Leave Page</Button>
69
- </AlertDialogTrigger>
70
- <AlertDialogContent>
71
- <AlertDialogHeader>
72
- <AlertDialogTitle>Unsaved Changes</AlertDialogTitle>
73
- <AlertDialogDescription>
74
- You have unsaved changes. Are you sure you want to leave? Your changes
75
- will be lost.
76
- </AlertDialogDescription>
77
- </AlertDialogHeader>
78
- <AlertDialogFooter>
79
- <AlertDialogCancel>Stay</AlertDialogCancel>
80
- <AlertDialogAction>Leave</AlertDialogAction>
81
- </AlertDialogFooter>
82
- </AlertDialogContent>
83
- </AlertDialog>
84
- );
85
-
86
- export const Logout = () => (
87
- <AlertDialog>
88
- <AlertDialogTrigger asChild>
89
- <Button variant="ghost">Log Out</Button>
90
- </AlertDialogTrigger>
91
- <AlertDialogContent>
92
- <AlertDialogHeader>
93
- <AlertDialogTitle>Log out</AlertDialogTitle>
94
- <AlertDialogDescription>
95
- Are you sure you want to log out of your account?
96
- </AlertDialogDescription>
97
- </AlertDialogHeader>
98
- <AlertDialogFooter>
99
- <AlertDialogCancel>Cancel</AlertDialogCancel>
100
- <AlertDialogAction>Log Out</AlertDialogAction>
101
- </AlertDialogFooter>
102
- </AlertDialogContent>
103
- </AlertDialog>
104
- );
@@ -1,212 +0,0 @@
1
- import { ArrowLeft, X } from 'lucide-react';
2
- import { defineStory } from '@djangocfg/playground';
3
- import {
4
- Dialog,
5
- DialogTrigger,
6
- DialogContent,
7
- DialogHeader,
8
- DialogFooter,
9
- DialogTitle,
10
- DialogDescription,
11
- DialogClose,
12
- } from '.';
13
- import { Button } from '../../forms/button';
14
- import { Input } from '../../forms/input';
15
- import { Label } from '../../forms/label';
16
-
17
- export default defineStory({
18
- title: 'Core/Dialog',
19
- component: Dialog,
20
- description: 'Modal dialog for focused interactions.',
21
- });
22
-
23
- export const Default = () => (
24
- <Dialog>
25
- <DialogTrigger asChild>
26
- <Button variant="outline">Open Dialog</Button>
27
- </DialogTrigger>
28
- <DialogContent>
29
- <DialogHeader>
30
- <DialogTitle>Dialog Title</DialogTitle>
31
- <DialogDescription>
32
- This is a dialog description. It explains what this dialog is about.
33
- </DialogDescription>
34
- </DialogHeader>
35
- <div className="py-4">
36
- <p>Dialog content goes here.</p>
37
- </div>
38
- <DialogFooter>
39
- <DialogClose asChild>
40
- <Button variant="outline">Cancel</Button>
41
- </DialogClose>
42
- <Button>Confirm</Button>
43
- </DialogFooter>
44
- </DialogContent>
45
- </Dialog>
46
- );
47
-
48
- export const EditProfile = () => (
49
- <Dialog>
50
- <DialogTrigger asChild>
51
- <Button>Edit Profile</Button>
52
- </DialogTrigger>
53
- <DialogContent className="sm:max-w-md">
54
- <DialogHeader>
55
- <DialogTitle>Edit Profile</DialogTitle>
56
- <DialogDescription>
57
- Make changes to your profile here. Click save when you're done.
58
- </DialogDescription>
59
- </DialogHeader>
60
- <div className="space-y-4 py-4">
61
- <div className="space-y-2">
62
- <Label htmlFor="name">Name</Label>
63
- <Input id="name" defaultValue="John Doe" />
64
- </div>
65
- <div className="space-y-2">
66
- <Label htmlFor="email">Email</Label>
67
- <Input id="email" type="email" defaultValue="john@example.com" />
68
- </div>
69
- </div>
70
- <DialogFooter>
71
- <DialogClose asChild>
72
- <Button variant="outline">Cancel</Button>
73
- </DialogClose>
74
- <Button>Save Changes</Button>
75
- </DialogFooter>
76
- </DialogContent>
77
- </Dialog>
78
- );
79
-
80
- export const Confirmation = () => (
81
- <Dialog>
82
- <DialogTrigger asChild>
83
- <Button variant="destructive">Delete Account</Button>
84
- </DialogTrigger>
85
- <DialogContent>
86
- <DialogHeader>
87
- <DialogTitle>Are you sure?</DialogTitle>
88
- <DialogDescription>
89
- This action cannot be undone. This will permanently delete your account
90
- and remove all your data from our servers.
91
- </DialogDescription>
92
- </DialogHeader>
93
- <DialogFooter>
94
- <DialogClose asChild>
95
- <Button variant="outline">Cancel</Button>
96
- </DialogClose>
97
- <Button variant="destructive">Delete Account</Button>
98
- </DialogFooter>
99
- </DialogContent>
100
- </Dialog>
101
- );
102
-
103
- export const Fullscreen = () => (
104
- <Dialog>
105
- <DialogTrigger asChild>
106
- <Button variant="outline">Open fullscreen dialog</Button>
107
- </DialogTrigger>
108
- <DialogContent fullscreen>
109
- <div className="mx-auto flex h-full w-full max-w-3xl flex-col px-4 py-10 sm:px-6 lg:px-8">
110
- <DialogTitle className="text-2xl font-semibold tracking-tight">
111
- Fullscreen layout
112
- </DialogTitle>
113
- <DialogDescription className="mt-1 text-muted-foreground">
114
- `fullscreen` removes the centred-card chrome and stretches the content to
115
- the viewport. Build your own header / footer inside.
116
- </DialogDescription>
117
- <div className="mt-6 flex-1 rounded-xl border border-dashed border-border/60 bg-muted/30 p-6 text-sm text-muted-foreground">
118
- Slot for a custom layout — gallery, onboarding, command palette, etc.
119
- </div>
120
- </div>
121
- </DialogContent>
122
- </Dialog>
123
- );
124
-
125
- export const CustomCloseButton = () => (
126
- <Dialog>
127
- <DialogTrigger asChild>
128
- <Button variant="outline">Open with pill close</Button>
129
- </DialogTrigger>
130
- <DialogContent
131
- fullscreen
132
- closeButton={
133
- <DialogClose
134
- aria-label="Close"
135
- className="absolute right-4 top-4 inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-full border border-border/60 bg-background/90 text-muted-foreground shadow-sm transition-colors hover:bg-accent hover:text-foreground"
136
- >
137
- <X className="h-4 w-4" />
138
- </DialogClose>
139
- }
140
- >
141
- <div className="mx-auto flex h-full w-full max-w-2xl flex-col px-4 py-10">
142
- <DialogTitle>Custom close affordance</DialogTitle>
143
- <DialogDescription className="mt-1 text-muted-foreground">
144
- Pass `closeButton` to override the default Radix `X`. Pass `false` to
145
- drop the affordance entirely.
146
- </DialogDescription>
147
- </div>
148
- </DialogContent>
149
- </Dialog>
150
- );
151
-
152
- export const NoCloseButton = () => (
153
- <Dialog>
154
- <DialogTrigger asChild>
155
- <Button variant="outline">Modal step (no close)</Button>
156
- </DialogTrigger>
157
- <DialogContent closeButton={false}>
158
- <DialogHeader>
159
- <DialogTitle>Saving…</DialogTitle>
160
- <DialogDescription>
161
- The dialog has no close affordance — dismissal is driven by the action
162
- buttons below or programmatically.
163
- </DialogDescription>
164
- </DialogHeader>
165
- <DialogFooter>
166
- <DialogClose asChild>
167
- <Button variant="outline">
168
- <ArrowLeft className="mr-1.5 h-3.5 w-3.5" />
169
- Back
170
- </Button>
171
- </DialogClose>
172
- </DialogFooter>
173
- </DialogContent>
174
- </Dialog>
175
- );
176
-
177
- export const LongContent = () => (
178
- <Dialog>
179
- <DialogTrigger asChild>
180
- <Button variant="outline">Terms of Service</Button>
181
- </DialogTrigger>
182
- <DialogContent className="max-h-[80vh] overflow-y-auto">
183
- <DialogHeader>
184
- <DialogTitle>Terms of Service</DialogTitle>
185
- </DialogHeader>
186
- <div className="space-y-4 py-4 text-sm text-muted-foreground">
187
- <p>
188
- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
189
- tempor incididunt ut labore et dolore magna aliqua.
190
- </p>
191
- <p>
192
- Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi
193
- ut aliquip ex ea commodo consequat.
194
- </p>
195
- <p>
196
- Duis aute irure dolor in reprehenderit in voluptate velit esse cillum
197
- dolore eu fugiat nulla pariatur.
198
- </p>
199
- <p>
200
- Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia
201
- deserunt mollit anim id est laborum.
202
- </p>
203
- </div>
204
- <DialogFooter>
205
- <DialogClose asChild>
206
- <Button variant="outline">Decline</Button>
207
- </DialogClose>
208
- <Button>Accept</Button>
209
- </DialogFooter>
210
- </DialogContent>
211
- </Dialog>
212
- );
@@ -1,359 +0,0 @@
1
- import * as React from 'react';
2
- import { defineStory, useSelect } from '@djangocfg/playground';
3
- import {
4
- Drawer,
5
- DrawerTrigger,
6
- DrawerContent,
7
- DrawerHeader,
8
- DrawerFooter,
9
- DrawerTitle,
10
- DrawerDescription,
11
- DrawerClose,
12
- useDrawerSize,
13
- type DrawerSize,
14
- } from '.';
15
- import { Button } from '../../forms/button';
16
- import { Input } from '../../forms/input';
17
- import { Label } from '../../forms/label';
18
-
19
- export default defineStory({
20
- title: 'Core/Drawer',
21
- component: Drawer,
22
- description: 'Mobile-friendly drawer that slides up from the bottom.',
23
- });
24
-
25
- export const Default = () => (
26
- <Drawer>
27
- <DrawerTrigger asChild>
28
- <Button variant="outline">Open Drawer</Button>
29
- </DrawerTrigger>
30
- <DrawerContent>
31
- <DrawerHeader>
32
- <DrawerTitle>Drawer Title</DrawerTitle>
33
- <DrawerDescription>
34
- This is a description of the drawer content.
35
- </DrawerDescription>
36
- </DrawerHeader>
37
- <div className="p-4">
38
- <p>Drawer content goes here.</p>
39
- </div>
40
- <DrawerFooter>
41
- <Button>Submit</Button>
42
- <DrawerClose asChild>
43
- <Button variant="outline">Cancel</Button>
44
- </DrawerClose>
45
- </DrawerFooter>
46
- </DrawerContent>
47
- </Drawer>
48
- );
49
-
50
- export const WithForm = () => (
51
- <Drawer>
52
- <DrawerTrigger asChild>
53
- <Button>Edit Profile</Button>
54
- </DrawerTrigger>
55
- <DrawerContent>
56
- <DrawerHeader>
57
- <DrawerTitle>Edit Profile</DrawerTitle>
58
- <DrawerDescription>
59
- Make changes to your profile information.
60
- </DrawerDescription>
61
- </DrawerHeader>
62
- <div className="p-4 space-y-4">
63
- <div className="space-y-2">
64
- <Label htmlFor="name">Name</Label>
65
- <Input id="name" defaultValue="John Doe" />
66
- </div>
67
- <div className="space-y-2">
68
- <Label htmlFor="email">Email</Label>
69
- <Input id="email" type="email" defaultValue="john@example.com" />
70
- </div>
71
- </div>
72
- <DrawerFooter>
73
- <Button>Save Changes</Button>
74
- <DrawerClose asChild>
75
- <Button variant="outline">Cancel</Button>
76
- </DrawerClose>
77
- </DrawerFooter>
78
- </DrawerContent>
79
- </Drawer>
80
- );
81
-
82
- export const Confirmation = () => (
83
- <Drawer>
84
- <DrawerTrigger asChild>
85
- <Button variant="destructive">Delete Account</Button>
86
- </DrawerTrigger>
87
- <DrawerContent>
88
- <DrawerHeader>
89
- <DrawerTitle>Are you sure?</DrawerTitle>
90
- <DrawerDescription>
91
- This action cannot be undone. Your account and all associated data will be permanently deleted.
92
- </DrawerDescription>
93
- </DrawerHeader>
94
- <DrawerFooter>
95
- <Button variant="destructive">Yes, Delete My Account</Button>
96
- <DrawerClose asChild>
97
- <Button variant="outline">Cancel</Button>
98
- </DrawerClose>
99
- </DrawerFooter>
100
- </DrawerContent>
101
- </Drawer>
102
- );
103
-
104
- export const ActionSheet = () => (
105
- <Drawer>
106
- <DrawerTrigger asChild>
107
- <Button variant="outline">Share</Button>
108
- </DrawerTrigger>
109
- <DrawerContent>
110
- <DrawerHeader>
111
- <DrawerTitle>Share this item</DrawerTitle>
112
- </DrawerHeader>
113
- <div className="p-4 space-y-2">
114
- <Button variant="ghost" className="w-full justify-start">
115
- Copy Link
116
- </Button>
117
- <Button variant="ghost" className="w-full justify-start">
118
- Share to Twitter
119
- </Button>
120
- <Button variant="ghost" className="w-full justify-start">
121
- Share to Facebook
122
- </Button>
123
- <Button variant="ghost" className="w-full justify-start">
124
- Send via Email
125
- </Button>
126
- </div>
127
- <DrawerFooter>
128
- <DrawerClose asChild>
129
- <Button variant="outline" className="w-full">Cancel</Button>
130
- </DrawerClose>
131
- </DrawerFooter>
132
- </DrawerContent>
133
- </Drawer>
134
- );
135
-
136
- const SIZES: readonly DrawerSize[] = ['sm', 'md', 'lg', 'xl', 'full'] as const;
137
-
138
- export const Sizes = () => {
139
- const [size, setSize] = React.useState<DrawerSize>('md');
140
- const [open, setOpen] = React.useState(false);
141
-
142
- return (
143
- <div className="space-y-4">
144
- <div className="flex flex-wrap gap-2">
145
- {SIZES.map((s) => (
146
- <Button
147
- key={s}
148
- variant={size === s ? 'default' : 'outline'}
149
- onClick={() => {
150
- setSize(s);
151
- setOpen(true);
152
- }}
153
- >
154
- {s}
155
- </Button>
156
- ))}
157
- </div>
158
- <p className="text-sm text-muted-foreground">
159
- Current size: <code>{size}</code>. Direction: right.
160
- </p>
161
- <Drawer open={open} onOpenChange={setOpen} direction="right">
162
- <DrawerContent direction="right" size={size}>
163
- <DrawerHeader>
164
- <DrawerTitle>Size: {size}</DrawerTitle>
165
- <DrawerDescription>
166
- Width preset is applied via inline style — vaul measures it
167
- correctly on first paint.
168
- </DrawerDescription>
169
- </DrawerHeader>
170
- <div className="p-4 text-sm">Content adapts to the chosen size.</div>
171
- <DrawerFooter>
172
- <DrawerClose asChild>
173
- <Button variant="outline">Close</Button>
174
- </DrawerClose>
175
- </DrawerFooter>
176
- </DrawerContent>
177
- </Drawer>
178
- </div>
179
- );
180
- };
181
-
182
- export const Directions = () => {
183
- const [direction] = useSelect('direction', {
184
- options: ['top', 'right', 'bottom', 'left'] as const,
185
- defaultValue: 'right',
186
- label: 'Direction',
187
- description: 'Edge from which the drawer slides.',
188
- });
189
- const [size] = useSelect('size', {
190
- options: ['sm', 'md', 'lg', 'xl', 'full'] as const,
191
- defaultValue: 'md',
192
- label: 'Size',
193
- description: 'Width (left/right) or height (top/bottom).',
194
- });
195
-
196
- return (
197
- <Drawer direction={direction}>
198
- <DrawerTrigger asChild>
199
- <Button variant="outline">Open from {direction}</Button>
200
- </DrawerTrigger>
201
- <DrawerContent direction={direction} size={size}>
202
- <DrawerHeader>
203
- <DrawerTitle>Direction: {direction}</DrawerTitle>
204
- <DrawerDescription>
205
- Size presets adapt: horizontal directions size width, vertical
206
- ones size height.
207
- </DrawerDescription>
208
- </DrawerHeader>
209
- <div className="p-4 text-sm">size = {size}</div>
210
- <DrawerFooter>
211
- <DrawerClose asChild>
212
- <Button variant="outline">Close</Button>
213
- </DrawerClose>
214
- </DrawerFooter>
215
- </DrawerContent>
216
- </Drawer>
217
- );
218
- };
219
-
220
- export const CustomWidth = () => (
221
- <Drawer direction="right">
222
- <DrawerTrigger asChild>
223
- <Button variant="outline">Open with width=720px</Button>
224
- </DrawerTrigger>
225
- <DrawerContent direction="right" width="720px">
226
- <DrawerHeader>
227
- <DrawerTitle>Custom width</DrawerTitle>
228
- <DrawerDescription>
229
- Explicit <code>width</code> overrides the <code>size</code> preset.
230
- </DrawerDescription>
231
- </DrawerHeader>
232
- <div className="p-4 text-sm">Width is exactly 720px.</div>
233
- <DrawerFooter>
234
- <DrawerClose asChild>
235
- <Button variant="outline">Close</Button>
236
- </DrawerClose>
237
- </DrawerFooter>
238
- </DrawerContent>
239
- </Drawer>
240
- );
241
-
242
- export const Resizable = () => {
243
- const [size, setSize] = React.useState<number | null>(null);
244
- return (
245
- <div className="space-y-3">
246
- <p className="text-sm text-muted-foreground">
247
- Drag the inner edge to resize. Disabled on mobile (&lt; 768px) by
248
- default — pass <code>resizableOnDesktopOnly={'{false}'}</code> to allow
249
- on touch.
250
- </p>
251
- <Drawer direction="right">
252
- <DrawerTrigger asChild>
253
- <Button variant="outline">Open resizable drawer</Button>
254
- </DrawerTrigger>
255
- <DrawerContent
256
- direction="right"
257
- size="md"
258
- resizable
259
- minSize={320}
260
- maxSize={900}
261
- onSizeChange={setSize}
262
- >
263
- <DrawerHeader>
264
- <DrawerTitle>Resizable</DrawerTitle>
265
- <DrawerDescription>
266
- Current width: {size != null ? `${Math.round(size)}px` : 'preset (480px)'}
267
- </DrawerDescription>
268
- </DrawerHeader>
269
- <div className="p-4 text-sm">Drag the left edge to resize.</div>
270
- <DrawerFooter>
271
- <DrawerClose asChild>
272
- <Button variant="outline">Close</Button>
273
- </DrawerClose>
274
- </DrawerFooter>
275
- </DrawerContent>
276
- </Drawer>
277
- </div>
278
- );
279
- };
280
-
281
- export const ResizablePersisted = () => {
282
- const drawer = useDrawerSize('story-demo', { axis: 'width', min: 320, max: 900 });
283
- return (
284
- <div className="space-y-3">
285
- <p className="text-sm text-muted-foreground">
286
- Persistence is wired via the <code>useDrawerSize(key)</code> hook —
287
- the drawer itself is just a controlled component. Reload the page and
288
- reopen, the last width is restored from <code>localStorage</code>.
289
- </p>
290
- <div className="flex gap-2">
291
- <Drawer direction="right">
292
- <DrawerTrigger asChild>
293
- <Button variant="outline">Open persisted drawer</Button>
294
- </DrawerTrigger>
295
- <DrawerContent
296
- direction="right"
297
- size="md"
298
- resizable
299
- minSize={320}
300
- maxSize={900}
301
- resizedSize={drawer.size}
302
- onSizeChange={drawer.setSize}
303
- >
304
- <DrawerHeader>
305
- <DrawerTitle>Resizable + persisted</DrawerTitle>
306
- <DrawerDescription>
307
- Stored in <code>djangocfg.ui.state</code>. Current:{' '}
308
- {drawer.size != null ? `${Math.round(drawer.size)}px` : 'preset (480px)'}
309
- </DrawerDescription>
310
- </DrawerHeader>
311
- <div className="p-4 text-sm">Drag the left edge to resize.</div>
312
- <DrawerFooter>
313
- <DrawerClose asChild>
314
- <Button variant="outline">Close</Button>
315
- </DrawerClose>
316
- </DrawerFooter>
317
- </DrawerContent>
318
- </Drawer>
319
- <Button variant="ghost" onClick={drawer.reset}>Reset stored size</Button>
320
- </div>
321
- </div>
322
- );
323
- };
324
-
325
- export const NarrowViewport = () => (
326
- <div className="space-y-3">
327
- <p className="text-sm text-muted-foreground">
328
- Resize the browser narrower than the size preset (e.g. &lt; 480px for
329
- <code> md</code>) to see <code>min(100vw, …)</code> clamp the drawer
330
- to the viewport. This is the regression we now guard against.
331
- </p>
332
- <div
333
- style={{ width: 360 }}
334
- className="rounded border border-dashed p-4"
335
- >
336
- <Drawer direction="right">
337
- <DrawerTrigger asChild>
338
- <Button variant="outline">Open in 360px container</Button>
339
- </DrawerTrigger>
340
- <DrawerContent direction="right" size="md">
341
- <DrawerHeader>
342
- <DrawerTitle>Narrow viewport</DrawerTitle>
343
- <DrawerDescription>
344
- Drawer is portaled to the body — viewport width still applies.
345
- </DrawerDescription>
346
- </DrawerHeader>
347
- <div className="p-4 text-sm">
348
- min(100vw, 480px) keeps the drawer within bounds on tiny screens.
349
- </div>
350
- <DrawerFooter>
351
- <DrawerClose asChild>
352
- <Button variant="outline">Close</Button>
353
- </DrawerClose>
354
- </DrawerFooter>
355
- </DrawerContent>
356
- </Drawer>
357
- </div>
358
- </div>
359
- );