@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e03511b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  2. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheet.js +4 -3
  4. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  7. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  9. package/dist/Button/Button.d.ts +2 -1
  10. package/dist/Button/Button.d.ts.map +1 -1
  11. package/dist/Button/Button.js +11 -4
  12. package/dist/Carousel/Carousel.d.ts.map +1 -1
  13. package/dist/Carousel/Carousel.js +2 -2
  14. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  15. package/dist/Chat/ChatToolCalls.js +34 -17
  16. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  17. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  18. package/dist/ContextMenu/ContextMenu.js +149 -26
  19. package/dist/ContextMenu/index.d.ts +1 -0
  20. package/dist/ContextMenu/index.d.ts.map +1 -1
  21. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  22. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  23. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  24. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  26. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  28. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  31. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  37. package/dist/DropdownMenu/index.d.ts +2 -1
  38. package/dist/DropdownMenu/index.d.ts.map +1 -1
  39. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  40. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  41. package/dist/DropdownMenu/menuWidth.js +35 -0
  42. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  44. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  45. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  46. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  47. package/dist/MoreMenu/MoreMenu.js +2 -0
  48. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  49. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  50. package/dist/MultiSelector/MultiSelector.js +97 -68
  51. package/dist/MultiSelector/index.d.ts +1 -1
  52. package/dist/MultiSelector/index.d.ts.map +1 -1
  53. package/dist/Popover/Popover.d.ts +10 -6
  54. package/dist/Popover/Popover.d.ts.map +1 -1
  55. package/dist/Popover/Popover.js +141 -16
  56. package/dist/Popover/usePopover.d.ts +4 -0
  57. package/dist/Popover/usePopover.d.ts.map +1 -1
  58. package/dist/Popover/usePopover.js +52 -5
  59. package/dist/Selector/Selector.d.ts +10 -0
  60. package/dist/Selector/Selector.d.ts.map +1 -1
  61. package/dist/Selector/Selector.js +106 -80
  62. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  63. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  64. package/dist/Selector/SelectorBottomSheet.js +83 -0
  65. package/dist/Selector/index.d.ts +1 -1
  66. package/dist/Selector/index.d.ts.map +1 -1
  67. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  68. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  69. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  70. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  72. package/dist/Selector/useSelectorPresentation.js +90 -0
  73. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  74. package/dist/Toast/useToastGesture.js +46 -9
  75. package/dist/astryx.css +19 -1
  76. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  77. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  78. package/dist/hooks/useAdaptivePresentation.js +19 -0
  79. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  81. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  82. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  83. package/dist/hooks/useFocusTrap.js +12 -0
  84. package/locales/en.json +20 -0
  85. package/locales/pseudo.json +15 -0
  86. package/package.json +2 -2
  87. package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
  88. package/src/BottomSheet/BottomSheet.spec.md +177 -0
  89. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  90. package/src/BottomSheet/BottomSheet.tsx +7 -4
  91. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  92. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  93. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  94. package/src/Button/Button.doc.mjs +56 -0
  95. package/src/Button/Button.test.tsx +10 -0
  96. package/src/Button/Button.tsx +13 -5
  97. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  98. package/src/Carousel/Carousel.test.tsx +83 -0
  99. package/src/Carousel/Carousel.tsx +8 -2
  100. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  101. package/src/Chat/ChatToolCalls.tsx +36 -15
  102. package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
  103. package/src/CheckboxList/CheckboxList.spec.md +214 -0
  104. package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
  105. package/src/CommandPalette/CommandPalette.spec.md +223 -0
  106. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  107. package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
  108. package/src/ContextMenu/ContextMenu.doc.mjs +61 -1
  109. package/src/ContextMenu/ContextMenu.spec.md +206 -0
  110. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  111. package/src/ContextMenu/ContextMenu.tsx +199 -32
  112. package/src/ContextMenu/index.ts +1 -0
  113. package/src/Divider/Divider.doc.mjs +24 -0
  114. package/src/Divider/Divider.spec.md +150 -0
  115. package/src/DropdownMenu/DropdownMenu.doc.mjs +305 -40
  116. package/src/DropdownMenu/DropdownMenu.spec.md +245 -0
  117. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  118. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  119. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  120. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  121. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  122. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  123. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  124. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  125. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  126. package/src/DropdownMenu/index.ts +2 -0
  127. package/src/DropdownMenu/menuWidth.ts +51 -0
  128. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  129. package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
  130. package/src/FieldStatus/FieldStatus.spec.md +212 -0
  131. package/src/Grid/Grid.doc.mjs +19 -0
  132. package/src/Grid/Grid.spec.md +161 -0
  133. package/src/Icon/Icon.doc.mjs +196 -48
  134. package/src/Icon/Icon.spec.md +191 -0
  135. package/src/IconButton/IconButton.doc.mjs +38 -0
  136. package/src/Kbd/Kbd.doc.mjs +18 -0
  137. package/src/Kbd/Kbd.spec.md +141 -0
  138. package/src/Layout/Layout.doc.mjs +32 -0
  139. package/src/Layout/Layout.spec.md +199 -0
  140. package/src/Lightbox/Lightbox.doc.mjs +149 -33
  141. package/src/Lightbox/Lightbox.spec.md +197 -0
  142. package/src/Markdown/Markdown.doc.mjs +60 -0
  143. package/src/Markdown/Markdown.spec.md +200 -0
  144. package/src/MobileNav/MobileNav.doc.mjs +140 -32
  145. package/src/MobileNav/MobileNav.spec.md +208 -0
  146. package/src/MoreMenu/MoreMenu.doc.mjs +59 -0
  147. package/src/MoreMenu/MoreMenu.spec.md +221 -0
  148. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  149. package/src/MoreMenu/MoreMenu.tsx +13 -1
  150. package/src/MultiSelector/MultiSelector.doc.mjs +128 -0
  151. package/src/MultiSelector/MultiSelector.spec.md +266 -0
  152. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  153. package/src/MultiSelector/MultiSelector.tsx +121 -73
  154. package/src/MultiSelector/index.ts +1 -0
  155. package/src/NavIcon/NavIcon.doc.mjs +17 -0
  156. package/src/NavIcon/NavIcon.spec.md +133 -0
  157. package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
  158. package/src/NavMenu/NavMenu.doc.mjs +37 -0
  159. package/src/Outline/Outline.doc.mjs +35 -0
  160. package/src/Outline/Outline.spec.md +161 -0
  161. package/src/OverflowList/OverflowList.doc.mjs +24 -0
  162. package/src/OverflowList/OverflowList.spec.md +156 -0
  163. package/src/Pagination/Pagination.doc.mjs +74 -0
  164. package/src/Pagination/Pagination.spec.md +225 -0
  165. package/src/Popover/Popover.doc.mjs +11 -11
  166. package/src/Popover/Popover.test.tsx +397 -2
  167. package/src/Popover/Popover.tsx +228 -21
  168. package/src/Popover/usePopover.tsx +92 -15
  169. package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
  170. package/src/ProgressBar/ProgressBar.spec.md +260 -0
  171. package/src/Resizable/Resizable.doc.mjs +59 -16
  172. package/src/Resizable/Resizable.spec.md +194 -0
  173. package/src/Section/Section.doc.mjs +17 -0
  174. package/src/Section/Section.spec.md +138 -0
  175. package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
  176. package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
  177. package/src/Selector/Selector.doc.mjs +12 -0
  178. package/src/Selector/Selector.spec.md +202 -0
  179. package/src/Selector/Selector.test.tsx +135 -0
  180. package/src/Selector/Selector.tsx +141 -82
  181. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  182. package/src/Selector/index.ts +1 -0
  183. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  184. package/src/Selector/useSelectorPresentation.ts +147 -0
  185. package/src/Skeleton/Skeleton.doc.mjs +12 -0
  186. package/src/Skeleton/Skeleton.spec.md +126 -0
  187. package/src/Slider/Slider.doc.mjs +184 -30
  188. package/src/Slider/Slider.spec.md +223 -0
  189. package/src/Stack/Stack.doc.mjs +22 -0
  190. package/src/Stack/Stack.spec.md +154 -0
  191. package/src/StatusDot/StatusDot.doc.mjs +17 -0
  192. package/src/StatusDot/StatusDot.spec.md +137 -0
  193. package/src/Switch/Switch.doc.mjs +40 -0
  194. package/src/Switch/Switch.spec.md +174 -0
  195. package/src/Table/Table.doc.mjs +102 -24
  196. package/src/Table/Table.spec.md +283 -0
  197. package/src/Text/Text.doc.mjs +190 -43
  198. package/src/Text/Text.spec.md +205 -0
  199. package/src/TextArea/TextArea.doc.mjs +255 -45
  200. package/src/TextArea/TextArea.spec.md +204 -0
  201. package/src/Toast/ToastViewport.test.tsx +118 -4
  202. package/src/Toast/useToastGesture.ts +61 -8
  203. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  204. package/src/Toolbar/Toolbar.doc.mjs +19 -0
  205. package/src/Toolbar/Toolbar.spec.md +173 -0
  206. package/src/Tooltip/Tooltip.doc.mjs +17 -0
  207. package/src/Tooltip/Tooltip.spec.md +141 -0
  208. package/src/TreeList/TreeList.doc.mjs +58 -0
  209. package/src/TreeList/TreeList.spec.md +220 -0
  210. package/src/Typeahead/Typeahead.doc.mjs +83 -1
  211. package/src/Typeahead/Typeahead.spec.md +250 -0
  212. package/src/hooks/useAdaptivePresentation.ts +35 -0
  213. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  214. package/src/hooks/useFocusTrap.test.tsx +40 -0
  215. package/src/hooks/useFocusTrap.ts +13 -0
  216. package/src/theme/themingTargets.test.ts +91 -25
