@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
@@ -0,0 +1,284 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {describe, it, expect} from 'vitest';
4
+ import {render, screen} from '@testing-library/react';
5
+ import * as stylex from '@stylexjs/stylex';
6
+ import {colorVars} from '../theme/tokens.stylex';
7
+ import {Avatar} from './Avatar';
8
+ import {AvatarStatusDot, type AvatarStatusDotVariant} from './AvatarStatusDot';
9
+
10
+ const GLYPH_SELECTOR = '.astryx-avatar-status-dot-glyph';
11
+ const DOT_SELECTOR = '.astryx-avatar-status-dot';
12
+
13
+ /**
14
+ * Renders an AvatarStatusDot inside an Avatar of the given numeric size and
15
+ * returns the dot root element. Size tiers: <=36 -> 10px dot (no icons),
16
+ * 40-72 -> 20px dot, >=96 -> 32px dot.
17
+ */
18
+ function renderDot(
19
+ dotProps: React.ComponentProps<typeof AvatarStatusDot>,
20
+ avatarSize: 36 | 48 | 128 = 48,
21
+ ) {
22
+ const {container} = render(
23
+ <Avatar
24
+ name="Ada Lovelace"
25
+ size={avatarSize}
26
+ status={<AvatarStatusDot {...dotProps} />}
27
+ />,
28
+ );
29
+ const dot = container.querySelector(DOT_SELECTOR);
30
+ expect(dot).not.toBeNull();
31
+ return dot as HTMLElement;
32
+ }
33
+
34
+ describe('AvatarStatusDot', () => {
35
+ describe('shape glyphs (WCAG 1.4.1 — colour is not the only signal)', () => {
36
+ it('renders no glyph for success: the plain filled dot is the reference shape', () => {
37
+ const dot = renderDot({variant: 'success'});
38
+ expect(dot.querySelector(GLYPH_SELECTOR)).toBeNull();
39
+ });
40
+
41
+ it('renders a ring glyph for neutral', () => {
42
+ const dot = renderDot({variant: 'neutral'});
43
+ const glyph = dot.querySelector(GLYPH_SELECTOR);
44
+ expect(glyph).not.toBeNull();
45
+ expect(glyph).toHaveAttribute('data-shape', 'ring');
46
+ });
47
+
48
+ it('renders a minus glyph for error', () => {
49
+ const dot = renderDot({variant: 'error'});
50
+ const glyph = dot.querySelector(GLYPH_SELECTOR);
51
+ expect(glyph).not.toBeNull();
52
+ expect(glyph).toHaveAttribute('data-shape', 'minus');
53
+ });
54
+
55
+ it('hides the glyph from assistive tech: it is a visual redundancy of the status', () => {
56
+ const dot = renderDot({variant: 'error'});
57
+ expect(dot.querySelector(GLYPH_SELECTOR)).toHaveAttribute(
58
+ 'aria-hidden',
59
+ 'true',
60
+ );
61
+ });
62
+
63
+ it('renders glyphs at every size tier', () => {
64
+ for (const size of [36, 48, 128] as const) {
65
+ const dot = renderDot({variant: 'neutral'}, size);
66
+ expect(dot.querySelector(GLYPH_SELECTOR)).not.toBeNull();
67
+ }
68
+ });
69
+
70
+ it('draws the glyph as an inline svg sized to the dot inner field', () => {
71
+ // The glyph is a stroked <svg>, matching how the rest of the system
72
+ // draws marks (Icon's defaultIcons, the CheckboxInput checkmark).
73
+ // Its viewBox is 1 user unit per px of the dot's inner field
74
+ // (dot minus both borders), so stroke widths below are literal px.
75
+ const cases = [
76
+ [36, 8],
77
+ [48, 16],
78
+ [128, 24],
79
+ ] as const;
80
+ for (const [avatarSize, field] of cases) {
81
+ const glyph = renderDot({variant: 'neutral'}, avatarSize).querySelector(
82
+ GLYPH_SELECTOR,
83
+ ) as SVGElement;
84
+ expect(glyph.tagName.toLowerCase(), `avatar ${avatarSize}`).toBe('svg');
85
+ expect(glyph.getAttribute('viewBox'), `avatar ${avatarSize}`).toBe(
86
+ `0 0 ${field} ${field}`,
87
+ );
88
+ expect(glyph.getAttribute('width'), `avatar ${avatarSize}`).toBe(
89
+ String(field),
90
+ );
91
+ expect(glyph.getAttribute('height'), `avatar ${avatarSize}`).toBe(
92
+ String(field),
93
+ );
94
+ }
95
+ });
96
+
97
+ it('strokes the ring at the system glyph weight, not a quarter of the field', () => {
98
+ // stroke ~= field / 12, floored at 1px for the smallest tier, which
99
+ // puts these on the icon family's 1 / 1.5 / 2 ladder (~6-12% of the
100
+ // field) instead of the 25% band a CSS box cutout produced.
101
+ const cases = [
102
+ // avatar size, field, stroke, radius = (field - stroke) / 2
103
+ [36, 8, 1, 3.5],
104
+ [48, 16, 1.5, 7.25],
105
+ [128, 24, 2, 11],
106
+ ] as const;
107
+ for (const [avatarSize, field, stroke, radius] of cases) {
108
+ const circle = renderDot(
109
+ {variant: 'neutral'},
110
+ avatarSize,
111
+ ).querySelector(`${GLYPH_SELECTOR} circle`) as SVGCircleElement;
112
+ expect(circle, `avatar ${avatarSize}`).not.toBeNull();
113
+ expect(
114
+ circle.getAttribute('stroke-width'),
115
+ `avatar ${avatarSize}`,
116
+ ).toBe(String(stroke));
117
+ // Radius is to the stroke centreline, so the ring's outer edge lands
118
+ // exactly on the inner field and never clips against the border.
119
+ expect(circle.getAttribute('r'), `avatar ${avatarSize}`).toBe(
120
+ String(radius),
121
+ );
122
+ expect(circle.getAttribute('cx'), `avatar ${avatarSize}`).toBe(
123
+ String(field / 2),
124
+ );
125
+ expect(circle.getAttribute('cy'), `avatar ${avatarSize}`).toBe(
126
+ String(field / 2),
127
+ );
128
+ expect(circle.getAttribute('fill'), `avatar ${avatarSize}`).toBe(
129
+ 'none',
130
+ );
131
+ }
132
+ });
133
+
134
+ it('strokes the minus bar at the same weight, spanning 75% of the field with round caps', () => {
135
+ // Ends are inset by half the stroke so the round caps land inside the
136
+ // 75% span rather than overhanging it.
137
+ const cases = [
138
+ // avatar size, field, stroke, x1, x2
139
+ [36, 8, 1, 1.5, 6.5],
140
+ [48, 16, 1.5, 2.75, 13.25],
141
+ [128, 24, 2, 4, 20],
142
+ ] as const;
143
+ for (const [avatarSize, field, stroke, x1, x2] of cases) {
144
+ const line = renderDot({variant: 'error'}, avatarSize).querySelector(
145
+ `${GLYPH_SELECTOR} line`,
146
+ ) as SVGLineElement;
147
+ expect(line, `avatar ${avatarSize}`).not.toBeNull();
148
+ expect(line.getAttribute('stroke-width'), `avatar ${avatarSize}`).toBe(
149
+ String(stroke),
150
+ );
151
+ expect(line.getAttribute('x1'), `avatar ${avatarSize}`).toBe(
152
+ String(x1),
153
+ );
154
+ expect(line.getAttribute('x2'), `avatar ${avatarSize}`).toBe(
155
+ String(x2),
156
+ );
157
+ expect(line.getAttribute('y1'), `avatar ${avatarSize}`).toBe(
158
+ String(field / 2),
159
+ );
160
+ expect(line.getAttribute('y2'), `avatar ${avatarSize}`).toBe(
161
+ String(field / 2),
162
+ );
163
+ expect(
164
+ line.getAttribute('stroke-linecap'),
165
+ `avatar ${avatarSize}`,
166
+ ).toBe('round');
167
+ }
168
+ });
169
+
170
+ it('paints both glyphs from currentColor so the dot owns the ink colour', () => {
171
+ for (const variant of ['neutral', 'error'] as const) {
172
+ const mark = renderDot({variant}, 48).querySelector(
173
+ `${GLYPH_SELECTOR} circle, ${GLYPH_SELECTOR} line`,
174
+ ) as SVGElement;
175
+ expect(mark.getAttribute('stroke'), variant).toBe('currentColor');
176
+ }
177
+ });
178
+
179
+ it('fills the ring variant with surface and inks it with the variant colour', () => {
180
+ // A ring only reads as hollow if its interior is not the variant
181
+ // colour, so `neutral` inverts: surface fill, coloured stroke.
182
+ // StyleX atomic classes are deterministic, so a local probe pins it.
183
+ const probe = stylex.create({
184
+ ring: {
185
+ backgroundColor: colorVars['--color-background-surface'],
186
+ color: colorVars['--color-text-secondary'],
187
+ },
188
+ });
189
+ const dot = renderDot({variant: 'neutral'}, 48);
190
+ const atomicClasses = (stylex.props(probe.ring).className ?? '')
191
+ .split(' ')
192
+ // Dev mode prepends a per-file `File__style.key` debug class that
193
+ // legitimately differs between the probe and the component.
194
+ .filter(cls => !cls.includes('__'));
195
+ expect(atomicClasses.length).toBeGreaterThan(0);
196
+ for (const cls of atomicClasses) {
197
+ expect(dot.className).toContain(cls);
198
+ }
199
+ });
200
+
201
+ it('lets a user icon inherit the dot ink instead of hard-coding surface', () => {
202
+ // Surface ink on the surface-filled ring variant would be invisible.
203
+ const probe = stylex.create({
204
+ surfaceInk: {color: colorVars['--color-background-surface']},
205
+ });
206
+ renderDot(
207
+ {variant: 'neutral', icon: <svg data-testid="user-icon" />},
208
+ 48,
209
+ );
210
+ const iconWrapper = screen.getByTestId('user-icon')
211
+ .parentElement as HTMLElement;
212
+ const surfaceInkClasses = (stylex.props(probe.surfaceInk).className ?? '')
213
+ .split(' ')
214
+ .filter(cls => !cls.includes('__'));
215
+ expect(surfaceInkClasses.length).toBeGreaterThan(0);
216
+ for (const cls of surfaceInkClasses) {
217
+ expect(iconWrapper.className).not.toContain(cls);
218
+ }
219
+ });
220
+
221
+ it('renders no glyph for custom augmented variants (documented: they must bring their own non-colour mark)', () => {
222
+ const dot = renderDot({variant: 'away' as AvatarStatusDotVariant});
223
+ expect(dot.querySelector(GLYPH_SELECTOR)).toBeNull();
224
+ });
225
+ });
226
+
227
+ describe('glyph and icon interplay', () => {
228
+ it('suppresses the glyph when a user icon renders: the icon is the non-colour mark', () => {
229
+ const dot = renderDot(
230
+ {variant: 'error', icon: <svg data-testid="user-icon" />},
231
+ 48,
232
+ );
233
+ expect(screen.getByTestId('user-icon')).toBeInTheDocument();
234
+ expect(dot.querySelector(GLYPH_SELECTOR)).toBeNull();
235
+ });
236
+
237
+ it('keeps the glyph at the smallest tier where icons never render', () => {
238
+ const dot = renderDot(
239
+ {variant: 'error', icon: <svg data-testid="user-icon" />},
240
+ 36,
241
+ );
242
+ expect(screen.queryByTestId('user-icon')).not.toBeInTheDocument();
243
+ expect(dot.querySelector(GLYPH_SELECTOR)).not.toBeNull();
244
+ });
245
+
246
+ it('keeps the glyph for non-renderable icons: booleans from `cond && <Icon />` render nothing', () => {
247
+ for (const nonRenderable of [true, false, ''] as const) {
248
+ const dot = renderDot({variant: 'error', icon: nonRenderable}, 48);
249
+ expect(
250
+ dot.querySelector(GLYPH_SELECTOR),
251
+ `icon={${JSON.stringify(nonRenderable)}}`,
252
+ ).not.toBeNull();
253
+ }
254
+ });
255
+ });
256
+
257
+ describe('existing contract', () => {
258
+ it('exposes role="img" with the label as accessible name when label is provided', () => {
259
+ const dot = renderDot({variant: 'success', label: 'Online'});
260
+ expect(dot).toHaveAttribute('role', 'img');
261
+ expect(dot).toHaveAttribute('aria-label', 'Online');
262
+ });
263
+
264
+ it('has no img role without a label', () => {
265
+ const dot = renderDot({variant: 'success'});
266
+ expect(dot).not.toHaveAttribute('role');
267
+ });
268
+
269
+ it('reflects the variant as a data attribute for theming', () => {
270
+ const dot = renderDot({variant: 'neutral'});
271
+ expect(dot).toHaveAttribute('data-variant', 'neutral');
272
+ });
273
+
274
+ it('renders the user icon at tiers with room for it, hidden from assistive tech', () => {
275
+ renderDot(
276
+ {variant: 'success', icon: <svg data-testid="user-icon" />},
277
+ 128,
278
+ );
279
+ const icon = screen.getByTestId('user-icon');
280
+ expect(icon).toBeInTheDocument();
281
+ expect(icon.parentElement).toHaveAttribute('aria-hidden', 'true');
282
+ });
283
+ });
284
+ });
@@ -20,37 +20,49 @@ import type {BaseProps} from '../BaseProps';
20
20
  import * as stylex from '@stylexjs/stylex';
