feat(evo-react): add menu - #787
Conversation
🦋 Changeset detectedLatest commit: 6b47f72 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new EvoMenu compound component to @evo-web/react (with command, radio, and checkbox items sharing a single roving-tabindex + typeahead system), aligns accessible naming for evo-menu in Marko to use a11yText/a11yLabelId, and updates Skin menu badge positioning to work without a dedicated “badged” modifier class.
Changes:
- Add
@evo-web/react/menucompound component implementation (context, items, groups, item variants, badge/separator/footer) plus shareduseRovingTabIndex+useTypeaheadutilities. - Update Skin menu badge positioning to support composed badges via
:has(.badge)and regenerate dist. - Update Marko
evo-menuaccessible-name API and snapshots; add React + Marko tests/stories and migration guidance.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/skin/src/sass/menu/stories/misc.stories.js | Updates menu “badged” examples to no longer rely on menu__item--badged. |
| packages/skin/src/sass/menu/menu.scss | Adds :has(.badge) styling to position menu badges without needing --badged. |
| packages/skin/dist/menu/menu.css | Regenerated dist CSS reflecting the :has(.badge) selector changes. |
| packages/evo-react/src/utils/use-typeahead.ts | Adds shared typeahead hook used by menu keyboard navigation. |
| packages/evo-react/src/utils/use-roving-tab-index.ts | Adds shared roving tabindex hook used by menu items. |
| packages/evo-react/src/menu/types.ts | Defines public React menu types (props, events, group APIs). |
| packages/evo-react/src/menu/menu.tsx | Adds EvoMenu root + provider wiring and Skin import. |
| packages/evo-react/src/menu/context.tsx | Adds React contexts for menu + items + selection groups. |
| packages/evo-react/src/menu/menu-items.tsx | Implements the role="menu" container with typeahead + roving handlers. |
| packages/evo-react/src/menu/menu-item.tsx | Implements command/radio/checkbox items with shared activation + focus behavior. |
| packages/evo-react/src/menu/menu-radio-group.tsx | Adds controlled/uncontrolled radio selection provider. |
| packages/evo-react/src/menu/menu-checkbox-group.tsx | Adds controlled/uncontrolled checkbox selection provider. |
| packages/evo-react/src/menu/menu-item-badge.tsx | Adds composed badge wrapper for menu usage. |
| packages/evo-react/src/menu/menu-separator.tsx | Adds separator component. |
| packages/evo-react/src/menu/menu-footer.tsx | Adds footer component. |
| packages/evo-react/src/menu/index.ts | Exports menu public API. |
| packages/evo-react/src/menu/menu.stories.tsx | Adds Storybook docs/examples for the compound API. |
| packages/evo-react/src/menu/README.md | Links to Storybook docs for EvoMenu. |
| packages/evo-react/src/menu/test/test.server.tsx | Adds SSR snapshot coverage for menu variants/structure. |
| packages/evo-react/src/menu/test/test.browser.tsx | Adds browser interaction coverage (roving, typeahead, selection, disabled). |
| packages/evo-react/src/menu/test/snapshots/test.server.tsx.snap | Adds SSR snapshots for EvoMenu. |
| packages/evo-marko/src/tags/evo-menu/index.marko | Updates Marko menu accessible-name API to a11yText/a11yLabelId. |
| packages/evo-marko/src/tags/evo-menu/menu.stories.ts | Adds Storybook controls for new a11y inputs. |
| packages/evo-marko/src/tags/evo-menu/test/test.server.ts | Updates Marko SSR tests to use a11yText/a11yLabelId. |
| packages/evo-marko/src/tags/evo-menu/test/snapshots/test.server.ts.snap | Updates Marko snapshots for renamed a11y inputs. |
| packages/evo-marko/src/tags/evo-menu-button/index.marko | Adjusts menu-button typing to continue supporting aria-label/labelledby externally. |
| .claude/skills/evo-app-migrate-react/SKILL.md | Adds ebay-menu → evo-menu migration doc entry. |
| .claude/skills/evo-app-migrate-react/components/evo-menu.md | Adds detailed migration guidance for menu usage + new APIs. |
| .changeset/add-evo-menu.md | Changeset for adding EvoMenu + Skin/a11y API alignment. |
| className={classNames( | ||
| classPrefix ? `${baseClass}__menu` : "menu", | ||
| variant === "filter" && `${baseClass}--filter`, | ||
| classPrefix && reverse && `${baseClass}__menu--reverse`, | ||
| classPrefix && fixed && `${baseClass}__menu--fixed`, | ||
| classPrefix && fixWidth && `${baseClass}__menu--fix-width`, | ||
| className, | ||
| )} |
PR Preview DeployedWebsite • commit 6b47f72 |
# Conflicts: # .claude/skills/evo-app-migrate-react/SKILL.md
# Conflicts: # .claude/skills/evo-app-migrate-react/SKILL.md
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… evo-menu-react
# Conflicts: # packages/evo-react/src/utils/use-roving-tab-index.ts
# Conflicts: # .claude/skills/evo-app-migrate-react/SKILL.md
Description
Adds
EvoMenuto@evo-web/reactas an extensible compound component with command, radio, and checkbox items sharing one menu-wide roving tabindex and typeahead controller.API approach
We considered three approaches:
1. EvoMenuItem single component
Based on the
selectedproperty being passed as array or one item, it does define the type of the componentPros:
Cons:
valuerequired for radio/checkbox (it is necessary when removing React.Children api usage)2.
EvoMenuItem,EvoMenuRadioItem, andEvoMenuCheckboxItemcomponentsExpose explicit
EvoMenuItem,EvoMenuRadioItem, andEvoMenuCheckboxItemcomponents, with provider-only radio and checkbox selection groups.Pros:
Cons:
This PR uses the second approach. Separate item components make each semantic type explicit, require
valueonly for selectable items, expose the correct ARIA role without runtime child inspection, and avoid a growing matrix of conditional props and rendering branches. They also allow command, radio, and checkbox items to participate in one menu while their groups own only controlled/uncontrolled selection state. Shared focus, activation, disabled handling, and keyboard behavior remain centralized internally.Additional changes:
useRovingTabIndexanduseTypeheadhooksa11yTextanda11yLabelIdconsistently in React and Marko for the menu's accessible name.EvoMenuItemBadgereceives existing badge positioning through:has(.badge).Screenshots
Storybook link
Checklist