@astryxdesign/core 0.6.0 → 0.6.1

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 (203) hide show
  1. package/CHANGELOG.md +41 -3
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +12 -1
  4. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheetSwitcher.js +44 -15
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +3 -2
  7. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  8. package/dist/Breadcrumbs/BreadcrumbItem.js +3 -7
  9. package/dist/Center/Center.d.ts +23 -16
  10. package/dist/Center/Center.d.ts.map +1 -1
  11. package/dist/Center/Center.js +7 -5
  12. package/dist/CodeBlock/CodeBlock.js +2 -2
  13. package/dist/DateInput/DateInput.d.ts.map +1 -1
  14. package/dist/DateInput/DateInput.js +12 -2
  15. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  16. package/dist/DateTimeInput/DateTimeInput.js +12 -2
  17. package/dist/Field/Field.d.ts.map +1 -1
  18. package/dist/Field/Field.js +1 -0
  19. package/dist/Field/InputClearButton.d.ts +2 -2
  20. package/dist/Field/InputClearButton.d.ts.map +1 -1
  21. package/dist/Field/InputClearButton.js +5 -1
  22. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  23. package/dist/Field/PanelSearchInput.js +16 -4
  24. package/dist/FileInput/FileInput.d.ts.map +1 -1
  25. package/dist/FileInput/FileInput.js +12 -1
  26. package/dist/HoverCard/useHoverCard.js +2 -2
  27. package/dist/Indicator/CheckboxIndicator.js +2 -2
  28. package/dist/Indicator/RadioIndicator.js +2 -2
  29. package/dist/Layer/layerStack.d.ts +10 -0
  30. package/dist/Layer/layerStack.d.ts.map +1 -1
  31. package/dist/Layer/layerStack.js +21 -9
  32. package/dist/Layer/useLayerDismissal.d.ts +2 -3
  33. package/dist/Layer/useLayerDismissal.d.ts.map +1 -1
  34. package/dist/Layer/useLayerDismissal.js +2 -3
  35. package/dist/NavIcon/NavIcon.js +2 -2
  36. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  37. package/dist/NumberInput/NumberInput.js +12 -2
  38. package/dist/Popover/usePopover.d.ts +3 -2
  39. package/dist/Popover/usePopover.d.ts.map +1 -1
  40. package/dist/Popover/usePopover.js +4 -2
  41. package/dist/ProgressBar/ProgressBar.js +2 -2
  42. package/dist/ScrollableArea/ScrollableArea.d.ts +79 -0
  43. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -0
  44. package/dist/ScrollableArea/ScrollableArea.js +144 -0
  45. package/dist/ScrollableArea/index.d.ts +11 -0
  46. package/dist/ScrollableArea/index.d.ts.map +1 -0
  47. package/dist/ScrollableArea/index.js +11 -0
  48. package/dist/StatusDot/StatusDot.js +2 -2
  49. package/dist/TextArea/TextArea.js +2 -2
  50. package/dist/TextInput/TextInput.d.ts.map +1 -1
  51. package/dist/TextInput/TextInput.js +19 -4
  52. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  53. package/dist/TimeInput/TimeInput.js +12 -2
  54. package/dist/Typeahead/BaseTypeahead.d.ts +21 -14
  55. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  56. package/dist/Typeahead/BaseTypeahead.js +56 -20
  57. package/dist/astryx.css +15 -0
  58. package/dist/hooks/index.d.ts +2 -0
  59. package/dist/hooks/index.d.ts.map +1 -1
  60. package/dist/hooks/index.js +1 -0
  61. package/dist/hooks/scrollGeometry.d.ts +24 -0
  62. package/dist/hooks/scrollGeometry.d.ts.map +1 -0
  63. package/dist/hooks/scrollGeometry.js +86 -0
  64. package/dist/hooks/scrollOwnerRegistry.d.ts +15 -0
  65. package/dist/hooks/scrollOwnerRegistry.d.ts.map +1 -0
  66. package/dist/hooks/scrollOwnerRegistry.js +24 -0
  67. package/dist/hooks/useFocusTrap.d.ts +8 -0
  68. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  69. package/dist/hooks/useFocusTrap.js +22 -11
  70. package/dist/hooks/useScrollableArea.d.ts +51 -0
  71. package/dist/hooks/useScrollableArea.d.ts.map +1 -0
  72. package/dist/hooks/useScrollableArea.js +287 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1 -0
  76. package/dist/theme/defineTheme.d.ts +2 -6
  77. package/dist/theme/defineTheme.d.ts.map +1 -1
  78. package/dist/theme/defineTheme.js +1 -1
  79. package/dist/theme/derivedVarRegistry.js +1 -1
  80. package/dist/theme/localTokens.d.ts +8 -11
  81. package/dist/theme/localTokens.d.ts.map +1 -1
  82. package/dist/theme/localTokens.js +17 -71
  83. package/dist/theme/themeAdaptations.d.ts.map +1 -1
  84. package/dist/theme/themeAdaptations.js +4 -4
  85. package/dist/utils/themeProps.d.ts +10 -10
  86. package/dist/utils/themeProps.d.ts.map +1 -1
  87. package/dist/utils/themeProps.js +27 -10
  88. package/locales/en.json +16 -0
  89. package/locales/pseudo.json +12 -0
  90. package/package.json +7 -2
  91. package/src/AppShell/AppShell.test.tsx +36 -0
  92. package/src/AppShell/AppShell.tsx +4 -1
  93. package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
  94. package/src/Banner/Banner.test.tsx +3 -1
  95. package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +56 -1
  96. package/src/BottomSheet/BottomSheetSwitcher.spec.md +211 -0
  97. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +134 -2
  98. package/src/BottomSheet/BottomSheetSwitcher.tsx +43 -20
  99. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +10 -5
  100. package/src/Breadcrumbs/BreadcrumbItem.spec.md +225 -0
  101. package/src/Breadcrumbs/BreadcrumbItem.tsx +8 -13
  102. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -2
  103. package/src/Breadcrumbs/Breadcrumbs.test.tsx +49 -2
  104. package/src/Center/Center.doc.mjs +32 -28
  105. package/src/Center/Center.spec.md +225 -0
  106. package/src/Center/Center.test.tsx +42 -4
  107. package/src/Center/Center.tsx +24 -17
  108. package/src/Chat/ChatSystemMessage.test.tsx +2 -9
  109. package/src/CodeBlock/CodeBlock.doc.mjs +2 -2
  110. package/src/CodeBlock/CodeBlock.tsx +2 -2
  111. package/src/DateInput/DateInput.test.tsx +4 -4
  112. package/src/DateInput/DateInput.tsx +15 -4
  113. package/src/DateRangeInput/DateRangeInput.test.tsx +2 -2
  114. package/src/DateTimeInput/DateTimeInput.test.tsx +6 -4
  115. package/src/DateTimeInput/DateTimeInput.tsx +18 -7
  116. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +4 -77
  117. package/src/Field/Field.test.tsx +42 -0
  118. package/src/Field/Field.tsx +6 -0
  119. package/src/Field/InputClearButton.test.tsx +35 -1
  120. package/src/Field/InputClearButton.tsx +7 -3
  121. package/src/Field/PanelSearchInput.tsx +21 -8
  122. package/src/FieldStatus/FieldStatus.spec.md +27 -17
  123. package/src/FieldStatus/FieldStatus.test.tsx +7 -5
  124. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +198 -0
  125. package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +13 -0
  126. package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +305 -0
  127. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +317 -0
  128. package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +155 -0
  129. package/src/FileInput/FileInput.tsx +10 -1
  130. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +3 -3
  131. package/src/HoverCard/HoverCard.doc.mjs +4 -4
  132. package/src/HoverCard/useHoverCard.tsx +2 -2
  133. package/src/Indicator/CheckboxIndicator.tsx +2 -2
  134. package/src/Indicator/Indicator.doc.mjs +2 -2
  135. package/src/Indicator/Indicator.test.tsx +1 -1
  136. package/src/Indicator/RadioIndicator.tsx +2 -2
  137. package/src/Layer/layerStack.ts +20 -9
  138. package/src/Layer/useLayerDismissal.ts +2 -3
  139. package/src/MultiSelector/MultiSelector.test.tsx +4 -4
  140. package/src/NavIcon/NavIcon.doc.mjs +4 -4
  141. package/src/NavIcon/NavIcon.tsx +2 -2
  142. package/src/NumberInput/NumberInput.tsx +18 -7
  143. package/src/Popover/Popover.doc.mjs +10 -10
  144. package/src/Popover/Popover.spec.md +55 -65
  145. package/src/Popover/Popover.test.tsx +29 -0
  146. package/src/Popover/usePopover.doc.mjs +4 -4
  147. package/src/Popover/usePopover.tsx +7 -4
  148. package/src/ProgressBar/ProgressBar.doc.mjs +4 -4
  149. package/src/ProgressBar/ProgressBar.test.tsx +1 -31
  150. package/src/ProgressBar/ProgressBar.tsx +2 -2
  151. package/src/RadioList/RadioList.test.tsx +5 -144
  152. package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +255 -0
  153. package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +12 -0
  154. package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +232 -0
  155. package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +503 -0
  156. package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +217 -0
  157. package/src/ScrollableArea/ScrollableArea.doc.mjs +100 -0
  158. package/src/ScrollableArea/ScrollableArea.spec.md +189 -0
  159. package/src/ScrollableArea/ScrollableArea.test.tsx +299 -0
  160. package/src/ScrollableArea/ScrollableArea.tsx +259 -0
  161. package/src/ScrollableArea/index.ts +26 -0
  162. package/src/ScrollableArea/modules/useScrollableArea.spec.md +121 -0
  163. package/src/SegmentedControl/SegmentedControl.test.tsx +5 -172
  164. package/src/Selector/Selector.test.tsx +4 -4
  165. package/src/Spinner/Spinner.test.tsx +0 -18
  166. package/src/StatusDot/StatusDot.doc.mjs +4 -4
  167. package/src/StatusDot/StatusDot.tsx +2 -2
  168. package/src/TabList/TabList.test.tsx +5 -9
  169. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +191 -0
  170. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +45 -0
  171. package/src/TabList/__tests__/Tabs.a11y.renders.tsx +92 -0
  172. package/src/TabList/__tests__/Tabs.a11y.states.ts +247 -0
  173. package/src/TabList/__tests__/Tabs.a11y.test.tsx +153 -0
  174. package/src/Table/Table.doc.mjs +2 -2
  175. package/src/TextArea/TextArea.doc.mjs +4 -4
  176. package/src/TextArea/TextArea.tsx +2 -2
  177. package/src/TextInput/TextInput.doc.mjs +2 -1
  178. package/src/TextInput/TextInput.test.tsx +94 -0
  179. package/src/TextInput/TextInput.tsx +22 -6
  180. package/src/TimeInput/TimeInput.tsx +18 -7
  181. package/src/Toast/ToastViewport.test.tsx +1 -39
  182. package/src/Typeahead/BaseTypeahead.doc.mjs +229 -33
  183. package/src/Typeahead/BaseTypeahead.spec.md +269 -0
  184. package/src/Typeahead/BaseTypeahead.test.tsx +200 -0
  185. package/src/Typeahead/BaseTypeahead.tsx +99 -30
  186. package/src/hooks/index.ts +13 -0
  187. package/src/hooks/scrollGeometry.ts +155 -0
  188. package/src/hooks/scrollOwnerRegistry.ts +47 -0
  189. package/src/hooks/useFocusTrap.ts +22 -11
  190. package/src/hooks/useFocusTrapEscapeShim.test.tsx +4 -3
  191. package/src/hooks/useScrollableArea.doc.mjs +108 -0
  192. package/src/hooks/useScrollableArea.test.tsx +437 -0
  193. package/src/hooks/useScrollableArea.ts +469 -0
  194. package/src/index.ts +1 -0
  195. package/src/theme/defineTheme.test.ts +65 -105
  196. package/src/theme/defineTheme.ts +3 -9
  197. package/src/theme/derivedVarRegistry.ts +1 -1
  198. package/src/theme/localTokens.ts +25 -96
  199. package/src/theme/publicThemeHelperContract.test.ts +2 -2
  200. package/src/theme/themeAdaptations.test.ts +16 -42
  201. package/src/theme/themeAdaptations.ts +6 -9
  202. package/src/utils/themeProps.test.ts +29 -10
  203. package/src/utils/themeProps.ts +36 -17
