@astryxdesign/core 0.1.8 → 0.1.9

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 (487) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +6 -2
  4. package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
  5. package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
  6. package/dist/Avatar/Avatar.d.ts +18 -2
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +63 -7
  9. package/dist/Avatar/AvatarStatusDot.d.ts +33 -5
  10. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  11. package/dist/Avatar/AvatarStatusDot.js +144 -19
  12. package/dist/Banner/Banner.d.ts +9 -1
  13. package/dist/Banner/Banner.d.ts.map +1 -1
  14. package/dist/Banner/Banner.js +31 -2
  15. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +31 -2
  16. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  17. package/dist/Breadcrumbs/BreadcrumbItem.js +208 -6
  18. package/dist/Breadcrumbs/index.d.ts +5 -0
  19. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  20. package/dist/Breadcrumbs/index.js +9 -1
  21. package/dist/Button/Button.d.ts +8 -2
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +31 -2
  24. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  25. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  26. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  27. package/dist/Calendar/Calendar.d.ts.map +1 -1
  28. package/dist/Calendar/Calendar.js +30 -2
  29. package/dist/Calendar/dayCellUtils.js +1 -1
  30. package/dist/Card/Card.d.ts +8 -2
  31. package/dist/Card/Card.d.ts.map +1 -1
  32. package/dist/Card/Card.js +28 -2
  33. package/dist/Carousel/Carousel.d.ts +4 -1
  34. package/dist/Carousel/Carousel.d.ts.map +1 -1
  35. package/dist/Carousel/Carousel.js +24 -4
  36. package/dist/Chat/ChatComposer.d.ts +8 -0
  37. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  38. package/dist/Chat/ChatComposer.js +46 -7
  39. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  40. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  41. package/dist/Chat/ChatComposerDrawer.js +11 -1
  42. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  43. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  44. package/dist/Chat/ChatComposerInput.js +35 -3
  45. package/dist/Chat/ChatContext.d.ts +33 -0
  46. package/dist/Chat/ChatContext.d.ts.map +1 -1
  47. package/dist/Chat/ChatContext.js +16 -2
  48. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  49. package/dist/Chat/ChatDictationButton.js +1 -1
  50. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  51. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  52. package/dist/Chat/ChatLayoutScrollButton.js +4 -4
  53. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  54. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  55. package/dist/Chat/ChatSendButton.js +7 -1
  56. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  57. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  58. package/dist/Chat/ChatToolCalls.js +18 -9
  59. package/dist/Chat/index.d.ts +2 -1
  60. package/dist/Chat/index.d.ts.map +1 -1
  61. package/dist/Chat/index.js +1 -1
  62. package/dist/Chat/useChatStreamScroll.d.ts +2 -1
  63. package/dist/Chat/useChatStreamScroll.d.ts.map +1 -1
  64. package/dist/Chat/useChatStreamScroll.js +22 -8
  65. package/dist/Chat/useSpeechRecognition.d.ts +0 -1
  66. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  67. package/dist/Chat/useSpeechRecognition.js +1 -1
  68. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  69. package/dist/CheckboxInput/CheckboxInput.js +1 -1
  70. package/dist/CheckboxList/CheckboxListItem.d.ts +20 -1
  71. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  72. package/dist/CheckboxList/CheckboxListItem.js +11 -1
  73. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  74. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  75. package/dist/ClickableCard/ClickableCard.js +5 -2
  76. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  77. package/dist/CodeBlock/CodeBlock.js +8 -1
  78. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  79. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  80. package/dist/CommandPalette/CommandPalette.js +3 -1
  81. package/dist/CommandPalette/CommandPaletteGroup.d.ts.map +1 -1
  82. package/dist/CommandPalette/CommandPaletteGroup.js +9 -2
  83. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  84. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  85. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  86. package/dist/DateInput/DateInput.d.ts +47 -1
  87. package/dist/DateInput/DateInput.d.ts.map +1 -1
  88. package/dist/DateInput/DateInput.js +26 -2
  89. package/dist/DateInput/index.d.ts +1 -1
  90. package/dist/DateInput/index.d.ts.map +1 -1
  91. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  92. package/dist/DateTimeInput/DateTimeInput.js +10 -1
  93. package/dist/Dialog/Dialog.d.ts +3 -0
  94. package/dist/Dialog/Dialog.d.ts.map +1 -1
  95. package/dist/Dialog/Dialog.js +57 -6
  96. package/dist/Dialog/DialogContext.d.ts +7 -0
  97. package/dist/Dialog/DialogContext.d.ts.map +1 -1
  98. package/dist/Dialog/DialogContext.js +2 -2
  99. package/dist/Dialog/DialogHeader.d.ts +5 -2
  100. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  101. package/dist/Dialog/DialogHeader.js +6 -1
  102. package/dist/Divider/Divider.js +1 -1
  103. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +1 -2
  104. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  105. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +1 -2
  106. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +1 -2
  107. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  108. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +1 -2
  109. package/dist/Field/Field.d.ts.map +1 -1
  110. package/dist/Field/Field.js +2 -3
  111. package/dist/Field/FieldLabel.d.ts +1 -1
  112. package/dist/Field/FieldLabel.d.ts.map +1 -1
  113. package/dist/Field/FieldLabel.js +42 -15
  114. package/dist/FieldStatus/FieldStatus.d.ts +8 -0
  115. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  116. package/dist/FieldStatus/FieldStatus.js +22 -4
  117. package/dist/FileInput/FileInput.d.ts.map +1 -1
  118. package/dist/FileInput/FileInput.js +10 -2
  119. package/dist/HoverCard/HoverCard.d.ts +9 -1
  120. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  121. package/dist/HoverCard/HoverCard.js +2 -0
  122. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  123. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  124. package/dist/HoverCard/useHoverCard.js +7 -2
  125. package/dist/Icon/Icon.d.ts +7 -2
  126. package/dist/Icon/Icon.d.ts.map +1 -1
  127. package/dist/Item/Item.d.ts +6 -1
  128. package/dist/Item/Item.d.ts.map +1 -1
  129. package/dist/Item/Item.js +22 -1
  130. package/dist/Layer/useLayer.d.ts +6 -0
  131. package/dist/Layer/useLayer.d.ts.map +1 -1
  132. package/dist/Layer/useLayer.js +2 -0
  133. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  134. package/dist/MobileNav/MobileNav.js +6 -1
  135. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  136. package/dist/MobileNav/MobileNavToggle.js +4 -0
  137. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  138. package/dist/MultiSelector/MultiSelector.js +39 -12
  139. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  140. package/dist/MultiSelector/hooks.js +16 -0
  141. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  142. package/dist/Outline/Outline.d.ts +0 -1
  143. package/dist/Outline/Outline.d.ts.map +1 -1
  144. package/dist/Outline/Outline.js +0 -1
  145. package/dist/OverflowList/OverflowList.d.ts +20 -3
  146. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  147. package/dist/OverflowList/OverflowList.js +29 -4
  148. package/dist/Popover/Popover.d.ts.map +1 -1
  149. package/dist/Popover/Popover.js +3 -2
  150. package/dist/Popover/usePopover.d.ts.map +1 -1
  151. package/dist/Popover/usePopover.js +3 -9
  152. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  153. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  154. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  155. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  156. package/dist/SelectableCard/SelectableCard.js +16 -13
  157. package/dist/Selector/Selector.d.ts.map +1 -1
  158. package/dist/Selector/Selector.js +4 -2
  159. package/dist/Selector/hooks.d.ts.map +1 -1
  160. package/dist/Selector/hooks.js +16 -0
  161. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  162. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  163. package/dist/SideNav/SideNavHeading.js +21 -8
  164. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  165. package/dist/Slider/Slider.d.ts.map +1 -1
  166. package/dist/Slider/Slider.js +19 -2
  167. package/dist/Stack/stack.stylex.d.ts +9 -9
  168. package/dist/Table/BaseTable.d.ts.map +1 -1
  169. package/dist/Table/BaseTable.js +3 -2
  170. package/dist/Table/index.d.ts +2 -0
  171. package/dist/Table/index.d.ts.map +1 -1
  172. package/dist/Table/index.js +1 -0
  173. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  174. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  175. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  176. package/dist/Table/plugins/rowStatus/index.js +3 -0
  177. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  178. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  179. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  180. package/dist/Table/plugins/selection/useTableSelection.d.ts +13 -0
  181. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  182. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  183. package/dist/Table/plugins/tree/useTableTreeData.d.ts +16 -0
  184. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  185. package/dist/Table/plugins/tree/useTableTreeData.js +87 -0
  186. package/dist/Table/plugins/tree/useTableTreeState.d.ts +6 -0
  187. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -1
  188. package/dist/Table/plugins/tree/useTableTreeState.js +22 -1
  189. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  190. package/dist/Table/useBaseTablePlugins.js +5 -3
  191. package/dist/Thumbnail/Thumbnail.d.ts +26 -5
  192. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  193. package/dist/Thumbnail/Thumbnail.js +97 -54
  194. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +9 -0
  195. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +1 -0
  196. package/dist/Thumbnail/thumbnail.markers.stylex.js +14 -0
  197. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  198. package/dist/TimeInput/TimeInput.js +15 -4
  199. package/dist/Timestamp/Timestamp.d.ts +3 -1
  200. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  201. package/dist/Timestamp/Timestamp.js +8 -6
  202. package/dist/Toast/useToast.d.ts.map +1 -1
  203. package/dist/Toast/useToast.js +2 -5
  204. package/dist/ToggleButton/ToggleButtonGroup.d.ts +0 -1
  205. package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
  206. package/dist/ToggleButton/ToggleButtonGroup.js +1 -1
  207. package/dist/Tokenizer/Tokenizer.d.ts +1 -1
  208. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  209. package/dist/Tokenizer/Tokenizer.js +16 -4
  210. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  211. package/dist/TopNav/TopNavHeading.js +18 -8
  212. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  213. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts +9 -1
  214. package/dist/TopNav/TopNavMegaMenuFeaturedCard.d.ts.map +1 -1
  215. package/dist/TopNav/TopNavMegaMenuFeaturedCard.js +8 -1
  216. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  217. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  218. package/dist/TopNav/TopNavMenu.js +68 -6
  219. package/dist/TreeList/TreeList.d.ts +12 -3
  220. package/dist/TreeList/TreeList.d.ts.map +1 -1
  221. package/dist/TreeList/TreeList.js +2 -0
  222. package/dist/TreeList/TreeListBranches.d.ts +5 -0
  223. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  224. package/dist/TreeList/TreeListBranches.js +10 -4
  225. package/dist/TreeList/TreeListItem.d.ts +8 -2
  226. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  227. package/dist/TreeList/TreeListItem.js +27 -7
  228. package/dist/TreeList/TreeListTypes.d.ts +25 -1
  229. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  230. package/dist/TreeList/index.d.ts +2 -2
  231. package/dist/TreeList/index.d.ts.map +1 -1
  232. package/dist/astryx.css +28 -18
  233. package/dist/astryx.umd.js +51 -51
  234. package/dist/astryx.umd.js.map +4 -4
  235. package/dist/authoring/doc.d.ts.map +1 -1
  236. package/dist/docs-types.d.ts +8 -0
  237. package/dist/docs-types.d.ts.map +1 -1
  238. package/dist/hooks/computeOverflow.d.ts +47 -0
  239. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  240. package/dist/hooks/computeOverflow.js +213 -0
  241. package/dist/hooks/index.d.ts +1 -0
  242. package/dist/hooks/index.d.ts.map +1 -1
  243. package/dist/hooks/index.js +2 -1
  244. package/dist/hooks/useDevWarning.d.ts +18 -0
  245. package/dist/hooks/useDevWarning.d.ts.map +1 -0
  246. package/dist/hooks/useDevWarning.js +49 -0
  247. package/dist/hooks/useOverflow.d.ts +19 -0
  248. package/dist/hooks/useOverflow.d.ts.map +1 -1
  249. package/dist/hooks/useOverflow.js +36 -25
  250. package/dist/i18n/resolve.d.ts.map +1 -1
  251. package/dist/i18n/resolve.js +7 -18
  252. package/dist/theme/Theme.d.ts.map +1 -1
  253. package/dist/theme/Theme.js +2 -7
  254. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  255. package/dist/theme/generateThemeRules.js +51 -0
  256. package/dist/theme/hct.d.ts +0 -1
  257. package/dist/theme/hct.d.ts.map +1 -1
  258. package/dist/theme/hct.js +1 -1
  259. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  260. package/dist/theme/syntax/defineSyntaxTheme.js +2 -1
  261. package/dist/utils/devWarning.d.ts +42 -0
  262. package/dist/utils/devWarning.d.ts.map +1 -0
  263. package/dist/utils/devWarning.js +88 -0
  264. package/dist/utils/index.d.ts +3 -1
  265. package/dist/utils/index.d.ts.map +1 -1
  266. package/dist/utils/index.js +3 -2
  267. package/dist/utils/plainDate.d.ts +30 -0
  268. package/dist/utils/plainDate.d.ts.map +1 -1
  269. package/dist/utils/plainDate.js +53 -0
  270. package/dist/utils/types.d.ts +13 -0
  271. package/dist/utils/types.d.ts.map +1 -1
  272. package/locales/en.json +24 -0
  273. package/locales/fr-FR.json +14 -0
  274. package/locales/pseudo.json +18 -0
  275. package/package.json +1 -1
  276. package/src/AppShell/AppShell.tsx +6 -0
  277. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  278. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  279. package/src/Avatar/Avatar.doc.mjs +15 -3
  280. package/src/Avatar/Avatar.test.tsx +156 -1
  281. package/src/Avatar/Avatar.tsx +96 -4
  282. package/src/Avatar/AvatarStatusDot.doc.mjs +23 -12
  283. package/src/Avatar/AvatarStatusDot.test.tsx +284 -0
  284. package/src/Avatar/AvatarStatusDot.tsx +178 -21
  285. package/src/Banner/Banner.doc.mjs +9 -0
  286. package/src/Banner/Banner.test.tsx +30 -0
  287. package/src/Banner/Banner.tsx +47 -3
  288. package/src/Breadcrumbs/BreadcrumbItem.doc.mjs +26 -0
  289. package/src/Breadcrumbs/BreadcrumbItem.tsx +333 -14
  290. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +2 -0
  291. package/src/Breadcrumbs/Breadcrumbs.test.tsx +284 -2
  292. package/src/Breadcrumbs/index.ts +27 -0
  293. package/src/Button/Button.doc.mjs +9 -0
  294. package/src/Button/Button.test.tsx +49 -0
  295. package/src/Button/Button.tsx +25 -1
  296. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  297. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  298. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  299. package/src/Calendar/Calendar.doc.mjs +12 -1
  300. package/src/Calendar/Calendar.test.tsx +193 -0
  301. package/src/Calendar/Calendar.tsx +38 -16
  302. package/src/Calendar/dayCellUtils.test.ts +5 -0
  303. package/src/Calendar/dayCellUtils.ts +1 -1
  304. package/src/Card/Card.doc.mjs +9 -0
  305. package/src/Card/Card.test.tsx +21 -3
  306. package/src/Card/Card.tsx +35 -2
  307. package/src/Carousel/Carousel.doc.mjs +1 -0
  308. package/src/Carousel/Carousel.test.tsx +53 -0
  309. package/src/Carousel/Carousel.tsx +34 -4
  310. package/src/Chat/ChatComposer.doc.mjs +12 -3
  311. package/src/Chat/ChatComposer.test.tsx +115 -0
  312. package/src/Chat/ChatComposer.tsx +73 -9
  313. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  314. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  315. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  316. package/src/Chat/ChatComposerInput.test.tsx +103 -0
  317. package/src/Chat/ChatComposerInput.tsx +49 -3
  318. package/src/Chat/ChatContext.tsx +37 -10
  319. package/src/Chat/ChatDictationButton.tsx +4 -1
  320. package/src/Chat/ChatLayout.tsx +3 -1
  321. package/src/Chat/ChatLayoutScrollButton.tsx +7 -2
  322. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  323. package/src/Chat/ChatSendButton.tsx +11 -1
  324. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  325. package/src/Chat/ChatToolCalls.tsx +25 -4
  326. package/src/Chat/index.ts +5 -1
  327. package/src/Chat/useChatStreamScroll.test.tsx +49 -0
  328. package/src/Chat/useChatStreamScroll.ts +27 -10
  329. package/src/Chat/useSpeechRecognition.ts +1 -1
  330. package/src/Chat/useTriggerMenu.tsx +3 -1
  331. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  332. package/src/CheckboxInput/CheckboxInput.tsx +1 -1
  333. package/src/CheckboxList/CheckboxList.test.tsx +81 -3
  334. package/src/CheckboxList/CheckboxListItem.doc.mjs +33 -5
  335. package/src/CheckboxList/CheckboxListItem.tsx +41 -5
  336. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  337. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  338. package/src/ClickableCard/ClickableCard.tsx +13 -3
  339. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  340. package/src/CodeBlock/CodeBlock.tsx +9 -1
  341. package/src/CommandPalette/CommandPalette.doc.mjs +1 -0
  342. package/src/CommandPalette/CommandPalette.tsx +3 -1
  343. package/src/CommandPalette/CommandPaletteGroup.test.tsx +71 -0
  344. package/src/CommandPalette/CommandPaletteGroup.tsx +12 -1
  345. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  346. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  347. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  348. package/src/ContextMenu/ContextMenu.test.tsx +193 -0
  349. package/src/DateInput/DateInput.doc.mjs +16 -0
  350. package/src/DateInput/DateInput.test.tsx +108 -0
  351. package/src/DateInput/DateInput.tsx +68 -3
  352. package/src/DateInput/index.ts +1 -0
  353. package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
  354. package/src/DateTimeInput/DateTimeInput.tsx +14 -2
  355. package/src/Dialog/Dialog.doc.mjs +2 -2
  356. package/src/Dialog/Dialog.test.tsx +77 -4
  357. package/src/Dialog/Dialog.tsx +89 -5
  358. package/src/Dialog/DialogContext.ts +9 -2
  359. package/src/Dialog/DialogHeader.doc.mjs +2 -2
  360. package/src/Dialog/DialogHeader.tsx +9 -2
  361. package/src/Divider/Divider.tsx +2 -2
  362. package/src/DropdownMenu/DropdownMenu.test.tsx +169 -0
  363. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +1 -2
  364. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +1 -2
  365. package/src/Field/Field.test.tsx +57 -12
  366. package/src/Field/Field.tsx +6 -5
  367. package/src/Field/FieldLabel.tsx +8 -0
  368. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  369. package/src/FieldStatus/FieldStatus.test.tsx +78 -40
  370. package/src/FieldStatus/FieldStatus.tsx +22 -4
  371. package/src/FileInput/FileInput.test.tsx +52 -3
  372. package/src/FileInput/FileInput.tsx +13 -2
  373. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  374. package/src/HoverCard/HoverCard.test.tsx +21 -2
  375. package/src/HoverCard/HoverCard.tsx +11 -0
  376. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  377. package/src/HoverCard/useHoverCard.tsx +24 -2
  378. package/src/Icon/Icon.doc.mjs +3 -2
  379. package/src/Icon/Icon.tsx +7 -2
  380. package/src/IconButton/IconButton.doc.mjs +8 -0
  381. package/src/IconButton/IconButton.test.tsx +15 -18
  382. package/src/Item/Item.test.tsx +40 -18
  383. package/src/Item/Item.tsx +38 -2
  384. package/src/Layer/useLayer.tsx +8 -0
  385. package/src/List/List.test.tsx +9 -8
  386. package/src/MobileNav/MobileNav.tsx +12 -1
  387. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  388. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  389. package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
  390. package/src/MultiSelector/MultiSelector.test.tsx +172 -3
  391. package/src/MultiSelector/MultiSelector.tsx +55 -12
  392. package/src/MultiSelector/hooks.ts +17 -0
  393. package/src/NumberInput/NumberInput.test.tsx +11 -7
  394. package/src/NumberInput/NumberInput.tsx +1 -2
  395. package/src/Outline/Outline.tsx +0 -1
  396. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  397. package/src/OverflowList/OverflowList.test.tsx +111 -0
  398. package/src/OverflowList/OverflowList.tsx +48 -8
  399. package/src/Popover/Popover.tsx +7 -4
  400. package/src/Popover/usePopover.tsx +9 -13
  401. package/src/PowerSearch/PowerSearch.doc.mjs +14 -0
  402. package/src/PowerSearch/PowerSearch.test.tsx +20 -0
  403. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  404. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  405. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  406. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  407. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  408. package/src/SelectableCard/SelectableCard.tsx +29 -16
  409. package/src/Selector/Selector.test.tsx +69 -0
  410. package/src/Selector/Selector.tsx +5 -1
  411. package/src/Selector/hooks.ts +17 -0
  412. package/src/SideNav/SideNav.test.tsx +119 -3
  413. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  414. package/src/SideNav/SideNavHeading.tsx +24 -8
  415. package/src/SideNav/SideNavItem.tsx +5 -1
  416. package/src/Slider/Slider.test.tsx +75 -2
  417. package/src/Slider/Slider.tsx +21 -5
  418. package/src/Switch/Switch.test.tsx +32 -1
  419. package/src/Table/BaseTable.tsx +3 -5
  420. package/src/Table/index.ts +5 -0
  421. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  422. package/src/Table/plugins/rowStatus/index.ts +7 -0
  423. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  424. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  425. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  426. package/src/Table/plugins/selection/useTableSelection.tsx +19 -1
  427. package/src/Table/plugins/tree/useTableTreeData.test.tsx +135 -3
  428. package/src/Table/plugins/tree/useTableTreeData.tsx +128 -1
  429. package/src/Table/plugins/tree/useTableTreeState.test.tsx +104 -0
  430. package/src/Table/plugins/tree/useTableTreeState.tsx +46 -2
  431. package/src/Table/useBaseTablePlugins.ts +10 -6
  432. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  433. package/src/Table/useTableSelection.doc.mjs +65 -14
  434. package/src/Table/useTableTreeData.doc.mjs +29 -0
  435. package/src/Table/useTableTreeState.doc.mjs +2 -2
  436. package/src/TextArea/TextArea.test.tsx +9 -1
  437. package/src/Thumbnail/Thumbnail.doc.mjs +19 -10
  438. package/src/Thumbnail/Thumbnail.test.tsx +98 -7
  439. package/src/Thumbnail/Thumbnail.tsx +141 -41
  440. package/src/Thumbnail/thumbnail.markers.stylex.ts +12 -0
  441. package/src/TimeInput/TimeInput.test.tsx +76 -1
  442. package/src/TimeInput/TimeInput.tsx +19 -6
  443. package/src/Timestamp/Timestamp.doc.mjs +4 -4
  444. package/src/Timestamp/Timestamp.test.tsx +32 -0
  445. package/src/Timestamp/Timestamp.tsx +28 -8
  446. package/src/Toast/useToast.tsx +7 -8
  447. package/src/ToggleButton/ToggleButtonGroup.tsx +1 -1
  448. package/src/Tokenizer/Tokenizer.doc.mjs +15 -1
  449. package/src/Tokenizer/Tokenizer.test.tsx +183 -2
  450. package/src/Tokenizer/Tokenizer.tsx +16 -4
  451. package/src/TopNav/TopNav.test.tsx +125 -1
  452. package/src/TopNav/TopNavHeading.tsx +24 -8
  453. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  454. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  455. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +3 -3
  456. package/src/TopNav/TopNavMegaMenuFeaturedCard.tsx +19 -2
  457. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  458. package/src/TopNav/TopNavMenu.tsx +83 -5
  459. package/src/TreeList/TreeList.doc.mjs +22 -0
  460. package/src/TreeList/TreeList.test.tsx +232 -0
  461. package/src/TreeList/TreeList.tsx +18 -2
  462. package/src/TreeList/TreeListBranches.tsx +16 -2
  463. package/src/TreeList/TreeListItem.tsx +51 -11
  464. package/src/TreeList/TreeListTypes.ts +27 -1
  465. package/src/TreeList/index.ts +2 -2
  466. package/src/authoring/doc.ts +1 -3
  467. package/src/docs-types.ts +5 -0
  468. package/src/hooks/computeOverflow.test.ts +422 -0
  469. package/src/hooks/computeOverflow.ts +290 -0
  470. package/src/hooks/index.ts +2 -0
  471. package/src/hooks/useDevWarning.test.tsx +45 -0
  472. package/src/hooks/useDevWarning.ts +54 -0
  473. package/src/hooks/useOverflow.test.ts +96 -4
  474. package/src/hooks/useOverflow.ts +74 -33
  475. package/src/i18n/InternationalizationProvider.doc.mjs +99 -2
  476. package/src/i18n/resolve.ts +9 -21
  477. package/src/theme/Theme.tsx +11 -14
  478. package/src/theme/generateThemeRules.test.ts +65 -0
  479. package/src/theme/generateThemeRules.ts +56 -0
  480. package/src/theme/hct.ts +1 -1
  481. package/src/theme/syntax/defineSyntaxTheme.ts +5 -6
  482. package/src/utils/devWarning.test.ts +56 -0
  483. package/src/utils/devWarning.ts +102 -0
  484. package/src/utils/index.ts +6 -0
  485. package/src/utils/plainDate.test.ts +22 -0
  486. package/src/utils/plainDate.ts +61 -0
  487. package/src/utils/types.ts +14 -0