@@ -0,0 +1,221 @@
1
+ ---
2
+ schema_version: 1
3
+ template_version: 3
4
+ kind: component
5
+ id: component:MoreMenu
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/MoreMenu/MoreMenu.test.tsx,
16
+ packages/core/src/DropdownMenu/DropdownMenu.test.tsx,
17
+ packages/core/src/Button/Button.test.tsx,
18
+ packages/core/src/Icon/Icon.test.tsx,
19
+ packages/core/src/List/List.test.tsx,
20
+ scripts/check-knowledge.mjs,
21
+ ]
22
+ families: [family:overlay-dismissal]
23
+ design_specs: []
24
+ architecture:
25
+ [
26
+ architecture:component-theming-surface,
27
+ architecture:icon-resolution-and-component-slots,
28
+ architecture:interaction-modality,
29
+ architecture:layer-runtime,
30
+ ]
31
+ contributing: []
32
+ system_specs: []
33
+ ---
34
+
35
+ # MoreMenu component contract
36
+
37
+ ## Intent
38
+
39
+ MoreMenu provides a visible icon-only overflow trigger and delegates its action
40
+ presentation to DropdownMenu. This draft records current consumer anatomy and
41
+ theming ownership without changing runtime behavior, styling, targets, or public
42
+ API.
43
+
44
+ ## Compatibility and migration
45
+
46
+ - Released default preserved: `yes`
47
+ - Compatibility class: additive documentation only; runtime, DOM, styling,
48
+ targets, and public API remain unchanged
49
+ - Controlled/uncontrolled behavior: unchanged
50
+ - Migration decision: none
51
+
52
+ Consumer migration instructions belong in consumer docs and release notes.
53
+
54
+ ## Ownership boundary
55
+
56
+ **Owns**
57
+
58
+ - The overflow-menu composition and its visible icon-only trigger defaults.
59
+ - The Menu surface's current `more-menu` target, which is a second class on the
60
+ composed DropdownMenu panel.
61
+
62
+ **Does not own / non-goals**
63
+
64
+ - Trigger-button presentation — delegated to `component:Button`.
65
+ - Default semantic trigger artwork resolution — delegated to the Icon registry;
66
+ the current artwork renders directly inside Button and does not carry Icon's
67
+ `icon` target. Arbitrary caller-rendered trigger content remains caller-owned.
68
+ - Menu-panel lifecycle and presentation switching — delegated to
69
+ `component:DropdownMenu`.
70
+ - Anchored action rows, sections, dividers, selectable indicators, and nested
71
+ flyouts — delegated to `component:DropdownMenu`.
72
+ - BottomSheet action rows and lists — delegated through DropdownMenu to
73
+ `component:List`; the touch frame remains owned by `component:BottomSheet`.
74
+ - Shared layer lifecycle or dismissal policy.
75
+
76
+ ## Public concepts
77
+
78
+ No new public concept is introduced. Consumer props and usage remain documented
79
+ in `MoreMenu.doc.mjs`; its action data and presentation values remain those of
80
+ DropdownMenu.
81
+
82
+ ## Behavioral and layout contract
83
+
84
+ | ID | Candidate invariant | Basis | Draft review state |
85
+ | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | --------------------------------------------------- |
86
+ | FR1 | Every current render contains one icon-only Trigger button and one DropdownMenu-owned Menu surface. Anchored presentation uses Pointer action rows; BottomSheet presentation uses Touch action rows. The default trigger icon resolves through the Icon registry, while a caller may replace it with arbitrary React content. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
87
+ | FR2 | The current `more-menu` target is applied to the composed DropdownMenu panel as an additional class. It is not applied to the Trigger button. | Current source, docs, and tests | Verified current target placement; no target change |
88
+ | FR3 | Button owns the Trigger button and the Icon registry owns default semantic trigger artwork resolution. DropdownMenu owns Pointer action rows; List owns Touch action rows rendered through DropdownMenu's BottomSheet path. | Current source and owner docs | Verified current delegation; no ownership change |
89
+ | FR4 | MoreMenu forwards the current open state, placement, alignment, and pointer/touch presentation policy to DropdownMenu without adding a parallel menu runtime. | Current source and tests | Verified composition; no behavior change |
90
+
91
+ ### Allowed variation
92
+
93
+ - **AV1 — Trigger icon.** The default semantic three-dot icon may be replaced by
94
+ an icon component or arbitrary React content without moving the MoreMenu target
95
+ onto the trigger.
96
+ - **AV2 — Presentation.** DropdownMenu may resolve the current anchored or
97
+ BottomSheet presentation while MoreMenu keeps the same trigger and Menu-surface
98
+ ownership.
99
+ - **AV3 — Interior.** Action rows, sections, dividers, icons, selectable
100
+ indicators, and nested actions may vary according to DropdownMenu's current
101
+ data contract.
102
+
103
+ ### Representative states
104
+
105
+ | State | Required invariant | Allowed variation |
106
+ | ---------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------- |
107
+ | Default overflow menu | Trigger button uses the default Icon-resolved glyph and Menu surface carries `more-menu`. | Button size, variant, placement, and alignment vary. |
108
+ | Arbitrary icon content | Trigger button remains Button-owned while caller content bypasses Icon ownership. | Caller owns the rendered content. |
109
+ | Pointer presentation | DropdownMenu owns the anchored Menu interior and Pointer action rows. | Rows, sections, dividers, and nested flyouts vary. |
110
+ | Touch presentation | DropdownMenu owns the BottomSheet-backed Menu interior; List owns Touch action rows. | Touch rows and drill-in depth vary. |
111
+
112
+ ### Transformation and precedence order
113
+
114
+ - No new icon-resolution, presentation-resolution, open-state, positioning, or
115
+ styling precedence rule is introduced.
116
+
117
+ ### Performance and resources
118
+
119
+ - No new loading, listener, measurement, or render constraint is introduced.
120
+
121
+ ## Accessibility contract
122
+
123
+ This draft does not change or extend MoreMenu's existing accessible label,
124
+ tooltip, icon-only Button semantics, focus behavior, keyboard operation, or
125
+ dismissal behavior.
126
+
127
+ ## Design relationships
128
+
129
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
130
+ | ------------------------------- | -------------------------------------------------------------------------------- | ------------------------------ | ----------------- | ------------------ |
131
+ | Trigger button | Provides the visible and discoverable entry point for secondary actions. | `component:Button` | Supporting | FR1, FR3 |
132
+ | Icon-resolved trigger icon | Draws semantic three-dot artwork from the active Icon registry. | Icon registry | Supporting | FR1, FR3 |
133
+ | Caller-rendered trigger content | Presents arbitrary caller content supplied through the icon override. | Caller-supplied content | Context-dependent | FR1, FR3 |
134
+ | Menu surface | Adds MoreMenu's target to the composed DropdownMenu panel, not the trigger. | Current source and public docs | Prominent | FR1, FR2 |
135
+ | Pointer action row | Presents an action or nested flyout trigger in the anchored presentation. | `component:DropdownMenu` | Prominent | FR1, FR3, FR4 |
136
+ | Touch action row | Presents an action or drill-in entry through ListItem in the touch presentation. | `component:List` | Prominent | FR1, FR3, FR4 |
137
+
138
+ The Menu surface is the DropdownMenu panel itself. It carries both
139
+ `astryx-more-menu` and DropdownMenu's own panel target; the local class is not a
140
+ wrapper and does not retarget the trigger. DropdownMenu remains the owner of the
141
+ presentation runtime and anchored interior. Its touch path delegates action rows
142
+ to List rather than extending the pointer-row target into the BottomSheet
143
+ presentation.
144
+
145
+ ### Theming anatomy
146
+
147
+ <!-- anatomy-theming:v1 -->
148
+
149
+ ```json
150
+ {
151
+ "Trigger button": {
152
+ "delegatesTo": {"owner": "component:Button", "target": "button"}
153
+ },
154
+ "Icon-resolved trigger icon": {
155
+ "none": {
156
+ "reason": "unsettled: The current useIcon artwork renders directly inside Button; whether it should carry Icon's icon target needs an owner decision."
157
+ }
158
+ },
159
+ "Caller-rendered trigger content": {
160
+ "none": {
161
+ "reason": "intentional: Arbitrary React icon content is caller-owned and receives no MoreMenu target."
162
+ }
163
+ },
164
+ "Menu surface": {"target": "more-menu"},
165
+ "Pointer action row": {
166
+ "delegatesTo": {
167
+ "owner": "component:DropdownMenu",
168
+ "target": "dropdown-menu-item"
169
+ }
170
+ },
171
+ "Touch action row": {
172
+ "delegatesTo": {"owner": "component:List", "target": "list-item"}
173
+ }
174
+ }
175
+ ```
176
+
177
+ ## Family and system relationships
178
+
179
+ - `architecture:component-theming-surface` owns anatomy qualification, the local
180
+ Menu-surface target, and composition delegation rules.
181
+ - `architecture:icon-resolution-and-component-slots` owns active-theme icon
182
+ resolution. MoreMenu's default artwork uses that registry but currently
183
+ bypasses Icon's painted `icon` target.
184
+ - `architecture:interaction-modality` owns the shared pointer, touch, keyboard,
185
+ and adaptive-presentation boundaries inherited through DropdownMenu; this
186
+ draft changes none of them.
187
+ - `architecture:layer-runtime` owns the current DropdownMenu Popover host, native
188
+ light-dismiss reconciliation, and BottomSheet host distinction. MoreMenu adds
189
+ no parallel layer runtime.
190
+ - `family:overlay-dismissal` owns shared Escape and platform-close ordering and
191
+ records MoreMenu as a shared owner through its composed DropdownMenu root;
192
+ BottomSheet presentation retains BottomSheet's recorded local-only adoption
193
+ gap.
194
+
195
+ ## Verification map
196
+
197
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
198
+ | ------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | ------------------------- |
199
+ | FR1, FR4 | `MoreMenu.test.tsx` render, pointer, bottom-sheet, placement, and controlled suites | Default, custom icon, pointer, touch | Replacing the composition or changing forwarded behavior fails existing render/interaction tests. | `audit:MoreMenu/anatomy` |
200
+ | FR2 | MoreMenu target test, source inspection, and theming target inventories | Closed trigger and composed menu panel | Moving `more-menu` onto the trigger or removing it from the panel fails target evidence. | `audit:MoreMenu/theming` |
201
+ | FR3 | MoreMenu, DropdownMenu, Button, Icon, and List source/owner tests | Trigger, icon branches, pointer rows, touch rows | A composed part loses its owner target or is documented as a new MoreMenu-owned descendant. | `audit:MoreMenu/theming` |
202
+ | Layer relationships | `MoreMenu.test.tsx`, `DropdownMenu.test.tsx`, and current layer/dismissal records | Light dismiss, Escape, pointer, touch | Documentation gives MoreMenu a parallel runtime or misstates the current dismissal owner. | `audit:MoreMenu/behavior` |
203
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current target | Missing, extra, prefixed, stale, or unclassified mappings fail repository validation. | `audit:MoreMenu/theming` |
204
+
205
+ ## Decision log
206
+
207
+ None. This draft records current facts and introduces no component-local design,
208
+ API, theming, modality, or layer-system decision.
209
+
210
+ ## Open questions
211
+
212
+ - **OQ1 — Should the default trigger artwork render through Icon's public
213
+ target?** (`human-api`) The current registry lookup preserves semantic artwork
214
+ replacement but leaves the painted glyph outside the `icon` target; this
215
+ draft does not change that reachability.
216
+
217
+ ## Content boundary
218
+
219
+ This file does not duplicate consumer prop tables, DropdownMenu item examples,
220
+ icon-resolution rules, implementation steps, or shared modality, layer,
221
+ dismissal, and theming rules. It links to their owners.
@@ -17,6 +17,30 @@ import {DropdownMenu} from '../DropdownMenu/DropdownMenu';
17
17
 