21
21
  import {colorVars, radiusVars} from '../theme/tokens.stylex';
22
22
  import {AvatarSizeContext} from './AvatarSizeContext';
23
- import {mergeProps} from '../utils';
23
+ import {isRenderable, mergeProps} from '../utils';
24
24
  import {themeProps} from '../utils/themeProps';
25
25
 
26
26
  /**
27
- * Resolves the status dot size, border width, and icon size based on the
28
- * avatar size.
27
+ * Discrete size tier of the status dot, derived from the avatar size.
28
+ * Keys the built-in shape glyph's stroke weight — see `GLYPH_STROKE_WIDTH`.
29
+ */
30
+ type StatusDotSizeTier = 'small' | 'medium' | 'large';
31
+
32
+ /**
33
+ * Resolves the status dot size, border width, icon size, and size tier
34
+ * based on the avatar size.
29
35
  *
30
36
  * Uses discrete size tiers rather than a continuous ratio so the dot
31
37
  * looks intentional at every avatar size:
32
38
  *
33
- * | Avatar size | Dot | Border | Icon |
34
- * |--------------|------|--------|------|
35
- * | ≤ 36px | 10px | 1px | — |
36
- * | 40–72px | 20px | 2px | 12px |
37
- * | ≥ 96px | 32px | 4px | 18px |
39
+ * | Avatar size | Tier | Dot | Border | Icon | Field | Glyph stroke |
40
+ * |--------------|--------|------|--------|------|-------|--------------|
41
+ * | ≤ 36px | small | 10px | 1px | — | 8px | 1px |
42
+ * | 40–72px | medium | 20px | 2px | 12px | 16px | 1.5px |
43
+ * | ≥ 96px | large | 32px | 4px | 18px | 24px | 2px |
44
+ *
45
+ * "Field" is the dot's inner field — the dot minus both borders — that the
46
+ * shape glyph is drawn into; see `GLYPH_STROKE_WIDTH` for the stroke ladder.
38
47
  *
39
48
  * Icons are not rendered at the smallest tier — there isn't enough
40
- * room for them to be legible.
49
+ * room for them to be legible. The built-in shape glyphs (see
50
+ * `glyphShapeMap`) do render there, so status stays distinguishable
51
+ * without colour at every size.
41
52
  */
