@astryxdesign/core 0.4.1 → 0.4.2-canary.464a445
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/dist/Avatar/Avatar.d.ts +4 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +19 -23
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +9 -7
- package/dist/Chat/ChatMessage.d.ts +7 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.d.ts +13 -1
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +19 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +5 -0
- package/dist/CommandPalette/CommandPaletteFooter.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteFooter.js +5 -3
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +2 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -15
- package/dist/HoverCard/HoverCard.d.ts +1 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +5 -13
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -5
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +7 -2
- package/dist/Layer/layerHost.d.ts +24 -0
- package/dist/Layer/layerHost.d.ts.map +1 -0
- package/dist/Layer/layerHost.js +79 -0
- package/dist/Layer/useLayer.d.ts +14 -6
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +158 -30
- package/dist/NavItem/navItemStyles.stylex.d.ts +17 -5
- package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
- package/dist/NavItem/navItemStyles.stylex.js +11 -5
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -0
- package/dist/SideNav/SideNav.d.ts +7 -9
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +32 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts +25 -9
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +39 -15
- package/dist/SideNav/SideNavCollapseContext.d.ts +21 -0
- package/dist/SideNav/SideNavCollapseContext.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseContext.js +16 -2
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +89 -32
- package/dist/SideNav/SideNavItem.d.ts +7 -2
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +119 -75
- package/dist/SideNav/SideNavSection.d.ts.map +1 -1
- package/dist/SideNav/SideNavSection.js +7 -16
- package/dist/SideNav/index.d.ts +1 -1
- package/dist/SideNav/index.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +56 -15
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -0
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +5 -0
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +14 -6
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +25 -93
- package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +10 -5
- package/dist/astryx.css +13 -7
- package/dist/astryx.umd.js +50 -50
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +16 -2
- package/dist/hooks/useMenuHover.d.ts +50 -5
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +171 -51
- package/dist/theme/defineTheme.d.ts +8 -2
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +8 -1
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +7 -0
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +1 -0
- package/dist/theme/expandMotionScale.d.ts +1 -0
- package/dist/theme/expandMotionScale.d.ts.map +1 -1
- package/dist/theme/expandMotionScale.js +1 -0
- package/dist/theme/expandRadiusScale.d.ts +1 -0
- package/dist/theme/expandRadiusScale.d.ts.map +1 -1
- package/dist/theme/expandRadiusScale.js +1 -0
- package/dist/theme/expandTypeScale.d.ts +1 -0
- package/dist/theme/expandTypeScale.d.ts.map +1 -1
- package/dist/theme/expandTypeScale.js +1 -0
- package/dist/theme/tokens.stylex.d.ts +3 -1
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +3 -1
- package/locales/en.json +84 -0
- package/locales/pseudo.json +63 -0
- package/package.json +3 -3
- package/src/Avatar/Avatar.doc.mjs +5 -2
- package/src/Avatar/Avatar.test.tsx +51 -0
- package/src/Avatar/Avatar.tsx +30 -20
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +14 -0
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -2
- package/src/Button/Button.tsx +5 -3
- package/src/ButtonGroup/ButtonGroup.test.tsx +7 -4
- package/src/Card/Card.doc.mjs +2 -0
- package/src/Chat/Chat.doc.mjs +4 -1
- package/src/Chat/ChatMessage.doc.mjs +3 -3
- package/src/Chat/ChatMessage.tsx +7 -0
- package/src/Chat/ChatMessageBubble.doc.mjs +7 -0
- package/src/Chat/ChatMessageBubble.test.tsx +95 -6
- package/src/Chat/ChatMessageBubble.tsx +25 -0
- package/src/CheckboxInput/CheckboxInput.tsx +20 -0
- package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
- package/src/CommandPalette/CommandPaletteFooter.tsx +6 -3
- package/src/DateTimeInput/DateTimeInput.tsx +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +72 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +24 -20
- package/src/HoverCard/HoverCard.doc.mjs +3 -3
- package/src/HoverCard/HoverCard.test.tsx +283 -55
- package/src/HoverCard/HoverCard.tsx +5 -13
- package/src/HoverCard/useHoverCard.tsx +8 -11
- package/src/InputGroup/groupStyles.ts +7 -8
- package/src/Item/Item.doc.mjs +4 -0
- package/src/Layer/layerHost.test.ts +99 -0
- package/src/Layer/layerHost.ts +141 -0
- package/src/Layer/useLayer.doc.mjs +14 -4
- package/src/Layer/useLayer.test.tsx +332 -7
- package/src/Layer/useLayer.tsx +235 -36
- package/src/MobileNav/MobileNav.doc.mjs +7 -0
- package/src/NavItem/navItemStyles.stylex.ts +31 -5
- package/src/RadioList/RadioListItem.tsx +20 -0
- package/src/SideNav/SideNav.doc.mjs +13 -4
- package/src/SideNav/SideNav.test.tsx +858 -2
- package/src/SideNav/SideNav.tsx +37 -16
- package/src/SideNav/SideNavCollapseButton.doc.mjs +28 -6
- package/src/SideNav/SideNavCollapseButton.tsx +67 -15
- package/src/SideNav/SideNavCollapseContext.ts +29 -9
- package/src/SideNav/SideNavHeading.tsx +53 -25
- package/src/SideNav/SideNavItem.doc.mjs +13 -0
- package/src/SideNav/SideNavItem.tsx +97 -84
- package/src/SideNav/SideNavSection.tsx +6 -20
- package/src/SideNav/index.ts +2 -0
- package/src/Slider/Slider.test.tsx +69 -33
- package/src/Slider/Slider.tsx +65 -13
- package/src/Switch/Switch.tsx +20 -0
- package/src/TabList/TabList.doc.mjs +3 -0
- package/src/Text/Text.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.doc.mjs +3 -0
- package/src/Thumbnail/Thumbnail.tsx +10 -0
- package/src/Timestamp/Timestamp.test.tsx +40 -22
- package/src/Toolbar/Toolbar.test.tsx +14 -14
- package/src/TopNav/TopNav.test.tsx +86 -1
- package/src/TopNav/TopNavHeading.tsx +21 -14
- package/src/TopNav/TopNavMegaMenu.test.tsx +43 -0
- package/src/TopNav/TopNavMegaMenu.tsx +29 -105
- package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
- package/src/TopNav/TopNavMenu.test.tsx +41 -0
- package/src/TopNav/TopNavMenu.tsx +14 -4
- package/src/TreeList/TreeList.doc.mjs +1 -0
- package/src/hooks/index.ts +5 -1
- package/src/hooks/useFocusTrap.test.tsx +16 -0
- package/src/hooks/useFocusTrap.ts +20 -2
- package/src/hooks/useMenuHover.test.tsx +364 -0
- package/src/hooks/useMenuHover.ts +243 -47
- package/src/theme/defineTheme.ts +9 -0
- package/src/theme/derivedVarRegistry.test.ts +78 -12
- package/src/theme/derivedVarRegistry.ts +4 -0
- package/src/theme/expandColorScale.ts +1 -0
- package/src/theme/expandMotionScale.ts +1 -0
- package/src/theme/expandRadiusScale.ts +1 -0
- package/src/theme/expandTypeScale.ts +1 -0
- package/src/theme/themingTargets.test.ts +152 -26
- package/src/theme/tokens.stylex.ts +3 -1
- package/src/theme/tokens.test.ts +12 -0
- package/src/theme/useTheme.test.tsx +18 -0
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* SYNC: When modified, update:
|
|
43
43
|
* - /packages/core/src/theme/expandTypeScale.test.ts
|
|
44
44
|
* - /packages/core/src/theme/defineTheme.ts
|
|
45
|
+
* - /packages/cli/assets/theme.template.ts (the annotated field reference)
|
|
45
46
|
*/
|
|
46
47
|
/** Font weight value — either a CSS string or a var() reference. */
|
|
47
48
|
export type FontWeightValue = string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expandTypeScale.d.ts","sourceRoot":"","sources":["../../src/theme/expandTypeScale.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"expandTypeScale.d.ts","sourceRoot":"","sources":["../../src/theme/expandTypeScale.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAMH,oEAAoE;AACpE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAC1C,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,eAAe,CAAC,CAC/C,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,OAAO,CACvC,MAAM,CACF,MAAM,GACN,OAAO,GACP,OAAO,GACP,MAAM,GACN,YAAY,GACZ,WAAW,GACX,WAAW,GACX,WAAW,GACX,CAAC,MAAM,GAAG,EAAE,CAAC,EACf,eAAe,CAChB,CACF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,OAAO,CAAC,EAAE;QACR,yEAAyE;QACzE,OAAO,CAAC,EAAE,sBAAsB,CAAC;QACjC,oEAAoE;QACpE,IAAI,CAAC,EAAE,mBAAmB,CAAC;KAC5B,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAwIrD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CA+CxE;AAiBD;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,eAAe,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAmDxD"}
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
* SYNC: When modified, update:
|
|
45
45
|
* - /packages/core/src/theme/expandTypeScale.test.ts
|
|
46
46
|
* - /packages/core/src/theme/defineTheme.ts
|
|
47
|
+
* - /packages/cli/assets/theme.template.ts (the annotated field reference)
|
|
47
48
|
*/
|
|
48
49
|
|
|
49
50
|
// =============================================================================
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* - *Vars: CSS custom properties that themes can override via createTheme
|
|
7
7
|
*
|
|
8
8
|
* SYNC: When modified, run `node scripts/generate-token-docs.mjs` to update docs.
|
|
9
|
-
* CI checks for drift via --check flag.
|
|
9
|
+
* CI checks for drift via --check flag. Adding or removing a token FAMILY (a
|
|
10
|
+
* whole `*Defaults` group) also means updating:
|
|
11
|
+
* - /packages/cli/assets/theme.template.ts (its inventory of what exists)
|
|
10
12
|
*
|
|
11
13
|
* Domain tokens (syntax highlighting, data visualization) live separately in
|
|
12
14
|
* /packages/core/src/theme/domainTokens/ — they're tree-shaken from core components.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokens.stylex.d.ts","sourceRoot":"","sources":["../../src/theme/tokens.stylex.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"tokens.stylex.d.ts","sourceRoot":"","sources":["../../src/theme/tokens.stylex.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAM3C,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyHhB,CAAC;AAEX,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAAmC,CAAC;AAM1D,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;CAgBlB,CAAC;AAEX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;WAAqC,CAAC;AAM9D,eAAO,MAAM,YAAY;;;;CAIf,CAAC;AAEX,eAAO,MAAM,QAAQ;;;;WAAkC,CAAC;AAMxD,eAAO,MAAM,cAAc;;CAEjB,CAAC;AAEX,eAAO,MAAM,UAAU;;WAAoC,CAAC;AAE5D,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,cAAc,CAAC;AAWxD,eAAO,MAAM,aAAa;;;;;CAKhB,CAAC;AAEX,eAAO,MAAM,SAAS;;;;;WAAmC,CAAC;AAM1D,eAAO,MAAM,cAAc;;;;;;;;CAWjB,CAAC;AAEX,eAAO,MAAM,UAAU;;;;;;;;WAAoC,CAAC;AAQ5D,eAAO,MAAM,cAAc;;;;;;;;;CAejB,CAAC;AAEX,eAAO,MAAM,UAAU;;;;;;;;;WAAoC,CAAC;AAU5D,eAAO,MAAM,gBAAgB;;;;;;;;;;CAUnB,CAAC;AAEX,eAAO,MAAM,YAAY;;;;;;;;;;WAAsC,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,gBAAgB,CAAC;AAM5D,eAAO,MAAM,YAAY;;CAEf,CAAC;AAEX,eAAO,MAAM,QAAQ;;WAAkC,CAAC;AAExD,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,YAAY,CAAC;AAKpD,eAAO,MAAM,kBAAkB;;;;CAMrB,CAAC;AAEX,eAAO,MAAM,cAAc;;;;WAAwC,CAAC;AAMpE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;CAcnB,CAAC;AAEX,eAAO,MAAM,YAAY;;;;;;;;;;;;;WAAsC,CAAC;AAMhE,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AAEX,eAAO,MAAM,cAAc;;;;;WAAwC,CAAC;AAMpE,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,aAAa,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,eAAe,CAAC;AAC1D,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,YAAY,CAAC;AACpD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,cAAc,CAAC;AACxD,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,cAAc,CAAC;AACxD,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,gBAAgB,CAAC;AAC5D,MAAM,MAAM,iBAAiB,GAAG,MAAM,OAAO,kBAAkB,CAAC;AAehE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DpB,CAAC;AAEX,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;WAAuC,CAAC;AAElE,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,iBAAiB,CAAC"}
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* - *Vars: CSS custom properties that themes can override via createTheme
|
|
9
9
|
*
|
|
10
10
|
* SYNC: When modified, run `node scripts/generate-token-docs.mjs` to update docs.
|
|
11
|
-
* CI checks for drift via --check flag.
|
|
11
|
+
* CI checks for drift via --check flag. Adding or removing a token FAMILY (a
|
|
12
|
+
* whole `*Defaults` group) also means updating:
|
|
13
|
+
* - /packages/cli/assets/theme.template.ts (its inventory of what exists)
|
|
12
14
|
*
|
|
13
15
|
* Domain tokens (syntax highlighting, data visualization) live separately in
|
|
14
16
|
* /packages/core/src/theme/domainTokens/ — they're tree-shaken from core components.
|
package/locales/en.json
CHANGED
|
@@ -419,6 +419,66 @@
|
|
|
419
419
|
"defaultMessage": "Next",
|
|
420
420
|
"description": "Screen-reader-only label on the right-arrow button in a Lightbox (navigates to next media item). Pairs with `lightbox.previous`."
|
|
421
421
|
},
|
|
422
|
+
"@astryx.listInput.emptyTitle": {
|
|
423
|
+
"defaultMessage": "No {itemName}s yet",
|
|
424
|
+
"description": "EmptyState title shown inside a lab ListInput when its collection has no records. `{itemName}` is the consumer's singular noun for one record (e.g. \"guest\"); the source appends a literal \"s\" to pluralize it, which only works for regular English plurals. If your language cannot pluralize an interpolated noun this way, rephrase around `{itemName}` instead (e.g. \"No {itemName} added yet\")."
|
|
425
|
+
},
|
|
426
|
+
"@astryx.listInput.emptyDescription": {
|
|
427
|
+
"defaultMessage": "Add a {itemName} to get started.",
|
|
428
|
+
"description": "EmptyState supporting text shown inside a lab ListInput when its collection has no records. `{itemName}` is the consumer's singular noun for one record (e.g. \"guest\")."
|
|
429
|
+
},
|
|
430
|
+
"@astryx.listInput.addItem": {
|
|
431
|
+
"defaultMessage": "Add {itemName}",
|
|
432
|
+
"description": "Accessible label on the button that appends a new record to a lab ListInput. `{itemName}` is the consumer's singular noun for one record (e.g. \"guest\")."
|
|
433
|
+
},
|
|
434
|
+
"@astryx.listInput.removeItem": {
|
|
435
|
+
"defaultMessage": "Remove {itemName} {position, number}",
|
|
436
|
+
"description": "Accessible label and tooltip on the button that deletes one record from a lab ListInput. `{itemName}` is the consumer's singular noun for one record; `{position}` is its 1-based row number (e.g. \"Remove guest 2\")."
|
|
437
|
+
},
|
|
438
|
+
"@astryx.listInput.reorderItem": {
|
|
439
|
+
"defaultMessage": "Reorder {itemName} {position, number}",
|
|
440
|
+
"description": "Accessible label on the drag-handle button that reorders one record in a lab ListInput. `{itemName}` is the consumer's singular noun for one record; `{position}` is its 1-based row number (e.g. \"Reorder guest 2\")."
|
|
441
|
+
},
|
|
442
|
+
"@astryx.listInput.fieldLabelWithPosition": {
|
|
443
|
+
"defaultMessage": "{header}, {itemName} {position, number} of {total, number}",
|
|
444
|
+
"description": "Accessible name for a field inside a lab ListInput row after the first row, disambiguating repeated column labels. `{header}` is the column's own label (e.g. \"Name\"); `{itemName}` is the consumer's singular noun for one record; `{position}`/`{total}` are the row's 1-based index and the total row count (e.g. \"Name, guest 2 of 3\")."
|
|
445
|
+
},
|
|
446
|
+
"@astryx.listInput.reorderInstructions": {
|
|
447
|
+
"defaultMessage": "Use Arrow Up or Arrow Down to move this item one position. Press Space or Enter to pick it up for extended keyboard reordering.",
|
|
448
|
+
"description": "Visually-hidden instructions describing how to use a lab ListInput row's keyboard reorder handle, referenced via aria-describedby from every reorder button."
|
|
449
|
+
},
|
|
450
|
+
"@astryx.listInput.announceAdded": {
|
|
451
|
+
"defaultMessage": "Added {itemName} {position, number}.",
|
|
452
|
+
"description": "Screen-reader-only live announcement after a new record is appended to a lab ListInput. `{itemName}` is the consumer's singular noun for one record; `{position}` is the new record's 1-based row number."
|
|
453
|
+
},
|
|
454
|
+
"@astryx.listInput.announceRemoved": {
|
|
455
|
+
"defaultMessage": "Removed {itemName} {position, number}.",
|
|
456
|
+
"description": "Screen-reader-only live announcement after a record is deleted from a lab ListInput. `{itemName}` is the consumer's singular noun for one record; `{position}` is the removed record's former 1-based row number."
|
|
457
|
+
},
|
|
458
|
+
"@astryx.listInput.announceGrabbed": {
|
|
459
|
+
"defaultMessage": "{itemName} {position, number} grabbed. Use arrow keys to move, Space or Enter to drop, and Escape to cancel.",
|
|
460
|
+
"description": "Screen-reader-only live announcement when a lab ListInput record's keyboard reorder handle enters extended \"lift\" mode. `{itemName}` is the consumer's singular noun for one record; `{position}` is its 1-based row number."
|
|
461
|
+
},
|
|
462
|
+
"@astryx.listInput.announceMovedToPosition": {
|
|
463
|
+
"defaultMessage": "{itemName} moved to position {position, number} of {total, number}.",
|
|
464
|
+
"description": "Screen-reader-only live announcement each time a lab ListInput record's reorder position changes (arrow-key step, keyboard lift-mode preview, or pointer drag). `{itemName}` is the consumer's singular noun for one record; `{position}`/`{total}` are the record's new 1-based position and the total row count."
|
|
465
|
+
},
|
|
466
|
+
"@astryx.listInput.announceReorderCancelled": {
|
|
467
|
+
"defaultMessage": "Reordering cancelled.",
|
|
468
|
+
"description": "Screen-reader-only live announcement when a lab ListInput reorder in progress is cancelled (Escape key, blur, or the collection becoming disabled/loading mid-drag)."
|
|
469
|
+
},
|
|
470
|
+
"@astryx.listInput.announceReturnedToPosition": {
|
|
471
|
+
"defaultMessage": "{itemName} returned to position {position, number}.",
|
|
472
|
+
"description": "Screen-reader-only live announcement when a lab ListInput reorder is committed without the record's position actually changing. `{itemName}` is the consumer's singular noun for one record; `{position}` is its unchanged 1-based row number."
|
|
473
|
+
},
|
|
474
|
+
"@astryx.listInput.announceDropped": {
|
|
475
|
+
"defaultMessage": "{itemName} dropped at position {position, number} of {total, number}.",
|
|
476
|
+
"description": "Screen-reader-only live announcement when a lab ListInput reorder is committed with the record's position actually changing. `{itemName}` is the consumer's singular noun for one record; `{position}`/`{total}` are its new 1-based position and the total row count."
|
|
477
|
+
},
|
|
478
|
+
"@astryx.listInput.announceAlreadyAtBoundary": {
|
|
479
|
+
"defaultMessage": "This {itemName} is already {boundary, select, first {first} last {last} other {}}.",
|
|
480
|
+
"description": "Screen-reader-only live announcement when an arrow-key reorder attempt has no effect because the record is already at that end of the list. `{itemName}` is the consumer's singular noun for one record; `{boundary}` is always exactly \"first\" or \"last\"."
|
|
481
|
+
},
|
|
422
482
|
"@astryx.markdown.taskList": {
|
|
423
483
|
"defaultMessage": "Task list",
|
|
424
484
|
"description": "Screen-reader-only accessible name for a GitHub-flavored Markdown task list (rendered from `- [ ] item` / `- [x] done` syntax)."
|
|
@@ -643,6 +703,14 @@
|
|
|
643
703
|
"defaultMessage": "Invalid date",
|
|
644
704
|
"description": "Screen-reader-only live-region alert in DateInput / DateTimeInput when the typed date cannot be parsed. The field silently reverts on blur, so this is the only rejection feedback a screen-reader user gets."
|
|
645
705
|
},
|
|
706
|
+
"@astryx.dateTimeInput.timeHint12h": {
|
|
707
|
+
"defaultMessage": "e.g., 2:30 PM",
|
|
708
|
+
"description": "Placeholder hint shown in the DateTimeInput time field when focused and empty with 12-hour format. Gives an example of expected input format."
|
|
709
|
+
},
|
|
710
|
+
"@astryx.dateTimeInput.timeHint24h": {
|
|
711
|
+
"defaultMessage": "e.g., 14:30",
|
|
712
|
+
"description": "Placeholder hint shown in the DateTimeInput time field when focused and empty with 24-hour format. Gives an example of expected input format."
|
|
713
|
+
},
|
|
646
714
|
"@astryx.dateTimeInput.timeSuffix": {
|
|
647
715
|
"defaultMessage": "{label} time",
|
|
648
716
|
"description": "Screen-reader-only accessible name for the time-of-day slot in a DateTimeInput. Example: `Start date time`; reorder freely (e.g. \"time of {label}\")."
|
|
@@ -935,6 +1003,10 @@
|
|
|
935
1003
|
"defaultMessage": "Collapse {label}",
|
|
936
1004
|
"description": "Screen-reader-only label on the same chevron when the group is expanded. Example: `Collapse Settings`. Pairs with `sideNavItem.expand`."
|
|
937
1005
|
},
|
|
1006
|
+
"@astryx.sideNavItem.submenuLabel": {
|
|
1007
|
+
"defaultMessage": "{label} submenu",
|
|
1008
|
+
"description": "Accessible name for the flyout dialog that opens from a collapsed (icon-only) SideNav item to show its sub-items. {label} is the parent item's label. Example: `Settings submenu`."
|
|
1009
|
+
},
|
|
938
1010
|
"@astryx.tableFiltering.filterByColumn": {
|
|
939
1011
|
"defaultMessage": "Filter {header}",
|
|
940
1012
|
"description": "Triple-use string on per-column filter controls: visible label, placeholder, AND aria label. `{header}` is the column header — example: `Filter Name`. Keep short."
|
|
@@ -1050,5 +1122,17 @@
|
|
|
1050
1122
|
"@astryx.token.remove": {
|
|
1051
1123
|
"defaultMessage": "Remove {label}",
|
|
1052
1124
|
"description": "Screen-reader-only label on the \"×\" on an individual Token/chip. Example: `Remove John Smith`, `Remove Active`. Imperative verb."
|
|
1125
|
+
},
|
|
1126
|
+
"@astryx.commandPalette.footer.navigate": {
|
|
1127
|
+
"defaultMessage": "Navigate",
|
|
1128
|
+
"description": "Keyboard hint in the CommandPalette footer, paired with up/down arrow key icons. Tells users arrow keys move between items."
|
|
1129
|
+
},
|
|
1130
|
+
"@astryx.commandPalette.footer.select": {
|
|
1131
|
+
"defaultMessage": "Select",
|
|
1132
|
+
"description": "Keyboard hint in the CommandPalette footer, paired with the Enter key icon. Tells users Enter activates the highlighted item."
|
|
1133
|
+
},
|
|
1134
|
+
"@astryx.commandPalette.footer.close": {
|
|
1135
|
+
"defaultMessage": "Close",
|
|
1136
|
+
"description": "Keyboard hint in the CommandPalette footer, paired with the Escape key icon. Tells users Escape dismisses the palette."
|
|
1053
1137
|
}
|
|
1054
1138
|
}
|
package/locales/pseudo.json
CHANGED
|
@@ -314,6 +314,51 @@
|
|
|
314
314
|
"@astryx.lightbox.next": {
|
|
315
315
|
"defaultMessage": "⟦Ñéẋţ⟧"
|
|
316
316
|
},
|
|
317
|
+
"@astryx.listInput.emptyTitle": {
|
|
318
|
+
"defaultMessage": "⟦Ñó {itemName}š ýéţ⟧"
|
|
319
|
+
},
|
|
320
|
+
"@astryx.listInput.emptyDescription": {
|
|
321
|
+
"defaultMessage": "⟦Àðð à {itemName} ţó ĝéţ šţàřţéð.⟧"
|
|
322
|
+
},
|
|
323
|
+
"@astryx.listInput.addItem": {
|
|
324
|
+
"defaultMessage": "⟦Àðð {itemName}⟧"
|
|
325
|
+
},
|
|
326
|
+
"@astryx.listInput.removeItem": {
|
|
327
|
+
"defaultMessage": "⟦Řéɱóṽé {itemName} {position, number}⟧"
|
|
328
|
+
},
|
|
329
|
+
"@astryx.listInput.reorderItem": {
|
|
330
|
+
"defaultMessage": "⟦Řéóřðéř {itemName} {position, number}⟧"
|
|
331
|
+
},
|
|
332
|
+
"@astryx.listInput.fieldLabelWithPosition": {
|
|
333
|
+
"defaultMessage": "⟦{header}, {itemName} {position, number} óƒ {total, number}⟧"
|
|
334
|
+
},
|
|
335
|
+
"@astryx.listInput.reorderInstructions": {
|
|
336
|
+
"defaultMessage": "⟦Úšé Àřřóŵ Úþ óř Àřřóŵ Ðóŵñ ţó ɱóṽé ţĥíš íţéɱ óñé þóšíţíóñ. Þřéšš Šþàçé óř Éñţéř ţó þíçķ íţ úþ ƒóř éẋţéñðéð ķéýƀóàřð řéóřðéříñĝ.⟧"
|
|
337
|
+
},
|
|
338
|
+
"@astryx.listInput.announceAdded": {
|
|
339
|
+
"defaultMessage": "⟦Àððéð {itemName} {position, number}.⟧"
|
|
340
|
+
},
|
|
341
|
+
"@astryx.listInput.announceRemoved": {
|
|
342
|
+
"defaultMessage": "⟦Řéɱóṽéð {itemName} {position, number}.⟧"
|
|
343
|
+
},
|
|
344
|
+
"@astryx.listInput.announceGrabbed": {
|
|
345
|
+
"defaultMessage": "⟦{itemName} {position, number} ĝřàƀƀéð. Úšé àřřóŵ ķéýš ţó ɱóṽé, Šþàçé óř Éñţéř ţó ðřóþ, àñð Éšçàþé ţó çàñçéł.⟧"
|
|
346
|
+
},
|
|
347
|
+
"@astryx.listInput.announceMovedToPosition": {
|
|
348
|
+
"defaultMessage": "⟦{itemName} ɱóṽéð ţó þóšíţíóñ {position, number} óƒ {total, number}.⟧"
|
|
349
|
+
},
|
|
350
|
+
"@astryx.listInput.announceReorderCancelled": {
|
|
351
|
+
"defaultMessage": "⟦Řéóřðéříñĝ çàñçéłłéð.⟧"
|
|
352
|
+
},
|
|
353
|
+
"@astryx.listInput.announceReturnedToPosition": {
|
|
354
|
+
"defaultMessage": "⟦{itemName} řéţúřñéð ţó þóšíţíóñ {position, number}.⟧"
|
|
355
|
+
},
|
|
356
|
+
"@astryx.listInput.announceDropped": {
|
|
357
|
+
"defaultMessage": "⟦{itemName} ðřóþþéð àţ þóšíţíóñ {position, number} óƒ {total, number}.⟧"
|
|
358
|
+
},
|
|
359
|
+
"@astryx.listInput.announceAlreadyAtBoundary": {
|
|
360
|
+
"defaultMessage": "⟦Ţĥíš {itemName} íš àłřéàðý {boundary, select, first {first} last {last} other {}}.⟧"
|
|
361
|
+
},
|
|
317
362
|
"@astryx.markdown.taskList": {
|
|
318
363
|
"defaultMessage": "⟦Ţàšķ łíšţ⟧"
|
|
319
364
|
},
|
|
@@ -482,6 +527,12 @@
|
|
|
482
527
|
"@astryx.dateInput.invalidDate": {
|
|
483
528
|
"defaultMessage": "⟦Íñṽàłíð ðàţé⟧"
|
|
484
529
|
},
|
|
530
|
+
"@astryx.dateTimeInput.timeHint12h": {
|
|
531
|
+
"defaultMessage": "⟦é.ĝ., 2:30 ÞṀ⟧"
|
|
532
|
+
},
|
|
533
|
+
"@astryx.dateTimeInput.timeHint24h": {
|
|
534
|
+
"defaultMessage": "⟦é.ĝ., 14:30⟧"
|
|
535
|
+
},
|
|
485
536
|
"@astryx.dateTimeInput.timeSuffix": {
|
|
486
537
|
"defaultMessage": "⟦{label} ţíɱé⟧"
|
|
487
538
|
},
|
|
@@ -701,6 +752,9 @@
|
|
|
701
752
|
"@astryx.sideNavItem.collapse": {
|
|
702
753
|
"defaultMessage": "⟦Çółłàþšé {label}⟧"
|
|
703
754
|
},
|
|
755
|
+
"@astryx.sideNavItem.submenuLabel": {
|
|
756
|
+
"defaultMessage": "⟦{label} šúƀɱéñú⟧"
|
|
757
|
+
},
|
|
704
758
|
"@astryx.tableFiltering.filterByColumn": {
|
|
705
759
|
"defaultMessage": "⟦Ƒíłţéř {header}⟧"
|
|
706
760
|
},
|
|
@@ -787,5 +841,14 @@
|
|
|
787
841
|
},
|
|
788
842
|
"@astryx.token.remove": {
|
|
789
843
|
"defaultMessage": "⟦Řéɱóṽé {label}⟧"
|
|
844
|
+
},
|
|
845
|
+
"@astryx.commandPalette.footer.navigate": {
|
|
846
|
+
"defaultMessage": "⟦Ñàṽíĝàţé⟧"
|
|
847
|
+
},
|
|
848
|
+
"@astryx.commandPalette.footer.select": {
|
|
849
|
+
"defaultMessage": "⟦Šéłéçţ⟧"
|
|
850
|
+
},
|
|
851
|
+
"@astryx.commandPalette.footer.close": {
|
|
852
|
+
"defaultMessage": "⟦Çłóšé⟧"
|
|
790
853
|
}
|
|
791
854
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2-canary.464a445",
|
|
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",
|
|
@@ -654,6 +654,7 @@
|
|
|
654
654
|
"react-dom": ">=19.0.0"
|
|
655
655
|
},
|
|
656
656
|
"devDependencies": {
|
|
657
|
+
"@astryxdesign/cli": "0.4.2-canary.464a445",
|
|
657
658
|
"@babel/cli": "^8.0.4",
|
|
658
659
|
"@babel/core": "^7.29.7",
|
|
659
660
|
"@babel/preset-react": "^8.0.1",
|
|
@@ -662,8 +663,7 @@
|
|
|
662
663
|
"@testing-library/dom": "^10.0.0",
|
|
663
664
|
"@testing-library/jest-dom": "^6.6.0",
|
|
664
665
|
"@testing-library/react": "^16.3.2",
|
|
665
|
-
"rimraf": "^6.0.1"
|
|
666
|
-
"@astryxdesign/cli": "0.4.1"
|
|
666
|
+
"rimraf": "^6.0.1"
|
|
667
667
|
},
|
|
668
668
|
"dependencies": {
|
|
669
669
|
"intl-messageformat": "^11.2.9"
|
|
@@ -33,6 +33,9 @@ export const docs = {
|
|
|
33
33
|
{className: 'astryx-avatar-status-dot', visualProps: ['variant']},
|
|
34
34
|
{className: 'astryx-avatar-status-dot-glyph', visualProps: ['shape']},
|
|
35
35
|
],
|
|
36
|
+
vars: [
|
|
37
|
+
{name: '--_avatar-group-overlap', description: 'Negative inline offset applied to every avatar after the first when avatars are stacked in an AvatarGroup. Set from the group size; a more negative value tightens the stack.', default: 'set at runtime from the group avatar size (px)', private: true},
|
|
38
|
+
],
|
|
36
39
|
},
|
|
37
40
|
description: 'Displays a user avatar with image, initials fallback, and optional status indicator.',
|
|
38
41
|
props: [
|
|
@@ -59,7 +62,7 @@ export const docs = {
|
|
|
59
62
|
{
|
|
60
63
|
name: 'size',
|
|
61
64
|
type: "'xsm' | 'sm' | 'md' | 'lg' | 'xl' | number",
|
|
62
|
-
description: "Avatar size. Use a named size ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or a numeric pixel value. Avatar shares Icon's abbreviated scale, but its tiers are larger because avatars align with media rather than glyphs.",
|
|
65
|
+
description: "Avatar size. Use a named size ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or a numeric pixel value. Avatar shares Icon's abbreviated scale, but its tiers are larger because avatars align with media rather than glyphs. Inside an AvatarGroup the group's size wins and this prop is ignored.",
|
|
63
66
|
default: "'md'",
|
|
64
67
|
},
|
|
65
68
|
{
|
|
@@ -151,7 +154,7 @@ export const docsDense = {
|
|
|
151
154
|
fallbackSrc: 'fallback image when primary fails',
|
|
152
155
|
name: 'user name for initials and alt text',
|
|
153
156
|
alt: 'alt text; falls back to name',
|
|
154
|
-
size: "avatar size. Named ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or numeric px.",
|
|
157
|
+
size: "avatar size. Named ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or numeric px. An AvatarGroup's size overrides it.",
|
|
155
158
|
status:
|
|
156
159
|
'corner content for status indicators; its `label` is composed into the avatar accessible name ("Jane Doe, Online")',
|
|
157
160
|
tooltip:
|
|
@@ -65,6 +65,27 @@ describe('Avatar', () => {
|
|
|
65
65
|
expect(icon?.querySelector('svg')).not.toBeNull();
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
+
it('puts the avatar box on the element that carries the theme target', () => {
|
|
69
|
+
// T7: `.astryx-avatar` documents a `size` visual prop, so the width and
|
|
70
|
+
// height that prop selects on must live on the targeted element — a theme
|
|
71
|
+
// rule that resizes the target has to resize the whole avatar, not leave a
|
|
72
|
+
// fixed-size circle inside a grown box.
|
|
73
|
+
render(<Avatar name="Ada Lovelace" size="lg" data-testid="a" />);
|
|
74
|
+
const root = screen.getByTestId('a');
|
|
75
|
+
expect(root.className).toContain('astryx-avatar');
|
|
76
|
+
const rootStyle = root.getAttribute('style') ?? '';
|
|
77
|
+
expect(rootStyle).toContain('48px');
|
|
78
|
+
|
|
79
|
+
const content = root.firstElementChild as HTMLElement;
|
|
80
|
+
expect(content.getAttribute('style') ?? '').not.toContain('48px');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('keeps the box on the root for an interactive avatar too', () => {
|
|
84
|
+
render(<Avatar name="Ada" size="lg" href="/ada" />);
|
|
85
|
+
const link = screen.getByRole('link', {name: 'Ada'});
|
|
86
|
+
expect(link.getAttribute('style') ?? '').toContain('48px');
|
|
87
|
+
});
|
|
88
|
+
|
|
68
89
|
it('does not split an emoji surrogate pair when generating initials', () => {
|
|
69
90
|
render(<Avatar name="😀 Ada" data-testid="avatar" />);
|
|
70
91
|
expect(screen.getByTestId('avatar')).toHaveTextContent('😀A');
|
|
@@ -184,6 +205,36 @@ describe('Avatar', () => {
|
|
|
184
205
|
});
|
|
185
206
|
});
|
|
186
207
|
|
|
208
|
+
describe('a whitespace-only name carries no identity', () => {
|
|
209
|
+
it('falls through to the default icon instead of an empty plate', () => {
|
|
210
|
+
render(<Avatar name=" " data-testid="a" />);
|
|
211
|
+
const el = screen.getByTestId('a');
|
|
212
|
+
expect(el.querySelector('svg')).not.toBeNull();
|
|
213
|
+
expect(el).toHaveTextContent('');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('is decorative rather than a role="img" with a blank name', () => {
|
|
217
|
+
render(<Avatar name=" " data-testid="a" />);
|
|
218
|
+
const el = screen.getByTestId('a');
|
|
219
|
+
expect(el).toHaveAttribute('aria-hidden', 'true');
|
|
220
|
+
expect(el).not.toHaveAttribute('aria-label');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('keeps a meaningful alt as the accessible name and still shows the icon', () => {
|
|
224
|
+
render(<Avatar name=" " alt="Profile photo" data-testid="a" />);
|
|
225
|
+
const el = screen.getByRole('img', {name: 'Profile photo'});
|
|
226
|
+
expect(el).toBe(screen.getByTestId('a'));
|
|
227
|
+
expect(el.querySelector('svg')).not.toBeNull();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('treats a whitespace-only alt the same way', () => {
|
|
231
|
+
render(<Avatar alt=" " name="Ada Lovelace" data-testid="a" />);
|
|
232
|
+
expect(
|
|
233
|
+
screen.getByRole('img', {name: 'Ada Lovelace'}),
|
|
234
|
+
).toBeInTheDocument();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
187
238
|
it('retries a new fallbackSrc after a previous fallbackSrc failed to load', () => {
|
|
188
239
|
const {rerender} = render(
|
|
189
240
|
<Avatar name="Ada" fallbackSrc="https://example.com/broken.jpg" />,
|
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -33,6 +33,7 @@ import {mergeProps, mergeRefs} from '../utils';
|
|
|
33
33
|
import {themeProps} from '../utils/themeProps';
|
|
34
34
|
import {focusOutlineProps} from '../utils/focusOutline.stylex';
|
|
35
35
|
import {useTooltip} from '../Tooltip/useTooltip';
|
|
36
|
+
import {useDevWarning} from '../hooks/useDevWarning';
|
|
36
37
|
import {useLinkComponent} from '../Link/useLinkComponent';
|
|
37
38
|
import type {LinkComponentType} from '../Link/types';
|
|
38
39
|
import {useTranslator} from '../i18n';
|
|
@@ -112,16 +113,17 @@ const styles = stylex.create({
|
|
|
112
113
|
position: 'relative',
|
|
113
114
|
display: 'inline-flex',
|
|
114
115
|
flexShrink: 0,
|
|
115
|
-
// The wrapper
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
// behind the circular content.
|
|
116
|
+
// The wrapper carries the avatar's box as well as its radius, so a theme
|
|
117
|
+
// rule on the `.astryx-avatar` target reaches both: the size the `size`
|
|
118
|
+
// visual prop selects on is set here, and the content below fills it.
|
|
119
119
|
borderRadius: radiusVars['--radius-full'],
|
|
120
120
|
},
|
|
121
121
|
content: {
|
|
122
122
|
display: 'flex',
|
|
123
123
|
alignItems: 'center',
|
|
124
124
|
justifyContent: 'center',
|
|
125
|
+
width: '100%',
|
|
126
|
+
height: '100%',
|
|
125
127
|
borderRadius: radiusVars['--radius-full'],
|
|
126
128
|
overflow: 'hidden',
|
|
127
129
|
userSelect: 'none',
|
|
@@ -251,6 +253,9 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
|
|
|
251
253
|
/**
|
|
252
254
|
* The size of the avatar. A named size (`xsm` 20px, `sm` 24px, `md` 36px,
|
|
253
255
|
* `lg` 48px, `xl` 128px) or a specific pixel value.
|
|
256
|
+
*
|
|
257
|
+
* Inside an `AvatarGroup` the group's `size` wins: a group sizes its members
|
|
258
|
+
* uniformly, so this prop is ignored there.
|
|
254
259
|
* @default 'md'
|
|
255
260
|
*/
|
|
256
261
|
size?: AvatarSize;
|
|
@@ -397,7 +402,7 @@ function DefaultIcon({size}: {size: number}) {
|
|
|
397
402
|
* ```
|
|
398
403
|
* <Avatar src="/user.jpg" name="John Doe" />
|
|
399
404
|
* <Avatar name="Jane Smith" size="xl" />
|
|
400
|
-
* <Avatar src="/user.jpg" status={<
|
|
405
|
+
* <Avatar src="/user.jpg" status={<AvatarStatusDot variant="success" label="Online" />} />
|
|
401
406
|
* <Avatar name="jsmith" tooltip="Jane Smith, Staff Engineer" />
|
|
402
407
|
* <Avatar name="Jane" tooltip={false} />
|
|
403
408
|
* <Avatar src="/user.jpg" name="John Doe" href="/users/john" />
|
|
@@ -434,8 +439,13 @@ export function Avatar({
|
|
|
434
439
|
const showImage = src && erroredSrc !== src;
|
|
435
440
|
const showFallbackImage =
|
|
436
441
|
!showImage && fallbackSrc && erroredFallbackSrc !== fallbackSrc;
|
|
437
|
-
|
|
438
|
-
|
|
442
|
+
// A whitespace-only string carries no identity. Without this it produces no
|
|
443
|
+
// initials (getInitials trims to nothing) and no default icon (a space is
|
|
444
|
+
// truthy), leaving an empty plate behind a blank accessible name.
|
|
445
|
+
const meaningfulName = name?.trim() ? name : undefined;
|
|
446
|
+
const meaningfulAlt = alt?.trim() ? alt : undefined;
|
|
447
|
+
const showInitials = !showImage && !showFallbackImage && meaningfulName;
|
|
448
|
+
const showIcon = !showImage && !showFallbackImage && !meaningfulName;
|
|
439
449
|
|
|
440
450
|
// A meaningful accessible name comes from `alt`/`name`, composed with the
|
|
441
451
|
// status element's `label` when one is present ("Jane Doe, Online") — the
|
|
@@ -446,7 +456,7 @@ export function Avatar({
|
|
|
446
456
|
// it as `presentation`/`aria-hidden` rather than announcing a meaningless
|
|
447
457
|
// generic "Avatar" (obs-9).
|
|
448
458
|
const t = useTranslator();
|
|
449
|
-
const nameLabel =
|
|
459
|
+
const nameLabel = meaningfulAlt || meaningfulName;
|
|
450
460
|
const statusLabel = getStatusLabel(status);
|
|
451
461
|
const accessibleName =
|
|
452
462
|
nameLabel && statusLabel
|
|
@@ -471,7 +481,7 @@ export function Avatar({
|
|
|
471
481
|
? undefined
|
|
472
482
|
: typeof tooltip === 'string'
|
|
473
483
|
? tooltip
|
|
474
|
-
:
|
|
484
|
+
: meaningfulName;
|
|
475
485
|
const trimmedTooltip = tooltipContent?.trim();
|
|
476
486
|
const showTooltip = trimmedTooltip != null && trimmedTooltip !== '';
|
|
477
487
|
// Whether the tooltip text is a consumer-authored override (a custom string)
|
|
@@ -513,20 +523,19 @@ export function Avatar({
|
|
|
513
523
|
const LinkComponent = useLinkComponent(as);
|
|
514
524
|
|
|
515
525
|
// An interactive control with no accessible name is an unacceptable control
|
|
516
|
-
// name.
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
'
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
526
|
+
// name. `useDevWarning` is the shared guardrail: it warns once per mount
|
|
527
|
+
// rather than on every render, and compiles out of production builds.
|
|
528
|
+
useDevWarning(
|
|
529
|
+
'Avatar',
|
|
530
|
+
'an interactive avatar (with `href` or `onClick`) needs a meaningful ' +
|
|
531
|
+
'accessible name. Pass `alt` or `name`.',
|
|
532
|
+
isInteractive && !accessibleName,
|
|
533
|
+
);
|
|
525
534
|
|
|
526
535
|
// The inner visuals are identical across the static and interactive variants.
|
|
527
536
|
const visualContent = (
|
|
528
537
|
<>
|
|
529
|
-
<div {...stylex.props(styles.content
|
|
538
|
+
<div {...stylex.props(styles.content)}>
|
|
530
539
|
{showImage && (
|
|
531
540
|
<img
|
|
532
541
|
src={src}
|
|
@@ -552,7 +561,7 @@ export function Avatar({
|
|
|
552
561
|
dynamicStyles.fontSize(numericSize),
|
|
553
562
|
),
|
|
554
563
|
)}>
|
|
555
|
-
{getInitials(
|
|
564
|
+
{getInitials(meaningfulName)}
|
|
556
565
|
</div>
|
|
557
566
|
)}
|
|
558
567
|
{showIcon && (
|
|
@@ -585,6 +594,7 @@ export function Avatar({
|
|
|
585
594
|
themeProps('avatar', {size: resolvedSize}),
|
|
586
595
|
focusOutlineProps.focusVisible(
|
|
587
596
|
styles.wrapper,
|
|
597
|
+
dynamicStyles.size(numericSize),
|
|
588
598
|
isInteractive && styles.interactive,
|
|
589
599
|
avatarGroup && groupStyles.ring,
|
|
590
600
|
avatarGroup && groupStyles.overlap,
|
|
@@ -25,7 +25,7 @@ export const docs = {
|
|
|
25
25
|
theming: {
|
|
26
26
|
targets: [
|
|
27
27
|
{className: 'astryx-avatar-group', visualProps: ['size']},
|
|
28
|
-
{className: 'astryx-avatar-group-overflow'},
|
|
28
|
+
{className: 'astryx-avatar-group-overflow', visualProps: ['size']},
|
|
29
29
|
],
|
|
30
30
|
},
|
|
31
31
|
description: 'Stacked avatar display with overlapping layout and optional overflow indicator. Children are Avatar elements.',
|
|
@@ -104,6 +104,20 @@ describe('AvatarGroupOverflow', () => {
|
|
|
104
104
|
expect(overflow).toHaveTextContent('+5');
|
|
105
105
|
});
|
|
106
106
|
|
|
107
|
+
it('applies the group size class to the overflow chip', () => {
|
|
108
|
+
render(
|
|
109
|
+
<AvatarGroup size="lg">
|
|
110
|
+
<Avatar name="Alice" />
|
|
111
|
+
<AvatarGroupOverflow count={5} />
|
|
112
|
+
</AvatarGroup>,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const overflow = screen.getByLabelText('5 more');
|
|
116
|
+
expect(overflow.className).toContain('astryx-avatar-group-overflow');
|
|
117
|
+
expect(overflow.className).toContain('lg');
|
|
118
|
+
expect(overflow).toHaveAttribute('data-size', 'lg');
|
|
119
|
+
});
|
|
120
|
+
|
|
107
121
|
it('renders as button when onClick is provided', () => {
|
|
108
122
|
render(
|
|
109
123
|
<AvatarGroup>
|
|
@@ -146,6 +146,7 @@ export function AvatarGroupOverflow({
|
|
|
146
146
|
}: AvatarGroupOverflowProps): ReactNode {
|
|
147
147
|
const t = useTranslator();
|
|
148
148
|
const group = useAvatarGroup();
|
|
149
|
+
const size = group?.size ?? 'md';
|
|
149
150
|
const numericSize = group?.numericSize ?? 36;
|
|
150
151
|
const overlap = group?.overlap ?? 0;
|
|
151
152
|
|
|
@@ -162,7 +163,7 @@ export function AvatarGroupOverflow({
|
|
|
162
163
|
aria-label={label}
|
|
163
164
|
data-avatar-item=""
|
|
164
165
|
{...mergeProps(
|
|
165
|
-
themeProps('avatar-group-overflow'),
|
|
166
|
+
themeProps('avatar-group-overflow', {size}),
|
|
166
167
|
focusOutlineProps.focusVisible(
|
|
167
168
|
styles.base,
|
|
168
169
|
styles.button,
|
|
@@ -186,7 +187,7 @@ export function AvatarGroupOverflow({
|
|
|
186
187
|
{...rest}
|
|
187
188
|
aria-label={label}
|
|
188
189
|
{...mergeProps(
|
|
189
|
-
themeProps('avatar-group-overflow'),
|
|
190
|
+
themeProps('avatar-group-overflow', {size}),
|
|
190
191
|
stylex.props(
|
|
191
192
|
styles.base,
|
|
192
193
|
styles.overlap,
|
package/src/Button/Button.tsx
CHANGED
|
@@ -438,8 +438,10 @@ const loadingStyles = stylex.create({
|
|
|
438
438
|
*
|
|
439
439
|
* Layers always carry the native `popover` attribute (useLayer.tsx), and a
|
|
440
440
|
* popover is never an in-flow member — it is `display: none` until shown, then
|
|
441
|
-
* promoted to the top layer.
|
|
442
|
-
*
|
|
441
|
+
* promoted to the top layer. Context layers also retain an inert `<template>`
|
|
442
|
+
* marker so they can re-resolve their JSX position. Neither element is a group
|
|
443
|
+
* member, so "last member" is: no following element sibling besides those two
|
|
444
|
+
* pieces of layer infrastructure.
|
|
443
445
|
*
|
|
444
446
|
* Reading it the other way round — marking the *buttons* and testing for a
|
|
445
447
|
* marked sibling — is the trap: it silently reclassifies anything it doesn't
|
|
@@ -455,7 +457,7 @@ const loadingStyles = stylex.create({
|
|
|
455
457
|
* The leading edge still uses `:first-child` — a member's button always precedes
|
|
456
458
|
* its own layer, so the first button is genuinely `:first-child`.
|
|
457
459
|
*/
|
|
458
|
-
const IS_LAST_ITEM = ':not(:has(~ *:not([popover])))';
|
|
460
|
+
const IS_LAST_ITEM = ':not(:has(~ *:not([popover]):not(template)))';
|
|
459
461
|
|
|
460
462
|
const groupStyles = stylex.create({
|
|
461
463
|
horizontal: {
|
|
@@ -202,9 +202,9 @@ describe('ButtonGroup', () => {
|
|
|
202
202
|
// Trailing radius (issue #2508)
|
|
203
203
|
//
|
|
204
204
|
// The trailing end cap cannot be keyed off `:last-child`: members render an
|
|
205
|
-
// invisible layer AFTER their button (tooltip'd Button,
|
|
206
|
-
// useLayer renders
|
|
207
|
-
// slot. See IS_LAST_ITEM in Button.tsx.
|
|
205
|
+
// invisible layer infrastructure AFTER their button (tooltip'd Button,
|
|
206
|
+
// DropdownMenu), and useLayer renders a marker plus the layer inline when the
|
|
207
|
+
// host is safe, so they steal the slot. See IS_LAST_ITEM in Button.tsx.
|
|
208
208
|
//
|
|
209
209
|
// HOW THESE TESTS CATCH THE BUG
|
|
210
210
|
// jsdom applies no StyleX CSS, so a DOM-only test cannot prove which rule
|
|
@@ -219,7 +219,9 @@ describe('ButtonGroup', () => {
|
|
|
219
219
|
describe('trailing radius (#2508)', () => {
|
|
220
220
|
/** The group members, in DOM order (excludes invisible layer siblings). */
|
|
221
221
|
const items = (group: HTMLElement): Element[] =>
|
|
222
|
-
Array.from(
|
|
222
|
+
Array.from(
|
|
223
|
+
group.querySelectorAll(':scope > *:not([popover]):not(template)'),
|
|
224
|
+
);
|
|
223
225
|
|
|
224
226
|
// -- Compiled CSS, read from the source -----------------------------------
|
|
225
227
|
|
|
@@ -375,6 +377,7 @@ describe('ButtonGroup', () => {
|
|
|
375
377
|
// .stylex.ts file it compiles to a mangled selector like `[x13pbwiz]`
|
|
376
378
|
// that matches nothing in the DOM.
|
|
377
379
|
expect(selector).toContain('[popover]');
|
|
380
|
+
expect(selector).toContain('template');
|
|
378
381
|
}
|
|
379
382
|
},
|
|
380
383
|
);
|
package/src/Card/Card.doc.mjs
CHANGED
|
@@ -95,6 +95,8 @@ export const docs = {
|
|
|
95
95
|
],
|
|
96
96
|
vars: [
|
|
97
97
|
{name: '--_card-radius', description: 'Border radius of the card', default: 'var(--radius-container)', private: true},
|
|
98
|
+
{name: '--_card-elevation', description: 'Resting shadow of the card, set from the elevation prop. Composed into the card box-shadow list alongside --_card-ring rather than written as boxShadow directly, so a ring and an elevation can coexist.', default: '0 0 transparent', private: true},
|
|
99
|
+
{name: '--_card-ring', description: 'Inset ring drawn in the card box-shadow list. SelectableCard sets it to show selection without taking over the shadow.', default: '0 0 transparent', private: true},
|
|
98
100
|
],
|
|
99
101
|
derived: [
|
|
100
102
|
{property: 'borderRadius', vars: ['--_card-radius']},
|