@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,45 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {afterEach, describe, expect, it, vi} from 'vitest';
4
+ import {render} from '@testing-library/react';
5
+ import {useDevWarning} from './useDevWarning';
6
+
7
+ afterEach(() => {
8
+ vi.restoreAllMocks();
9
+ });
10
+
11
+ function Probe({
12
+ condition,
13
+ message = 'boom',
14
+ }: {
15
+ condition: boolean;
16
+ message?: string;
17
+ }) {
18
+ useDevWarning('TestComponent', message, condition);
19
+ return null;
20
+ }
21
+
22
+ describe('useDevWarning', () => {
23
+ it('warns once, in the standardized format, when the condition is true', () => {
24
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
25
+ const {rerender} = render(<Probe condition={true} />);
26
+ rerender(<Probe condition={true} />);
27
+ rerender(<Probe condition={true} />);
28
+ expect(warn).toHaveBeenCalledTimes(1);
29
+ expect(warn).toHaveBeenCalledWith('TestComponent: boom');
30
+ });
31
+
32
+ it('does not warn when the condition is false', () => {
33
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
34
+ render(<Probe condition={false} />);
35
+ expect(warn).not.toHaveBeenCalled();
36
+ });
37
+
38
+ it('warns after the condition flips from false to true', () => {
39
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
40
+ const {rerender} = render(<Probe condition={false} />);
41
+ expect(warn).not.toHaveBeenCalled();
42
+ rerender(<Probe condition={true} />);
43
+ expect(warn).toHaveBeenCalledTimes(1);
44
+ });
45
+ });
@@ -0,0 +1,54 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useDevWarning.ts
7
+ * @input A component name, a message, and a boolean condition
8
+ * @output Fires a dev-only `Component: message` warning once per mount
9
+ * @position Core hook; the render-safe way to warn the builder from a component
10
+ *
11
+ * The right way to surface a dev guardrail from inside a component. A naive
12
+ * `if (condition) console.warn(...)` in the render body repeats on every
13
+ * render, and reaching for `useState` to gate it adds needless state and
14
+ * re-renders. This hook uses a ref + effect: the warning fires once per mount
15
+ * when the condition holds, never during render, and never triggers a
16
+ * re-render. Messages use the standardized `Component: message` format.
17
+ *
18
+ * SYNC: When modified, update:
19
+ * - /packages/core/src/hooks/index.ts
20
+ * - /packages/core/src/utils/devWarning.ts (the imperative counterpart)
21
+ */
22
+
23
+ import {useEffect, useRef} from 'react';
24
+ import {devWarn} from '../utils/devWarning';
25
+
26
+ /**
27
+ * Fire a dev-only warning once per mount while `condition` is true.
28
+ *
29
+ * @param component - Component or hook name (message prefix)
30
+ * @param message - What went wrong and how to fix it
31
+ * @param condition - Whether to warn; defaults to `true`
32
+ *
33
+ * @example
34
+ * ```
35
+ * useDevWarning(
36
+ * 'Field',
37
+ * 'isOptional and isRequired are mutually exclusive. isOptional takes precedence.',
38
+ * isOptional && isRequired,
39
+ * );
40
+ * ```
41
+ */
42
+ export function useDevWarning(
43
+ component: string,
44
+ message: string,
45
+ condition: boolean = true,
46
+ ): void {
47
+ const hasWarnedRef = useRef(false);
48
+ useEffect(() => {
49
+ if (condition && !hasWarnedRef.current) {
50
+ hasWarnedRef.current = true;
51
+ devWarn(component, message);
52
+ }
53
+ }, [component, message, condition]);
54
+ }
@@ -14,12 +14,15 @@ function mockContainer(width: number): HTMLElement {
14
14
  function mockMeasure(
15
15
  itemWidths: number[],
16
16
  indicatorWidth?: number,
17
+ itemHeight = 0,
17
18
  ): HTMLElement {
18
- const children: {offsetWidth: number}[] = itemWidths.map(w => ({
19
- offsetWidth: w,
20
- }));
19
+ const children: {offsetWidth: number; offsetHeight: number}[] =
20
+ itemWidths.map(w => ({
21
+ offsetWidth: w,
22
+ offsetHeight: itemHeight,
23
+ }));
21
24
  if (indicatorWidth != null) {
22
- children.push({offsetWidth: indicatorWidth});
25
+ children.push({offsetWidth: indicatorWidth, offsetHeight: itemHeight});
23
26
  }
24
27
  return {children} as unknown as HTMLElement;
25
28
  }
@@ -332,6 +335,95 @@ describe('useOverflow', () => {
332
335
  });
333
336
  });
