MorphingPopover
Morph a trigger element into a centred dialog panel using a shared layout animation.
MorphingPopover is a client component that uses a shared motion/react layoutId to animate a trigger into a panel. Use it for short, focused tasks started from a single button, such as sharing or renaming. The panel is portalled to document.body and centred in a fixed full-screen overlay, rather than anchored to the trigger.
Import#
import { MorphingPopover } from "@reactberry/system/blocks";
Usage#
import { MorphingPopover } from "@reactberry/system/blocks";
import { Box, Button, Text } from "@reactberry/system/elements";
export default function ShareAction() {
return (
<MorphingPopover
trigger={
<Button variant="primary" shape="pill">
<Text>Share</Text>
</Button>
}
panelProps={{ width: "20rem", p: "medium" }}
>
{({ close }) => (
<Box display="flex" flexDirection="column" gap="small">
<Text fontWeight="600">Share this page</Text>
<Button variant="ghost" onClick={close}>
<Text>Done</Text>
</Button>
</Box>
)}
</MorphingPopover>
);
}
Examples#
Controlled#
Pass open and onOpenChange to own the state, for example to reset a draft each time the panel opens or to close it after saving.
Custom trigger#
renderTrigger receives an onClick that opens the panel. Wire it to your element yourself; it is not called automatically.
API#
MorphingPopover#
| Prop | Type | Default | Description |
|---|---|---|---|
trigger | ReactNode | — | Trigger element shown when the popover is closed. |
triggerProps | Record<string, any> | — | Props spread onto the wrapper |
placement | string | bottom end | Optional placement hint (kept for API compatibility; currently ignored). |
panelProps | Record<string, any> | — | Props forwarded to the morphing panel container. |
containerProps | Record<string, any> | — | Props forwarded to the outer container Box. |
renderTrigger | ((props: MorphingPopoverRenderTriggerProps) => ReactNode) | — | Custom trigger renderer for full control over the trigger element. Signature mirrors the standard Popover API. |
children | ReactNode | ((context: { close: () => void; }) => ReactNode) | — | Popover content. If a function, receives a close() helper. |
transition | Transition | {… | Motion transition applied via MotionConfig. |
variants | Variants | {… | Framer Motion variants for the morphing panel. |
defaultOpen | boolean | false | Uncontrolled initial open state. |
open | boolean | — | Controlled open state. |
onOpenChange | ((open: boolean) => void) | — | Callback when open state changes. |
A valid trigger element is recreated with motion.create and given the shared layoutId. Clicking it calls its own onClick and then opens the panel unless event.preventDefault() was called. panelProps override the panel defaults (skin="surface", shape="rounded", p="xsmall", $shadow="medium"); containerProps override the outer Box defaults (display="inline-flex", position="relative"). Remaining props are not spread.
Keyboard#
| Key | Action |
|---|---|
Escape | Closes the panel while it is open. The listener is on `document`. |
Accessibility#
- The panel has
role="dialog"andaria-modal="true", but focus is not moved into or trapped within it, and is not returned to the trigger on close. - The panel has no accessible name; pass
aria-labeloraria-labelledbythroughpanelProps. - The trigger gets no
aria-expandedoraria-controls.
Notes#
- A
mousedownortouchstartoutside the panel closes it. - The overlay uses
zIndex="999999". A transparentUnderlaybehind the panel blurs the page (12px backdrop blur, masked to fade out toward the bottom). - The trigger remains mounted while the panel is open; the shared
layoutIddrives the morph between them.