42
53
  function resolveStatusDotSize(avatarSize: number): {
43
54
  dotSize: number;
44
55
  borderWidth: number;
45
56
  iconSize: number;
57
+ tier: StatusDotSizeTier;
46
58
  } {
47
59
  if (avatarSize <= 36) {
48
- return {dotSize: 10, borderWidth: 1, iconSize: 0};
60
+ return {dotSize: 10, borderWidth: 1, iconSize: 0, tier: 'small'};
49
61
  }
50
62
  if (avatarSize <= 72) {
51
- return {dotSize: 20, borderWidth: 2, iconSize: 12};
63
+ return {dotSize: 20, borderWidth: 2, iconSize: 12, tier: 'medium'};
52
64
  }
53
- return {dotSize: 32, borderWidth: 4, iconSize: 18};
65
+ return {dotSize: 32, borderWidth: 4, iconSize: 18, tier: 'large'};
54
66
  }
55
67
 
56
68
  /**
@@ -65,6 +77,13 @@ function resolveStatusDotSize(avatarSize: number): {
65
77
  * }
66
78
  * }
67
79
  * ```
80
+ *
81
+ * Custom variants render no background fill, no ink colour, and no built-in
82
+ * shape glyph — the theme must supply the fill and, if it passes an `icon`,
83
+ * a `color` for it to paint with. It should also supply a non-colour mark
84
+ * so the status is not distinguishable by colour alone (a WCAG 1.4.1
85
+ * failure): pass `icon`, or theme a glyph onto the dot via
86
+ * `.astryx-avatar-status-dot[data-variant="..."]` (e.g. a `::before` mark).
68
87
  */
