Skip to content
Source
Sign inSign up
Open History to see the last commit

sheet.tsx

172 lines5.7 KB

1import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
2import { XIcon } from "lucide-react";
3import type { CSSProperties } from "react";
4import { cn } from "~/lib/utils";
5import { Button } from "~/components/ui/button";
6import { ControlledOpenContext, renderRetainedPopup } from "~/components/ui/dialog";
7​
8function Sheet<Payload>(props: SheetPrimitive.Root.Props<Payload>) {
9 return (
10 <ControlledOpenContext value={props.open}>
11 <SheetPrimitive.Root {...props} />
12 </ControlledOpenContext>
13 );
15​
16const SheetPortal = SheetPrimitive.Portal;
17​
18function SheetBackdrop({ className, ...props }: SheetPrimitive.Backdrop.Props) {
19 return (
20 <SheetPrimitive.Backdrop
21 className={cn(
22 "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",
23 className,
24 )}
25 data-slot="sheet-backdrop"
26 {...props}
27 />
28 );
30​
31function SheetViewport({
32 className,
33 side,
34 variant = "default",
35 ...props
36}: SheetPrimitive.Viewport.Props & {
37 side?: "right" | "left" | "top" | "bottom";
38 variant?: "default" | "inset";
39}) {
40 return (
41 <SheetPrimitive.Viewport
42 className={cn(
43 "fixed inset-0 z-(--z-sheet) grid [-webkit-app-region:no-drag]",
44 side === "bottom" && "grid grid-rows-[1fr_auto] pt-12",
45 side === "top" && "grid grid-rows-[auto_1fr] pb-12",
46 side === "left" && "flex justify-start",
47 side === "right" && "flex justify-end",
48 variant === "inset" && "sm:p-4",
49 className,
50 )}
51 data-slot="sheet-viewport"
52 {...props}
53 />
54 );
56​
57function SheetPopup({
58 className,
59 children,
60 showCloseButton = true,
61 keepMounted = false,
62 transitionDurationMs,
63 side = "right",
64 variant = "default",
65 style,
66 ...props
67}: SheetPrimitive.Popup.Props & {
68 showCloseButton?: boolean;
69 keepMounted?: boolean;
70 transitionDurationMs?: number;
71 side?: "right" | "left" | "top" | "bottom";
72 variant?: "default" | "inset";
73}) {
74 const transitionStyle =
75 transitionDurationMs === undefined
76 ? undefined
77 : ({ transitionDuration: `${transitionDurationMs}ms` } satisfies CSSProperties);
78 const instant = transitionDurationMs === 0;
79​
80 return (
81 <SheetPortal keepMounted={keepMounted}>
82 <SheetBackdrop
83 className={
84 instant
85 ? "transition-none! data-ending-style:opacity-100! data-starting-style:opacity-100!"
86 : undefined
87 }
88 style={transitionStyle}
89 />
90 <SheetViewport side={side} variant={variant}>
91 <SheetPrimitive.Popup
92 className={cn(
93 "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%)]",
94 side === "bottom" &&
95 "row-start-2 border-t data-ending-style:translate-y-8 data-starting-style:translate-y-8",
96 side === "top" &&
97 "data-ending-style:-translate-y-8 data-starting-style:-translate-y-8 border-b",
98 side === "left" &&
99 "data-ending-style:-translate-x-8 data-starting-style:-translate-x-8 w-[calc(100%-(--spacing(12)))] max-w-md border-e",
100 side === "right" &&
101 "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",
102 variant === "inset" &&
103 "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)]",
104 instant &&
105 "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!",
106 className,
107 )}
108 data-slot="sheet-popup"
109 render={keepMounted ? undefined : renderRetainedPopup}
110 style={{ ...transitionStyle, ...style }}
111 {...props}
112 >
113 {children}
114 {showCloseButton && (
115 <SheetPrimitive.Close
116 aria-label="Close"
117 className="absolute end-2 top-2"
118 render={<Button size="icon" variant="ghost" />}
119 >
120 <XIcon />
121 </SheetPrimitive.Close>
122 )}
123 </SheetPrimitive.Popup>
124 </SheetViewport>
125 </SheetPortal>
126 );
128​
129function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
130 return (
131 <div
132 className={cn(
133 "flex flex-col gap-2 p-6 in-[[data-slot=sheet-popup]:has([data-slot=sheet-panel])]:pb-3 max-sm:pb-4",
134 className,
135 )}
136 data-slot="sheet-header"
137 {...props}
138 />
139 );
141​
142function SheetTitle({ className, ...props }: SheetPrimitive.Title.Props) {
143 return (
144 <SheetPrimitive.Title
145 className={cn("font-semibold text-xl leading-snug", className)}
146 data-slot="sheet-title"
147 {...props}
148 />
149 );
151​
152function SheetDescription({ className, ...props }: SheetPrimitive.Description.Props) {
153 return (
154 <SheetPrimitive.Description
155 className={cn("text-muted-foreground text-sm", className)}
156 data-slot="sheet-description"
157 {...props}
158 />
159 );
161​
162export {
163 Sheet,
164 SheetPortal,
165 SheetBackdrop,
166 SheetBackdrop as SheetOverlay,
167 SheetPopup,
168 SheetPopup as SheetContent,
169 SheetHeader,
170 SheetTitle,
171 SheetDescription,
172};