fix(admin): support nested menu items in the menu editor#1739
Conversation
MenuItem.parentId was already supported end-to-end by the API and admin API client, but the MenuEditor UI never exposed it, so there was no way to build a nested menu from the admin. Adds a Parent select to the edit dialog (excluding the item itself and its descendants to prevent cycles, which the server doesn't validate against either), renders items indented by nesting depth, and makes the up/down reorder buttons operate on siblings only instead of the whole flat list. Fixes emdash-cms#208
Three of the new tests added for emdash-cms#208 were broken: two used getByText("Services") which ambiguously matched both the item label and its "/services" URL text (case-insensitive substring match), and the Parent-select test used a Playwright locator .click() that this browser-test env's missing Tailwind CSS causes to fail Base UI's pointer-event hit test on a Dialog-nested trigger. Add exact:true to the ambiguous text queries, wait for the edit dialog to render before interacting with it, and use a native DOM .click() for the Select trigger (matching the pattern already used for Dialog buttons elsewhere in this test suite).
🦋 Changeset detectedLatest commit: 1e68c2e The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 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 |
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
There was a problem hiding this comment.
This is the right change for #208: the API and repository already support parentId, and the gap was purely in the admin UI. The implementation is mostly idiomatic — a hierarchical display list, cycle exclusion in the parent picker, sibling-scoped reorder buttons, RTL-safe indentation via marginInlineStart, and Lingui-wrapped strings. The targeted tests pass and cover the headline behaviors.
What I checked
- Changeset — found one, but it names the wrong package (only
packages/adminchanged, notemdashcore). - Localization / RTL — new user-facing strings go through Lingui; indentation uses logical
marginInlineStart. The option-label prefix is worth an RTL review. - Cycle safety — the UI excludes the item itself and its descendants from parent options, so the picker cannot create cycles.
getSelfAndDescendantIdsuses itsidsset to avoid infinite recursion on cyclic data, andbuildDisplayListhas a visited guard. - Reorder logic —
moveItemswaps only same-parent siblings for the buttons, and the resulting flatsortOrderindices preserve the sibling ordering after the next fetch/display. - Tests — three new tests cover the parent select, indentation, and sibling-only disables. Coverage of the descendant-exclusion rule is missing.
Headline conclusion: fix the changeset package name before merge; otherwise the code looks solid with two minor suggestions.
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| "emdash": patch | |||
There was a problem hiding this comment.
[needs fixing] The changeset names emdash, but this PR only changes packages/admin/src/components/MenuEditor.tsx and the @emdash-cms/admin tests. Because the workspace uses fixed versioning, the version bump will still propagate, but the changelog entry will be attributed to the wrong package (and may be missed from the @emdash-cms/admin release notes).
| "emdash": patch | |
| "@emdash-cms/admin": patch |
| expect(inputEl.validity.valid).toBe(false); | ||
| }); | ||
|
|
||
| it("edit dialog shows a Parent select that excludes the item itself", async () => { |
There was a problem hiding this comment.
[suggestion] The title says the Parent select excludes "the item itself", but the implemented logic also excludes all descendants to prevent cycles. The current fixture (Home) has no descendants, so that branch is untested. Consider adding a nested fixture (e.g. Home → Services) and editing Home to assert that Services is also absent from the options.
| .filter(({ item }) => !excluded.has(item.id)) | ||
| .map(({ item, depth }) => ({ | ||
| value: item.id, | ||
| label: `${"— ".repeat(depth)}${item.label}`, |
There was a problem hiding this comment.
[suggestion] Using a leading "— " text prefix for indentation may not align correctly under RTL document direction, because neutral prefixes can move with the bidi context. If Kumo’s Select.Option supports custom rendering or per-option padding, prefer an RTL-safe padding-inline-start / ps-* style instead of a text prefix.
There was a problem hiding this comment.
This is a valid comment. Can you use a RTL-safe alternative
There was a problem hiding this comment.
Fixed in 7168473 — swapped the text prefix for marginInlineStart, matching the item-list row indentation a few lines up.
|
recheck |
Only packages/admin changed in this PR, but the changeset named "emdash" (core). Fixed per review on emdash-cms#1739.
Text-based "— " repeat prefix could misalign under RTL bidi reordering. Match the same-file precedent (item list rows) and indent via marginInlineStart instead.
Existing test only exercised self-exclusion (flat fixture had no descendants to check against). Adds a nested fixture and asserts a child is also absent from its ancestor's Parent options.
What does this PR do?
MenuItem.parentIdwas already supported end-to-end by the API and admin API client, but the MenuEditor UI never exposed it, so there was no way to build a nested menu from the admin. Adds a Parent select to the edit dialog (excluding the item itself and its descendants, to prevent cycles), renders items indented by nesting depth, and makes the up/down reorder buttons operate on siblings only instead of the whole flat list.Closes #208
Type of change
Checklist
pnpm typecheckpassespnpm lintpassespnpm testpasses (targeted:packages/admin/tests/components/MenuEditor.test.tsx, 14/14)pnpm formathas been runAI-generated code disclosure