feat(evo-menu): replace @item separator with @group attribute tag - #878
feat(evo-menu): replace @item separator with @group attribute tag#878LuLaValva wants to merge 2 commits into
Conversation
Adds <@group> to evo-menu and evo-menu-button: each group has its own selected/selectedChange and nested <@item> tags, with separators rendered automatically between groups. Top-level items render first as an implicit group sharing the menu-level selected. selected now also accepts null (single-select with nothing selected), and default item values are unique across the whole menu so roving focus works across groups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: f715d1b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 updates @evo-web/marko’s evo-menu API to support grouped menu items via a new <@group> attribute tag, removing the old per-<@item> separator attribute. It also propagates the grouping API through evo-menu-button, and updates stories/tests/snapshots plus a breaking-change changeset.
Changes:
- Add
<@group>toevo-menuand pass it throughevo-menu-button, with automatic separators between groups. - Expand selection semantics so
selectedcan benull(single-select with nothing selected yet) and ensure default item values are unique across the whole menu. - Add/adjust Storybook examples and update browser/server tests and snapshots to cover groups + controlled selection.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/evo-marko/src/tags/evo-menu/index.marko | Implements grouped rendering, selection handling, and automatic separators. |
| packages/evo-marko/src/tags/evo-menu/menu.stories.ts | Updates docs/argTypes and adds Groups/Controlled stories. |
| packages/evo-marko/src/tags/evo-menu/test/test.server.ts | Adds SSR snapshot coverage for selected=null, groups, and controlled selection. |
| packages/evo-marko/src/tags/evo-menu/test/test.browser.ts | Adds interaction tests for selected=null, grouped behavior, and controlled selection. |
| packages/evo-marko/src/tags/evo-menu/test/snapshots/test.server.ts.snap | Updates snapshots for new stories/behaviors. |
| packages/evo-marko/src/tags/evo-menu/examples/separator.marko | Removes the old separator example. |
| packages/evo-marko/src/tags/evo-menu/examples/groups.marko | Adds a grouped menu example. |
| packages/evo-marko/src/tags/evo-menu/examples/controlled.marko | Adds a controlled-selection-across-groups example. |
| packages/evo-marko/src/tags/evo-menu-button/index.marko | Passes group through to evo-menu and adjusts filter selected detection for null. |
| packages/evo-marko/src/tags/evo-menu-button/menu-button.stories.ts | Replaces separator story with groups story. |
| packages/evo-marko/src/tags/evo-menu-button/test/test.server.ts | Updates SSR coverage from separators to groups. |
| packages/evo-marko/src/tags/evo-menu-button/test/snapshots/test.server.ts.snap | Updates snapshots to reflect new groups story and reordered snapshot entries. |
| packages/evo-marko/src/tags/evo-menu-button/examples/separator.marko | Removes the old separator example. |
| packages/evo-marko/src/tags/evo-menu-button/examples/groups.marko | Adds a grouped menu-button example. |
| .changeset/hungry-melons-repeat.md | Documents the breaking API change (separator removed; <@group> added; selected supports null). |
Suppressed comments (3)
packages/evo-marko/src/tags/evo-menu/index.marko:153
- Clicking an item updates
selectedeven when the menu/group is meant to be non-selectable (i.e., whenselectedwas omitted). This can accidentally turn a plain menu into a selectable one after the first click.
onClick(e, target) {
rovTabindex.onClick(value);
selected = nextSelected(selected, value);
item.onClick && item.onClick(e, target);
}
packages/evo-marko/src/tags/evo-menu/index.marko:158
- Keyboard activation (Enter/Space) updates
selectedeven when the menu/group is non-selectable (whenselectedis omitted). This should be guarded the same way as click selection updates.
onKeyDown(e, target) {
if (e.key === "Enter" || e.key === " ") {
selected = nextSelected(selected, value);
}
rovTabindex.onKeyDown(e);
packages/evo-marko/src/tags/evo-menu/menu.stories.ts:76
- The
@group.selecteddoctypestring doesn’t includenull, but the description documentsnullas valid for single-select with no selection. This is a docs/type mismatch in Storybook.
selected: {
type: "number | string | (number | string)[]",
description:
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <const/isCheckbox=Array.isArray(group.selected) || (isFilter && !isRadio)> | ||
| <const/isCheckable=isRadio || isCheckbox> |
| "If present, indicates the selected item(s) among the top-level `@item`s and automatically updates them on click. Use a single value for single-select (`null` for single-select with nothing selected), or an array for multi-select. Compares with `value` if present on `@item`, otherwise the item's index in the menu.", | ||
| table: { type: { summary: "number | string | (number | string)[]" } }, |
Group<Index> defaults to the full selection union so existing usage is unchanged, while consumers can pin a narrow type (e.g. Group<string>) and get a matching selectedChange parameter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR Preview DeployedWebsite • commit f715d1b |
| <if=groupIndex> | ||
| <hr class=`${baseClass}__separator` role="separator"> | ||
| </if> |
| <const/isRadio=( | ||
| typeof group.selected === "number" || | ||
| typeof group.selected === "string" || | ||
| group.selected === null |
There was a problem hiding this comment.
Should we use == null instead of === null.
There was a problem hiding this comment.
Definitely not! null or string or number opts this group into radio behavior, while undefined means the items should be non-selectable buttons!
Adds a
<@group>attribute tag toevo-menu(and passes it throughevo-menu-button), replacing theseparatorattribute on<@item>. Each group takes its ownselected/selectedChangeand nested<@item>tags; separators render automatically between groups, and roving tabindex, typeahead, and arrow navigation treat the whole menu as one list. Top-level<@item>tags may be mixed with groups and always render first, sharing the menu-levelselected.selected(menu-level or per-group) now also acceptsnull, meaning single-select with nothing selected yet — distinct from omittingselected, which renders plain non-selectable menu items. Default item values are now unique across the whole menu so grouped items keep distinctdata-values.Includes new
GroupsandControlledstories/examples, updated tests, and a changeset (BREAKING:separatorremoved from<@item>).🤖 Generated with Claude Code