Propel

Popover

A floating panel anchored to a trigger for arbitrary controls and content.

Basic

Show code
import { Button } from "@makeplane/propel/components/button";
import { Checkbox } from "@makeplane/propel/components/checkbox";
import { Popover, PopoverContent, PopoverTrigger } from "@makeplane/propel/components/popover";
import * as React from "react";

export default function BasicDemo() {
  const [showSubItems, setShowSubItems] = React.useState(true);
  const [showEmptyGroups, setShowEmptyGroups] = React.useState(false);

  return (
    <Popover>
      <Button
        sizing="hug"
        prominence="secondary"
        tone="neutral"
        magnitude="xl"
        render={<PopoverTrigger />}
        label="Options"
      />
      <PopoverContent side="bottom" align="start" sizing="md" aria-label="Options">
        <Checkbox
          sizing="fill"
          checked={showSubItems}
          onCheckedChange={(next) => setShowSubItems(Boolean(next))}
          label="Show sub-work items"
        />
        <Checkbox
          sizing="fill"
          checked={showEmptyGroups}
          onCheckedChange={(next) => setShowEmptyGroups(Boolean(next))}
          label="Show empty groups"
        />
      </PopoverContent>
    </Popover>
  );
}

Open on hover

PopoverTrigger’s openOnHover (with a tuned delay) opens the panel on hover for glanceable, read-mostly surfaces. Clicking the trigger still toggles the panel, so keyboard and touch users are not locked out.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Checkbox } from "@makeplane/propel/components/checkbox";
import { Popover, PopoverContent, PopoverTrigger } from "@makeplane/propel/components/popover";
import * as React from "react";

export default function OpenOnHoverDemo() {
  const [inProgress, setInProgress] = React.useState(true);
  const [assignedToMe, setAssignedToMe] = React.useState(true);

  return (
    <Popover>
      <Button
        sizing="hug"
        prominence="secondary"
        tone="neutral"
        magnitude="xl"
        render={<PopoverTrigger openOnHover delay={100} />}
        label="Filters"
      />
      <PopoverContent side="bottom" align="start" sizing="md" aria-label="Active filters">
        <Checkbox
          sizing="fill"
          checked={inProgress}
          onCheckedChange={(next) => setInProgress(Boolean(next))}
          label="State: In progress"
        />
        <Checkbox
          sizing="fill"
          checked={assignedToMe}
          onCheckedChange={(next) => setAssignedToMe(Boolean(next))}
          label="Assignee: You"
        />
      </PopoverContent>
    </Popover>
  );
}

Controlled

External state drives the popover via open and onOpenChange, so any control can open or close the panel.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Checkbox } from "@makeplane/propel/components/checkbox";
import { Popover, PopoverContent, PopoverTrigger } from "@makeplane/propel/components/popover";
import * as React from "react";

export default function ControlledDemo() {
  const [open, setOpen] = React.useState(false);
  const [showSubItems, setShowSubItems] = React.useState(true);

  return (
    <div className="flex flex-wrap items-center gap-3">
      <Popover open={open} onOpenChange={(next) => setOpen(next)}>
        <Button
          sizing="hug"
          prominence="secondary"
          tone="neutral"
          magnitude="xl"
          render={<PopoverTrigger />}
          label="Display"
        />
        <PopoverContent side="bottom" align="start" sizing="md" aria-label="Display options">
          <Checkbox
            sizing="fill"
            checked={showSubItems}
            onCheckedChange={(next) => setShowSubItems(Boolean(next))}
            label="Show sub-work items"
          />
        </PopoverContent>
      </Popover>
      <Button
        sizing="hug"
        prominence="tertiary"
        tone="neutral"
        magnitude="xl"
        onClick={() => setOpen((prev) => !prev)}
        label={open ? "Close panel" : "Open panel"}
      />
    </div>
  );
}

Detached trigger

createPopoverHandle() links a trigger that lives outside the Popover to it via the handle prop, so a button can open a panel declared elsewhere in the tree. The panel still anchors to the trigger that opened it.

Show code
import { Button } from "@makeplane/propel/components/button";
import { Checkbox } from "@makeplane/propel/components/checkbox";
import {
  createPopoverHandle,
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@makeplane/propel/components/popover";
import * as React from "react";

// A handle created outside the React tree links a detached trigger to its popover.
const displayPopoverHandle = createPopoverHandle();

export default function DetachedTriggerDemo() {
  const [showSubItems, setShowSubItems] = React.useState(true);

  return (
    <div className="flex flex-wrap items-center gap-3">
      {/* The trigger lives outside the Popover, linked through the shared handle. */}
      <Button
        sizing="hug"
        prominence="secondary"
        tone="neutral"
        magnitude="xl"
        render={<PopoverTrigger handle={displayPopoverHandle} />}
        label="Display"
      />
      {/* The popover is declared separately and associated by `handle`. */}
      <Popover handle={displayPopoverHandle}>
        <PopoverContent side="bottom" align="end" sizing="md" aria-label="Display options">
          <Checkbox
            sizing="fill"
            checked={showSubItems}
            onCheckedChange={(next) => setShowSubItems(Boolean(next))}
            label="Show sub-work items"
          />
        </PopoverContent>
      </Popover>
    </div>
  );
}

Installation

import { Popover, PopoverTrigger, PopoverContent } from "@makeplane/propel/components/popover";

Props

Popover

PropTypeRequiredDescription
openbooleanNoWhether the popover is open. Controlled; pair with `onOpenChange`.
defaultOpenbooleanNoWhether the popover is open on mount. Uncontrolled.
onOpenChange((open: boolean, eventDetails: PopoverRootChangeEventDetails) => void)NoCalled with the next open state when the popover opens or closes.
modalboolean | "trap-focus"NoModal behavior while open.
childrenReactNode | PayloadChildRenderFunction<Payload>NoThe popover's trigger and panel (`PopoverTrigger`, `PopoverContent`).

PopoverContent

PropTypeRequiredDescription
renderReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverPopupState>NoAllows you to replace the component's HTML element with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.
sideundefined | "top" | "bottom" | "left" | "right" | "inline-end" | "inline-start"YesWhich side of the trigger the panel opens toward. The spec's adjustable placement axis.
alignundefined | "center" | "start" | "end"YesAlignment of the panel relative to the trigger along `side`. The spec's alignment axis.
sizing"auto" | "anchor" | "sm" | "md" | "lg"YesHow the panel sizes itself. The spec's adjustable width axis.
sideOffsetnumber | OffsetFunctionNoDistance in px between the trigger and the panel.