| 1 | "use client"; |
| 2 | |
| 3 | import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog"; |
| 4 | import { useRef } from "react"; |
| 5 | |
| 6 | import { cn } from "~/lib/utils"; |
| 7 | import { |
| 8 | DIALOG_BACKDROP_CLASS, |
| 9 | DIALOG_MOBILE_SHEET_CLASS, |
| 10 | DIALOG_POPUP_CLASS, |
| 11 | } from "~/components/ui/dialog-styles"; |
| 12 | |
| 13 | const AlertDialog = AlertDialogPrimitive.Root; |
| 14 | |
| 15 | const AlertDialogPortal = AlertDialogPrimitive.Portal; |
| 16 | |
| 17 | function AlertDialogBackdrop({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) { |
| 18 | return ( |
| 19 | <AlertDialogPrimitive.Backdrop |
| 20 | forceRender |
| 21 | className={cn(DIALOG_BACKDROP_CLASS, className)} |
| 22 | data-slot="alert-dialog-backdrop" |
| 23 | {...props} |
| 24 | /> |
| 25 | ); |
| 26 | } |
| 27 | |
| 28 | function AlertDialogViewport({ className, ...props }: AlertDialogPrimitive.Viewport.Props) { |
| 29 | return ( |
| 30 | <AlertDialogPrimitive.Viewport |
| 31 | className={cn( |
| 32 | "fixed inset-0 z-50 grid grid-rows-[1fr_auto_1fr] justify-items-center p-4", |
| 33 | className, |
| 34 | )} |
| 35 | data-slot="alert-dialog-viewport" |
| 36 | {...props} |
| 37 | /> |
| 38 | ); |
| 39 | } |
| 40 | |
| 41 | function AlertDialogPopup({ |
| 42 | className, |
| 43 | bottomStickOnMobile = true, |
| 44 | portalContainer, |
| 45 | initialFocus, |
| 46 | ref, |
| 47 | ...props |
| 48 | }: AlertDialogPrimitive.Popup.Props & { |
| 49 | bottomStickOnMobile?: boolean; |
| 50 | portalContainer?: AlertDialogPrimitive.Portal.Props["container"]; |
| 51 | }) { |
| 52 | const popup = useRef<HTMLDivElement | null>(null); |
| 53 | return ( |
| 54 | <AlertDialogPortal container={portalContainer}> |
| 55 | <AlertDialogBackdrop /> |
| 56 | <AlertDialogViewport |
| 57 | className={cn(bottomStickOnMobile && "max-sm:grid-rows-[1fr_auto] max-sm:p-0 max-sm:pt-12")} |
| 58 | > |
| 59 | <AlertDialogPrimitive.Popup |
| 60 | ref={(element) => { |
| 61 | popup.current = element; |
| 62 | if (typeof ref === "function") return ref(element); |
| 63 | if (ref) ref.current = element; |
| 64 | }} |
| 65 | initialFocus={ |
| 66 | initialFocus ?? |
| 67 | (() => { |
| 68 | return ( |
| 69 | popup.current?.querySelector<HTMLButtonElement>( |
| 70 | '[data-slot="alert-dialog-footer"] button:not([data-slot="alert-dialog-close"]):not(:disabled):not([aria-disabled="true"])', |
| 71 | ) ?? true |
| 72 | ); |
| 73 | }) |
| 74 | } |
| 75 | className={cn( |
| 76 | DIALOG_POPUP_CLASS, |
| 77 | "row-start-2 max-h-full max-w-lg text-popover-foreground", |
| 78 | bottomStickOnMobile && DIALOG_MOBILE_SHEET_CLASS, |
| 79 | className, |
| 80 | )} |
| 81 | data-slot="alert-dialog-popup" |
| 82 | {...props} |
| 83 | /> |
| 84 | </AlertDialogViewport> |
| 85 | </AlertDialogPortal> |
| 86 | ); |
| 87 | } |
| 88 | |
| 89 | function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">) { |
| 90 | return ( |
| 91 | <div |
| 92 | className={cn( |
| 93 | "flex min-h-0 flex-col gap-2 p-6 text-center max-sm:pb-4 sm:text-left", |
| 94 | className, |
| 95 | )} |
| 96 | data-slot="alert-dialog-header" |
| 97 | {...props} |
| 98 | /> |
| 99 | ); |
| 100 | } |
| 101 | |
| 102 | function AlertDialogFooter({ |
| 103 | className, |
| 104 | variant = "default", |
| 105 | ...props |
| 106 | }: React.ComponentProps<"div"> & { |
| 107 | variant?: "default" | "bare"; |
| 108 | }) { |
| 109 | return ( |
| 110 | <div |
| 111 | className={cn( |
| 112 | "flex flex-col-reverse gap-2 px-6 sm:flex-row sm:justify-end sm:rounded-b-[calc(var(--radius-2xl)-1px)]", |
| 113 | variant === "default" && "border-t bg-muted/72 py-4", |
| 114 | variant === "bare" && "pb-6", |
| 115 | className, |
| 116 | )} |
| 117 | data-slot="alert-dialog-footer" |
| 118 | {...props} |
| 119 | /> |
| 120 | ); |
| 121 | } |
| 122 | |
| 123 | function AlertDialogTitle({ className, ...props }: AlertDialogPrimitive.Title.Props) { |
| 124 | return ( |
| 125 | <AlertDialogPrimitive.Title |
| 126 | className={cn("wrap-anywhere shrink-0 font-semibold text-xl leading-none", className)} |
| 127 | data-slot="alert-dialog-title" |
| 128 | {...props} |
| 129 | /> |
| 130 | ); |
| 131 | } |
| 132 | |
| 133 | function AlertDialogDescription({ className, ...props }: AlertDialogPrimitive.Description.Props) { |
| 134 | return ( |
| 135 | <AlertDialogPrimitive.Description |
| 136 | className={cn( |
| 137 | // A long description (say, the arguments of a tool call awaiting |
| 138 | // approval) scrolls in place instead of pushing the actions out of |
| 139 | // the popup, which the viewport already caps. |
| 140 | "wrap-anywhere max-h-[min(24rem,60dvh)] min-h-0 overflow-y-auto overscroll-contain text-muted-foreground text-sm select-text", |
| 141 | className, |
| 142 | )} |
| 143 | data-slot="alert-dialog-description" |
| 144 | {...props} |
| 145 | /> |
| 146 | ); |
| 147 | } |
| 148 | |
| 149 | function AlertDialogClose(props: AlertDialogPrimitive.Close.Props) { |
| 150 | return <AlertDialogPrimitive.Close data-slot="alert-dialog-close" {...props} />; |
| 151 | } |
| 152 | |
| 153 | export { |
| 154 | AlertDialog, |
| 155 | AlertDialogPortal, |
| 156 | AlertDialogBackdrop, |
| 157 | AlertDialogBackdrop as AlertDialogOverlay, |
| 158 | AlertDialogPopup, |
| 159 | AlertDialogPopup as AlertDialogContent, |
| 160 | AlertDialogHeader, |
| 161 | AlertDialogFooter, |
| 162 | AlertDialogTitle, |
| 163 | AlertDialogDescription, |
| 164 | AlertDialogClose, |
| 165 | AlertDialogViewport, |
| 166 | }; |