334
337
 
338
+ describe('useOverflow with maxVisibleItems (cap)', () => {
339
+ it('caps visible items even when they all fit', () => {
340
+ const {result} = renderHook(() =>
341
+ useOverflow(5, {gap: 10, maxVisibleItems: 3}),
342
+ );
343
+
344
+ act(() => {
345
+ result.current.measureRef(mockMeasure([50, 50, 50, 50, 50], 30));
346
+ result.current.containerRef(mockContainer(10000));
347
+ });
348
+
349
+ expect(result.current.visibleCount).toBe(3);
350
+ expect(result.current.hasOverflow).toBe(true);
351
+ });
352
+
353
+ it('cap does not raise a fit-limited count', () => {
354
+ const {result} = renderHook(() =>
355
+ useOverflow(4, {gap: 10, maxVisibleItems: 4}),
356
+ );
357
+
358
+ act(() => {
359
+ result.current.measureRef(mockMeasure([50, 50, 50, 50], 30));
360
+ result.current.containerRef(mockContainer(150));
361
+ });
362
+
363
+ expect(result.current.visibleCount).toBe(2);
364
+ });
365
+
366
+ it('min wins when maxVisibleItems < minVisibleItems and warns in dev', () => {
367
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
368
+ const {result} = renderHook(() =>
369
+ useOverflow(5, {gap: 10, minVisibleItems: 3, maxVisibleItems: 1}),
370
+ );
371
+
372
+ act(() => {
373
+ result.current.measureRef(mockMeasure([50, 50, 50, 50, 50], 30));
374
+ result.current.containerRef(mockContainer(10000));
375
+ });
376
+
377
+ expect(result.current.visibleCount).toBe(3);
378
+ expect(warn).toHaveBeenCalled();
379
+ warn.mockRestore();
380
+ });
381
+ });
382
+
383
+ describe('useOverflow with maxRows (multi-row)', () => {
384
+ it('exposes rows and rowHeight', () => {
385
+ const {result} = renderHook(() => useOverflow(6, {gap: 10, maxRows: 2}));
386
+
387
+ act(() => {
388
+ result.current.measureRef(mockMeasure([50, 50, 50, 50, 50, 50], 40, 24));
389
+ result.current.containerRef(mockContainer(170));
390
+ });
391
+
392
+ // 3 items per row (170), 2 rows → all 6 fit
393
+ expect(result.current.visibleCount).toBe(6);
394
+ expect(result.current.rows).toBe(2);
395
+ expect(result.current.rowHeight).toBe(24);
396
+ expect(result.current.hasOverflow).toBe(false);
397
+ });
398
+
399
+ it('collapses items beyond maxRows into the indicator', () => {
400
+ const {result} = renderHook(() => useOverflow(8, {gap: 10, maxRows: 2}));
401
+
402
+ act(() => {
403
+ result.current.measureRef(
404
+ mockMeasure([50, 50, 50, 50, 50, 50, 50, 50], 40, 24),
405
+ );
406
+ result.current.containerRef(mockContainer(170));
407
+ });
408
+
409
+ expect(result.current.visibleCount).toBe(5);
410
+ expect(result.current.rows).toBe(2);
411
+ expect(result.current.hasOverflow).toBe(true);
412
+ });
413
+
414
+ it('maxRows=1 matches single-line behavior', () => {
415
+ const {result} = renderHook(() => useOverflow(4, {gap: 10, maxRows: 1}));
416
+
417
+ act(() => {
418
+ result.current.measureRef(mockMeasure([50, 50, 50, 50], 30, 24));
419
+ result.current.containerRef(mockContainer(150));
420
+ });
421
+
422
+ expect(result.current.visibleCount).toBe(2);
423
+ expect(result.current.rows).toBe(1);
424
+ });
425
+ });
426
+
335
427
  describe('useOverflow with behavior=observeParent', () => {
336
428
  function mockContainerWithParent(
337
429
  parentWidth: number,
@@ -10,15 +10,20 @@
10
10
  *
11
11
  * Measures children rendered in a hidden container to determine how many fit
12
12
  * in the available width, without flickering. Uses ResizeObserver to react
13
- * to container size changes.
13
+ * to container size changes. Supports an optional item cap (`maxVisibleItems`)
14
+ * and bounded multi-row wrapping (`maxRows`); the fit/clamp/row-packing math
15
+ * lives in the pure `computeOverflow` helper.
14
16
  *
15
17
  * SYNC: When modified, update:
16
18
  * - /packages/core/src/hooks/index.ts
19
+ * - /packages/core/src/hooks/computeOverflow.ts (the pure computation)
17
20
  */
18
21
 
19
22
  import {useState, useCallback, useRef} from 'react';
20
23
  import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
21
24
  import {observeResize, unobserveResize} from '../utils/sharedResizeObserver';
25
+ import {computeOverflow} from './computeOverflow';
26
+ import {useDevWarning} from './useDevWarning';
22
27
 
23
28
  export interface UseOverflowOptions {
24
29
  /**
@@ -33,6 +38,23 @@ export interface UseOverflowOptions {
33
38
  */
34
39
  minVisibleItems?: number;
35
40
 
41
+ /**
42
+ * Maximum number of items to ever show, even if they all fit. The ceiling
43
+ * partner to `minVisibleItems`. `undefined` means no cap. When it is less
44
+ * than `minVisibleItems`, the floor wins and a dev-only warning is emitted.
45
+ * @default undefined
46
+ */
47
+ maxVisibleItems?: number;
48
+
49
+ /**
50
+ * Wrap items across up to this many rows before collapsing the rest into the
51
+ * overflow indicator. `undefined` (or `1`) keeps the single-line behavior.
52
+ * A number, not a boolean: unbounded wrapping is a plain flex-wrap layout,
53
+ * not overflow collapse. Assumes uniform row height.
54
+ * @default undefined
55
+ */
56
+ maxRows?: number;
57
+
36
58
  /**
37
59
  * Which end to collapse items from.
38
60
  * @default 'end'
@@ -61,6 +83,10 @@ export interface UseOverflowReturn {
61
83
  visibleCount: number;
62
84
  /** Whether any items are overflowing */
63
85
  hasOverflow: boolean;
86
+ /** Number of rows the visible items occupy (1 for the single-line path) */
87
+ rows: number;
88
+ /** Measured max item height in pixels; used to size the multi-row container */
89
+ rowHeight: number;
64
90
  }
65
91
 
66
92
  /**
@@ -88,13 +114,25 @@ export function useOverflow(
88
114
  const {
89
115
  gap = 0,
90
116
  minVisibleItems = 0,
117
+ maxVisibleItems,
118
+ maxRows,
91
119
  collapseFrom = 'end',
92
120
  behavior = 'observeSelf',
93
121
  } = options;
94
122
 
123
+ useDevWarning(
124
+ 'useOverflow',
125
+ `maxVisibleItems (${maxVisibleItems}) is less than ` +
126
+ `minVisibleItems (${minVisibleItems}); the floor wins and ` +
127
+ `minVisibleItems items will be shown.`,
128
+ maxVisibleItems != null && maxVisibleItems < minVisibleItems,
129
+ );
130
+
95
131
  const observeParent = behavior === 'observeParent';
96
132
 
97
133
  const [visibleCount, setVisibleCount] = useState(itemCount);
134
+ const [rows, setRows] = useState(1);
135
+ const [rowHeight, setRowHeight] = useState(0);
98
136
  const containerElRef = useRef<HTMLElement | null>(null);
99
137
  const measureElRef = useRef<HTMLElement | null>(null);
100
138
  const observedElRef = useRef<HTMLElement | null>(null);
@@ -134,44 +172,45 @@ export function useOverflow(
134
172
  if (children.length === 0) {
135
173
  // eslint-disable-next-line @eslint-react/set-state-in-effect -- overflow count is derived from measured DOM widths
136
174
  setVisibleCount(0);
175
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- derived from measured DOM
176
+ setRows(0);
137
177
  return;
138
178
  }
139
179
 
140
- // Collect widths of all children
141
180
  const widths = children.map(child => child.offsetWidth);
142
-
143
- // Determine how many items fit
144
- let totalWidth = 0;
145
- let count = 0;
146
-
147
- const orderedWidths =
148
- collapseFrom === 'end' ? widths : [...widths].reverse();
149
-
150
- for (let i = 0; i < orderedWidths.length; i++) {
151
- const itemWidth = orderedWidths[i];
152
- const gapWidth = i > 0 ? gap : 0;
153
- const candidateWidth = totalWidth + itemWidth + gapWidth;
154
-
155
- // If this isn't the last item, we need to reserve space for the indicator
156
- const isLastItem = i === orderedWidths.length - 1;
157
- const reservedWidth = isLastItem
158
- ? 0
159
- : indicatorWidth + (count > 0 || indicatorWidth > 0 ? gap : 0);
160
-
161
- if (
162
- candidateWidth + reservedWidth > availableWidth &&
163
- count >= minVisibleItems
164
- ) {
165
- break;
166
- }
167
-
168
- totalWidth = candidateWidth;
169
- count++;
170
- }
181
+ const measuredRowHeight = children.reduce(
182
+ (max, child) => Math.max(max, child.offsetHeight || 0),
183
+ 0,
184
+ );
185
+
186
+ const {visibleCount: nextVisible, rows: nextRows} = computeOverflow({
187
+ widths,
188
+ gap,
189
+ availableWidth,
190
+ indicatorWidth,
191
+ minVisibleItems,
192
+ maxVisibleItems,
193
+ maxRows,
194
+ collapseFrom,
195
+ });
171
196
 
172
197
  // eslint-disable-next-line @eslint-react/set-state-in-effect -- overflow count is derived from measured DOM widths
173
- setVisibleCount(Math.max(Math.min(count, itemCount), minVisibleItems));
174
- }, [itemCount, gap, minVisibleItems, collapseFrom, observeParent]);
198
+ setVisibleCount(nextVisible);
199
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- derived from measured DOM
200
+ setRows(prev => (prev === nextRows ? prev : nextRows));
201
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- derived from measured DOM
202
+ setRowHeight(prev =>
203
+ prev === measuredRowHeight ? prev : measuredRowHeight,
204
+ );
205
+ }, [
206
+ itemCount,
207
+ gap,
208
+ minVisibleItems,
209
+ maxVisibleItems,
210
+ maxRows,
211
+ collapseFrom,
212
+ observeParent,
213
+ ]);
175
214
 
176
215
  const containerRef = useCallback(
177
216
  (el: HTMLElement | null) => {
@@ -217,5 +256,7 @@ export function useOverflow(
217
256
  measureRef,
218
257
  visibleCount,
219
258
  hasOverflow,
259
+ rows,
260
+ rowHeight,
220
261
  };
221
262
  }
@@ -20,7 +20,29 @@ export const docs = {
20
20
  ],
21
21
  usage: {
22
22
  description:
23
- "Wraps your app to set the active locale and (optionally) merge additional translation catalogs + per-locale overrides. Astryx components inside the subtree resolve their strings against this context. If no provider is present, components fall back to the shipped English defaults.",
23
+ 'Wraps your app to set the active locale and (optionally) merge additional translation catalogs + per-locale overrides. Astryx components inside the subtree resolve their strings against this context. If no provider is present, components fall back to the shipped English defaults.',
24
+ bestPractices: [
25
+ {
26
+ guidance: true,
27
+ description:
28
+ 'Use shipped Astryx locale catalogs from `@astryxdesign/core/locales/*` when one exists for your target locale.',
29
+ },
30
+ {
31
+ guidance: true,
32
+ description:
33
+ 'Use a same-shape local catalog only when Astryx has not shipped that locale yet or you are testing in-progress translations.',
34
+ },
35
+ {
36
+ guidance: true,
37
+ description:
38
+ 'Use real BCP 47 tags such as `fr`, `pt-BR`, or `ar`; regional locales fall back to their base language before English.',
39
+ },
40
+ {
41
+ guidance: false,
42
+ description:
43
+ 'Cast custom catalog maps to `any`; the i18n package exports `MessagesByLocale` and `Catalog` for local catalog typing.',
44
+ },
45
+ ],
24
46
  },
25
47
  props: [
26
48
  {
@@ -35,7 +57,7 @@ export const docs = {
35
57
  type: 'MessagesByLocale',
36
58
  required: false,
37
59
  description:
38
- 'Optional map of BCP 47 tag to translation catalog. The shipped "en" catalog is always available and does not need to be listed here.',
60
+ 'Optional map of BCP 47 tag to translation catalog. Import shipped catalogs from `@astryxdesign/core/locales/*`; the shipped "en" catalog is always available and does not need to be listed here.',
39
61
  },
40
62
  {
41
63
  name: 'overrides',
@@ -51,6 +73,59 @@ export const docs = {
51
73
  description: 'Content to render with the internationalization provider.',
52
74
  },
53
75
  ],
76
+ examples: [
77
+ {
78
+ label: 'Load a shipped Astryx locale catalog',
79
+ code: `import type {ReactNode} from 'react';
80
+ import {InternationalizationProvider} from '@astryxdesign/core/i18n';
81
+ import frFR from '@astryxdesign/core/locales/fr-FR.json';
82
+
83
+ export function AppI18n({children}: {children: ReactNode}) {
84
+ return (
85
+ <InternationalizationProvider locale="fr-FR" messages={{'fr-FR': frFR}}>
86
+ {children}
87
+ </InternationalizationProvider>
88
+ );
89
+ }`,
90
+ },
91
+ {
92
+ label: 'Override one Astryx string',
93
+ code: `import type {ReactNode} from 'react';
94
+ import {InternationalizationProvider} from '@astryxdesign/core/i18n';
95
+
96
+ export function AppI18n({children}: {children: ReactNode}) {
97
+ return (
98
+ <InternationalizationProvider
99
+ locale="en"
100
+ overrides={{
101
+ en: {'@astryx.selector.placeholder': 'Choose...'},
102
+ }}
103
+ >
104
+ {children}
105
+ </InternationalizationProvider>
106
+ );
107
+ }`,
108
+ },
109
+ {
110
+ label: 'Provide a local fallback catalog',
111
+ code: `import type {ReactNode} from 'react';
112
+ import {
113
+ InternationalizationProvider,
114
+ type MessagesByLocale,
115
+ } from '@astryxdesign/core/i18n';
116
+ import ptBR from './locales/pt-BR.json';
117
+
118
+ const messages: MessagesByLocale = {'pt-BR': ptBR};
119
+
120
+ export function AppI18n({children}: {children: ReactNode}) {
121
+ return (
122
+ <InternationalizationProvider locale="pt-BR" messages={messages}>
123
+ {children}
124
+ </InternationalizationProvider>
125
+ );
126
+ }`,
127
+ },
128
+ ],
54
129
  };
55
130
 
56
131
  /** @type {import('../docs-types').TranslationDoc} */
@@ -60,6 +135,28 @@ export const docsDense = {
60
135
  usage: {
61
136
  description:
62
137
  'Wraps app to set active locale and (optionally) merge translation catalogs + per-locale overrides. Astryx components resolve strings against this context; missing keys fall back to shipped English.',
138
+ bestPractices: [
139
+ {
140
+ guidance: true,
141
+ description:
142
+ 'Use shipped Astryx locale catalogs from `@astryxdesign/core/locales/*` when one exists for the target locale.',
143
+ },
144
+ {
145
+ guidance: true,
146
+ description:
147
+ 'Use a same-shape local catalog only when Astryx has not shipped that locale yet or when testing in-progress translations.',
148
+ },
149
+ {
150
+ guidance: true,
151
+ description:
152
+ 'Use real BCP 47 tags like `fr`, `pt-BR`, or `ar`; regional locales fall back to base language before English.',
153
+ },
154
+ {
155
+ guidance: false,
156
+ description:
157
+ 'Cast custom catalog maps to `any`; the i18n package exports `MessagesByLocale` and `Catalog` for local catalog typing.',
158
+ },
159
+ ],
63
160
  },
64
161
  propDescriptions: {
65
162
  locale:
@@ -22,6 +22,7 @@
22
22
  import IntlMessageFormat from 'intl-messageformat';
23
23
  import type {Catalog, Locale, MessagesByLocale, Overrides} from './types';
24
24
  import enSource from '../../locales/en.json' with {type: 'json'};
25
+ import {warnOnce, __resetDevWarnings} from '../utils/devWarning';
25
26
 
26
27
  const EN_CATALOG = enSource as Catalog;
27
28
 
@@ -72,23 +73,6 @@ export function resolveLocaleChain(locale: Locale): Locale[] {
72
73
  return chain;
73
74
  }
74
75
 
75
- /**
76
- * Missing-key warn-once tracking. Fires ONLY when a key is missing from
77
- * every source including the shipped `en` catalog — that's a real bug
78
- * (typo, stale catalog, deleted key). Fallback to `en` from a non-en
79
- * locale is expected and silent, matching the FormatJS / i18next default
80
- * of not spamming the console when a translation simply hasn't been
81
- * written yet.
82
- */
83
- const warnedMissing = new Set<string>();
84
-
85
- function warnOnce(bucket: Set<string>, key: string, message: string): void {
86
- if (!bucket.has(key)) {
87
- bucket.add(key);
88
- console.warn(message);
89
- }
90
- }
91
-
92
76
  function lookup(
93
77
  key: string,
94
78
  locale: Locale,
@@ -135,10 +119,14 @@ export function resolve(
135
119
  const result = lookup(key, locale, messages, overrides);
136
120
 
137
121
  if (result === null) {
122
+ // Fires ONLY when a key is missing from every source including the
123
+ // shipped `en` catalog — a real bug (typo, stale catalog, deleted key).
124
+ // Fallback to `en` from a non-en locale is expected and stays silent,
125
+ // matching the FormatJS / i18next default.
138
126
  warnOnce(
139
- warnedMissing,
140
- `${locale}::${key}`,
141
- `[astryx-i18n] missing key: ${key} (locale: ${locale})`,
127
+ `astryx-i18n:${locale}::${key}`,
128
+ 'astryx-i18n',
129
+ `missing key: ${key} (locale: ${locale})`,
142
130
  );
143
131
  return key;
144
132
  }
@@ -161,5 +149,5 @@ export function resolve(
161
149
  */
162
150
  export function __resetForTests(): void {
163
151
  formatterCache.clear();
164
- warnedMissing.clear();
152
+ __resetDevWarnings();
165
153
  }
@@ -45,6 +45,7 @@ import {registerIcons} from '../Icon/globalIconRegistry';
45
45
  import {generateThemeCSS, type DefinedTheme} from './defineTheme';
46
46
  import {dataAttr} from '../naming';
47
47
  import {ThemeContext} from './useTheme';
48
+ import {warnOnce} from '../utils/devWarning';
48
49
 
49
50
  /**
50
51
  * Theme provider props
@@ -97,9 +98,6 @@ ThemeNestingContext.displayName = 'ThemeNestingContext';
97
98
  /** Track which themes have already been injected */
98
99
  const injectedThemes = new Set<string>();
99
100
 
100
- /** Track which themes have already logged the perf warning */
101
- const warnedThemes = new Set<string>();
102
-
103
101
  /**
104
102
  * Hook to inject theme CSS into the document.
105
103
  * Built themes (from `astryx theme build`) skip injection — their CSS
@@ -120,17 +118,16 @@ function useThemeStyleInjection(theme: DefinedTheme): void {
120
118
  }
121
119
 
122
120
  // One-time perf hint per theme
123
- if (!warnedThemes.has(theme.name)) {
124
- warnedThemes.add(theme.name);
125
- console.warn(
126
- `[Astryx] Theme "${theme.name}" is using runtime style injection. ` +
127
- `For better performance, use the pre-built theme:\n\n` +
128
- ` import {${theme.name}Theme} from '@astryxdesign/theme-${theme.name}/built';\n` +
129
- ` import '@astryxdesign/theme-${theme.name}/theme.css';\n\n` +
130
- `For custom themes, run \`npx @astryxdesign/cli theme build <file>\` to generate ` +
131
- `the built artifacts.`,
132
- );
133
- }
121
+ warnOnce(
122
+ `theme-injection:${theme.name}`,
123
+ 'Theme',
124
+ `"${theme.name}" is using runtime style injection. ` +
125
+ `For better performance, use the pre-built theme:\n\n` +
126
+ ` import {${theme.name}Theme} from '@astryxdesign/theme-${theme.name}/built';\n` +
127
+ ` import '@astryxdesign/theme-${theme.name}/theme.css';\n\n` +
128
+ `For custom themes, run \`npx @astryxdesign/cli theme build <file>\` to generate ` +
129
+ `the built artifacts.`,
130
+ );
134
131
 
135
132
  const {prose, component} = generateThemeCSS(theme);
136
133
  if (!prose && !component) {