dialog.tsx raw
1 import * as DialogPrimitive from '@radix-ui/react-dialog'
2 import { X } from 'lucide-react'
3 import * as React from 'react'
4
5 import { randomString } from '@/lib/random'
6 import { cn } from '@/lib/utils'
7 import modalManager from '@/services/modal-manager.service'
8
9 const Dialog = ({ children, open, onOpenChange, ...props }: DialogPrimitive.DialogProps) => {
10 const [innerOpen, setInnerOpen] = React.useState(open ?? false)
11 const id = React.useMemo(() => `dialog-${randomString()}`, [])
12
13 React.useEffect(() => {
14 if (open) {
15 modalManager.register(id, () => {
16 onOpenChange?.(false)
17 })
18 } else {
19 modalManager.unregister(id)
20 }
21 }, [open])
22
23 React.useEffect(() => {
24 if (open !== undefined) {
25 return
26 }
27
28 if (innerOpen) {
29 modalManager.register(id, () => {
30 setInnerOpen(false)
31 })
32 } else {
33 modalManager.unregister(id)
34 }
35 }, [innerOpen])
36
37 return (
38 <DialogPrimitive.Root
39 open={open ?? innerOpen}
40 onOpenChange={onOpenChange ?? setInnerOpen}
41 {...props}
42 >
43 {children}
44 </DialogPrimitive.Root>
45 )
46 }
47
48 const DialogTrigger = DialogPrimitive.Trigger
49
50 const DialogPortal = DialogPrimitive.Portal
51
52 const DialogClose = DialogPrimitive.Close
53
54 const DialogOverlay = React.forwardRef<
55 React.ElementRef<typeof DialogPrimitive.Overlay>,
56 React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
57 >(({ className, ...props }, ref) => (
58 <DialogPrimitive.Overlay
59 ref={ref}
60 className={cn(
61 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
62 className
63 )}
64 {...props}
65 />
66 ))
67 DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
68
69 const DialogContent = React.forwardRef<
70 React.ElementRef<typeof DialogPrimitive.Content>,
71 React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
72 withoutClose?: boolean
73 }
74 >(({ className, children, withoutClose, ...props }, ref) => (
75 <DialogPortal>
76 <DialogOverlay />
77 <DialogPrimitive.Content
78 ref={ref}
79 className={cn(
80 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 sm:border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-xl',
81 className
82 )}
83 {...props}
84 >
85 {children}
86 {!withoutClose && (
87 <DialogPrimitive.Close className="absolute right-4 top-4 rounded-lg opacity-70 ring-offset-background transition-all hover:opacity-100 hover:bg-accent focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground p-1">
88 <X className="h-4 w-4" />
89 <span className="sr-only">Close</span>
90 </DialogPrimitive.Close>
91 )}
92 </DialogPrimitive.Content>
93 </DialogPortal>
94 ))
95 DialogContent.displayName = DialogPrimitive.Content.displayName
96
97 const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
98 <div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
99 )
100 DialogHeader.displayName = 'DialogHeader'
101
102 const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
103 <div
104 className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
105 {...props}
106 />
107 )
108 DialogFooter.displayName = 'DialogFooter'
109
110 const DialogTitle = React.forwardRef<
111 React.ElementRef<typeof DialogPrimitive.Title>,
112 React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
113 >(({ className, ...props }, ref) => (
114 <DialogPrimitive.Title
115 ref={ref}
116 className={cn('text-lg font-semibold leading-none tracking-tight', className)}
117 {...props}
118 />
119 ))
120 DialogTitle.displayName = DialogPrimitive.Title.displayName
121
122 const DialogDescription = React.forwardRef<
123 React.ElementRef<typeof DialogPrimitive.Description>,
124 React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
125 >(({ className, ...props }, ref) => (
126 <DialogPrimitive.Description
127 ref={ref}
128 className={cn('text-sm text-muted-foreground', className)}
129 {...props}
130 />
131 ))
132 DialogDescription.displayName = DialogPrimitive.Description.displayName
133
134 export {
135 Dialog,
136 DialogClose,
137 DialogContent,
138 DialogDescription,
139 DialogFooter,
140 DialogHeader,
141 DialogOverlay,
142 DialogPortal,
143 DialogTitle,
144 DialogTrigger
145 }
146