Popover
A floating panel anchored to a trigger for arbitrary controls and content.
Basic
Show codeHide 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 codeHide 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 codeHide 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 codeHide 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
| Prop | Type | Required | Description |
|---|---|---|---|
| open | boolean | No | Whether the popover is open. Controlled; pair with `onOpenChange`. |
| defaultOpen | boolean | No | Whether the popover is open on mount. Uncontrolled. |
| onOpenChange | ((open: boolean, eventDetails: PopoverRootChangeEventDetails) => void) | No | Called with the next open state when the popover opens or closes. |
| modal | boolean | "trap-focus" | No | Modal behavior while open. |
| children | ReactNode | PayloadChildRenderFunction<Payload> | No | The popover's trigger and panel (`PopoverTrigger`, `PopoverContent`). |
PopoverContent
| Prop | Type | Required | Description |
|---|---|---|---|
| render | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, PopoverPopupState> | No | Allows 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. |
| side | undefined | "top" | "bottom" | "left" | "right" | "inline-end" | "inline-start" | Yes | Which side of the trigger the panel opens toward. The spec's adjustable placement axis. |
| align | undefined | "center" | "start" | "end" | Yes | Alignment of the panel relative to the trigger along `side`. The spec's alignment axis. |
| sizing | "auto" | "anchor" | "sm" | "md" | "lg" | Yes | How the panel sizes itself. The spec's adjustable width axis. |
| sideOffset | number | OffsetFunction | No | Distance in px between the trigger and the panel. |