"use client";
import { type ComponentProps } from "react";
import { Dialog as SheetPrimitive } from "@base-ui/react/dialog";
import { Button } from "./button";
import { cn } from "./cn";
/*
* A Sheet is a Dialog pinned to an edge, and it is built on the same primitive
* for that reason — focus capture, focus return, Escape, outside click, and
* inerting the background are the modal behaviours a drawer needs too, and none
* of them are re-implemented here.
*
* Both products already ship exactly this anatomy under exactly these names, so
* the shape is theirs rather than invented: ten parts, a `side` defaulting to
* right, three-quarter width capped at `sm` on wider viewports, and a close
* button in the top corner.
*/
function Sheet(props: SheetPrimitive.Root.Props) {
return ;
}
function SheetTrigger(props: SheetPrimitive.Trigger.Props) {
return ;
}
function SheetPortal(props: SheetPrimitive.Portal.Props) {
return ;
}
function SheetClose(props: SheetPrimitive.Close.Props) {
return ;
}
function SheetOverlay({ className, ...props }: SheetPrimitive.Backdrop.Props) {
return (
);
}
function XIcon() {
return (
);
}
/*
* Side styling is a lookup rather than `data-[side=…]:` variants, and that is
* not a style preference. A variant-prefixed `data-[side=right]:w-3/4` and a
* caller's plain `w-72` are different groups to tailwind-merge, so both survive
* the merge — and the prefixed one wins on specificity, silently defeating the
* override. Both products pass a width (`w-72`, `sm:max-w-md`, `w-[380px]`) and
* all of them were being ignored until this became a lookup. The `data-side`
* attribute stays for styling hooks and tests; it just does not carry layout.
*
* A full-width translate rather than a fixed nudge: the sheet leaves by the
* edge it is pinned to, which is what makes it read as a drawer instead of a
* dialog that fades.
*/
const sideClasses = {
right:
"inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm " +
"data-starting-style:translate-x-full data-ending-style:translate-x-full",
left:
"inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm " +
"data-starting-style:-translate-x-full data-ending-style:-translate-x-full",
top:
"inset-x-0 top-0 h-auto border-b " +
"data-starting-style:-translate-y-full data-ending-style:-translate-y-full",
bottom:
"inset-x-0 bottom-0 h-auto border-t " +
"data-starting-style:translate-y-full data-ending-style:translate-y-full",
} as const;
export interface SheetContentProps extends SheetPrimitive.Popup.Props {
/** Edge the sheet is pinned to. @default "right" */
side?: "top" | "right" | "bottom" | "left";
/** @default true */
showCloseButton?: boolean;
/** See DialogContent — retargets the portal so brand tokens are inherited. */
container?: SheetPrimitive.Portal.Props["container"];
}
function SheetContent({
className,
children,
side = "right",
showCloseButton = true,
container,
...props
}: SheetContentProps) {
return (
{children}
{showCloseButton && (
}
>
Close
)}
);
}
function SheetHeader({ className, ...props }: ComponentProps<"div">) {
return (