Document overlay
A full-screen document reader. It opens by scaling up from the card you
clicked (a FLIP from the card's rect to a centered column) and, on close, scales
back down into that same card. Content- and router-agnostic: pass the
document as children and optional prev/next controls via the footer slot.
It renders full-screen (portaled to document.body). The back arrow, the scrim,
and the Escape key all close it.
Installation
npx shadcn@latest add https://canvas.blode.co/r/document-overlay.jsonUsage
Capture the source card's rect on click, then drive open. Give the card a
stable data-card-id (or data-item-id) matching sourceId so the overlay can
find it again to scale back into, even if it moved while open.
import { DocumentOverlay } from "@/components/canvas/document-overlay";
const [open, setOpen] = useState(false);
const [rect, setRect] = useState<DOMRect | null>(null);
<button
data-card-id="doc-1"
onClick={(e) => {
setRect(e.currentTarget.getBoundingClientRect());
setOpen(true);
}}
type="button"
>
Open
</button>
<DocumentOverlay
onClose={() => setOpen(false)}
open={open}
sourceId="doc-1"
sourceRect={rect}
title="Brand & identity"
>
<p>…document content…</p>
</DocumentOverlay>Pair it with folder-card / document-card
(whose items expose data-item-id / data-card-id) and a
pagination footer, see the live demo.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| open* | boolean | — | Whether the overlay is open. |
| children* | ReactNode | — | The document content. |
| onClose* | () => void | — | Called after the close animation completes. |
| sourceRect | DOMRect | null | — | Rect of the source card, for the scale-up FLIP. |
| sourceId | string | — | Source card id (`data-card-id`/`data-item-id`), re-found on close. |
| title | string | — | Heading rendered above the content. |
| footer | ReactNode | — | Rendered below the content once open (e.g. pagination). |
| skipEntrance | boolean | false | Skip the entrance animation (e.g. for a direct link). |
| onBeforeClose | () => void | — | Fired right before the close animation starts. |
| onExitComplete | () => void | — | Fired after the close animation completes. |