@astryxdesign/core 0.4.5 → 0.4.6-canary.cea9ecd

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 (538) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/Avatar/Avatar.d.ts +10 -5
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +139 -30
  5. package/dist/Avatar/AvatarStatusDot.d.ts +2 -1
  6. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  7. package/dist/Avatar/AvatarStatusDot.js +30 -4
  8. package/dist/Avatar/AvatarStatusLabelContext.d.ts +39 -0
  9. package/dist/Avatar/AvatarStatusLabelContext.d.ts.map +1 -0
  10. package/dist/Avatar/AvatarStatusLabelContext.js +41 -0
  11. package/dist/AvatarGroup/AvatarGroup.d.ts +3 -1
  12. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.js +15 -9
  15. package/dist/Blockquote/Blockquote.d.ts +4 -3
  16. package/dist/Blockquote/Blockquote.d.ts.map +1 -1
  17. package/dist/Blockquote/Blockquote.js +9 -10
  18. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  19. package/dist/BottomSheet/BottomSheetPanel.js +21 -5
  20. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  21. package/dist/Breadcrumbs/BreadcrumbItem.js +47 -34
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +7 -7
  24. package/dist/Calendar/Calendar.d.ts.map +1 -1
  25. package/dist/Calendar/Calendar.js +21 -12
  26. package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
  27. package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
  28. package/dist/Calendar/styles.d.ts +2 -2
  29. package/dist/Calendar/styles.d.ts.map +1 -1
  30. package/dist/Calendar/styles.js +5 -5
  31. package/dist/Center/Center.d.ts +23 -1
  32. package/dist/Center/Center.d.ts.map +1 -1
  33. package/dist/Center/Center.js +12 -6
  34. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  35. package/dist/Chat/ChatComposer.js +3 -3
  36. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  37. package/dist/Chat/ChatComposerDrawer.js +2 -2
  38. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  39. package/dist/Chat/ChatToolCalls.js +2 -2
  40. package/dist/Chat/useChatDictation.d.ts +1 -1
  41. package/dist/Chat/useChatDictation.d.ts.map +1 -1
  42. package/dist/Chat/useSpeechRecognition.d.ts +1 -1
  43. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  44. package/dist/Chat/useSpeechRecognition.js +4 -3
  45. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  46. package/dist/Chat/useTriggerMenu.js +2 -2
  47. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  48. package/dist/CheckboxInput/CheckboxInput.js +2 -2
  49. package/dist/Citation/Citation.d.ts.map +1 -1
  50. package/dist/Citation/Citation.js +5 -5
  51. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  52. package/dist/ClickableCard/ClickableCard.js +4 -4
  53. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  54. package/dist/CodeBlock/CodeBlock.js +1 -1
  55. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  56. package/dist/Collapsible/Collapsible.js +2 -2
  57. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  58. package/dist/CommandPalette/CommandPaletteItem.js +3 -3
  59. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  60. package/dist/ComplexSelector/ComplexSelector.js +6 -6
  61. package/dist/DateInput/DateInput.d.ts +31 -2
  62. package/dist/DateInput/DateInput.d.ts.map +1 -1
  63. package/dist/DateInput/DateInput.js +90 -22
  64. package/dist/DateInput/MonthScroller.d.ts +35 -0
  65. package/dist/DateInput/MonthScroller.d.ts.map +1 -0
  66. package/dist/DateInput/MonthScroller.js +658 -0
  67. package/dist/DateInput/MonthYearWheels.d.ts +24 -0
  68. package/dist/DateInput/MonthYearWheels.d.ts.map +1 -0
  69. package/dist/DateInput/MonthYearWheels.js +114 -0
  70. package/dist/DateInput/TouchDateField.d.ts +10 -0
  71. package/dist/DateInput/TouchDateField.d.ts.map +1 -0
  72. package/dist/DateInput/TouchDateField.js +810 -0
  73. package/dist/DateInput/Wheel.d.ts +41 -0
  74. package/dist/DateInput/Wheel.d.ts.map +1 -0
  75. package/dist/DateInput/Wheel.js +315 -0
  76. package/dist/DateInput/monthGeometry.d.ts +65 -0
  77. package/dist/DateInput/monthGeometry.d.ts.map +1 -0
  78. package/dist/DateInput/monthGeometry.js +98 -0
  79. package/dist/DateInput/tokens.stylex.d.ts +63 -0
  80. package/dist/DateInput/tokens.stylex.d.ts.map +1 -0
  81. package/dist/DateInput/tokens.stylex.js +74 -0
  82. package/dist/DateInput/useOwnScrollGesture.d.ts +41 -0
  83. package/dist/DateInput/useOwnScrollGesture.d.ts.map +1 -0
  84. package/dist/DateInput/useOwnScrollGesture.js +202 -0
  85. package/dist/DateInput/usePointerDragScroll.d.ts +15 -0
  86. package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -0
  87. package/dist/DateInput/usePointerDragScroll.js +216 -0
  88. package/dist/DateInput/useScrollSettle.d.ts +26 -0
  89. package/dist/DateInput/useScrollSettle.d.ts.map +1 -0
  90. package/dist/DateInput/useScrollSettle.js +149 -0
  91. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  92. package/dist/DateRangeInput/DateRangeInput.js +17 -14
  93. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  94. package/dist/DateTimeInput/DateTimeInput.js +17 -14
  95. package/dist/Dialog/Dialog.d.ts.map +1 -1
  96. package/dist/Dialog/Dialog.js +3 -3
  97. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  98. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +2 -2
  99. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  100. package/dist/DropdownMenu/DropdownMenuItem.js +2 -2
  101. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  102. package/dist/DropdownMenu/DropdownMenuRadioItem.js +2 -2
  103. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  104. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -2
  105. package/dist/Field/FieldLabel.d.ts.map +1 -1
  106. package/dist/Field/FieldLabel.js +4 -4
  107. package/dist/Field/InputClearButton.d.ts.map +1 -1
  108. package/dist/Field/InputClearButton.js +6 -0
  109. package/dist/Field/inputStyles.stylex.d.ts +1 -1
  110. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  111. package/dist/Field/inputStyles.stylex.js +5 -5
  112. package/dist/FileInput/FileInput.d.ts.map +1 -1
  113. package/dist/FileInput/FileInput.js +6 -6
  114. package/dist/HoverCard/HoverCard.d.ts +13 -3
  115. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  116. package/dist/HoverCard/HoverCard.js +2 -0
  117. package/dist/HoverCard/index.d.ts +1 -1
  118. package/dist/HoverCard/index.d.ts.map +1 -1
  119. package/dist/HoverCard/useHoverCard.d.ts +17 -2
  120. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  121. package/dist/HoverCard/useHoverCard.js +78 -9
  122. package/dist/InputGroup/InputGroup.js +1 -1
  123. package/dist/Item/Item.d.ts +9 -1
  124. package/dist/Item/Item.d.ts.map +1 -1
  125. package/dist/Item/Item.js +52 -14
  126. package/dist/Layer/index.d.ts +4 -2
  127. package/dist/Layer/index.d.ts.map +1 -1
  128. package/dist/Layer/index.js +2 -0
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +3 -2
  131. package/dist/Layer/useTouchTrigger.d.ts +113 -0
  132. package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
  133. package/dist/Layer/useTouchTrigger.js +241 -0
  134. package/dist/Layout/container.stylex.d.ts +4 -2
  135. package/dist/Layout/container.stylex.d.ts.map +1 -1
  136. package/dist/Layout/container.stylex.js +19 -11
  137. package/dist/Layout/index.d.ts +1 -0
  138. package/dist/Layout/index.d.ts.map +1 -1
  139. package/dist/Layout/index.js +4 -0
  140. package/dist/Layout/padding.stylex.d.ts +315 -12
  141. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  142. package/dist/Layout/padding.stylex.js +389 -12
  143. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  144. package/dist/Lightbox/Lightbox.js +5 -4
  145. package/dist/Link/Link.d.ts.map +1 -1
  146. package/dist/Link/Link.js +6 -6
  147. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  148. package/dist/MetadataList/MetadataList.js +1 -1
  149. package/dist/MobileNav/MobileNav.d.ts +1 -0
  150. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  151. package/dist/MobileNav/MobileNav.js +24 -6
  152. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  153. package/dist/MultiSelector/MultiSelector.js +8 -8
  154. package/dist/NavItem/navItemStyles.stylex.d.ts +4 -4
  155. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  156. package/dist/NavItem/navItemStyles.stylex.js +4 -4
  157. package/dist/NavMenu/NavHeadingMenuItem.d.ts.map +1 -1
  158. package/dist/NavMenu/NavHeadingMenuItem.js +3 -3
  159. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  160. package/dist/NumberInput/NumberInput.js +36 -8
  161. package/dist/Outline/Outline.d.ts.map +1 -1
  162. package/dist/Outline/Outline.js +3 -3
  163. package/dist/Pagination/Pagination.d.ts.map +1 -1
  164. package/dist/Pagination/Pagination.js +2 -2
  165. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  166. package/dist/PowerSearch/PowerSearch.js +11 -12
  167. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchToken.js +3 -1
  169. package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
  170. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  171. package/dist/PowerSearch/formatFilterValue.js +23 -8
  172. package/dist/RadioList/RadioListItem.d.ts +2 -2
  173. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  174. package/dist/RadioList/RadioListItem.js +70 -40
  175. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  176. package/dist/Resizable/ResizeHandle.js +7 -7
  177. package/dist/Section/Section.d.ts +36 -1
  178. package/dist/Section/Section.d.ts.map +1 -1
  179. package/dist/Section/Section.js +12 -2
  180. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  181. package/dist/SegmentedControl/SegmentedControlItem.js +7 -2
  182. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  183. package/dist/SelectableCard/SelectableCard.js +3 -3
  184. package/dist/Selector/Selector.d.ts +26 -1
  185. package/dist/Selector/Selector.d.ts.map +1 -1
  186. package/dist/Selector/Selector.js +93 -20
  187. package/dist/Selector/SelectorOption.d.ts +14 -1
  188. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  189. package/dist/Selector/SelectorOption.js +6 -0
  190. package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
  191. package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
  192. package/dist/Selector/SelectorRowLayoutContext.js +27 -0
  193. package/dist/Selector/types.d.ts +1 -0
  194. package/dist/Selector/types.d.ts.map +1 -1
  195. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  196. package/dist/SideNav/SideNavHeading.js +4 -4
  197. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  198. package/dist/SideNav/SideNavItem.js +3 -3
  199. package/dist/Slider/Slider.d.ts.map +1 -1
  200. package/dist/Slider/Slider.js +7 -7
  201. package/dist/Stack/Stack.d.ts +23 -1
  202. package/dist/Stack/Stack.d.ts.map +1 -1
  203. package/dist/Stack/Stack.js +12 -6
  204. package/dist/Switch/Switch.d.ts.map +1 -1
  205. package/dist/Switch/Switch.js +2 -2
  206. package/dist/TabList/Tab.d.ts.map +1 -1
  207. package/dist/TabList/Tab.js +1 -1
  208. package/dist/TabList/TabMenu.d.ts.map +1 -1
  209. package/dist/TabList/TabMenu.js +3 -3
  210. package/dist/Table/TableRow.js +4 -4
  211. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  212. package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
  213. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  214. package/dist/Table/plugins/filtering/useTableFiltering.js +2 -2
  215. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  216. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +2 -2
  217. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  218. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -2
  219. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  220. package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
  221. package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
  222. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  223. package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
  224. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  225. package/dist/Table/plugins/tree/useTableTreeData.js +3 -3
  226. package/dist/TextArea/TextArea.js +1 -1
  227. package/dist/TextInput/TextInput.js +1 -1
  228. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  229. package/dist/Thumbnail/Thumbnail.js +3 -3
  230. package/dist/TimeInput/TimeInput.js +2 -2
  231. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  232. package/dist/Timestamp/Timestamp.js +6 -4
  233. package/dist/Timestamp/formatInstant.d.ts +3 -2
  234. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  235. package/dist/Timestamp/formatInstant.js +8 -8
  236. package/dist/Timestamp/tooltipEntries.d.ts +2 -1
  237. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
  238. package/dist/Timestamp/tooltipEntries.js +5 -3
  239. package/dist/Toast/Toast.d.ts +4 -3
  240. package/dist/Toast/Toast.d.ts.map +1 -1
  241. package/dist/Toast/Toast.js +10 -7
  242. package/dist/Token/Token.d.ts.map +1 -1
  243. package/dist/Token/Token.js +6 -6
  244. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  245. package/dist/Tokenizer/Tokenizer.js +1 -1
  246. package/dist/Tooltip/Tooltip.d.ts +16 -3
  247. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  248. package/dist/Tooltip/Tooltip.js +2 -0
  249. package/dist/Tooltip/index.d.ts +1 -1
  250. package/dist/Tooltip/index.d.ts.map +1 -1
  251. package/dist/Tooltip/useTooltip.d.ts +21 -2
  252. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  253. package/dist/Tooltip/useTooltip.js +71 -13
  254. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  255. package/dist/TopNav/TopNavHeading.js +4 -4
  256. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  257. package/dist/TopNav/TopNavItem.js +3 -3
  258. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  259. package/dist/TopNav/TopNavMegaMenu.js +2 -2
  260. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  261. package/dist/TopNav/TopNavMegaMenuItem.js +2 -2
  262. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  263. package/dist/TopNav/TopNavMenu.js +4 -4
  264. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  265. package/dist/TreeList/TreeListItem.js +7 -7
  266. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  267. package/dist/Typeahead/BaseTypeahead.js +2 -2
  268. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  269. package/dist/Typeahead/Typeahead.js +1 -1
  270. package/dist/astryx.css +124 -62
  271. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  272. package/dist/hooks/containerReveal.stylex.js +18 -18
  273. package/dist/hooks/scrollbarGutter.d.ts +28 -0
  274. package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
  275. package/dist/hooks/scrollbarGutter.js +103 -0
  276. package/dist/hooks/useAutoMediaMode.d.ts +31 -0
  277. package/dist/hooks/useAutoMediaMode.d.ts.map +1 -0
  278. package/dist/hooks/useAutoMediaMode.js +201 -0
  279. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  280. package/dist/hooks/useInputStatusIcon.js +1 -1
  281. package/dist/hooks/useListFocus.d.ts +5 -2
  282. package/dist/hooks/useListFocus.d.ts.map +1 -1
  283. package/dist/hooks/useListFocus.js +12 -6
  284. package/dist/hooks/useScrollLock.d.ts +4 -0
  285. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  286. package/dist/hooks/useScrollLock.js +13 -1
  287. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  288. package/dist/i18n/InternationalizationContext.js +2 -0
  289. package/dist/i18n/index.d.ts +6 -0
  290. package/dist/i18n/index.d.ts.map +1 -1
  291. package/dist/i18n/index.js +6 -0
  292. package/dist/i18n/useCollator.d.ts +12 -0
  293. package/dist/i18n/useCollator.d.ts.map +1 -0
  294. package/dist/i18n/useCollator.js +40 -0
  295. package/dist/i18n/useLocale.d.ts +3 -0
  296. package/dist/i18n/useLocale.d.ts.map +1 -0
  297. package/dist/i18n/useLocale.js +31 -0
  298. package/dist/theme/MediaTheme.d.ts +25 -5
  299. package/dist/theme/MediaTheme.d.ts.map +1 -1
  300. package/dist/theme/MediaTheme.js +31 -6
  301. package/dist/theme/defineTheme.d.ts +5 -0
  302. package/dist/theme/defineTheme.d.ts.map +1 -1
  303. package/dist/theme/defineTheme.js +5 -0
  304. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  305. package/dist/theme/derivedVarRegistry.js +7 -0
  306. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  307. package/dist/theme/generateThemeRules.js +66 -5
  308. package/dist/theme/index.d.ts +1 -1
  309. package/dist/theme/index.d.ts.map +1 -1
  310. package/dist/theme/syntax/index.d.ts +6 -0
  311. package/dist/theme/syntax/index.d.ts.map +1 -1
  312. package/dist/theme/syntax/index.js +7 -2
  313. package/dist/utils/dateParser.d.ts +4 -3
  314. package/dist/utils/dateParser.d.ts.map +1 -1
  315. package/dist/utils/dateParser.js +8 -8
  316. package/dist/utils/index.d.ts +1 -1
  317. package/dist/utils/index.d.ts.map +1 -1
  318. package/dist/utils/index.js +1 -1
  319. package/dist/utils/plainDate.d.ts +5 -2
  320. package/dist/utils/plainDate.d.ts.map +1 -1
  321. package/dist/utils/plainDate.js +20 -4
  322. package/locales/en.json +28 -0
  323. package/locales/pseudo.json +21 -0
  324. package/package.json +3 -3
  325. package/src/Avatar/Avatar.doc.mjs +10 -6
  326. package/src/Avatar/Avatar.test.tsx +219 -1
  327. package/src/Avatar/Avatar.tsx +179 -37
  328. package/src/Avatar/AvatarStatusDot.tsx +30 -4
  329. package/src/Avatar/AvatarStatusLabelContext.ts +47 -0
  330. package/src/AvatarGroup/AvatarGroup.doc.mjs +10 -6
  331. package/src/AvatarGroup/AvatarGroup.test.tsx +62 -0
  332. package/src/AvatarGroup/AvatarGroup.tsx +3 -1
  333. package/src/AvatarGroup/AvatarGroupOverflow.tsx +31 -8
  334. package/src/Banner/Banner.doc.mjs +2 -0
  335. package/src/Blockquote/Blockquote.doc.mjs +30 -7
  336. package/src/Blockquote/Blockquote.test.tsx +34 -3
  337. package/src/Blockquote/Blockquote.tsx +11 -9
  338. package/src/BottomSheet/BottomSheet.doc.mjs +3 -3
  339. package/src/BottomSheet/BottomSheet.test.tsx +24 -0
  340. package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
  341. package/src/BottomSheet/BottomSheetPanel.tsx +58 -3
  342. package/src/Breadcrumbs/BreadcrumbItem.tsx +36 -23
  343. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +26 -2
  344. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -0
  345. package/src/Button/Button.tsx +12 -9
  346. package/src/Calendar/Calendar.tsx +15 -9
  347. package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
  348. package/src/Calendar/styles.ts +8 -5
  349. package/src/Center/Center.doc.mjs +48 -0
  350. package/src/Center/Center.test.tsx +158 -0
  351. package/src/Center/Center.tsx +50 -9
  352. package/src/Chat/ChatComposer.tsx +11 -5
  353. package/src/Chat/ChatComposerDrawer.tsx +4 -1
  354. package/src/Chat/ChatToolCalls.tsx +13 -4
  355. package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
  356. package/src/Chat/useChatDictation.ts +1 -1
  357. package/src/Chat/useSpeechRecognition.ts +5 -3
  358. package/src/Chat/useTriggerMenu.tsx +4 -1
  359. package/src/CheckboxInput/CheckboxInput.tsx +5 -2
  360. package/src/Citation/Citation.test.tsx +6 -1
  361. package/src/Citation/Citation.tsx +11 -5
  362. package/src/ClickableCard/ClickableCard.tsx +7 -4
  363. package/src/CodeBlock/CodeBlock.tsx +4 -1
  364. package/src/Collapsible/Collapsible.tsx +5 -2
  365. package/src/CommandPalette/CommandPaletteItem.tsx +6 -3
  366. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
  367. package/src/ComplexSelector/ComplexSelector.tsx +14 -7
  368. package/src/DateInput/DateInput.test.tsx +17 -1
  369. package/src/DateInput/DateInput.tsx +93 -21
  370. package/src/DateInput/DateInputTouch.test.tsx +2582 -0
  371. package/src/DateInput/MonthScroller.tsx +878 -0
  372. package/src/DateInput/MonthYearWheels.tsx +147 -0
  373. package/src/DateInput/TouchDateField.tsx +1223 -0
  374. package/src/DateInput/Wheel.tsx +473 -0
  375. package/src/DateInput/monthGeometry.ts +118 -0
  376. package/src/DateInput/tokens.stylex.ts +99 -0
  377. package/src/DateInput/useOwnScrollGesture.ts +227 -0
  378. package/src/DateInput/usePointerDragScroll.ts +220 -0
  379. package/src/DateInput/useScrollSettle.ts +164 -0
  380. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  381. package/src/DateRangeInput/DateRangeInput.tsx +31 -13
  382. package/src/DateTimeInput/DateTimeInput.tsx +25 -9
  383. package/src/Dialog/Dialog.test.tsx +24 -0
  384. package/src/Dialog/Dialog.tsx +3 -0
  385. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -2
  386. package/src/DropdownMenu/DropdownMenuItem.tsx +5 -2
  387. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -2
  388. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -2
  389. package/src/Field/Field.doc.mjs +2 -0
  390. package/src/Field/FieldLabel.tsx +9 -3
  391. package/src/Field/InputClearButton.test.tsx +110 -3
  392. package/src/Field/InputClearButton.tsx +38 -0
  393. package/src/Field/inputStyles.stylex.ts +17 -13
  394. package/src/FileInput/FileInput.tsx +15 -8
  395. package/src/FormLayout/FormLayout.doc.mjs +1 -1
  396. package/src/HoverCard/HoverCard.doc.mjs +18 -0
  397. package/src/HoverCard/HoverCard.test.tsx +189 -0
  398. package/src/HoverCard/HoverCard.tsx +22 -2
  399. package/src/HoverCard/index.ts +1 -0
  400. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  401. package/src/HoverCard/useHoverCard.tsx +103 -7
  402. package/src/InputGroup/InputGroup.tsx +1 -1
  403. package/src/Item/Item.doc.mjs +1 -0
  404. package/src/Item/Item.test.tsx +50 -0
  405. package/src/Item/Item.tsx +54 -6
  406. package/src/Layer/index.ts +10 -0
  407. package/src/Layer/useLayer.tsx +13 -2
  408. package/src/Layer/useTouchTrigger.test.ts +70 -0
  409. package/src/Layer/useTouchTrigger.ts +366 -0
  410. package/src/Layout/Layout.doc.mjs +18 -0
  411. package/src/Layout/container.stylex.ts +11 -3
  412. package/src/Layout/index.ts +4 -0
  413. package/src/Layout/overlayPaddingReset.test.tsx +222 -0
  414. package/src/Layout/padding.stylex.ts +192 -12
  415. package/src/Lightbox/Lightbox.tsx +11 -3
  416. package/src/Link/Link.tsx +9 -6
  417. package/src/MetadataList/MetadataList.tsx +4 -1
  418. package/src/MobileNav/MobileNav.tsx +61 -5
  419. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
  420. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
  421. package/src/MultiSelector/MultiSelector.test.tsx +1 -1
  422. package/src/MultiSelector/MultiSelector.tsx +26 -10
  423. package/src/NavItem/navItemStyles.stylex.ts +7 -4
  424. package/src/NavMenu/NavHeadingMenuItem.tsx +6 -3
  425. package/src/NumberInput/NumberInput.doc.mjs +24 -0
  426. package/src/NumberInput/NumberInput.test.tsx +138 -0
  427. package/src/NumberInput/NumberInput.tsx +47 -8
  428. package/src/Outline/Outline.tsx +5 -2
  429. package/src/Pagination/Pagination.tsx +5 -2
  430. package/src/Popover/Popover.test.tsx +27 -1
  431. package/src/PowerSearch/PowerSearch.tsx +17 -8
  432. package/src/PowerSearch/PowerSearchToken.tsx +3 -0
  433. package/src/PowerSearch/formatFilterValue.test.ts +22 -2
  434. package/src/PowerSearch/formatFilterValue.ts +31 -6
  435. package/src/RadioList/RadioList.doc.mjs +1 -1
  436. package/src/RadioList/RadioList.test.tsx +170 -2
  437. package/src/RadioList/RadioListItem.tsx +88 -53
  438. package/src/Resizable/ResizeHandle.test.tsx +22 -0
  439. package/src/Resizable/ResizeHandle.tsx +25 -7
  440. package/src/Section/Section.doc.mjs +55 -0
  441. package/src/Section/Section.test.tsx +201 -4
  442. package/src/Section/Section.tsx +69 -0
  443. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  444. package/src/SegmentedControl/SegmentedControlItem.tsx +15 -3
  445. package/src/SelectableCard/SelectableCard.tsx +6 -3
  446. package/src/Selector/Selector.doc.mjs +16 -2
  447. package/src/Selector/Selector.test.tsx +525 -1
  448. package/src/Selector/Selector.tsx +180 -25
  449. package/src/Selector/SelectorOption.doc.mjs +7 -0
  450. package/src/Selector/SelectorOption.tsx +21 -0
  451. package/src/Selector/SelectorRowLayoutContext.ts +32 -0
  452. package/src/Selector/types.ts +4 -4
  453. package/src/SideNav/SideNavHeading.tsx +14 -5
  454. package/src/SideNav/SideNavItem.tsx +9 -3
  455. package/src/Slider/Slider.tsx +25 -6
  456. package/src/Stack/Stack.doc.mjs +72 -0
  457. package/src/Stack/Stack.test.tsx +156 -0
  458. package/src/Stack/Stack.tsx +50 -8
  459. package/src/Switch/Switch.tsx +5 -2
  460. package/src/TabList/Tab.tsx +4 -1
  461. package/src/TabList/TabMenu.tsx +9 -3
  462. package/src/Table/TableRow.tsx +4 -4
  463. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +6 -2
  464. package/src/Table/plugins/filtering/useTableFiltering.tsx +4 -1
  465. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +10 -3
  466. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +6 -3
  467. package/src/Table/plugins/sortable/useTableSortable.tsx +4 -1
  468. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
  469. package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
  470. package/src/Table/plugins/tree/useTableTreeData.tsx +10 -4
  471. package/src/TextArea/TextArea.doc.mjs +1 -1
  472. package/src/TextArea/TextArea.tsx +1 -1
  473. package/src/TextInput/TextInput.tsx +1 -1
  474. package/src/Thumbnail/Thumbnail.tsx +9 -3
  475. package/src/TimeInput/TimeInput.tsx +1 -1
  476. package/src/Timestamp/Timestamp.test.tsx +27 -7
  477. package/src/Timestamp/Timestamp.tsx +8 -4
  478. package/src/Timestamp/formatInstant.ts +9 -7
  479. package/src/Timestamp/tooltipEntries.test.ts +11 -6
  480. package/src/Timestamp/tooltipEntries.ts +6 -2
  481. package/src/Toast/Toast.tsx +9 -7
  482. package/src/Token/Token.tsx +14 -5
  483. package/src/Tokenizer/Tokenizer.tsx +4 -1
  484. package/src/Tooltip/Tooltip.doc.mjs +18 -0
  485. package/src/Tooltip/Tooltip.test.tsx +254 -1
  486. package/src/Tooltip/Tooltip.tsx +22 -2
  487. package/src/Tooltip/index.ts +1 -0
  488. package/src/Tooltip/useTooltip.doc.mjs +2 -0
  489. package/src/Tooltip/useTooltip.tsx +99 -21
  490. package/src/TopNav/TopNavHeading.tsx +13 -4
  491. package/src/TopNav/TopNavItem.tsx +6 -3
  492. package/src/TopNav/TopNavMegaMenu.tsx +5 -2
  493. package/src/TopNav/TopNavMegaMenuItem.tsx +5 -2
  494. package/src/TopNav/TopNavMenu.tsx +10 -4
  495. package/src/TreeList/TreeListItem.tsx +22 -7
  496. package/src/Typeahead/BaseTypeahead.tsx +5 -2
  497. package/src/Typeahead/Typeahead.tsx +4 -1
  498. package/src/hooks/containerReveal.stylex.ts +54 -18
  499. package/src/hooks/scrollbarGutter.test.ts +167 -0
  500. package/src/hooks/scrollbarGutter.ts +121 -0
  501. package/src/hooks/useAutoMediaMode.test.ts +109 -0
  502. package/src/hooks/useAutoMediaMode.ts +233 -0
  503. package/src/hooks/useInputStatusIcon.tsx +4 -1
  504. package/src/hooks/useListFocus.doc.mjs +2 -2
  505. package/src/hooks/useListFocus.test.tsx +65 -3
  506. package/src/hooks/useListFocus.ts +15 -7
  507. package/src/hooks/useScrollLock.doc.mjs +4 -4
  508. package/src/hooks/useScrollLock.test.ts +73 -0
  509. package/src/hooks/useScrollLock.ts +14 -0
  510. package/src/i18n/InternationalizationContext.ts +2 -0
  511. package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
  512. package/src/i18n/__tests__/useCollator.test.tsx +115 -0
  513. package/src/i18n/__tests__/useLocale.test.tsx +52 -0
  514. package/src/i18n/index.ts +6 -0
  515. package/src/i18n/useCollator.doc.mjs +84 -0
  516. package/src/i18n/useCollator.ts +44 -0
  517. package/src/i18n/useLocale.doc.mjs +70 -0
  518. package/src/i18n/useLocale.ts +34 -0
  519. package/src/i18n/useTranslator.doc.mjs +1 -1
  520. package/src/reset.css +9 -2
  521. package/src/reset.test.ts +22 -0
  522. package/src/theme/MediaTheme.doc.mjs +23 -4
  523. package/src/theme/MediaTheme.dom.test.tsx +97 -0
  524. package/src/theme/MediaTheme.tsx +42 -7
  525. package/src/theme/defineTheme.test.ts +9 -3
  526. package/src/theme/defineTheme.ts +5 -0
  527. package/src/theme/derivedVarRegistry.test.ts +19 -5
  528. package/src/theme/derivedVarRegistry.ts +4 -0
  529. package/src/theme/generateThemeRules.test.ts +118 -0
  530. package/src/theme/generateThemeRules.ts +83 -5
  531. package/src/theme/index.ts +1 -1
  532. package/src/theme/syntax/index.ts +6 -2
  533. package/src/theme/syntax/serverSafeSyntax.test.ts +68 -0
  534. package/src/utils/dateParser.test.ts +20 -0
  535. package/src/utils/dateParser.ts +14 -7
  536. package/src/utils/index.ts +2 -0
  537. package/src/utils/plainDate.test.ts +6 -0
  538. package/src/utils/plainDate.ts +21 -4
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file Avatar.tsx
7
- * @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
7
+ * @input Uses React, HTMLAttributes, ReactNode, useState, useRef; useTooltip
8
8
  * (Tooltip hook) for the optional name-on-hover tooltip; useTranslator (i18n)
