Skip to content

feat(DropdownMenu): data items take a sub-menu's header and footer, and the bottom sheet shows a level's own - #7839

Closed
vjeux wants to merge 15 commits into
meta:mainfrom
vjeux:submenu-sheet-regions
Closed

vjeux wants to merge 15 commits into
meta:mainfrom
vjeux:submenu-sheet-regions

Conversation

@vjeux

@vjeux vjeux commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review only this PR's change: the branch sits on main, so this diff is already its own.

User need

What led to it: this comes from moving every menu and picker in our app onto Astryx, which began with a press, slide and lift bug in its phone menus. Our session menu's "Labels" sub-menu, built from items, has a filter field and a "Create label" button, and on a phone they were missing.

Why: DropdownMenuSubMenu takes a header and footer, but a data item can't, and the bottom sheet shows the menu's own regions at every level, so a level's own controls never appear on a phone.

Problem and solution fit

A data item with items takes header and footer, and the popover shows them around that sub-menu. In the bottom sheet, a level whose row has either shows them around its rows in place of the menu's. Each level's regions mount on their own, so typed text never carries between levels.

<DropdownMenu
  presentation="adaptive"
  button={{label: 'Session'}}
  items={[{label: 'Labels', header: <TextInput label="Filter labels" … />, footer: <Button label="Create label" … />, items: visibleLabelItems}]}
/>

Current specification

component:DropdownMenu (draft): FR20 and DEC-13, proposed with #7530, now name data items and the sheet's levels, and a new FR23 says every sheet level follows the current items. component:DropdownMenuSubMenu (draft) names the sheet's levels. #7840 extends FR20 to ContextMenu.

Public delta and caller burden

  • header and footer on a data item with items. With either, the row stays a sub-menu even when items is empty, so a filter that matches nothing keeps its field.
  • Bottom sheet: a level with its own regions shows them instead of the menu's. Every level follows the current items while it shows; rows that share or change a label, or reorder, need an id.

Design and alternatives

  • Show the menu's regions at every level. No change, but a level's own controls stay hidden on a phone.
  • Make only levels with regions follow items. Smaller, but a menu-wide search couldn't filter nested rows.
  • Each level shows its own regions and follows items (chosen). The sheet is already a modal dialog, so a level's regions sit in it directly. The cost: callers whose rows share or change labels add an id.

Evidence

New cases in DropdownMenuSubMenu.test.tsx and ContextMenu.test.tsx cover the data items, the sheet's level regions and live levels; SubMenuHeaderFooter.a11y.chromium.spec.ts checks the sheet in Chromium. They fail on main.

Writing rubric results

R1 narration: none.
R2 research: none.
R3 evidence: none.
R4 rejected alternatives: none.
R5 change references: none.
R6 open questions: none.

Evidence

See Testing.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, a story and a changeset ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

In Storybook, open Core/DropdownMenu → Submenu / header and footer, action sheet and open "Session".

  1. Click "Labels": its level shows "Filter labels" above the rows and "Create label" below them.
  2. Type in the filter: the rows narrow as you type; a filter that matches nothing keeps the field.
  3. Go back: the menu's first level shows no filter.

@vercel

vercel Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 27, 2026 10:01am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 21, 2026
@astracat-bot

astracat-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 30, 2026, 1:43 AM UTC

@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR

🧪 Sandbox Preview

View Sandbox for this PR

Modified Components

ContextMenu (@astryxdesign/core) · View in Storybook
Metric Before After Delta
Entry File (ESM) 1.1KB 1.1KB 0B
Lines of Code 678 678 -
Complexity Very High Very High (86) -

Component Size

