@astryxdesign/core 0.5.1 → 0.5.2-canary.005d975

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 (214) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +4 -3
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  6. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  8. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  9. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  10. package/dist/Button/Button.d.ts +2 -1
  11. package/dist/Button/Button.d.ts.map +1 -1
  12. package/dist/Button/Button.js +11 -4
  13. package/dist/Carousel/Carousel.d.ts.map +1 -1
  14. package/dist/Carousel/Carousel.js +2 -2
  15. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  16. package/dist/Chat/ChatToolCalls.js +34 -17
  17. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  18. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  19. package/dist/ContextMenu/ContextMenu.js +149 -26
  20. package/dist/ContextMenu/index.d.ts +1 -0
  21. package/dist/ContextMenu/index.d.ts.map +1 -1
  22. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  23. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  24. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  26. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  28. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  31. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  34. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  37. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  38. package/dist/DropdownMenu/index.d.ts +2 -1
  39. package/dist/DropdownMenu/index.d.ts.map +1 -1
  40. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  41. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  42. package/dist/DropdownMenu/menuWidth.js +35 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  44. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  45. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  46. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  47. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  48. package/dist/MoreMenu/MoreMenu.js +2 -0
  49. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  50. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  51. package/dist/MultiSelector/MultiSelector.js +97 -68
  52. package/dist/MultiSelector/index.d.ts +1 -1
  53. package/dist/MultiSelector/index.d.ts.map +1 -1
  54. package/dist/Popover/Popover.d.ts +10 -6
  55. package/dist/Popover/Popover.d.ts.map +1 -1
  56. package/dist/Popover/Popover.js +141 -16
  57. package/dist/Popover/usePopover.d.ts +4 -0
  58. package/dist/Popover/usePopover.d.ts.map +1 -1
  59. package/dist/Popover/usePopover.js +52 -5
  60. package/dist/Selector/Selector.d.ts +10 -0
  61. package/dist/Selector/Selector.d.ts.map +1 -1
  62. package/dist/Selector/Selector.js +106 -80
  63. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  64. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  65. package/dist/Selector/SelectorBottomSheet.js +83 -0
  66. package/dist/Selector/index.d.ts +1 -1
  67. package/dist/Selector/index.d.ts.map +1 -1
  68. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  69. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  70. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  72. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  73. package/dist/Selector/useSelectorPresentation.js +90 -0
  74. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  75. package/dist/Toast/useToastGesture.js +46 -9
  76. package/dist/astryx.css +19 -1
  77. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  78. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  79. package/dist/hooks/useAdaptivePresentation.js +19 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  81. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  82. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  83. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  84. package/dist/hooks/useFocusTrap.js +12 -0
  85. package/dist/i18n/InternationalizationContext.d.ts +2 -0
  86. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  87. package/dist/i18n/InternationalizationContext.js +3 -1
  88. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  89. package/dist/i18n/InternationalizationProvider.js +3 -1
  90. package/dist/i18n/resolve.d.ts +1 -1
  91. package/dist/i18n/resolve.d.ts.map +1 -1
  92. package/dist/i18n/resolve.js +37 -38
  93. package/dist/i18n/useTranslator.d.ts.map +1 -1
  94. package/dist/i18n/useTranslator.js +2 -3
  95. package/locales/af-ZA.json +0 -4
  96. package/locales/ar-SA.json +0 -4
  97. package/locales/ca-ES.json +0 -4
  98. package/locales/cs-CZ.json +0 -4
  99. package/locales/da-DK.json +0 -4
  100. package/locales/de-DE.json +0 -4
  101. package/locales/el-GR.json +0 -4
  102. package/locales/en.json +20 -0
  103. package/locales/es-ES.json +0 -4
  104. package/locales/fi-FI.json +0 -4
  105. package/locales/fr-FR.json +0 -4
  106. package/locales/he-IL.json +0 -4
  107. package/locales/hu-HU.json +0 -4
  108. package/locales/it-IT.json +0 -4
  109. package/locales/ja-JP.json +0 -4
  110. package/locales/ko-KR.json +0 -4
  111. package/locales/nl-NL.json +0 -4
  112. package/locales/no-NO.json +0 -4
  113. package/locales/pl-PL.json +0 -4
  114. package/locales/pseudo.json +15 -0
  115. package/locales/pt-BR.json +0 -4
  116. package/locales/pt-PT.json +0 -4
  117. package/locales/ro-RO.json +0 -4
  118. package/locales/ru-RU.json +0 -4
  119. package/locales/sr-SP.json +0 -4
  120. package/locales/sv-SE.json +0 -4
  121. package/locales/tr-TR.json +0 -4
  122. package/locales/uk-UA.json +0 -4
  123. package/locales/vi-VN.json +0 -4
  124. package/locales/zh-CN.json +0 -4
  125. package/locales/zh-TW.json +0 -4
  126. package/package.json +3 -3
  127. package/src/BottomSheet/BottomSheet.doc.mjs +6 -0
  128. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  129. package/src/BottomSheet/BottomSheet.tsx +7 -4
  130. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  131. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  132. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  133. package/src/Button/Button.doc.mjs +56 -0
  134. package/src/Button/Button.test.tsx +10 -0
  135. package/src/Button/Button.tsx +13 -5
  136. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  137. package/src/Calendar/Calendar.doc.mjs +1 -1
  138. package/src/Carousel/Carousel.test.tsx +83 -0
  139. package/src/Carousel/Carousel.tsx +8 -2
  140. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  141. package/src/Chat/ChatToolCalls.tsx +36 -15
  142. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  143. package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
  144. package/src/ComplexSelector/ComplexSelector.doc.mjs +2 -2
  145. package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
  146. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  147. package/src/ContextMenu/ContextMenu.tsx +199 -32
  148. package/src/ContextMenu/index.ts +1 -0
  149. package/src/DateInput/DateInput.doc.mjs +1 -1
  150. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  151. package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
  152. package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
  153. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  154. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  155. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  156. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  157. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  158. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  159. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  160. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  161. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  162. package/src/DropdownMenu/index.ts +2 -0
  163. package/src/DropdownMenu/menuWidth.ts +51 -0
  164. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  165. package/src/Field/Field.doc.mjs +1 -1
  166. package/src/FieldStatus/FieldStatus.doc.mjs +22 -1
  167. package/src/FieldStatus/FieldStatus.spec.md +212 -0
  168. package/src/FileInput/FileInput.doc.mjs +1 -1
  169. package/src/IconButton/IconButton.doc.mjs +38 -0
  170. package/src/Indicator/Indicator.doc.mjs +1 -1
  171. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  172. package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
  173. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  174. package/src/MoreMenu/MoreMenu.tsx +13 -1
  175. package/src/MultiSelector/MultiSelector.doc.mjs +17 -1
  176. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  177. package/src/MultiSelector/MultiSelector.tsx +121 -73
  178. package/src/MultiSelector/index.ts +1 -0
  179. package/src/NumberInput/NumberInput.doc.mjs +1 -1
  180. package/src/Popover/Popover.doc.mjs +11 -11
  181. package/src/Popover/Popover.test.tsx +397 -2
  182. package/src/Popover/Popover.tsx +228 -21
  183. package/src/Popover/usePopover.tsx +92 -15
  184. package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
  185. package/src/ProgressBar/ProgressBar.spec.md +208 -0
  186. package/src/RadioList/RadioList.doc.mjs +1 -1
  187. package/src/SegmentedControl/SegmentedControl.doc.mjs +56 -0
  188. package/src/Selector/Selector.doc.mjs +13 -1
  189. package/src/Selector/Selector.test.tsx +135 -0
  190. package/src/Selector/Selector.tsx +141 -82
  191. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  192. package/src/Selector/index.ts +1 -0
  193. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  194. package/src/Selector/useSelectorPresentation.ts +147 -0
  195. package/src/Slider/Slider.doc.mjs +2 -2
  196. package/src/Switch/Switch.doc.mjs +1 -1
  197. package/src/TextArea/TextArea.doc.mjs +1 -1
  198. package/src/TextInput/TextInput.doc.mjs +1 -1
  199. package/src/TimeInput/TimeInput.doc.mjs +1 -1
  200. package/src/Toast/ToastViewport.test.tsx +118 -4
  201. package/src/Toast/useToastGesture.ts +61 -8
  202. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  203. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  204. package/src/Typeahead/Typeahead.doc.mjs +1 -1
  205. package/src/hooks/useAdaptivePresentation.ts +35 -0
  206. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  207. package/src/hooks/useFocusTrap.test.tsx +40 -0
  208. package/src/hooks/useFocusTrap.ts +13 -0
  209. package/src/i18n/InternationalizationContext.ts +3 -0
  210. package/src/i18n/InternationalizationProvider.tsx +8 -1
  211. package/src/i18n/__tests__/resolve.test.ts +23 -1
  212. package/src/i18n/resolve.ts +46 -49
  213. package/src/i18n/useTranslator.ts +2 -7
  214. package/src/theme/themingTargets.test.ts +91 -25
