| 1 | "use client"; |
| 2 | |
| 3 | import { Dialog as SheetPrimitive } from "@base-ui/react/dialog"; |
| 4 | import { XIcon } from "lucide-react"; |
| 5 | import type { CSSProperties } from "react"; |
| 6 | import { cn } from "~/lib/utils"; |
| 7 | import { Button } from "~/components/ui/button"; |
| 8 | |
| 9 | const Sheet = SheetPrimitive.Root; |
| 10 | |
| 11 | const SheetPortal = SheetPrimitive.Portal; |
| 12 | |
| 13 | // Sheets are docked panels, not dialogs: their layer (--z-sheet) sits under dialogs that open |
| 14 | // from inside them and under anything the app floats above panels. |
| 15 | function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) { |
| 16 | return ( |
| 17 | <SheetPrimitive.Backdrop |
| 18 | className={cn( |
| 19 | "fixed inset-0 z-(--z-sheet) bg-background/60 backdrop-blur-xs transition-all duration-200 data-ending-style:opacity-0 data-starting-style:opacity-0", |
| 20 | className, |
| 21 | )} |
| 22 | data-slot="sheet-backdrop" |
| 23 | {...props} |
| 24 | /> |
| 25 | ); |
| 26 | } |
| 27 | |
| 28 | function SheetViewport({ |
| 29 | className, |
| 30 | side, |
| 31 | variant = "default", |
| 32 | ...props |
| 33 | }: SheetPrimitive.Viewport.Props & { |
| 34 | side?: "right" | "left" | "top" | "bottom"; |
| 35 | variant?: "default" | "inset"; |
| 36 | }) { |
| 37 | return ( |
| 38 | <SheetPrimitive.Viewport |
| 39 | className={cn( |
| 40 | "fixed inset-0 z-(--z-sheet) grid", |
| 41 | side === "bottom" && "grid grid-rows-[1fr_auto] pt-12", |
| 42 | side === "top" && "grid grid-rows-[auto_1fr] pb-12", |
| 43 | side === "left" && "flex justify-start", |
| 44 | side === "right" && "flex justify-end", |
| 45 | variant === "inset" && "sm:p-4", |
| 46 | className, |
| 47 | )} |
| 48 | data-slot="sheet-viewport" |
| 49 | {...props} |
| 50 | /> |
| 51 | ); |
| 52 | } |
| 53 | |
| 54 | function SheetPopup({ |
| 55 | className, |
| 56 | children, |
| 57 | showCloseButton = true, |
| 58 | keepMounted = false, |
| 59 | transitionDurationMs, |
| 60 | side = "right", |
| 61 | variant = "default", |
| 62 | style, |
| 63 | ...props |
| 64 | }: SheetPrimitive.Popup.Props & { |
| 65 | showCloseButton?: boolean; |
| 66 | keepMounted?: boolean; |
| 67 | transitionDurationMs?: number; |
| 68 | side?: "right" | "left" | "top" | "bottom"; |
| 69 | variant?: "default" | "inset"; |
| 70 | }) { |
| 71 | const transitionStyle = |
| 72 | transitionDurationMs === undefined |
| 73 | ? undefined |
| 74 | : ({ transitionDuration: `${transitionDurationMs}ms` } satisfies CSSProperties); |
| 75 | const instant = transitionDurationMs === 0; |
| 76 | |
| 77 | return ( |
| 78 | <SheetPortal keepMounted={keepMounted}> |
| 79 | <SheetBackdrop |
| 80 | className={ |
| 81 | instant |
| 82 | ? "transition-none! data-ending-style:opacity-100! data-starting-style:opacity-100!" |
| 83 | : undefined |
| 84 | } |
| 85 | style={transitionStyle} |
| 86 | /> |
| 87 | <SheetViewport side={side} variant={variant}> |
| 88 | <SheetPrimitive.Popup |
| 89 | className={cn( |
| 90 | "relative flex max-h-full min-h-0 w-full min-w-0 flex-col bg-popover not-dark:bg-clip-padding text-popover-foreground shadow-lg/5 transition-[opacity,translate] duration-200 ease-in-out will-change-transform before:pointer-events-none before:absolute before:inset-0 before:shadow-[0_1px_--theme(--color-black/4%)] data-ending-style:opacity-0 data-starting-style:opacity-0 max-sm:before:hidden dark:before:shadow-[0_-1px_--theme(--color-white/6%)]", |
| 91 | side === "bottom" && |
| 92 | "row-start-2 border-t data-ending-style:translate-y-8 data-starting-style:translate-y-8", |
| 93 | side === "top" && |
| 94 | "data-ending-style:-translate-y-8 data-starting-style:-translate-y-8 border-b", |
| 95 | side === "left" && |
| 96 | "data-ending-style:-translate-x-8 data-starting-style:-translate-x-8 w-[calc(100%-(--spacing(12)))] max-w-md border-e", |
| 97 | side === "right" && |
| 98 | "col-start-2 w-[calc(100%-(--spacing(12)))] max-w-md border-s data-ending-style:translate-x-8 data-starting-style:translate-x-8", |
| 99 | variant === "inset" && |
| 100 | "before:hidden sm:rounded-2xl sm:border sm:before:rounded-[calc(var(--radius-2xl)-1px)] sm:**:data-[slot=sheet-footer]:rounded-b-[calc(var(--radius-2xl)-1px)]", |
| 101 | instant && |
| 102 | "transition-none! will-change-auto! data-ending-style:translate-x-0! data-starting-style:translate-x-0! data-ending-style:translate-y-0! data-starting-style:translate-y-0! data-ending-style:opacity-100! data-starting-style:opacity-100!", |
| 103 | className, |
| 104 | )} |
| 105 | data-slot="sheet-popup" |
| 106 | style={{ ...transitionStyle, ...style }} |
| 107 | {...props} |
| 108 | > |
| 109 | {children} |
| 110 | {showCloseButton && ( |
| 111 | <SheetPrimitive.Close |
| 112 | aria-label="Close" |
| 113 | className="absolute end-2 top-2" |
| 114 | render={<Button size="icon" variant="ghost" />} |
| 115 | > |
| 116 | <XIcon /> |
| 117 | </SheetPrimitive.Close> |
| 118 | )} |
| 119 | </SheetPrimitive.Popup> |
| 120 | </SheetViewport> |
| 121 | </SheetPortal> |
| 122 | ); |
| 123 | } |
| 124 | |
| 125 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { |
| 126 | return ( |
| 127 | <div |
| 128 | className={cn( |
| 129 | "flex flex-col gap-2 p-6 in-[[data-slot=sheet-popup]:has([data-slot=sheet-panel])]:pb-3 max-sm:pb-4", |
| 130 | className, |
| 131 | )} |
| 132 | data-slot="sheet-header" |
| 133 | {...props} |
| 134 | /> |
| 135 | ); |
| 136 | } |
| 137 | |
| 138 | function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) { |
| 139 | return ( |
| 140 | <SheetPrimitive.Title |
| 141 | className={cn("font-semibold text-xl leading-none", className)} |
| 142 | data-slot="sheet-title" |
| 143 | {...props} |
| 144 | /> |
| 145 | ); |
| 146 | } |
| 147 | |
| 148 | function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props) { |
| 149 | return ( |
| 150 | <SheetPrimitive.Description |
| 151 | className={cn("text-muted-foreground text-sm", className)} |
| 152 | data-slot="sheet-description" |
| 153 | {...props} |
| 154 | /> |
| 155 | ); |
| 156 | } |
| 157 | |
| 158 | export { |
| 159 | Sheet, |
| 160 | SheetPortal, |
| 161 | SheetBackdrop, |
| 162 | SheetBackdrop as SheetOverlay, |
| 163 | SheetPopup, |
| 164 | SheetPopup as SheetContent, |
| 165 | SheetHeader, |
| 166 | SheetTitle, |
| 167 | SheetDescription, |
| 168 | }; |