Skip to content

Commit 6cbb152

Browse files
committed
feat: add component drawer
1 parent 877ca0b commit 6cbb152

File tree

17 files changed

+410
-2
lines changed

17 files changed

+410
-2
lines changed

packages/ui/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
"react-dom": "19.1.0",
5252
"sonner": "2.0.6",
5353
"tailwind-merge": "3.3.0",
54-
"tailwind-variants": "1.0.0"
54+
"tailwind-variants": "1.0.0",
55+
"vaul": "^1.1.2"
5556
},
5657
"devDependencies": {
5758
"@turbo/gen": "2.5.0",
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { type ComponentRef, forwardRef } from 'react';
2+
import type { Content } from 'vaul';
3+
import { Root as _Root } from 'vaul';
4+
5+
import { DialogFooter, DialogHeader, DialogTrigger } from '../dialog';
6+
7+
import DrawerClose from './DrawerClose';
8+
import DrawerContent from './DrawerContent';
9+
import DrawerDescription from './DrawerDescription';
10+
import DrawerTitle from './DrawerTitle';
11+
import type { DrawerProps } from './types';
12+
13+
const Drawer = forwardRef<ComponentRef<typeof Content>, DrawerProps>((props, ref) => {
14+
const {
15+
children,
16+
classNames,
17+
contentProps,
18+
description,
19+
footer,
20+
shouldScaleBackground = true,
21+
showClose,
22+
size,
23+
title,
24+
trigger
25+
} = props;
26+
27+
return (
28+
<_Root
29+
shouldScaleBackground={shouldScaleBackground}
30+
{...props}
31+
>
32+
<DialogTrigger asChild>{trigger}</DialogTrigger>
33+
34+
<DrawerContent
35+
classNames={classNames}
36+
ref={ref}
37+
{...contentProps}
38+
>
39+
<DialogHeader
40+
className={classNames?.header}
41+
size={size}
42+
>
43+
<DrawerTitle
44+
className={classNames?.title}
45+
size={size}
46+
>
47+
{title}
48+
</DrawerTitle>
49+
50+
<DrawerDescription
51+
className={classNames?.description}
52+
size={size}
53+
>
54+
{description}
55+
</DrawerDescription>
56+
</DialogHeader>
57+
58+
{showClose && (
59+
<DrawerClose
60+
className={classNames?.close}
61+
size={size}
62+
/>
63+
)}
64+
65+
{children}
66+
67+
{footer && (
68+
<DialogFooter
69+
className={classNames?.footer || '!flex-col-reverse'}
70+
size={size}
71+
>
72+
{footer}
73+
</DialogFooter>
74+
)}
75+
</DrawerContent>
76+
</_Root>
77+
);
78+
});
79+
80+
Drawer.displayName = 'Drawer';
81+
82+
export default Drawer;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Drawer } from 'vaul';
2+
3+
import { DialogClose } from '../dialog';
4+
5+
import type { DrawerCloseProps } from './types';
6+
7+
const DrawerClose = (props: DrawerCloseProps) => {
8+
return (
9+
<DialogClose
10+
component={Drawer.Close}
11+
{...props}
12+
/>
13+
);
14+
};
15+
16+
export default DrawerClose;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { ComponentRef } from 'react';
2+
import { forwardRef } from 'react';
3+
import { Content, Portal } from 'vaul';
4+
5+
import { cn } from '@/lib/utils';
6+
7+
import DrawerContentBody from './DrawerContentBody';
8+
import DrawerKnob from './DrawerKnob';
9+
import DrawerOverlay from './DrawerOverlay';
10+
import { drawerVariants } from './drawer-variants';
11+
import type { DrawerContentProps } from './types';
12+
13+
const DrawerContent = forwardRef<ComponentRef<typeof Content>, DrawerContentProps>((props, ref) => {
14+
const { children, className, classNames, size, ...rest } = props;
15+
16+
const { content } = drawerVariants({ size });
17+
18+
const mergedCls = cn(content(), className, classNames?.content);
19+
20+
return (
21+
<Portal>
22+
<DrawerOverlay className={classNames?.overlay} />
23+
24+
<Content
25+
className={mergedCls}
26+
data-slot="drawer-content"
27+
ref={ref}
28+
{...rest}
29+
>
30+
<DrawerContentBody className={classNames?.contentBody}>
31+
<DrawerKnob className={classNames?.knob} />
32+
33+
{children}
34+
</DrawerContentBody>
35+
</Content>
36+
</Portal>
37+
);
38+
});
39+
40+
DrawerContent.displayName = 'DrawerContent';
41+
42+
export default DrawerContent;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { cn } from '@/lib/utils';
2+
3+
import { drawerVariants } from './drawer-variants';
4+
import type { DrawerContentBodyProps } from './types';
5+
6+
const DrawerContentBody = (props: DrawerContentBodyProps) => {
7+
const { className, size, ...rest } = props;
8+
9+
const { contentBody } = drawerVariants({ size });
10+
11+
const mergedCls = cn(contentBody(), className);
12+
return (
13+
<div
14+
className={mergedCls}
15+
{...rest}
16+
/>
17+
);
18+
};
19+
20+
export default DrawerContentBody;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Drawer } from 'vaul';
2+
3+
import { DialogDescription } from '../dialog';
4+
5+
import type { DrawerDescriptionProps } from './types';
6+
7+
const DrawerDescription = (props: DrawerDescriptionProps) => {
8+
return (
9+
<DialogDescription
10+
component={Drawer.Description}
11+
{...props}
12+
/>
13+
);
14+
};
15+
16+
export default DrawerDescription;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { cn } from '@/lib/utils';
2+
3+
import { drawerVariants } from './drawer-variants';
4+
import type { DrawerKnobProps } from './types';
5+
6+
const DrawerKnob = (props: DrawerKnobProps) => {
7+
const { className, size, ...rest } = props;
8+
9+
const { knob } = drawerVariants({ size });
10+
11+
const mergedCls = cn(knob(), className);
12+
13+
return (
14+
<div
15+
className={mergedCls}
16+
{...rest}
17+
/>
18+
);
19+
};
20+
21+
export default DrawerKnob;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Overlay as _Overlay } from 'vaul';
2+
3+
import { DialogOverlay } from '../dialog';
4+
5+
import type { DrawerOverlayProps } from './types';
6+
7+
const DrawerOverlay = (props: DrawerOverlayProps) => {
8+
return (
9+
<DialogOverlay
10+
component={_Overlay}
11+
{...props}
12+
/>
13+
);
14+
};
15+
16+
export default DrawerOverlay;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Drawer } from 'vaul';
2+
3+
import { DialogTitle } from '../dialog';
4+
5+
import type { DrawerTitleProps } from './types';
6+
7+
const DrawerTitle = (props: DrawerTitleProps) => {
8+
return (
9+
<DialogTitle
10+
component={Drawer.Title}
11+
{...props}
12+
/>
13+
);
14+
};
15+
16+
export default DrawerTitle;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { tv } from 'tailwind-variants';
2+
3+
import { dialogVariants } from '../dialog/dialog-variants';
4+
5+
export const drawerVariants = tv({
6+
defaultVariants: {
7+
size: 'md'
8+
},
9+
slots: {
10+
...dialogVariants.slots,
11+
content: `fixed inset-x-0 bottom-0 z-50 border bg-background`,
12+
contentBody: `flex h-auto flex-col`,
13+
knob: `mx-auto shrink-0 cursor-grab active:cursor-grabbing rounded-full bg-muted`
14+
},
15+
variants: {
16+
size: {
17+
'2xl': {
18+
content: `mt-30 rounded-t-[16px]`,
19+
contentBody: `gap-y-6 max-w-3xl px-7 py-6 text-xl`,
20+
knob: 'mt-7 h-3.5 w-40'
21+
},
22+
lg: {
23+
content: `mt-26 rounded-t-[12px]`,
24+
contentBody: `gap-y-4 max-w-xl px-5 py-4 text-base`,
25+
knob: 'mt-5 h-2.5 w-30'
26+
},
27+
md: {
28+
content: `mt-24 rounded-t-[10px]`,
29+
contentBody: `gap-y-3 max-w-lg px-4 py-3 text-sm`,
30+
knob: 'mt-4 h-2 w-25'
31+
},
32+
sm: {
33+
content: `mt-22 rounded-t-[8px]`,
34+
contentBody: `gap-y-2 max-w-md px-3 py-2 text-xs`,
35+
knob: 'mt-3.5 h-1.75 w-22'
36+
},
37+
xl: {
38+
content: `mt-28 rounded-t-[14px]`,
39+
contentBody: `gap-y-5 max-w-2xl px-6 py-5 text-lg`,
40+
knob: 'mt-6 h-3 w-35'
41+
},
42+
xs: {
43+
content: `mt-20 rounded-t-[6px]`,
44+
contentBody: `gap-y-1.5 max-w-md px-2 py-1.5 text-2xs`,
45+
knob: 'mt-3 h-1.5 w-20'
46+
}
47+
}
48+
}
49+
});
50+
51+
export type DrawerSlots = keyof typeof drawerVariants.slots;

0 commit comments

Comments
 (0)