@@ -1,11 +1,30 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
+ const anatomy = [
5
+ {
6
+ name: 'Message box',
7
+ required: true,
8
+ description: 'Painted container for the validation feedback.',
9
+ },
10
+ {
11
+ name: 'Detached icon',
12
+ required: false,
13
+ description: 'Leading status glyph shown only by the detached variant.',
14
+ },
15
+ {
16
+ name: 'Message text',
17
+ required: true,
18
+ description: 'Text describing the validation status.',
19
+ },
20
+ ];
21
+
3
22
  /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
23
  export const docs = {
5
24
  name: 'FieldStatus',
6
25
  displayName: 'Field Status',
7
26
  group: 'Field',
8
- category: 'Data Input',
27
+ category: 'Form Controls',
9
28
  isHiddenFromOverview: true,
10
29
  description:
11
30
  'Status message component for form field validation feedback. Messages are announced to screen readers through persistent live regions (assertive for errors, polite otherwise), so conditional mounting is safe.',
@@ -61,6 +80,7 @@ export const docs = {
61
80
  'Use FieldStatus for general alerts or page-level notices; use Banner or Toast instead.',
62
81
  },
63
82
  ],
83
+ anatomy,
64
84
  },
65
85
  };
66
86
 
@@ -86,6 +106,7 @@ export const docsDense = {
86
106
  'Use FieldStatus for general alerts or page-level notices; use Banner or Toast instead.',
87
107
  },
