From 2c3728ab38e78cd0e0780d440173e3bf0d2ebb32 Mon Sep 17 00:00:00 2001 From: HenriqueLimas Date: Mon, 3 Aug 2026 16:02:02 -0700 Subject: [PATCH 1/5] feat(evo-react): add menu --- .changeset/add-evo-menu.md | 7 + .claude/skills/evo-app-migrate-react/SKILL.md | 1 + .../components/evo-menu.md | 128 ++++++ .../src/tags/evo-menu-button/index.marko | 3 +- .../evo-marko/src/tags/evo-menu/index.marko | 14 +- .../src/tags/evo-menu/menu.stories.ts | 12 + .../test/__snapshots__/test.server.ts.snap | 8 +- .../src/tags/evo-menu/test/test.server.ts | 8 +- packages/evo-react/src/menu/README.md | 5 + packages/evo-react/src/menu/context.tsx | 154 +++++++ packages/evo-react/src/menu/index.ts | 27 ++ .../src/menu/menu-checkbox-group.tsx | 37 ++ packages/evo-react/src/menu/menu-footer.tsx | 16 + .../evo-react/src/menu/menu-item-badge.tsx | 6 + packages/evo-react/src/menu/menu-item.tsx | 190 +++++++++ packages/evo-react/src/menu/menu-items.tsx | 81 ++++ .../evo-react/src/menu/menu-radio-group.tsx | 36 ++ .../evo-react/src/menu/menu-separator.tsx | 16 + packages/evo-react/src/menu/menu.stories.tsx | 116 ++++++ packages/evo-react/src/menu/menu.tsx | 44 ++ .../test/__snapshots__/test.server.tsx.snap | 9 + .../evo-react/src/menu/test/test.browser.tsx | 391 ++++++++++++++++++ .../evo-react/src/menu/test/test.server.tsx | 91 ++++ packages/evo-react/src/menu/types.ts | 118 ++++++ .../src/utils/use-roving-tab-index.ts | 278 +++++++++++++ packages/evo-react/src/utils/use-typeahead.ts | 143 +++++++ packages/skin/dist/menu/menu.css | 6 +- packages/skin/src/sass/menu/menu.scss | 6 +- .../src/sass/menu/stories/misc.stories.js | 12 +- 29 files changed, 1939 insertions(+), 24 deletions(-) create mode 100644 .changeset/add-evo-menu.md create mode 100644 .claude/skills/evo-app-migrate-react/components/evo-menu.md create mode 100644 packages/evo-react/src/menu/README.md create mode 100644 packages/evo-react/src/menu/context.tsx create mode 100644 packages/evo-react/src/menu/index.ts create mode 100644 packages/evo-react/src/menu/menu-checkbox-group.tsx create mode 100644 packages/evo-react/src/menu/menu-footer.tsx create mode 100644 packages/evo-react/src/menu/menu-item-badge.tsx create mode 100644 packages/evo-react/src/menu/menu-item.tsx create mode 100644 packages/evo-react/src/menu/menu-items.tsx create mode 100644 packages/evo-react/src/menu/menu-radio-group.tsx create mode 100644 packages/evo-react/src/menu/menu-separator.tsx create mode 100644 packages/evo-react/src/menu/menu.stories.tsx create mode 100644 packages/evo-react/src/menu/menu.tsx create mode 100644 packages/evo-react/src/menu/test/__snapshots__/test.server.tsx.snap create mode 100644 packages/evo-react/src/menu/test/test.browser.tsx create mode 100644 packages/evo-react/src/menu/test/test.server.tsx create mode 100644 packages/evo-react/src/menu/types.ts create mode 100644 packages/evo-react/src/utils/use-roving-tab-index.ts create mode 100644 packages/evo-react/src/utils/use-typeahead.ts diff --git a/.changeset/add-evo-menu.md b/.changeset/add-evo-menu.md new file mode 100644 index 00000000000..f6609758a0f --- /dev/null +++ b/.changeset/add-evo-menu.md @@ -0,0 +1,7 @@ +--- +"@ebay/skin": patch +"@evo-web/marko": patch +"@evo-web/react": patch +--- + +Add EvoMenu to React and align its accessible naming API with Marko using `a11yText` and `a11yLabelId`. diff --git a/.claude/skills/evo-app-migrate-react/SKILL.md b/.claude/skills/evo-app-migrate-react/SKILL.md index b7fddf3c273..29d7dbaff57 100644 --- a/.claude/skills/evo-app-migrate-react/SKILL.md +++ b/.claude/skills/evo-app-migrate-react/SKILL.md @@ -74,6 +74,7 @@ When migrating a listed component, read the linked file completely and apply the | `ebay-confirm-dialog` | [evo-confirm-dialog.md](components/evo-confirm-dialog.md) | | `ebay-filter-chip` | [evo-filter-chip.md](components/evo-filter-chip.md) | | `ebay-icon-button` | [evo-icon-button.md](components/evo-icon-button.md) | +| `ebay-menu` | [evo-menu.md](components/evo-menu.md) | --- diff --git a/.claude/skills/evo-app-migrate-react/components/evo-menu.md b/.claude/skills/evo-app-migrate-react/components/evo-menu.md new file mode 100644 index 00000000000..5fb533de8e9 --- /dev/null +++ b/.claude/skills/evo-app-migrate-react/components/evo-menu.md @@ -0,0 +1,128 @@ +# ebay-menu → evo-menu + +EvoMenu uses an explicit compound-component API. Wrap all navigable items in `EvoMenuItems`, and use the item and group component that matches each item's behavior. + +## Command menu, badge, separator, and footer + +**Before:** + +```tsx +import { + EbayMenu, + EbayMenuItem, + EbayMenuSeparator, +} from "@ebay/ui-core-react/ebay-menu"; + + handleSelect(index)}> + + Messages + + + Settings +; +``` + +**After:** + +```tsx +import { EvoButton } from "@evo-web/react/button"; +import { + EvoMenu, + EvoMenuFooter, + EvoMenuItem, + EvoMenuItemBadge, + EvoMenuItems, + EvoMenuSeparator, +} from "@evo-web/react/menu"; + + + + + Messages + + + + Settings + + + Apply + +; +``` + +`EvoMenuFooter` is optional. When footer content is needed, place it next to `EvoMenuItems` and use `EvoButton` directly instead of a menu-specific footer button. + +## Radio menu + +**Before:** + +```tsx + handleSortChange(index)} +> + Price + Distance + +``` + +**After:** + +```tsx + + + + Price + Distance + + + +``` + +Use `selected` instead of `defaultSelected` when the radio group is controlled. Selection callbacks now receive the selected item value rather than its child index. + +## Checkbox menu + +**Before:** + +```tsx + handleFilterChange(checkedValues)} +> + + Free shipping + + Free returns + +``` + +**After:** + +```tsx + + + + Free shipping + Free returns + + + +``` + +Use `selected` instead of `defaultSelected` when the checkbox group is controlled. Radio and checkbox item `value` props are required; selection never falls back to child indexes. + +## Additional changes + +- Use `a11yText` on `EvoMenuItems` for an accessible name, or use `a11yLabelId` when an external element labels the menu. Do not use both. +- Move plain-menu `onSelect` to each `EvoMenuItem`; move parent `onClick` handlers to the applicable items. +- Move `onKeyDown` to `EvoMenuItems` when needed. It receives the native keyboard event without legacy index or checked callback data. +- Remove `baseEl`, `priority`, `autofocus`, `forwardedRef`, and `menuRef`. The root is a span, refs use native React 19 `ref`, and `EvoMenuItems` accepts its own ref. +- `reverse`, `fixed`, `fixWidth`, `classPrefix`, and native HTML attributes remain supported. diff --git a/packages/evo-marko/src/tags/evo-menu-button/index.marko b/packages/evo-marko/src/tags/evo-menu-button/index.marko index 750bca1d47f..f9d5cd6b81e 100644 --- a/packages/evo-marko/src/tags/evo-menu-button/index.marko +++ b/packages/evo-marko/src/tags/evo-menu-button/index.marko @@ -4,7 +4,8 @@ import Button, { type Input as ButtonInput } from ""; import { type Input as MenuInput } from ""; export interface Input< Index extends number | string | (number | string)[], -> extends Omit, "variant"> { +> extends Omit, "a11yLabelId" | "a11yText" | "variant">, + Pick { open?: boolean; openChange?: (open: boolean) => void; collapseOnSelect?: boolean; diff --git a/packages/evo-marko/src/tags/evo-menu/index.marko b/packages/evo-marko/src/tags/evo-menu/index.marko index 54dab5078ea..0fed4d443b5 100644 --- a/packages/evo-marko/src/tags/evo-menu/index.marko +++ b/packages/evo-marko/src/tags/evo-menu/index.marko @@ -7,7 +7,11 @@ export interface Item extends Marko.HTML.Div { } export interface Input< Index extends number | string | (number | string)[], -> extends Marko.HTML.Span { +> extends Omit { + /** Localized accessible name for the menu. Cannot be used with `a11yLabelId`. */ + a11yText?: string; + /** ID of the element that labels the menu. Cannot be used with `a11yText`. */ + a11yLabelId?: string; selected?: Index; selectedChange?: (selected: Index) => void; item?: Marko.AttrTag; @@ -23,6 +27,8 @@ export interface Input< @@ -58,8 +62,8 @@ export interface Input<