| 1 | "use client"; |
| 2 | |
| 3 | import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"; |
| 4 | import { XIcon } from "lucide-react"; |
| 5 | import { cn } from "~/lib/utils"; |
| 6 | import { Button } from "~/components/ui/button"; |
| 7 | import { |
| 8 | DIALOG_BACKDROP_CLASS, |
| 9 | DIALOG_MEDIA_BACKDROP_CLASS, |
| 10 | DIALOG_MEDIA_POPUP_CLASS, |
| 11 | DIALOG_MOBILE_SHEET_CLASS, |
| 12 | DIALOG_POPUP_CLASS, |
| 13 | } from "~/components/ui/dialog-styles"; |
| 14 | import { ScrollArea } from "~/components/ui/scroll-area"; |
| 15 | |
| 16 | const Dialog = DialogPrimitive.Root; |
| 17 | |
| 18 | const DialogPortal = DialogPrimitive.Portal; |
| 19 | |
| 20 | function DialogTrigger(props: DialogPrimitive.Trigger.Props) { |
| 21 | return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />; |
| 22 | } |
| 23 | |
| 24 | function DialogClose(props: DialogPrimitive.Close.Props) { |
| 25 | return <DialogPrimitive.Close data-slot="dialog-close" {...props} />; |
| 26 | } |
| 27 | |
| 28 | function DialogBackdrop({ |
| 29 | className, |
| 30 | variant = "default", |
| 31 | ...props |
| 32 | }: DialogPrimitive.Backdrop.Props & { variant?: "default" | "media" }) { |
| 33 | return ( |
| 34 | <DialogPrimitive.Backdrop |
| 35 | forceRender |
| 36 | className={cn( |
| 37 | variant === "media" ? DIALOG_MEDIA_BACKDROP_CLASS : DIALOG_BACKDROP_CLASS, |
| 38 | className, |
| 39 | )} |
| 40 | data-slot="dialog-backdrop" |
| 41 | {...props} |
| 42 | /> |
| 43 | ); |
| 44 | } |
| 45 | |
| 46 | function DialogViewport({ className, ...props }: DialogPrimitive.Viewport.Props) { |
| 47 | return ( |
| 48 | <DialogPrimitive.Viewport |
| 49 | className={cn( |
| 50 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_1fr] justify-items-center p-4", |
| 51 | className, |
| 52 | )} |
| 53 | data-slot="dialog-viewport" |
| 54 | {...props} |
| 55 | /> |
| 56 | ); |
| 57 | } |
| 58 | |
| 59 | function DialogPopup({ |
| 60 | className, |
| 61 | children, |
| 62 | showCloseButton = true, |
| 63 | bottomStickOnMobile = true, |
| 64 | variant = "default", |
| 65 | ...props |
| 66 | }: DialogPrimitive.Popup.Props & { |
| 67 | showCloseButton?: boolean; |
| 68 | bottomStickOnMobile?: boolean; |
| 69 | variant?: "default" | "media"; |
| 70 | }) { |
| 71 | return ( |
| 72 | <DialogPortal> |
| 73 | {/* Media opens from inside other overlays (a composer chip, a popover), so it sits above them. */} |
| 74 | <DialogBackdrop className={variant === "media" ? "z-[60]" : undefined} variant={variant} /> |
| 75 | <DialogViewport |
| 76 | className={cn( |
| 77 | bottomStickOnMobile && "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12", |
| 78 | variant === "media" && |
| 79 | "z-[60] grid-rows-1 place-items-center px-4 py-6 [-webkit-app-region:no-drag]", |
| 80 | )} |
| 81 | > |
| 82 | <DialogPrimitive.Popup |
| 83 | className={cn( |
| 84 | variant === "media" ? DIALOG_MEDIA_POPUP_CLASS : DIALOG_POPUP_CLASS, |
| 85 | "row-start-2 text-popover-foreground", |
| 86 | variant === "default" && "max-h-full max-w-lg", |
| 87 | bottomStickOnMobile && DIALOG_MOBILE_SHEET_CLASS, |
| 88 | className, |
| 89 | )} |
| 90 | data-slot="dialog-popup" |
| 91 | {...props} |
| 92 | > |
| 93 | {children} |
| 94 | {showCloseButton && ( |
| 95 | <DialogPrimitive.Close |
| 96 | aria-label="Close" |
| 97 | className="absolute end-2 top-2" |
| 98 | render={<Button size="icon" variant="ghost" />} |
| 99 | > |
| 100 | <XIcon /> |
| 101 | </DialogPrimitive.Close> |
| 102 | )} |
| 103 | </DialogPrimitive.Popup> |
| 104 | </DialogViewport> |
| 105 | </DialogPortal> |
| 106 | ); |
| 107 | } |
| 108 | |
| 109 | function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { |
| 110 | return ( |
| 111 | <div |
| 112 | className={cn( |
| 113 | "flex flex-col gap-2 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-panel])]:pb-3 max-sm:pb-4", |
| 114 | className, |
| 115 | )} |
| 116 | data-slot="dialog-header" |
| 117 | {...props} |
| 118 | /> |
| 119 | ); |
| 120 | } |
| 121 | |
| 122 | function DialogFooter({ |
| 123 | className, |
| 124 | variant = "default", |
| 125 | ...props |
| 126 | }: React.ComponentProps<"div"> & { |
| 127 | variant?: "default" | "bare"; |
| 128 | }) { |
| 129 | return ( |
| 130 | <div |
| 131 | className={cn( |
| 132 | "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]", |
| 133 | variant === "default" && "border-t bg-muted/72 py-4", |
| 134 | variant === "bare" && "py-4", |
| 135 | className, |
| 136 | )} |
| 137 | data-slot="dialog-footer" |
| 138 | {...props} |
| 139 | /> |
| 140 | ); |
| 141 | } |
| 142 | |
| 143 | function DialogTitle({ className, ...props }: DialogPrimitive.Title.Props) { |
| 144 | return ( |
| 145 | <DialogPrimitive.Title |
| 146 | className={cn("wrap-anywhere font-semibold text-xl leading-none", className)} |
| 147 | data-slot="dialog-title" |
| 148 | {...props} |
| 149 | /> |
| 150 | ); |
| 151 | } |
| 152 | |
| 153 | function DialogDescription({ className, ...props }: DialogPrimitive.Description.Props) { |
| 154 | return ( |
| 155 | <DialogPrimitive.Description |
| 156 | className={cn("text-muted-foreground text-sm", className)} |
| 157 | data-slot="dialog-description" |
| 158 | {...props} |
| 159 | /> |
| 160 | ); |
| 161 | } |
| 162 | |
| 163 | function DialogPanel({ |
| 164 | className, |
| 165 | scrollFade = true, |
| 166 | ...props |
| 167 | }: React.ComponentProps<"div"> & { scrollFade?: boolean }) { |
| 168 | return ( |
| 169 | <ScrollArea scrollFade={scrollFade}> |
| 170 | <div |
| 171 | className={cn( |
| 172 | "space-y-4 p-6 in-[[data-slot=dialog-popup]:has([data-slot=dialog-header])]:pt-1 in-[[data-slot=dialog-popup]:has([data-slot=dialog-footer]:not(.border-t))]:pb-1", |
| 173 | className, |
| 174 | )} |
| 175 | data-slot="dialog-panel" |
| 176 | {...props} |
| 177 | /> |
| 178 | </ScrollArea> |
| 179 | ); |
| 180 | } |
| 181 | |
| 182 | export { |
| 183 | Dialog, |
| 184 | DialogTrigger, |
| 185 | DialogPortal, |
| 186 | DialogClose, |
| 187 | DialogBackdrop, |
| 188 | DialogBackdrop as DialogOverlay, |
| 189 | DialogPopup, |
| 190 | DialogPopup as DialogContent, |
| 191 | DialogHeader, |
| 192 | DialogFooter, |
| 193 | DialogTitle, |
| 194 | DialogDescription, |
| 195 | DialogPanel, |
| 196 | DialogViewport, |
| 197 | }; |