@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.
- package/CHANGELOG.md +19 -0
- package/dist/BottomSheet/BottomSheet.d.ts +3 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -3
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
- package/dist/Button/Button.d.ts +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -4
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +34 -17
- package/dist/ContextMenu/ContextMenu.d.ts +14 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +149 -26
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +340 -26
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
- package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
- package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/menuWidth.d.ts +14 -0
- package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
- package/dist/DropdownMenu/menuWidth.js +35 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
- package/dist/DropdownMenu/useMenuOverflow.js +60 -0
- package/dist/MoreMenu/MoreMenu.d.ts +8 -2
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +2 -0
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +97 -68
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Popover/Popover.d.ts +10 -6
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +141 -16
- package/dist/Popover/usePopover.d.ts +4 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +52 -5
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +106 -80
- package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
- package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
- package/dist/Selector/SelectorBottomSheet.js +83 -0
- package/dist/Selector/index.d.ts +1 -1
- package/dist/Selector/index.d.ts.map +1 -1
- package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
- package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
- package/dist/Selector/selectorPresentation.stylex.js +21 -0
- package/dist/Selector/useSelectorPresentation.d.ts +32 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
- package/dist/Selector/useSelectorPresentation.js +90 -0
- package/dist/Toast/useToastGesture.d.ts.map +1 -1
- package/dist/Toast/useToastGesture.js +46 -9
- package/dist/astryx.css +19 -1
- package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
- package/dist/hooks/useAdaptivePresentation.js +19 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
- package/dist/hooks/useFocusReturnVisibility.js +35 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +12 -0
- package/dist/i18n/InternationalizationContext.d.ts +2 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +3 -1
- package/dist/i18n/resolve.d.ts +1 -1
- package/dist/i18n/resolve.d.ts.map +1 -1
- package/dist/i18n/resolve.js +37 -38
- package/dist/i18n/useTranslator.d.ts.map +1 -1
- package/dist/i18n/useTranslator.js +2 -3
- package/locales/af-ZA.json +0 -4
- package/locales/ar-SA.json +0 -4
- package/locales/ca-ES.json +0 -4
- package/locales/cs-CZ.json +0 -4
- package/locales/da-DK.json +0 -4
- package/locales/de-DE.json +0 -4
- package/locales/el-GR.json +0 -4
- package/locales/en.json +20 -0
- package/locales/es-ES.json +0 -4
- package/locales/fi-FI.json +0 -4
- package/locales/fr-FR.json +0 -4
- package/locales/he-IL.json +0 -4
- package/locales/hu-HU.json +0 -4
- package/locales/it-IT.json +0 -4
- package/locales/ja-JP.json +0 -4
- package/locales/ko-KR.json +0 -4
- package/locales/nl-NL.json +0 -4
- package/locales/no-NO.json +0 -4
- package/locales/pl-PL.json +0 -4
- package/locales/pseudo.json +15 -0
- package/locales/pt-BR.json +0 -4
- package/locales/pt-PT.json +0 -4
- package/locales/ro-RO.json +0 -4
- package/locales/ru-RU.json +0 -4
- package/locales/sr-SP.json +0 -4
- package/locales/sv-SE.json +0 -4
- package/locales/tr-TR.json +0 -4
- package/locales/uk-UA.json +0 -4
- package/locales/vi-VN.json +0 -4
- package/locales/zh-CN.json +0 -4
- package/locales/zh-TW.json +0 -4
- package/package.json +3 -3
- package/src/BottomSheet/BottomSheet.doc.mjs +6 -0
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +7 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
- package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
- package/src/Button/Button.doc.mjs +56 -0
- package/src/Button/Button.test.tsx +10 -0
- package/src/Button/Button.tsx +13 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatToolCalls.test.tsx +44 -0
- package/src/Chat/ChatToolCalls.tsx +36 -15
- package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
- package/src/ComplexSelector/ComplexSelector.doc.mjs +2 -2
- package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/DateInput/DateInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
- package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
- package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
- package/src/DropdownMenu/DropdownMenu.tsx +459 -28
- package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
- package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
- package/src/DropdownMenu/index.ts +2 -0
- package/src/DropdownMenu/menuWidth.ts +51 -0
- package/src/DropdownMenu/useMenuOverflow.ts +77 -0
- package/src/Field/Field.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +22 -1
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/FileInput/FileInput.doc.mjs +1 -1
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Indicator/Indicator.doc.mjs +1 -1
- package/src/InputGroup/InputGroup.doc.mjs +1 -1
- package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +17 -1
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/NumberInput/NumberInput.doc.mjs +1 -1
- package/src/Popover/Popover.doc.mjs +11 -11
- package/src/Popover/Popover.test.tsx +397 -2
- package/src/Popover/Popover.tsx +228 -21
- package/src/Popover/usePopover.tsx +92 -15
- package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
- package/src/ProgressBar/ProgressBar.spec.md +208 -0
- package/src/RadioList/RadioList.doc.mjs +1 -1
- package/src/SegmentedControl/SegmentedControl.doc.mjs +56 -0
- package/src/Selector/Selector.doc.mjs +13 -1
- package/src/Selector/Selector.test.tsx +135 -0
- package/src/Selector/Selector.tsx +141 -82
- package/src/Selector/SelectorBottomSheet.tsx +97 -0
- package/src/Selector/index.ts +1 -0
- package/src/Selector/selectorPresentation.stylex.ts +20 -0
- package/src/Selector/useSelectorPresentation.ts +147 -0
- package/src/Slider/Slider.doc.mjs +2 -2
- package/src/Switch/Switch.doc.mjs +1 -1
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +1 -1
- package/src/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
- package/src/Typeahead/Typeahead.doc.mjs +1 -1
- package/src/hooks/useAdaptivePresentation.ts +35 -0
- package/src/hooks/useFocusReturnVisibility.ts +45 -0
- package/src/hooks/useFocusTrap.test.tsx +40 -0
- package/src/hooks/useFocusTrap.ts +13 -0
- package/src/i18n/InternationalizationContext.ts +3 -0
- package/src/i18n/InternationalizationProvider.tsx +8 -1
- package/src/i18n/__tests__/resolve.test.ts +23 -1
- package/src/i18n/resolve.ts +46 -49
- package/src/i18n/useTranslator.ts +2 -7
- 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: '
|
|
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.
|
|
@@ -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.' },
|
|
@@ -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 {
|
|
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: '
|
|
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
|
},
|