@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.
- 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/locales/en.json +20 -0
- package/locales/pseudo.json +15 -0
- package/package.json +2 -2
- package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
- package/src/BottomSheet/BottomSheet.spec.md +177 -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/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/CheckboxList/CheckboxList.doc.mjs +172 -31
- package/src/CheckboxList/CheckboxList.spec.md +214 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +223 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
- package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +61 -1
- package/src/ContextMenu/ContextMenu.spec.md +206 -0
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +150 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +305 -40
- package/src/DropdownMenu/DropdownMenu.spec.md +245 -0
- 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/FieldStatus/FieldStatus.doc.mjs +21 -0
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +161 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +191 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +141 -0
- package/src/Layout/Layout.doc.mjs +32 -0
- package/src/Layout/Layout.spec.md +199 -0
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +197 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +200 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +208 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +59 -0
- package/src/MoreMenu/MoreMenu.spec.md +221 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +128 -0
- package/src/MultiSelector/MultiSelector.spec.md +266 -0
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +133 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +161 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +156 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +225 -0
- 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/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +260 -0
- package/src/Resizable/Resizable.doc.mjs +59 -16
- package/src/Resizable/Resizable.spec.md +194 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +138 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
- package/src/Selector/Selector.doc.mjs +12 -0
- package/src/Selector/Selector.spec.md +202 -0
- 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/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +126 -0
- package/src/Slider/Slider.doc.mjs +184 -30
- package/src/Slider/Slider.spec.md +223 -0
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +154 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +137 -0
- package/src/Switch/Switch.doc.mjs +40 -0
- package/src/Switch/Switch.spec.md +174 -0
- package/src/Table/Table.doc.mjs +102 -24
- package/src/Table/Table.spec.md +283 -0
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +205 -0
- package/src/TextArea/TextArea.doc.mjs +255 -45
- package/src/TextArea/TextArea.spec.md +204 -0
- 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/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +173 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +141 -0
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +220 -0
- package/src/Typeahead/Typeahead.doc.mjs +83 -1
- package/src/Typeahead/Typeahead.spec.md +250 -0
- 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/theme/themingTargets.test.ts +91 -25
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:SegmentedControl
|
|
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, accessibility]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/SegmentedControl/SegmentedControl.test.tsx,
|
|
16
|
+
packages/core/src/theme/themingTargets.test.ts,
|
|
17
|
+
scripts/check-knowledge.mjs,
|
|
18
|
+
]
|
|
19
|
+
families: []
|
|
20
|
+
design_specs: []
|
|
21
|
+
architecture: [architecture:component-theming-surface]
|
|
22
|
+
contributing: []
|
|
23
|
+
system_specs: []
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# SegmentedControl component contract
|
|
27
|
+
|
|
28
|
+
## Intent
|
|
29
|
+
|
|
30
|
+
SegmentedControl presents a group of mutually exclusive choices through
|
|
31
|
+
SegmentedControlItem children. This draft records the current consumer anatomy
|
|
32
|
+
and theming ownership without changing selection behavior, targets, or public
|
|
33
|
+
API.
|
|
34
|
+
|
|
35
|
+
## Compatibility and migration
|
|
36
|
+
|
|
37
|
+
- Released default preserved: `yes`
|
|
38
|
+
- Compatibility class: additive documentation only; runtime, DOM, styling,
|
|
39
|
+
targets, and public API remain unchanged
|
|
40
|
+
- Controlled/uncontrolled behavior: unchanged; selection remains controlled by
|
|
41
|
+
`value` and `onChange`
|
|
42
|
+
- Migration decision: none
|
|
43
|
+
|
|
44
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
45
|
+
|
|
46
|
+
## Ownership boundary
|
|
47
|
+
|
|
48
|
+
**Owns**
|
|
49
|
+
|
|
50
|
+
- The radiogroup control and its current `segmented-control` theming target.
|
|
51
|
+
- SegmentedControlItem's segment button and its current
|
|
52
|
+
`segmented-control-item` theming target.
|
|
53
|
+
- Rendering optional icon content and a visible label inside a segment.
|
|
54
|
+
|
|
55
|
+
**Does not own / non-goals**
|
|
56
|
+
|
|
57
|
+
- The artwork supplied through an item's `icon` prop — owned by the caller.
|
|
58
|
+
- A separate anatomy part or target for selected state; selection is state on the
|
|
59
|
+
segment target.
|
|
60
|
+
- A visible control-level label; the control's required `label` is its accessible
|
|
61
|
+
name and is not rendered as visual anatomy.
|
|
62
|
+
|
|
63
|
+
## Public concepts
|
|
64
|
+
|
|
65
|
+
No new public concept is introduced. Consumer props and usage remain documented
|
|
66
|
+
in `SegmentedControl.doc.mjs` and `SegmentedControlItem.doc.mjs`.
|
|
67
|
+
|
|
68
|
+
## Behavioral and layout contract
|
|
69
|
+
|
|
70
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
71
|
+
| --- | ------------------------------------------------------------------------------------------------------------- | ------------------------------- | -------------------------------------------------- |
|
|
72
|
+
| FR1 | The radiogroup container carries the current `segmented-control` target and renders its supplied children. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
73
|
+
| FR2 | Each SegmentedControlItem renders one radio button carrying the current `segmented-control-item` target. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
74
|
+
| FR3 | A visible item label and optional icon render inside the item without separate public targets. | Current source and tests | Verified current behavior; no target change |
|
|
75
|
+
| FR4 | Selected and disabled remain reflected states on `segmented-control-item`, not standalone anatomy or targets. | Current source, docs, and tests | Verified current behavior; no target change |
|
|
76
|
+
|
|
77
|
+
### Allowed variation
|
|
78
|
+
|
|
79
|
+
- A segment may render its visible label, an icon and label, or an icon while the
|
|
80
|
+
required label supplies the accessible name.
|
|
81
|
+
|
|
82
|
+
### Representative states
|
|
83
|
+
|
|
84
|
+
- Selected, unselected, disabled, and enabled items retain the same anatomy.
|
|
85
|
+
Selection and disabled styling vary on the segment target.
|
|
86
|
+
|
|
87
|
+
### Transformation and precedence order
|
|
88
|
+
|
|
89
|
+
- No new selection, focus, layout, or styling precedence rule is introduced.
|
|
90
|
+
|
|
91
|
+
### Performance and resources
|
|
92
|
+
|
|
93
|
+
- No new performance or resource rule is introduced.
|
|
94
|
+
|
|
95
|
+
## Accessibility contract
|
|
96
|
+
|
|
97
|
+
This draft does not change or extend the existing radiogroup, radio, accessible
|
|
98
|
+
name, roving focus, disabled-message, or selection behavior.
|
|
99
|
+
|
|
100
|
+
## Design relationships
|
|
101
|
+
|
|
102
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
103
|
+
| ---------------- | ----------------------------------------------------------- | ------------------------------ | -------------- | ------------------ |
|
|
104
|
+
| Control | Presents the current grouped choice container. | Current source and public docs | Supporting | FR1 |
|
|
105
|
+
| Segment | Presents one mutually exclusive choice and its item states. | Current source and public docs | Prominent | FR2, FR4 |
|
|
106
|
+
| Label | Identifies a segment visually when not hidden. | Current source and public docs | Prominent | FR3 |
|
|
107
|
+
| Icon | Presents optional caller-supplied artwork within a segment. | Caller-supplied content | Supporting | FR3 |
|
|
108
|
+
|
|
109
|
+
Selection is a state of Segment, not a separate anatomy part.
|
|
110
|
+
|
|
111
|
+
### Theming anatomy
|
|
112
|
+
|
|
113
|
+
<!-- anatomy-theming:v1 -->
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"Control": {"target": "segmented-control"},
|
|
118
|
+
"Segment": {"target": "segmented-control-item"},
|
|
119
|
+
"Label": {"inherits": "segmented-control-item"},
|
|
120
|
+
"Icon": {"inherits": "segmented-control-item"}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Family and system relationships
|
|
125
|
+
|
|
126
|
+
- `architecture:component-theming-surface` owns anatomy qualification, inherited
|
|
127
|
+
parts, and state-on-target rules.
|
|
128
|
+
|
|
129
|
+
## Verification map
|
|
130
|
+
|
|
131
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
132
|
+
| ------------------- | ----------------------------------------------------------- | ---------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------- |
|
|
133
|
+
| FR1, FR2 | `SegmentedControl.test.tsx` radiogroup and rendering suites | Control with multiple segment items | Removing the group or item semantics fails existing role and content assertions. | `audit:SegmentedControl/anatomy` |
|
|
134
|
+
| FR3 | `SegmentedControl.test.tsx` label and icon suites | Visible label, icon with label, icon-only item | Removing or changing current label/icon rendering fails existing DOM assertions. | `audit:SegmentedControl/anatomy` |
|
|
135
|
+
| FR4 | `SegmentedControl.test.tsx` selection and disabled suites | Selected, unselected, and disabled items | Breaking reflected item state fails current ARIA, class, or data-attribute assertions. | `audit:SegmentedControl/theming` |
|
|
136
|
+
| Target inventory | `packages/core/src/theme/themingTargets.test.ts` | Control and item targets | Runtime and documented target metadata drift fails target validation. | `audit:SegmentedControl/theming` |
|
|
137
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and both current targets | Missing, extra, prefixed, or stale mappings fail repository validation. | `audit:SegmentedControl/theming` |
|
|
138
|
+
|
|
139
|
+
## Decision log
|
|
140
|
+
|
|
141
|
+
None. This draft records current facts and introduces no component-local design,
|
|
142
|
+
selection, accessibility, or API decision.
|
|
143
|
+
|
|
144
|
+
## Open questions
|
|
145
|
+
|
|
146
|
+
None.
|
|
147
|
+
|
|
148
|
+
## Content boundary
|
|
149
|
+
|
|
150
|
+
This file does not duplicate consumer prop tables, examples, selection or focus
|
|
151
|
+
mechanics, implementation steps, or system rules. It links to their owners.
|
|
@@ -194,6 +194,13 @@ export const docs = {
|
|
|
194
194
|
'Which edge of the option row carries the selected mark. start reserves a mark column ahead of every label so they stay aligned, the way a native menu does; end is the house convention shared with Typeahead and CommandPalette.',
|
|
195
195
|
default: "'end'",
|
|
196
196
|
},
|
|
197
|
+
{
|
|
198
|
+
name: 'presentation',
|
|
199
|
+
type: "'popover' | 'bottom-sheet' | 'adaptive'",
|
|
200
|
+
description:
|
|
201
|
+
'How the option list is presented. adaptive uses a bottom sheet on compact touch screens and an anchored popover otherwise.',
|
|
202
|
+
default: "'popover'",
|
|
203
|
+
},
|
|
197
204
|
{
|
|
198
205
|
name: 'width',
|
|
199
206
|
type: 'SizeValue',
|
|
@@ -253,6 +260,11 @@ export const docs = {
|
|
|
253
260
|
description:
|
|
254
261
|
'Use variant="ghost" when a selector sits in a toolbar with ghost buttons. If validation status is needed there, prefer statusVariant="tooltip" so the toolbar height stays compact.',
|
|
255
262
|
},
|
|
263
|
+
{
|
|
264
|
+
guidance: true,
|
|
265
|
+
description:
|
|
266
|
+
'Use presentation="adaptive" when the selector should become a bottom sheet on compact touch screens.',
|
|
267
|
+
},
|
|
256
268
|
{
|
|
257
269
|
guidance: false,
|
|
258
270
|
description:
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:Selector
|
|
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, layout, theming, accessibility]
|
|
13
|
+
verified_by:
|
|
14
|
+
[
|
|
15
|
+
packages/core/src/Selector/Selector.test.tsx,
|
|
16
|
+
packages/core/src/Selector/Selector.source-build.test.mjs,
|
|
17
|
+
]
|
|
18
|
+
families: []
|
|
19
|
+
design_specs: []
|
|
20
|
+
architecture:
|
|
21
|
+
[
|
|
22
|
+
architecture:component-theming-surface,
|
|
23
|
+
architecture:icon-resolution-and-component-slots,
|
|
24
|
+
architecture:interaction-modality,
|
|
25
|
+
architecture:layer-runtime,
|
|
26
|
+
architecture:public-component-api,
|
|
27
|
+
]
|
|
28
|
+
contributing: []
|
|
29
|
+
system_specs: [spec:AST-004/DEC-1]
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Selector component contract
|
|
33
|
+
|
|
34
|
+
## Intent
|
|
35
|
+
|
|
36
|
+
Selector lets a person choose one value from a moderate list while keeping the
|
|
37
|
+
trigger usable as a form field or compact toolbar control. It owns single-value
|
|
38
|
+
selection, option navigation, search within the supplied options, and the
|
|
39
|
+
connection between the closed trigger and its selection surface.
|
|
40
|
+
|
|
41
|
+
## Compatibility and migration
|
|
42
|
+
|
|
43
|
+
- Released defaults and behavior remain unchanged by this record.
|
|
44
|
+
- `indicatorPosition` defaults to `end`; every option row currently reserves the
|
|
45
|
+
indicator column at the configured logical edge.
|
|
46
|
+
- `presentation` defaults to `popover`. `bottom-sheet` is an explicit modal
|
|
47
|
+
presentation, and `adaptive` selects it on compact coarse-pointer screens.
|
|
48
|
+
- `hasClear` changes the value contract to include `null`; that distinction is
|
|
49
|
+
already part of the public type.
|
|
50
|
+
- `spec:AST-004/DEC-1` accepts a future change that collapses an empty indicator
|
|
51
|
+
column. It is not shipped behavior and does not replace FR3 until implementation,
|
|
52
|
+
visual verification, and an update to this current contract land together.
|
|
53
|
+
|
|
54
|
+
## Ownership boundary
|
|
55
|
+
|
|
56
|
+
**Owns**
|
|
57
|
+
|
|
58
|
+
- Choosing one value from supplied options.
|
|
59
|
+
- Trigger, listbox, option, search, empty, loading, and disabled behavior.
|
|
60
|
+
- Keyboard navigation and announcements for that selection flow.
|
|
61
|
+
- Selector-specific composition of shared Field, Layer, adaptive presentation,
|
|
62
|
+
and indicator behavior.
|
|
63
|
+
|
|
64
|
+
**Does not own / non-goals**
|
|
65
|
+
|
|
66
|
+
- Action or navigation menus; DropdownMenu owns those.
|
|
67
|
+
- Multi-value selection; MultiSelector owns it.
|
|
68
|
+
- Product-specific option content or filtering performed outside the supplied
|
|
69
|
+
option list.
|
|
70
|
+
- Family-wide input sizing, loading, end-lane, or disabled-reason policy. The
|
|
71
|
+
draft `family:input-fields` record is non-authoritative context only.
|
|
72
|
+
|
|
73
|
+
## Public concepts
|
|
74
|
+
|
|
75
|
+
This table names semantic concepts reviewers need. Prop syntax, complete defaults,
|
|
76
|
+
and examples remain in `Selector.doc.mjs`.
|
|
77
|
+
|
|
78
|
+
| Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
|
|
79
|
+
| ---------------------- | -------------------------------------- | ------------------------------------------------------- | ----------------------------------------- | --------- | -------- | --------- | ------------------------------- |
|
|
80
|
+
| trigger variant | `input`, `ghost` | Form-field or toolbar presentation | All trigger states | `input` | Selector | released | TypeScript rejects other values |
|
|
81
|
+
| size | `sm`, `md`, `lg` | Trigger and option-row density | All presentations | `md` | Selector | released | TypeScript rejects other values |
|
|
82
|
+
| selected-mark position | `start`, `end` | Logical edge containing the reserved selection column | Every option row | `end` | Selector | released | TypeScript rejects other values |
|
|
83
|
+
| presentation | `popover`, `bottom-sheet`, `adaptive` | Anchored pointer surface or modal compact-touch surface | All trigger variants | `popover` | Selector | released | TypeScript rejects other values |
|
|
84
|
+
| popup semantics | `listbox`; modal dialog containing one | Semantics follow the active presentation | Popover; bottom sheet | `listbox` | Selector | released | No separate role prop is public |
|
|
85
|
+
| option-row state | `selected`, `disabled` | Stable theming state on each option row | Every rendered option | neither | Selector | released | Unknown states are not emitted |
|
|
86
|
+
|
|
87
|
+
## Behavioral and layout contract
|
|
88
|
+
|
|
89
|
+
These requirements describe shipped behavior on current `main`.
|
|
90
|
+
|
|
91
|
+
| ID | Shipped invariant | Evidence |
|
|
92
|
+
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
93
|
+
| FR1 | Selecting an enabled option updates the one selected value, closes the active presentation, and returns the trigger to its stable closed state. | Consumer docs and selection interaction tests |
|
|
94
|
+
| FR2 | Without explicit placement, a non-search popover aligns the selected row over the trigger and clamps it to the viewport. Search popovers and explicit placement use normal Layer positioning. | Consumer docs, implementation, and geometry tests |
|
|
95
|
+
| FR3 | Every option row reserves one selection-mark column at `indicatorPosition`, even when the default unchecked indicator draws nothing. The column has a minimum width and can grow for a larger themed replacement. | `itemMarkColumn`, indicator-position tests, and theme tests |
|
|
96
|
+
| FR4 | `popover` uses an anchored Popover. `bottom-sheet` uses a modal BottomSheet. `adaptive` resolves to the modal bottom sheet on compact coarse-pointer screens and Popover otherwise. | Presentation controller and adaptive-presentation tests |
|
|
97
|
+
| FR5 | While `isLoading` is true, the trigger exposes busy state and the listbox suppresses empty and no-results output. | Loading, empty-state, and announcement tests |
|
|
98
|
+
|
|
99
|
+
### Allowed variation
|
|
100
|
+
|
|
101
|
+
- **AV1 — Indicator rendering.** A theme may replace the check indicator. The
|
|
102
|
+
replacement may draw in both selected and unselected states; the current row
|
|
103
|
+
still reserves its indicator column.
|
|
104
|
+
- **AV2 — Option content.** `renderOption` may replace visible option content,
|
|
105
|
+
while Selector keeps row role, selection, disabled state, navigation, and
|
|
106
|
+
theming state.
|
|
107
|
+
- **AV3 — Selected value content.** `renderValue` may replace the closed value
|
|
108
|
+
display without changing the placeholder or trigger's selection role.
|
|
109
|
+
|
|
110
|
+
### Representative states
|
|
111
|
+
|
|
112
|
+
| State | Required invariant | Allowed variation |
|
|
113
|
+
| ------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
|
|
114
|
+
| closed with no value | Label and placeholder identify the field | Consumer placeholder text |
|
|
115
|
+
| closed with a value | Selected option is represented in the trigger | Custom `renderValue` content |
|
|
116
|
+
| pointer / popover | Anchored surface exposes the listbox without modal-dialog semantics | Default or explicit placement |
|
|
117
|
+
| compact coarse pointer | BottomSheet exposes a modal dialog containing the listbox | Explicit `bottom-sheet` or resolved `adaptive` |
|
|
118
|
+
| searching | Visible options, keyboard navigation, and announced result count use one filter | Consumer search and empty text |
|
|
119
|
+
| loading | Trigger is busy; empty and no-results output is suppressed | Consumer loading duration |
|
|
120
|
+
| disabled with reason | Trigger remains focusable enough to expose the reason while activation stays blocked | Consumer reason text |
|
|
121
|
+
| selected/unselected rows | Row semantics and theming state are correct; both reserve the indicator column | Start/end position and themed indicator representation |
|
|
122
|
+
|
|
123
|
+
### Transformation and precedence order
|
|
124
|
+
|
|
125
|
+
- **ORD1 — Option normalization precedes filtering and selection.** Strings and
|
|
126
|
+
option objects become one option shape before search, keyboard matching,
|
|
127
|
+
rendering, and value comparison.
|
|
128
|
+
- **ORD2 — Caller-selected content wins deliberately.** `startIcon` takes
|
|
129
|
+
precedence over a selected option's icon; explicit placement takes precedence
|
|
130
|
+
over selected-item overlay alignment.
|
|
131
|
+
|
|
132
|
+
### Performance and resources
|
|
133
|
+
|
|
134
|
+
- **PR1 — Search does not add effect-driven result-count renders.** The next
|
|
135
|
+
filtered count is derived from the input change and announced once for that
|
|
136
|
+
query.
|
|
137
|
+
- **PR2 — Geometry work is scoped to the open popover.** Selected-item alignment
|
|
138
|
+
may read browser layout while opening; it does not impose document-wide or
|
|
139
|
+
persistent observation while closed.
|
|
140
|
+
|
|
141
|
+
## Accessibility contract
|
|
142
|
+
|
|
143
|
+
- **AR1 — Semantics follow presentation.** The anchored presentation exposes a
|
|
144
|
+
listbox through a Popover and the trigger uses `aria-haspopup="listbox"`. The
|
|
145
|
+
touch presentation exposes a modal BottomSheet dialog containing the listbox
|
|
146
|
+
and the trigger uses `aria-haspopup="dialog"`.
|
|
147
|
+
- **AR2 — Focus follows the active interaction model.** The anchored presentation
|
|
148
|
+
keeps the combobox relationship at the trigger/search control. The modal touch
|
|
149
|
+
presentation moves focus into its search control or listbox and restores focus
|
|
150
|
+
to the trigger when it closes.
|
|
151
|
+
- **AR3 — Keyboard selection matches the visible set.** Arrow, Home/End,
|
|
152
|
+
typeahead, search, Enter, Escape, and Tab behavior operate on the options a
|
|
153
|
+
person can currently perceive.
|
|
154
|
+
|
|
155
|
+
## Design relationships
|
|
156
|
+
|
|
157
|
+
No current design spec is linked.
|
|
158
|
+
|
|
159
|
+
| Anatomy or state | Current representation requirement | Representation authority | Hierarchy role | Component contract |
|
|
160
|
+
| -------------------------------- | --------------------------------------------------------------------------------------- | -------------------------- | ----------------------- | ------------------ |
|
|
161
|
+
| selected mark and option spacing | Every row reserves the indicator column, preserving label alignment and available width | existing shipped behavior | supporting | FR3 |
|
|
162
|
+
| input versus ghost trigger | none recorded | existing released behavior | form or toolbar control | Public concepts |
|
|
163
|
+
|
|
164
|
+
The approved but unimplemented replacement for the first row is owned by
|
|
165
|
+
`spec:AST-004/DEC-1`.
|
|
166
|
+
|
|
167
|
+
## Family and system relationships
|
|
168
|
+
|
|
169
|
+
- The current architecture links in frontmatter own public API, theming, icon
|
|
170
|
+
resolution, interaction modality, and Layer behavior used by Selector.
|
|
171
|
+
- `spec:AST-004/DEC-1` owns the accepted future indicator-space change. Its
|
|
172
|
+
`accepted` phase is direction for implementation, not evidence that the runtime
|
|
173
|
+
has changed.
|
|
174
|
+
- `family:input-fields` remains a draft. It may inform future reconciliation, but
|
|
175
|
+
this current component contract neither backlinks to it in frontmatter nor
|
|
176
|
+
depends on it for present policy.
|
|
177
|
+
|
|
178
|
+
## Verification map
|
|
179
|
+
|
|
180
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
181
|
+
| --------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -------------------------------- |
|
|
182
|
+
| FR1, AR2, AR3 | `Selector.test.tsx` selection, focus, and keyboard suites | closed/open, search/non-search, disabled option | Removing selection wiring, focus movement, or keyboard behavior fails the named interaction tests | `audit:Selector/behavior` |
|
|
183
|
+
| FR2 | placement and selected-item geometry tests | default, explicit, RTL, transformed entry | Using the wrong positioning model fails the expected position-area or margin | `audit:Selector/behavior` |
|
|
184
|
+
| FR3, AV1 | `itemMarkColumn` source inspection plus indicator-position and replacement-indicator tests | start/end, selected/unselected, check/radio | Removing the wrapper fails row-structure tests; changing its reserved width requires source/layout review | `audit:Selector/design-rendered` |
|
|
185
|
+
| FR4, AR1, AR2 | presentation tests plus `aria-haspopup` source review | pointer, compact coarse pointer, search/non-search | Wrong Popover/dialog roles or focus destinations fail tests; `aria-haspopup` values require source/a11y review | `audit:Selector/accessibility` |
|
|
186
|
+
| FR5 | loading, empty-state, and live-region tests | empty options, unmatched search, loading | Empty/no-results output appears or is announced while loading | `audit:Selector/behavior` |
|
|
187
|
+
| source-build contract | `Selector.source-build.test.mjs` | package source compiled by consumer Babel | Moving evaluated StyleX values outside the supported source form fails compilation | `audit:Selector/code-health` |
|
|
188
|
+
|
|
189
|
+
## Decision log
|
|
190
|
+
|
|
191
|
+
No component-local future decision is recorded here. Accepted unimplemented work
|
|
192
|
+
is owned by `spec:AST-004`.
|
|
193
|
+
|
|
194
|
+
## Open questions
|
|
195
|
+
|
|
196
|
+
None.
|
|
197
|
+
|
|
198
|
+
## Content boundary
|
|
199
|
+
|
|
200
|
+
This file does not copy the full prop reference, examples, current audit score,
|
|
201
|
+
family proposals, or future implementation steps. Those remain with their
|
|
202
|
+
existing owners.
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
13
|
+
import {readFileSync} from 'node:fs';
|
|
13
14
|
import {
|
|
14
15
|
act,
|
|
15
16
|
render,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
within,
|
|
20
21
|
} from '@testing-library/react';
|
|
21
22
|
import userEvent from '@testing-library/user-event';
|
|
23
|
+
import * as stylex from '@stylexjs/stylex';
|
|
22
24
|
import {useState} from 'react';
|
|
23
25
|
import type {ReactNode} from 'react';
|
|
24
26
|
import {Selector} from './Selector';
|
|
@@ -35,6 +37,7 @@ import {defineTheme} from '../theme/defineTheme';
|
|
|
35
37
|
import {Theme} from '../theme/Theme';
|
|
36
38
|
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
37
39
|
import {spacingVars} from '../theme/tokens.stylex';
|
|
40
|
+
import {selectorPresentationStyles} from './selectorPresentation.stylex';
|
|
38
41
|
|
|
39
42
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
40
43
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -45,6 +48,28 @@ function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
|
45
48
|
beforeEach(() => {
|
|
46
49
|
// The live regions are a document-level singleton; start each test clean.
|
|
47
50
|
__resetLiveRegionsForTest();
|
|
51
|
+
__resetInteractionModalityForTest();
|
|
52
|
+
HTMLDialogElement.prototype.showModal = vi.fn(function (
|
|
53
|
+
this: HTMLDialogElement,
|
|
54
|
+
) {
|
|
55
|
+
this.setAttribute('open', '');
|
|
56
|
+
});
|
|
57
|
+
HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
|
|
58
|
+
this.removeAttribute('open');
|
|
59
|
+
});
|
|
60
|
+
vi.stubGlobal(
|
|
61
|
+
'matchMedia',
|
|
62
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
63
|
+
matches: false,
|
|
64
|
+
media: query,
|
|
65
|
+
onchange: null,
|
|
66
|
+
addEventListener: vi.fn(),
|
|
67
|
+
removeEventListener: vi.fn(),
|
|
68
|
+
addListener: vi.fn(),
|
|
69
|
+
removeListener: vi.fn(),
|
|
70
|
+
dispatchEvent: vi.fn(),
|
|
71
|
+
})),
|
|
72
|
+
);
|
|
48
73
|
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
49
74
|
this.setAttribute('popover-open', '');
|
|
50
75
|
const event = new Event('toggle', {bubbles: false});
|
|
@@ -228,6 +253,116 @@ function mockSelectorRects({
|
|
|
228
253
|
}
|
|
229
254
|
|
|
230
255
|
describe('Selector', () => {
|
|
256
|
+
it('uses a bottom sheet and closes after a selection when requested', async () => {
|
|
257
|
+
const user = userEvent.setup();
|
|
258
|
+
const onChange = vi.fn();
|
|
259
|
+
render(
|
|
260
|
+
<Selector
|
|
261
|
+
label="Fruit"
|
|
262
|
+
options={OPTIONS}
|
|
263
|
+
onChange={onChange}
|
|
264
|
+
presentation="bottom-sheet"
|
|
265
|
+
/>,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const trigger = screen.getByRole('combobox');
|
|
269
|
+
await user.click(trigger);
|
|
270
|
+
|
|
271
|
+
expect(
|
|
272
|
+
await screen.findByRole('dialog', {name: 'Fruit'}),
|
|
273
|
+
).toBeInTheDocument();
|
|
274
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
275
|
+
|
|
276
|
+
await user.click(screen.getByRole('option', {name: /Banana/}));
|
|
277
|
+
expect(onChange).toHaveBeenCalledWith('Banana');
|
|
278
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('restores touch focus without painting a trigger focus ring', async () => {
|
|
282
|
+
render(
|
|
283
|
+
<Selector
|
|
284
|
+
label="Fruit"
|
|
285
|
+
options={OPTIONS}
|
|
286
|
+
onChange={() => {}}
|
|
287
|
+
presentation="bottom-sheet"
|
|
288
|
+
/>,
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const trigger = screen.getByRole('combobox');
|
|
292
|
+
fireEvent.pointerDown(trigger, {pointerType: 'touch'});
|
|
293
|
+
fireEvent.click(trigger, {detail: 1});
|
|
294
|
+
const option = await screen.findByRole('option', {name: /Banana/});
|
|
295
|
+
fireEvent.pointerDown(option, {pointerType: 'touch'});
|
|
296
|
+
fireEvent.click(option, {detail: 1});
|
|
297
|
+
|
|
298
|
+
trigger.focus();
|
|
299
|
+
expect(trigger).toHaveFocus();
|
|
300
|
+
expect(trigger.parentElement).toHaveClass(
|
|
301
|
+
stylex.props(selectorPresentationStyles.pointerRestoredFocus).className!,
|
|
302
|
+
);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('uses content-hugging height without extra bottom content padding', () => {
|
|
306
|
+
const source = readFileSync(
|
|
307
|
+
'packages/core/src/Selector/SelectorBottomSheet.tsx',
|
|
308
|
+
'utf8',
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(source).toContain('height="hug"');
|
|
312
|
+
expect(source).toContain('paddingBlockStart={4}');
|
|
313
|
+
expect(source).toContain('paddingBlockEnd={0}');
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('moves keyboard focus into a bottom-sheet listbox', async () => {
|
|
317
|
+
const user = userEvent.setup();
|
|
318
|
+
render(
|
|
319
|
+
<Selector label="Fruit" options={OPTIONS} presentation="bottom-sheet" />,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const trigger = screen.getByRole('combobox');
|
|
323
|
+
trigger.focus();
|
|
324
|
+
await user.keyboard('{Enter}');
|
|
325
|
+
|
|
326
|
+
await waitFor(() => expect(screen.getByRole('listbox')).toHaveFocus());
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('uses a bottom sheet for adaptive presentation on compact touch', async () => {
|
|
330
|
+
vi.stubGlobal(
|
|
331
|
+
'matchMedia',
|
|
332
|
+
vi.fn().mockImplementation((query: string) => ({
|
|
333
|
+
matches: query === '(max-width: 768px) and (pointer: coarse)',
|
|
334
|
+
media: query,
|
|
335
|
+
onchange: null,
|
|
336
|
+
addEventListener: vi.fn(),
|
|
337
|
+
removeEventListener: vi.fn(),
|
|
338
|
+
addListener: vi.fn(),
|
|
339
|
+
removeListener: vi.fn(),
|
|
340
|
+
dispatchEvent: vi.fn(),
|
|
341
|
+
})),
|
|
342
|
+
);
|
|
343
|
+
const user = userEvent.setup();
|
|
344
|
+
render(
|
|
345
|
+
<Selector label="Fruit" options={OPTIONS} presentation="adaptive" />,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
await user.click(screen.getByRole('combobox'));
|
|
349
|
+
expect(
|
|
350
|
+
await screen.findByRole('dialog', {name: 'Fruit'}),
|
|
351
|
+
).toBeInTheDocument();
|
|
352
|
+
expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
it('keeps adaptive presentation anchored without compact touch', async () => {
|
|
356
|
+
const user = userEvent.setup();
|
|
357
|
+
render(
|
|
358
|
+
<Selector label="Fruit" options={OPTIONS} presentation="adaptive" />,
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
await user.click(screen.getByRole('combobox'));
|
|
362
|
+
expect(HTMLElement.prototype.showPopover).toHaveBeenCalledOnce();
|
|
363
|
+
expect(HTMLDialogElement.prototype.showModal).not.toHaveBeenCalled();
|
|
364
|
+
});
|
|
365
|
+
|
|
231
366
|
it('renders with placeholder when no value', () => {
|
|
232
367
|
render(<Selector label="Fruit" options={OPTIONS} placeholder="Pick one" />);
|
|
233
368
|
expect(screen.getByRole('combobox')).toHaveTextContent('Pick one');
|