@astryxdesign/core 0.2.0-canary.ea693ac → 0.2.0

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 (121) hide show
  1. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  2. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  3. package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -6
  4. package/dist/Button/Button.d.ts +1 -1
  5. package/dist/Button/Button.d.ts.map +1 -1
  6. package/dist/Button/Button.js +2 -4
  7. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  8. package/dist/CheckboxInput/CheckboxInput.js +2 -2
  9. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  10. package/dist/CheckboxList/CheckboxListItem.js +4 -19
  11. package/dist/CodeBlock/CodeBlock.js +2 -2
  12. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  13. package/dist/ContextMenu/ContextMenu.js +1 -10
  14. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +11 -8
  15. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  16. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -3
  17. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  18. package/dist/DropdownMenu/DropdownMenuRadioItem.js +1 -1
  19. package/dist/Field/FieldLabel.d.ts +1 -2
  20. package/dist/Field/FieldLabel.d.ts.map +1 -1
  21. package/dist/Field/FieldLabel.js +3 -7
  22. package/dist/Item/Item.d.ts +3 -16
  23. package/dist/Item/Item.d.ts.map +1 -1
  24. package/dist/Item/Item.js +6 -31
  25. package/dist/List/ListItem.d.ts +1 -11
  26. package/dist/List/ListItem.d.ts.map +1 -1
  27. package/dist/List/ListItem.js +0 -2
  28. package/dist/MetadataList/MetadataList.d.ts +1 -1
  29. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  30. package/dist/MetadataList/MetadataList.js +2 -4
  31. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  32. package/dist/MultiSelector/MultiSelector.js +2 -13
  33. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  34. package/dist/RadioList/RadioListItem.js +1 -1
  35. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  36. package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
  37. package/dist/Selector/Selector.d.ts.map +1 -1
  38. package/dist/Selector/Selector.js +0 -5
  39. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  40. package/dist/Skeleton/Skeleton.js +2 -2
  41. package/dist/Switch/Switch.d.ts.map +1 -1
  42. package/dist/Switch/Switch.js +4 -15
  43. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  44. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +1 -1
  45. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  46. package/dist/Toast/ToastViewport.js +9 -61
  47. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  48. package/dist/ToggleButton/ToggleButton.js +1 -3
  49. package/dist/Token/Token.d.ts +1 -4
  50. package/dist/Token/Token.d.ts.map +1 -1
  51. package/dist/Token/Token.js +43 -68
  52. package/dist/Typeahead/BaseTypeahead.d.ts +0 -9
  53. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  54. package/dist/Typeahead/BaseTypeahead.js +0 -2
  55. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  56. package/dist/Typeahead/Typeahead.js +1 -8
  57. package/dist/astryx.css +3 -16
  58. package/dist/astryx.umd.js +48 -48
  59. package/dist/astryx.umd.js.map +4 -4
  60. package/dist/hooks/useKeyboardHint.d.ts +1 -1
  61. package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
  62. package/dist/hooks/useKeyboardHint.js +2 -4
  63. package/locales/en.json +0 -32
  64. package/locales/pseudo.json +0 -24
  65. package/package.json +1 -1
  66. package/src/AvatarGroup/AvatarGroup.test.tsx +1 -21
  67. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -4
  68. package/src/Breadcrumbs/Breadcrumbs.test.tsx +1 -1
  69. package/src/Button/Button.test.tsx +0 -16
  70. package/src/Button/Button.tsx +2 -4
  71. package/src/CheckboxInput/CheckboxInput.test.tsx +0 -23
  72. package/src/CheckboxInput/CheckboxInput.tsx +2 -17
  73. package/src/CheckboxList/CheckboxList.test.tsx +3 -94
  74. package/src/CheckboxList/CheckboxListItem.tsx +10 -17
  75. package/src/CodeBlock/CodeBlock.test.tsx +0 -17
  76. package/src/CodeBlock/CodeBlock.tsx +2 -2
  77. package/src/ContextMenu/ContextMenu.test.tsx +2 -17
  78. package/src/ContextMenu/ContextMenu.tsx +1 -10
  79. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +12 -4
  80. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +10 -9
  81. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
  82. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +2 -20
  83. package/src/Field/FieldLabel.test.tsx +0 -25
  84. package/src/Field/FieldLabel.tsx +2 -11
  85. package/src/Item/Item.doc.mjs +0 -5
  86. package/src/Item/Item.test.tsx +0 -88
  87. package/src/Item/Item.tsx +11 -57
  88. package/src/List/ListItem.doc.mjs +0 -12
  89. package/src/List/ListItem.tsx +0 -13
  90. package/src/MetadataList/MetadataList.test.tsx +0 -24
  91. package/src/MetadataList/MetadataList.tsx +2 -6
  92. package/src/MultiSelector/MultiSelector.test.tsx +0 -76
  93. package/src/MultiSelector/MultiSelector.tsx +2 -22
  94. package/src/RadioList/RadioList.test.tsx +4 -26
  95. package/src/RadioList/RadioListItem.tsx +1 -8
  96. package/src/SegmentedControl/SegmentedControl.test.tsx +0 -28
  97. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -20
  98. package/src/Selector/Selector.test.tsx +0 -22
  99. package/src/Selector/Selector.tsx +1 -8
  100. package/src/Skeleton/Skeleton.test.tsx +0 -16
  101. package/src/Skeleton/Skeleton.tsx +1 -13
  102. package/src/Switch/Switch.test.tsx +0 -37
  103. package/src/Switch/Switch.tsx +3 -52
  104. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +0 -19
  105. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -3
  106. package/src/Toast/ToastViewport.test.tsx +8 -218
  107. package/src/Toast/ToastViewport.tsx +12 -71
  108. package/src/ToggleButton/ToggleButton.test.tsx +0 -24
  109. package/src/ToggleButton/ToggleButton.tsx +1 -19
  110. package/src/Token/Token.test.tsx +0 -145
  111. package/src/Token/Token.tsx +33 -73
  112. package/src/Typeahead/BaseTypeahead.tsx +0 -12
  113. package/src/Typeahead/Typeahead.test.tsx +0 -62
  114. package/src/Typeahead/Typeahead.tsx +0 -6
  115. package/src/hooks/useKeyboardHint.test.tsx +0 -22
  116. package/src/hooks/useKeyboardHint.tsx +2 -6
  117. package/dist/Token/TokenLink.d.ts +0 -62
  118. package/dist/Token/TokenLink.d.ts.map +0 -1
  119. package/dist/Token/TokenLink.js +0 -76
  120. package/src/Token/TokenLink.tsx +0 -107
  121. package/src/__tests__/forcedColors.ts +0 -60
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file MetadataList.tsx
7
- * @input Uses React, ReactNode, StyleXStyles, theme tokens, MetadataListContext, i18n (useTranslator)
7
+ * @input Uses React, ReactNode, StyleXStyles, theme tokens, MetadataListContext
8
8
  * @output Exports MetadataList component, MetadataListProps, MetadataListColumns types
