Component
Sheet
Side panel with a title, close control and focus management.
npx -y github:anwarlemu/agentcard-brand-cli#release add sheetUsage
Use for item details, activity logs or longer forms. Choose right, left or bottom with side. Set modal={false} when the rest of the page must remain interactive.
- Sheets for a quick confirmation (use a dialog)
- Full-screen sheets on desktop
- Two sheets at once
Dependencies: window, surface, @radix-ui/react-dialog, lucide-react.
Example
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Sheet, SheetBody, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
const PURCHASES = [
{ merchant: "Delta", amount: "$412.00", status: "Approved" },
{ merchant: "Marriott", amount: "$238.40", status: "Approved" },
{ merchant: "Uber", amount: "$31.75", status: "Declined" },
];
function PurchaseLog({ defaultOpen }: { defaultOpen?: boolean }) {
return (
<Sheet defaultOpen={defaultOpen}>
<SheetTrigger asChild>
<Button variant="secondary">Purchase log</Button>
</SheetTrigger>
<SheetContent name="purchase.log">
<SheetHeader>
<SheetTitle>Travel agent</SheetTitle>
<SheetDescription>Every purchase this card made, newest first.</SheetDescription>
</SheetHeader>
<SheetBody>
<ul>
{PURCHASES.map((p) => (
<li key={p.merchant} className="flex items-center justify-between gap-4 border-b border-line py-3.5">
<span className="font-display text-body-sm">{p.merchant}</span>
<span className="flex items-center gap-3">
<span className="text-body-sm tabular-nums">{p.amount}</span>
<Badge variant={p.status === "Approved" ? "success" : "destructive"}>{p.status}</Badge>
</span>
</li>
))}
</ul>
</SheetBody>
</SheetContent>
</Sheet>
);
}
export function Site() {
return <PurchaseLog />;
}
export function Open() {
return (
<div className="h-130">
<PurchaseLog defaultOpen />
</div>
);
}Source
"use client";
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { WindowBar } from "@/components/ui/window";
import { usePortalContainer } from "@/lib/surface";
import { cn } from "@/lib/utils";
// A side panel that is one of the brand's windows (the guides' panel, .v7-guide-nav, which closes to its bar over the page): white, a 16 percent ink hairline, 12px corners, the 32px sky title bar
// (`name`) with a close mark where the [*] sits, floating 8px in from the screen's edge over the ink scrim, blurred 4px as behind the get-started dialog (.v7-gate::backdrop). It
// slides in from its side as it fades (260ms ease-out, on transform); reduced motion keeps the fade. Pass
// `modal={false}` to Sheet to keep the page behind it usable (no scrim then).
function Sheet(props: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
}
function SheetTrigger(props: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
}
function SheetClose(props: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
}
const SIDES = {
right: "inset-y-2 right-2 w-[min(28rem,calc(100vw-1rem))] starting:translate-x-full",
left: "inset-y-2 left-2 w-[min(28rem,calc(100vw-1rem))] starting:-translate-x-full",
bottom: "inset-x-2 bottom-2 max-h-[85dvh] starting:translate-y-full",
} as const;
function SheetContent({
className,
children,
side = "right",
name = "panel",
tone,
...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: keyof typeof SIDES;
/** The title bar's label, file-like: `purchase.log`, `card.detail`. */
name?: React.ReactNode;
tone?: React.ComponentProps<typeof WindowBar>["tone"];
}) {
const container = usePortalContainer();
return (
<SheetPrimitive.Portal container={container}>
<SheetPrimitive.Overlay data-slot="sheet-overlay" className="fixed inset-0 z-50 bg-scrim backdrop-blur-xs transition-opacity duration-260 ease-out starting:opacity-0" />
<SheetPrimitive.Content
data-slot="sheet-content"
className={cn(
"fixed z-50 flex flex-col overflow-hidden rounded-card border border-hairline bg-surface text-fg shadow-code outline-none transition-[opacity,translate] duration-260 ease-out starting:opacity-0 motion-reduce:starting:translate-x-0 motion-reduce:starting:translate-y-0",
SIDES[side],
className,
)}
{...props}
>
<WindowBar
name={name}
tone={tone}
actions={
<SheetPrimitive.Close
aria-label="Close"
className="-mr-1.5 grid size-7 shrink-0 place-items-center rounded-sm opacity-60 outline-none transition-[opacity,background-color] duration-150 ease-out hover:bg-wash-selected hover:opacity-100 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-0 focus-visible:outline-solid focus-visible:outline-focus-ring [&_svg]:size-3.5"
>
<X strokeWidth={1.5} />
</SheetPrimitive.Close>
}
/>
<div className="relative flex min-h-0 flex-1 flex-col">{children}</div>
</SheetPrimitive.Content>
</SheetPrimitive.Portal>
);
}
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-header" className={cn("flex flex-col gap-2 px-6 pt-6", className)} {...props} />;
}
function SheetBody({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-body" className={cn("min-h-0 flex-1 overflow-y-auto px-6 pt-4 pb-6 text-body text-fg", className)} {...props} />;
}
function SheetTitle({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Title>) {
return <SheetPrimitive.Title data-slot="sheet-title" className={cn("truncate font-display text-h3 leading-display font-medium tracking-tight text-fg", className)} {...props} />;
}
function SheetDescription({ className, ...props }: React.ComponentProps<typeof SheetPrimitive.Description>) {
return <SheetPrimitive.Description data-slot="sheet-description" className={cn("text-body-sm leading-relaxed text-fg-muted", className)} {...props} />;
}
export { Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger };Reference: agentcard-website src/app/globals.css (.v7-guide-nav, .v7-windowbar); live https://www.agentcard.sh (.v7-gate::backdrop).