9
9
  * @output Exports Avatar component, AvatarProps, AvatarSize types
10
10
  * @position Core implementation; consumed by index.ts
@@ -12,13 +12,14 @@
12
12
  * SYNC: When modified, update these files to stay in sync:
13
13
  * - /packages/core/src/Avatar/Avatar.doc.mjs (props table, features, implementation notes)
14
14
  * - /packages/core/src/Avatar/index.ts (exports if types change)
15
+ * - /packages/core/src/Avatar/AvatarStatusLabelContext.ts (the status label ref)
15
16
  * - /apps/storybook/stories/Avatar.stories.tsx (storybook stories)
16
17
  * - /packages/cli/assets/templates/blocks/components/Avatar/ (showcase blocks)
17
18
  *
18
19
  * Last synced props: alt, fallbackSrc, name, size, src, status, href, as, target, rel, onClick
19
20
  */
20
21
 
21
- import {isValidElement, useMemo, useState, type ReactNode} from 'react';
22
+ import {isValidElement, useMemo, useRef, useState, type ReactNode} from 'react';
22
23
  import type {BaseProps} from '../BaseProps';
23
24
  import * as stylex from '@stylexjs/stylex';
24
25
  import {
@@ -28,6 +29,10 @@ import {
28
29
  radiusVars,
29
30
  } from '../theme/tokens.stylex';
30
31
  import {AvatarSizeContext} from './AvatarSizeContext';
32
+ import {
33
+ AvatarStatusLabelContext,
34
+ type AvatarStatusLabelTarget,
35
+ } from './AvatarStatusLabelContext';
31
36
  import {useAvatarGroup} from '../AvatarGroup/AvatarGroupContext';
32
37
  import {mergeProps, mergeRefs} from '../utils';
33
38
  import {themeProps} from '../utils/themeProps';
@@ -37,7 +42,7 @@ import {useTooltip} from '../Tooltip/useTooltip';
37
42
  import {useDevWarning} from '../hooks/useDevWarning';
38
43
  import {useLinkComponent} from '../Link/useLinkComponent';
39
44
  import type {LinkComponentType} from '../Link/types';
40
- import {useTranslator} from '../i18n';
45
+ import {useTranslator, type TranslatorFn} from '../i18n';
41
46
 
42
47
  /**
43
48
  * The offset ratio for positioning elements on a circle's edge at 45°.
@@ -170,7 +175,10 @@ const styles = stylex.create({
170
175
  color: 'inherit',
171
176
  font: 'inherit',
172
177
  textDecoration: 'none',
173
- cursor: 'pointer',
178
+ cursor: {
179
+ default: 'pointer',
180
+ ':is(:disabled,[aria-disabled="true"])': 'default',
181
+ },
174
182
  // Match the avatar's circular shape so the focus ring hugs it.
175
183
  borderRadius: radiusVars['--radius-full'],
176
184
  },
@@ -268,10 +276,14 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
268
276
  * Content displayed in the corner of the avatar.
269
277
  * Typically used for status indicators or badges.
270
278
  *
271
- * When the element carries a string `label` prop (as `AvatarStatusDot`
272
- * does), the label is composed into the avatar's accessible name
273
- * (e.g. "Jane Doe, Online") so assistive tech can reach the status —
274
- * the `role="img"` root prunes descendant semantics (WCAG 4.1.2).
279
+ * `AvatarStatusDot` reports its own `label` to the avatar, which composes
280
+ * it into the avatar's accessible name (e.g. "Jane Doe, Online") so
281
+ * assistive tech can reach the status: the `role="img"` root prunes
282
+ * descendant semantics (WCAG 4.1.2). Reporting goes through context, so it
283
+ * still works when the dot sits inside a wrapper component of your own. A
284
+ * custom status element that is not an `AvatarStatusDot` names itself the
285
+ * same way, by rendering an `AvatarStatusDot` or by carrying a string
286
+ * `label` prop.
275
287
  */
276
288
  status?: ReactNode;
277
289
  /**
@@ -342,13 +354,13 @@ function getInitials(name: string): string {
342
354
 
343
355
  /**
344
356
  * Reads the accessible status label off the `status` element, when it
345
- * exposes one. `AvatarStatusDot`'s `label` prop is the canonical source,
346
- * but any custom status element with a string `label` prop participates.
357
+ * exposes one.
347
358
  *
348
- * The avatar root is `role="img"`, which prunes ALL descendant semantics
349
- * from the accessibility tree — a label inside the status subtree is never
350
- * announced on its own. Composing it into the avatar's own accessible name
351
- * is the only way the status reaches assistive tech (WCAG 4.1.2).
359
+ * Only sees a literal `label` prop on the element the consumer passes, so a
360
+ * consumer's own wrapper around the dot hides it — that case comes in through
361
+ * `AvatarStatusLabelContext` instead. Kept because it answers the first
362
+ * render, before any report has landed, and because it tracks a label that
363
+ * changes on an element that does not report at all.
352
364
  */
353
365
  function getStatusLabel(status: ReactNode): string | undefined {
354
366
  if (!isValidElement(status)) {
@@ -358,6 +370,68 @@ function getStatusLabel(status: ReactNode): string | undefined {
358
370
  return typeof label === 'string' && label !== '' ? label : undefined;
359
371
  }
360
372
 
373
+ /**
374
+ * The string when it carries something, `undefined` when it is absent or
375
+ * blank. An empty accessible name is meaningless, so a blank reads as
376
+ * absent, the way Icon treats `label=""`.
377
+ */
378
+ function meaningful(value: unknown): string | undefined {
379
+ return typeof value === 'string' && value.trim() !== '' ? value : undefined;
380
+ }
381
+
382
+ /**
383
+ * Derives the root's ARIA attributes from the resolved accessible name,
384
+ * following Icon's `getIconA11yProps`.
385
+ *
386
+ * - A name → meaningful image: `role="img"` + `aria-label`.
387
+ * - No name → decorative: `role="presentation"` + `aria-hidden="true"`, rather
388
+ * than announcing a meaningless generic "Avatar" (obs-9).
389
+ *
390
+ * The result is spread BEFORE `{...props}` on every root so an explicit
391
+ * `aria-label` / `role` / `aria-hidden` from the consumer always wins.
392
+ */
393
+ function getAvatarA11yProps(
394
+ accessibleName: string | undefined,
395
+ ):
396
+ | {role: 'img'; 'aria-label': string}
397
+ | {role: 'presentation'; 'aria-hidden': 'true'} {
398
+ return accessibleName != null && accessibleName !== ''
399
+ ? {role: 'img', 'aria-label': accessibleName}
400
+ : {role: 'presentation', 'aria-hidden': 'true'};
401
+ }
402
+
403
+ /**
404
+ * The avatar's accessible name: its own name, the status label, or the two
405
+ * composed ("Jane Doe, Online"). Used both by render and by the commit-phase
406
+ * update, so the two can never compose differently.
407
+ */
408
+ function composeAccessibleName(
409
+ t: TranslatorFn,
410
+ nameLabel: string | undefined,
411
+ statusLabel: string | undefined,
412
+ ): string | undefined {
413
+ if (nameLabel && statusLabel) {
414
+ return t('@astryx.avatar.nameWithStatus', {
415
+ name: nameLabel,
416
+ status: statusLabel,
417
+ });
418
+ }
419
+ return nameLabel || statusLabel;
420
+ }
421
+
422
+ /** Sets an attribute, or removes it when the value is absent. */
423
+ function setAttributeOrRemove(
424
+ element: HTMLElement,
425
+ name: string,
426
+ value: string | undefined,
427
+ ): void {
428
+ if (value == null) {
429
+ element.removeAttribute(name);
430
+ } else {
431
+ element.setAttribute(name, value);
432
+ }
433
+ }
434
+
361
435
  /**
362
436
  * Default person icon SVG for when no image or name is provided
363
437
  */
@@ -424,8 +498,8 @@ export function Avatar({
424
498
  // A whitespace-only string carries no identity. Without this it produces no
425
499
  // initials (getInitials trims to nothing) and no default icon (a space is
426
500
  // truthy), leaving an empty plate behind a blank accessible name.
427
- const meaningfulName = name?.trim() ? name : undefined;
428
- const meaningfulAlt = alt?.trim() ? alt : undefined;
501
+ const meaningfulName = meaningful(name);
502
+ const meaningfulAlt = meaningful(alt);
429
503
  const showInitials = !showImage && !showFallbackImage && meaningfulName;
430
504
  const showIcon = !showImage && !showFallbackImage && !meaningfulName;
431
505
 
@@ -439,15 +513,21 @@ export function Avatar({
439
513
  // generic "Avatar" (obs-9).
440
514
  const t = useTranslator();
441
515
  const nameLabel = meaningfulAlt || meaningfulName;
442
- const statusLabel = getStatusLabel(status);
443
- const accessibleName =
444
- nameLabel && statusLabel
445
- ? t('@astryx.avatar.nameWithStatus', {
446
- name: nameLabel,
447
- status: statusLabel,
448
- })
449
- : nameLabel || statusLabel;
450
- const isDecorative = !accessibleName;
516
+ // This render can only see a label introspected off a directly-passed
517
+ // element. A status inside a consumer's own wrapper reports through the ref
518
+ // below, in the commit phase, and lands on the root element from there.
519
+ const accessibleName = composeAccessibleName(
520
+ t,
521
+ nameLabel,
522
+ getStatusLabel(status),
523
+ );
524
+ const a11yProps = getAvatarA11yProps(accessibleName);
525
+ // An `<a>`/`<button>` root carries its own role and must not be hidden, so
526
+ // only the name transfers. Spread before `{...props}` for the same
527
+ // consumer-wins precedence as the static root.
528
+ const a11yLabelProps = accessibleName
529
+ ? {'aria-label': accessibleName}
530
+ : undefined;
451
531
  const avatarGroup = useAvatarGroup();
452
532
  const resolvedSize = avatarGroup?.size ?? size;
453
533
  const numericSize = useMemo(() => resolveSize(resolvedSize), [resolvedSize]);
@@ -484,8 +564,8 @@ export function Avatar({
484
564
  isEnabled: showTooltip,
485
565
  });
486
566
  // The tooltip ref attaches to whichever root element renders (static or
487
- // interactive), so the tooltip works for link/button avatars too.
488
- const rootRef = mergeRefs(ref, showTooltip ? tooltipHook.ref : undefined);
567
+ // interactive), so the tooltip works for link/button avatars too. The root
568
+ // ref itself is assembled below, once the element swap is resolved.
489
569
  const describedByProp =
490
570
  showTooltip && isCustomTooltip
491
571
  ? {
@@ -504,14 +584,74 @@ export function Avatar({
504
584
  const isInteractive = renderAsLink || renderAsButton;
505
585
  const LinkComponent = useLinkComponent(as);
506
586
 
507
- // An interactive control with no accessible name is an unacceptable control
508
- // name. `useDevWarning` is the shared guardrail: it warns once per mount
509
- // rather than on every render, and compiles out of production builds.
587
+ // An interactive control needs an identity of its own. A status label is not
588
+ // one: it composes into the accessible name, so `<Avatar href status={<dot
589
+ // label="Online" />} />` resolves a name that reads as legitimate and says
590
+ // nothing about who the link points at. Only `alt`/`name` count, or a
591
+ // consumer's own `aria-label`/`aria-labelledby` escape hatch, which wins
592
+ // over the derived props below.
593
+ const consumerName =
594
+ meaningful(props['aria-label']) ?? meaningful(props['aria-labelledby']);
510
595
  useDevWarning(
511
596
  'Avatar',
512
597
  'an interactive avatar (with `href` or `onClick`) needs a meaningful ' +
513
598
  'accessible name. Pass `alt` or `name`.',
514
- isInteractive && !accessibleName,
599
+ isInteractive && !nameLabel && consumerName == null,
600
+ );
601
+
602
+ // A status inside a consumer's own wrapper is invisible to render: the dot
603
+ // writes its label into this ref from its own callback ref, in the commit
604
+ // phase. A ref write cannot re-render, so the composed name is written
605
+ // straight onto the root element instead — still before paint, and with no
606
+ // second render.
607
+ const statusLabelRef = useRef<AvatarStatusLabelTarget>({
608
+ label: undefined,
609
+ update: null,
610
+ });
611
+ // A fresh callback ref every render, so React reattaches it on every commit
612
+ // and the name is recomposed after React has written this render's props.
613
+ // `update` covers the other direction: a label that changes while the avatar
614
+ // itself does not re-render.
615
+ const nameRef = (element: HTMLElement | null) => {
616
+ if (element == null) {
617
+ return;
618
+ }
619
+ const target = statusLabelRef.current;
620
+ target.update = () => {
621
+ const composed = composeAccessibleName(
622
+ t,
623
+ nameLabel,
624
+ meaningful(target.label) ?? getStatusLabel(status),
625
+ );
626
+ // A consumer's own ARIA wins here exactly as it does in render, where
627
+ // the derived props spread before `{...props}`.
628
+ if (props['aria-label'] == null) {
629
+ setAttributeOrRemove(element, 'aria-label', composed);
630
+ }
631
+ // An `<a>`/`<button>` root carries its own role and must not be hidden,
632
+ // so only the name transfers there.
633
+ if (!isInteractive) {
634
+ if (props.role == null) {
635
+ element.setAttribute('role', composed ? 'img' : 'presentation');
636
+ }
637
+ if (props['aria-hidden'] == null) {
638
+ setAttributeOrRemove(
639
+ element,
640
+ 'aria-hidden',
641
+ composed ? undefined : 'true',
642
+ );
643
+ }
644
+ }
645
+ };
646
+ target.update();
647
+ return () => {
648
+ target.update = null;
649
+ };
650
+ };
651
+ const rootRef = mergeRefs(
652
+ ref,
653
+ showTooltip ? tooltipHook.ref : undefined,
654
+ nameRef,
515
655
  );
516
656
 
517
657
  // The inner visuals are identical across the static and interactive variants.
@@ -601,13 +741,13 @@ export function Avatar({
601
741
  // it up while ignoring nested buttons in a custom status/badge slot.
602
742
  rootElement = (
603
743
  <LinkComponent
744
+ {...a11yLabelProps}
604
745
  {...interactivePassthrough}
605
746
  {...describedByProp}
606
747
  ref={rootRef as React.Ref<HTMLAnchorElement>}
607
748
  href={href}
608
749
  target={target}
609
750
  rel={rel}
610
- aria-label={accessibleName}
611
751
  data-avatar-item=""
612
752
  data-testid={testId}
613
753
  onClick={onClick}
@@ -618,11 +758,11 @@ export function Avatar({
618
758
  } else if (renderAsButton) {
619
759
  rootElement = (
620
760
  <button
761
+ {...a11yLabelProps}
621
762
  {...interactivePassthrough}
622
763
  {...describedByProp}
623
764
  ref={rootRef}
624
765
  type="button"
625
- aria-label={accessibleName}
626
766
  data-avatar-item=""
627
767
  data-testid={testId}
628
768
  onClick={onClick}
@@ -633,11 +773,9 @@ export function Avatar({
633
773
  } else {
634
774
  rootElement = (
635
775
  <div
776
+ {...a11yProps}
636
777
  {...props}
637
778
  ref={rootRef}
638
- role={isDecorative ? 'presentation' : 'img'}
639
- aria-label={isDecorative ? undefined : accessibleName}
640
- aria-hidden={isDecorative || undefined}
641
779
  // The root is a div[role="img"], not natively focusable. When a name
642
780
  // tooltip is active, add a tab stop so keyboard users can reveal it
643
781
  // (WCAG 1.4.13 / 2.1.1) — matching Timestamp/Button. Suppressed inside
@@ -652,7 +790,11 @@ export function Avatar({
652
790
  }
653
791
 
654
792
  const avatarElement = (
655
- <AvatarSizeContext value={numericSize}>{rootElement}</AvatarSizeContext>
793
+ <AvatarSizeContext value={numericSize}>
794
+ <AvatarStatusLabelContext value={statusLabelRef}>
795
+ {rootElement}
796
+ </AvatarStatusLabelContext>
797
+ </AvatarSizeContext>
656
798
  );
657
799
 
658
800
  // Always return the same structure so the avatar keeps its position in the
@@ -4,7 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file AvatarStatusDot.tsx
7
- * @input Uses React, StyleX, theme tokens, and AvatarSizeContext
7
+ * @input Uses React, StyleX, theme tokens, AvatarSizeContext, and
8
+ * AvatarStatusLabelContext (label reporting)
8
9
  * @output Exports AvatarStatusDot component and AvatarStatusDotProps type
9
10
  * @position Sub-component of Avatar; renders a size-aware status indicator
10
11
  *
@@ -15,12 +16,13 @@
15
16
  * - /packages/cli/assets/templates/blocks/components/Avatar/ (showcase blocks)
16
17
  */
17
18
 
18
- import React, {use, type ReactNode} from 'react';
19
+ import React, {use, useCallback, useMemo, type ReactNode} from 'react';
19
20
  import type {BaseProps} from '../BaseProps';
20
21
  import * as stylex from '@stylexjs/stylex';
21
22
  import {colorVars, radiusVars} from '../theme/tokens.stylex';
22
23
  import {AvatarSizeContext} from './AvatarSizeContext';
23
- import {isRenderable, mergeProps} from '../utils';
24
+ import {AvatarStatusLabelContext} from './AvatarStatusLabelContext';
25
+ import {isRenderable, mergeProps, mergeRefs} from '../utils';
24
26
  import {themeProps} from '../utils/themeProps';
25
27
  import type {AvatarStatusDotVariantMap} from './index';
26
28
 
@@ -311,6 +313,30 @@ export function AvatarStatusDot({
311
313
  ...props
312
314
  }: AvatarStatusDotProps) {
313
315
  const avatarSize = use(AvatarSizeContext);
316
+ const statusLabelRef = use(AvatarStatusLabelContext);
317
+ // Report the label through the avatar's ref from a callback ref rather than
318
+ // an Effect: the ref runs in the commit phase, so the avatar's accessible
319
+ // name is composed before paint, and a ref write costs no render.
320
+ /* eslint-disable react-compiler/react-compiler -- the avatar shares its own
321
+ ref through context so the dot can write into it; a context read looks
322
+ immutable to the compiler */
323
+ const reportRef = useCallback(() => {
324
+ const target = statusLabelRef?.current;
325
+ if (target == null) {
326
+ return;
327
+ }
328
+ target.label = label;
329
+ target.update?.();
330
+ return () => {
331
+ target.label = undefined;
332
+ target.update?.();
333
+ };
334
+ }, [statusLabelRef, label]);
335
+ /* eslint-enable react-compiler/react-compiler */
336
+ // Memoized so React only detaches and reattaches when the reported label
337
+ // actually changes; an inline merge would withdraw and re-report on every
338
+ // render of the avatar.
339
+ const rootRef = useMemo(() => mergeRefs(ref, reportRef), [ref, reportRef]);
314
340
  const {dotSize, borderWidth, iconSize, tier} =
315
341
  resolveStatusDotSize(avatarSize);
316
342
  const showsIcon = isRenderable(icon) && iconSize > 0;
@@ -321,7 +347,7 @@ export function AvatarStatusDot({
321
347
  return (
322
348
  <div
323
349
  {...props}
324
- ref={ref}
350
+ ref={rootRef}
325
351
  {...(label ? {role: 'img', 'aria-label': label} : undefined)}
326
352
  {...mergeProps(
327
353
  themeProps('avatar-status-dot', {variant}),
@@ -0,0 +1,47 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file AvatarStatusLabelContext.ts
7
+ * @input Uses React createContext, RefObject
8
+ * @output Exports AvatarStatusLabelContext, AvatarStatusLabelTarget
9
+ * @position Internal context; provided by Avatar, consumed by AvatarStatusDot
10
+ *
11
+ * SYNC: When modified, update:
12
+ * - /packages/core/src/Avatar/Avatar.doc.mjs
13
+ */
14
+
15
+ import {createContext, type RefObject} from 'react';
16
+
17
+ /**
18
+ * The box a status element writes its label into.
19
+ *
20
+ * `update` is the avatar's own recompose, set while its root element is
21
+ * attached and null otherwise. Writing `label` alone is enough on mount (the
22
+ * avatar reads the box when its root attaches, which happens after its
23
+ * children's refs); calling `update` covers a label that changes later
24
+ * without the avatar itself re-rendering.
25
+ */
26
+ export interface AvatarStatusLabelTarget {
27
+ label: string | undefined;
28
+ update: (() => void) | null;
29
+ }
30
+
31
+ /**
32
+ * Lets a status element hand its accessible label to the enclosing Avatar,
33
+ * which composes it into its own accessible name ("Jane Doe, Online").
34
+ *
35
+ * The avatar root is `role="img"`, which prunes all descendant semantics, so
36
+ * composing the label in is the only way the status reaches assistive tech
37
+ * (WCAG 4.1.2). Reading `label` off the passed element only works when the
38
+ * consumer passes `AvatarStatusDot` directly; this route works through a
39
+ * consumer's own wrapper, at any depth.
40
+ *
41
+ * The value is a ref, written from the dot's own callback ref in the commit
42
+ * phase, so reporting a label costs no render and no state. `null` outside an
43
+ * Avatar, where a standalone dot names itself and has nobody to report to.
44
+ */
45
+ export const AvatarStatusLabelContext =
46
+ createContext<RefObject<AvatarStatusLabelTarget> | null>(null);
47
+ AvatarStatusLabelContext.displayName = 'AvatarStatusLabelContext';
@@ -11,11 +11,12 @@ export const docs = {
11
11
  description:
12
12
  'AvatarGroup displays multiple avatars in an overlapping row with an optional overflow indicator. Uses a compositional API: pass Avatar children directly so each avatar can carry its own props (status dots, click handlers, etc.).',
13
13
  bestPractices: [
14
- {guidance: true, description: 'Set max to limit visible avatars when the list is long; 3-5 is typical.'},
14
+ {guidance: true, description: 'Slice the list yourself and pass only the avatars you want visible; 3-5 is typical. The group renders exactly the children it is given and never slices for you.'},
15
15
  {guidance: true, description: 'Use AvatarGroupOverflow for custom overflow content like a popover trigger or "add member" button.'},
16
16
  {guidance: true, description: 'Pass status dots, click handlers, or tooltips directly on each Avatar child.'},
17
- {guidance: true, description: 'Make avatars interactive with href or onClick to link to profiles. Interactive avatars share a single Tab stop; arrow keys move between them, and the group announces a keyboard hint to assistive tech.'},
17
+ {guidance: true, description: 'Make avatars interactive with href or onClick to link to profiles. Interactive avatars share a single Tab stop; arrow keys move between them, and the group announces a keyboard hint to assistive tech. This follows the WAI-ARIA APG roving tabindex technique for managing focus in a composite: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex'},
18
18
  {guidance: false, description: "Don't nest AvatarGroups; use a single group with all avatars."},
19
+ {guidance: false, description: "Don't set size on the child avatars. The group's size wins over each child's own size prop, including when the group leaves size at its default, so a child's size is silently ignored inside a group."},
19
20
  ],
20
21
  anatomy: [
21
22
  {name: 'Avatar children', required: true, description: 'Avatar elements that form the overlapping row. Each can have its own props.'},
@@ -47,7 +48,8 @@ export const docs = {
47
48
  {
48
49
  name: 'size',
49
50
  type: 'AvatarSize',
50
- description: 'Size applied to all avatars via context.',
51
+ description:
52
+ "Size applied to all avatars via context. This wins over each child Avatar's own size prop, including when it is left at the default, so set the size here rather than on the children.",
51
53
  default: "'md'",
52
54
  },
53
55
  {
@@ -77,10 +79,11 @@ export const docsZh = {
77
79
  description:
78
80
  'AvatarGroup 以重叠排列方式显示多个头像,并可选择显示溢出指示器。使用组合式 API:直接传入 Avatar 子元素,每个头像可携带自己的属性。',
79
81
  bestPractices: [
80
- {guidance: true, description: '当列表较长时设置 max 来限制可见头像数量,通常 3-5 个为佳。'},
82
+ {guidance: true, description: '自行切分列表,只传入需要显示的头像,通常 3-5 个为佳。组件只渲染传入的子元素,不会自动切分。'},
81
83
  {guidance: true, description: '使用 AvatarGroupOverflow 自定义溢出内容,如弹出触发器或"添加成员"按钮。'},
82
84
  {guidance: true, description: '直接在每个 Avatar 子元素上传递状态点、点击处理器或工具提示。'},
83
85
  {guidance: false, description: '不要嵌套 AvatarGroup,使用单个组包含所有头像。'},
86
+ {guidance: false, description: '不要在子头像上设置 size。分组的 size 会覆盖每个子 Avatar 自身的 size,即使分组使用默认值也是如此。'},
84
87
  ],
85
88
  },
86
89
  };
@@ -92,11 +95,12 @@ export const docsDense = {
92
95
  description:
93
96
  'Stacked avatar display with overlapping layout. Compositional API: Avatar children with per-avatar props (status dots, clicks). Optional AvatarGroupOverflow slot for custom overflow.',
94
97
  bestPractices: [
95
- {guidance: true, description: 'Set max to limit visible avatars (3-5 typical).'},
98
+ {guidance: true, description: 'Slice the list yourself; pass only visible avatars (3-5 typical). No auto-slicing.'},
96
99
  {guidance: true, description: 'Use AvatarGroupOverflow for custom overflow (popover, add button).'},
97
100
  {guidance: true, description: 'Pass status dots / click handlers directly on each Avatar.'},
98
- {guidance: true, description: 'Interactive avatars (href/onClick) share one Tab stop; arrows rove between them + the overflow button.'},
101
+ {guidance: true, description: 'Interactive avatars (href/onClick) share one Tab stop; arrows rove between them + the overflow button (APG roving tabindex).'},
99
102
  {guidance: false, description: "Don't nest AvatarGroups."},
103
+ {guidance: false, description: "Don't set size on children; the group's size always wins, default included."},
100
104
  ],
101
105
  },
102
106
  };
@@ -231,6 +231,31 @@ describe('AvatarGroupOverflow — hardening', () => {
231
231
  expect(screen.getByLabelText('0 more')).toBeInTheDocument();
232
232
  });
233
233
 
234
+ it('clamps a negative count rather than rendering "+-3"', () => {
235
+ // `count={total - visibleCount}` is the documented shape, and it goes
236
+ // negative whenever the list is shorter than the slice.
237
+ render(
238
+ <AvatarGroup>
239
+ <Avatar name="Alice" />
240
+ <AvatarGroupOverflow count={-3} />
241
+ </AvatarGroup>,
242
+ );
243
+
244
+ expect(screen.queryByText('+-3')).not.toBeInTheDocument();
245
+ expect(screen.getByText('+0')).toBeInTheDocument();
246
+ expect(screen.getByLabelText('0 more')).toBeInTheDocument();
247
+ });
248
+
249
+ it('renders outside an AvatarGroup at the md fallback size', () => {
250
+ render(<AvatarGroupOverflow count={3} data-testid="loose" />);
251
+
252
+ const overflow = screen.getByTestId('loose');
253
+ expect(overflow).toHaveTextContent('+3');
254
+ // inline-flex, so a standalone indicator stays a circle instead of
255
+ // stretching to its parent's width.
256
+ expect(overflow.tagName).toBe('SPAN');
257
+ });
258
+
234
259
  it('handles very large count', () => {
235
260
  render(
236
261
  <AvatarGroup>
@@ -400,3 +425,40 @@ describe('AvatarGroup — roving focus + keyboard hint', () => {
400
425
  expect(screen.getByTestId('bob')).not.toHaveAttribute('tabindex');
401
426
  });
402
427
  });
428
+
429
+ describe('AvatarGroup — size cascade', () => {
430
+ const sizeClasses = (el: HTMLElement) => el.className.split(/\s+/);
431
+
432
+ it("the group's size overrides a child's own size prop", () => {
433
+ render(
434
+ <AvatarGroup size="lg">
435
+ <Avatar name="Alice" size="xsm" data-testid="alice" />
436
+ </AvatarGroup>,
437
+ );
438
+
439
+ const classes = sizeClasses(screen.getByTestId('alice'));
440
+ expect(classes).toContain('lg');
441
+ expect(classes).not.toContain('xsm');
442
+ });
443
+
444
+ it("the group's default size also overrides a child's own size prop", () => {
445
+ // Known open finding: the group always publishes a size on its context, so
446
+ // a group that never set one still imposes md on children that did set one.
447
+ // Pinned here so the behaviour cannot change without a deliberate edit.
448
+ render(
449
+ <AvatarGroup>
450
+ <Avatar name="Alice" size="xl" data-testid="alice" />
451
+ </AvatarGroup>,
452
+ );
453
+
454
+ const classes = sizeClasses(screen.getByTestId('alice'));
455
+ expect(classes).toContain('md');
456
+ expect(classes).not.toContain('xl');
457
+ });
458
+
459
+ it("outside a group the avatar's own size applies", () => {
460
+ render(<Avatar name="Alice" size="xl" data-testid="alice" />);
461
+
462
+ expect(sizeClasses(screen.getByTestId('alice'))).toContain('xl');
463
+ });
464
+ });
@@ -48,7 +48,9 @@ export interface AvatarGroupProps extends BaseProps<HTMLDivElement> {
48
48
  */
49
49
  children: ReactNode;
50
50
  /**
51
- * Size applied to all avatars via context.
51
+ * Size applied to all avatars via context. This wins over each child
52
+ * Avatar's own `size` prop, including when it is left at the default, so
53
+ * set the size here rather than on the children.
52
54
  * @default 'md'
53
55
  */
54
56
  size?: AvatarSize;