drawer.tsx raw
1 import * as React from 'react'
2 import { Drawer as DrawerPrimitive } from 'vaul'
3
4 import { randomString } from '@/lib/random'
5 import { cn } from '@/lib/utils'
6 import modalManager from '@/services/modal-manager.service'
7
8 const Drawer = ({
9 shouldScaleBackground = true,
10 open,
11 onOpenChange,
12 ...props
13 }: React.ComponentProps<typeof DrawerPrimitive.Root>) => {
14 const [innerOpen, setInnerOpen] = React.useState(open ?? false)
15 const id = React.useMemo(() => `drawer-${randomString()}`, [])
16
17 React.useEffect(() => {
18 if (open) {
19 modalManager.register(id, () => {
20 onOpenChange?.(false)
21 })
22 } else {
23 modalManager.unregister(id)
24 }
25 }, [open])
26
27 React.useEffect(() => {
28 if (open !== undefined) {
29 return
30 }
31
32 if (innerOpen) {
33 modalManager.register(id, () => {
34 setInnerOpen(false)
35 })
36 } else {
37 modalManager.unregister(id)
38 }
39 }, [innerOpen])
40
41 return (
42 <DrawerPrimitive.Root
43 shouldScaleBackground={shouldScaleBackground}
44 open={open ?? innerOpen}
45 onOpenChange={onOpenChange ?? setInnerOpen}
46 {...props}
47 />
48 )
49 }
50 Drawer.displayName = 'Drawer'
51
52 const DrawerTrigger = DrawerPrimitive.Trigger
53
54 const DrawerPortal = DrawerPrimitive.Portal
55
56 const DrawerClose = DrawerPrimitive.Close
57
58 const DrawerOverlay = React.forwardRef<
59 React.ElementRef<typeof DrawerPrimitive.Overlay>,
60 React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
61 >(({ className, ...props }, ref) => (
62 <DrawerPrimitive.Overlay
63 ref={ref}
64 className={cn('fixed inset-0 z-50 bg-black/80', className)}
65 {...props}
66 />
67 ))
68 DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
69
70 const DrawerContent = React.forwardRef<
71 React.ElementRef<typeof DrawerPrimitive.Content>,
72 React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> & { hideOverlay?: boolean }
73 >(({ className, children, hideOverlay = false, ...props }, ref) => (
74 <DrawerPortal>
75 {!hideOverlay && <DrawerOverlay />}
76 <DrawerPrimitive.Content
77 ref={ref}
78 className={cn(
79 'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-2xl sm:border bg-background',
80 className
81 )}
82 style={{
83 paddingBottom: 'env(safe-area-inset-bottom)'
84 }}
85 onOpenAutoFocus={(e) => e.preventDefault()}
86 {...props}
87 >
88 <div className="mx-auto mt-4 pb-2 mb-2 h-2 w-[100px] rounded-full bg-muted" />
89 {children}
90 </DrawerPrimitive.Content>
91 </DrawerPortal>
92 ))
93 DrawerContent.displayName = 'DrawerContent'
94
95 const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
96 <div className={cn('grid gap-1.5 p-4 text-center sm:text-left', className)} {...props} />
97 )
98 DrawerHeader.displayName = 'DrawerHeader'
99
100 const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
101 <div className={cn('mt-auto flex flex-col gap-2 p-4', className)} {...props} />
102 )
103 DrawerFooter.displayName = 'DrawerFooter'
104
105 const DrawerTitle = React.forwardRef<
106 React.ElementRef<typeof DrawerPrimitive.Title>,
107 React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
108 >(({ className, ...props }, ref) => (
109 <DrawerPrimitive.Title
110 ref={ref}
111 className={cn('text-lg font-semibold leading-none tracking-tight', className)}
112 {...props}
113 />
114 ))
115 DrawerTitle.displayName = DrawerPrimitive.Title.displayName
116
117 const DrawerDescription = React.forwardRef<
118 React.ElementRef<typeof DrawerPrimitive.Description>,
119 React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
120 >(({ className, ...props }, ref) => (
121 <DrawerPrimitive.Description
122 ref={ref}
123 className={cn('text-sm text-muted-foreground', className)}
124 {...props}
125 />
126 ))
127 DrawerDescription.displayName = DrawerPrimitive.Description.displayName
128
129 export {
130 Drawer,
131 DrawerClose,
132 DrawerContent,
133 DrawerDescription,
134 DrawerFooter,
135 DrawerHeader,
136 DrawerOverlay,
137 DrawerPortal,
138 DrawerTitle,
139 DrawerTrigger
140 }
141