18
18
  // Mock showPopover and hidePopover methods since they're not implemented in jsdom
19
19
  beforeEach(() => {
20
+ HTMLDialogElement.prototype.showModal = vi.fn(function (
21
+ this: HTMLDialogElement,
22
+ ) {
23
+ this.setAttribute('open', '');
24
+ });
25
+ HTMLDialogElement.prototype.show = vi.fn(function (this: HTMLDialogElement) {
26
+ this.setAttribute('open', '');
27
+ });
28
+ HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
29
+ this.removeAttribute('open');
30
+ });
31
+ vi.stubGlobal(
32
+ 'matchMedia',
33
+ vi.fn().mockImplementation((query: string) => ({
34
+ matches: false,
35
+ media: query,
36
+ onchange: null,
37
+ addEventListener: vi.fn(),
38
+ removeEventListener: vi.fn(),
39
+ addListener: vi.fn(),
40
+ removeListener: vi.fn(),
41
+ dispatchEvent: vi.fn(),
42
+ })),
43
+ );
20
44
  HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
21
45
  this.setAttribute('popover-open', '');
22
46
  const event = new Event('toggle', {bubbles: false});
@@ -85,6 +109,29 @@ describe('MoreMenu', () => {
85
109
  expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
86
110
  });
87
111
 
