Components
Popover
Every popover in the product: one plate, one motion, two primitives. A menu is a list of choices and gets Radix's roving focus, typeahead and radio semantics; a panel holds arbitrary content and behaves like a small dialog. One component wrapping both would mean picking which behaviours a caller silently got.
Menu
MenuContent wraps Radix's DropdownMenu content — portalled and already
dressed, because a menu that renders inside the dialog or scroller that opened
it is clipped by it. MenuRadioItem puts the tick on the trailing edge: the
mark for “this one” comes after the thing it marks, and alignment on the
leading edge was already paid for by the row itself. Its icon slot is for
menus whose rows are a shape before they are a word. Rows that cannot be radio
items take the exported MENU_ROW class.
import { DropdownMenu } from "radix-ui";
import { MenuContent, MenuRadioItem } from "@/components/base/popover";
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>…</DropdownMenu.Trigger>
<MenuContent align="start">
<DropdownMenu.RadioGroup value={sort} onValueChange={setSort}>
<MenuRadioItem value="updated">Recently updated</MenuRadioItem>
…
</DropdownMenu.RadioGroup>
</MenuContent>
</DropdownMenu.Root>Panel
PanelContent is the same plate and the same motion around content that is
not a list of choices — the chat record, the side menu's More panel. A larger corner
(1.25rem against the menu's 0.75rem), the same z-[1000] surface.
import { Popover } from "radix-ui";
import { PanelContent } from "@/components/base/popover";
<Popover.Root>
<Popover.Trigger asChild>…</Popover.Trigger>
<PanelContent align="start" className="w-[16rem] p-4">…</PanelContent>
</Popover.Root>A panel that opens on hover — the download QR, hanging off a row or a pill the
pointer is already over — is HoverPanelContent: the same plate and motion on
Radix's HoverCard instead of its Popover.
Motion
Both primitives grow from the corner Radix actually placed them against — the
transform origin is Radix's own computed variable, written out per primitive
because Tailwind finds classes by scanning source text, and an interpolated
origin-[var(--radix-…)] would emit nothing and the panels would scale from
their own centres. For a popover Radix is not positioning — a panel anchored
to its own button — POPOVER_MOTION exports the pair alone, and whatever
drives data-state must keep the element mounted for the length of the closed
half.