69
88
  export interface AvatarStatusDotVariantMap {
70
89
  success: true;
@@ -80,12 +99,14 @@ export type AvatarStatusDotVariant = keyof AvatarStatusDotVariantMap;
80
99
  export interface AvatarStatusDotProps extends BaseProps<HTMLDivElement> {
81
100
  ref?: React.Ref<HTMLDivElement>;
82
101
  /**
83
- * The semantic color variant of the dot.
84
- * - `success` — green dot (e.g. online, accepted)
85
- * - `neutral` — gray dot (e.g. offline, pending)
86
- * - `error` — red dot (e.g. busy, rejected)
102
+ * The semantic variant of the dot. Each variant pairs a colour with a
103
+ * distinct built-in shape so status is never conveyed by colour alone
104
+ * (WCAG 2.1 SC 1.4.1):
105
+ * - `success` — filled green dot (e.g. online, accepted)
106
+ * - `neutral` — grey ring (e.g. away, offline, pending)
107
+ * - `error` — red dot with a minus bar (e.g. busy, do not disturb)
87
108
  *
88
- * Matches the `variant` convention from `StatusDot`.
109
+ * Matches the `variant` naming convention from `StatusDot`.
89
110
  * @default 'success'
90
111
  */
91
112
  variant?: AvatarStatusDotVariant;
@@ -93,6 +114,11 @@ export interface AvatarStatusDotProps extends BaseProps<HTMLDivElement> {
93
114
  * Accessible label for the status dot.
94
115
  * Describes the meaning of the indicator for screen readers
95
116
  * (e.g. "Online", "Accepted", "John Doe is busy").
117
+ *
118
+ * Note: inside an Avatar the label is currently not announced — the
119
+ * Avatar root is `role="img"`, which prunes descendant semantics.
120
+ * Pass it anyway; composing status into the avatar's accessible name
121
+ * is a planned Avatar-level fix.
96
122
  */
97
123
  label?: string;
98
124
  /**
@@ -101,6 +127,14 @@ export interface AvatarStatusDotProps extends BaseProps<HTMLDivElement> {
101
127
  * The icon is automatically sized to fit the dot and hidden
102
128
  * at the smallest avatar sizes where there isn't enough room.
103
129
  *
130
+ * A rendered icon replaces the variant's built-in shape glyph, so use a
131
+ * different icon per status — the same icon on every variant leaves the
132
+ * statuses distinguishable by colour alone (WCAG 1.4.1). At the smallest
133
+ * avatar sizes the built-in glyph still shows instead of the icon.
134
+ * Booleans and empty strings are ignored (safe for `cond && <Icon />`),
135
+ * but a component that renders nothing still counts as an icon and
136
+ * suppresses the glyph.
137
+ *
104
138
  * @example
105
139
  * ```
106
140
  * <AvatarStatusDot variant="success" label="Verified" icon={<CheckIcon />} />
@@ -119,20 +153,29 @@ const styles = stylex.create({
119
153
  alignItems: 'center',
120
154
  justifyContent: 'center',
121
155
  },
156
+ // Each variant sets both the plate colour and the ink colour. Everything
157
+ // drawn on the dot — the shape glyph and any user `icon` — paints from
158
+ // `currentColor`, so the two can never drift out of contrast.
122
159
  success: {
123
160
  backgroundColor: colorVars['--color-success'],
161
+ color: colorVars['--color-background-surface'],
124
162
  },
163
+ // The ring variant inverts: a hollow shape only reads as hollow if its
164
+ // interior is not the variant colour, so the plate is surface and the
165
+ // colour moves to the stroke. This also keeps a user `icon` legible on
166
+ // it — surface ink on a surface plate would be invisible.
125
167
  neutral: {
126
- backgroundColor: colorVars['--color-text-secondary'],
168
+ backgroundColor: colorVars['--color-background-surface'],
169
+ color: colorVars['--color-text-secondary'],
127
170
  },
128
171
  error: {
129
172
  backgroundColor: colorVars['--color-error'],
173
+ color: colorVars['--color-background-surface'],
130
174
  },
131
175
  icon: {
132
176
  display: 'flex',
133
177
  alignItems: 'center',
134
178
  justifyContent: 'center',
135
- color: colorVars['--color-background-surface'],
136
179
  lineHeight: 0,
137
180
  },
138
181
  });
@@ -157,10 +200,112 @@ const variantStyleMap: Partial<
157
200
  error: styles.error,
158
201
  };
159
202
 
203
+ /**
204
+ * Built-in shape glyph per variant, so each status differs by shape and not
205
+ * only by colour (WCAG 2.1 SC 1.4.1). The glyph is a stroked inline SVG
206
+ * painted in `currentColor`:
207
+ * - `ring` — a stroked circle on a surface plate; the dot reads as hollow
208
+ * (away/offline).
209
+ * - `minus` — a round-capped bar across the filled dot; the dot reads as
210
+ * "do not disturb" (busy).
211
+ *
212
+ * `success` stays the plain filled dot — filled, hollow, and barred are the
213
+ * three distinct fill topologies. Custom augmented variants have no entry
214
+ * and render no glyph; see the `AvatarStatusDotVariantMap` docs.
215
+ */
216
+ type AvatarStatusDotGlyphShape = 'ring' | 'minus';
217
+
218
+ const glyphShapeMap: Partial<
219
+ Record<AvatarStatusDotVariant, AvatarStatusDotGlyphShape>
220
+ > = {
221
+ neutral: 'ring',
222
+ error: 'minus',
223
+ };
224
+
225
+ /**
226
+ * Glyph stroke weight per tier, in px of the dot's inner field.
227
+ *
228
+ * Roughly `field / 12`, floored at 1px so the smallest tier stays visible.
229
+ * That lands on the 1 / 1.5 / 2 ladder the rest of the system draws with —
230
+ * `Icon`'s default set strokes at 1.5 in a 24 viewBox, as does the
231
+ * `CheckboxInput` checkmark — rather than the 25% band a CSS box cutout
232
+ * produced, which was several times heavier than any other glyph we ship.
233
+ */
234
+ const GLYPH_STROKE_WIDTH: Record<StatusDotSizeTier, number> = {
235
+ small: 1,
236
+ medium: 1.5,
237
+ large: 2,
238
+ };
239
+
240
+ /** Fraction of the inner field the minus bar spans, cap to cap. */
241
+ const MINUS_BAR_SPAN = 0.75;
242
+
243
+ /**
244
+ * The built-in shape glyph, drawn as a stroked inline SVG in `currentColor`.
245
+ *
246
+ * Stroking (rather than a CSS box) is what buys sub-pixel control and round
247
+ * line caps, so the mark stays intentional at every tier — including the
248
+ * 10px dot, where a box cutout can only land on whole pixels.
249
+ *
250
+ * `viewBox` is one user unit per px of the inner field, so every value here
251
+ * is literal px.
252
+ */
253
+ function StatusDotGlyph({
254
+ shape,
255
+ field,
256
+ stroke,
257
+ }: {
258
+ shape: AvatarStatusDotGlyphShape;
259
+ field: number;
260
+ stroke: number;
261
+ }) {
262
+ const center = field / 2;
263
+ return (
264
+ <svg
265
+ aria-hidden="true"
266
+ viewBox={`0 0 ${field} ${field}`}
267
+ width={field}
268
+ height={field}
269
+ fill="none"
270
+ {...themeProps('avatar-status-dot-glyph', {shape})}>
271
+ {shape === 'ring' ? (
272
+ // Radius is to the stroke centreline, so the ring's outer edge lands
273
+ // exactly on the inner field and never clips against the border.
274
+ <circle
275
+ cx={center}
276
+ cy={center}
277
+ r={(field - stroke) / 2}
278
+ fill="none"
279
+ stroke="currentColor"
280
+ strokeWidth={stroke}
281
+ />
282
+ ) : (
283
+ // Ends inset by half the stroke so the round caps land inside the
284
+ // span rather than overhanging it.
285
+ <line
286
+ x1={(field * (1 - MINUS_BAR_SPAN)) / 2 + stroke / 2}
287
+ y1={center}
288
+ x2={(field * (1 + MINUS_BAR_SPAN)) / 2 - stroke / 2}
289
+ y2={center}
290
+ stroke="currentColor"
291
+ strokeWidth={stroke}
292
+ strokeLinecap="round"
293
+ />
294
+ )}
295
+ </svg>
296
+ );
297
+ }
298
+
160
299
  /**
161
300
  * A status indicator dot that automatically scales to match the parent
162
301
  * Avatar's size.
163
302
  *
303
+ * Each variant pairs a colour with a distinct built-in shape (filled dot,
304
+ * ring, minus bar) so status stays distinguishable without colour
305
+ * perception (WCAG 2.1 SC 1.4.1). Themes can target the shape glyph via
306
+ * the `astryx-avatar-status-dot-glyph` class and its `data-shape`
307
+ * attribute.
308
+ *
164
309
  * Must be used inside an Avatar's `status` prop so it can read
165
310
  * the avatar size from context.
166
311
  *
@@ -189,7 +334,12 @@ export function AvatarStatusDot({
189
334
  ...props
190
335
  }: AvatarStatusDotProps) {
191
336
  const avatarSize = use(AvatarSizeContext);
192
- const {dotSize, borderWidth, iconSize} = resolveStatusDotSize(avatarSize);
337
+ const {dotSize, borderWidth, iconSize, tier} =
338
+ resolveStatusDotSize(avatarSize);
339
+ const showsIcon = isRenderable(icon) && iconSize > 0;
340
+ // A rendered icon is itself a non-colour mark; overlaying both cutouts in
341
+ // the dot's small inner field would make each illegible.
342
+ const glyphShape = showsIcon ? undefined : glyphShapeMap[variant];
193
343
 
194
344
  return (
195
345
  <div
@@ -207,13 +357,20 @@ export function AvatarStatusDot({
207
357
  className,
208
358
  style,
209
359
  )}>
210
- {icon && iconSize > 0 && (
360
+ {showsIcon && (
211
361
  <span
212
362
  aria-hidden="true"
213
363
  {...stylex.props(styles.icon, dynamicStyles.iconSize(iconSize))}>
214
364
  {icon}
215
365
  </span>
216
366
  )}
367
+ {glyphShape && (
368
+ <StatusDotGlyph
369
+ shape={glyphShape}
370
+ field={dotSize - borderWidth * 2}
371
+ stroke={GLYPH_STROKE_WIDTH[tier]}
372
+ />
373
+ )}
217
374
  </div>
218
375
  );
219
376
  }
@@ -81,6 +81,13 @@ export const docs = {
81
81
  'Container type: card has border-radius; section is full-width with no border-radius for page-level use.',
82
82
  default: "'card'",
83
83
  },
84
+ {
85
+ name: 'elevation',
86
+ type: "'none' | 'low' | 'med' | 'high'",
87
+ description:
88
+ 'Resting shadow depth. Use for a floating banner that hovers above content; `none` is the default inline banner. A `card`-container banner rounds its shadow to match.',
89
+ default: "'none'",
90
+ },
84
91
  {
85
92
  name: 'children',
86
93
  type: 'ReactNode',
@@ -157,6 +164,7 @@ export const docsZh = {
157
164
  {name: 'onDismiss', type: '() => void', description: '点击关闭按钮时调用;无论是否提供此回调,横幅都会自动隐藏。'},
158
165
  {name: 'endContent', type: 'ReactNode', description: '渲染在头部区域末端对齐的操作内容,通常是按钮或链接。'},
159
166
  {name: 'container', type: "'card' | 'section'", description: '视觉变体:card 带圆角;section 无圆角全宽,适用于页面级场景。', default: "'card'"},
167
+ {name: 'elevation', type: "'none' | 'low' | 'med' | 'high'", description: '静止阴影深度。用于悬浮于内容之上的浮动横幅;none 为默认内联横幅。', default: "'none'"},
160
168
  {name: 'children', type: 'ReactNode', description: '渲染在彩色头部下方卡片背景区域的内容。'},
161
169
  {name: 'defaultIsExpanded', type: 'boolean', description: '内容区域(children)是否初始展开。仅在提供 children 时相关。', default: 'false'},
162
170
  {
@@ -230,6 +238,7 @@ export const docsDense = {
230
238
  onDismiss: 'dismiss callback; banner self-hides regardless',
231
239
  endContent: 'end-aligned action in header, typically button/link',
232
240
  container: 'card=border-radius; section=full-width no radius for page-level',
241
+ elevation: 'resting shadow depth: none|low|med|high; raise for a floating banner',
233
242
  children: 'content in card-bg area below colored header',
234
243
  xstyle: 'StyleX layout customization via stylex.create()',
235
244
  },
@@ -368,4 +368,34 @@ describe('Banner', () => {
368
368
  );
369
369
  expect(screen.getByTestId('custom-chevron')).toBeInTheDocument();
370
370
  });
371
+
372
+ describe('elevation', () => {
373
+ it('renders a distinct root class for each elevation level', () => {
374
+ const classFor = (elevation: 'none' | 'low' | 'med' | 'high') => {
375
+ const {container} = render(
376
+ <Banner status="info" title="Heads up" elevation={elevation} />,
377
+ );
378
+ return container.firstElementChild!.className;
379
+ };
380
+ const classes = new Set([
381
+ classFor('none'),
382
+ classFor('low'),
383
+ classFor('med'),
384
+ classFor('high'),
385
+ ]);
386
+ expect(classes.size).toBe(4);
387
+ });
388
+
389
+ it('defaults to flat (elevation none)', () => {
390
+ const {container: def} = render(
391
+ <Banner status="info" title="Heads up" />,
392
+ );
393
+ const {container: none} = render(
394
+ <Banner status="info" title="Heads up" elevation="none" />,
395
+ );
396
+ expect(def.firstElementChild!.className).toBe(
397
+ none.firstElementChild!.className,
398
+ );
399
+ });
400
+ });
371
401
  });