112
+ it('keeps the trigger visually pressed while the menu is open', async () => {
113
+ const user = userEvent.setup();
114
+ render(<MoreMenu items={defaultItems} />);
115
+
116
+ const trigger = screen.getByRole('button', {name: 'More options'});
117
+ expect(trigger.className).not.toContain('DropdownMenu__styles.triggerOpen');
118
+
119
+ await user.click(trigger);
120
+
121
+ expect(trigger.className).toContain('DropdownMenu__styles.triggerOpen');
122
+ });
123
+
124
+ it('forwards BottomSheet presentation to DropdownMenu', async () => {
125
+ const user = userEvent.setup();
126
+ render(<MoreMenu items={defaultItems} presentation="bottom-sheet" />);
127
+
128
+ await user.click(screen.getByRole('button', {name: 'More options'}));
129
+ await waitFor(() =>
130
+ expect(HTMLDialogElement.prototype.showModal).toHaveBeenCalledOnce(),
131
+ );
132
+ expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
133
+ });
134
+
88
135
  it('calls onClick when item is clicked', async () => {
89
136
  const handleEdit = vi.fn();
90
137
  const items = [
@@ -22,7 +22,10 @@ import type {ReactNode} from 'react';
22
22
  import {useIcon} from '../Icon';
23
23
  import {DropdownMenu} from '../DropdownMenu/DropdownMenu';
24
24
  import {useSize} from '../SizeContext/SizeContext';
25
- import type {DropdownMenuOption} from '../DropdownMenu';
25
+ import type {
26
+ DropdownMenuOption,
27
+ DropdownMenuPresentation,
28
+ } from '../DropdownMenu';
26
29
  import type {LayerAlignment, LayerPlacement} from '../Layer';
27
30
  import type {ButtonVariant, ButtonSize} from '../Button';
28
31
  import type {BaseProps} from '../BaseProps';
@@ -87,6 +90,13 @@ export interface MoreMenuProps extends Pick<
87
90
  */
88
91
  alignment?: LayerAlignment;
89
92
 
93
+ /**
94
+ * Menu presentation policy. `adaptive` uses a BottomSheet on compact
95
+ * coarse-pointer viewports and an anchored popover elsewhere.
96
+ * @default 'popover'
97
+ */
98
+ presentation?: DropdownMenuPresentation;
99
+
90
100
  /**
91
101
  * Controlled open state for the menu.
92
102
  */
@@ -125,6 +135,7 @@ export function MoreMenu({
125
135
  isDisabled = false,
126
136
  placement,
127
137
  alignment,
138
+ presentation,
128
139
  isMenuOpen,
129
140
  onOpenChange,
130
141
  xstyle,
@@ -151,6 +162,7 @@ export function MoreMenu({
151
162
  onOpenChange={onOpenChange}
152
163
  placement={placement}
153
164
  alignment={alignment}
165
+ presentation={presentation}
154
166
  button={{
155
167
  label,
156
168
  icon: icon ?? moreIcon,
@@ -1,5 +1,116 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
+ const anatomy = [
5
+ {
6
+ name: 'Field',
7
+ required: false,
8
+ description:
9
+ 'Standalone Field shell that provides the label and optional supporting content; omitted inside InputGroup.',
10
+ },
11
+ {
12
+ name: 'Trigger',
13
+ required: true,
14
+ description:
15
+ 'Painted control that displays the current selection or placeholder and opens the selection surface.',
16
+ },
17
+ {
18
+ name: 'Icon-rendered start icon',
19
+ required: false,
20
+ description:
21
+ 'Optional leading semantic icon or icon component rendered through Icon.',
22
+ },
23
+ {
24
+ name: 'Caller-rendered start content',
25
+ required: false,
26
+ description:
27
+ 'Optional arbitrary React content rendered directly at the start of the trigger.',
28
+ },
29
+ {
30
+ name: 'Trigger clear button',
31
+ required: false,
32
+ description:
33
+ 'Shared clear action that removes every selected value when hasClear is enabled.',
34
+ },
35
+ {
36
+ name: 'Status icon',
37
+ required: false,
38
+ description:
39
+ 'Status glyph shown in place of the disclosure indicator for attached or tooltip status.',
40
+ },
41
+ {
42
+ name: 'Indicator icon',
43
+ required: false,
44
+ description:
45
+ 'Trailing chevron shown when status presentation does not replace it; reflects collapsed or expanded state.',
46
+ },
47
+ {
48
+ name: 'Search row',
49
+ required: false,
50
+ description:
51
+ 'Panel header with a borderless search input and optional clear action.',
52
+ },
53
+ {
54
+ name: 'Search icon',
55
+ required: false,
56
+ description:
57
+ 'Leading magnifier rendered through Icon inside the search row.',
58
+ },
59
+ {
60
+ name: 'Search clear button',
61
+ required: false,
62
+ description:
63
+ 'Shared clear action shown in the search row while a query is present.',
64
+ },
65
+ {
66
+ name: 'Option row',
67
+ required: false,
68
+ description:
69
+ 'Selectable row for an option or the optional select-all choice.',
70
+ },
71
+ {
72
+ name: 'Option checkbox indicator',
73
+ required: false,
74
+ description:
75
+ 'CheckboxInput indicator that presents each row’s selected, unselected, or indeterminate state.',
76
+ },
77
+ {
78
+ name: 'Option divider',
79
+ required: false,
80
+ description:
81
+ 'Divider supplied in the public options data to separate adjacent option groups.',
82
+ },
83
+ {
84
+ name: 'Section heading',
85
+ required: false,
86
+ description: 'Visible heading for a labeled group of option rows.',
87
+ },
88
+ {
89
+ name: 'Empty state',
90
+ required: false,
91
+ description:
92
+ 'Message shown when the shared panel content has no options or no search matches.',
93
+ },
94
+ {
95
+ name: 'Pointer popup',
96
+ required: false,
97
+ description:
98
+ 'Anchored painted surface that hosts the shared panel content for popover presentation.',
99
+ },
100
+ {
101
+ name: 'Touch sheet heading',
102
+ required: false,
103
+ description:
104
+ 'Heading above the shared panel content in bottom-sheet presentation.',
105
+ },
106
+ {
107
+ name: 'Touch sheet',
108
+ required: false,
109
+ description:
110
+ 'BottomSheet surface that hosts the same panel content for bottom-sheet presentation.',
111
+ },
112
+ ];
113
+
3
114
  /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
115
 
5
116
  export const docs = {
@@ -224,6 +335,13 @@ export const docs = {
224
335
  'Which edge of the option row carries the checkbox. end pushes it to the far edge of the row, including on the select-all row.',
225
336
  default: "'start'",
226
337
  },
338
+ {
339
+ name: 'presentation',
340
+ type: "'popover' | 'bottom-sheet' | 'adaptive'",
341
+ description:
342
+ 'How the option list is presented. adaptive uses a bottom sheet on compact touch screens and an anchored popover otherwise.',
343
+ default: "'popover'",
344
+ },
227
345
  {
228
346
  name: 'width',
229
347
  type: 'SizeValue',
@@ -257,6 +375,7 @@ export const docs = {
257
375
  },
258
376
  ],
259
377
  usage: {
378
+ anatomy,
260
379
  description:
261
380
  'A checkbox dropdown for selecting multiple values from a list. Selected items can display as a count, labels, or badges. Use it for filtering or when presenting a finite set of options where multiple choices are needed.',
262
381
  bestPractices: [
@@ -290,6 +409,11 @@ export const docs = {
290
409
  description:
291
410
  'Use variant="ghost" when a multi-selector sits in a toolbar with ghost buttons. If validation status is needed there, prefer statusVariant="tooltip" so the toolbar height stays compact.',
292
411
  },
412
+ {
413
+ guidance: true,
414
+ description:
415
+ 'Use presentation="adaptive" when the multi-selector should become a bottom sheet on compact touch screens.',
416
+ },
293
417
  {
294
418
  guidance: false,
295
419
  description: 'Use for single-value selection; use Selector instead.',
@@ -349,6 +473,8 @@ export const docsZh = {
349
473
  '状态消息的放置方式:attached 直接叠加在输入框下方;detached 作为独立元素浮于下方并留有间距。',
350
474
  renderOption:
351
475
  '每个可选选项的自定义渲染函数。不会用于分隔线、分组或全选行。',
476
+ presentation:
477
+ '选项列表的呈现方式:锚定弹出框、底部抽屉,或根据紧凑触控屏自适应。',
352
478
  xstyle: '布局自定义的 StyleX 样式,必须是 stylex.create() 值。',
353
479
  },
354
480
  },
@@ -487,6 +613,8 @@ export const docsDense = {
487
613
  'status message placement; ghost detaches attached by default; use tooltip for compact toolbars.',
488
614
  renderOption:
489
615
  'custom render fn per selectable option; not dividers/sections/select-all',
616
+ presentation:
617
+ 'popover, bottom-sheet, or compact-touch adaptive presentation',
490
618
  xstyle: 'StyleX layout styles; stylex.create() only',
491
619
  },
492
620
  },