9
9
  * @position Core implementation; consumed by index.ts, tested by MetadataList.test.tsx
10
10
  *
@@ -31,7 +31,6 @@ import {
31
31
  import type {BaseProps} from '../BaseProps';
32
32
  import {mergeProps} from '../utils';
33
33
  import {themeProps} from '../utils/themeProps';
34
- import {useTranslator} from '../i18n';
35
34
 
36
35
  // =============================================================================
37
36
  // Types
@@ -198,7 +197,6 @@ export function MetadataList({
198
197
  const labelConfig = label ?? (isMultiColumn ? LABEL_TOP : LABEL_START);
199
198
  const [isShowAll, setIsShowAll] = useState(false);
200
199
  const contentId = useId();
201
- const t = useTranslator();
202
200
 
203
201
  const contextValue = useMemo(
204
202
  () => ({
@@ -285,9 +283,7 @@ export function MetadataList({
285
283
  aria-expanded={isShowAll}
286
284
  onClick={() => setIsShowAll(prev => !prev)}
287
285
  {...stylex.props(styles.toggleButton)}>
288
- {isShowAll
289
- ? t('@astryx.metadataList.showLess')
290
- : t('@astryx.metadataList.showMore')}
286
+ {isShowAll ? 'Show less' : 'Show more'}
291
287
  </button>
292
288
  )}
293
289
  </div>
@@ -510,59 +510,6 @@ describe('MultiSelector', () => {
510
510
  expect(options[0]).toHaveTextContent('Select all');
511
511
  });
512
512
 
513
- it('select-all accessible name reflects none/partial/all selection', async () => {
514
- const user = userEvent.setup();
515
- const options = [
516
- {value: 'apple', label: 'Apple'},
517
- {value: 'banana', label: 'Banana'},
518
- ];
519
- const {rerender} = render(
520
- <MultiSelector
521
- label="Fruit"
522
- options={options}
523
- value={[]}
524
- onChange={() => {}}
525
- hasSelectAll
526
- />,
527
- );
528
-
529
- await user.click(screen.getByRole('combobox'));
530
-
531
- // None selected: plain name, not selected
532
- let selectAll = screen.getAllByRole('option', h)[0];
533
- expect(selectAll).not.toHaveAccessibleName(/partially selected/);
534
- expect(selectAll).toHaveAttribute('aria-selected', 'false');
535
-
536
- // Partial: aria-selected="mixed" is invalid on role="option", so the
537
- // indeterminate state must be conveyed through the accessible name.
538
- rerender(
539
- <MultiSelector
540
- label="Fruit"
541
- options={options}
542
- value={['apple']}
543
- onChange={() => {}}
544
- hasSelectAll
545
- />,
546
- );
547
- selectAll = screen.getAllByRole('option', h)[0];
548
- expect(selectAll).toHaveAccessibleName('Select all, partially selected');
549
- expect(selectAll).toHaveAttribute('aria-selected', 'false');
550
-
551
- // All selected: plain name again, selected
552
- rerender(
553
- <MultiSelector
554
- label="Fruit"
555
- options={options}
556
- value={['apple', 'banana']}
557
- onChange={() => {}}
558
- hasSelectAll
559
- />,
560
- );
561
- selectAll = screen.getAllByRole('option', h)[0];
562
- expect(selectAll).not.toHaveAccessibleName(/partially selected/);
563
- expect(selectAll).toHaveAttribute('aria-selected', 'true');
564
- });
565
-
566
513
  it('select-all toggles via keyboard Enter', async () => {
567
514
  const user = userEvent.setup();
568
515
  const onChange = vi.fn();
@@ -767,29 +714,6 @@ describe('MultiSelector', () => {
767
714
  ).toBeInTheDocument();
768
715
  });
769
716
 
770
- it('empty-state message is not exposed as a listbox child', async () => {
771
- const user = userEvent.setup();
772
- render(
773
- <MultiSelector
774
- label="Fruit"
775
- options={defaultOptions}
776
- value={[]}
777
- onChange={() => {}}
778
- hasSearch
779
- />,
780
- );
781
-
782
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
783
- await user.type(screen.getByRole('combobox', h), 'xyz');
784
-
785
- // role="listbox" only permits option/group children — the visual
786
- // empty-state message must be presentational (it is announced through
787
- // the result-count live region instead).
788
- const listbox = screen.getByRole('listbox', h);
789
- const empty = within(listbox).getByText('No results found');
790
- expect(empty).toHaveAttribute('role', 'presentation');
791
- });
792
-
793
717
  describe('result announcements', () => {
794
718
  it('announces the match count politely while searching', async () => {
795
719
  const user = userEvent.setup();
@@ -1129,11 +1129,6 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1129
1129
  ? allEnabledSelected
1130
1130
  : optimisticValue.includes(item.value);
1131
1131
  const checkboxValue = isSelectAll ? selectAllState : isSelected;
1132
- // aria-selected="mixed" is invalid on role="option", and the tri-state
1133
- // checkbox is inert/decorative, so the indeterminate state must be
1134
- // conveyed through the option's accessible name (WCAG 4.1.2).
1135
- const isPartiallySelected =
1136
- isSelectAll && selectAllState === 'indeterminate';
1137
1132
 
1138
1133
  return (
1139
1134
  <div
@@ -1141,13 +1136,6 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1141
1136
  id={getItemId(flatIndex)}
1142
1137
  role="option"
1143
1138
  aria-selected={isSelected}
1144
- aria-label={
1145
- isPartiallySelected
1146
- ? t('@astryx.multiSelector.selectAllPartiallySelected', {
1147
- label: selectAllLabel,
1148
- })
1149
- : undefined
1150
- }
1151
1139
  aria-disabled={item.disabled}
1152
1140
  onClick={() => {
1153
1141
  if (!item.disabled) {
@@ -1192,12 +1180,10 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1192
1180
  optimisticValue,
1193
1181
  allEnabledSelected,
1194
1182
  selectAllState,
1195
- selectAllLabel,
1196
1183
  getItemId,
1197
1184
  handleNavigableToggle,
1198
1185
  onItemMouseEnter,
1199
1186
  size,
1200
- t,
1201
1187
  ],
1202
1188
  );
1203
1189
 
@@ -1225,16 +1211,10 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1225
1211
  cursor = 1;
1226
1212
  }
1227
1213
 
1228
- // Empty state — no real items to show. role="presentation" keeps the
1229
- // message out of the listbox's accessibility tree (role="listbox" only
1230
- // permits option/group children); the no-results outcome is announced
1231
- // via the result-count live region instead.
1214
+ // Empty state — no real items to show
1232
1215
  if (realItemCount === 0) {
1233
1216
  elements.push(
1234
- <div
1235
- key="empty"
1236
- role="presentation"
1237
- {...stylex.props(styles.emptyState)}>
1217
+ <div key="empty" {...stylex.props(styles.emptyState)}>
1238
1218
  No results found
1239
1219
  </div>,
1240
1220
  );
@@ -14,7 +14,6 @@ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {RadioList} from './RadioList';
16
16
  import {RadioListItem} from './RadioListItem';
17
- import {getForcedColorsRules} from '../__tests__/forcedColors';
18
17
 
19
18
  // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
20
19
  // reflects its open state via a `popover-open` attribute the tests can assert.
@@ -581,11 +580,7 @@ describe('RadioList', () => {
581
580
  it('submits the selected value under htmlName', () => {
582
581
  const {container} = render(
583
582
  <form>
584
- <RadioList
585
- label="Preference"
586
- htmlName="pref"
587
- value="b"
588
- onChange={() => {}}>
583
+ <RadioList label="Preference" htmlName="pref" value="b" onChange={() => {}}>
589
584
  <RadioListItem label="Option A" value="a" />
590
585
  <RadioListItem label="Option B" value="b" />
591
586
  </RadioList>
@@ -604,14 +599,13 @@ describe('RadioList', () => {
604
599
  value="a"
605
600
  onChange={() => {}}
606
601
  isDisabled
607
- disabledMessage="Locked">
602
+ disabledMessage="Locked"
603
+ >
608
604
  <RadioListItem label="Option A" value="a" />
609
605
  </RadioList>
610
606
  </form>,
611
607
  );
612
- expect([
613
- ...new FormData(container.querySelector('form')!).keys(),
614
- ]).toEqual([]);
608
+ expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
615
609
  });
616
610
 
617
611
  it('keeps working as an isolated group when htmlName is omitted', () => {
@@ -648,19 +642,3 @@ describe('RadioList', () => {
648
642
  });
649
643
  });
650
644
  });
651
-
652
- // jsdom cannot emulate forced-colors rendering, so this asserts that the
653
- // compiled output includes the forced-colors rule; visual behavior needs
654
- // manual verification under Windows High Contrast.
655
- describe('forced colors (WCAG 1.4.11)', () => {
656
- it('compiles a forced-colors fill so the selected dot survives Windows High Contrast', () => {
657
- render(
658
- <RadioList label="Preference" value="a" onChange={() => {}}>
659
- <RadioListItem label="Option A" value="a" />
660
- </RadioList>,
661
- );
662
- // The painted inner dot would be stripped to Canvas (invisible), making
663
- // checked and unchecked radios identical; CanvasText keeps it perceivable.
664
- expect(getForcedColorsRules()).toContain('background-color: canvastext;');
665
- });
666
- });
@@ -119,14 +119,7 @@ const styles = stylex.create({
119
119
  },
120
120
  innerDot: {
121
121
  borderRadius: '50%',
122
- backgroundColor: {
123
- default: colorVars['--color-on-accent'],
124
- // Forced colors (Windows High Contrast) strips painted backgrounds,
125
- // which would make the selected dot invisible — checked and unchecked
126
- // radios would look identical. CanvasText keeps the dot perceivable on
127
- // the Canvas circle fill (WCAG 1.4.11).
128
- '@media (forced-colors: active)': 'CanvasText',
129
- },
122
+ backgroundColor: colorVars['--color-on-accent'],
130
123
  },
131
124
  labelDisabled: {
132
125
  color: colorVars['--color-text-disabled'],
@@ -14,10 +14,6 @@ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {SegmentedControl} from './SegmentedControl';
16
16
  import {SegmentedControlItem} from './SegmentedControlItem';
17
- import {
18
- getAllInjectedCss,
19
- getForcedColorsRules,
20
- } from '../__tests__/forcedColors';
21
17
 
22
18
  // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
23
19
  // reflects its open state via a `popover-open` attribute the tests can assert.
@@ -784,27 +780,3 @@ describe('SegmentedControl container handler forwarding', () => {
784
780
  expect(onChange).not.toHaveBeenCalled();
785
781
  });
786
782
  });
787
-
788
- // jsdom cannot emulate forced-colors rendering, so these assert that the
789
- // compiled output includes the forced-colors rules; visual behavior needs
790
- // manual verification under Windows High Contrast.
791
- describe('forced colors (WCAG 1.4.11)', () => {
792
- it('compiles forced-colors overrides so the selected segment survives Windows High Contrast', () => {
793
- render(
794
- <SegmentedControl value="grid" onChange={() => {}} label="View mode">
795
- <SegmentedControlItem value="grid" label="Grid" />
796
- <SegmentedControlItem value="list" label="List" />
797
- </SegmentedControl>,
798
- );
799
- const css = getForcedColorsRules();
800
- // The painted surface fill and shadow are stripped; Highlight/
801
- // HighlightText marks the selected segment.
802
- expect(css).toContain('background-color: highlight;');
803
- expect(css).toContain('color: highlighttext;');
804
- // The segment is a <button>; without opting out of UA remapping it keeps
805
- // the native ButtonFace surface and ignores the Highlight fill, leaving
806
- // HighlightText text on a white surface. forced-color-adjust: none makes
807
- // both render as authored.
808
- expect(getAllInjectedCss()).toContain('forced-color-adjust: none;');
809
- });
810
- });
@@ -103,27 +103,9 @@ const styles = stylex.create({
103
103
  },
104
104
  },
105
105
  selected: {
106
- // Forced colors (Windows High Contrast) strips the painted surface fill
107
- // and box shadow, which would leave the selected segment with no state
108
- // indication beyond font weight. Highlight/HighlightText is the platform
109
- // convention for a selected item (WCAG 1.4.11).
110
- //
111
- // forced-color-adjust must be `none` here: the segment is a <button>, and
112
- // the UA keeps native form-control colors (ButtonFace surface) for it under
113
- // forced colors, ignoring the authored Highlight fill — the label kept its
114
- // HighlightText color, giving white text on a white surface. Opting the
115
- // selected segment out of UA remapping makes both the Highlight surface and
116
- // the HighlightText label render as authored, restoring figure-ground.
117
- forcedColorAdjust: 'none',
118
- color: {
119
- default: colorVars['--color-text-primary'],
120
- '@media (forced-colors: active)': 'HighlightText',
121
- },
106
+ color: colorVars['--color-text-primary'],
122
107
  fontWeight: fontWeightVars['--font-weight-semibold'],
123
- backgroundColor: {
124
- default: colorVars['--color-background-surface'],
125
- '@media (forced-colors: active)': 'Highlight',
126
- },
108
+ backgroundColor: colorVars['--color-background-surface'],
127
109
  boxShadow: shadowVars['--shadow-low'],
128
110
  },
129
111
  disabled: {
@@ -613,28 +613,6 @@ describe('Selector', () => {
613
613
  expect(within(listbox).getByText('No results found')).toBeInTheDocument();
614
614
  });
615
615
 
616
- it('empty-state message is not exposed as a listbox child', async () => {
617
- const user = userEvent.setup();
618
- render(
619
- <Selector
620
- label="Fruit"
621
- options={OPTIONS}
622
- value="Apple"
623
- onChange={() => {}}
624
- hasSearch
625
- />,
626
- );
627
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
628
- await user.type(screen.getByRole('combobox', h), 'xyz');
629
-
630
- // role="listbox" only permits option/group children — the visual
631
- // empty-state message must be presentational (it is announced through
632
- // the result-count live region instead).
633
- const listbox = screen.getByRole('listbox', h);
634
- const empty = within(listbox).getByText('No results found');
635
- expect(empty).toHaveAttribute('role', 'presentation');
636
- });
637
-
638
616
  it('calls onChange when selecting a filtered option', async () => {
639
617
  const user = userEvent.setup();
640
618
  const onChange = vi.fn();
@@ -963,15 +963,8 @@ export function Selector<T extends SelectorOptionType>(
963
963
 
964
964
  // Nothing matched across every group/option: show the empty state.
965
965
  if (isSearching && filteredItems.length === 0) {
966
- // role="presentation" keeps the message out of the listbox's
967
- // accessibility tree (role="listbox" only permits option/group
968
- // children); the no-results outcome is announced via the
969
- // result-count live region instead.
970
966
  return [
971
- <div
972
- key="empty"
973
- role="presentation"
974
- {...stylex.props(styles.emptyState)}>
967
+ <div key="empty" {...stylex.props(styles.emptyState)}>
975
968
  No results found
976
969
  </div>,
977
970
  ];
@@ -3,7 +3,6 @@
3
3
  import {describe, it, expect} from 'vitest';
4
4
  import {render, screen} from '@testing-library/react';
5
5
  import {Skeleton} from './Skeleton';
6
- import {getForcedColorsRules} from '../__tests__/forcedColors';
7
6
 
8
7
  describe('Skeleton', () => {
9
8
  it('renders a placeholder element', () => {
@@ -24,18 +23,3 @@ describe('Skeleton', () => {
24
23
  expect(screen.getByTestId('sk')).toHaveAttribute('aria-hidden', 'false');
25
24
  });
26
25
  });
27
-
28
- // jsdom cannot emulate forced-colors rendering, so these assert that the
29
- // compiled output includes the forced-colors rules; visual behavior needs
30
- // manual verification under Windows High Contrast.
31
- describe('forced colors (WCAG 1.4.11)', () => {
32
- it('compiles forced-colors overrides so the placeholder stays visible under Windows High Contrast', () => {
33
- render(<Skeleton width={200} height={20} data-testid="sk" />);
34
- const css = getForcedColorsRules();
35
- // The painted fill is stripped to Canvas (invisible); GrayText is a
36
- // system color, so it survives forcing.
37
- expect(css).toContain('background-color: graytext;');
38
- // The resting 0.25 opacity is lifted so the static placeholder reads.
39
- expect(css).toContain('opacity: 1;');
40
- });
41
- });
@@ -55,20 +55,8 @@ const styles = stylex.create({
55
55
  backgroundColor: {
56
56
  default: colorVars['--color-skeleton'],
57
57
  '@media (prefers-contrast: more)': `color-mix(in srgb, ${colorVars['--color-skeleton']}, ${colorVars['--color-text-primary']} 30%)`,
58
- // Forced colors (Windows High Contrast) strips painted backgrounds,
59
- // which would make the placeholder invisible. GrayText is a system
60
- // color, so it survives forcing and keeps the placeholder visible
61
- // (WCAG 1.4.11). Listed after prefers-contrast so it wins when both
62
- // media features are active.
63
- '@media (forced-colors: active)': 'GrayText',
64
- },
65
- opacity: {
66
- default: 0.25,
67
- // The resting 0.25 opacity would render GrayText nearly invisible on
68
- // Canvas; full opacity keeps the static placeholder perceivable (the
69
- // fade animation still pulses when motion is allowed).
70
- '@media (forced-colors: active)': 1,
71
58
  },
59
+ opacity: 0.25,
72
60
  },
73
61
  animate: {
74
62
  animationDirection: 'alternate',
@@ -13,10 +13,6 @@ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
13
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {Switch} from './Switch';
16
- import {
17
- getAllInjectedCss,
18
- getForcedColorsRules,
19
- } from '../__tests__/forcedColors';
20
16
  import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
21
17
 
22
18
  afterEach(() => {
@@ -733,36 +729,3 @@ describe('Switch', () => {
733
729
  });
734
730
  });
735
731
  });
736
-
737
- // jsdom cannot emulate forced-colors rendering, so these assert that the
738
- // compiled output includes the forced-colors rules; visual behavior needs
739
- // manual verification under Windows High Contrast.
740
- describe('forced colors (WCAG 1.4.11)', () => {
741
- it('compiles forced-colors overrides so on/off state survives Windows High Contrast', () => {
742
- render(<Switch label="Notifications" value={true} onChange={() => {}} />);
743
- const css = getForcedColorsRules();
744
- // Track outline (backgrounds are stripped; the border keeps the bounds).
745
- expect(css).toContain('border-color: canvastext;');
746
- // Off track stays empty; on track uses the selection color.
747
- expect(css).toContain('background-color: canvas;');
748
- expect(css).toContain('background-color: highlight;');
749
- // Thumb fill per state.
750
- expect(css).toContain('background-color: canvastext;');
751
- expect(css).toContain('background-color: highlighttext;');
752
- // Disabled affordance (opacity dimming does not survive forcing).
753
- expect(css).toContain('border-color: graytext;');
754
- });
755
-
756
- it('gates the hover tint out of forced colors so the thumb stays visible on hover', () => {
757
- render(<Switch label="Notifications" value={true} onChange={() => {}} />);
758
- // The ancestor-hover tint is a non-system color-mix whose rule outranks the
759
- // plain forced-colors track rule. It is gated behind `forced-colors: none`
760
- // so it cannot reassert on hover and flatten the Highlight track to white
761
- // under the HighlightText thumb (white-on-white).
762
- expect(getAllInjectedCss()).toContain(
763
- '(hover: hover) and (forced-colors: none)',
764
- );
765
- // And the tint never leaks into the forced-colors output.
766
- expect(getForcedColorsRules()).not.toContain('color-mix');
767
- });
768
- });
@@ -173,21 +173,6 @@ const styles = stylex.create({
173
173
  },
174
174
  transitionTimingFunction: easeVars['--ease-standard'],
175
175
  boxSizing: 'border-box',
176
- // Forced colors (Windows High Contrast) strips painted backgrounds, which
177
- // would leave the track invisible. A system-color border keeps the
178
- // control's bounds perceivable (WCAG 1.4.11).
179
- borderWidth: {
180
- default: 0,
181
- '@media (forced-colors: active)': '1px',
182
- },
183
- borderStyle: {
184
- default: 'none',
185
- '@media (forced-colors: active)': 'solid',
186
- },
187
- borderColor: {
188
- default: null,
189
- '@media (forced-colors: active)': 'CanvasText',
190
- },
191
176
  },
192
177
  trackFocus: {
193
178
  outline: {
@@ -204,39 +189,21 @@ const styles = stylex.create({
204
189
  trackOff: {
205
190
  backgroundColor: {
206
191
  default: colorVars['--color-background-gray'],
207
- // Off = empty (Canvas) track; on = Highlight track, so the two states
208
- // stay distinguishable under forced colors.
209
- '@media (forced-colors: active)': 'Canvas',
210
- // The ancestor-hover tint is a non-system color-mix, and its rule
211
- // outranks the plain forced-colors rule above. Left ungated it would
212
- // reassert on hover under forced colors, where the UA flattens the
213
- // color-mix back to Canvas — so the HighlightText thumb would sit on a
214
- // white track (white-on-white). Gating on `forced-colors: none` keeps
215
- // the tint out of forced colors and lets the system-color track stand.
216
192
  [stylex.when.ancestor(':hover', switchScope)]: {
217
- '@media (hover: hover) and (forced-colors: none)': `color-mix(in srgb, ${colorVars['--color-background-gray']}, ${colorVars['--color-tint-hover']} 5%)`,
193
+ '@media (hover: hover)': `color-mix(in srgb, ${colorVars['--color-background-gray']}, ${colorVars['--color-tint-hover']} 5%)`,
218
194
  },
219
195
  },
220
196
  },
221
197
  trackOn: {
222
198
  backgroundColor: {
223
199
  default: colorVars['--color-accent'],
224
- '@media (forced-colors: active)': 'Highlight',
225
- // See trackOff: gate the hover tint out of forced colors so it cannot
226
- // flatten the Highlight track to white under the HighlightText thumb.
227
200
  [stylex.when.ancestor(':hover', switchScope)]: {
228
- '@media (hover: hover) and (forced-colors: none)': `color-mix(in srgb, ${colorVars['--color-accent']}, ${colorVars['--color-tint-hover']} 15%)`,
201
+ '@media (hover: hover)': `color-mix(in srgb, ${colorVars['--color-accent']}, ${colorVars['--color-tint-hover']} 15%)`,
229
202
  },
230
203
  },
231
204
  },
232
205
  trackDisabled: {
233
206
  opacity: 0.5,
234
- // Opacity dimming does not survive forced colors; GrayText is the
235
- // platform's disabled affordance there.
236
- borderColor: {
237
- default: null,
238
- '@media (forced-colors: active)': 'GrayText',
239
- },
240
207
  },
241
208
  trackDisabledOff: {
242
209
  backgroundColor: colorVars['--color-background-gray'],
@@ -246,6 +213,7 @@ const styles = stylex.create({
246
213
  alignItems: 'center',
247
214
  justifyContent: 'center',
248
215
  borderRadius: radiusVars['--radius-full'],
216
+ backgroundColor: colorVars['--color-background-surface'],
249
217
  transitionProperty: 'transform, width, height',
250
218
  transitionDuration: {
251
219
  default: durationVars['--duration-fast'],
@@ -253,22 +221,6 @@ const styles = stylex.create({
253
221
  },
254
222
  transitionTimingFunction: easeVars['--ease-standard'],
255
223
  },
256
- // The thumb fill lives on the on/off styles (not the shared thumb style)
257
- // because forced colors needs a per-state system color: CanvasText on the
258
- // empty off track, HighlightText on the Highlight on track. Sizing stays in
259
- // thumbOffSizeStyles/thumbOnSizeStyles; only the fill is state-dependent.
260
- thumbOff: {
261
- backgroundColor: {
262
- default: colorVars['--color-background-surface'],
263
- '@media (forced-colors: active)': 'CanvasText',
264
- },
265
- },
266
- thumbOn: {
267
- backgroundColor: {
268
- default: colorVars['--color-background-surface'],
269
- '@media (forced-colors: active)': 'HighlightText',
270
- },
271
- },
272
224
  labelWrapper: {
273
225
  display: 'flex',
274
226
  flexDirection: 'column',
@@ -585,7 +537,6 @@ export function Switch({
585
537
  stylex.props(
586
538
  styles.thumb,
587
539
  isOn ? thumbOnSizeStyles[size] : thumbOffSizeStyles[size],
588
- isOn ? styles.thumbOn : styles.thumbOff,
589
540
  ),
590
541
  )}>
591
542
  {isBusy && <Spinner size="sm" />}
@@ -9,7 +9,6 @@ import {
9
9
  useTableRowExpansion,
10
10
  useTableRowExpansionState,
11
11
  } from './useTableRowExpansion';
12
- import {InternationalizationProvider} from '../../../i18n';
13
12
 
14
13
  // popover mock for context-menu tests
15
14
  beforeEach(() => {
@@ -119,24 +118,6 @@ describe('useTableRowExpansion', () => {
119
118
  expect(items.length).toBeGreaterThan(0);
120
119
  });
121
120
 
122
- it('localizes the context-menu action label through the i18n catalog', () => {
123
- render(
124
- <InternationalizationProvider
125
- locale="fr"
126
- overrides={{
127
- fr: {'@astryx.tableRowExpansion.expandRow': 'Développer la ligne'},
128
- }}>
129
- <Harness />
130
- </InternationalizationProvider>,
131
- );
132
- fireEvent.contextMenu(screen.getByText('Folder A'));
133
- const items = screen.getAllByRole('menuitem', {
134
- name: /Développer la ligne/,
135
- hidden: true,
136
- });
137
- expect(items.length).toBeGreaterThan(0);
138
- });
139
-
140
121
  it('does not show chevron for leaf nodes', () => {
141
122
  render(<Harness />);
142
123
  // Leaf C has no children
@@ -576,9 +576,7 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
576
576
  {
577
577
  id: 'row-expansion-toggle',
578
578
  group: 'row-expansion',
579
- label: isExpanded
580
- ? t('@astryx.tableRowExpansion.collapseRow')
581
- : t('@astryx.tableRowExpansion.expandRow'),
579
+ label: isExpanded ? 'Collapse row' : 'Expand row',
582
580
  icon: (
583
581
  <span {...stylex.props(rtlStyles.mirror)}>
584
582
  <Icon