## popover (`poetry_popover`)

Rich floating content anchored to a trigger.

Class: Poetry::Ui::Popover::Component - BEM block `poetry-ui-popover`.
Slot REQUIRED: with_trigger (the panel's control) - a call without it raises.
- `align:` (symbol) - one of start|center|end, default "center" - Panel alignment along the chosen side.
- `align_offset:` (integer) - default 0 - Shift in pixels along the alignment axis.
- `avoid_collisions:` (boolean) - default true - Flips and shifts the panel to stay inside the viewport.
- `content_class:` (string) - Class merge seam for the panel itself (e.g. widen the default with content_class: "w-80") - root-level class: styles the wrapper, not the panel.
- `label:` (string) - role=dialog fallback name when no title part is present.
- `modal:` (boolean) - default false - Reserves interaction for the panel while open; the default keeps the rest of the page interactive.
- `open:` (boolean) - default false - Server-renders the panel open.
- `side:` (symbol) - one of top|right|bottom|left, default "bottom" - Which side of the anchor the panel opens on.
- `side_offset:` (integer) - default 4 - Gap in pixels between the anchor and the panel.
Slots: trigger (The control that opens the panel - a composed Button. The slot owns the aria-haspopup/expanded/controls wiring regardless of the composed content, so composition cannot drop the aria; aria-controls renders even while closed (the stable id is the wiring's resolution seam).; takes poetry_button props, not a block; with_trigger yields NOTHING to the block - no |param|, write content directly), anchor (Optional alternate anchor: when present, the panel positions against IT instead of the trigger (targets beat selectors in the positioning fallback chain).; with_anchor yields NOTHING to the block - no |param|, write content directly), title (Panel heading - presence wires the content's aria-labelledby, so the title names the dialog.), description (Supporting text - presence wires the content's aria-describedby.).
- PART `popover` - Root wrapper around the trigger, the optional anchor, and the panel
- PART `popover-anchor` - Optional alternate popper anchor - when present the panel positions against it instead of the trigger
- PART `popover-content` - The role=dialog panel - positioning, animation, and the open state ride here | states: data-open (panel is open (the controller flips the pair at runtime)); data-closed (panel is closed (the server-rendered state; hidden rides along)); data-side=top|right|bottom|left (always - the side (initial placement, re-resolved live by popper after flip)); data-align=start|center|end (always - the alignment (re-resolved live by popper)) | vars: --transform-origin (the anchor-facing origin popper writes for scale-in animation); --available-width (viewport space left for the panel (popper, post-flip)); --available-height (viewport space left for the panel (popper, post-flip)); --anchor-width (the anchor's measured width (popper)); --anchor-height (the anchor's measured height (popper))
- PART `popover-header` - Title block wrapping the title and description (renders only when either is present)
- PART `popover-title` - The heading - the panel's accessible name via aria-labelledby
- PART `popover-description` - Muted copy under the title, wired to aria-describedby
- WIRING root: `poetry--core--popover` registers; values open, modal | `poetry--core--popper` registers; values anchor (unless anchor?), side, align, side_offset, align_offset, avoid_collisions
- WIRING trigger: `poetry--core--popover` actions toggle on click
- WIRING anchor_part: `poetry--core--popper` targets anchor
- WIRING content: `poetry--core--popper` targets content
- RULE: with_trigger(compose: true) { |wiring| ... } composes YOUR control as the trigger: the block is yielded the trigger wiring (the Stimulus behavior the overlay needs; poppers add id/aria and their trigger slot, modals hand only the open action) - splat it onto a wiring-free control (poetry_sidebar_menu_button, a plain tag); without compose: the classic composed Button renders.
- RULE: Use poetry_popover - never hand-roll an anchored role=dialog panel with Tailwind.
- RULE: Popover content is INTERACTIVE - for text-only hover hints use Tooltip; for pointer-only previews use HoverCard.
- RULE: Give the panel a name: use with_title (preferred) or label: - a role=dialog without a name fails the audit.
- RULE: Icon-only triggers MUST have an accessible name (the composed Button's label: rule).
- RULE: Default is NON-modal (modal: false) - reach for modal: true only when stray outside interaction would corrupt the task; reach for Dialog when the task deserves full modality.
- RULE: Critical-path panels must also be reachable without JS (full page or server-rendered open: true) - popovers are JS-required interaction.
- RULE: Do not nest a Popover inside a Popover - restructure (the layer stack allows it; comprehension does not).