Component Import before Import after Delta CSS before CSS after Delta
core/BreadcrumbItem 66.4KB 66.4KB +83B 11.0KB 11.0KB 0B
core/ContextMenu 77.7KB 77.8KB +74B 14.6KB 14.6KB 0B
core/ContextMenuItem 16.6KB 16.6KB 0B 5.2KB 5.2KB 0B
core/DropdownMenu 72.7KB 73.1KB +444B 14.7KB 14.7KB 0B
core/DropdownMenuCheckboxItem 16.1KB 16.1KB 0B 5.6KB 5.6KB 0B
core/DropdownMenuDivider 6.6KB 6.6KB 0B 3.6KB 3.6KB 0B
core/DropdownMenuGroup 6.1KB 6.1KB 0B 3.3KB 3.3KB 0B
core/DropdownMenuItem 15.4KB 15.4KB 0B 5.2KB 5.2KB 0B
core/DropdownMenuRadioGroup 6.0KB 6.0KB 0B 3.2KB 3.2KB 0B
core/DropdownMenuRadioItem 16.2KB 16.2KB 0B 5.6KB 5.6KB 0B
core/DropdownMenuSubMenu 58.1KB 58.1KB 0B 9.5KB 9.5KB 0B
core/Markdown 144.5KB 144.5KB +94B 17.4KB 17.4KB 0B
core/MoreMenu 72.9KB 73.3KB +436B 14.7KB 14.7KB 0B
core/Table 84.0KB 84.0KB +82B 15.1KB 15.1KB 0B
core/TableCell 78.8KB 78.9KB +83B 14.8KB 14.8KB 0B
core/TableHeaderCell 78.6KB 78.7KB +86B 14.8KB 14.8KB 0B

Sizes are gzip. Import counts the JavaScript the import loads eagerly; CSS counts the package stylesheet rules referenced by the modules it can load from its own package, lazily loaded ones included.

Bundle Size Summary

Gzip figures measure each package's entry file, not the code an import pulls in.

Package Entry (ESM) Entry gzip before Entry gzip after Delta
@astryxdesign/core 4.9KB 1.2KB 1.2KB 0B
Package stylesheet Gzip before Gzip after Delta
@astryxdesign/core 34.6KB 34.6KB 0B

Accessibility Audit

Status: No accessibility violations detected.

Visual Regression

55 of 502 shot(s) changed.

A change here is a question, not a failure: check whether the after is the
picture you intended. Record that review on the PR. Baseline maintenance is an
explicit dispatch of CI; this report never rewrites the baseline or adds a release gate.

component story theme mode pixels
Selector Indicator Space Bottom Sheet Narrow End RTL neutral light 753,933
Selector Indicator Space Bottom Sheet Wide Start neutral light 740,072
RichTextEditor Markdown parity: side by side probe dark 558,840
RichTextEditor Markdown parity: side by side probe light 540,756
Chat Full AI Chat probe light 230,823
Chat Full AI Chat probe dark 229,805
AppShell With Mobile Nav probe light 217,010
AppShell With Mobile Nav probe dark 216,960
Collapsible Dividers probe dark 66,445
Collapsible Dividers probe light 66,445
AppShell Playground neutral dark 65,198
AppShell Playground neutral light 63,788
Chat Default neutral dark 36,304
Chat Default neutral light 35,767
Chat Empty State probe light 23,531
Chat Empty State probe dark 22,622
ChatAutoScroll Scroll Behavior Comparison neutral dark 15,378
ChatAutoScroll Scroll Behavior Comparison neutral light 14,107
Chat With Attachments probe light 13,406
Chat With Attachments probe dark 11,232

and 35 more.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on a maintainer to approve or replace the governing spec for this addition, because component:DropdownMenu is still a draft record: what must be settled is the data-item header/footer and bottom-sheet level-region requirements this PR writes into FR20, the Sub-menu regions row and DEC-13. Two defects need fixes either way, each with a test that reproduces it:

  • In a bottom sheet opened from the keyboard, typing into a level's header field (or the menu's own field at a level) moves focus to Back after the first character, because the keyboard-open focus effect in DropdownMenu.tsx now re-runs whenever the caller re-renders items.
  • A level whose items filter to none becomes a plain row in both presentations (renderDropdownItems.tsx, MenuBottomSheetActionList.tsx), so its field and footer disappear mid-typing and the level can't be reopened while the query stays.

[Automated review]

