## navigation_menu (`poetry_navigation_menu`)

A site-navigation bar with links and optional dropdown panels.

Class: Poetry::Ui::NavigationMenu::Component - BEM block `poetry-ui-navigation_menu`.
- `label:` (string) - required - The nav landmark's accessible name - a page may hold more than one nav.
- `viewport:` (boolean) - default false - Opts into the shared morphing viewport: panels adopt into one positioned card that morphs size and position between triggers. Off, each panel opens under its own item (also the no-JS shape).
Slots: items (The bar entries. with_item(title, value:) { panel } declares a trigger + panel; with_item(title, href:) a top-level link (with_link is the shorthand).; many; with_item yields NOTHING to the block - no |param|, write content directly).
- PART `navigation-menu` - The <nav> landmark around the whole disclosure bar | states: data-viewport (the mode marker ("true" = shared morphing viewport, "false" = per-item panels) - the dictionary's group-data chrome keys on it)
- PART `navigation-menu-list` - The bar row holding every item
- PART `navigation-menu-item` - One bar entry - wraps a trigger + panel pair or a top-level link | states: data-value (the entry's value - the controller's open/close key)
- PART `navigation-menu-trigger` - The disclosure button opening its panel | states: data-popup-open (its panel is open (written with aria-expanded - the chevron rotation hook)); data-open (its panel is open (the controller writes both vocabularies)); data-closed (its panel is closed (written after the first close))
- PART `navigation-menu-content` - One item's panel - presence-animated; in viewport mode it is adopted into the shared viewport on first activation | states: data-open (panel is open (presence flips the pair at runtime)); data-closed (panel is closed or animating out (the server-rendered state)); data-activation-direction (which way the activation traveled between triggers (left/right, viewport mode) - keys the slide styles); data-viewport-panel (stamped once the panel is adopted into the shared viewport)
- PART `navigation-menu-positioner` - The viewport-mode shell popper positions against the active trigger | states: data-instant (suppresses the morph transitions for one painted frame (cold opens)) | vars: --positioner-width (the pinned morph width (reset to auto once the transition settles)); --positioner-height (the pinned morph height (reset to auto once the transition settles))
- PART `navigation-menu-popup` - The morphing card inside the positioner - open state and the size transition ride here | states: data-open (a panel is showing (the controller flips the pair)); data-closed (the popup is closed (the server-rendered state)); data-instant (suppresses the morph transitions for one painted frame (cold opens)); data-starting-style (the enter transition's first frame (the presence module's two-frame trick)); data-ending-style (held through the exit transition before the popup hides) | vars: --popup-width (the pinned morph width (reset to auto once the transition settles)); --popup-height (the pinned morph height (reset to auto once the transition settles))
- PART `navigation-menu-viewport` - The adoption container inside the popup - adopted panels stack absolutely in it
- PART `navigation-menu-link` - A REAL destination link - top-level (with_link) or a panel entry (poetry_navigation_menu_link) | states: data-active (the current page (active: true))
In blocks: `top-nav` - for a screen, start from the block (MCP compose/describe_block, or `bin/rails g poetry:block`), not from scratch.
- WIRING root: `poetry--core--navigation-menu` registers; actions keydown on keydown, focusLeft on focusout | `poetry--core--popper` (if viewport) registers; values side, align, side_offset, strategy
- WIRING item: `poetry--core--navigation-menu` actions scheduleOpen on pointerenter, scheduleClose on pointerleave
- WIRING trigger: `poetry--core--navigation-menu` actions toggle on click
- WIRING positioner: `poetry--core--popper` targets content | `poetry--core--navigation-menu` actions cancelClose on pointerenter, scheduleClose on pointerleave
- RULE: label: is REQUIRED (the nav landmark's accessible name).
- RULE: with_item(title, value:) declares a trigger + panel; with_link(title, href:) is a top-level destination - use links for pages, panels for groups of links.
- RULE: Panel content is poetry_navigation_menu_link entries (active: marks the current page) - never buttons; navigation navigates.
- RULE: This is a DISCLOSURE bar: Tab moves through it normally and nothing traps - do not wire menu/menuitem roles.
- RULE: Rich panels (title + description grids) want viewport: true - the shared morphing card contains and sizes them; the default per-item mode suits simple link lists (the top-nav block shows the viewport pattern).