@@ -24,6 +24,7 @@ import {
24
24
  isValidElement,
25
25
  useCallback,
26
26
  useEffect,
27
+ useId,
27
28
  useMemo,
28
29
  useRef,
29
30
  useState,
@@ -600,10 +601,14 @@ export function AppShell({
600
601
  // =========================================================================
601
602
  // Mobile context — shared with MobileNavToggle and future TopNav mobile
602
603
  // =========================================================================
604
+ // Stable id shared by the drawer (applied as its `id`) and the toggle
605
+ // (referenced via `aria-controls`) so screen readers know what it controls.
606
+ const mobileNavId = useId();
603
607
  const mobileContextValue = useMemo<AppShellMobileContextValue>(
604
608
  () => ({
605
609
  isMobile: isBelowBreakpoint,
606
610
  isMobileNavOpen,
611
+ mobileNavId,
607
612
  toggleMobileNav: () =>
608
613
  mobileNavEnabled && setMobileNavOpen(!isMobileNavOpen),
609
614
  openMobileNav: () => mobileNavEnabled && setMobileNavOpen(true),
@@ -614,6 +619,7 @@ export function AppShell({
614
619
  [
615
620
  isBelowBreakpoint,
616
621
  isMobileNavOpen,
622
+ mobileNavId,
617
623
  setMobileNavOpen,
618
624
  mobileNavEnabled,
619
625
  mobileNavHasToggle,
@@ -20,6 +20,14 @@ export interface AppShellMobileContextValue {
20
20
  isMobile: boolean;
21
21
  /** Whether the mobile nav drawer is currently open */
22
22
  isMobileNavOpen: boolean;
23
+ /**
24
+ * DOM id of the mobile nav drawer. `AppShell` sets this so the toggle can
25
+ * point its `aria-controls` at the drawer and the drawer can apply it as its
26
+ * `id`. Optional: callers that construct this context by hand may omit it, in
27
+ * which case `MobileNav` falls back to a locally generated id and the toggle
28
+ * simply drops `aria-controls`.
29
+ */
30
+ mobileNavId?: string;
23
31
  /** Toggle the mobile nav drawer open/closed */
24
32
  toggleMobileNav: () => void;
25
33
  /** Open the mobile nav drawer */
@@ -31,6 +31,12 @@ export const docs = {
31
31
  description:
32
32
  'Whether the AppShell-managed mobile navigation drawer is open.',
33
33
  },
34
+ {
35
+ name: 'mobileNavId',
36
+ type: 'string | undefined',
37
+ description:
38
+ 'DOM id of the mobile navigation drawer, set by AppShell. Point aria-controls of a custom toggle at this so screen-reader users know which element the toggle expands. Undefined outside an AppShell that manages the drawer.',
39
+ },
34
40
  {
35
41
  name: 'toggleMobileNav',
36
42
  type: '() => void',
@@ -111,6 +117,8 @@ export const docsDense = {
111
117
  isMobile:
112
118
  'viewport below AppShell mobile nav breakpoint? Use to sync AppShell-adjacent mobile UI',
113
119
  isMobileNavOpen: 'AppShell-managed mobile nav drawer open?',
120
+ mobileNavId:
121
+ 'DOM id of the drawer; point custom toggle aria-controls at it',
114
122
  toggleMobileNav: 'toggle drawer; no-op when mobile nav disabled',
115
123
  openMobileNav: 'open drawer; no-op when mobile nav disabled',
116
124
  closeMobileNav: 'close drawer',
@@ -15,6 +15,7 @@ export const docs = {
15
15
  {guidance: true, description: 'Always pass a name so the avatar can show initials if the photo fails to load, and so screen readers can announce who it represents.'},
16
16
  {guidance: true, description: 'Pick a size that matches the context: xsm or sm for inline mentions, md or lg for lists and cards, xl for profile headers.'},
17
17
  {guidance: true, description: 'Add a status dot when knowing someone\'s availability matters, like in chat or team views.'},
18
+ {guidance: true, description: 'When wrapping an Avatar in your own Tooltip or HoverCard, set tooltip={false} so the built-in name tooltip does not overlap yours.'},
18
19
  {guidance: false, description: 'Use Avatar for logos, product images, or anything that isn\'t a person or team. Use an image or icon instead.'},
19
20
  {guidance: false, description: 'Force a square or custom shape. Avatars are always circular to stay consistent across the system.'},
20
21
  ],
@@ -22,13 +23,14 @@ export const docs = {
22
23
  {name: 'Photo', required: false, description: 'The profile image, loaded from the src URL. Shown when available.'},
23
24
  {name: 'Initials', required: false, description: 'One or two letters extracted from the name. Shown when no photo is available.'},
24
25
  {name: 'Default icon', required: false, description: 'A generic person silhouette. Shown when there is no photo or name.'},
25
- {name: 'Status dot', required: false, description: 'A small indicator in the bottom-right corner showing availability (online, away, busy).'},
26
+ {name: 'Status dot', required: false, description: 'A small indicator in the bottom-right corner showing availability (online, away, busy). Each variant pairs colour with a distinct shape so status does not rely on colour alone.'},
26
27
  ],
27
28
  },
28
29
  theming: {
29
30
  targets: [
30
31
  {className: 'astryx-avatar', visualProps: ['size']},
31
32
  {className: 'astryx-avatar-status-dot', visualProps: ['variant']},
33
+ {className: 'astryx-avatar-status-dot-glyph', visualProps: ['shape']},
32
34
  ],
33
35
  },
34
36
  description: 'Displays a user avatar with image, initials fallback, and optional status indicator.',
@@ -65,13 +67,21 @@ export const docs = {
65
67
  description: 'Corner content for status indicators.',
66
68
  slotElements: [
67
69
  {
68
- __element: 'StatusDot',
70
+ __element: 'AvatarStatusDot',
69
71
  props: {
70
- variant: 'online',
72
+ variant: 'success',
73
+ label: 'Online',
71
74
  },
72
75
  },
73
76
  ],
74
77
  },
78
+ {
79
+ name: 'tooltip',
80
+ type: 'string | boolean',
81
+ description:
82
+ "Tooltip shown on hover and keyboard focus. Omitted or true shows the avatar's name; a string shows that text instead; false shows no tooltip. Not auto-disabled when wrapped in your own Tooltip/HoverCard — set tooltip={false} if you supply your own overlay. No tooltip is shown when tooltip is true/omitted and there is no name.",
83
+ default: 'true',
84
+ },
75
85
  ],
76
86
  components: [
77
87
  {name: 'AvatarStatusDot'},
@@ -113,6 +123,8 @@ export const docsDense = {
113
123
  alt: 'alt text; falls back to name',
114
124
  size: "avatar size. Named ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or numeric px.",
115
125
  status: 'corner content for status indicators',
126
+ tooltip:
127
+ "hover/focus tooltip. true/omitted → name; string → that text; false → none. Owns its tooltip; set false when wrapping in your own Tooltip/HoverCard. Default true.",
116
128
  },
117
129
  components: [
118
130
  {name: 'AvatarStatusDot', description: 'size-aware status indicator rendered in the Avatar corner'},
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
- import {describe, it, expect} from 'vitest';
3
+ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
4
4
  import {render, screen, fireEvent} from '@testing-library/react';
5
5
  import {Avatar} from './Avatar';
6
6
 
@@ -65,4 +65,159 @@ describe('Avatar', () => {
65
65
  expect(img).not.toBeNull();
66
66
  expect(img).toHaveAttribute('src', 'https://example.com/ada.jpg');
67
67
  });
68
+
69
+ // --- Name tooltip (tooltip?: string | boolean) ---
70
+ describe('name tooltip', () => {
71
+ const originalShowPopover = HTMLElement.prototype.showPopover;
72
+ const originalHidePopover = HTMLElement.prototype.hidePopover;
73
+
74
+ beforeEach(() => {
75
+ // jsdom does not implement the Popover API the tooltip layer uses.
76
+ HTMLElement.prototype.showPopover = vi.fn();
77
+ HTMLElement.prototype.hidePopover = vi.fn();
78
+ });
79
+
80
+ afterEach(() => {
81
+ HTMLElement.prototype.showPopover = originalShowPopover;
82
+ HTMLElement.prototype.hidePopover = originalHidePopover;
83
+ });
84
+
85
+ it('shows the name in a tooltip by default', () => {
86
+ render(<Avatar name="Ada Lovelace" />);
87
+ const tooltip = screen.getByRole('tooltip', {hidden: true});
88
+ expect(tooltip).toHaveTextContent('Ada Lovelace');
89
+ });
90
+
91
+ it('shows a custom string tooltip instead of the name', () => {
92
+ render(<Avatar name="alovelace" tooltip="Ada Lovelace, Mathematician" />);
93
+ const tooltip = screen.getByRole('tooltip', {hidden: true});
94
+ expect(tooltip).toHaveTextContent('Ada Lovelace, Mathematician');
95
+ expect(tooltip).not.toHaveTextContent('alovelace');
96
+ });
97
+
98
+ it('renders no tooltip when tooltip={false}', () => {
99
+ render(<Avatar name="Ada Lovelace" tooltip={false} />);
100
+ expect(screen.queryByRole('tooltip', {hidden: true})).toBeNull();
101
+ });
102
+
103
+ it('renders no tooltip for a decorative avatar (no name/alt)', () => {
104
+ render(<Avatar src="https://example.com/x.jpg" />);
105
+ expect(screen.queryByRole('tooltip', {hidden: true})).toBeNull();
106
+ });
107
+
108
+ it('renders no tooltip when tooltip is true/default but name is empty', () => {
109
+ render(<Avatar name=" " alt="Profile photo" data-testid="a" />);
110
+ // A whitespace-only name yields nothing to show, and the default tooltip
111
+ // uses `name` (not `alt`), so there is no tooltip.
112
+ expect(screen.queryByRole('tooltip', {hidden: true})).toBeNull();
113
+ // The accessible name still comes from alt, unaffected.
114
+ expect(
115
+ screen.getByRole('img', {name: 'Profile photo'}),
116
+ ).toBeInTheDocument();
117
+ });
118
+
119
+ it('still shows a custom string tooltip when there is no name', () => {
120
+ render(<Avatar tooltip="Anonymous user" data-testid="a" />);
121
+ const tooltip = screen.getByRole('tooltip', {hidden: true});
122
+ expect(tooltip).toHaveTextContent('Anonymous user');
123
+ });
124
+
125
+ it('makes the root focusable while a tooltip is attached', () => {
126
+ render(<Avatar name="Ada Lovelace" data-testid="a" />);
127
+ expect(screen.getByTestId('a')).toHaveAttribute('tabindex', '0');
128
+ });
129
+
130
+ it('does not add a tab stop when the tooltip is disabled', () => {
131
+ render(<Avatar name="Ada Lovelace" tooltip={false} data-testid="a" />);
132
+ expect(screen.getByTestId('a')).not.toHaveAttribute('tabindex');
133
+ });
134
+
135
+ it('keeps the accessible name on the root regardless of the tooltip', () => {
136
+ const {rerender} = render(<Avatar name="Ada Lovelace" />);
137
+ expect(
138
+ screen.getByRole('img', {name: 'Ada Lovelace'}),
139
+ ).toBeInTheDocument();
140
+
141
+ rerender(<Avatar name="Ada Lovelace" tooltip={false} />);
142
+ expect(
143
+ screen.getByRole('img', {name: 'Ada Lovelace'}),
144
+ ).toBeInTheDocument();
145
+
146
+ rerender(<Avatar name="alovelace" tooltip="Ada Lovelace, Eng" />);
147
+ // alt||name still drives the accessible name — not the custom tooltip.
148
+ expect(screen.getByRole('img', {name: 'alovelace'})).toBeInTheDocument();
149
+
150
+ rerender(<Avatar name="alovelace" alt="Ada's profile photo" />);
151
+ expect(
152
+ screen.getByRole('img', {name: "Ada's profile photo"}),
153
+ ).toBeInTheDocument();
154
+ });
155
+
156
+ it('does not describe the root with the default name tooltip (no double-announce)', () => {
157
+ render(<Avatar name="Ada Lovelace" data-testid="a" />);
158
+ // The default name tooltip is visual-only: its text duplicates the root
159
+ // aria-label, so we deliberately do NOT wire aria-describedby (OQ-4).
160
+ expect(screen.getByTestId('a')).not.toHaveAttribute('aria-describedby');
161
+ });
162
+
163
+ it('describes the root with a custom string tooltip (matches Button)', () => {
164
+ render(
165
+ <Avatar
166
+ name="alovelace"
167
+ tooltip="Ada Lovelace, Mathematician"
168
+ data-testid="a"
169
+ />,
170
+ );
171
+ const root = screen.getByTestId('a');
172
+ const describedBy = root.getAttribute('aria-describedby');
173
+ expect(describedBy).toBeTruthy();
174
+ // aria-describedby points at the rendered tooltip layer.
175
+ const tooltip = screen.getByRole('tooltip', {hidden: true});
176
+ expect(tooltip.id).toBeTruthy();
177
+ expect(describedBy).toContain(tooltip.id);
178
+ });
179
+
180
+ it('composes a consumer aria-describedby with the custom tooltip description', () => {
181
+ render(
182
+ <>
183
+ <span id="extra-desc">extra description</span>
184
+ <Avatar
185
+ name="alovelace"
186
+ tooltip="Ada Lovelace, Eng"
187
+ aria-describedby="extra-desc"
188
+ data-testid="a"
189
+ />
190
+ </>,
191
+ );
192
+ const describedBy = screen
193
+ .getByTestId('a')
194
+ .getAttribute('aria-describedby');
195
+ expect(describedBy).toContain('extra-desc');
196
+ });
197
+
198
+ it('preserves a consumer aria-describedby with the default name tooltip', () => {
199
+ render(
200
+ <>
201
+ <span id="extra-desc">extra description</span>
202
+ <Avatar
203
+ name="Ada Lovelace"
204
+ aria-describedby="extra-desc"
205
+ data-testid="a"
206
+ />
207
+ </>,
208
+ );
209
+ // Default name tooltip does not touch aria-describedby, so the consumer
210
+ // value passes through untouched.
211
+ expect(screen.getByTestId('a')).toHaveAttribute(
212
+ 'aria-describedby',
213
+ 'extra-desc',
214
+ );
215
+ });
216
+
217
+ it('forwards the consumer ref to the root while a tooltip is attached', () => {
218
+ const ref = {current: null as HTMLDivElement | null};
219
+ render(<Avatar name="Ada Lovelace" ref={ref} data-testid="a" />);
220
+ expect(ref.current).toBe(screen.getByTestId('a'));
221
+ });
222
+ });
68
223
  });
@@ -4,7 +4,8 @@
4
4
 
5
5
  /**
6
6
  * @file Avatar.tsx
7
- * @input Uses React, HTMLAttributes, ReactNode, useState
7
+ * @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
8
+ * (Tooltip hook) for the optional name-on-hover tooltip
8
9
  * @output Exports Avatar component, AvatarProps, AvatarSize types
9
10
  * @position Core implementation; consumed by index.ts
10
11
  *
@@ -26,8 +27,9 @@ import {
26
27
  } from '../theme/tokens.stylex';
27
28
  import {AvatarSizeContext} from './AvatarSizeContext';
28
29
  import {useAvatarGroup} from '../AvatarGroup/AvatarGroupContext';
29
- import {mergeProps} from '../utils';
30
+ import {mergeProps, mergeRefs} from '../utils';
30
31
  import {themeProps} from '../utils/themeProps';
32
+ import {useTooltip} from '../Tooltip/useTooltip';
31
33
 
32
34
  /**
33
35
  * The offset ratio for positioning elements on a circle's edge at 45°.
@@ -133,6 +135,19 @@ const styles = stylex.create({
133
135
  status: {
134
136
  position: 'absolute',
135
137
  },
138
+ // Visible focus ring for the name-tooltip tab stop, matching the repo-wide
139
+ // focus-visible outline treatment (see Timestamp, Token, Thumbnail). Only
140
+ // applied when a tooltip is active so keyboard users can reveal it.
141
+ focusable: {
142
+ outline: {
143
+ default: null,
144
+ ':focus-visible': `2px solid ${colorVars['--color-accent']}`,
145
+ },
146
+ outlineOffset: {
147
+ default: '0',
148
+ ':focus-visible': '2px',
149
+ },
150
+ },
136
151
  });
137
152
 
138
153
  /**
@@ -216,6 +231,19 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
216
231
  * Typically used for status indicators or badges.
217
232
  */
218
233
  status?: ReactNode;
234
+ /**
235
+ * Tooltip shown on hover (and keyboard focus).
236
+ * - omitted / `true`: show the avatar's `name`
237
+ * - a string: show that text instead
238
+ * - `false`: no tooltip
239
+ *
240
+ * The avatar owns this tooltip. It is NOT auto-disabled when wrapped in your
241
+ * own Tooltip/HoverCard — set `tooltip={false}` if you provide your own
242
+ * overlay. No tooltip is shown if `tooltip` is `true`/omitted and there is
243
+ * no (non-whitespace) `name`.
244
+ * @default true
245
+ */
246
+ tooltip?: string | boolean;
219
247
  }
220
248
 
221
249
  /**
@@ -265,6 +293,8 @@ function DefaultIcon({size}: {size: number}) {
265
293
  * <Avatar src="/user.jpg" name="John Doe" />
266
294
  * <Avatar name="Jane Smith" size="xl" />
267
295
  * <Avatar src="/user.jpg" status={<OnlineIndicator />} />
296
+ * <Avatar name="jsmith" tooltip="Jane Smith, Staff Engineer" />
297
+ * <Avatar name="Jane" tooltip={false} />
268
298
  * ```
269
299
  */
270
300
  export function Avatar({
@@ -275,6 +305,7 @@ export function Avatar({
275
305
  size = 'md',
276
306
  src,
277
307
  status,
308
+ tooltip = true,
278
309
  xstyle,
279
310
  className,
280
311
  style,
@@ -303,19 +334,66 @@ export function Avatar({
303
334
  const resolvedSize = avatarGroup?.size ?? size;
304
335
  const numericSize = useMemo(() => resolveSize(resolvedSize), [resolvedSize]);
305
336
 
306
- return (
337
+ // Resolve the tooltip content:
338
+ // - `false` → no tooltip
339
+ // - a string → that string
340
+ // - `true` / omitted → the `name` (a whitespace-only name yields nothing)
341
+ // Note: the *visible* tooltip prefers `name` (not `alt`); the *accessible
342
+ // name* on the root still uses `alt || name` above, independent of this.
343
+ const tooltipContent =
344
+ tooltip === false
345
+ ? undefined
346
+ : typeof tooltip === 'string'
347
+ ? tooltip
348
+ : name;
349
+ const trimmedTooltip = tooltipContent?.trim();
350
+ const showTooltip = trimmedTooltip != null && trimmedTooltip !== '';
351
+ // Whether the tooltip text is a consumer-authored override (a custom string)
352
+ // rather than the default name. A custom description is worth wiring to
353
+ // `aria-describedby` (it adds information, matching Button); the default name
354
+ // tooltip is visual-only — its text duplicates the root `aria-label`, so
355
+ // describing it too would double-announce the same name (OQ-4).
356
+ const isCustomTooltip = typeof tooltip === 'string';
357
+
358
+ // Own the name tooltip via the Tooltip hook (the Button pattern), which
359
+ // returns `describedBy` as a value we choose whether to apply — the only way
360
+ // to satisfy the per-case aria-describedby rule (default name: none; custom
361
+ // string: describe) without editing Tooltip. `focusTrigger: 'auto'` shows the
362
+ // tooltip on keyboard focus once the root is made focusable (below).
363
+ const tooltipHook = useTooltip({
364
+ placement: 'above',
365
+ isEnabled: showTooltip,
366
+ });
367
+ const rootRef = mergeRefs(ref, showTooltip ? tooltipHook.ref : undefined);
368
+ const describedByProp =
369
+ showTooltip && isCustomTooltip
370
+ ? {
371
+ 'aria-describedby':
372
+ [props['aria-describedby'], tooltipHook.describedBy]
373
+ .filter(Boolean)
374
+ .join(' ') || undefined,
375
+ }
376
+ : null;
377
+
378
+ const avatarElement = (
307
379
  <AvatarSizeContext value={numericSize}>
308
380
  <div
309
381
  {...props}
310
- ref={ref}
382
+ ref={rootRef}
311
383
  role={isDecorative ? 'presentation' : 'img'}
312
384
  aria-label={isDecorative ? undefined : accessibleName}
313
385
  aria-hidden={isDecorative || undefined}
386
+ // The root is a div[role="img"], not natively focusable. When a name
387
+ // tooltip is active, add a tab stop so keyboard users can reveal it
388
+ // (WCAG 1.4.13 / 2.1.1) — matching Timestamp/Button. Only while active.
389
+ tabIndex={showTooltip ? 0 : undefined}
314
390
  data-testid={testId}
391
+ {...describedByProp}
315
392
  {...mergeProps(
316
393
  themeProps('avatar', {size: resolvedSize}),
317
394
  stylex.props(
318
395
  styles.wrapper,
396
+ showTooltip && styles.focusable,
319
397
  avatarGroup && groupStyles.ring,
320
398
  avatarGroup && groupStyles.overlap,
321
399
  avatarGroup && groupDynamicStyles.overlap(-avatarGroup.overlap),
@@ -368,6 +446,20 @@ export function Avatar({
368
446
  </div>
369
447
  </AvatarSizeContext>
370
448
  );
449
+
450
+ if (!showTooltip) {
451
+ return avatarElement;
452
+ }
453
+
454
+ // Render the tooltip as a sibling (no wrapper DOM) — the hook's ref is already
455
+ // attached to the root via `rootRef` above. `trimmedTooltip` is guaranteed
456
+ // non-empty here.
457
+ return (
458
+ <>
459
+ {avatarElement}
460
+ {tooltipHook.renderTooltip(trimmedTooltip)}
461
+ </>
462
+ );
371
463
  }
372
464
 
373
465
  Avatar.displayName = 'Avatar';
@@ -7,23 +7,27 @@ export const docs = {
7
7
  subComponentOf: 'Avatar',
8
8
  displayName: 'Avatar Status Dot',
9
9
  isHiddenFromOverview: true,
10
- description: 'Size-aware status indicator dot that reads avatar size from context and scales proportionally.',
10
+ description:
11
+ 'Size-aware status indicator dot that reads avatar size from context and scales proportionally. Each variant pairs a colour with a distinct built-in shape (filled, ring, minus) so status is never conveyed by colour alone (WCAG 1.4.1).',
11
12
  props: [
12
13
  {
13
14
  name: 'variant',
14
15
  type: "'success' | 'neutral' | 'error'",
15
- description: 'Semantic color variant of the dot.',
16
+ description:
17
+ 'Semantic variant pairing colour with a distinct shape: success = filled green dot, neutral = grey ring, error = red dot with a minus bar.',
16
18
  default: "'success'",
17
19
  },
18
20
  {
19
21
  name: 'label',
20
22
  type: 'string',
21
- description: 'Accessible label for screen readers.',
23
+ description:
24
+ 'Accessible label describing the status. Not announced inside an Avatar today (the Avatar root is role="img", which prunes child semantics) — pass it anyway; an Avatar-level name-composition fix is planned.',
22
25
  },
23
26
  {
24
27
  name: 'icon',
25
28
  type: 'ReactNode',
26
- description: 'Icon centered inside the dot (hidden at tiny sizes).',
29
+ description:
30
+ 'Icon centered inside the dot (hidden at tiny sizes). A rendered icon replaces the built-in shape glyph, so use a different icon per status.',
27
31
  slotElements: [
28
32
  {
29
33
  __element: 'Icon',
@@ -41,23 +45,27 @@ export const docsZh = {
41
45
  name: 'AvatarStatusDot',
42
46
  isHiddenFromOverview: true,
43
47
  displayName: 'Avatar Status Dot',
44
- description: '尺寸感知的状态指示点,从上下文中读取头像尺寸并等比缩放。',
48
+ description:
49
+ '尺寸感知的状态指示点,从上下文中读取头像尺寸并等比缩放。每个变体将颜色与独特形状(实心、圆环、横杠)配对,确保状态不仅靠颜色传达(WCAG 1.4.1)。',
45
50
  props: [
46
51
  {
47
52
  name: 'variant',
48
53
  type: "'success' | 'neutral' | 'error'",
49
- description: '状态点的语义颜色变体。',
54
+ description:
55
+ '语义变体,颜色与形状配对:success = 绿色实心点,neutral = 灰色圆环,error = 带横杠的红色点。',
50
56
  default: "'success'",
51
57
  },
52
58
  {
53
59
  name: 'label',
54
60
  type: 'string',
55
- description: '屏幕阅读器的无障碍标签。',
61
+ description:
62
+ '描述状态的无障碍标签。目前在 Avatar 内不会被朗读(Avatar 根元素为 role="img",会裁剪子元素语义)——仍请传入;Avatar 层面的名称合成修复已在计划中。',
56
63
  },
57
64
  {
58
65
  name: 'icon',
59
66
  type: 'ReactNode',
60
- description: '居中显示在状态点内的图标(tiny 尺寸时隐藏)。',
67
+ description:
68
+ '居中显示在状态点内的图标(tiny 尺寸时隐藏)。渲染的图标会替换内置形状标记,请为每个状态使用不同的图标。',
61
69
  },
62
70
  ],
63
71
  };
@@ -66,10 +74,13 @@ export const docsDense = {
66
74
  name: 'AvatarStatusDot',
67
75
  isHiddenFromOverview: true,
68
76
  displayName: 'Avatar Status Dot',
69
- description: 'size-aware status dot, reads avatar size from context + scales proportionally',
77
+ description:
78
+ 'size-aware status dot, reads avatar size from context + scales proportionally; variants pair colour with shape (filled/ring/minus) per WCAG 1.4.1',
70
79
  propDescriptions: {
71
- variant: 'semantic color variant',
72
- label: 'accessible label for screen readers',
73
- icon: 'icon centered in dot (hidden at tiny sizes)',
80
+ variant:
81
+ 'colour + shape variant: success filled, neutral ring, error minus',
82
+ label:
83
+ 'accessible status label (not yet announced inside Avatar — role="img" prunes children)',
84
+ icon: 'icon centered in dot (hidden at tiny sizes); replaces the built-in shape glyph — differ it per status',
74
85
  },
75
86
  };