Portal
Portal renders its children into a new element outside the current DOM tree, at the end of document.body by default. Overlays use it to escape overflow: hidden and stacking contexts. Most apps never render it directly.
Usage#
import { Portal } from "@clawscale/react";Portal creates its element after mount, so it renders nothing on the server. React events from portal content still bubble through the React tree, not the DOM tree.
Examples#
Basic#
The status card renders at the end of document.body, outside the example frame.
import { Button, Card, Classes, H6, Portal } from "@clawscale/react";
import { useState } from "react";
export default function PortalBasic() {
const [isShown, setIsShown] = useState(false);
return (
<>
<Button
icon="refresh"
text={isShown ? "Hide sync status" : "Show sync status"}
onClick={() => setIsShown((shown) => !shown)}
/>
{isShown && (
<Portal>
<Card elevation={3} style={{ position: "fixed", right: 24, bottom: 24, width: 300, zIndex: 20 }}>
<H6>Syncing 3 datasets to eu-west-1</H6>
<p className={Classes.TEXT_MUTED}>orders_daily, events_raw and customers. 64% done.</p>
</Card>
</Portal>
)}
</>
);
}Container#
container sets the element that receives the portal. The tags are declared in the editor and render in the inspector.
Pipeline editor
The tags are declared here.
Inspector
import { Card, H6, Portal, Tag } from "@clawscale/react";
import { useState } from "react";
export default function PortalContainer() {
const [target, setTarget] = useState<HTMLDivElement | null>(null);
return (
<div style={{ display: "grid", gap: 12, gridTemplateColumns: "1fr 1fr", width: "100%", maxWidth: 520 }}>
<Card compact>
<H6>Pipeline editor</H6>
<p>The tags are declared here.</p>
{target && (
<Portal container={target}>
<Tag intent="primary" minimal>
orders_daily
</Tag>{" "}
<Tag minimal>events_raw</Tag>
</Portal>
)}
</Card>
<Card compact>
<H6>Inspector</H6>
<div ref={setTarget} style={{ position: "relative", minHeight: 24 }} />
</Card>
</div>
);
}Where portals render#
Portal picks its parent element in this order:
- The
containerprop. portalContainerfrom the closest PortalProvider.document.body.
The new element gets the bp6-portal class, your className and the portalClassName of the closest PortalProvider. It is absolutely positioned at the top of its parent.
Options for the whole app#
ClawscaleProvider renders a PortalProvider. Pass portal options to ClawscaleProvider to change the portals of every popover, tooltip, dialog and drawer.
<ClawscaleProvider portalClassName="app-portal">{children}</ClawscaleProvider>Props interface#
interface PortalProps extends Props@clawscale/react| Props | Description |
|---|---|
childrenrequired | React.ReactNodeContents to send through the portal. |
className | stringA space-delimited list of class names to pass along to a child element. Inherited from Props |
container | HTMLElement = PortalProvider#portalContainer ?? document.bodyThe HTML element that children will be mounted to. |
onChildrenMount | () => voidCallback invoked when the children of this Portal have been added to the DOM. |
stopPropagationEventsdeprecated | Array<keyof HTMLElementEventMap>A list of DOM events which should be stopped from propagating through this portal element. this prop's implementation no longer works in React v17+ |