vinta-schedule-design-system 0.1.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.
- package/components.json +21 -0
- package/dist/styles.css +3452 -0
- package/package.json +103 -0
- package/src/layout/app-shell.tsx +115 -0
- package/src/layout/app-topbar.stories.tsx +74 -0
- package/src/layout/app-topbar.tsx +145 -0
- package/src/layout/auth-layout.tsx +45 -0
- package/src/layout/box.stories.tsx +86 -0
- package/src/layout/box.tsx +34 -0
- package/src/layout/center.tsx +24 -0
- package/src/layout/container.stories.tsx +44 -0
- package/src/layout/container.tsx +39 -0
- package/src/layout/divider.tsx +64 -0
- package/src/layout/flex.stories.tsx +109 -0
- package/src/layout/flex.test.tsx +71 -0
- package/src/layout/flex.tsx +112 -0
- package/src/layout/grid.stories.tsx +91 -0
- package/src/layout/grid.tsx +132 -0
- package/src/layout/heading.tsx +53 -0
- package/src/layout/index.ts +47 -0
- package/src/layout/layout-style.ts +291 -0
- package/src/layout/link.stories.tsx +54 -0
- package/src/layout/link.test.tsx +75 -0
- package/src/layout/link.tsx +100 -0
- package/src/layout/navbar.stories.tsx +70 -0
- package/src/layout/navbar.tsx +128 -0
- package/src/layout/page-header.stories.tsx +80 -0
- package/src/layout/page-header.tsx +59 -0
- package/src/layout/section.tsx +22 -0
- package/src/layout/spacer.tsx +21 -0
- package/src/layout/stack.stories.tsx +58 -0
- package/src/layout/stack.tsx +19 -0
- package/src/layout/text.tsx +139 -0
- package/src/layout/typography.stories.tsx +112 -0
- package/src/lib/utils.ts +6 -0
- package/src/story-types.ts +80 -0
- package/src/styles/build.css +33 -0
- package/src/styles/globals.css +9 -0
- package/src/styles/tokens.css +281 -0
- package/src/ui/accordion.stories.tsx +84 -0
- package/src/ui/accordion.tsx +57 -0
- package/src/ui/alert-dialog.stories.tsx +57 -0
- package/src/ui/alert-dialog.tsx +141 -0
- package/src/ui/alert.stories.tsx +46 -0
- package/src/ui/alert.tsx +59 -0
- package/src/ui/avatar.stories.tsx +56 -0
- package/src/ui/avatar.tsx +50 -0
- package/src/ui/badge.stories.tsx +105 -0
- package/src/ui/badge.tsx +57 -0
- package/src/ui/breadcrumb.stories.tsx +69 -0
- package/src/ui/breadcrumb.tsx +140 -0
- package/src/ui/button.stories.tsx +85 -0
- package/src/ui/button.tsx +60 -0
- package/src/ui/card.stories.tsx +62 -0
- package/src/ui/card.tsx +96 -0
- package/src/ui/carousel.stories.tsx +55 -0
- package/src/ui/carousel.tsx +262 -0
- package/src/ui/checkbox.stories.tsx +52 -0
- package/src/ui/checkbox.tsx +30 -0
- package/src/ui/combobox.stories.tsx +138 -0
- package/src/ui/combobox.tsx +217 -0
- package/src/ui/command.tsx +184 -0
- package/src/ui/dialog.stories.tsx +66 -0
- package/src/ui/dialog.tsx +163 -0
- package/src/ui/drawer.stories.tsx +80 -0
- package/src/ui/drawer.tsx +118 -0
- package/src/ui/dropdown-menu.stories.tsx +57 -0
- package/src/ui/dropdown-menu.tsx +201 -0
- package/src/ui/form.stories.tsx +70 -0
- package/src/ui/form.tsx +181 -0
- package/src/ui/input-otp.stories.tsx +52 -0
- package/src/ui/input-otp.tsx +71 -0
- package/src/ui/input.stories.tsx +68 -0
- package/src/ui/input.tsx +22 -0
- package/src/ui/label.stories.tsx +35 -0
- package/src/ui/label.tsx +26 -0
- package/src/ui/navigation-menu.stories.tsx +79 -0
- package/src/ui/navigation-menu.tsx +128 -0
- package/src/ui/pagination.stories.tsx +62 -0
- package/src/ui/pagination.tsx +137 -0
- package/src/ui/popover.tsx +89 -0
- package/src/ui/progress.stories.tsx +33 -0
- package/src/ui/progress.tsx +28 -0
- package/src/ui/radio-group.stories.tsx +52 -0
- package/src/ui/radio-group.tsx +44 -0
- package/src/ui/scheduling-chip.stories.tsx +117 -0
- package/src/ui/scheduling-chip.tsx +62 -0
- package/src/ui/scroll-area.stories.tsx +52 -0
- package/src/ui/scroll-area.tsx +48 -0
- package/src/ui/select.stories.tsx +56 -0
- package/src/ui/select.tsx +159 -0
- package/src/ui/sheet.stories.tsx +63 -0
- package/src/ui/sheet.tsx +141 -0
- package/src/ui/skeleton.stories.tsx +40 -0
- package/src/ui/skeleton.tsx +15 -0
- package/src/ui/sonner.stories.tsx +62 -0
- package/src/ui/sonner.tsx +34 -0
- package/src/ui/switch.stories.tsx +41 -0
- package/src/ui/switch.tsx +29 -0
- package/src/ui/table.stories.tsx +88 -0
- package/src/ui/table.tsx +132 -0
- package/src/ui/tabs.stories.tsx +63 -0
- package/src/ui/tabs.tsx +55 -0
- package/src/ui/textarea.stories.tsx +45 -0
- package/src/ui/textarea.tsx +22 -0
- package/src/ui/tooltip.stories.tsx +48 -0
- package/src/ui/tooltip.tsx +32 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Command as CommandPrimitive } from 'cmdk';
|
|
5
|
+
import { SearchIcon } from 'lucide-react';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils';
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogDescription,
|
|
12
|
+
DialogHeader,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
} from './dialog';
|
|
15
|
+
|
|
16
|
+
function Command({
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<typeof CommandPrimitive>) {
|
|
20
|
+
return (
|
|
21
|
+
<CommandPrimitive
|
|
22
|
+
data-slot='command'
|
|
23
|
+
className={cn(
|
|
24
|
+
'bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md',
|
|
25
|
+
className
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function CommandDialog({
|
|
33
|
+
title = 'Command Palette',
|
|
34
|
+
description = 'Search for a command to run...',
|
|
35
|
+
children,
|
|
36
|
+
className,
|
|
37
|
+
showCloseButton = true,
|
|
38
|
+
...props
|
|
39
|
+
}: React.ComponentProps<typeof Dialog> & {
|
|
40
|
+
title?: string;
|
|
41
|
+
description?: string;
|
|
42
|
+
className?: string;
|
|
43
|
+
showCloseButton?: boolean;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<Dialog {...props}>
|
|
47
|
+
<DialogHeader className='sr-only'>
|
|
48
|
+
<DialogTitle>{title}</DialogTitle>
|
|
49
|
+
<DialogDescription>{description}</DialogDescription>
|
|
50
|
+
</DialogHeader>
|
|
51
|
+
<DialogContent
|
|
52
|
+
className={cn('overflow-hidden p-0', className)}
|
|
53
|
+
showCloseButton={showCloseButton}
|
|
54
|
+
>
|
|
55
|
+
<Command className='[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5'>
|
|
56
|
+
{children}
|
|
57
|
+
</Command>
|
|
58
|
+
</DialogContent>
|
|
59
|
+
</Dialog>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function CommandInput({
|
|
64
|
+
className,
|
|
65
|
+
...props
|
|
66
|
+
}: React.ComponentProps<typeof CommandPrimitive.Input>) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
data-slot='command-input-wrapper'
|
|
70
|
+
className='flex h-9 items-center gap-2 border-b px-3'
|
|
71
|
+
>
|
|
72
|
+
<SearchIcon className='size-4 shrink-0 opacity-50' />
|
|
73
|
+
<CommandPrimitive.Input
|
|
74
|
+
data-slot='command-input'
|
|
75
|
+
className={cn(
|
|
76
|
+
'placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50',
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function CommandList({
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
}: React.ComponentProps<typeof CommandPrimitive.List>) {
|
|
89
|
+
return (
|
|
90
|
+
<CommandPrimitive.List
|
|
91
|
+
data-slot='command-list'
|
|
92
|
+
className={cn(
|
|
93
|
+
'max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto',
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function CommandEmpty({
|
|
102
|
+
...props
|
|
103
|
+
}: React.ComponentProps<typeof CommandPrimitive.Empty>) {
|
|
104
|
+
return (
|
|
105
|
+
<CommandPrimitive.Empty
|
|
106
|
+
data-slot='command-empty'
|
|
107
|
+
className='py-6 text-center text-sm'
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function CommandGroup({
|
|
114
|
+
className,
|
|
115
|
+
...props
|
|
116
|
+
}: React.ComponentProps<typeof CommandPrimitive.Group>) {
|
|
117
|
+
return (
|
|
118
|
+
<CommandPrimitive.Group
|
|
119
|
+
data-slot='command-group'
|
|
120
|
+
className={cn(
|
|
121
|
+
'text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium',
|
|
122
|
+
className
|
|
123
|
+
)}
|
|
124
|
+
{...props}
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function CommandSeparator({
|
|
130
|
+
className,
|
|
131
|
+
...props
|
|
132
|
+
}: React.ComponentProps<typeof CommandPrimitive.Separator>) {
|
|
133
|
+
return (
|
|
134
|
+
<CommandPrimitive.Separator
|
|
135
|
+
data-slot='command-separator'
|
|
136
|
+
className={cn('bg-border -mx-1 h-px', className)}
|
|
137
|
+
{...props}
|
|
138
|
+
/>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function CommandItem({
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}: React.ComponentProps<typeof CommandPrimitive.Item>) {
|
|
146
|
+
return (
|
|
147
|
+
<CommandPrimitive.Item
|
|
148
|
+
data-slot='command-item'
|
|
149
|
+
className={cn(
|
|
150
|
+
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
151
|
+
className
|
|
152
|
+
)}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function CommandShortcut({
|
|
159
|
+
className,
|
|
160
|
+
...props
|
|
161
|
+
}: React.ComponentProps<'span'>) {
|
|
162
|
+
return (
|
|
163
|
+
<span
|
|
164
|
+
data-slot='command-shortcut'
|
|
165
|
+
className={cn(
|
|
166
|
+
'text-muted-foreground ml-auto text-xs tracking-widest',
|
|
167
|
+
className
|
|
168
|
+
)}
|
|
169
|
+
{...props}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export {
|
|
175
|
+
Command,
|
|
176
|
+
CommandDialog,
|
|
177
|
+
CommandInput,
|
|
178
|
+
CommandList,
|
|
179
|
+
CommandEmpty,
|
|
180
|
+
CommandGroup,
|
|
181
|
+
CommandItem,
|
|
182
|
+
CommandShortcut,
|
|
183
|
+
CommandSeparator,
|
|
184
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogClose,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogFooter,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogTrigger,
|
|
12
|
+
} from './dialog';
|
|
13
|
+
import { Button } from './button';
|
|
14
|
+
import { Input } from './input';
|
|
15
|
+
import { Label } from './label';
|
|
16
|
+
|
|
17
|
+
const meta = {
|
|
18
|
+
title: 'Components/Dialog',
|
|
19
|
+
component: Dialog,
|
|
20
|
+
tags: ['autodocs'],
|
|
21
|
+
// Radix Dialog.Root scalars (`open` / `onOpenChange` are controlled-mode only,
|
|
22
|
+
// so they stay out). `children` (trigger + content) is composed → a slot.
|
|
23
|
+
argTypes: {
|
|
24
|
+
defaultOpen: {
|
|
25
|
+
control: 'boolean',
|
|
26
|
+
description: 'Open on first render (uncontrolled)',
|
|
27
|
+
},
|
|
28
|
+
modal: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
description: 'Trap focus and block interaction outside the dialog',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
args: { defaultOpen: false, modal: true },
|
|
34
|
+
parameters: { puck: { slots: ['children'] } },
|
|
35
|
+
} satisfies Meta<typeof Dialog>;
|
|
36
|
+
|
|
37
|
+
export default meta;
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Default: Story = {
|
|
41
|
+
render: (args) => (
|
|
42
|
+
<Dialog {...args}>
|
|
43
|
+
<DialogTrigger asChild>
|
|
44
|
+
<Button>New booking</Button>
|
|
45
|
+
</DialogTrigger>
|
|
46
|
+
<DialogContent className='sm:max-w-md'>
|
|
47
|
+
<DialogHeader>
|
|
48
|
+
<DialogTitle>Create a booking</DialogTitle>
|
|
49
|
+
<DialogDescription>
|
|
50
|
+
Add a one-off appointment to your schedule.
|
|
51
|
+
</DialogDescription>
|
|
52
|
+
</DialogHeader>
|
|
53
|
+
<div className='grid gap-3 py-2'>
|
|
54
|
+
<Label htmlFor='title'>Title</Label>
|
|
55
|
+
<Input id='title' placeholder='Follow-up visit' />
|
|
56
|
+
</div>
|
|
57
|
+
<DialogFooter>
|
|
58
|
+
<DialogClose asChild>
|
|
59
|
+
<Button variant='outline'>Cancel</Button>
|
|
60
|
+
</DialogClose>
|
|
61
|
+
<Button>Save</Button>
|
|
62
|
+
</DialogFooter>
|
|
63
|
+
</DialogContent>
|
|
64
|
+
</Dialog>
|
|
65
|
+
),
|
|
66
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { XIcon } from 'lucide-react';
|
|
5
|
+
import { Dialog as DialogPrimitive } from 'radix-ui';
|
|
6
|
+
|
|
7
|
+
import { cn } from '../lib/utils';
|
|
8
|
+
import { Button } from './button';
|
|
9
|
+
|
|
10
|
+
function Dialog({
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
13
|
+
return <DialogPrimitive.Root data-slot='dialog' {...props} />;
|
|
14
|
+
}
|
|
15
|
+
// Pinned: bundlers rename this function (it collides with radix-ui's own
|
|
16
|
+
// `Dialog` export once inlined), and tooling that resolves the component by
|
|
17
|
+
// name — Storybook docgen, the composer's contract extractor — would see the
|
|
18
|
+
// mangled name instead.
|
|
19
|
+
Dialog.displayName = 'Dialog';
|
|
20
|
+
|
|
21
|
+
function DialogTrigger({
|
|
22
|
+
...props
|
|
23
|
+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
24
|
+
return <DialogPrimitive.Trigger data-slot='dialog-trigger' {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function DialogPortal({
|
|
28
|
+
...props
|
|
29
|
+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
30
|
+
return <DialogPrimitive.Portal data-slot='dialog-portal' {...props} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function DialogClose({
|
|
34
|
+
...props
|
|
35
|
+
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
36
|
+
return <DialogPrimitive.Close data-slot='dialog-close' {...props} />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function DialogOverlay({
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
43
|
+
return (
|
|
44
|
+
<DialogPrimitive.Overlay
|
|
45
|
+
data-slot='dialog-overlay'
|
|
46
|
+
className={cn(
|
|
47
|
+
'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function DialogContent({
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
showCloseButton = true,
|
|
59
|
+
...props
|
|
60
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
61
|
+
showCloseButton?: boolean;
|
|
62
|
+
}) {
|
|
63
|
+
return (
|
|
64
|
+
<DialogPortal data-slot='dialog-portal'>
|
|
65
|
+
<DialogOverlay />
|
|
66
|
+
<DialogPrimitive.Content
|
|
67
|
+
data-slot='dialog-content'
|
|
68
|
+
className={cn(
|
|
69
|
+
'bg-background data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg',
|
|
70
|
+
className
|
|
71
|
+
)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
{showCloseButton && (
|
|
76
|
+
<DialogPrimitive.Close
|
|
77
|
+
data-slot='dialog-close'
|
|
78
|
+
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
|
79
|
+
>
|
|
80
|
+
<XIcon />
|
|
81
|
+
<span className='sr-only'>Close</span>
|
|
82
|
+
</DialogPrimitive.Close>
|
|
83
|
+
)}
|
|
84
|
+
</DialogPrimitive.Content>
|
|
85
|
+
</DialogPortal>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot='dialog-header'
|
|
93
|
+
className={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function DialogFooter({
|
|
100
|
+
className,
|
|
101
|
+
showCloseButton = false,
|
|
102
|
+
children,
|
|
103
|
+
...props
|
|
104
|
+
}: React.ComponentProps<'div'> & {
|
|
105
|
+
showCloseButton?: boolean;
|
|
106
|
+
}) {
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
data-slot='dialog-footer'
|
|
110
|
+
className={cn(
|
|
111
|
+
'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
>
|
|
116
|
+
{children}
|
|
117
|
+
{showCloseButton && (
|
|
118
|
+
<DialogPrimitive.Close asChild>
|
|
119
|
+
<Button variant='outline'>Close</Button>
|
|
120
|
+
</DialogPrimitive.Close>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function DialogTitle({
|
|
127
|
+
className,
|
|
128
|
+
...props
|
|
129
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
130
|
+
return (
|
|
131
|
+
<DialogPrimitive.Title
|
|
132
|
+
data-slot='dialog-title'
|
|
133
|
+
className={cn('text-lg leading-none font-semibold', className)}
|
|
134
|
+
{...props}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function DialogDescription({
|
|
140
|
+
className,
|
|
141
|
+
...props
|
|
142
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
143
|
+
return (
|
|
144
|
+
<DialogPrimitive.Description
|
|
145
|
+
data-slot='dialog-description'
|
|
146
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
147
|
+
{...props}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
Dialog,
|
|
154
|
+
DialogClose,
|
|
155
|
+
DialogContent,
|
|
156
|
+
DialogDescription,
|
|
157
|
+
DialogFooter,
|
|
158
|
+
DialogHeader,
|
|
159
|
+
DialogOverlay,
|
|
160
|
+
DialogPortal,
|
|
161
|
+
DialogTitle,
|
|
162
|
+
DialogTrigger,
|
|
163
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Drawer,
|
|
5
|
+
DrawerClose,
|
|
6
|
+
DrawerContent,
|
|
7
|
+
DrawerDescription,
|
|
8
|
+
DrawerFooter,
|
|
9
|
+
DrawerHeader,
|
|
10
|
+
DrawerTitle,
|
|
11
|
+
DrawerTrigger,
|
|
12
|
+
} from './drawer';
|
|
13
|
+
import { Button } from './button';
|
|
14
|
+
|
|
15
|
+
const meta = {
|
|
16
|
+
title: 'Components/Drawer',
|
|
17
|
+
component: Drawer,
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
// vaul Drawer.Root scalars (drawer.tsx forwards every prop to it, and owns
|
|
20
|
+
// `shouldScaleBackground`). `children` (trigger + content) is composed → slot.
|
|
21
|
+
argTypes: {
|
|
22
|
+
direction: {
|
|
23
|
+
control: 'select',
|
|
24
|
+
options: ['bottom', 'top', 'left', 'right'],
|
|
25
|
+
description:
|
|
26
|
+
'Edge the drawer slides from. DrawerContent is styled for `bottom`; other directions need content classes to match.',
|
|
27
|
+
},
|
|
28
|
+
modal: {
|
|
29
|
+
control: 'boolean',
|
|
30
|
+
description: 'Block interaction with the page behind the drawer',
|
|
31
|
+
},
|
|
32
|
+
dismissible: {
|
|
33
|
+
control: 'boolean',
|
|
34
|
+
description: 'Allow closing by dragging or clicking the overlay',
|
|
35
|
+
},
|
|
36
|
+
defaultOpen: {
|
|
37
|
+
control: 'boolean',
|
|
38
|
+
description: 'Open on first render (uncontrolled)',
|
|
39
|
+
},
|
|
40
|
+
shouldScaleBackground: {
|
|
41
|
+
control: 'boolean',
|
|
42
|
+
description: 'Scale the page behind the drawer while it is open',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
args: {
|
|
46
|
+
direction: 'bottom',
|
|
47
|
+
modal: true,
|
|
48
|
+
dismissible: true,
|
|
49
|
+
defaultOpen: false,
|
|
50
|
+
shouldScaleBackground: true,
|
|
51
|
+
},
|
|
52
|
+
parameters: { puck: { slots: ['children'] } },
|
|
53
|
+
} satisfies Meta<typeof Drawer>;
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
type Story = StoryObj<typeof meta>;
|
|
57
|
+
|
|
58
|
+
export const Default: Story = {
|
|
59
|
+
render: (args) => (
|
|
60
|
+
<Drawer {...args}>
|
|
61
|
+
<DrawerTrigger asChild>
|
|
62
|
+
<Button variant='outline'>Open drawer</Button>
|
|
63
|
+
</DrawerTrigger>
|
|
64
|
+
<DrawerContent>
|
|
65
|
+
<div className='mx-auto w-full max-w-sm'>
|
|
66
|
+
<DrawerHeader>
|
|
67
|
+
<DrawerTitle>Confirm time</DrawerTitle>
|
|
68
|
+
<DrawerDescription>Mon, 9:00–9:30 AM</DrawerDescription>
|
|
69
|
+
</DrawerHeader>
|
|
70
|
+
<DrawerFooter>
|
|
71
|
+
<Button>Confirm</Button>
|
|
72
|
+
<DrawerClose asChild>
|
|
73
|
+
<Button variant='outline'>Cancel</Button>
|
|
74
|
+
</DrawerClose>
|
|
75
|
+
</DrawerFooter>
|
|
76
|
+
</div>
|
|
77
|
+
</DrawerContent>
|
|
78
|
+
</Drawer>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Drawer as DrawerPrimitive } from 'vaul';
|
|
5
|
+
|
|
6
|
+
import { cn } from '../lib/utils';
|
|
7
|
+
|
|
8
|
+
const Drawer = ({
|
|
9
|
+
shouldScaleBackground = true,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
|
12
|
+
<DrawerPrimitive.Root
|
|
13
|
+
shouldScaleBackground={shouldScaleBackground}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
Drawer.displayName = 'Drawer';
|
|
18
|
+
|
|
19
|
+
const DrawerTrigger = DrawerPrimitive.Trigger;
|
|
20
|
+
|
|
21
|
+
const DrawerPortal = DrawerPrimitive.Portal;
|
|
22
|
+
|
|
23
|
+
const DrawerClose = DrawerPrimitive.Close;
|
|
24
|
+
|
|
25
|
+
const DrawerOverlay = React.forwardRef<
|
|
26
|
+
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
|
27
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
|
28
|
+
>(({ className, ...props }, ref) => (
|
|
29
|
+
<DrawerPrimitive.Overlay
|
|
30
|
+
ref={ref}
|
|
31
|
+
className={cn('fixed inset-0 z-50 bg-black/80', className)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
));
|
|
35
|
+
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
|
36
|
+
|
|
37
|
+
const DrawerContent = React.forwardRef<
|
|
38
|
+
React.ElementRef<typeof DrawerPrimitive.Content>,
|
|
39
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
|
40
|
+
>(({ className, children, ...props }, ref) => (
|
|
41
|
+
<DrawerPortal>
|
|
42
|
+
<DrawerOverlay />
|
|
43
|
+
<DrawerPrimitive.Content
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn(
|
|
46
|
+
'bg-background fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border',
|
|
47
|
+
className
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
>
|
|
51
|
+
<div className='bg-muted mx-auto mt-4 h-2 w-[100px] rounded-full' />
|
|
52
|
+
{children}
|
|
53
|
+
</DrawerPrimitive.Content>
|
|
54
|
+
</DrawerPortal>
|
|
55
|
+
));
|
|
56
|
+
DrawerContent.displayName = 'DrawerContent';
|
|
57
|
+
|
|
58
|
+
const DrawerHeader = ({
|
|
59
|
+
className,
|
|
60
|
+
...props
|
|
61
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
62
|
+
<div
|
|
63
|
+
className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
DrawerHeader.displayName = 'DrawerHeader';
|
|
68
|
+
|
|
69
|
+
const DrawerFooter = ({
|
|
70
|
+
className,
|
|
71
|
+
...props
|
|
72
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
73
|
+
<div
|
|
74
|
+
className={cn('mt-auto flex flex-col gap-2 p-4', className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
DrawerFooter.displayName = 'DrawerFooter';
|
|
79
|
+
|
|
80
|
+
const DrawerTitle = React.forwardRef<
|
|
81
|
+
React.ElementRef<typeof DrawerPrimitive.Title>,
|
|
82
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
|
83
|
+
>(({ className, ...props }, ref) => (
|
|
84
|
+
<DrawerPrimitive.Title
|
|
85
|
+
ref={ref}
|
|
86
|
+
className={cn(
|
|
87
|
+
'text-lg leading-none font-semibold tracking-tight',
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
));
|
|
93
|
+
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
|
94
|
+
|
|
95
|
+
const DrawerDescription = React.forwardRef<
|
|
96
|
+
React.ElementRef<typeof DrawerPrimitive.Description>,
|
|
97
|
+
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
|
98
|
+
>(({ className, ...props }, ref) => (
|
|
99
|
+
<DrawerPrimitive.Description
|
|
100
|
+
ref={ref}
|
|
101
|
+
className={cn('text-muted-foreground text-sm', className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
));
|
|
105
|
+
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
Drawer,
|
|
109
|
+
DrawerPortal,
|
|
110
|
+
DrawerOverlay,
|
|
111
|
+
DrawerTrigger,
|
|
112
|
+
DrawerClose,
|
|
113
|
+
DrawerContent,
|
|
114
|
+
DrawerHeader,
|
|
115
|
+
DrawerFooter,
|
|
116
|
+
DrawerTitle,
|
|
117
|
+
DrawerDescription,
|
|
118
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '../story-types';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuLabel,
|
|
8
|
+
DropdownMenuSeparator,
|
|
9
|
+
DropdownMenuShortcut,
|
|
10
|
+
DropdownMenuTrigger,
|
|
11
|
+
} from './dropdown-menu';
|
|
12
|
+
import { Button } from './button';
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'Components/DropdownMenu',
|
|
16
|
+
component: DropdownMenu,
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
// Radix DropdownMenu.Root scalars. `children` (trigger + content) is composed
|
|
19
|
+
// content → a slot, never an argTypes key.
|
|
20
|
+
argTypes: {
|
|
21
|
+
defaultOpen: {
|
|
22
|
+
control: 'boolean',
|
|
23
|
+
description: 'Open on first render (uncontrolled)',
|
|
24
|
+
},
|
|
25
|
+
modal: {
|
|
26
|
+
control: 'boolean',
|
|
27
|
+
description: 'Block interaction with the page while the menu is open',
|
|
28
|
+
},
|
|
29
|
+
dir: { control: 'inline-radio', options: ['ltr', 'rtl'] },
|
|
30
|
+
},
|
|
31
|
+
args: { defaultOpen: false, modal: true },
|
|
32
|
+
parameters: { puck: { slots: ['children'] } },
|
|
33
|
+
} satisfies Meta<typeof DropdownMenu>;
|
|
34
|
+
|
|
35
|
+
export default meta;
|
|
36
|
+
type Story = StoryObj<typeof meta>;
|
|
37
|
+
|
|
38
|
+
export const Default: Story = {
|
|
39
|
+
render: (args) => (
|
|
40
|
+
<DropdownMenu {...args}>
|
|
41
|
+
<DropdownMenuTrigger asChild>
|
|
42
|
+
<Button variant='outline'>Actions</Button>
|
|
43
|
+
</DropdownMenuTrigger>
|
|
44
|
+
<DropdownMenuContent align='start' className='w-52'>
|
|
45
|
+
<DropdownMenuLabel>Appointment</DropdownMenuLabel>
|
|
46
|
+
<DropdownMenuSeparator />
|
|
47
|
+
<DropdownMenuItem>
|
|
48
|
+
Reschedule
|
|
49
|
+
<DropdownMenuShortcut>⌘R</DropdownMenuShortcut>
|
|
50
|
+
</DropdownMenuItem>
|
|
51
|
+
<DropdownMenuItem>Copy link</DropdownMenuItem>
|
|
52
|
+
<DropdownMenuSeparator />
|
|
53
|
+
<DropdownMenuItem className='text-destructive'>Cancel</DropdownMenuItem>
|
|
54
|
+
</DropdownMenuContent>
|
|
55
|
+
</DropdownMenu>
|
|
56
|
+
),
|
|
57
|
+
};
|