const currentSubmenu = submenuPath.at(-1);
// The opened level as the caller renders it now: its rows, header and
// footer stay live while it shows.
const currentSubmenu = resolveSheetLevel(items, submenuPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading the level live makes currentItems (next line) a new array on every render where the caller rebuilds items, and the keyboard-open effect further down ([currentItems, isOpen], ending around line 763) then refocuses the sheet's first button, which at a level is Back. In a sheet opened with Enter, typing "perf" into the action-sheet story's Filter labels field leaves "p" with focus on Back, and the menu's own header field at a level regresses the same way. Keying that effect to the open and the level (submenuPath) instead of currentItems would keep the field focused; a keyboard-open typing test would cover it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 056b6f6, the same issue the 01:48Z review raised again. The keyboard-open effect now depends on [isOpen, submenuPath], not currentItems, so it runs on the open and on each level change but not on caller renders. A case opens the sheet from the keyboard and types into a level's field key by key; it fails with the previous effect.

label={option.label}
isDisabled={option.isDisabled}>
isDisabled={option.isDisabled}
header={option.header}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This branch only runs while option.items.length > 0 (line 114), so when a header filter matches nothing the row turns into a leaf: the flyout, the field being typed in and the footer unmount, focus drops to the page, and while the caller keeps the query the level can't be reopened. A compound DropdownMenuSubMenu with no rows keeps all three. Treating a row with items and a renderable header or footer as a sub-menu even with zero rows, here and in MenuBottomSheetActionList's isSubmenu, would match it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e9f7275 and narrowed in ecedadd. renderDropdownItems and MenuBottomSheetActionList both read opensSubMenu(item, {showsRegions}): where sub-menu regions show (DropdownMenu's popover and sheet), a row with header or footer stays a sub-menu while its items is empty. Elsewhere, and without either, an empty items is still a plain row. Cases type a filter that matches nothing, in the popover and in the sheet; both fail with the previous code.

// A leaf row carries no header or footer of its own.
expect(
screen.getByRole('menuitem', {name: 'Rename', ...h}),
).not.toHaveAttribute('header');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Rename has no header, so this passes with or without the strip in renderLeafItem. Giving a leaf row a header and asserting it renders nowhere would prove the strip.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the assertion was vacuous. In 2f03c7c, Rename now has a header. The case checks that no such field renders and that the row carries no header attribute; with renderLeafItem's strip removed, the attribute check fails.

* @file menuSheetPath.ts
* @input The menu's live `items` and the bottom sheet's path of opened rows
* @output `resolveSheetLevel`: the opened level as the caller renders it now
* @position Internal; used by the DropdownMenu and ContextMenu bottom sheets.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: only DropdownMenu.tsx imports this at this head; ContextMenu's sheet doesn't use it yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right for this PR: the header now reads "Internal; used by the DropdownMenu bottom sheet" (since 056b6f6). #7840, where ContextMenu's sheet starts using it, changes that line to name both.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on a maintainer to approve or replace the governing spec for this addition: component:DropdownMenu is still a draft record, and what must be settled is the data-item header/footer and bottom-sheet level-region requirements this PR writes into FR20, the Sub-menu regions row and DEC-13. Two defects need fixes either way, each with a test that reproduces it:

  • In a bottom sheet opened from the keyboard, typing into a level's header field (or the menu's own field at a level) moves focus to Back after the first character, because the keyboard-open focus effect in DropdownMenu.tsx re-runs whenever the caller re-renders items.
  • A level whose items filter to none becomes a plain row in both presentations (renderDropdownItems.tsx, MenuBottomSheetActionList.tsx), so its field and footer disappear mid-typing and the level can't be reopened while the query stays.

[Automated review]

@vjeux
vjeux force-pushed the submenu-sheet-regions branch from fcf3a1d to e9f7275 Compare October 22, 2026 01:11
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 22, 2026
… its filter matches nothing

ContextMenu's popover and bottom sheet read the same opensSubMenu as DropdownMenu's (meta#7839's fix). A ContextMenu bottom-sheet case types a filter that matches nothing, goes Back, and opens the level again with the field as left; it fails with the previous MenuBottomSheetActionList.tsx. The items doc says a row with header or footer stays a sub-menu while items is empty.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on a maintainer to approve or replace the governing spec for this addition: component:DropdownMenu is still a draft record, and what must be settled is the data-item header/footer requirement (including a row staying a sub-menu while its items is empty) and the bottom-sheet level regions this PR writes into FR20, the Sub-menu regions row and DEC-13. The emptied-filter fix holds; two defects need fixes either way, each with a test that reproduces it:

  • In a bottom sheet opened from the keyboard, typing into a header field at a level (the level's own, or the menu's own header) moves focus to Back after the first character, because the keyboard-open focus effect in DropdownMenu.tsx re-runs whenever the caller re-renders items; menus that pass no new prop regress too.
  • A sheet level whose row label changes while it is open, such as a count in "Labels (5)", and that has no id keeps its open-time header, so its controlled filter ignores typing; find the row another way, or document and warn that such a row needs an id.

[Automated review]

// The opened level as the caller renders it now: its rows, header and
// footer stay live while it shows.
const currentSubmenu = resolveSheetLevel(items, submenuPath);
const currentItems = currentSubmenu?.items ?? items;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still open on this revision: at a level currentItems is now a new array on every caller render with inline items, and the keyboard-open effect at lines 729-764 depends on [currentItems, isOpen], so after Enter on the trigger each keystroke in a header field at a level refocuses the sheet's first button, Back. A menu's own controlled header at a nested level keeps "abc" on the base and "a" here. Running that effect on the open and on level navigation (submenuPath) instead, with a case that opens the sheet from the keyboard and types several characters into a level's field, would fix and cover it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 056b6f6: the keyboard-open focus effect now depends on [isOpen, submenuPath], so it runs on the open and on each level change but not on caller renders, which make a new currentItems at a level read from inline items. New case: open the sheet with Enter, Enter into a level with a controlled filter, focus the field and type "f", "e", "a", letting the sheet's animation frames run after each key. With the previous effect the field keeps "" because focus went back to Back; now it reads "fea" and keeps focus.

for (const opened of path) {
const live: DropdownMenuItemData | undefined =
options != null ? findRow(options, opened) : undefined;
level = live ?? opened;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a row's label changes while its level shows (a count such as Labels (${visible.length})) and it has no id, no row matches and the level keeps its open-time shape, header included: typing "perf" into that level's controlled filter leaves it empty and the rows unfiltered, while id: 'labels' makes it work. The docs ask for an id only when labels repeat. A positional fallback at this level, or docs plus a development warning for rows whose label can change, would close it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 056b6f6 with the positional fallback. Each opened row is now kept with its index among its level's rows (openSheetLevel). When no row at the level carries the opened text label any more, as with a count in it, the row at that index is the level, provided it opens a sub-menu. Repeated labels without an id still keep the open-time shape, so they never swap levels; the existing repeated-label case still passes.

New case: a row labelled Labels (${n}), no id, with a controlled filter. Typing "perf" now shows "perf" and filters to Performance, and the heading reads "Labels (1)". With the previous menuSheetPath.ts the field stays empty.

The header JSDoc and the changeset now say an id is needed for repeated labels or rows that reorder.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 22, 2026
… its filter matches nothing

ContextMenu's popover and bottom sheet read the same opensSubMenu as DropdownMenu's (meta#7839's fix). A ContextMenu bottom-sheet case types a filter that matches nothing, goes Back, and opens the level again with the field as left; it fails with the previous MenuBottomSheetActionList.tsx. The items doc says a row with header or footer stays a sub-menu while items is empty.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on a maintainer to approve or reject the requirements this PR writes into the draft component:DropdownMenu record: data items taking a sub-menu header and footer (and staying a sub-menu while items is empty), the bottom sheet showing a level's own regions in place of the menu's, and how the sheet finds an opened level again, including the new positional match. The keyboard-focus and relabelled-row fixes hold; one new defect needs a fix either way:

  • When an opened row without an id leaves items while its level shows, for example filtered out by the menu's own search header, the sheet switches to whichever sub-menu row now sits at that position (menuSheetPath.ts); take the positional match only when it can still be the same row, such as while the level keeps its row count, and add a case for it.
  • Minor: ContextMenu shares the item type and helpers, so a ContextMenu row with a header and empty items now opens an empty sub-menu instead of acting.

[Automated review]

Comment on lines +70 to +72
(sameLabel.length === 0 && atPosition != null && opensSubMenu(atPosition)
? atPosition
: undefined)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the opened row has no id and leaves items while its level shows (say the menu's own search header, which still shows at a level without regions of its own, filters it out), no row carries its label, so this takes whatever sub-menu row now sits at that index: the level's rows, heading and dialog name switch to a sub-menu the person never opened, with no new prop involved. The base and the previous revision keep the opened level. Taking the positional candidate only while the level keeps the row count it had when the row opened (a relabel keeps it, a removal does not) would close it, and a case that filters the opened row out would cover it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecedadd. Each path entry now also keeps its level's row count (SheetPathEntry.count). Position is used only for an opened row without an id, when no row carries its label any more, while the level still has that many rows and the row there opens a sub-menu: that is, only when it can still be the same row. Otherwise the level keeps its last known shape.

New case: a sheet whose own search header filters the root rows "Move to", "Labels" and "Copy to". Open Labels, then type "to" into that search, which still shows on Labels. The level stays Labels with "Bug", with ids and without. The previous menuSheetPath.ts switches to "Copy to" and "Drafts" in both. The counted-label and repeated-label cases still pass.

Comment on lines +26 to +29
item.items != null &&
(item.items.length > 0 ||
isRenderable(item.header) ||
isRenderable(item.footer))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ContextMenu also renders through renderDropdownItems and MenuBottomSheetActionList, and ContextMenuItemData aliases DropdownMenuItemData, so a ContextMenu row with header and items: [] now opens an empty sub-menu (no rows, no field) where the base rendered a plain row. Applying the empty-items rule only where regions render would keep ContextMenu as it was.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ecedadd. The rule now applies only where regions render. opensSubMenu(item, {showsRegions}) reads the presentation:

  • renderDropdownItems passes it through its recursion;
  • MenuBottomSheetActionList takes it as showsLevelRegions;
  • DropdownMenu's popover and sheet pass true; ContextMenu and Breadcrumbs leave it off.

So in this PR a ContextMenu row with header and items: [] stays a plain row, as before. A new ContextMenu case checks it in the pointer menu (no aria-haspopup) and in the sheet (a press acts and closes); it fails on the previous head. #7840, where ContextMenu shows sub-menu regions, passes the flag there and turns that case into an empty sub-menu that shows its field.

@vjeux
vjeux force-pushed the submenu-sheet-regions branch from 056b6f6 to ecedadd Compare October 22, 2026 03:11

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the requirements this pull request writes into the draft component:DropdownMenu and component:DropdownMenuSubMenu records: data items taking a sub-menu header and footer (and staying a sub-menu while items is empty), a bottom-sheet level showing its own regions in place of the menu's, new FR23's live sheet levels with their row matching, and the sub-menu record's Regions cell and Sheet state. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Two small wording fixes apply either way:

  • The menu's own header and footer entries in DropdownMenu.doc.mjs, and FR15, still say the bottom sheet shows them at every level, while a level with regions of its own shows neither.
  • The DropdownMenuSubMenu record's new Sheet state describes a level only a data item can open, since a compound menu never uses the bottom sheet.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
meta#7839's sheet regions meet main's new sheet structure (meta#8392) here as they do at meta#7839's own head: the level regions sit on the new structure.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The record's invariant table keeps both sides: meta#7839's FR20 (a data item with items) and FR23, and the menu stack's FR5 status and FR21.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The bottom sheet now has meta#7839's structure on main's sheet (its view heading and level regions); the action-sheet card stays the branch taken for plain actions on a compact touch screen, and every other menu keeps that sheet. The card no longer registers the springs or alert tables: the alert's motion values are internal constants its motion reads straight from the tables (meta#8084), so registering them would register nothing and no theme variable retunes them.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
DropdownMenuTouch's header names this pull request's action-sheet card beside the springs table's register function; the menu table registers nothing.
…t level with its own, and the Sheet state names a data item's level

Two wording points from the review on 5745647. The menu's own header and footer entries in DropdownMenu.doc.mjs, and FR15, still said the bottom sheet shows them at every level, while FR20 has a level's own regions replace both: the doc entries now say so, and FR15 says it with a pointer to FR20 (its closing sentence loses a restatement so the cell keeps its column width). The DropdownMenuSubMenu record's Sheet state now names a data item's level, since a compound menu never uses the bottom sheet. Record and doc wording only.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
… its filter matches nothing

ContextMenu's popover and bottom sheet read the same opensSubMenu as DropdownMenu's (meta#7839's fix). A ContextMenu bottom-sheet case types a filter that matches nothing, goes Back, and opens the level again with the field as left; it fails with the previous MenuBottomSheetActionList.tsx. The items doc says a row with header or footer stays a sub-menu while items is empty.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the requirements this pull request writes into the draft component:DropdownMenu and component:DropdownMenuSubMenu records: data items taking a sub-menu header and footer (and staying a sub-menu while items is empty), a bottom-sheet level showing its own regions in place of the menu's (FR15, FR20, DEC-13), new FR23's live sheet levels with their row matching, the sub-menu record's Regions cell and Sheet state, and whether this data spelling of DropdownMenuSubMenu's regions stands under architecture:public-component-api INV21 ("One API per job"). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One fix applies either way: architecture:public-component-api INV18, now on main, asks that a rendered content slot be a part named in the component's anatomy, so the records need anatomy and theming-map entries for the sub-menu and sheet-level header and footer regions.

[Automated review]

* it shows, or when the rows can reorder or grow. Ignored on a row without
* `items`.
*/
header?: ReactNode;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

header and footer here are rendered content slots. architecture:public-component-api INV18, now on main, asks that such a slot be a part named in the component's anatomy, and neither component:DropdownMenu's anatomy nor component:DropdownMenuSubMenu's names a sub-menu or sheet-level header or footer. Naming those parts, with theming-map entries, in the records settles it with no API change.

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
meta#8220's dependencies, meta#8089 and meta#7839, in one commit, so its compare link shows only its own change. The DropdownMenu record takes meta#8089's rows with meta#7839's own changes to it (the sub-menu regions, FR15, FR20, FR23 and the bottom sheet's regions).
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The DropdownMenu record is the dependencies' own combination; the usage doc keeps meta#8089's open under a finger and this pull request's sentence on a sub-menu that opens in place.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
meta#8104's dependencies, meta#8089, meta#7839 and meta#8110, in one commit, so its compare link shows only its own change.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 26, 2026
The DropdownMenu record is the dependencies' own combination; this pull request's own change does not edit it.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
meta#8220's dependencies in one commit, so its compare link shows only its own change: meta#8089 and meta#7839, the sub-menu's header and footer. The spec table takes meta#7839's row changes (Sub-menu regions, FR15, FR20, FR23 and their verification rows) on top of the rows the stack already carries, none of which meta#7839 touches.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
meta#8089 keeps a phone menu beside its trigger (the engineering owner's decision): this pull request's in-place sub-menu keeps its own code and constants, and the covering placement's import, export and constants it sat beside are gone with meta#8089's.
Main's meta#8515 added DropdownMenu FR21 (the menu grows out of its trigger under a finger) beside FR20; the requirements table keeps this branch's FR20 wording and FR23 row around it.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
meta#8220's dependencies in one commit, so its compare link shows only its own change: meta#8089 and meta#7839. The record keeps main's meta#8515 rows and meta#8080's FR5, and takes meta#7839's row changes (Sub-menu regions, FR15, FR20, FR23 and their verification rows).
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
…nu sub-menu regions

Main's meta#8515 added DropdownMenu FR21 beside FR20; meta#7839's merge keeps its FR20 wording and FR23 row around it. This branch's own commits apply unchanged.

# Conflicts:
#	packages/core/src/DropdownMenu/DropdownMenu.spec.md

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the requirements this pull request writes into the draft component:DropdownMenu and component:DropdownMenuSubMenu records: data items taking a sub-menu header and footer (and staying a sub-menu while items is empty), a bottom-sheet level showing its own regions in place of the menu's (FR15, FR20, DEC-13), new FR23's live sheet levels with their row matching, the sub-menu record's Regions cell and Sheet state, and whether this data spelling of DropdownMenuSubMenu's regions stands under architecture:public-component-api INV21 ("One API per job"). An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. One fix applies either way: architecture:public-component-api INV18 asks that a rendered content slot be a part named in the component's anatomy, so the records need anatomy and theming-map entries for the sub-menu and sheet-level header and footer regions.

[Automated review]

vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
0eba86c

meta#8220's dependencies in one commit, so its compare link shows only its own change: meta#8089 and meta#7839.
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 27, 2026
vjeux added a commit to vjeux/astryx that referenced this pull request Oct 28, 2026
…nu sub-menu regions

Main's meta#8648 put ContextMenu on the public headless context menu (FR10) and rewrote the record's ownership lines. Her text is kept as merged; this branch adds back only its own surface-host wording (the ownership line, FR2 and its verification row) and proposed FR12 after FR10.
@vjeux

vjeux commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

I'm pausing this work for now to reduce contention on the repo. Thank you for the reviews and the spec guidance.

This branch was successfully deployed

1 active deployment
Preview — 53e8deba Deployed Oct 27, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. needs:design-review Affects visuals — Design should review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant