@astryxdesign/core 0.4.1 → 0.4.2-canary.356d2f9
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 +40 -8
- package/dist/astryx.umd.js +50 -50
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/containerReveal.stylex.d.ts +71 -4
- package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
- package/dist/hooks/containerReveal.stylex.js +57 -5
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useContainerReveal.d.ts +60 -3
- package/dist/hooks/useContainerReveal.d.ts.map +1 -1
- package/dist/hooks/useContainerReveal.js +27 -5
- 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 +16 -5
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +44 -48
- 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/mergeComponents.d.ts +20 -0
- package/dist/theme/mergeComponents.d.ts.map +1 -0
- package/dist/theme/mergeComponents.js +56 -0
- package/dist/theme/onMediaTokens.d.ts +6 -1
- package/dist/theme/onMediaTokens.d.ts.map +1 -1
- package/dist/theme/onMediaTokens.js +11 -3
- 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/containerReveal.stylex.ts +140 -9
- package/src/hooks/index.ts +6 -1
- package/src/hooks/useContainerReveal.doc.mjs +11 -5
- package/src/hooks/useContainerReveal.test.tsx +70 -3
- package/src/hooks/useContainerReveal.ts +86 -7
- 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.test.ts +127 -0
- package/src/theme/defineTheme.ts +57 -51
- 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/mergeComponents.ts +59 -0
- package/src/theme/onMediaTokens.ts +9 -2
- 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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Component style-map merging
|
|
3
|
+
*
|
|
4
|
+
* One merge rule for component overrides, shared by every layer that composes
|
|
5
|
+
* them: `extends` inheritance, generated type-scale rules, and the on-media
|
|
6
|
+
* (`onDark`/`onLight`) surfaces. Merging is per style key, so a child that
|
|
7
|
+
* restates one property of `button.base` keeps the rest of the base's.
|
|
8
|
+
*
|
|
9
|
+
* @input two ComponentStyleMaps — the base and the overrides that win
|
|
10
|
+
* @output a merged ComponentStyleMap
|
|
11
|
+
* @position packages/core/src/theme/mergeComponents.ts
|
|
12
|
+
*/
|
|
13
|
+
import type { ComponentStyleMap } from './defineTheme';
|
|
14
|
+
/**
|
|
15
|
+
* Deep-merge component style maps: `overrides` wins per style key, and every
|
|
16
|
+
* component and key the base declared that the overrides do not mention is
|
|
17
|
+
* carried through untouched.
|
|
18
|
+
*/
|
|
19
|
+
export declare function deepMergeComponents(base?: ComponentStyleMap, overrides?: ComponentStyleMap): ComponentStyleMap | undefined;
|
|
20
|
+
//# sourceMappingURL=mergeComponents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mergeComponents.d.ts","sourceRoot":"","sources":["../../src/theme/mergeComponents.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAErD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,CAAC,EAAE,iBAAiB,EACxB,SAAS,CAAC,EAAE,iBAAiB,GAC5B,iBAAiB,GAAG,SAAS,CAiC/B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Component style-map merging
|
|
5
|
+
*
|
|
6
|
+
* One merge rule for component overrides, shared by every layer that composes
|
|
7
|
+
* them: `extends` inheritance, generated type-scale rules, and the on-media
|
|
8
|
+
* (`onDark`/`onLight`) surfaces. Merging is per style key, so a child that
|
|
9
|
+
* restates one property of `button.base` keeps the rest of the base's.
|
|
10
|
+
*
|
|
11
|
+
* @input two ComponentStyleMaps — the base and the overrides that win
|
|
12
|
+
* @output a merged ComponentStyleMap
|
|
13
|
+
* @position packages/core/src/theme/mergeComponents.ts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Deep-merge component style maps: `overrides` wins per style key, and every
|
|
18
|
+
* component and key the base declared that the overrides do not mention is
|
|
19
|
+
* carried through untouched.
|
|
20
|
+
*/
|
|
21
|
+
export function deepMergeComponents(base, overrides) {
|
|
22
|
+
if (!base && !overrides) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (!base) {
|
|
26
|
+
return overrides;
|
|
27
|
+
}
|
|
28
|
+
if (!overrides) {
|
|
29
|
+
return base;
|
|
30
|
+
}
|
|
31
|
+
const result = {};
|
|
32
|
+
|
|
33
|
+
// Start with all base entries
|
|
34
|
+
for (const [component, rules] of Object.entries(base)) {
|
|
35
|
+
result[component] = {
|
|
36
|
+
...rules
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Merge overrides on top
|
|
41
|
+
for (const [component, rules] of Object.entries(overrides)) {
|
|
42
|
+
if (!result[component]) {
|
|
43
|
+
result[component] = {
|
|
44
|
+
...rules
|
|
45
|
+
};
|
|
46
|
+
} else {
|
|
47
|
+
for (const [key, styles] of Object.entries(rules)) {
|
|
48
|
+
result[component][key] = {
|
|
49
|
+
...result[component][key],
|
|
50
|
+
...styles
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
@@ -53,6 +53,11 @@ export declare const defaultOnLightTokens: Record<string, string>;
|
|
|
53
53
|
/**
|
|
54
54
|
* Resolve on-media overrides: merge user tokens with defaults,
|
|
55
55
|
* pass through component overrides.
|
|
56
|
+
*
|
|
57
|
+
* `base` is the already-resolved surface of a theme being extended. It sits
|
|
58
|
+
* between the defaults and this theme's own input, so a child theme inherits
|
|
59
|
+
* the surface customizations of the theme it extends instead of silently
|
|
60
|
+
* reverting them to the defaults.
|
|
56
61
|
*/
|
|
57
|
-
export declare function resolveOnMedia(surface: 'dark' | 'light', input?: OnMediaOverrides): ResolvedOnMedia;
|
|
62
|
+
export declare function resolveOnMedia(surface: 'dark' | 'light', input?: OnMediaOverrides, base?: ResolvedOnMedia): ResolvedOnMedia;
|
|
58
63
|
//# sourceMappingURL=onMediaTokens.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"onMediaTokens.d.ts","sourceRoot":"","sources":["../../src/theme/onMediaTokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"onMediaTokens.d.ts","sourceRoot":"","sources":["../../src/theme/onMediaTokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAC,UAAU,EAAE,iBAAiB,EAAC,MAAM,eAAe,CAAC;AAGjE;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAC7C,yDAAyD;IACzD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,gCAAgC;IAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,yDAAyD;IACzD,UAAU,CAAC,EAAE,iBAAiB,CAAC;CAChC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAStD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CASvD,CAAC;AAYF;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,OAAO,EACzB,KAAK,CAAC,EAAE,gBAAgB,EACxB,IAAI,CAAC,EAAE,eAAe,GACrB,eAAe,CAkBjB"}
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* `onDark`/`onLight` fields in `defineTheme()`.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { deepMergeComponents } from "./mergeComponents.js";
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* On-media theme overrides — same shape as the main theme but scoped
|
|
25
27
|
* to a surface luminance context.
|
|
@@ -71,11 +73,17 @@ function resolveValue(value) {
|
|
|
71
73
|
/**
|
|
72
74
|
* Resolve on-media overrides: merge user tokens with defaults,
|
|
73
75
|
* pass through component overrides.
|
|
76
|
+
*
|
|
77
|
+
* `base` is the already-resolved surface of a theme being extended. It sits
|
|
78
|
+
* between the defaults and this theme's own input, so a child theme inherits
|
|
79
|
+
* the surface customizations of the theme it extends instead of silently
|
|
80
|
+
* reverting them to the defaults.
|
|
74
81
|
*/
|
|
75
|
-
export function resolveOnMedia(surface, input) {
|
|
82
|
+
export function resolveOnMedia(surface, input, base) {
|
|
76
83
|
const defaults = surface === 'dark' ? defaultOnDarkTokens : defaultOnLightTokens;
|
|
77
84
|
const tokens = {
|
|
78
|
-
...defaults
|
|
85
|
+
...defaults,
|
|
86
|
+
...base?.tokens
|
|
79
87
|
};
|
|
80
88
|
if (input?.tokens) {
|
|
81
89
|
for (const [key, value] of Object.entries(input.tokens)) {
|
|
@@ -86,6 +94,6 @@ export function resolveOnMedia(surface, input) {
|
|
|
86
94
|
}
|
|
87
95
|
return {
|
|
88
96
|
tokens,
|
|
89
|
-
components: input?.components
|
|
97
|
+
components: deepMergeComponents(base?.components, input?.components)
|
|
90
98
|
};
|
|
91
99
|
}
|
|
@@ -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.356d2f9",
|
|
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.356d2f9",
|
|
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.',
|