88
108
  ],
109
+ anatomy,
89
110
  },
90
111
  propDescriptions: {
91
112
  type: 'error/warning/success status tone',
@@ -0,0 +1,212 @@
1
+ ---
2
+ schema_version: 1
3
+ template_version: 1
4
+ kind: component
5
+ id: component:FieldStatus
6
+ authority: current
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-08-30
11
+ owners: [cixzhang, imdreamrunner]
12
+ review_triggers: [public-api, behavior, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/FieldStatus/FieldStatus.test.tsx,
16
+ scripts/check-knowledge.mjs,
17
+ ]
18
+ families: []
19
+ design_specs: []
20
+ architecture: [architecture:component-theming-surface]
21
+ contributing: []
22
+ system_specs: []
23
+ ---
24
+
25
+ # FieldStatus component contract
26
+
27
+ ## Intent
28
+
29
+ FieldStatus presents one validation message for a field or field-like control. It
30
+ owns the visible message treatment and announcement behavior for attached and
31
+ detached presentations; consumer usage remains documented in
32
+ `FieldStatus.doc.mjs`.
33
+
34
+ ## Compatibility and migration
35
+
36
+ - Released default preserved: `yes`
37
+ - Compatibility class: additive documentation only; runtime, DOM, styling, and
38
+ public API remain unchanged
39
+ - Controlled/uncontrolled behavior: not applicable
40
+ - Migration decision: direct `FieldStatus variant="tooltip"` support is rejected.
41
+ `tooltip` remains a field-family sentinel that MUST be handled before rendering
42
+ FieldStatus. Narrowing the currently permissive type/runtime path requires a
43
+ separate compatibility-aware implementation change.
44
+
45
+ Consumer migration instructions belong in consumer docs and release notes.
46
+
47
+ ## Ownership boundary
48
+
49
+ **Owns**
50
+
51
+ - The attached and detached validation-message presentations.
52
+ - Routing visible messages through the persistent announcement channels.
53
+ - The detached presentation's redundant visual status glyph.
54
+
55
+ **Does not own / non-goals**
56
+
57
+ - Whether a caller renders a status message, or which control it describes —
58
+ owned by the composing field component or product callsite.
59
+ - The bordered control's status treatment and on-field tooltip presentation —
60
+ owned by the composing field component.
61
+ - General alerts and page-level notices — owned by the corresponding feedback
62
+ components.
63
+
64
+ ## Public concepts
65
+
66
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
67
+ | ------------ | ----------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ---------- | ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- |
68
+ | Status type | `error`, `warning`, `success` | Selects the message tone and announcement channel. | All rendered presentations. | Required | `component:FieldStatus` | Stable | TypeScript rejects unsupported values. |
69
+ | Presentation | `attached`, `detached` | Selects the message presentation. | All direct FieldStatus usage. The field family's `tooltip` sentinel is handled before FieldStatus renders. | `attached` | `component:FieldStatus` | Stable. | TypeScript rejects values absent from the extensible variant map; its current acceptance of `tooltip` is an implementation gap. |
70
+
71
+ The field family's `tooltip` status presentation suppresses FieldStatus and
72
+ surfaces the message from the control's on-field icon. `tooltip` is not a
73
+ FieldStatus presentation. The public extensible variant map currently permits a
74
+ direct `tooltip` value; that is an implementation gap, not supported behavior.
75
+
76
+ ## Behavioral and layout contract
77
+
78
+ Draft requirements identify their basis so observed code is not mistaken for an
79
+ intentional decision.
80
+
81
+ | ID | Candidate invariant | Basis | Draft review state |
82
+ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------- |
83
+ | FR1 | FieldStatus MUST render the provided message in one visible message box when mounted. | Documented promise and current tests. | Settled intent. |
84
+ | FR2 | The attached presentation MUST render the message without a leading glyph; the detached presentation MUST render its status glyph before the message text. | Documented promise, current DOM, and current tests. | Settled intent. |
85
+ | FR3 | Status type MUST select distinct error, warning, or success treatment and MUST be reflected on the message-box target. | Current behavior and theming tests. | Settled intent. |
86
+ | FR4 | The detached glyph MUST reflect status type on its own target; the target MUST be absent from attached presentation. | Current behavior and target tests. | Settled intent. |
87
+ | FR5 | The root MUST continue to forward its ref, ID, supported DOM props, and consumer styling escape hatches. | Current behavior and tests. | Current compatibility behavior; verify before changing. |
88
+ | FR6 | Message and status-type updates MUST update the visible output without replacing the component contract. | Current behavior and tests. | Current compatibility behavior; verify before changing. |
89
+ | FR7 | Direct FieldStatus usage MUST support only `attached` and `detached`; the Field family MUST consume its `tooltip` sentinel before rendering FieldStatus. | Approved component boundary. | Settled intent; current type acceptance is a gap. |
90
+
91
+ ### Allowed variation
92
+
93
+ - **AV1 — Theme styling.** Theme and consumer overrides may change visual CSS
94
+ properties through the existing targets and styling escape hatches without
95
+ changing anatomy, ownership, or presentation behavior.
96
+ - **AV2 — Message length.** The message may wrap; the detached glyph remains
97
+ aligned with the first text line.
98
+
99
+ ### Representative states
100
+
101
+ | State | Required invariant | Allowed variation |
102
+ | ---------------- | -------------------------------------------------------------- | ----------------------------------------------------------- |
103
+ | Attached error | Message box and text render; no detached glyph renders. | Theme-controlled visual properties on `field-status`. |
104
+ | Detached warning | Message box, leading warning glyph, then message text render. | Theme-controlled visual properties on both current targets. |
105
+ | Detached success | Message box, leading success glyph, then message text render. | Message length and theme-controlled visual properties. |
106
+ | Empty message | The root remains mounted and renders the provided empty value. | No announcement channel is created. |
107
+
108
+ ### Transformation and precedence order
109
+
110
+ - **ORD1 — Presentation styling.** Resolve the default presentation, reflect
111
+ status and presentation through `themeProps`, apply component styles, then
112
+ merge `xstyle`, `className`, and inline `style` in their established order.
113
+ - **ORD2 — Detached content.** Render the decorative status glyph before the
114
+ message text, using the same status-to-glyph mapping as field controls.
115
+
116
+ ### Performance and resources
117
+
118
+ - **PR1 — Announcement work.** A message change schedules only the persistent
119
+ live-region announcement; FieldStatus owns no global event listeners,
120
+ observers, or measurements.
121
+
122
+ ## Accessibility contract
123
+
124
+ - **AR1 — Announcement channel.** Errors MUST announce through the persistent
125
+ assertive region; warnings and successes MUST announce through the persistent
126
+ polite region, including on first mount and when message or type changes.
127
+ - **AR2 — Visible description.** The rendered message box MUST remain available
128
+ to assistive technology for `aria-describedby` association and MUST NOT become
129
+ its own newly mounted live region.
130
+ - **AR3 — Non-color cue.** Detached status MUST include a leading status glyph so
131
+ status is not conveyed only by color or position.
132
+ - **AR4 — Redundant glyph.** The detached glyph MUST remain hidden from assistive
133
+ technology because the visible message already conveys and announces the
134
+ status.
135
+
136
+ ## Design relationships
137
+
138
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
139
+ | ---------------- | ---------------------------------------------------------- | ----------------------------------------------- | -------------- | ------------------ |
140
+ | Message box | Carries the visible validation treatment. | Current behavior and approved component intent. | Supporting | FR1, FR3 |
141
+ | Detached icon | Adds a non-color status cue only in detached presentation. | Current behavior and approved component intent. | Supporting | FR2, FR4, AR3 |
142
+ | Message text | Communicates the validation result in words. | Current behavior and approved component intent. | Prominent | FR1, AR1, AR2 |
143
+
144
+ Current behavior renders the detached icon before message text. The intended
145
+ public theming ownership is the exact map below; it does not add a target or
146
+ change either presentation.
147
+
148
+ ### Theming anatomy
149
+
150
+ <!-- anatomy-theming:v1 -->
151
+
152
+ ```json
153
+ {
154
+ "Message box": {"target": "field-status"},
155
+ "Detached icon": {"target": "field-status-icon"},
156
+ "Message text": {"inherits": "field-status"}
157
+ }
158
+ ```
159
+
160
+ ## Family and system relationships
161
+
162
+ - `architecture:component-theming-surface` owns the qualification and validation
163
+ rules for the two targets and inherited message text.
164
+ - Field-like controls compose FieldStatus but continue to own control rendering,
165
+ status placement decisions, and association with the control.
166
+
167
+ ## Verification map
168
+
169
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
170
+ | ------------------- | --------------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------- |
171
+ | FR1, FR2, AR3, AR4 | `FieldStatus.test.tsx` rendering and detached-icon suites | Attached and detached; all status types | Removing or reordering presentation parts fails DOM and accessibility assertions. | `audit:FieldStatus/anatomy` |
172
+ | FR3, FR4 | `FieldStatus.test.tsx` theme target suites | Attached/detached; error/warning/success | Removing a target or status reflection fails class/data-attribute assertions. | `audit:FieldStatus/theming` |
173
+ | FR5, FR6 | `FieldStatus.test.tsx` forwarding and update suites | Initial render and rerender | Breaking the released root or update behavior fails compatibility assertions. | `audit:FieldStatus/behavior` |
174
+ | AR1, AR2 | `FieldStatus.test.tsx` announcement suites | Mount, message update, type update, empty message | Reintroducing a local live region or misrouting severity fails announcement assertions. | `audit:FieldStatus/accessibility` |
175
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Consumer anatomy and current targets | Missing, extra, duplicated, prefixed, or stale mappings fail repository validation. | `audit:FieldStatus/theming` |
176
+
177
+ ## Decision log
178
+
179
+ ### DEC-1 — Presentation-specific anatomy remains explicit
180
+
181
+ **Reference:** `component:FieldStatus/DEC-1`
182
+ **Decider:** cixzhang, 2026-08-30
183
+
184
+ The detached icon is a separate anatomy part because only detached presentation
185
+ renders it and it owns a target distinct from the message box. Message text
186
+ inherits the message-box target rather than creating a redundant child target.
187
+
188
+ Rejected: collapsing the glyph into a generic message row, which would hide its
189
+ presentation condition and distinct theming owner.
190
+
191
+ ### DEC-2 — `tooltip` is not a FieldStatus presentation
192
+
193
+ **Reference:** `component:FieldStatus/DEC-2`
194
+ **Decider:** cixzhang, 2026-08-30
195
+
196
+ Direct FieldStatus usage supports only `attached` and `detached`. The Field
197
+ family may use `tooltip` as an internal presentation sentinel, but it MUST
198
+ consume that value before rendering FieldStatus and surface the message through
199
+ the control's on-field status treatment instead.
200
+
201
+ Rejected: defining direct `FieldStatus variant="tooltip"` rendering semantics.
202
+ That would expose a field-composition mechanism as a third FieldStatus
203
+ presentation and preserve currently unintentional output.
204
+
205
+ ## Open questions
206
+
207
+ None.
208
+
209
+ ## Content boundary
210
+
211
+ This file does not duplicate consumer prop examples, current audit results,
212
+ implementation steps, or family/system rules. It links to their owners.
@@ -5,7 +5,7 @@
5
5
  export const docs = {
6
6
  name: 'FileInput',
7
7
  displayName: 'File Input',
8
- category: 'Data Input',
8
+ category: 'Form Controls',
9
9
  keywords: ["fileinput","file","upload","drag","drop","dropzone","attachment","browse"],
10
10
  props: [
11
11
  {
@@ -74,6 +74,44 @@ export const docs = {
74
74
 
75
75
  usage: {
76
76
  description: 'A button that shows only an icon with no visible text. Use IconButton in toolbars, table rows, and compact UI where space is tight and the icon is universally understood.',
77
+ accessibility: [
78
+ {
79
+ name: 'Essential icon or spinner arc',
80
+ category: 'Color contrast',
81
+ criterion: '1.4.11 Non-text Contrast',
82
+ requirement: '3:1',
83
+ states: ['Rest', 'Hover', 'Pointer down', 'Loading'],
84
+ description:
85
+ 'IconButton has no visible label. Its icon must have at least 3:1 contrast with the button background in Rest, Hover, and Pointer down. The moving spinner arc must also meet 3:1 while loading.',
86
+ },
87
+ {
88
+ name: 'Visible control boundary',
89
+ category: 'Color contrast',
90
+ criterion: '1.4.11 Non-text Contrast',
91
+ requirement: '3:1 if needed',
92
+ states: ['Rest'],
93
+ description:
94
+ 'The button edge needs 3:1 contrast only when users need it to see the control.',
95
+ },
96
+ {
97
+ name: 'Keyboard focus indicator',
98
+ category: 'Color contrast',
99
+ criterion: '1.4.11 Non-text Contrast',
100
+ requirement: '3:1',
101
+ states: ['Focus visible'],
102
+ description:
103
+ 'The focus outline must have at least 3:1 contrast with the area around the button. Check the red outline on destructive buttons too.',
104
+ },
105
+ {
106
+ name: 'Disabled appearance',
107
+ category: 'Color contrast',
108
+ criterion: '1.4.3 and 1.4.11 exceptions',
109
+ requirement: 'Not required',
110
+ states: ['Disabled'],
111
+ description:
112
+ 'Disabled controls do not need to meet these contrast ratios.',
113
+ },
114
+ ],
77
115
  bestPractices: [
78
116
  { guidance: true, description: 'Make the aria-label specific: a trash icon labeled "Delete conversation" is clearer than just "Delete" for screen readers.' },
79
117
  { guidance: true, description: 'Add a tooltip: even a gear icon can mean Settings, Preferences, or Configure.' },
@@ -6,7 +6,7 @@ export const docs = {
6
6
  name: 'Indicator',
7
7
  displayName: 'Indicator',
8
8
  group: 'Indicator',
9
- category: 'Data Input',
9
+ category: 'Form Controls',
10
10
  isHiddenFromOverview: true,
11
11
  keywords: [
12
12
  'indicator',
@@ -6,7 +6,7 @@ export const docs = {
6
6
  name: 'InputGroup',
7
7
  displayName: 'Input Group',
8
8
  group: 'Field',
9
- category: 'Data Input',
9
+ category: 'Form Controls',
10
10
  isHiddenFromOverview: true,
11
11
  keywords: [
12
12
  'inputgroup',
@@ -61,6 +61,12 @@ export const docs = {
61
61
  "Alignment along the placement axis. Use 'end' to align the menu with the trigger's trailing edge, which is usually what an overflow menu wants.",
62
62
  default: "'start'",
63
63
  },
64
+ {
65
+ name: 'presentation',
66
+ type: "'popover' | 'bottom-sheet' | 'adaptive'",
67
+ description: 'Presentation policy forwarded to DropdownMenu. `adaptive` keeps the anchored popover on pointer-based layouts and uses a BottomSheet at 768px and below when the primary pointer is coarse.',
68
+ default: "'popover'",
69
+ },
64
70
  {
65
71
  name: 'onOpenChange',
66
72
  type: '(isOpen: boolean) => void',
@@ -93,6 +99,7 @@ export const docs = {
93
99
  bestPractices: [
94
100
  { guidance: true, description: 'Use for overflow or secondary actions; keep primary actions visible outside the menu.' },
95
101
  { guidance: true, description: 'Use dividers or sections to group related actions when the menu has many items.' },
102
+ { guidance: true, description: 'Use `presentation="adaptive"` when the visible overflow trigger should open a thumb-reachable BottomSheet on compact touch devices.' },
96
103
  { guidance: false, description: 'Hide primary actions inside a MoreMenu; they should be directly visible.' },
97
104
  ],
98
105
  },
@@ -153,6 +160,12 @@ export const docsZh = {
153
160
  "沿放置轴的对齐方式。使用 'end' 让菜单与触发器的尾部边缘对齐,这通常是溢出菜单所需的效果。",
154
161
  default: "'start'",
155
162
  },
163
+ {
164
+ name: 'presentation',
165
+ type: "'popover' | 'bottom-sheet' | 'adaptive'",
166
+ description: '菜单呈现策略。`adaptive` 在指针布局中使用锚定浮层,在主指针为粗略指针且宽度不超过 768px 时使用 BottomSheet。',
167
+ default: "'popover'",
168
+ },
156
169
  {
157
170
  name: 'xstyle',
158
171
  type: 'StyleXStyles',
@@ -171,6 +184,7 @@ export const docsZh = {
171
184
  bestPractices: [
172
185
  { guidance: true, description: 'Use for overflow or secondary actions; keep primary actions visible outside the menu.' },
173
186
  { guidance: true, description: 'Use dividers or sections to group related actions when the menu has many items.' },
187
+ { guidance: true, description: 'Use `presentation="adaptive"` when the visible overflow trigger should open a thumb-reachable BottomSheet on compact touch devices.' },
174
188
  { guidance: false, description: 'Hide primary actions inside a MoreMenu; they should be directly visible.' },
175
189
  ],
176
190
  },
@@ -186,6 +200,7 @@ export const docsDense = {
186
200
  bestPractices: [
187
201
  { guidance: true, description: 'Use for overflow or secondary actions; keep primary actions visible outside the menu.' },
188
202
  { guidance: true, description: 'Use dividers or sections to group related actions when the menu has many items.' },
203
+ { guidance: true, description: 'Use `presentation="adaptive"` when the visible overflow trigger should open a thumb-reachable BottomSheet on compact touch devices.' },
189
204
  { guidance: false, description: 'Hide primary actions inside a MoreMenu; they should be directly visible.' },
190
205
  ],
191
206
  },
@@ -199,6 +214,8 @@ export const docsDense = {
199
214
  placement: 'Menu position relative to trigger. Logical (RTL mirrors).',
200
215
  alignment:
201
216
  "Alignment along the placement axis. 'end' aligns with the trigger's trailing edge.",
217
+ presentation:
218
+ 'Popover, BottomSheet, or adaptive compact-touch presentation.',
202
219
  xstyle:
203
220
  'StyleX styles for layout customization (margins, positioning, sizing). Must be stylex.create() value.',
204
221
  },
@@ -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,
@@ -6,7 +6,7 @@ export const docs = {
6
6
  name: 'MultiSelector',
7
7
  displayName: 'Multi Selector',
8
8
  group: 'Selector',
9
- category: 'Data Input',
9
+ category: 'Form Controls',
10
10
  keywords: [
11
11
  'multiselect',
12
12
  'checkbox',
@@ -224,6 +224,13 @@ export const docs = {
224
224
  '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
225
  default: "'start'",
226
226
  },
227
+ {
228
+ name: 'presentation',
229
+ type: "'popover' | 'bottom-sheet' | 'adaptive'",
230
+ description:
231
+ 'How the option list is presented. adaptive uses a bottom sheet on compact touch screens and an anchored popover otherwise.',
232
+ default: "'popover'",
233
+ },
227
234
  {
228
235
  name: 'width',
229
236
  type: 'SizeValue',
@@ -290,6 +297,11 @@ export const docs = {
290
297
  description:
291
298
  '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
299
  },
300
+ {
301
+ guidance: true,
302
+ description:
303
+ 'Use presentation="adaptive" when the multi-selector should become a bottom sheet on compact touch screens.',
304
+ },
293
305
  {
294
306
  guidance: false,
295
307
  description: 'Use for single-value selection; use Selector instead.',
@@ -349,6 +361,8 @@ export const docsZh = {
349
361
  '状态消息的放置方式:attached 直接叠加在输入框下方;detached 作为独立元素浮于下方并留有间距。',
350
362
  renderOption:
351
363
  '每个可选选项的自定义渲染函数。不会用于分隔线、分组或全选行。',
364
+ presentation:
365
+ '选项列表的呈现方式:锚定弹出框、底部抽屉,或根据紧凑触控屏自适应。',
352
366
  xstyle: '布局自定义的 StyleX 样式,必须是 stylex.create() 值。',
353
367
  },
354
368
  },
@@ -487,6 +501,8 @@ export const docsDense = {
487
501
  'status message placement; ghost detaches attached by default; use tooltip for compact toolbars.',
488
502
  renderOption:
489
503
  'custom render fn per selectable option; not dividers/sections/select-all',
504
+ presentation:
505
+ 'popover, bottom-sheet, or compact-touch adaptive presentation',
490
506
  xstyle: 'StyleX layout styles; stylex.create() only',
491
507
  },
492
508
  },