@@ -0,0 +1,255 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file RadioGroup.a11y.chromium.spec.ts
5
+ * @input Uses the shared radio-group contract, Chromium harness, checked-in stories, inventory, and exact known failures
6
+ * @output Real-browser and accessibility-tree evidence for every current bound radio-group part
7
+ * @position Browser lane required by AST-013; it makes no real-AT claim.
8
+ */
9
+
10
+ import {
11
+ expect,
12
+ test,
13
+ type CDPSession,
14
+ type Locator,
15
+ type Page,
16
+ } from '@playwright/test';
17
+ import {
18
+ RADIO_GROUP_PATTERN,
19
+ blockingResults,
20
+ checkAccessibilitySpec,
21
+ formatFailures,
22
+ formatReport,
23
+ neverExercised,
24
+ summarize,
25
+ unmatchedKnownFailures,
26
+ type BindingResult,
27
+ } from '@astryxdesign/a11y-spec';
28
+ import {
29
+ createChromiumHarness,
30
+ holdMotionStill,
31
+ } from '@astryxdesign/a11y-spec/chromium';
32
+ import {
33
+ DEFAULT_STORYBOOK_DIR,
34
+ serveStorybook,
35
+ type StaticServer,
36
+ } from '@astryxdesign/a11y-spec/storybook';
37
+ import {RADIO_GROUP_KNOWN_FAILURES} from './RadioGroup.a11y.known-failures';
38
+ import {
39
+ RADIO_GROUP_BINDING_STATES,
40
+ type RadioGroupBindingRow,
41
+ type RadioGroupBindingState,
42
+ } from './RadioGroup.a11y.states';
43
+
44
+ let storybook: StaticServer;
45
+
46
+ test.beforeAll(async () => {
47
+ storybook = await serveStorybook(
48
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
49
+ );
50
+ });
51
+
52
+ test.afterAll(async () => {
53
+ await storybook?.close();
54
+ });
55
+
56
+ function storyUrl(storyId: string): string {
57
+ return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
58
+ }
59
+
60
+ function subjectFor(page: Page, state: RadioGroupBindingRow): Locator {
61
+ return page
62
+ .locator('#storybook-root')
63
+ .getByRole(state.facts.role, {
64
+ name: state.subjectName,
65
+ includeHidden: true,
66
+ })
67
+ .first();
68
+ }
69
+
70
+ function relatedFor(
71
+ page: Page,
72
+ state: RadioGroupBindingRow,
73
+ ): Readonly<Record<string, Locator>> {
74
+ const metadata = state as RadioGroupBindingState;
75
+ return Object.fromEntries(
76
+ Object.entries(metadata.relations ?? {}).map(([relation, option]) => [
77
+ relation,
78
+ page
79
+ .locator('#storybook-root')
80
+ .getByRole(option.role, {name: option.name, includeHidden: true})
81
+ .first(),
82
+ ]),
83
+ );
84
+ }
85
+
86
+ async function mountState(
87
+ page: Page,
88
+ state: RadioGroupBindingRow,
89
+ ): Promise<void> {
90
+ await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
91
+ await holdMotionStill(page);
92
+ const root = page.locator('#storybook-root');
93
+ if ((state as RadioGroupBindingState).opensMenu === true) {
94
+ await root.getByRole('button', {name: 'Sort options'}).click();
95
+ }
96
+ await subjectFor(page, state).waitFor({state: 'attached'});
97
+ }
98
+
99
+ async function runState(
100
+ page: Page,
101
+ cdp: CDPSession,
102
+ state: RadioGroupBindingRow,
103
+ ): Promise<BindingResult> {
104
+ let mounted = false;
105
+ return checkAccessibilitySpec({
106
+ spec: RADIO_GROUP_PATTERN,
107
+ binding: state.binding,
108
+ state: state.id,
109
+ facts: state.facts,
110
+ knownFailures: RADIO_GROUP_KNOWN_FAILURES,
111
+ mount: async () => {
112
+ if (!mounted || state.facts.selection === 'none') {
113
+ await mountState(page, state);
114
+ mounted = true;
115
+ }
116
+ return createChromiumHarness({
117
+ page,
118
+ subject: subjectFor(page, state),
119
+ cdp,
120
+ visibleLabel: state.facts.visibleLabel ? undefined : null,
121
+ related: relatedFor(page, state),
122
+ });
123
+ },
124
+ });
125
+ }
126
+
127
+ test('every binding fact matches the rendered semantic state', async ({
128
+ page,
129
+ }) => {
130
+ test.setTimeout(4 * 60 * 1000);
131
+ const cdp = await page.context().newCDPSession(page);
132
+ const wrong: string[] = [];
133
+
134
+ for (const state of RADIO_GROUP_BINDING_STATES) {
135
+ await mountState(page, state);
136
+ const harness = createChromiumHarness({
137
+ page,
138
+ subject: subjectFor(page, state),
139
+ cdp,
140
+ related: relatedFor(page, state),
141
+ });
142
+ const subject = await harness.subject();
143
+ const computed = await subject.computed();
144
+
145
+ if (computed.role !== state.facts.role) {
146
+ wrong.push(
147
+ `${state.id}: declares role=${state.facts.role}, browser exposes ${computed.role}`,
148
+ );
149
+ }
150
+ if (
151
+ state.facts.checked != null &&
152
+ computed.checked !== (state.facts.checked ? 'true' : 'false')
153
+ ) {
154
+ wrong.push(
155
+ `${state.id}: declares checked=${state.facts.checked}, browser exposes ${computed.checked}`,
156
+ );
157
+ }
158
+ if (
159
+ state.facts.disabled != null &&
160
+ computed.disabled !== state.facts.disabled
161
+ ) {
162
+ wrong.push(
163
+ `${state.id}: declares disabled=${state.facts.disabled}, browser exposes ${computed.disabled}`,
164
+ );
165
+ }
166
+ const description =
167
+ computed.description.trim() === '' ? null : computed.description;
168
+ if (description !== state.facts.description) {
169
+ wrong.push(
170
+ `${state.id}: declares description=${JSON.stringify(state.facts.description)}, browser exposes ${JSON.stringify(description)}`,
171
+ );
172
+ }
173
+ const required =
174
+ (await subject.attribute('required')) != null ||
175
+ (await subject.attribute('aria-required')) === 'true';
176
+ if (required !== state.facts.required) {
177
+ wrong.push(
178
+ `${state.id}: declares required=${state.facts.required}, page exposes ${required}`,
179
+ );
180
+ }
181
+ if (computed.invalid !== state.facts.invalid) {
182
+ wrong.push(
183
+ `${state.id}: declares invalid=${state.facts.invalid}, browser exposes ${computed.invalid}`,
184
+ );
185
+ }
186
+
187
+ await harness.resetFocus();
188
+ let reachedByTab = false;
189
+ for (let step = 0; step < 10 && !reachedByTab; step += 1) {
190
+ await harness.press('Tab');
191
+ reachedByTab =
192
+ state.facts.part === 'group'
193
+ ? await subject.containsFocus()
194
+ : await subject.isFocused();
195
+ }
196
+ if (reachedByTab !== state.facts.tabReachable) {
197
+ wrong.push(
198
+ `${state.id}: declares tabReachable=${state.facts.tabReachable}, page exposes ${reachedByTab}`,
199
+ );
200
+ }
201
+ }
202
+
203
+ expect(wrong).toEqual([]);
204
+ });
205
+
206
+ test('every expectation is exercised by at least one bound state', async ({
207
+ page,
208
+ }) => {
209
+ test.setTimeout(6 * 60 * 1000);
210
+ const cdp = await page.context().newCDPSession(page);
211
+ const results: BindingResult[] = [];
212
+ for (const state of RADIO_GROUP_BINDING_STATES) {
213
+ let mounted = false;
214
+ results.push(
215
+ await checkAccessibilitySpec({
216
+ spec: RADIO_GROUP_PATTERN,
217
+ binding: state.binding,
218
+ state: state.id,
219
+ facts: state.facts,
220
+ knownFailures: RADIO_GROUP_KNOWN_FAILURES,
221
+ mount: async () => {
222
+ if (!mounted || state.facts.selection === 'none') {
223
+ await mountState(page, state);
224
+ mounted = true;
225
+ }
226
+ return createChromiumHarness({
227
+ page,
228
+ subject: subjectFor(page, state),
229
+ cdp,
230
+ visibleLabel: state.facts.visibleLabel ? undefined : null,
231
+ related: relatedFor(page, state),
232
+ });
233
+ },
234
+ }),
235
+ );
236
+ }
237
+ expect(neverExercised(results)).toEqual([]);
238
+ expect(unmatchedKnownFailures(RADIO_GROUP_KNOWN_FAILURES, results)).toEqual(
239
+ [],
240
+ );
241
+ });
242
+
243
+ for (const state of RADIO_GROUP_BINDING_STATES) {
244
+ test(`${state.binding} [${state.id}] — ${state.summary}`, async ({page}) => {
245
+ test.setTimeout(3 * 60 * 1000);
246
+ const cdp = await page.context().newCDPSession(page);
247
+ const result = await runState(page, cdp, state);
248
+ const report = summarize(RADIO_GROUP_PATTERN, [result]);
249
+ // eslint-disable-next-line no-console -- the report is this run's artifact
250
+ console.log(formatReport(report));
251
+ expect(formatFailures(blockingResults([result]))).toBe('');
252
+ expect(report.unrunLayers).toEqual([]);
253
+ expect(report.counts.unexpectedPass).toBe(0);
254
+ });
255
+ }
@@ -0,0 +1,12 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file RadioGroup.a11y.known-failures.ts
5
+ * @input Uses KnownFailure from @astryxdesign/a11y-spec
6
+ * @output Exact public-safe radio-group migration failures
7
+ * @position AST-021 debt records; operational ownership remains outside public source.
8
+ */
9
+
10
+ import type {KnownFailure} from '@astryxdesign/a11y-spec';
11
+
12
+ export const RADIO_GROUP_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [];
@@ -0,0 +1,232 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file RadioGroup.a11y.renders.tsx
7
+ * @input Uses every current Astryx radio-group adopter and the checked state inventory
8
+ * @output Consumer-realistic render functions shared by jsdom and Storybook
9
+ * @position Binding fixture layer; callbacks, form behavior, composition, and styling stay local.
10
+ */
11
+
12
+ import {useState, type ReactElement} from 'react';
13
+ import {
14
+ DropdownMenu,
15
+ DropdownMenuRadioGroup,
16
+ DropdownMenuRadioItem,
17
+ } from '../../DropdownMenu';
18
+ import {SegmentedControl, SegmentedControlItem} from '../../SegmentedControl';
19
+ import {RadioList, RadioListItem} from '../index';
20
+ import type {RadioGroupStateId} from './RadioGroup.a11y.states';
21
+
22
+ function RadioListFixture({
23
+ initial = 'standard',
24
+ orientation = 'horizontal',
25
+ direction = 'ltr',
26
+ description,
27
+ isRequired = false,
28
+ isInvalid = false,
29
+ isDisabled = false,
30
+ disabledMessage,
31
+ disabledOption,
32
+ }: {
33
+ initial?: string;
34
+ orientation?: 'horizontal' | 'vertical';
35
+ direction?: 'ltr' | 'rtl';
36
+ description?: string;
37
+ isRequired?: boolean;
38
+ isInvalid?: boolean;
39
+ isDisabled?: boolean;
40
+ disabledMessage?: string;
41
+ disabledOption?: 'express';
42
+ }) {
43
+ const [value, setValue] = useState(initial);
44
+ return (
45
+ <div dir={direction}>
46
+ <RadioList
47
+ label="Delivery speed"
48
+ description={description}
49
+ value={value}
50
+ onChange={setValue}
51
+ orientation={orientation}
52
+ isRequired={isRequired}
53
+ isDisabled={isDisabled}
54
+ disabledMessage={disabledMessage}
55
+ status={
56
+ isInvalid
57
+ ? {type: 'error', message: 'Please select an option.'}
58
+ : undefined
59
+ }>
60
+ <RadioListItem
61
+ label="Standard"
62
+ value="standard"
63
+ description={
64
+ description == null ? undefined : 'Arrives in three to five days'
65
+ }
66
+ />
67
+ <RadioListItem
68
+ label="Express"
69
+ value="express"
70
+ isDisabled={disabledOption === 'express'}
71
+ />
72
+ <RadioListItem label="Overnight" value="overnight" />
73
+ </RadioList>
74
+ </div>
75
+ );
76
+ }
77
+
78
+ function SegmentedFixture({
79
+ initial = 'list',
80
+ direction = 'ltr',
81
+ isDisabled = false,
82
+ disabledMessage,
83
+ disabledOption,
84
+ hideSelectedLabel = false,
85
+ }: {
86
+ initial?: string;
87
+ direction?: 'ltr' | 'rtl';
88
+ isDisabled?: boolean;
89
+ disabledMessage?: string;
90
+ disabledOption?: 'grid';
91
+ hideSelectedLabel?: boolean;
92
+ }) {
93
+ const [value, setValue] = useState(initial);
94
+ return (
95
+ <div dir={direction}>
96
+ <SegmentedControl
97
+ value={value}
98
+ onChange={setValue}
99
+ label="View mode"
100
+ isDisabled={isDisabled}
101
+ disabledMessage={disabledMessage}>
102
+ <SegmentedControlItem
103
+ value="list"
104
+ label="List"
105
+ isLabelHidden={hideSelectedLabel}
106
+ icon={
107
+ hideSelectedLabel ? <span aria-hidden="true">≡</span> : undefined
108
+ }
109
+ />
110
+ <SegmentedControlItem
111
+ value="grid"
112
+ label="Grid"
113
+ isDisabled={disabledOption === 'grid'}
114
+ />
115
+ <SegmentedControlItem value="table" label="Table" />
116
+ </SegmentedControl>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ function MenuRadioFixture({
122
+ initial = 'newest',
123
+ described = false,
124
+ disabledOption,
125
+ }: {
126
+ initial?: string;
127
+ described?: boolean;
128
+ disabledOption?: 'oldest';
129
+ }) {
130
+ const [value, setValue] = useState<string | undefined>(initial || undefined);
131
+ return (
132
+ <DropdownMenu button={{label: 'Sort options'}}>
133
+ <DropdownMenuRadioGroup
134
+ value={value}
135
+ onChange={setValue}
136
+ label="Sort by"
137
+ hasCloseOnSelect={false}>
138
+ <DropdownMenuRadioItem value="newest" label="Newest" />
139
+ <DropdownMenuRadioItem
140
+ value="oldest"
141
+ label="Oldest"
142
+ description={described ? 'Oldest items first' : undefined}
143
+ isDisabled={disabledOption === 'oldest'}
144
+ />
145
+ <DropdownMenuRadioItem value="popular" label="Most viewed" />
146
+ </DropdownMenuRadioGroup>
147
+ </DropdownMenu>
148
+ );
149
+ }
150
+
151
+ export type RadioGroupStateRender = () => ReactElement;
152
+
153
+ export const RADIO_GROUP_STATE_RENDERS: Record<
154
+ RadioGroupStateId,
155
+ RadioGroupStateRender
156
+ > = {
157
+ 'radio-list-group-selected-ltr': () => <RadioListFixture />,
158
+ 'radio-list-group-selected-rtl': () => <RadioListFixture direction="rtl" />,
159
+ 'radio-list-group-selected-vertical': () => (
160
+ <RadioListFixture orientation="vertical" />
161
+ ),
162
+ 'radio-list-group-none-selected': () => (
163
+ <RadioListFixture initial="" orientation="vertical" />
164
+ ),
165
+ 'radio-list-group-required-invalid-described': () => (
166
+ <RadioListFixture
167
+ initial=""
168
+ orientation="vertical"
169
+ description="Choose a delivery speed."
170
+ isRequired
171
+ isInvalid
172
+ />
173
+ ),
174
+ 'radio-list-group-disabled-with-message': () => (
175
+ <RadioListFixture
176
+ isDisabled
177
+ disabledMessage="Managed by your administrator"
178
+ orientation="vertical"
179
+ />
180
+ ),
181
+ 'radio-list-option-unselected-described': () => (
182
+ <RadioListFixture
183
+ initial="express"
184
+ description="Choose a delivery speed."
185
+ />
186
+ ),
187
+ 'radio-list-option-selected': () => <RadioListFixture />,
188
+ 'radio-list-option-disabled': () => (
189
+ <RadioListFixture disabledOption="express" />
190
+ ),
191
+ 'radio-list-option-group-disabled': () => (
192
+ <RadioListFixture isDisabled orientation="vertical" />
193
+ ),
194
+ 'radio-list-option-disabled-with-message': () => (
195
+ <RadioListFixture
196
+ isDisabled
197
+ disabledMessage="Managed by your administrator"
198
+ orientation="vertical"
199
+ />
200
+ ),
201
+
202
+ 'segmented-group-selected-ltr': () => <SegmentedFixture />,
203
+ 'segmented-group-selected-rtl': () => <SegmentedFixture direction="rtl" />,
204
+ 'segmented-group-none-selected': () => (
205
+ <SegmentedFixture initial="not-an-option" />
206
+ ),
207
+ 'segmented-group-disabled': () => <SegmentedFixture isDisabled />,
208
+ 'segmented-group-disabled-with-message': () => (
209
+ <SegmentedFixture
210
+ isDisabled
211
+ disabledMessage="Choose a project to switch views"
212
+ />
213
+ ),
214
+ 'segmented-option-unselected': () => <SegmentedFixture />,
215
+ 'segmented-option-selected-hidden-label': () => (
216
+ <SegmentedFixture hideSelectedLabel />
217
+ ),
218
+ 'segmented-option-disabled': () => <SegmentedFixture disabledOption="grid" />,
219
+ 'segmented-option-group-disabled': () => <SegmentedFixture isDisabled />,
220
+ 'segmented-option-group-disabled-with-message': () => (
221
+ <SegmentedFixture
222
+ isDisabled
223
+ disabledMessage="Choose a project to switch views"
224
+ />
225
+ ),
226
+
227
+ 'menu-group-selected': () => <MenuRadioFixture />,
228
+ 'menu-group-none-selected': () => <MenuRadioFixture initial="" />,
229
+ 'menu-option-unselected-described': () => <MenuRadioFixture described />,
230
+ 'menu-option-selected': () => <MenuRadioFixture />,
231
+ 'menu-option-disabled': () => <MenuRadioFixture disabledOption="oldest" />,
232
+ };