@astryxdesign/core 0.4.6 → 0.4.7-canary.019ae5a

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 (486) hide show
  1. package/CHANGELOG.md +37 -1
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +3 -2
  4. package/dist/AspectRatio/AspectRatio.d.ts +24 -0
  5. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  6. package/dist/AspectRatio/AspectRatio.js +23 -4
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +15 -10
  9. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  10. package/dist/AvatarGroup/AvatarGroup.js +3 -2
  11. package/dist/Banner/Banner.d.ts +40 -15
  12. package/dist/Banner/Banner.d.ts.map +1 -1
  13. package/dist/Banner/Banner.js +51 -18
  14. package/dist/BottomSheet/BottomSheet.d.ts +1 -0
  15. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  16. package/dist/BottomSheet/BottomSheet.js +54 -15
  17. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +6 -0
  18. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -0
  19. package/dist/BottomSheet/BottomSheetEdgeTint.js +62 -0
  20. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  21. package/dist/BottomSheet/BottomSheetPanel.js +1 -0
  22. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +1 -0
  23. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  24. package/dist/BottomSheet/BottomSheetSwitcher.js +13 -6
  25. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  26. package/dist/BottomSheet/useSheetGestures.js +23 -5
  27. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  28. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
  29. package/dist/Button/Button.d.ts.map +1 -1
  30. package/dist/Button/Button.js +5 -4
  31. package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
  32. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  33. package/dist/ButtonGroup/ButtonGroup.js +26 -7
  34. package/dist/Calendar/Calendar.d.ts +3 -1
  35. package/dist/Calendar/Calendar.d.ts.map +1 -1
  36. package/dist/Calendar/Calendar.js +13 -14
  37. package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
  38. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
  39. package/dist/Calendar/getInitialFocusDate.js +62 -0
  40. package/dist/Carousel/Carousel.d.ts.map +1 -1
  41. package/dist/Carousel/Carousel.js +3 -3
  42. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposerInput.js +2 -3
  44. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  45. package/dist/Chat/ChatLayout.js +3 -3
  46. package/dist/Chat/ChatMessageList.d.ts +22 -3
  47. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  48. package/dist/Chat/ChatMessageList.js +6 -3
  49. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  50. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  51. package/dist/Chat/ChatTokenizedText.js +11 -6
  52. package/dist/Chat/useChatComposerTokens.js +1 -1
  53. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  54. package/dist/CheckboxInput/CheckboxInput.js +3 -2
  55. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  56. package/dist/ClickableCard/ClickableCard.js +3 -3
  57. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  58. package/dist/CommandPalette/CommandPaletteInput.js +3 -2
  59. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  60. package/dist/CommandPalette/CommandPaletteItem.js +3 -2
  61. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  62. package/dist/ContextMenu/ContextMenu.js +4 -3
  63. package/dist/DateInput/DateInput.d.ts.map +1 -1
  64. package/dist/DateInput/DateInput.js +3 -2
  65. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  66. package/dist/DateInput/TouchDateField.js +38 -4
  67. package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
  68. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  69. package/dist/DateTimeInput/DateTimeInput.js +406 -12
  70. package/dist/DateTimeInput/index.d.ts +1 -1
  71. package/dist/DateTimeInput/index.d.ts.map +1 -1
  72. package/dist/Dialog/Dialog.d.ts +7 -1
  73. package/dist/Dialog/Dialog.d.ts.map +1 -1
  74. package/dist/Dialog/Dialog.js +89 -58
  75. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  76. package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
  77. package/dist/FileInput/FileInput.d.ts.map +1 -1
  78. package/dist/FileInput/FileInput.js +3 -2
  79. package/dist/Heading/Heading.d.ts.map +1 -1
  80. package/dist/Heading/Heading.js +6 -2
  81. package/dist/HoverCard/HoverCard.d.ts +6 -0
  82. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  83. package/dist/HoverCard/useHoverCard.d.ts +5 -0
  84. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  85. package/dist/HoverCard/useHoverCard.js +67 -25
  86. package/dist/Item/Item.d.ts.map +1 -1
  87. package/dist/Item/Item.js +4 -2
  88. package/dist/Kbd/Kbd.d.ts.map +1 -1
  89. package/dist/Kbd/Kbd.js +10 -3
  90. package/dist/Layer/LayerDepthContext.d.ts +52 -0
  91. package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
  92. package/dist/Layer/LayerDepthContext.js +66 -0
  93. package/dist/Layer/index.d.ts +3 -0
  94. package/dist/Layer/index.d.ts.map +1 -1
  95. package/dist/Layer/index.js +4 -0
  96. package/dist/Layer/layerStack.d.ts +98 -0
  97. package/dist/Layer/layerStack.d.ts.map +1 -0
  98. package/dist/Layer/layerStack.js +306 -0
  99. package/dist/Layer/useLayerDismissal.d.ts +93 -0
  100. package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
  101. package/dist/Layer/useLayerDismissal.js +91 -0
  102. package/dist/Lightbox/Lightbox.d.ts +1 -0
  103. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  104. package/dist/Lightbox/Lightbox.js +103 -81
  105. package/dist/Markdown/index.d.ts +1 -1
  106. package/dist/Markdown/index.d.ts.map +1 -1
  107. package/dist/Markdown/parser.d.ts +38 -1
  108. package/dist/Markdown/parser.d.ts.map +1 -1
  109. package/dist/Markdown/parser.js +177 -29
  110. package/dist/MobileNav/MobileNav.d.ts +6 -1
  111. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  112. package/dist/MobileNav/MobileNav.js +61 -36
  113. package/dist/MultiSelector/MultiSelector.d.ts +15 -1
  114. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  115. package/dist/MultiSelector/MultiSelector.js +17 -6
  116. package/dist/MultiSelector/index.d.ts +1 -1
  117. package/dist/MultiSelector/index.d.ts.map +1 -1
  118. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  119. package/dist/NavMenu/NavHeadingMenu.js +3 -2
  120. package/dist/Outline/Outline.d.ts.map +1 -1
  121. package/dist/Outline/Outline.js +3 -2
  122. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  123. package/dist/OverflowList/OverflowList.js +3 -2
  124. package/dist/Overlay/Overlay.d.ts.map +1 -1
  125. package/dist/Overlay/Overlay.js +3 -2
  126. package/dist/Popover/usePopover.d.ts.map +1 -1
  127. package/dist/Popover/usePopover.js +20 -17
  128. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  129. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  130. package/dist/PowerSearch/PowerSearch.js +19 -6
  131. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  132. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  133. package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
  134. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  135. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  136. package/dist/PowerSearch/PowerSearchValueEditor.js +19 -9
  137. package/dist/PowerSearch/types.d.ts +2 -0
  138. package/dist/PowerSearch/types.d.ts.map +1 -1
  139. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  140. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  141. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  142. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  143. package/dist/Resizable/ResizeHandle.js +3 -2
  144. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  145. package/dist/SegmentedControl/SegmentedControl.js +3 -2
  146. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  147. package/dist/SelectableCard/SelectableCard.js +3 -3
  148. package/dist/Selector/Selector.d.ts.map +1 -1
  149. package/dist/Selector/Selector.js +5 -0
  150. package/dist/SideNav/SideNav.d.ts.map +1 -1
  151. package/dist/SideNav/SideNav.js +4 -3
  152. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  153. package/dist/SideNav/SideNavHeading.js +6 -5
  154. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  155. package/dist/SideNav/SideNavItem.js +4 -3
  156. package/dist/Slider/Slider.d.ts.map +1 -1
  157. package/dist/Slider/Slider.js +3 -3
  158. package/dist/Spinner/Spinner.d.ts +3 -3
  159. package/dist/Spinner/Spinner.d.ts.map +1 -1
  160. package/dist/Spinner/Spinner.js +130 -78
  161. package/dist/Stepper/Step.d.ts +140 -0
  162. package/dist/Stepper/Step.d.ts.map +1 -0
  163. package/dist/Stepper/Step.js +1045 -0
  164. package/dist/Stepper/StepStatus.d.ts +24 -0
  165. package/dist/Stepper/StepStatus.d.ts.map +1 -0
  166. package/dist/Stepper/StepStatus.js +1 -0
  167. package/dist/Stepper/Stepper.d.ts +93 -0
  168. package/dist/Stepper/Stepper.d.ts.map +1 -0
  169. package/dist/Stepper/Stepper.js +184 -0
  170. package/dist/Stepper/StepperContext.d.ts +41 -0
  171. package/dist/Stepper/StepperContext.d.ts.map +1 -0
  172. package/dist/Stepper/StepperContext.js +34 -0
  173. package/dist/Stepper/index.d.ts +8 -0
  174. package/dist/Stepper/index.d.ts.map +1 -0
  175. package/dist/Stepper/index.js +7 -0
  176. package/dist/Stepper/stepper.stylex.d.ts +15 -0
  177. package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
  178. package/dist/Stepper/stepper.stylex.js +20 -0
  179. package/dist/Switch/Switch.d.ts.map +1 -1
  180. package/dist/Switch/Switch.js +3 -2
  181. package/dist/TabList/Tab.d.ts +15 -2
  182. package/dist/TabList/Tab.d.ts.map +1 -1
  183. package/dist/TabList/Tab.js +32 -3
  184. package/dist/TabList/TabList.d.ts +46 -7
  185. package/dist/TabList/TabList.d.ts.map +1 -1
  186. package/dist/TabList/TabList.js +353 -23
  187. package/dist/TabList/TabListContext.d.ts +11 -1
  188. package/dist/TabList/TabListContext.d.ts.map +1 -1
  189. package/dist/TabList/TabListContext.js +11 -2
  190. package/dist/TabList/TabMenu.d.ts.map +1 -1
  191. package/dist/TabList/TabMenu.js +3 -2
  192. package/dist/TabList/index.d.ts +2 -2
  193. package/dist/TabList/index.d.ts.map +1 -1
  194. package/dist/Table/BaseTable.d.ts.map +1 -1
  195. package/dist/Table/BaseTable.js +4 -1
  196. package/dist/Table/TableRow.d.ts +16 -5
  197. package/dist/Table/TableRow.d.ts.map +1 -1
  198. package/dist/Table/TableRow.js +16 -5
  199. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  200. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
  201. package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
  202. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  203. package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
  204. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  205. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  206. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  207. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
  208. package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
  209. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  210. package/dist/Table/plugins/selection/useTableSelection.js +19 -5
  211. package/dist/Table/types.d.ts +22 -4
  212. package/dist/Table/types.d.ts.map +1 -1
  213. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  214. package/dist/Table/useBaseTablePlugins.js +5 -0
  215. package/dist/Text/Text.d.ts.map +1 -1
  216. package/dist/Text/Text.js +6 -2
  217. package/dist/TextArea/TextArea.d.ts.map +1 -1
  218. package/dist/TextArea/TextArea.js +3 -2
  219. package/dist/TextInput/TextInput.d.ts.map +1 -1
  220. package/dist/TextInput/TextInput.js +3 -2
  221. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  222. package/dist/TimeInput/TimeInput.js +3 -2
  223. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  224. package/dist/Timestamp/Timestamp.js +5 -3
  225. package/dist/Token/TokenLink.d.ts.map +1 -1
  226. package/dist/Token/TokenLink.js +2 -2
  227. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  228. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  229. package/dist/Tokenizer/Tokenizer.js +2 -0
  230. package/dist/Tooltip/Tooltip.d.ts +5 -0
  231. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  232. package/dist/Tooltip/useTooltip.d.ts +5 -0
  233. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  234. package/dist/Tooltip/useTooltip.js +39 -24
  235. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  236. package/dist/TopNav/TopNavHeading.js +3 -2
  237. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  238. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  239. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  240. package/dist/TopNav/TopNavMenu.js +4 -3
  241. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  242. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  243. package/dist/Typeahead/BaseTypeahead.js +68 -29
  244. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  245. package/dist/Typeahead/Typeahead.js +3 -2
  246. package/dist/astryx.css +72 -2
  247. package/dist/hooks/index.d.ts +1 -0
  248. package/dist/hooks/index.d.ts.map +1 -1
  249. package/dist/hooks/index.js +1 -0
  250. package/dist/hooks/useFocusTrap.d.ts +7 -4
  251. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  252. package/dist/hooks/useFocusTrap.js +60 -95
  253. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  254. package/dist/hooks/useHotkeys.js +10 -3
  255. package/dist/hooks/useListFocus.d.ts +5 -2
  256. package/dist/hooks/useListFocus.d.ts.map +1 -1
  257. package/dist/hooks/useListFocus.js +12 -6
  258. package/dist/hooks/useMergedRefs.d.ts +18 -0
  259. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  260. package/dist/hooks/useMergedRefs.js +25 -0
  261. package/dist/index.d.ts +1 -0
  262. package/dist/index.d.ts.map +1 -1
  263. package/dist/index.js +1 -0
  264. package/dist/utils/groupItems.d.ts +5 -1
  265. package/dist/utils/groupItems.d.ts.map +1 -1
  266. package/dist/utils/groupItems.js +9 -9
  267. package/dist/utils/index.d.ts +1 -1
  268. package/dist/utils/index.d.ts.map +1 -1
  269. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  270. package/dist/utils/sharedResizeObserver.js +10 -1
  271. package/dist/utils/timeParser.d.ts +1 -1
  272. package/dist/utils/timeParser.d.ts.map +1 -1
  273. package/dist/utils/timeParser.js +26 -12
  274. package/locales/en.json +31 -7
  275. package/locales/pseudo.json +18 -0
  276. package/package.json +8 -3
  277. package/src/AppShell/AppShell.tsx +3 -2
  278. package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
  279. package/src/AspectRatio/AspectRatio.test.tsx +57 -10
  280. package/src/AspectRatio/AspectRatio.tsx +40 -1
  281. package/src/Avatar/Avatar.doc.mjs +2 -1
  282. package/src/Avatar/Avatar.test.tsx +17 -0
  283. package/src/Avatar/Avatar.tsx +61 -38
  284. package/src/AvatarGroup/AvatarGroup.tsx +3 -2
  285. package/src/Banner/Banner.doc.mjs +13 -11
  286. package/src/Banner/Banner.test.tsx +102 -15
  287. package/src/Banner/Banner.tsx +72 -25
  288. package/src/BottomSheet/BottomSheet.test.tsx +123 -0
  289. package/src/BottomSheet/BottomSheet.tsx +36 -4
  290. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +225 -0
  291. package/src/BottomSheet/BottomSheetEdgeTint.tsx +82 -0
  292. package/src/BottomSheet/BottomSheetPanel.test.tsx +66 -0
  293. package/src/BottomSheet/BottomSheetPanel.tsx +19 -0
  294. package/src/BottomSheet/BottomSheetSwitcher.tsx +16 -2
  295. package/src/BottomSheet/useSheetGestures.test.ts +27 -0
  296. package/src/BottomSheet/useSheetGestures.ts +25 -5
  297. package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
  298. package/src/Button/Button.doc.mjs +22 -0
  299. package/src/Button/Button.test.tsx +13 -0
  300. package/src/Button/Button.tsx +5 -4
  301. package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
  302. package/src/ButtonGroup/ButtonGroup.test.tsx +391 -1
  303. package/src/ButtonGroup/ButtonGroup.tsx +23 -5
  304. package/src/Calendar/Calendar.doc.mjs +4 -3
  305. package/src/Calendar/Calendar.test.tsx +52 -0
  306. package/src/Calendar/Calendar.tsx +18 -15
  307. package/src/Calendar/getInitialFocusDate.test.ts +94 -0
  308. package/src/Calendar/getInitialFocusDate.ts +93 -0
  309. package/src/Carousel/Carousel.tsx +3 -2
  310. package/src/Chat/ChatComposerInput.tsx +5 -1
  311. package/src/Chat/ChatLayout.tsx +3 -2
  312. package/src/Chat/ChatMessageList.doc.mjs +9 -1
  313. package/src/Chat/ChatMessageList.test.tsx +46 -0
  314. package/src/Chat/ChatMessageList.tsx +28 -4
  315. package/src/Chat/ChatTokenizedText.tsx +17 -10
  316. package/src/Chat/useChatComposerTokens.ts +1 -1
  317. package/src/CheckboxInput/CheckboxInput.tsx +3 -2
  318. package/src/ClickableCard/ClickableCard.tsx +3 -2
  319. package/src/CodeBlock/CodeBlock.doc.mjs +6 -0
  320. package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
  321. package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
  322. package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
  323. package/src/ContextMenu/ContextMenu.tsx +4 -3
  324. package/src/DateInput/DateInput.tsx +3 -2
  325. package/src/DateInput/DateInputTouch.test.tsx +36 -0
  326. package/src/DateInput/TouchDateField.tsx +38 -4
  327. package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
  328. package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
  329. package/src/DateTimeInput/DateTimeInput.tsx +556 -24
  330. package/src/DateTimeInput/index.ts +1 -0
  331. package/src/Dialog/Dialog.doc.mjs +8 -3
  332. package/src/Dialog/Dialog.test.tsx +261 -1
  333. package/src/Dialog/Dialog.tsx +109 -58
  334. package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
  335. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
  336. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
  337. package/src/Field/Field.doc.mjs +13 -2
  338. package/src/FileInput/FileInput.tsx +3 -2
  339. package/src/Heading/Heading.test.tsx +23 -1
  340. package/src/Heading/Heading.tsx +6 -2
  341. package/src/HoverCard/HoverCard.doc.mjs +5 -0
  342. package/src/HoverCard/HoverCard.tsx +6 -0
  343. package/src/HoverCard/useHoverCard.tsx +81 -30
  344. package/src/Item/Item.tsx +5 -4
  345. package/src/Kbd/Kbd.test.tsx +17 -0
  346. package/src/Kbd/Kbd.tsx +10 -3
  347. package/src/Layer/LayerDepthContext.tsx +62 -0
  348. package/src/Layer/index.ts +9 -0
  349. package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
  350. package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
  351. package/src/Layer/layerStack.ts +362 -0
  352. package/src/Layer/useLayerDismissal.test.tsx +469 -0
  353. package/src/Layer/useLayerDismissal.ts +173 -0
  354. package/src/Layout/Layout.doc.mjs +2 -2
  355. package/src/Lightbox/Lightbox.doc.mjs +12 -0
  356. package/src/Lightbox/Lightbox.tsx +124 -102
  357. package/src/Link/Link.doc.mjs +39 -0
  358. package/src/Markdown/Markdown.doc.mjs +5 -0
  359. package/src/Markdown/incremental.test.ts +16 -2
  360. package/src/Markdown/index.ts +1 -0
  361. package/src/Markdown/parser.perf.test.ts +96 -1
  362. package/src/Markdown/parser.test.ts +145 -2
  363. package/src/Markdown/parser.ts +244 -29
  364. package/src/MobileNav/MobileNav.tsx +62 -36
  365. package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
  366. package/src/MultiSelector/MultiSelector.doc.mjs +27 -0
  367. package/src/MultiSelector/MultiSelector.test.tsx +106 -0
  368. package/src/MultiSelector/MultiSelector.tsx +50 -6
  369. package/src/MultiSelector/index.ts +1 -0
  370. package/src/NavMenu/NavHeadingMenu.tsx +3 -2
  371. package/src/NumberInput/NumberInput.doc.mjs +5 -0
  372. package/src/Outline/Outline.tsx +3 -2
  373. package/src/OverflowList/OverflowList.tsx +3 -2
  374. package/src/Overlay/Overlay.tsx +3 -2
  375. package/src/Popover/Popover.test.tsx +27 -1
  376. package/src/Popover/usePopover.tsx +31 -28
  377. package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
  378. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  379. package/src/PowerSearch/PowerSearch.tsx +34 -5
  380. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +201 -0
  381. package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
  382. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +47 -1
  383. package/src/PowerSearch/PowerSearchValueEditor.tsx +15 -0
  384. package/src/PowerSearch/types.ts +2 -0
  385. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  386. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  387. package/src/Resizable/ResizeHandle.tsx +3 -2
  388. package/src/SegmentedControl/SegmentedControl.tsx +3 -2
  389. package/src/SelectableCard/SelectableCard.tsx +3 -2
  390. package/src/Selector/Selector.doc.mjs +12 -1
  391. package/src/Selector/Selector.test.tsx +45 -1
  392. package/src/Selector/Selector.tsx +11 -1
  393. package/src/SideNav/SideNav.doc.mjs +6 -6
  394. package/src/SideNav/SideNav.tsx +4 -2
  395. package/src/SideNav/SideNavHeading.tsx +10 -10
  396. package/src/SideNav/SideNavItem.tsx +4 -2
  397. package/src/Slider/Slider.tsx +3 -2
  398. package/src/Spinner/Spinner.test.tsx +147 -1
  399. package/src/Spinner/Spinner.tsx +120 -101
  400. package/src/Stepper/Step.doc.mjs +98 -0
  401. package/src/Stepper/Step.tsx +1638 -0
  402. package/src/Stepper/StepStatus.ts +26 -0
  403. package/src/Stepper/Stepper.doc.mjs +370 -0
  404. package/src/Stepper/Stepper.test.tsx +1024 -0
  405. package/src/Stepper/Stepper.tsx +258 -0
  406. package/src/Stepper/StepperContext.ts +71 -0
  407. package/src/Stepper/index.ts +18 -0
  408. package/src/Stepper/stepper.stylex.ts +19 -0
  409. package/src/Switch/Switch.tsx +3 -2
  410. package/src/TabList/Tab.doc.mjs +13 -1
  411. package/src/TabList/Tab.tsx +67 -3
  412. package/src/TabList/TabList.doc.mjs +22 -6
  413. package/src/TabList/TabList.test.tsx +580 -4
  414. package/src/TabList/TabList.tsx +490 -22
  415. package/src/TabList/TabListContext.ts +13 -2
  416. package/src/TabList/TabMenu.tsx +3 -2
  417. package/src/TabList/index.ts +6 -2
  418. package/src/Table/BaseTable.tsx +6 -3
  419. package/src/Table/Table.doc.mjs +6 -1
  420. package/src/Table/Table.test.tsx +132 -14
  421. package/src/Table/TableBody.doc.mjs +40 -0
  422. package/src/Table/TableFooter.doc.mjs +40 -0
  423. package/src/Table/TableHeader.doc.mjs +40 -0
  424. package/src/Table/TableRow.tsx +16 -5
  425. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
  426. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
  427. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
  428. package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
  429. package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
  430. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
  431. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
  432. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
  433. package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
  434. package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
  435. package/src/Table/types.ts +22 -4
  436. package/src/Table/useBaseTablePlugins.ts +5 -0
  437. package/src/Table/useTableGroupedRows.doc.mjs +5 -4
  438. package/src/Table/useTablePagination.doc.mjs +7 -0
  439. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  440. package/src/Table/useTableSelection.doc.mjs +31 -0
  441. package/src/Text/Text.test.tsx +23 -1
  442. package/src/Text/Text.tsx +6 -2
  443. package/src/TextArea/TextArea.tsx +3 -2
  444. package/src/TextInput/TextInput.doc.mjs +10 -0
  445. package/src/TextInput/TextInput.tsx +3 -2
  446. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  447. package/src/TimeInput/TimeInput.tsx +2 -2
  448. package/src/Timestamp/Timestamp.tsx +4 -2
  449. package/src/Toast/Toast.doc.mjs +6 -0
  450. package/src/Token/TokenLink.tsx +2 -2
  451. package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
  452. package/src/Tokenizer/Tokenizer.tsx +4 -0
  453. package/src/Toolbar/Toolbar.doc.mjs +5 -0
  454. package/src/Tooltip/Tooltip.doc.mjs +5 -0
  455. package/src/Tooltip/Tooltip.test.tsx +32 -0
  456. package/src/Tooltip/Tooltip.tsx +5 -0
  457. package/src/Tooltip/useTooltip.tsx +47 -24
  458. package/src/TopNav/TopNavHeading.tsx +3 -2
  459. package/src/TopNav/TopNavMegaMenu.tsx +3 -2
  460. package/src/TopNav/TopNavMenu.tsx +4 -3
  461. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  462. package/src/Typeahead/BaseTypeahead.tsx +85 -33
  463. package/src/Typeahead/Typeahead.doc.mjs +5 -0
  464. package/src/Typeahead/Typeahead.test.tsx +49 -0
  465. package/src/Typeahead/Typeahead.tsx +3 -2
  466. package/src/hooks/index.ts +2 -0
  467. package/src/hooks/useContainerReveal.doc.mjs +2 -2
  468. package/src/hooks/useFocusTrap.test.tsx +14 -0
  469. package/src/hooks/useFocusTrap.ts +62 -111
  470. package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
  471. package/src/hooks/useHotkeys.test.ts +18 -0
  472. package/src/hooks/useHotkeys.ts +10 -3
  473. package/src/hooks/useListFocus.doc.mjs +2 -2
  474. package/src/hooks/useListFocus.test.tsx +65 -3
  475. package/src/hooks/useListFocus.ts +15 -7
  476. package/src/hooks/useMergedRefs.doc.mjs +79 -0
  477. package/src/hooks/useMergedRefs.test.tsx +62 -0
  478. package/src/hooks/useMergedRefs.ts +36 -0
  479. package/src/index.ts +1 -0
  480. package/src/theme/MediaTheme.doc.mjs +5 -5
  481. package/src/utils/groupItems.ts +12 -6
  482. package/src/utils/index.ts +1 -1
  483. package/src/utils/sharedResizeObserver.ts +10 -2
  484. package/src/utils/timeParser.test.ts +6 -1
  485. package/src/utils/timeParser.ts +35 -13
  486. package/src/__tests__/inputWidthContract.test.tsx +0 -69
