@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.
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -6
- package/dist/Button/Button.d.ts +1 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +2 -4
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +2 -2
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +4 -19
- package/dist/CodeBlock/CodeBlock.js +2 -2
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +1 -10
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +11 -8
- package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -3
- package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuRadioItem.js +1 -1
- package/dist/Field/FieldLabel.d.ts +1 -2
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +3 -7
- package/dist/Item/Item.d.ts +3 -16
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +6 -31
- package/dist/List/ListItem.d.ts +1 -11
- package/dist/List/ListItem.d.ts.map +1 -1
- package/dist/List/ListItem.js +0 -2
- package/dist/MetadataList/MetadataList.d.ts +1 -1
- package/dist/MetadataList/MetadataList.d.ts.map +1 -1
- package/dist/MetadataList/MetadataList.js +2 -4
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +2 -13
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +0 -5
- package/dist/Skeleton/Skeleton.d.ts.map +1 -1
- package/dist/Skeleton/Skeleton.js +2 -2
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +4 -15
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +1 -1
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +9 -61
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +1 -3
- package/dist/Token/Token.d.ts +1 -4
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +43 -68
- package/dist/Typeahead/BaseTypeahead.d.ts +0 -9
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +0 -2
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +1 -8
- package/dist/astryx.css +3 -16
- package/dist/astryx.umd.js +48 -48
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/useKeyboardHint.d.ts +1 -1
- package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
- package/dist/hooks/useKeyboardHint.js +2 -4
- package/locales/en.json +0 -32
- package/locales/pseudo.json +0 -24
- package/package.json +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +1 -21
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -4
- package/src/Breadcrumbs/Breadcrumbs.test.tsx +1 -1
- package/src/Button/Button.test.tsx +0 -16
- package/src/Button/Button.tsx +2 -4
- package/src/CheckboxInput/CheckboxInput.test.tsx +0 -23
- package/src/CheckboxInput/CheckboxInput.tsx +2 -17
- package/src/CheckboxList/CheckboxList.test.tsx +3 -94
- package/src/CheckboxList/CheckboxListItem.tsx +10 -17
- package/src/CodeBlock/CodeBlock.test.tsx +0 -17
- package/src/CodeBlock/CodeBlock.tsx +2 -2
- package/src/ContextMenu/ContextMenu.test.tsx +2 -17
- package/src/ContextMenu/ContextMenu.tsx +1 -10
- package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +12 -4
- package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +10 -9
- package/src/DropdownMenu/DropdownMenuRadioItem.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +2 -20
- package/src/Field/FieldLabel.test.tsx +0 -25
- package/src/Field/FieldLabel.tsx +2 -11
- package/src/Item/Item.doc.mjs +0 -5
- package/src/Item/Item.test.tsx +0 -88
- package/src/Item/Item.tsx +11 -57
- package/src/List/ListItem.doc.mjs +0 -12
- package/src/List/ListItem.tsx +0 -13
- package/src/MetadataList/MetadataList.test.tsx +0 -24
- package/src/MetadataList/MetadataList.tsx +2 -6
- package/src/MultiSelector/MultiSelector.test.tsx +0 -76
- package/src/MultiSelector/MultiSelector.tsx +2 -22
- package/src/RadioList/RadioList.test.tsx +4 -26
- package/src/RadioList/RadioListItem.tsx +1 -8
- package/src/SegmentedControl/SegmentedControl.test.tsx +0 -28
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -20
- package/src/Selector/Selector.test.tsx +0 -22
- package/src/Selector/Selector.tsx +1 -8
- package/src/Skeleton/Skeleton.test.tsx +0 -16
- package/src/Skeleton/Skeleton.tsx +1 -13
- package/src/Switch/Switch.test.tsx +0 -37
- package/src/Switch/Switch.tsx +3 -52
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +0 -19
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +1 -3
- package/src/Toast/ToastViewport.test.tsx +8 -218
- package/src/Toast/ToastViewport.tsx +12 -71
- package/src/ToggleButton/ToggleButton.test.tsx +0 -24
- package/src/ToggleButton/ToggleButton.tsx +1 -19
- package/src/Token/Token.test.tsx +0 -145
- package/src/Token/Token.tsx +33 -73
- package/src/Typeahead/BaseTypeahead.tsx +0 -12
- package/src/Typeahead/Typeahead.test.tsx +0 -62
- package/src/Typeahead/Typeahead.tsx +0 -6
- package/src/hooks/useKeyboardHint.test.tsx +0 -22
- package/src/hooks/useKeyboardHint.tsx +2 -6
- package/dist/Token/TokenLink.d.ts +0 -62
- package/dist/Token/TokenLink.d.ts.map +0 -1
- package/dist/Token/TokenLink.js +0 -76
- package/src/Token/TokenLink.tsx +0 -107
- package/src/__tests__/forcedColors.ts +0 -60
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file useKeyboardHint.tsx
|
|
3
|
-
* @input Uses React, StyleX, Kbd, useLayer
|
|
3
|
+
* @input Uses React, StyleX, Kbd, useLayer
|
|
4
4
|
* @output Exports useKeyboardHint hook — ephemeral arrow-key navigation hint
|
|
5
5
|
* @position Core hook; shows sighted keyboard users how to navigate composite
|
|
6
6
|
* widgets that use roving tabindex (single Tab stop, arrows inside)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKeyboardHint.d.ts","sourceRoot":"","sources":["../../src/hooks/useKeyboardHint.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useKeyboardHint.d.ts","sourceRoot":"","sources":["../../src/hooks/useKeyboardHint.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAAiC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAgB5E,MAAM,MAAM,uBAAuB,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,CAAC;AAEzE,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,uBAAuB,CAAC;IAEtC;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,WAAW,EAAE,SAAS,CAAC;IAEvB;;;OAGG;IACH,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAEvC;;;OAGG;IACH,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAEtC;;;OAGG;IACH,SAAS,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,aAAa,KAAK,IAAI,CAAC;CAC7C;AAkED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CA6JvB"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useKeyboardHint.tsx
|
|
7
|
-
* @input Uses React, StyleX, Kbd, useLayer
|
|
7
|
+
* @input Uses React, StyleX, Kbd, useLayer
|
|
8
8
|
* @output Exports useKeyboardHint hook — ephemeral arrow-key navigation hint
|
|
9
9
|
* @position Core hook; shows sighted keyboard users how to navigate composite
|
|
10
10
|
* widgets that use roving tabindex (single Tab stop, arrows inside)
|
|
@@ -21,7 +21,6 @@ import "../theme/tokens.stylex.js";
|
|
|
21
21
|
import { colorVars, radiusVars, shadowVars, spacingVars, typeScaleVars } from "../theme/tokens.stylex.js";
|
|
22
22
|
import { Kbd } from "../Kbd/index.js";
|
|
23
23
|
import { useLayer } from "../Layer/useLayer.js";
|
|
24
|
-
import { useTranslator } from "../i18n/index.js";
|
|
25
24
|
|
|
26
25
|
// ---------------------------------------------------------------------------
|
|
27
26
|
// Types
|
|
@@ -91,7 +90,6 @@ export function useKeyboardHint(options = {}) {
|
|
|
91
90
|
dismissAfterMs = 3000,
|
|
92
91
|
isEnabled = true
|
|
93
92
|
} = options;
|
|
94
|
-
const t = useTranslator();
|
|
95
93
|
const timeoutRef = useRef(null);
|
|
96
94
|
const dismissedRef = useRef(false);
|
|
97
95
|
const isVisibleRef = useRef(false);
|
|
@@ -203,7 +201,7 @@ export function useKeyboardHint(options = {}) {
|
|
|
203
201
|
...{
|
|
204
202
|
className: "x11g1kdw"
|
|
205
203
|
},
|
|
206
|
-
children:
|
|
204
|
+
children: "to navigate"
|
|
207
205
|
})]
|
|
208
206
|
}), {
|
|
209
207
|
placement: 'below',
|
package/locales/en.json
CHANGED
|
@@ -247,10 +247,6 @@
|
|
|
247
247
|
"defaultMessage": "Use arrow keys to move between avatars",
|
|
248
248
|
"description": "Screen-reader-only instruction attached (via aria-describedby) to a group of interactive avatars that share a single Tab stop. Tells keyboard users the Left/Right arrow keys move focus between the avatars. Only announced when the group has interactive (link/button) avatars."
|
|
249
249
|
},
|
|
250
|
-
"@astryx.avatarGroup.overflow": {
|
|
251
|
-
"defaultMessage": "{count, number} more",
|
|
252
|
-
"description": "Accessible name for the \"+N\" overflow indicator at the end of an AvatarGroup — announces how many additional avatars are not shown. Example: `5 more`. The visible \"+N\" text is unaffected; this is the aria-label only."
|
|
253
|
-
},
|
|
254
250
|
"@astryx.banner.dismiss": {
|
|
255
251
|
"defaultMessage": "Dismiss",
|
|
256
252
|
"description": "\"Dismiss\" = close/hide this notification (not \"reject a person\"). Aria label AND tooltip on the small X button on a Banner."
|
|
@@ -411,10 +407,6 @@
|
|
|
411
407
|
"defaultMessage": "Search options",
|
|
412
408
|
"description": "Screen-reader-only accessible name for that same search input inside a MultiSelector."
|
|
413
409
|
},
|
|
414
|
-
"@astryx.multiSelector.selectAllPartiallySelected": {
|
|
415
|
-
"defaultMessage": "{label}, partially selected",
|
|
416
|
-
"description": "Accessible name for the MultiSelector select-all option while only some options are selected. `{label}` is the visible select-all label (e.g. \"Select all\"). ARIA forbids aria-selected=\"mixed\" on options, so the indeterminate state is conveyed through the name instead. \"Partially\" = some but not all."
|
|
417
|
-
},
|
|
418
410
|
"@astryx.popover.close": {
|
|
419
411
|
"defaultMessage": "Close popover",
|
|
420
412
|
"description": "\"Close\" = shut/dismiss, not \"nearby\". Screen-reader-only label on the close button inside a Popover."
|
|
@@ -691,10 +683,6 @@
|
|
|
691
683
|
"defaultMessage": "Expand",
|
|
692
684
|
"description": "\"Expand\" = reveal more, not grow physically. Aria label AND tooltip on the same toggle when currently collapsed. Pairs with `banner.collapse`."
|
|
693
685
|
},
|
|
694
|
-
"@astryx.button.loading": {
|
|
695
|
-
"defaultMessage": "Loading",
|
|
696
|
-
"description": "Screen-reader-only live-region announcement while a Button is in its loading state (spinner shown, action in flight). Progressive sense (\"work in progress\"), not a noun. Kept separate from `typeahead.loading` — translations may diverge."
|
|
697
|
-
},
|
|
698
686
|
"@astryx.chatComposerDrawer.expand": {
|
|
699
687
|
"defaultMessage": "Expand {label}",
|
|
700
688
|
"description": "Screen-reader-only label on the ChatComposerDrawer toggle when the drawer is collapsed. `{label}` is the drawer's visible name — example: `Expand Attachments`. Pairs with `collapse`."
|
|
@@ -759,14 +747,6 @@
|
|
|
759
747
|
"defaultMessage": "Code",
|
|
760
748
|
"description": "Screen-reader-only fallback name for a code-snippet scroll container when the code's language is unknown. \"Code\" = source code, not secret code or code of conduct."
|
|
761
749
|
},
|
|
762
|
-
"@astryx.field.optional": {
|
|
763
|
-
"defaultMessage": "Optional",
|
|
764
|
-
"description": "Visible indicator shown next to a form field's label when the field is optional (isOptional). Mutually exclusive with the required indicator. Very short — appears inline after the label text."
|
|
765
|
-
},
|
|
766
|
-
"@astryx.field.required": {
|
|
767
|
-
"defaultMessage": "Required",
|
|
768
|
-
"description": "Visible indicator shown next to a form field's label when the field must be filled in (isRequired). Mutually exclusive with the optional indicator. Very short — appears inline after the label text."
|
|
769
|
-
},
|
|
770
750
|
"@astryx.fileInput.clearLabel": {
|
|
771
751
|
"defaultMessage": "Clear {label}",
|
|
772
752
|
"description": "Screen-reader-only label on the X that removes the selected file from a FileInput. Example: `Clear Attachment`, `Clear Résumé`."
|
|
@@ -779,22 +759,10 @@
|
|
|
779
759
|
"defaultMessage": "{label}, {fileNames}",
|
|
780
760
|
"description": "Accessible name for a FileInput trigger that has files attached, composing the field label with the selected filenames. Example: `Attachments, report.pdf, notes.txt`."
|
|
781
761
|
},
|
|
782
|
-
"@astryx.keyboardHint.toNavigate": {
|
|
783
|
-
"defaultMessage": "to navigate",
|
|
784
|
-
"description": "Trailing text in the keyboard-hint badge, shown after arrow-key icons — reads as `← → to navigate`. Lowercase sentence fragment completing the visual phrase; the badge is aria-hidden, so this is for sighted users only."
|
|
785
|
-
},
|
|
786
762
|
"@astryx.lightbox.mediaViewer": {
|
|
787
763
|
"defaultMessage": "Media viewer",
|
|
788
764
|
"description": "Screen-reader-only fallback name for the Lightbox dialog when the current media item has no alt text. Should rarely render — consumers should provide alt."
|
|
789
765
|
},
|
|
790
|
-
"@astryx.metadataList.showMore": {
|
|
791
|
-
"defaultMessage": "Show more",
|
|
792
|
-
"description": "Button label under a MetadataList that reveals items hidden beyond the configured maximum. Toggles with `metadataList.showLess` — keep the pair parallel in your language."
|
|
793
|
-
},
|
|
794
|
-
"@astryx.metadataList.showLess": {
|
|
795
|
-
"defaultMessage": "Show less",
|
|
796
|
-
"description": "Button label under a MetadataList that re-hides the extra items revealed by `metadataList.showMore`. Keep the pair parallel."
|
|
797
|
-
},
|
|
798
766
|
"@astryx.mobileNav.navigation": {
|
|
799
767
|
"defaultMessage": "Navigation",
|
|
800
768
|
"description": "Screen-reader-only fallback name for the MobileNav overlay dialog when no explicit label was passed."
|
package/locales/pseudo.json
CHANGED
|
@@ -185,9 +185,6 @@
|
|
|
185
185
|
"@astryx.avatarGroup.keyboardHint": {
|
|
186
186
|
"defaultMessage": "⟦Úšé àřřóŵ ķéýš ţó ɱóṽé ƀéţŵééñ àṽàţàřš⟧"
|
|
187
187
|
},
|
|
188
|
-
"@astryx.avatarGroup.overflow": {
|
|
189
|
-
"defaultMessage": "⟦{count, number} ɱóřé⟧"
|
|
190
|
-
},
|
|
191
188
|
"@astryx.banner.dismiss": {
|
|
192
189
|
"defaultMessage": "⟦Ðíšɱíšš⟧"
|
|
193
190
|
},
|
|
@@ -308,9 +305,6 @@
|
|
|
308
305
|
"@astryx.multiSelector.searchOptions": {
|
|
309
306
|
"defaultMessage": "⟦Šéàřçĥ óþţíóñš⟧"
|
|
310
307
|
},
|
|
311
|
-
"@astryx.multiSelector.selectAllPartiallySelected": {
|
|
312
|
-
"defaultMessage": "⟦{label}, þàřţíàłłý šéłéçţéð⟧"
|
|
313
|
-
},
|
|
314
308
|
"@astryx.popover.close": {
|
|
315
309
|
"defaultMessage": "⟦Çłóšé þóþóṽéř⟧"
|
|
316
310
|
},
|
|
@@ -518,9 +512,6 @@
|
|
|
518
512
|
"@astryx.banner.expand": {
|
|
519
513
|
"defaultMessage": "⟦Éẋþàñð⟧"
|
|
520
514
|
},
|
|
521
|
-
"@astryx.button.loading": {
|
|
522
|
-
"defaultMessage": "⟦Łóàðíñĝ⟧"
|
|
523
|
-
},
|
|
524
515
|
"@astryx.chatComposerDrawer.expand": {
|
|
525
516
|
"defaultMessage": "⟦Éẋþàñð {label}⟧"
|
|
526
517
|
},
|
|
@@ -569,12 +560,6 @@
|
|
|
569
560
|
"@astryx.codeBlock.code": {
|
|
570
561
|
"defaultMessage": "⟦Çóðé⟧"
|
|
571
562
|
},
|
|
572
|
-
"@astryx.field.optional": {
|
|
573
|
-
"defaultMessage": "⟦Óþţíóñàł⟧"
|
|
574
|
-
},
|
|
575
|
-
"@astryx.field.required": {
|
|
576
|
-
"defaultMessage": "⟦Řéɋúířéð⟧"
|
|
577
|
-
},
|
|
578
563
|
"@astryx.fileInput.clearLabel": {
|
|
579
564
|
"defaultMessage": "⟦Çłéàř {label}⟧"
|
|
580
565
|
},
|
|
@@ -584,18 +569,9 @@
|
|
|
584
569
|
"@astryx.fileInput.triggerWithFiles": {
|
|
585
570
|
"defaultMessage": "⟦{label}, {fileNames}⟧"
|
|
586
571
|
},
|
|
587
|
-
"@astryx.keyboardHint.toNavigate": {
|
|
588
|
-
"defaultMessage": "⟦ţó ñàṽíĝàţé⟧"
|
|
589
|
-
},
|
|
590
572
|
"@astryx.lightbox.mediaViewer": {
|
|
591
573
|
"defaultMessage": "⟦Ṁéðíà ṽíéŵéř⟧"
|
|
592
574
|
},
|
|
593
|
-
"@astryx.metadataList.showMore": {
|
|
594
|
-
"defaultMessage": "⟦Šĥóŵ ɱóřé⟧"
|
|
595
|
-
},
|
|
596
|
-
"@astryx.metadataList.showLess": {
|
|
597
|
-
"defaultMessage": "⟦Šĥóŵ łéšš⟧"
|
|
598
|
-
},
|
|
599
575
|
"@astryx.mobileNav.navigation": {
|
|
600
576
|
"defaultMessage": "⟦Ñàṽíĝàţíóñ⟧"
|
|
601
577
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -5,7 +5,6 @@ import userEvent from '@testing-library/user-event';
|
|
|
5
5
|
import {AvatarGroup} from './AvatarGroup';
|
|
6
6
|
import {AvatarGroupOverflow} from './AvatarGroupOverflow';
|
|
7
7
|
import {Avatar, AvatarStatusDot} from '../Avatar';
|
|
8
|
-
import {InternationalizationProvider} from '../i18n';
|
|
9
8
|
|
|
10
9
|
describe('AvatarGroup', () => {
|
|
11
10
|
it('renders all avatar children', () => {
|
|
@@ -239,26 +238,7 @@ describe('AvatarGroupOverflow — hardening', () => {
|
|
|
239
238
|
);
|
|
240
239
|
|
|
241
240
|
expect(screen.getByText('+4912')).toBeInTheDocument();
|
|
242
|
-
|
|
243
|
-
// argument, so the en locale adds a grouping separator.
|
|
244
|
-
expect(screen.getByLabelText('4,912 more')).toBeInTheDocument();
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it('localizes the overflow label through the i18n catalog', () => {
|
|
248
|
-
render(
|
|
249
|
-
<InternationalizationProvider
|
|
250
|
-
locale="fr"
|
|
251
|
-
overrides={{
|
|
252
|
-
fr: {'@astryx.avatarGroup.overflow': '{count, number} de plus'},
|
|
253
|
-
}}>
|
|
254
|
-
<AvatarGroup>
|
|
255
|
-
<Avatar name="Alice" />
|
|
256
|
-
<AvatarGroupOverflow count={3} />
|
|
257
|
-
</AvatarGroup>
|
|
258
|
-
</InternationalizationProvider>,
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
expect(screen.getByLabelText('3 de plus')).toBeInTheDocument();
|
|
241
|
+
expect(screen.getByLabelText('4912 more')).toBeInTheDocument();
|
|
262
242
|
});
|
|
263
243
|
});
|
|
264
244
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @file AvatarGroupOverflow.tsx
|
|
6
|
-
* @input Uses React, StyleX, AvatarGroupContext
|
|
6
|
+
* @input Uses React, StyleX, AvatarGroupContext
|
|
7
7
|
* @output Exports AvatarGroupOverflow for overflow indicator
|
|
8
8
|
* @position Slot component used inside AvatarGroup
|
|
9
9
|
*
|
|
@@ -26,7 +26,6 @@ import {mergeProps} from '../utils';
|
|
|
26
26
|
import {useAvatarGroup} from './AvatarGroupContext';
|
|
27
27
|
import type {BaseProps} from '../BaseProps';
|
|
28
28
|
import {themeProps} from '../utils/themeProps';
|
|
29
|
-
import {useTranslator} from '../i18n';
|
|
30
29
|
|
|
31
30
|
const BORDER_WIDTH = 2;
|
|
32
31
|
const OVERFLOW_FONT_RATIO = 0.35;
|
|
@@ -151,12 +150,11 @@ export function AvatarGroupOverflow({
|
|
|
151
150
|
style,
|
|
152
151
|
...rest
|
|
153
152
|
}: AvatarGroupOverflowProps): ReactNode {
|
|
154
|
-
const t = useTranslator();
|
|
155
153
|
const group = useAvatarGroup();
|
|
156
154
|
const numericSize = group?.numericSize ?? 36;
|
|
157
155
|
const overlap = group?.overlap ?? 0;
|
|
158
156
|
|
|
159
|
-
const label =
|
|
157
|
+
const label = `${count} more`;
|
|
160
158
|
const content = children ?? `+${count}`;
|
|
161
159
|
|
|
162
160
|
if (onClick) {
|
|
@@ -14,7 +14,6 @@ import {render, screen, fireEvent, act} from '@testing-library/react';
|
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
import {Button} from './Button';
|
|
16
16
|
import {Badge} from '../Badge/Badge';
|
|
17
|
-
import {InternationalizationProvider} from '../i18n';
|
|
18
17
|
|
|
19
18
|
describe('Button', () => {
|
|
20
19
|
it('renders label as visible text', () => {
|
|
@@ -447,21 +446,6 @@ describe('Button', () => {
|
|
|
447
446
|
expect(liveRegion).toHaveTextContent('Loading');
|
|
448
447
|
});
|
|
449
448
|
|
|
450
|
-
it('localizes the loading announcement through the i18n catalog', () => {
|
|
451
|
-
render(
|
|
452
|
-
<InternationalizationProvider
|
|
453
|
-
locale="fr"
|
|
454
|
-
overrides={{fr: {'@astryx.button.loading': 'Chargement'}}}>
|
|
455
|
-
<Button label="Submit" isLoading />
|
|
456
|
-
</InternationalizationProvider>,
|
|
457
|
-
);
|
|
458
|
-
const button = screen.getByRole('button');
|
|
459
|
-
// The Spinner also has role="status", so grab the live region explicitly.
|
|
460
|
-
const regions = button.querySelectorAll('[role="status"]');
|
|
461
|
-
const liveRegion = regions[regions.length - 1];
|
|
462
|
-
expect(liveRegion).toHaveTextContent('Chargement');
|
|
463
|
-
});
|
|
464
|
-
|
|
465
449
|
describe('elevation', () => {
|
|
466
450
|
it('renders a distinct class for each elevation level', () => {
|
|
467
451
|
const classFor = (elevation: 'none' | 'low' | 'med' | 'high') => {
|
package/src/Button/Button.tsx
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Button.tsx
|
|
7
|
-
* @input Uses React, ButtonHTMLAttributes, ReactNode
|
|
7
|
+
* @input Uses React, ButtonHTMLAttributes, ReactNode
|
|
8
8
|
* @output Exports Button component, ButtonProps, ButtonVariant types
|
|
9
9
|
* @position Core implementation; consumed by index.ts, tested by Button.test.tsx
|
|
10
10
|
*
|
|
@@ -45,7 +45,6 @@ import {mergeProps, mergeRefs} from '../utils';
|
|
|
45
45
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
46
46
|
import type {LinkComponentType} from '../Link/types';
|
|
47
47
|
import {themeProps} from '../utils/themeProps';
|
|
48
|
-
import {useTranslator} from '../i18n';
|
|
49
48
|
|
|
50
49
|
/**
|
|
51
50
|
* Base button styles
|
|
@@ -612,7 +611,6 @@ export function Button({
|
|
|
612
611
|
ref,
|
|
613
612
|
...props
|
|
614
613
|
}: ButtonProps): ReactNode {
|
|
615
|
-
const t = useTranslator();
|
|
616
614
|
const size = useSize(sizeProp, 'md');
|
|
617
615
|
const buttonGroup = useButtonGroup();
|
|
618
616
|
|
|
@@ -762,7 +760,7 @@ export function Button({
|
|
|
762
760
|
</span>
|
|
763
761
|
{/* Live region for loading state announcements */}
|
|
764
762
|
<VisuallyHidden role="status" aria-live="polite">
|
|
765
|
-
{isLoadingState ?
|
|
763
|
+
{isLoadingState ? 'Loading' : ''}
|
|
766
764
|
</VisuallyHidden>
|
|
767
765
|
</>
|
|
768
766
|
);
|
|
@@ -13,7 +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 {CheckboxInput} from './CheckboxInput';
|
|
16
|
-
import {getForcedColorsRules} from '../__tests__/forcedColors';
|
|
17
16
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
18
17
|
|
|
19
18
|
afterEach(() => {
|
|
@@ -544,25 +543,3 @@ describe('CheckboxInput', () => {
|
|
|
544
543
|
});
|
|
545
544
|
});
|
|
546
545
|
});
|
|
547
|
-
|
|
548
|
-
// jsdom cannot emulate forced-colors rendering, so this asserts that the
|
|
549
|
-
// compiled output includes the forced-colors rule; visual behavior needs
|
|
550
|
-
// manual verification under Windows High Contrast.
|
|
551
|
-
describe('forced colors (WCAG 1.4.11)', () => {
|
|
552
|
-
it('compiles a forced-colors fill so the indeterminate mark survives Windows High Contrast', () => {
|
|
553
|
-
render(
|
|
554
|
-
<CheckboxInput label="All" value="indeterminate" onChange={() => {}} />,
|
|
555
|
-
);
|
|
556
|
-
// The painted indeterminate bar would be stripped to Canvas (invisible);
|
|
557
|
-
// CanvasText keeps it perceivable.
|
|
558
|
-
expect(getForcedColorsRules()).toContain('background-color: canvastext;');
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
it('compiles a forced-colors color so the checkmark survives Windows High Contrast', () => {
|
|
562
|
-
render(<CheckboxInput label="Accept" value={true} onChange={() => {}} />);
|
|
563
|
-
// The check strokes with currentColor; forced colors leaves it the same
|
|
564
|
-
// white as the flattened box, so it needs its own CanvasText color to stay
|
|
565
|
-
// perceivable on the Canvas box.
|
|
566
|
-
expect(getForcedColorsRules()).toContain('color: canvastext;');
|
|
567
|
-
});
|
|
568
|
-
});
|
|
@@ -158,29 +158,14 @@ const styles = stylex.create({
|
|
|
158
158
|
},
|
|
159
159
|
checkmark: {
|
|
160
160
|
display: 'none',
|
|
161
|
-
color:
|
|
162
|
-
default: colorVars['--color-on-accent'],
|
|
163
|
-
// Forced colors (Windows High Contrast) does not reliably force an SVG
|
|
164
|
-
// stroke painted with currentColor, so the check stays the same white as
|
|
165
|
-
// the flattened (Canvas) box fill — a white check on a white box.
|
|
166
|
-
// CanvasText keeps it perceivable on the Canvas box, matching the
|
|
167
|
-
// indeterminate mark (WCAG 1.4.11).
|
|
168
|
-
'@media (forced-colors: active)': 'CanvasText',
|
|
169
|
-
},
|
|
161
|
+
color: colorVars['--color-on-accent'],
|
|
170
162
|
},
|
|
171
163
|
checkmarkVisible: {
|
|
172
164
|
display: 'block',
|
|
173
165
|
},
|
|
174
166
|
indeterminateMark: {
|
|
175
167
|
display: 'none',
|
|
176
|
-
backgroundColor:
|
|
177
|
-
default: colorVars['--color-on-accent'],
|
|
178
|
-
// Forced colors (Windows High Contrast) strips painted backgrounds,
|
|
179
|
-
// which would make the indeterminate bar invisible; CanvasText keeps it
|
|
180
|
-
// perceivable on the Canvas box fill (WCAG 1.4.11). The checkmark carries
|
|
181
|
-
// the matching CanvasText treatment on its own style.
|
|
182
|
-
'@media (forced-colors: active)': 'CanvasText',
|
|
183
|
-
},
|
|
168
|
+
backgroundColor: colorVars['--color-on-accent'],
|
|
184
169
|
borderRadius: radiusVars['--radius-full'],
|
|
185
170
|
},
|
|
186
171
|
indeterminateMarkVisible: {
|
|
@@ -154,105 +154,14 @@ describe('CheckboxList', () => {
|
|
|
154
154
|
</CheckboxList>,
|
|
155
155
|
);
|
|
156
156
|
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
expect(
|
|
161
|
-
screen.queryByRole('button', {name: 'Option B'}),
|
|
162
|
-
).not.toBeInTheDocument();
|
|
163
|
-
// The text also exists in the checkbox's visually hidden <label>; target
|
|
164
|
-
// the row's label <span> to click the row surface.
|
|
165
|
-
await user.click(screen.getByText('Option B', {selector: 'span'}));
|
|
157
|
+
// Click the interactive row (invisible-button pattern), which is where the
|
|
158
|
+
// composed onClick lives — not the inner checkbox.
|
|
159
|
+
await user.click(screen.getByRole('button', {name: 'Option B'}));
|
|
166
160
|
|
|
167
161
|
expect(handleClick).toHaveBeenCalledTimes(1);
|
|
168
162
|
expect(handleChange).toHaveBeenCalledWith(['a', 'b']);
|
|
169
163
|
});
|
|
170
164
|
|
|
171
|
-
describe('single tab stop per option (WCAG 4.1.2 / APG checkbox pattern)', () => {
|
|
172
|
-
it('renders exactly one focusable control per option — the checkbox, no row button', () => {
|
|
173
|
-
render(
|
|
174
|
-
<CheckboxList label="Preferences" value={['a']} onChange={() => {}}>
|
|
175
|
-
<CheckboxListItem label="Option A" value="a" />
|
|
176
|
-
<CheckboxListItem label="Option B" value="b" />
|
|
177
|
-
</CheckboxList>,
|
|
178
|
-
);
|
|
179
|
-
// No invisible whole-row button duplicating the checkbox's action.
|
|
180
|
-
expect(screen.queryByRole('button')).not.toBeInTheDocument();
|
|
181
|
-
for (const item of screen.getAllByRole('listitem')) {
|
|
182
|
-
const focusables = item.querySelectorAll(
|
|
183
|
-
'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
|
|
184
|
-
);
|
|
185
|
-
expect(focusables).toHaveLength(1);
|
|
186
|
-
expect(focusables[0]).toBe(within(item).getByRole('checkbox'));
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('Tab moves directly from one option checkbox to the next', async () => {
|
|
191
|
-
const user = userEvent.setup();
|
|
192
|
-
render(
|
|
193
|
-
<CheckboxList label="Preferences" value={[]} onChange={() => {}}>
|
|
194
|
-
<CheckboxListItem label="Option A" value="a" />
|
|
195
|
-
<CheckboxListItem label="Option B" value="b" />
|
|
196
|
-
</CheckboxList>,
|
|
197
|
-
);
|
|
198
|
-
await user.tab();
|
|
199
|
-
expect(screen.getByRole('checkbox', {name: 'Option A'})).toHaveFocus();
|
|
200
|
-
await user.tab();
|
|
201
|
-
expect(screen.getByRole('checkbox', {name: 'Option B'})).toHaveFocus();
|
|
202
|
-
// No further tab stop inside the list (the row itself is not focusable).
|
|
203
|
-
await user.tab();
|
|
204
|
-
expect(document.body).toHaveFocus();
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('clicking the row surface (outside the checkbox) still toggles', async () => {
|
|
208
|
-
const user = userEvent.setup();
|
|
209
|
-
const handleChange = vi.fn();
|
|
210
|
-
render(
|
|
211
|
-
<CheckboxList label="Preferences" value={['a']} onChange={handleChange}>
|
|
212
|
-
<CheckboxListItem label="Option A" value="a" />
|
|
213
|
-
<CheckboxListItem label="Option B" value="b" />
|
|
214
|
-
</CheckboxList>,
|
|
215
|
-
);
|
|
216
|
-
// The text also exists in the checkbox's visually hidden <label>;
|
|
217
|
-
// target the row's label <span> to click the row surface.
|
|
218
|
-
await user.click(screen.getByText('Option B', {selector: 'span'}));
|
|
219
|
-
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
220
|
-
expect(handleChange).toHaveBeenCalledWith(['a', 'b']);
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('toggles with Space on the focused checkbox, which reports checked state', async () => {
|
|
224
|
-
const user = userEvent.setup();
|
|
225
|
-
const handleChange = vi.fn();
|
|
226
|
-
render(
|
|
227
|
-
<CheckboxList label="Preferences" value={['a']} onChange={handleChange}>
|
|
228
|
-
<CheckboxListItem label="Option A" value="a" />
|
|
229
|
-
<CheckboxListItem label="Option B" value="b" />
|
|
230
|
-
</CheckboxList>,
|
|
231
|
-
);
|
|
232
|
-
await user.tab();
|
|
233
|
-
// The focused control is the checkbox itself, so its accessible name
|
|
234
|
-
// and checked state are what assistive tech announces on focus.
|
|
235
|
-
const checkboxA = screen.getByRole('checkbox', {name: 'Option A'});
|
|
236
|
-
expect(checkboxA).toHaveFocus();
|
|
237
|
-
expect(checkboxA).toBeChecked();
|
|
238
|
-
await user.keyboard(' ');
|
|
239
|
-
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
240
|
-
expect(handleChange).toHaveBeenCalledWith([]);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('the row itself is not focusable', () => {
|
|
244
|
-
render(
|
|
245
|
-
<CheckboxList label="Preferences" value={[]} onChange={() => {}}>
|
|
246
|
-
<CheckboxListItem label="Option A" value="a" />
|
|
247
|
-
</CheckboxList>,
|
|
248
|
-
);
|
|
249
|
-
const item = screen.getByRole('listitem');
|
|
250
|
-
expect(item).not.toHaveAttribute('tabindex');
|
|
251
|
-
item.focus();
|
|
252
|
-
expect(item).not.toHaveFocus();
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
165
|
it('disables all checkboxes when group isDisabled is true', () => {
|
|
257
166
|
render(
|
|
258
167
|
<CheckboxList
|
|
@@ -16,11 +16,12 @@
|
|
|
16
16
|
* - /packages/cli/templates/blocks/components/CheckboxList/ (showcase blocks)
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import {use,
|
|
19
|
+
import {use, type MouseEvent, type ReactNode} from 'react';
|
|
20
20
|
import * as stylex from '@stylexjs/stylex';
|
|
21
21
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
22
22
|
import {colorVars} from '../theme/tokens.stylex';
|
|
23
23
|
import type {BaseProps} from '../BaseProps';
|
|
24
|
+
import {composeEventHandlers} from '../utils';
|
|
24
25
|
import {useDevWarning} from '../hooks/useDevWarning';
|
|
25
26
|
import {CheckboxInput} from '../CheckboxInput/CheckboxInput';
|
|
26
27
|
import {ListItem} from '../List/ListItem';
|
|
@@ -211,14 +212,6 @@ export function CheckboxListItem({
|
|
|
211
212
|
// Whether this item is interactive (has a toggle handler)
|
|
212
213
|
const isInteractive = !effectiveReadOnly && (ctx != null || onCheck != null);
|
|
213
214
|
|
|
214
|
-
// The checkbox is the row's single keyboard control and action. The row is
|
|
215
|
-
// an enlarged click/tap target that delegates surface clicks to it via
|
|
216
|
-
// ListItem's `interactiveRef` (useClickableContainer), so each option is
|
|
217
|
-
// exactly one tab stop. Delegate whenever the row should respond to clicks:
|
|
218
|
-
// a toggleable item, or one carrying a consumer `onClick`.
|
|
219
|
-
const checkboxRef = useRef<HTMLInputElement | null>(null);
|
|
220
|
-
const hasRowInteraction = isInteractive || onClickProp != null;
|
|
221
|
-
|
|
222
215
|
const handleToggle = () => {
|
|
223
216
|
if (effectiveDisabled || effectiveReadOnly || isBusy) {
|
|
224
217
|
return;
|
|
@@ -251,12 +244,14 @@ export function CheckboxListItem({
|
|
|
251
244
|
description={description}
|
|
252
245
|
endContent={endContent}
|
|
253
246
|
isDisabled={effectiveDisabled}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
247
|
+
onClick={
|
|
248
|
+
isInteractive || onClickProp
|
|
249
|
+
? composeEventHandlers<MouseEvent>(
|
|
250
|
+
onClickProp as ((event: MouseEvent) => void) | undefined,
|
|
251
|
+
isInteractive ? handleToggle : undefined,
|
|
252
|
+
)
|
|
253
|
+
: undefined
|
|
254
|
+
}
|
|
260
255
|
aria-busy={isBusy || undefined}
|
|
261
256
|
xstyle={
|
|
262
257
|
[
|
|
@@ -271,12 +266,10 @@ export function CheckboxListItem({
|
|
|
271
266
|
style={style}
|
|
272
267
|
startContent={
|
|
273
268
|
<CheckboxInput
|
|
274
|
-
ref={checkboxRef}
|
|
275
269
|
label={checkboxLabel}
|
|
276
270
|
isLabelHidden
|
|
277
271
|
value={resolvedChecked}
|
|
278
272
|
onChange={() => handleToggle()}
|
|
279
|
-
onClick={onClickProp}
|
|
280
273
|
isDisabled={effectiveDisabled}
|
|
281
274
|
isReadOnly={effectiveReadOnly}
|
|
282
275
|
isLoading={isBusy}
|
|
@@ -14,7 +14,6 @@ import {act, render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
|
14
14
|
import {CodeBlock} from './CodeBlock';
|
|
15
15
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
16
16
|
import {dracula} from '../theme/syntax';
|
|
17
|
-
import {InternationalizationProvider} from '../i18n';
|
|
18
17
|
|
|
19
18
|
function politeRegion(): HTMLElement | null {
|
|
20
19
|
return document.querySelector('[data-astryx-live-region="polite"]');
|
|
@@ -73,22 +72,6 @@ describe('CodeBlock', () => {
|
|
|
73
72
|
});
|
|
74
73
|
});
|
|
75
74
|
|
|
76
|
-
it('localizes the copy announcement through the i18n catalog', async () => {
|
|
77
|
-
render(
|
|
78
|
-
<InternationalizationProvider
|
|
79
|
-
locale="fr"
|
|
80
|
-
overrides={{fr: {'@astryx.codeBlock.copied': 'Copié'}}}>
|
|
81
|
-
<CodeBlock code="const x = 1;" language="javascript" />
|
|
82
|
-
</InternationalizationProvider>,
|
|
83
|
-
);
|
|
84
|
-
// The button label and the live-region announcement share the same key.
|
|
85
|
-
fireEvent.click(screen.getByRole('button', {name: 'Copy code'}));
|
|
86
|
-
await waitFor(() => {
|
|
87
|
-
expect(politeRegion()).toHaveTextContent('Copié');
|
|
88
|
-
});
|
|
89
|
-
expect(screen.getByRole('button', {name: 'Copié'})).toBeInTheDocument();
|
|
90
|
-
});
|
|
91
|
-
|
|
92
75
|
it('keeps the copied indicator a full 2s after a rapid re-copy', async () => {
|
|
93
76
|
vi.useFakeTimers();
|
|
94
77
|
try {
|
|
@@ -786,7 +786,7 @@ export function CodeBlock({
|
|
|
786
786
|
setCopied(true);
|
|
787
787
|
// Swapping the button's aria-label alone isn't reliably announced by
|
|
788
788
|
// screen readers, so confirm the copy via a polite live region.
|
|
789
|
-
announce(
|
|
789
|
+
announce('Copied');
|
|
790
790
|
onCopy?.();
|
|
791
791
|
// Restart the reset timer on every copy — otherwise a rapid re-copy
|
|
792
792
|
// is reverted early by the previous click's timer.
|
|
@@ -800,7 +800,7 @@ export function CodeBlock({
|
|
|
800
800
|
} catch {
|
|
801
801
|
// Clipboard failures leave the copied state unchanged.
|
|
802
802
|
}
|
|
803
|
-
}, [code, onCopy, announce
|
|
803
|
+
}, [code, onCopy, announce]);
|
|
804
804
|
|
|
805
805
|
const sizeStyle = size === 'sm' ? styles.sizeSm : styles.sizeMd;
|
|
806
806
|
// Digits in the largest line number — sizes the gutter column width.
|