Clawscale

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:

  1. The container prop.
  2. portalContainer from the closest PortalProvider.
  3. 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
PropsDescription
childrenrequired
React.ReactNode
Contents to send through the portal.
className
string
A space-delimited list of class names to pass along to a child element.
Inherited from Props
container
HTMLElement = PortalProvider#portalContainer ?? document.body
The HTML element that children will be mounted to.
onChildrenMount
() => void
Callback 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+