feat(eidos): port Menubar (desktop File/Edit/View bar)
#129. Eidos wrapper + types + recipe + canonical-depth demo. Soma owns
active-menu state, cross-menu ArrowLeft/Right navigation, hover-switch
when active, focus return on Escape, typeahead, RTL. Eidos paints the
bar surface and trigger states; per-menu Content reuses the
dropdown-menu recipe (Item / Sub / Checkbox / Radio chrome + the shared
menu-indicator partial from `a2709476`) because soma already
re-exports the DropdownMenu parts for those slots.
Reference parity:
| Feature | Radix | bits-ui | ark-ui | this port |
|---|---|---|---|---|
| Root provider w/ active-menu binding | ✓ | ✓ | ✗ (Menu only) | ✓ |
| Menu / Trigger / Content | ✓ | ✓ | ✓ | ✓ |
| Item / Group / GroupHeading / Separator | ✓ | ✓ | ✓ | ✓ |
| CheckboxItem / CheckboxGroup / RadioGroup / RadioItem | ✓ | ✓ | ✓ | ✓ |
| Sub / SubTrigger / SubContent | ✓ | ✓ | ✓ | ✓ |
| Cross-menu ArrowLeft/Right (with loop) | ✓ | ✓ | n/a | ✓ |
| Auto-open on hover when active | ✓ | ✓ | n/a | ✓ |
| Typeahead | ✓ | ✓ | ✓ | ✓ |
| Arrow part | ✓ | ✓ | ✓ | ⚠ dropped — menubar idiom anchors to bar edge |
Parts shipped (15): Provider, Menu, Trigger, Portal, Content, Item,
Group, GroupHeading, Separator, Arrow (re-exported), CheckboxItem,
CheckboxGroup, RadioGroup, RadioItem, Sub, SubTrigger, SubContent. The
inner 12 (Item onwards) are re-exports of the eidos DropdownMenu
wrappers so the menu-indicator partial applies automatically — no
recipe duplication.
Variant set: only `size: 'sm' | 'md' | 'lg'` (xs would crowd triggers,
xl unnecessary for a chrome bar). Size cascades from Menubar root to
per-menu Content via a Svelte context (`menubar-context.ts`,
`getMenubarSizeContext` / `setMenubarSizeContext`).
Cross-layer: eidos Menubar reuses eidos DropdownMenu internals — the
recipe doesn't redefine Item/Sub/Checkbox/Radio. Menubar.Content stamps
`data-size` AND inherits `data-dropdown-menu-content` (soma's
MenubarContent specializes DropdownMenuContent), so dropdown-menu.css
paints the panel chrome and our menubar.css only declares the bar
surface + trigger states.
Workaround note: menubar-content.svelte casts rest props through
`Record<string, unknown>` to bypass a TS "union too complex" depth
limit triggered by soma's inline-augmented MenuContentProps + HTML
attribute union. Runtime types are correct.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>