@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.
Files changed (201) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/Avatar/Avatar.d.ts +4 -1
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +19 -23
  5. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  6. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
  7. package/dist/Button/Button.d.ts.map +1 -1
  8. package/dist/Button/Button.js +9 -7
  9. package/dist/Chat/ChatMessage.d.ts +7 -0
  10. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  11. package/dist/Chat/ChatMessageBubble.d.ts +13 -1
  12. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  13. package/dist/Chat/ChatMessageBubble.js +19 -1
  14. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  15. package/dist/CheckboxInput/CheckboxInput.js +5 -0
  16. package/dist/CommandPalette/CommandPaletteFooter.d.ts.map +1 -1
  17. package/dist/CommandPalette/CommandPaletteFooter.js +5 -3
  18. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  19. package/dist/DateTimeInput/DateTimeInput.js +2 -2
  20. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  21. package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -15
  22. package/dist/HoverCard/HoverCard.d.ts +1 -1
  23. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  24. package/dist/HoverCard/HoverCard.js +5 -13
  25. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  26. package/dist/HoverCard/useHoverCard.js +6 -5
  27. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  28. package/dist/InputGroup/groupStyles.js +7 -2
  29. package/dist/Layer/layerHost.d.ts +24 -0
  30. package/dist/Layer/layerHost.d.ts.map +1 -0
  31. package/dist/Layer/layerHost.js +79 -0
  32. package/dist/Layer/useLayer.d.ts +14 -6
  33. package/dist/Layer/useLayer.d.ts.map +1 -1
  34. package/dist/Layer/useLayer.js +158 -30
  35. package/dist/NavItem/navItemStyles.stylex.d.ts +17 -5
  36. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  37. package/dist/NavItem/navItemStyles.stylex.js +11 -5
  38. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  39. package/dist/RadioList/RadioListItem.js +5 -0
  40. package/dist/SideNav/SideNav.d.ts +7 -9
  41. package/dist/SideNav/SideNav.d.ts.map +1 -1
  42. package/dist/SideNav/SideNav.js +32 -5
  43. package/dist/SideNav/SideNavCollapseButton.d.ts +25 -9
  44. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  45. package/dist/SideNav/SideNavCollapseButton.js +39 -15
  46. package/dist/SideNav/SideNavCollapseContext.d.ts +21 -0
  47. package/dist/SideNav/SideNavCollapseContext.d.ts.map +1 -1
  48. package/dist/SideNav/SideNavCollapseContext.js +16 -2
  49. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  50. package/dist/SideNav/SideNavHeading.js +89 -32
  51. package/dist/SideNav/SideNavItem.d.ts +7 -2
  52. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  53. package/dist/SideNav/SideNavItem.js +119 -75
  54. package/dist/SideNav/SideNavSection.d.ts.map +1 -1
  55. package/dist/SideNav/SideNavSection.js +7 -16
  56. package/dist/SideNav/index.d.ts +1 -1
  57. package/dist/SideNav/index.d.ts.map +1 -1
  58. package/dist/Slider/Slider.d.ts.map +1 -1
  59. package/dist/Slider/Slider.js +56 -15
  60. package/dist/Switch/Switch.d.ts.map +1 -1
  61. package/dist/Switch/Switch.js +5 -0
  62. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  63. package/dist/Thumbnail/Thumbnail.js +5 -0
  64. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  65. package/dist/TopNav/TopNavHeading.js +14 -6
  66. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  67. package/dist/TopNav/TopNavMegaMenu.js +25 -93
  68. package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
  69. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  70. package/dist/TopNav/TopNavMenu.js +10 -5
  71. package/dist/astryx.css +40 -8
  72. package/dist/astryx.umd.js +50 -50
  73. package/dist/astryx.umd.js.map +4 -4
  74. package/dist/hooks/containerReveal.stylex.d.ts +71 -4
  75. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  76. package/dist/hooks/containerReveal.stylex.js +57 -5
  77. package/dist/hooks/index.d.ts +2 -2
  78. package/dist/hooks/index.d.ts.map +1 -1
  79. package/dist/hooks/index.js +1 -1
  80. package/dist/hooks/useContainerReveal.d.ts +60 -3
  81. package/dist/hooks/useContainerReveal.d.ts.map +1 -1
  82. package/dist/hooks/useContainerReveal.js +27 -5
  83. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  84. package/dist/hooks/useFocusTrap.js +16 -2
  85. package/dist/hooks/useMenuHover.d.ts +50 -5
  86. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  87. package/dist/hooks/useMenuHover.js +171 -51
  88. package/dist/theme/defineTheme.d.ts +16 -5
  89. package/dist/theme/defineTheme.d.ts.map +1 -1
  90. package/dist/theme/defineTheme.js +44 -48
  91. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  92. package/dist/theme/derivedVarRegistry.js +7 -0
  93. package/dist/theme/expandColorScale.d.ts.map +1 -1
  94. package/dist/theme/expandColorScale.js +1 -0
  95. package/dist/theme/expandMotionScale.d.ts +1 -0
  96. package/dist/theme/expandMotionScale.d.ts.map +1 -1
  97. package/dist/theme/expandMotionScale.js +1 -0
  98. package/dist/theme/expandRadiusScale.d.ts +1 -0
  99. package/dist/theme/expandRadiusScale.d.ts.map +1 -1
  100. package/dist/theme/expandRadiusScale.js +1 -0
  101. package/dist/theme/expandTypeScale.d.ts +1 -0
  102. package/dist/theme/expandTypeScale.d.ts.map +1 -1
  103. package/dist/theme/expandTypeScale.js +1 -0
  104. package/dist/theme/mergeComponents.d.ts +20 -0
  105. package/dist/theme/mergeComponents.d.ts.map +1 -0
  106. package/dist/theme/mergeComponents.js +56 -0
  107. package/dist/theme/onMediaTokens.d.ts +6 -1
  108. package/dist/theme/onMediaTokens.d.ts.map +1 -1
  109. package/dist/theme/onMediaTokens.js +11 -3
  110. package/dist/theme/tokens.stylex.d.ts +3 -1
  111. package/dist/theme/tokens.stylex.d.ts.map +1 -1
  112. package/dist/theme/tokens.stylex.js +3 -1
  113. package/locales/en.json +84 -0
  114. package/locales/pseudo.json +63 -0
  115. package/package.json +3 -3
  116. package/src/Avatar/Avatar.doc.mjs +5 -2
  117. package/src/Avatar/Avatar.test.tsx +51 -0
  118. package/src/Avatar/Avatar.tsx +30 -20
  119. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  120. package/src/AvatarGroup/AvatarGroup.test.tsx +14 -0
  121. package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -2
  122. package/src/Button/Button.tsx +5 -3
  123. package/src/ButtonGroup/ButtonGroup.test.tsx +7 -4
  124. package/src/Card/Card.doc.mjs +2 -0
  125. package/src/Chat/Chat.doc.mjs +4 -1
  126. package/src/Chat/ChatMessage.doc.mjs +3 -3
  127. package/src/Chat/ChatMessage.tsx +7 -0
  128. package/src/Chat/ChatMessageBubble.doc.mjs +7 -0
  129. package/src/Chat/ChatMessageBubble.test.tsx +95 -6
  130. package/src/Chat/ChatMessageBubble.tsx +25 -0
  131. package/src/CheckboxInput/CheckboxInput.tsx +20 -0
  132. package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
  133. package/src/CommandPalette/CommandPaletteFooter.tsx +6 -3
  134. package/src/DateTimeInput/DateTimeInput.tsx +4 -2
  135. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +72 -1
  136. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +24 -20
  137. package/src/HoverCard/HoverCard.doc.mjs +3 -3
  138. package/src/HoverCard/HoverCard.test.tsx +283 -55
  139. package/src/HoverCard/HoverCard.tsx +5 -13
  140. package/src/HoverCard/useHoverCard.tsx +8 -11
  141. package/src/InputGroup/groupStyles.ts +7 -8
  142. package/src/Item/Item.doc.mjs +4 -0
  143. package/src/Layer/layerHost.test.ts +99 -0
  144. package/src/Layer/layerHost.ts +141 -0
  145. package/src/Layer/useLayer.doc.mjs +14 -4
  146. package/src/Layer/useLayer.test.tsx +332 -7
  147. package/src/Layer/useLayer.tsx +235 -36
  148. package/src/MobileNav/MobileNav.doc.mjs +7 -0
  149. package/src/NavItem/navItemStyles.stylex.ts +31 -5
  150. package/src/RadioList/RadioListItem.tsx +20 -0
  151. package/src/SideNav/SideNav.doc.mjs +13 -4
  152. package/src/SideNav/SideNav.test.tsx +858 -2
  153. package/src/SideNav/SideNav.tsx +37 -16
  154. package/src/SideNav/SideNavCollapseButton.doc.mjs +28 -6
  155. package/src/SideNav/SideNavCollapseButton.tsx +67 -15
  156. package/src/SideNav/SideNavCollapseContext.ts +29 -9
  157. package/src/SideNav/SideNavHeading.tsx +53 -25
  158. package/src/SideNav/SideNavItem.doc.mjs +13 -0
  159. package/src/SideNav/SideNavItem.tsx +97 -84
  160. package/src/SideNav/SideNavSection.tsx +6 -20
  161. package/src/SideNav/index.ts +2 -0
  162. package/src/Slider/Slider.test.tsx +69 -33
  163. package/src/Slider/Slider.tsx +65 -13
  164. package/src/Switch/Switch.tsx +20 -0
  165. package/src/TabList/TabList.doc.mjs +3 -0
  166. package/src/Text/Text.doc.mjs +1 -1
  167. package/src/Thumbnail/Thumbnail.doc.mjs +3 -0
  168. package/src/Thumbnail/Thumbnail.tsx +10 -0
  169. package/src/Timestamp/Timestamp.test.tsx +40 -22
  170. package/src/Toolbar/Toolbar.test.tsx +14 -14
  171. package/src/TopNav/TopNav.test.tsx +86 -1
  172. package/src/TopNav/TopNavHeading.tsx +21 -14
  173. package/src/TopNav/TopNavMegaMenu.test.tsx +43 -0
  174. package/src/TopNav/TopNavMegaMenu.tsx +29 -105
  175. package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
  176. package/src/TopNav/TopNavMenu.test.tsx +41 -0
  177. package/src/TopNav/TopNavMenu.tsx +14 -4
  178. package/src/TreeList/TreeList.doc.mjs +1 -0
  179. package/src/hooks/containerReveal.stylex.ts +140 -9
  180. package/src/hooks/index.ts +6 -1
  181. package/src/hooks/useContainerReveal.doc.mjs +11 -5
  182. package/src/hooks/useContainerReveal.test.tsx +70 -3
  183. package/src/hooks/useContainerReveal.ts +86 -7
  184. package/src/hooks/useFocusTrap.test.tsx +16 -0
  185. package/src/hooks/useFocusTrap.ts +20 -2
  186. package/src/hooks/useMenuHover.test.tsx +364 -0
  187. package/src/hooks/useMenuHover.ts +243 -47
  188. package/src/theme/defineTheme.test.ts +127 -0
  189. package/src/theme/defineTheme.ts +57 -51
  190. package/src/theme/derivedVarRegistry.test.ts +78 -12
  191. package/src/theme/derivedVarRegistry.ts +4 -0
  192. package/src/theme/expandColorScale.ts +1 -0
  193. package/src/theme/expandMotionScale.ts +1 -0
  194. package/src/theme/expandRadiusScale.ts +1 -0
  195. package/src/theme/expandTypeScale.ts +1 -0
  196. package/src/theme/mergeComponents.ts +59 -0
  197. package/src/theme/onMediaTokens.ts +9 -2
  198. package/src/theme/themingTargets.test.ts +152 -26
  199. package/src/theme/tokens.stylex.ts +3 -1
  200. package/src/theme/tokens.test.ts +12 -0
  201. 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;AAEjE;;;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;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,GAAG,OAAO,EACzB,KAAK,CAAC,EAAE,gBAAgB,GACvB,eAAe,CAkBjB"}
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;;;;;;;;;;;;GAYG;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"}
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
  }
@@ -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.1",
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" />,
@@ -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 is not clipped (so the status dot can overflow), so it must be
116
- // rounded itself: a theme can set a background on the `.astryx-avatar`
117
- // wrapper, and an unrounded wrapper would show that fill as square corners
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={<OnlineIndicator />} />
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
- const showInitials = !showImage && !showFallbackImage && name;
438
- const showIcon = !showImage && !showFallbackImage && !name;
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 = alt || name;
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
- : name;
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. Warn in the same client-safe way sibling components do (Field,
517
- // Timestamp, Popover) — a plain `console.warn`, never gated on `process.env`
518
- // (which is not available on the client in this codebase).
519
- if (isInteractive && !accessibleName) {
520
- console.warn(
521
- 'Avatar: an interactive avatar (with `href` or `onClick`) needs a ' +
522
- 'meaningful accessible name. Pass `alt` or `name`.',
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, dynamicStyles.size(numericSize))}>
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(name)}
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.',