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 4b2bf2d152c..79189451eb8 100644 --- a/.claude/skills/evo-app-migrate-react/SKILL.md +++ b/.claude/skills/evo-app-migrate-react/SKILL.md @@ -75,6 +75,7 @@ When migrating a listed component, read the linked file completely and apply the - `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-lightbox-dialog`: [evo-dialog.md](components/evo-dialog.md) +- `ebay-menu`: [evo-menu.md](components/evo-menu.md) - `ebay-progress-spinner`: [evo-progress-spinner.md](components/evo-progress-spinner.md) - `ebay-tabs`: [evo-tabs.md](components/evo-tabs.md) - `ebay-textbox`: [evo-input.md](components/evo-input.md) / [evo-textarea.md](components/evo-textarea.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/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..8d35d1f9eec --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Generated changelogs are managed by Changesets +packages/*/CHANGELOG.md diff --git a/packages/ebayui-core-react/.prettierignore b/packages/ebayui-core-react/.prettierignore index bc2718ad916..0951e14ded5 100644 --- a/packages/ebayui-core-react/.prettierignore +++ b/packages/ebayui-core-react/.prettierignore @@ -1,6 +1,8 @@ # Autogenerated files +CHANGELOG.md src/ebay-icon/icons/* src/ebay-icon/__tests__/constants.ts +src/ebay-icon/__tests__/inline.stories.tsx src/ebay-icon/types.ts src/ebay-flag/__tests__/constants.ts src/ebay-flag/types.ts diff --git a/packages/ebayui-core-react/eslint.config.mjs b/packages/ebayui-core-react/eslint.config.mjs index 419cf8afb52..035cb22350e 100644 --- a/packages/ebayui-core-react/eslint.config.mjs +++ b/packages/ebayui-core-react/eslint.config.mjs @@ -9,7 +9,16 @@ import tseslint from "typescript-eslint"; export default tseslint.config( { - ignores: ["dist", "config", "scripts", ".storybook", "vitest.config.ts", "node_modules", "_site"], + ignores: [ + "dist", + "config", + "scripts", + ".storybook", + "vitest.config.ts", + "node_modules", + "_site", + "src/ebay-icon/__tests__/inline.stories.tsx", + ], }, eslint.configs.recommended, ...tseslint.configs.recommended, 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<