@@ -28,7 +28,7 @@ import {useId, useMemo, useState, type ReactNode} from 'react';
28
28
  import type {BaseProps} from '../BaseProps';
29
29
  import {resolveSize, type AvatarSize} from '../Avatar';
30
30
  import * as stylex from '@stylexjs/stylex';
31
- import {mergeProps, mergeRefs} from '../utils';
31
+ import {mergeProps} from '../utils';
32
32
  import {composeEventHandlers} from '../utils/composeEventHandlers';
33
33
  import {AvatarGroupContext} from './AvatarGroupContext';
34
34
  import {themeProps} from '../utils/themeProps';
@@ -37,6 +37,7 @@ import {useListFocus} from '../hooks/useListFocus';
37
37
  import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
38
38
  import {VisuallyHidden} from '../VisuallyHidden';
39
39
 
40
+ import {useMergedRefs} from '../hooks/useMergedRefs';
40
41
  const OVERLAP_RATIO = 0.25;
41
42
 
42
43
  export interface AvatarGroupProps extends BaseProps<HTMLDivElement> {
@@ -143,7 +144,7 @@ export function AvatarGroup({
143
144
  <AvatarGroupContext value={contextValue}>
144
145
  <div
145
146
  {...props}
146
- ref={mergeRefs(ref, listRef)}
147
+ ref={useMergedRefs(ref, listRef)}
147
148
  role="group"
148
149
  aria-label={ariaLabel}
149
150
  aria-describedby={describedBy}
@@ -17,6 +17,7 @@ export const docs = {
17
17
  {guidance: true, description: 'Keep titles short and scannable: "Payment failed" not "There was a problem processing your most recent payment."'},
18
18
  {guidance: false, description: 'Use Banner for short-lived messages that disappear on their own; use Toast instead.'},
19
19
  {guidance: false, description: 'Stack multiple banners with the same status; combine related messages into one banner.'},
20
+ {guidance: true, description: 'Set collapsible={false} when the user needs the content to act on the message, like the list of fields that failed validation. Keep the default toggle when the detail is long enough to bury the banner\u2019s own message.'},
20
21
  {guidance: true, description: 'Error and warning banners render as role="alert"; info and success render as role="status". Mount an alert banner in response to an event rather than on first paint, so assistive tech has a change to report.'},
21
22
  {guidance: false, description: 'Rely on the status color or icon alone to carry meaning; say which status it is in the title text, because the icon is decorative to a screen reader.'},
22
23
  ],
@@ -26,7 +27,7 @@ export const docs = {
26
27
  {name: 'Description', required: false, description: 'Additional detail below the title.'},
27
28
  {name: 'Action button', required: false, description: 'A button for the user to act on the message, like "Review" or "Retry".'},
28
29
  {name: 'Dismiss button', required: false, description: 'Lets the user close the banner. Enabled by setting isDismissable.'},
29
- {name: 'Collapsible content', required: false, description: 'Extra detail that expands below the banner header, like a list of errors.'},
30
+ {name: 'Content', required: false, description: 'Extra detail below the banner header, like a list of errors. Sits behind an expand/collapse toggle by default; set collapsible={false} to keep it visible.'},
30
31
  ],
31
32
  },
32
33
 
@@ -94,14 +95,14 @@ export const docs = {
94
95
  name: 'children',
95
96
  type: 'ReactNode',
96
97
  description:
97
- 'Content rendered in the card-background area below the colored header.',
98
+ 'Content rendered in the card-background area below the colored header. Sits behind an expand/collapse toggle unless `collapsible={false}`.',
98
99
  },
99
100
  {
100
- name: 'defaultIsExpanded',
101
- type: 'boolean',
101
+ name: 'collapsible',
102
+ type: 'boolean | {defaultIsOpen?: boolean; isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void}',
102
103
  description:
103
- 'Whether the content area (children) starts expanded. Only relevant when children are provided.',
104
- default: 'false',
104
+ 'Whether the content area (children) sits behind an expand/collapse toggle in the header. On by default, starting collapsed. `false` opts out: children are always visible with no toggle. `{defaultIsOpen: true}` starts open; `{isOpen, onOpenChange}` is controlled. Takes the same CollapsibleConfig as Collapsible.',
105
+ default: 'true',
105
106
  },
106
107
  {
107
108
  name: 'xstyle',
@@ -154,7 +155,7 @@ export const docsZh = {
154
155
  {name: 'Description', required: false, description: 'Additional detail below the title.'},
155
156
  {name: 'Action button', required: false, description: 'A button for the user to act on the message, like "Review" or "Retry".'},
156
157
  {name: 'Dismiss button', required: false, description: 'Lets the user close the banner. Enabled by setting isDismissable.'},
157
- {name: 'Collapsible content', required: false, description: 'Extra detail that expands below the banner header, like a list of errors.'},
158
+ {name: 'Content', required: false, description: 'Extra detail below the banner header, like a list of errors. Sits behind an expand/collapse toggle by default; set collapsible={false} to keep it visible.'},
158
159
  ],
159
160
  },
160
161
  props: [
@@ -167,8 +168,8 @@ export const docsZh = {
167
168
  {name: 'endContent', type: 'ReactNode', description: '渲染在头部区域末端对齐的操作内容,通常是按钮或链接。头部过窄时会整体换行到文本下方,自成一行。'},
168
169
  {name: 'container', type: "'card' | 'section'", description: '视觉变体:card 带圆角;section 无圆角全宽,适用于页面级场景。', default: "'card'"},
169
170
  {name: 'elevation', type: "'none' | 'low' | 'med' | 'high'", description: '静止阴影深度。用于悬浮于内容之上的浮动横幅;none 为默认内联横幅。', default: "'none'"},
170
- {name: 'children', type: 'ReactNode', description: '渲染在彩色头部下方卡片背景区域的内容。'},
171
- {name: 'defaultIsExpanded', type: 'boolean', description: '内容区域(children)是否初始展开。仅在提供 children 时相关。', default: 'false'},
171
+ {name: 'children', type: 'ReactNode', description: '渲染在彩色头部下方卡片背景区域的内容。默认位于展开/折叠开关之后,除非设置 collapsible={false}。'},
172
+ {name: 'collapsible', type: 'boolean | {defaultIsOpen?: boolean; isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void}', description: '内容区域(children)是否位于头部的展开/折叠开关之后。默认开启,且初始为折叠状态。false 表示关闭:内容始终可见且没有开关。{defaultIsOpen: true} 表示初始展开;{isOpen, onOpenChange} 为受控模式。与 Collapsible 使用同一套 CollapsibleConfig。', default: 'true'},
172
173
  {
173
174
  name: 'xstyle',
174
175
  type: 'StyleXStyles',
@@ -230,7 +231,7 @@ export const docsDense = {
230
231
  {name: 'Description', required: false, description: 'Detail below title.'},
231
232
  {name: 'Action button', required: false, description: 'CTA like Review or Retry.'},
232
233
  {name: 'Dismiss button', required: false, description: 'Close button via isDismissable.'},
233
- {name: 'Collapsible content', required: false, description: 'Expandable detail area.'},
234
+ {name: 'Content', required: false, description: 'Detail area; behind a toggle unless collapsible={false}.'},
234
235
  ],
235
236
  },
236
237
  propDescriptions: {
@@ -243,7 +244,8 @@ export const docsDense = {
243
244
  endContent: 'end-aligned action in header, typically button/link; wraps to its own row when the header is too narrow',
244
245
  container: 'card=border-radius; section=full-width no radius for page-level',
245
246
  elevation: 'resting shadow depth: none|low|med|high; raise for a floating banner',
246
- children: 'content in card-bg area below colored header',
247
+ children: 'content in card-bg area below colored header; behind a toggle unless collapsible={false}',
248
+ collapsible: 'children behind a header toggle; default true=collapsed. false=always visible, no toggle. {defaultIsOpen:true}=starts open, {isOpen,onOpenChange}=controlled',
247
249
  xstyle: 'StyleX layout customization via stylex.create()',
248
250
  },
249
251
  };
@@ -184,33 +184,84 @@ describe('Banner', () => {
184
184
  });
185
185
 
186
186
  // =========================================================================
187
- // Collapsible content area
187
+ // Content area — collapsible by default, `collapsible={false}` opts out
188
188
  // =========================================================================
189
189
 
190
- it('hides children by default (collapsed)', () => {
190
+ it('hides children behind a toggle by default', () => {
191
191
  render(
192
192
  <Banner status="info" title="Collapsible">
193
193
  <div data-testid="child-content">Extra content</div>
194
194
  </Banner>,
195
195
  );
196
+ // The historical default, unchanged: chevron present, content collapsed.
196
197
  expect(screen.queryByTestId('child-content')).not.toBeInTheDocument();
198
+ expect(screen.getByRole('button', {name: 'Expand'})).toBeInTheDocument();
199
+ });
200
+
201
+ it('treats an explicit collapsible={true} as the default', () => {
202
+ render(
203
+ <Banner status="info" title="Explicit" collapsible>
204
+ <div data-testid="child-content">Extra content</div>
205
+ </Banner>,
206
+ );
207
+ expect(screen.queryByTestId('child-content')).not.toBeInTheDocument();
208
+ expect(screen.getByRole('button', {name: 'Expand'})).toBeInTheDocument();
197
209
  });
198
210
 
199
- it('shows children when defaultIsExpanded is true', () => {
211
+ it('shows children with no toggle for collapsible={false}', () => {
200
212
  render(
201
- <Banner status="info" title="Expanded" defaultIsExpanded>
213
+ <Banner status="info" title="Opted out" collapsible={false}>
202
214
  <div data-testid="child-content">Extra content</div>
203
215
  </Banner>,
204
216
  );
205
217
  expect(screen.getByTestId('child-content')).toBeInTheDocument();
218
+ expect(
219
+ screen.queryByRole('button', {name: 'Expand'}),
220
+ ).not.toBeInTheDocument();
221
+ expect(
222
+ screen.queryByRole('button', {name: 'Collapse'}),
223
+ ).not.toBeInTheDocument();
206
224
  });
207
225
 
208
- it('shows expand button when children are provided', () => {
226
+ it('leaves non-collapsible content out of the disclosure wiring', () => {
209
227
  render(
210
- <Banner status="info" title="With Toggle">
211
- <div>Content</div>
228
+ <Banner
229
+ status="info"
230
+ title="Plain content"
231
+ isDismissable
232
+ collapsible={false}>
233
+ <div data-testid="child-content">Extra content</div>
234
+ </Banner>,
235
+ );
236
+ // No toggle exists, so nothing should carry disclosure state, and the
237
+ // region needs no id for a button to point at.
238
+ const dismiss = screen.getByRole('button', {name: 'Dismiss'});
239
+ expect(dismiss).not.toHaveAttribute('aria-expanded');
240
+ expect(dismiss).not.toHaveAttribute('aria-controls');
241
+ expect(
242
+ screen.getByTestId('child-content').parentElement,
243
+ ).not.toHaveAttribute('id');
244
+ });
245
+
246
+ it('starts open for collapsible={{defaultIsOpen: true}}', () => {
247
+ render(
248
+ <Banner status="info" title="Open" collapsible={{defaultIsOpen: true}}>
249
+ <div data-testid="child-content">Content</div>
250
+ </Banner>,
251
+ );
252
+ expect(screen.getByTestId('child-content')).toBeInTheDocument();
253
+ expect(screen.getByRole('button', {name: 'Collapse'})).toBeInTheDocument();
254
+ });
255
+
256
+ it('treats a null collapsible as the default', () => {
257
+ render(
258
+ // @ts-expect-error null is outside the prop's type, but JS callers and a
259
+ // value widened to `| null` still reach this.
260
+ <Banner status="info" title="Nullish" collapsible={null}>
261
+ <div data-testid="child-content">Content</div>
212
262
  </Banner>,
213
263
  );
264
+ expect(screen.queryByTestId('child-content')).not.toBeInTheDocument();
214
265
  expect(screen.getByRole('button', {name: 'Expand'})).toBeInTheDocument();
215
266
  });
216
267
 
@@ -247,13 +298,38 @@ describe('Banner', () => {
247
298
  expect(screen.getByRole('button', {name: 'Expand'})).toBeInTheDocument();
248
299
  });
249
300
 
250
- it('shows collapse button when defaultIsExpanded', () => {
301
+ it('reports open-state changes through onOpenChange', async () => {
302
+ const user = userEvent.setup();
303
+ const onOpenChange = vi.fn();
251
304
  render(
252
- <Banner status="info" title="Expanded" defaultIsExpanded>
253
- <div>Content</div>
305
+ <Banner status="info" title="Notify" collapsible={{onOpenChange}}>
306
+ <div data-testid="child-content">Extra content</div>
254
307
  </Banner>,
255
308
  );
256
- expect(screen.getByRole('button', {name: 'Collapse'})).toBeInTheDocument();
309
+
310
+ await user.click(screen.getByRole('button', {name: 'Expand'}));
311
+ expect(onOpenChange).toHaveBeenCalledWith(true);
312
+ await user.click(screen.getByRole('button', {name: 'Collapse'}));
313
+ expect(onOpenChange).toHaveBeenLastCalledWith(false);
314
+ });
315
+
316
+ it('defers to the consumer when collapsible is controlled', async () => {
317
+ const user = userEvent.setup();
318
+ const onOpenChange = vi.fn();
319
+ render(
320
+ <Banner
321
+ status="info"
322
+ title="Controlled"
323
+ collapsible={{isOpen: false, onOpenChange}}>
324
+ <div data-testid="child-content">Extra content</div>
325
+ </Banner>,
326
+ );
327
+
328
+ // A controlled banner must not move on its own: the click reports, the
329
+ // content stays hidden until the consumer re-renders with isOpen.
330
+ await user.click(screen.getByRole('button', {name: 'Expand'}));
331
+ expect(onOpenChange).toHaveBeenCalledWith(true);
332
+ expect(screen.queryByTestId('child-content')).not.toBeInTheDocument();
257
333
  });
258
334
 
259
335
  it('renders expand button to the left of dismiss button', () => {
@@ -273,7 +349,10 @@ describe('Banner', () => {
273
349
 
274
350
  it('links the expand toggle to its content region via aria-controls', () => {
275
351
  render(
276
- <Banner status="info" title="Controls Test" defaultIsExpanded>
352
+ <Banner
353
+ status="info"
354
+ title="Controls Test"
355
+ collapsible={{defaultIsOpen: true}}>
277
356
  <div data-testid="region-content">Region content</div>
278
357
  </Banner>,
279
358
  );
@@ -509,9 +588,17 @@ describe('Banner', () => {
509
588
  <p>Detail</p>
510
589
  </Banner>,
511
590
  );
512
- expect(
513
- screen.getByRole('button', {name: 'Expand'}),
514
- ).toBeInTheDocument();
591
+ expect(screen.getByRole('button', {name: 'Expand'})).toBeInTheDocument();
592
+ });
593
+
594
+ it('renders no content area for children that render nothing', () => {
595
+ const {container} = render(
596
+ <Banner status="info" title="Heads up" collapsible={false}>
597
+ {false}
598
+ </Banner>,
599
+ );
600
+ // Header only — an empty slot must not draw the card-background area.
601
+ expect(container.firstElementChild?.children).toHaveLength(1);
515
602
  });
516
603
 
517
604
  it('renders no description node for a description that renders nothing', () => {
@@ -4,20 +4,24 @@
4
4
 
5
5
  /**
6
6
  * @file Banner.tsx
7
- * @input Uses React useState/useRef/useId, Button, Icon (with registry string names), StyleX
7
+ * @input Uses React useState/useRef/useId, useCollapsible, Button, Icon (with registry string names), StyleX
8
8
  * @output Exports Banner component, BannerProps, BannerStatus, BannerContainer types
9
9
  * @position Core implementation; consumed by index.ts, tested by Banner.test.tsx
10
10
  *
11
11
  * Visual structure:
12
12
  * - Root container: layout-only wrapper (flex column), no visual styling, no theme target
13
13
  * - Header area (themeProps 'banner'): colored status background with icon, title, description, actions, dismiss
14
- * - Content area (themeProps 'banner-content'): collapsible card background for additional content (children)
14
+ * - Content area (themeProps 'banner-content'): card background for additional content (children)
15
15
  * - Status icon (themeProps 'banner-icon'): the target rides on the default
16
16
  * <Icon> itself — the element that paints — so 'status:X' overrides reach
17
17
  * the glyph (#4166); for a custom `icon` node it stays on the layout wrapper
18
18
  * - No left border accent — color is expressed through the full header background
19
19
  * - Each visual area owns its own border-radius (no overflow:clip on the container)
20
- * - When children are provided, a collapse/expand toggle button appears in the end area
20
+ * - Children are collapsible by default: a toggle appears in the header end
21
+ * area and the content starts closed. `collapsible={false}` opts out and
22
+ * pins the content open with no toggle. The whole collapse axis (enabled /
23
+ * default / controlled) lives on that one prop and is driven by the shared
24
+ * `useCollapsible` hook rather than local state.
21
25
  *
22
26
  * A status added through `BannerStatusMap` augmentation has no entry in the
23
27
  * status lookups, so it renders with no status fill, no default glyph and the
@@ -55,6 +59,8 @@ import {
55
59
  easeVars,
56
60
  shadowVars,
57
61
  } from '../theme/tokens.stylex';
62
+ import {useCollapsible} from '../Collapsible/useCollapsible';
63
+ import type {CollapsibleConfig} from '../Collapsible/useCollapsible';
58
64
  import {composeEventHandlers, isRenderable, mergeProps} from '../utils';
59
65
  import type {Elevation} from '../utils/types';
60
66
  import {edgeCompSlot} from '../Layout/edgeCompensation.stylex';
@@ -140,15 +146,29 @@ export interface BannerProps extends BaseProps<HTMLDivElement> {
140
146
  */
141
147
  elevation?: Elevation;
142
148
  /**
143
- * Whether the content area (children) starts expanded.
144
- * Only relevant when children are provided.
145
- * @default false
149
+ * Whether the content area (children) sits behind an expand/collapse toggle
150
+ * in the header. On by default, so a banner with children behaves as it
151
+ * always has.
152
+ *
153
+ * - omitted / `true` — collapsible, starts collapsed
154
+ * - `{defaultIsOpen: true}` — collapsible, starts open
155
+ * - `{isOpen, onOpenChange}` — controlled by the consumer
156
+ * - `false` — not collapsible: children are always visible, with no toggle,
157
+ * no `aria-expanded`, and a content region that stays mounted
158
+ *
159
+ * Takes the shared `CollapsibleConfig` so a banner's disclosure is
160
+ * configured exactly like `Collapsible`'s, rather than through a
161
+ * Banner-only vocabulary. Banner's default differs from the hook's on one
162
+ * point — it starts closed, not open — because a banner's message lives in
163
+ * its header and the content is supplementary.
164
+ *
165
+ * @default true
146
166
  */
147
- defaultIsExpanded?: boolean;
167
+ collapsible?: boolean | CollapsibleConfig;
148
168
  /**
149
- * Extra content rendered below the header in a collapsible card-background area.
169
+ * Extra content rendered below the header in a card-background area.
150
170
  * Use for rich content like lists, links, or detailed information.
151
- * When provided, a collapse/expand toggle button appears in the header.
171
+ * Collapsed behind a toggle unless `collapsible={false}`.
152
172
  */
153
173
  children?: ReactNode;
154
174
  }
@@ -367,11 +387,12 @@ const elevationStyles = stylex.create({
367
387
  *
368
388
  * Two-part visual structure:
369
389
  * - Header: colored status background with icon, title, description, and actions
370
- * - Content (optional): collapsible card background area for additional rich content
390
+ * - Content (optional): card background area for additional rich content
371
391
  *
372
- * When children are provided, a collapse/expand chevron button appears in the
373
- * header end area (to the left of the dismiss button if present). Clicking it
374
- * toggles the visibility of the content area.
392
+ * When children are provided, a chevron toggle appears in the header end area
393
+ * (to the left of the dismiss button if present) and the content starts
394
+ * collapsed. Pass `collapsible={false}` for content that is always visible,
395
+ * or a config object to change the initial state or control it.
375
396
  *
376
397
  * Manages its own dismissed state internally — the banner hides on dismiss
377
398
  * even if `onDismiss` is not provided, so product teams don't need to wire
@@ -402,9 +423,14 @@ const elevationStyles = stylex.create({
402
423
  * <Banner
403
424
  * status="warning"
404
425
  * title="Configuration changes"
405
- * defaultIsExpanded>
426
+ * collapsible={{defaultIsOpen: true}}>
406
427
  * <p>Details here...</p>
407
428
  * </Banner>
429
+ * <Banner status="error" title="3 fields need attention" collapsible={false}>
430
+ * <ul>
431
+ * <li>Email address is invalid</li>
432
+ * </ul>
433
+ * </Banner>
408
434
  * ```
409
435
  */
410
436
  export function Banner({
@@ -417,7 +443,7 @@ export function Banner({
417
443
  endContent,
418
444
  container = 'card',
419
445
  elevation = 'none',
420
- defaultIsExpanded = false,
446
+ collapsible = true,
421
447
  children,
422
448
  xstyle,
423
449
  className,
@@ -429,7 +455,26 @@ export function Banner({
429
455
  }: BannerProps) {
430
456
  const t = useTranslator();
431
457
  const [isDismissed, setIsDismissed] = useState(false);
432
- const [isExpanded, setIsExpanded] = useState(defaultIsExpanded);
458
+ // The disclosure state machine is the shared one — Banner owns no collapse
459
+ // state of its own. `collapsible={false}` disables it, and that is what
460
+ // makes the content permanently visible.
461
+ //
462
+ // The one place Banner departs from the hook's defaults: `useCollapsible`
463
+ // opens by default, a banner starts closed. Its header already carries the
464
+ // message, so the content is supplementary — and this is the behaviour
465
+ // Banner has always had.
466
+ const collapsibleConfig =
467
+ collapsible != null && typeof collapsible === 'object' ? collapsible : {};
468
+ const {
469
+ isEnabled: isCollapsible,
470
+ isOpen: isExpanded,
471
+ toggle: handleToggleExpand,
472
+ } = useCollapsible({
473
+ isCollapsible: collapsible !== false && {
474
+ ...collapsibleConfig,
475
+ defaultIsOpen: collapsibleConfig.defaultIsOpen ?? false,
476
+ },
477
+ });
433
478
  // The element focus came from before it entered the banner. Dismissing
434
479
  // unmounts the whole banner, dismiss button included, so without a handoff
435
480
  // the browser drops focus to <body> and a keyboard user loses their place.
@@ -438,7 +483,8 @@ export function Banner({
438
483
  // Links the expand/collapse toggle to the content region it shows/hides so
439
484
  // assistive tech can move from the button to its controlled content
440
485
  // (disclosure pattern). The region is conditionally rendered, so aria-controls
441
- // below is set only while it's mounted to avoid a dangling reference.
486
+ // below is set only while it's mounted to avoid a dangling reference. A
487
+ // non-collapsible banner has no toggle, so neither end of the link is used.
442
488
  const contentId = useId();
443
489
  const defaultIconName = defaultIconNames[status];
444
490
  const role = statusRole[status] ?? FALLBACK_ROLE;
@@ -482,18 +528,19 @@ export function Banner({
482
528
  }
483
529
  };
484
530
 
485
- const handleToggleExpand = () => {
486
- setIsExpanded(prev => !prev);
487
- };
488
-
531
+ // The toggle exists only for a collapsible banner that actually has content
532
+ // to disclose.
533
+ const hasToggle = isCollapsible && hasChildren;
489
534
  // Show the end area if there are actions, dismiss, or a collapsible toggle
490
- const showEndArea = isRenderable(endContent) || isDismissable || hasChildren;
535
+ const showEndArea = isRenderable(endContent) || isDismissable || hasToggle;
491
536
  // Center items vertically when there's only a title (no description)
492
537
  // and the banner has action buttons
493
538
  const hasActions = isRenderable(endContent) || isDismissable;
494
539
  const isSingleLine = !isRenderable(description) && hasActions;
495
540
 
496
- const showContent = hasChildren && isExpanded;
541
+ // Non-collapsible children are always shown; collapsible ones follow the
542
+ // hook's open state.
543
+ const showContent = hasChildren && (!isCollapsible || isExpanded);
497
544
  const isCard = container === 'card';
498
545
 
499
546
  return (
@@ -575,7 +622,7 @@ export function Banner({
575
622
  edgeCompSlot.inset(spacingVars['--spacing-2']),
576
623
  )}>
577
624
  {endContent}
578
- {hasChildren && (
625
+ {hasToggle && (
579
626
  <Button
580
627
  variant="ghost"
581
628
  size="sm"
@@ -623,7 +670,7 @@ export function Banner({
623
670
  {/* Content area: collapsible card background — theme target ('banner-content') */}
624
671
  {showContent && (
625
672
  <div
626
- id={contentId}
673
+ id={hasToggle ? contentId : undefined}
627
674
  {...mergeProps(
628
675
  themeProps('banner-content', {container, status}),
629
676
  stylex.props(styles.contentArea, isCard && styles.contentAreaCard),
@@ -334,6 +334,106 @@ describe('BottomSheet', () => {
334
334
  expect(onOpenChange).toHaveBeenCalledWith(false);
335
335
  });
336
336
 
337
+ it('ignores Escape while an IME composition is active', () => {
338
+ const onOpenChange = vi.fn();
339
+ render(
340
+ <BottomSheet isOpen onOpenChange={onOpenChange} label="Filters">
341
+ <input aria-label="Search" />
342
+ </BottomSheet>,
343
+ );
344
+ const dialog = screen.getByRole('dialog');
345
+
346
+ fireEvent.keyDown(dialog, {key: 'Escape', isComposing: true});
347
+ fireEvent.keyDown(dialog, {key: 'Escape', keyCode: 229});
348
+
349
+ expect(onOpenChange).not.toHaveBeenCalled();
350
+ });
351
+
352
+ it('claims the composing Escape so no native close request follows', () => {
353
+ const onOpenChange = vi.fn();
354
+ render(
355
+ <BottomSheet isOpen onOpenChange={onOpenChange} label="Filters">
356
+ <input aria-label="Search" />
357
+ </BottomSheet>,
358
+ );
359
+ const dialog = screen.getByRole('dialog');
360
+
361
+ // An unclaimed Escape lets the browser raise its own close request, which
362
+ // arrives as `cancel` and dismisses the sheet on the same keypress. The
363
+ // guard has to swallow the composing Escape, not merely skip dismissal.
364
+ const wasNotClaimed = fireEvent.keyDown(dialog, {
365
+ key: 'Escape',
366
+ isComposing: true,
367
+ });
368
+
369
+ expect(wasNotClaimed).toBe(false);
370
+ expect(onOpenChange).not.toHaveBeenCalled();
371
+ });
372
+
373
+ it('keeps a form sheet open when Escape cancels a field composition', () => {
374
+ const onOpenChange = vi.fn();
375
+ render(
376
+ <BottomSheet
377
+ isOpen
378
+ purpose="form"
379
+ onOpenChange={onOpenChange}
380
+ label="Edit profile">
381
+ <input aria-label="Name" />
382
+ </BottomSheet>,
383
+ );
384
+
385
+ // The reported path: the keydown starts at the composing field and bubbles
386
+ // to the sheet's <dialog>, so the guard has to survive the trip.
387
+ fireEvent.keyDown(screen.getByRole('textbox', {name: 'Name'}), {
388
+ key: 'Escape',
389
+ isComposing: true,
390
+ });
391
+
392
+ expect(onOpenChange).not.toHaveBeenCalled();
393
+ });
394
+
395
+ it('still dismisses on the Escape after a composition is cancelled', () => {
396
+ const onOpenChange = vi.fn();
397
+ render(
398
+ <BottomSheet isOpen onOpenChange={onOpenChange} label="Filters">
399
+ <input aria-label="Search" />
400
+ </BottomSheet>,
401
+ );
402
+ const dialog = screen.getByRole('dialog');
403
+
404
+ fireEvent.keyDown(dialog, {key: 'Escape', isComposing: true});
405
+ expect(onOpenChange).not.toHaveBeenCalled();
406
+
407
+ fireEvent.keyDown(dialog, {key: 'Escape'});
408
+
409
+ expect(onOpenChange).toHaveBeenCalledWith(false);
410
+ });
411
+
412
+ it('leaves the IME the composition keys that are not Escape', () => {
413
+ const onOpenChange = vi.fn();
414
+ render(
415
+ <BottomSheet isOpen onOpenChange={onOpenChange} label="Filters">
416
+ <input aria-label="Search" />
417
+ </BottomSheet>,
418
+ );
419
+ const dialog = screen.getByRole('dialog');
420
+
421
+ // Enter commits a candidate and the arrows walk the candidate window. The
422
+ // sheet must claim neither, or the IME loses keys it owns.
423
+ const enterUnclaimed = fireEvent.keyDown(dialog, {
424
+ key: 'Enter',
425
+ isComposing: true,
426
+ });
427
+ const arrowUnclaimed = fireEvent.keyDown(dialog, {
428
+ key: 'ArrowDown',
429
+ isComposing: true,
430
+ });
431
+
432
+ expect(enterUnclaimed).toBe(true);
433
+ expect(arrowUnclaimed).toBe(true);
434
+ expect(onOpenChange).not.toHaveBeenCalled();
435
+ });
436
+
337
437
  it('requests close when the scrim (dialog element itself) is clicked', () => {
338
438
  const onOpenChange = vi.fn();
339
439
  render(
@@ -474,6 +574,29 @@ describe('BottomSheet', () => {
474
574
  expect(onOpenChange).toHaveBeenCalledWith(false);
475
575
  });
476
576
 
577
+ it('ignores Escape while an IME composition is active', () => {
578
+ const onOpenChange = vi.fn();
579
+ render(
580
+ <BottomSheet
581
+ isOpen
582
+ hasScrim={false}
583
+ onOpenChange={onOpenChange}
584
+ label="Filters">
585
+ <input aria-label="Search" />
586
+ </BottomSheet>,
587
+ );
588
+
589
+ // Escape is this sheet's only keyboard route out — there is no native
590
+ // close request behind it — so the guard is all that stands between a
591
+ // composing CJK user and a dismissed sheet.
592
+ fireEvent.keyDown(screen.getByRole('dialog'), {
593
+ key: 'Escape',
594
+ isComposing: true,
595
+ });
596
+
597
+ expect(onOpenChange).not.toHaveBeenCalled();
598
+ });
599
+
477
600
  it('still dismisses on a downward swipe past the threshold', () => {
478
601
  const onOpenChange = vi.fn();
479
602
  render(