@dxos/react-ui 0.8.4-main.c85a9c8dae → 0.8.4-main.cb12b3f963

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 (301) hide show
  1. package/dist/lib/browser/{chunk-2FKSMWNY.mjs → chunk-BDBC6H6V.mjs} +79 -5
  2. package/dist/lib/browser/chunk-BDBC6H6V.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +1203 -1020
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +44 -14
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/browser/translations.mjs +18 -0
  9. package/dist/lib/browser/translations.mjs.map +7 -0
  10. package/dist/lib/node-esm/{chunk-ZNBLTSHI.mjs → chunk-3JSJK2ZY.mjs} +79 -5
  11. package/dist/lib/node-esm/chunk-3JSJK2ZY.mjs.map +7 -0
  12. package/dist/lib/node-esm/index.mjs +1203 -1020
  13. package/dist/lib/node-esm/index.mjs.map +4 -4
  14. package/dist/lib/node-esm/meta.json +1 -1
  15. package/dist/lib/node-esm/testing/index.mjs +44 -14
  16. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  17. package/dist/lib/node-esm/translations.mjs +20 -0
  18. package/dist/lib/node-esm/translations.mjs.map +7 -0
  19. package/dist/types/src/components/Avatars/Avatar.d.ts +1 -1
  20. package/dist/types/src/components/Avatars/Avatar.d.ts.map +1 -1
  21. package/dist/types/src/components/Avatars/Avatar.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/Avatars/AvatarGroup.stories.d.ts.map +1 -1
  23. package/dist/types/src/components/Breadcrumb/Breadcrumb.d.ts.map +1 -1
  24. package/dist/types/src/components/Breadcrumb/Breadcrumb.stories.d.ts.map +1 -1
  25. package/dist/types/src/components/Button/Button.d.ts +2 -2
  26. package/dist/types/src/components/Button/Button.d.ts.map +1 -1
  27. package/dist/types/src/components/Button/Button.stories.d.ts +1 -1
  28. package/dist/types/src/components/Button/Button.stories.d.ts.map +1 -1
  29. package/dist/types/src/components/Button/IconButton.d.ts +1 -0
  30. package/dist/types/src/components/Button/IconButton.d.ts.map +1 -1
  31. package/dist/types/src/components/Button/IconButton.stories.d.ts +3 -0
  32. package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -1
  33. package/dist/types/src/components/Button/Toggle.d.ts +2 -2
  34. package/dist/types/src/components/Button/Toggle.d.ts.map +1 -1
  35. package/dist/types/src/components/Button/Toggle.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/Button/ToggleGroup.d.ts +6 -6
  37. package/dist/types/src/components/Button/ToggleGroup.d.ts.map +1 -1
  38. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts +2 -2
  39. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/Card/Card.d.ts +70 -64
  41. package/dist/types/src/components/Card/Card.d.ts.map +1 -1
  42. package/dist/types/src/components/Card/Card.stories.d.ts +2 -2
  43. package/dist/types/src/components/Card/Card.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/Clipboard/ClipboardProvider.d.ts.map +1 -1
  45. package/dist/types/src/components/Clipboard/CopyButton.d.ts.map +1 -1
  46. package/dist/types/src/components/Clipboard/index.d.ts +10 -1
  47. package/dist/types/src/components/Clipboard/index.d.ts.map +1 -1
  48. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts.map +1 -1
  49. package/dist/types/src/components/Dialog/AlertDialog.d.ts +42 -31
  50. package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -1
  51. package/dist/types/src/components/Dialog/AlertDialog.stories.d.ts.map +1 -1
  52. package/dist/types/src/components/Dialog/Dialog.d.ts +47 -30
  53. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -1
  54. package/dist/types/src/components/Dialog/Dialog.stories.d.ts +3 -2
  55. package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/ElevationProvider/ElevationProvider.d.ts.map +1 -1
  57. package/dist/types/src/components/ErrorFallback/ErrorFallback.d.ts.map +1 -1
  58. package/dist/types/src/components/ErrorFallback/ErrorFallback.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/ErrorFallback/ErrorStack.d.ts +14 -3
  60. package/dist/types/src/components/ErrorFallback/ErrorStack.d.ts.map +1 -1
  61. package/dist/types/src/components/ErrorFallback/ThrowError.d.ts.map +1 -1
  62. package/dist/types/src/components/Focus/Focus.d.ts +36 -0
  63. package/dist/types/src/components/Focus/Focus.d.ts.map +1 -0
  64. package/dist/types/src/components/Focus/Focus.stories.d.ts +9 -0
  65. package/dist/types/src/components/Focus/Focus.stories.d.ts.map +1 -0
  66. package/dist/types/src/components/Focus/index.d.ts +2 -0
  67. package/dist/types/src/components/Focus/index.d.ts.map +1 -0
  68. package/dist/types/src/components/Icon/Icon.d.ts +3 -0
  69. package/dist/types/src/components/Icon/Icon.d.ts.map +1 -1
  70. package/dist/types/src/components/Icon/Icon.stories.d.ts +11 -3
  71. package/dist/types/src/components/Icon/Icon.stories.d.ts.map +1 -1
  72. package/dist/types/src/components/Image/Image.d.ts +2 -1
  73. package/dist/types/src/components/Image/Image.d.ts.map +1 -1
  74. package/dist/types/src/components/Image/Image.stories.d.ts +3 -2
  75. package/dist/types/src/components/Image/Image.stories.d.ts.map +1 -1
  76. package/dist/types/src/components/Input/Input.d.ts +14 -17
  77. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  78. package/dist/types/src/components/Input/Input.stories.d.ts +3 -3
  79. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  80. package/dist/types/src/components/Link/Link.stories.d.ts.map +1 -1
  81. package/dist/types/src/components/List/List.d.ts +5 -3
  82. package/dist/types/src/components/List/List.d.ts.map +1 -1
  83. package/dist/types/src/components/List/List.stories.d.ts +3 -1
  84. package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
  85. package/dist/types/src/components/List/ListDropIndicator.d.ts.map +1 -1
  86. package/dist/types/src/components/List/Tree.d.ts +2 -2
  87. package/dist/types/src/components/List/Tree.d.ts.map +1 -1
  88. package/dist/types/src/components/List/Tree.stories.d.ts.map +1 -1
  89. package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +1 -1
  90. package/dist/types/src/components/List/Treegrid.d.ts +5 -9
  91. package/dist/types/src/components/List/Treegrid.d.ts.map +1 -1
  92. package/dist/types/src/components/List/Treegrid.stories.d.ts.map +1 -1
  93. package/dist/types/src/components/Main/Main.d.ts +7 -3
  94. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  95. package/dist/types/src/components/Main/Main.stories.d.ts.map +1 -1
  96. package/dist/types/src/components/Main/useSwipeToDismiss.d.ts.map +1 -1
  97. package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -1
  98. package/dist/types/src/components/Menu/ContextMenu.stories.d.ts.map +1 -1
  99. package/dist/types/src/components/Menu/DropdownMenu.d.ts +58 -49
  100. package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -1
  101. package/dist/types/src/components/Menu/DropdownMenu.stories.d.ts +14 -1
  102. package/dist/types/src/components/Menu/DropdownMenu.stories.d.ts.map +1 -1
  103. package/dist/types/src/components/Message/Message.d.ts +1 -1
  104. package/dist/types/src/components/Message/Message.d.ts.map +1 -1
  105. package/dist/types/src/components/Message/Message.stories.d.ts +2 -2
  106. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  107. package/dist/types/src/components/Popover/Popover.d.ts +38 -22
  108. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  109. package/dist/types/src/components/Popover/Popover.stories.d.ts.map +1 -1
  110. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +12 -11
  111. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  112. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +21 -10
  113. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  114. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +42 -13
  115. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -1
  116. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +5 -6
  117. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -1
  118. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  119. package/dist/types/src/components/Select/Select.stories.d.ts +2 -2
  120. package/dist/types/src/components/Select/Select.stories.d.ts.map +1 -1
  121. package/dist/types/src/components/Separator/Separator.d.ts +3 -3
  122. package/dist/types/src/components/Separator/Separator.d.ts.map +1 -1
  123. package/dist/types/src/components/Skeleton/Skeleton.stories.d.ts.map +1 -1
  124. package/dist/types/src/components/Splitter/Splitter.d.ts +19 -21
  125. package/dist/types/src/components/Splitter/Splitter.d.ts.map +1 -1
  126. package/dist/types/src/components/Splitter/Splitter.stories.d.ts.map +1 -1
  127. package/dist/types/src/components/Status/Status.d.ts +3 -4
  128. package/dist/types/src/components/Status/Status.d.ts.map +1 -1
  129. package/dist/types/src/components/Status/Status.stories.d.ts.map +1 -1
  130. package/dist/types/src/components/Tag/Tag.stories.d.ts.map +1 -1
  131. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +1 -1
  132. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  133. package/dist/types/src/components/ThemeProvider/ThemeProvider.stories.d.ts +1 -1
  134. package/dist/types/src/components/ThemeProvider/ThemeProvider.stories.d.ts.map +1 -1
  135. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +54 -55
  136. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  137. package/dist/types/src/components/ThemeProvider/index.d.ts +1 -1
  138. package/dist/types/src/components/ThemeProvider/index.d.ts.map +1 -1
  139. package/dist/types/src/components/Toast/Toast.d.ts +16 -16
  140. package/dist/types/src/components/Toast/Toast.d.ts.map +1 -1
  141. package/dist/types/src/components/Toast/Toast.stories.d.ts.map +1 -1
  142. package/dist/types/src/components/Toolbar/Toolbar.d.ts +10 -20
  143. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  144. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  145. package/dist/types/src/components/Tooltip/Tooltip.d.ts +10 -10
  146. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  147. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts +2 -2
  148. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  149. package/dist/types/src/components/index.d.ts +1 -0
  150. package/dist/types/src/components/index.d.ts.map +1 -1
  151. package/dist/types/src/exemplars/generics.stories.d.ts +6 -5
  152. package/dist/types/src/exemplars/generics.stories.d.ts.map +1 -1
  153. package/dist/types/src/exemplars/slot.stories.d.ts +1 -0
  154. package/dist/types/src/exemplars/slot.stories.d.ts.map +1 -1
  155. package/dist/types/src/exemplars/tabster.stories.d.ts.map +1 -1
  156. package/dist/types/src/exemplars/virtualizer.stories.d.ts.map +1 -1
  157. package/dist/types/src/hooks/index.d.ts +1 -0
  158. package/dist/types/src/hooks/index.d.ts.map +1 -1
  159. package/dist/types/src/hooks/useDensityContext.d.ts +1 -1
  160. package/dist/types/src/hooks/useDensityContext.d.ts.map +1 -1
  161. package/dist/types/src/hooks/useElevationContext.d.ts.map +1 -1
  162. package/dist/types/src/hooks/useIconHref.d.ts.map +1 -1
  163. package/dist/types/src/hooks/useSafeArea.d.ts.map +1 -1
  164. package/dist/types/src/hooks/useSafeCollisionPadding.d.ts.map +1 -1
  165. package/dist/types/src/hooks/useVisualViewport.d.ts.map +1 -1
  166. package/dist/types/src/playground/Controls.stories.d.ts.map +1 -1
  167. package/dist/types/src/playground/Custom.stories.d.ts +1 -1
  168. package/dist/types/src/playground/Custom.stories.d.ts.map +1 -1
  169. package/dist/types/src/playground/Typography.stories.d.ts.map +1 -1
  170. package/dist/types/src/primitives/Column/Column.d.ts +20 -19
  171. package/dist/types/src/primitives/Column/Column.d.ts.map +1 -1
  172. package/dist/types/src/primitives/Column/Column.stories.d.ts +19 -0
  173. package/dist/types/src/primitives/Column/Column.stories.d.ts.map +1 -1
  174. package/dist/types/src/primitives/Container/Container.d.ts +7 -0
  175. package/dist/types/src/primitives/Container/Container.d.ts.map +1 -0
  176. package/dist/types/src/primitives/Container/Container.stories.d.ts +6 -0
  177. package/dist/types/src/primitives/Container/Container.stories.d.ts.map +1 -0
  178. package/dist/types/src/primitives/Container/index.d.ts +2 -0
  179. package/dist/types/src/primitives/Container/index.d.ts.map +1 -0
  180. package/dist/types/src/primitives/Flex/Flex.d.ts +8 -5
  181. package/dist/types/src/primitives/Flex/Flex.d.ts.map +1 -1
  182. package/dist/types/src/primitives/Flex/Flex.stories.d.ts.map +1 -1
  183. package/dist/types/src/primitives/Grid/Grid.d.ts +6 -5
  184. package/dist/types/src/primitives/Grid/Grid.d.ts.map +1 -1
  185. package/dist/types/src/primitives/Grid/Grid.stories.d.ts.map +1 -1
  186. package/dist/types/src/primitives/Panel/Panel.d.ts +23 -22
  187. package/dist/types/src/primitives/Panel/Panel.d.ts.map +1 -1
  188. package/dist/types/src/primitives/Panel/Panel.stories.d.ts.map +1 -1
  189. package/dist/types/src/primitives/index.d.ts +1 -0
  190. package/dist/types/src/primitives/index.d.ts.map +1 -1
  191. package/dist/types/src/testing/Loading.d.ts +9 -0
  192. package/dist/types/src/testing/Loading.d.ts.map +1 -0
  193. package/dist/types/src/testing/decorators/withLayout.d.ts.map +1 -1
  194. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts.map +1 -1
  195. package/dist/types/src/testing/decorators/withTheme.d.ts +1 -1
  196. package/dist/types/src/testing/decorators/withTheme.d.ts.map +1 -1
  197. package/dist/types/src/testing/index.d.ts +1 -0
  198. package/dist/types/src/testing/index.d.ts.map +1 -1
  199. package/dist/types/src/translations.d.ts +3 -3
  200. package/dist/types/src/translations.d.ts.map +1 -1
  201. package/dist/types/src/util/usePx.d.ts.map +1 -1
  202. package/dist/types/tsconfig.tsbuildinfo +1 -1
  203. package/package.json +28 -25
  204. package/src/components/Avatars/Avatar.stories.tsx +2 -3
  205. package/src/components/Avatars/Avatar.tsx +1 -2
  206. package/src/components/Avatars/AvatarGroup.stories.tsx +0 -1
  207. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -2
  208. package/src/components/Button/Button.stories.tsx +0 -1
  209. package/src/components/Button/Button.tsx +5 -13
  210. package/src/components/Button/IconButton.stories.tsx +6 -4
  211. package/src/components/Button/IconButton.tsx +3 -4
  212. package/src/components/Button/Toggle.stories.tsx +0 -1
  213. package/src/components/Button/Toggle.tsx +4 -4
  214. package/src/components/Button/ToggleGroup.stories.tsx +0 -1
  215. package/src/components/Button/ToggleGroup.tsx +12 -16
  216. package/src/components/Card/Card.stories.tsx +12 -12
  217. package/src/components/Card/Card.tsx +283 -128
  218. package/src/components/Clipboard/CopyButton.tsx +3 -4
  219. package/src/components/Dialog/AlertDialog.stories.tsx +5 -6
  220. package/src/components/Dialog/AlertDialog.tsx +67 -126
  221. package/src/components/Dialog/Dialog.stories.tsx +64 -9
  222. package/src/components/Dialog/Dialog.tsx +84 -88
  223. package/src/components/ErrorFallback/ErrorFallback.stories.tsx +3 -8
  224. package/src/components/ErrorFallback/ErrorStack.tsx +36 -2
  225. package/src/components/Focus/AUDIT.md +43 -0
  226. package/src/components/Focus/Focus.stories.tsx +230 -0
  227. package/src/components/Focus/Focus.tsx +201 -0
  228. package/src/components/Focus/index.ts +5 -0
  229. package/src/components/Icon/Icon.stories.tsx +43 -13
  230. package/src/components/Icon/Icon.tsx +5 -1
  231. package/src/components/Image/Image.stories.tsx +3 -3
  232. package/src/components/Image/Image.tsx +31 -8
  233. package/src/components/Input/Input.stories.tsx +3 -4
  234. package/src/components/Input/Input.tsx +7 -7
  235. package/src/components/Link/Link.stories.tsx +0 -1
  236. package/src/components/List/List.stories.tsx +2 -3
  237. package/src/components/List/List.tsx +6 -5
  238. package/src/components/List/Tree.stories.tsx +1 -2
  239. package/src/components/List/Tree.tsx +0 -1
  240. package/src/components/List/TreeDropIndicator.tsx +1 -1
  241. package/src/components/List/Treegrid.stories.tsx +26 -27
  242. package/src/components/List/Treegrid.tsx +14 -14
  243. package/src/components/Main/Main.stories.tsx +0 -1
  244. package/src/components/Main/Main.tsx +1 -2
  245. package/src/components/Menu/ContextMenu.stories.tsx +0 -1
  246. package/src/components/Menu/DropdownMenu.stories.tsx +0 -1
  247. package/src/components/Menu/DropdownMenu.tsx +48 -42
  248. package/src/components/Message/Message.stories.tsx +7 -8
  249. package/src/components/Message/Message.tsx +23 -10
  250. package/src/components/Popover/Popover.stories.tsx +4 -5
  251. package/src/components/Popover/Popover.tsx +42 -42
  252. package/src/components/ScrollArea/ScrollArea.stories.tsx +93 -30
  253. package/src/components/ScrollArea/ScrollArea.tsx +41 -25
  254. package/src/components/ScrollContainer/ScrollContainer.stories.tsx +20 -18
  255. package/src/components/ScrollContainer/ScrollContainer.tsx +200 -91
  256. package/src/components/Select/Select.stories.tsx +5 -6
  257. package/src/components/Separator/Separator.tsx +4 -7
  258. package/src/components/Skeleton/Skeleton.stories.tsx +0 -1
  259. package/src/components/Splitter/Splitter.stories.tsx +29 -29
  260. package/src/components/Splitter/Splitter.tsx +35 -46
  261. package/src/components/Status/Status.stories.tsx +0 -1
  262. package/src/components/Status/Status.tsx +8 -5
  263. package/src/components/Tag/Tag.stories.tsx +0 -1
  264. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +0 -1
  265. package/src/components/ThemeProvider/ThemeProvider.tsx +5 -4
  266. package/src/components/ThemeProvider/index.ts +1 -1
  267. package/src/components/Toast/Toast.stories.tsx +0 -1
  268. package/src/components/Toast/Toast.tsx +16 -31
  269. package/src/components/Toolbar/Toolbar.stories.tsx +0 -1
  270. package/src/components/Toolbar/Toolbar.tsx +34 -47
  271. package/src/components/Tooltip/Tooltip.stories.tsx +6 -7
  272. package/src/components/Tooltip/Tooltip.tsx +15 -16
  273. package/src/components/index.ts +1 -0
  274. package/src/exemplars/generics.stories.tsx +7 -15
  275. package/src/exemplars/slot.stories.tsx +66 -68
  276. package/src/exemplars/tabster.stories.tsx +1 -1
  277. package/src/exemplars/virtualizer.stories.tsx +4 -4
  278. package/src/hooks/index.ts +1 -0
  279. package/src/hooks/useDensityContext.ts +2 -2
  280. package/src/playground/Custom.stories.tsx +6 -9
  281. package/src/primitives/Column/AUDIT.md +148 -0
  282. package/src/primitives/Column/Column.stories.tsx +128 -19
  283. package/src/primitives/Column/Column.tsx +89 -80
  284. package/src/primitives/Container/Container.stories.tsx +29 -0
  285. package/src/primitives/Container/Container.tsx +19 -0
  286. package/src/primitives/Container/index.ts +5 -0
  287. package/src/primitives/Flex/Flex.stories.tsx +0 -1
  288. package/src/primitives/Flex/Flex.tsx +20 -20
  289. package/src/primitives/Grid/Grid.stories.tsx +0 -1
  290. package/src/primitives/Grid/Grid.tsx +23 -36
  291. package/src/primitives/Panel/Panel.stories.tsx +9 -8
  292. package/src/primitives/Panel/Panel.tsx +43 -60
  293. package/src/primitives/index.ts +1 -0
  294. package/src/testing/Loading.tsx +47 -0
  295. package/src/testing/decorators/withLayout.tsx +6 -6
  296. package/src/testing/decorators/withTheme.tsx +10 -7
  297. package/src/testing/index.ts +2 -0
  298. package/src/translations.ts +3 -3
  299. package/src/util/usePx.ts +1 -0
  300. package/dist/lib/browser/chunk-2FKSMWNY.mjs.map +0 -7
  301. package/dist/lib/node-esm/chunk-ZNBLTSHI.mjs.map +0 -7
@@ -4,8 +4,8 @@
4
4
 
5
5
  import { createContext } from '@radix-ui/react-context';
6
6
  import React, {
7
- type HTMLAttributes,
8
7
  type PropsWithChildren,
8
+ type RefObject,
9
9
  forwardRef,
10
10
  useCallback,
11
11
  useEffect,
@@ -17,12 +17,14 @@ import React, {
17
17
 
18
18
  import { addEventListener, combine } from '@dxos/async';
19
19
  import { invariant } from '@dxos/invariant';
20
- import { useForwardedRef } from '@dxos/react-hooks';
20
+ import { useMergeRefs } from '@dxos/react-hooks';
21
+ import { composable, composableProps, slottable } from '@dxos/ui-theme';
21
22
  import { mx } from '@dxos/ui-theme';
23
+ import { type SlottableProps } from '@dxos/ui-types';
22
24
 
23
25
  import { type ThemedClassName } from '../../util';
24
26
  import { IconButton } from '../Button';
25
- import { ScrollArea } from '../ScrollArea';
27
+ import { ScrollArea, type ScrollAreaRootProps } from '../ScrollArea';
26
28
 
27
29
  const isBottom = (el: HTMLElement | null) => {
28
30
  return !!(el && el.scrollHeight - el.scrollTop === el.clientHeight);
@@ -35,9 +37,15 @@ export interface ScrollController {
35
37
  }
36
38
 
37
39
  type ScrollContainerContextValue = {
38
- scrollToBottom: (behavior?: ScrollBehavior) => void;
39
40
  controller?: ScrollController;
40
41
  pinned?: boolean;
42
+ overflow?: boolean;
43
+ /** Called by Viewport to register/unregister the scroll element. */
44
+ setViewport: (el: HTMLDivElement | null) => void;
45
+ /** Called by Viewport on wheel events to update pinned state. */
46
+ setPinned: (value: boolean) => void;
47
+ /** Called by Viewport on scroll events to update overflow state. */
48
+ setOverflow: (value: boolean) => void;
41
49
  };
42
50
 
43
51
  const [ScrollContainerProvider, useScrollContainerContext] =
@@ -47,102 +55,85 @@ const [ScrollContainerProvider, useScrollContainerContext] =
47
55
  // Root
48
56
  //
49
57
 
50
- type RootProps = ThemedClassName<
51
- PropsWithChildren<{
52
- pin?: boolean;
53
- fade?: boolean;
54
- behavior?: ScrollBehavior;
55
- }>
56
- >;
58
+ type RootProps = PropsWithChildren<{
59
+ pin?: boolean;
60
+ behavior?: ScrollBehavior;
61
+ }>;
57
62
 
58
63
  /**
59
- * Scroll container that automatically scrolls to the bottom when new content is added.
64
+ * Headless scroll container that provides context for scroll state.
65
+ * Render ScrollContainer.Content and ScrollContainer.Viewport as children.
60
66
  */
61
67
  const Root = forwardRef<ScrollController, RootProps>(
62
- ({ children, classNames, pin, fade, behavior: behaviorProp = 'smooth' }, forwardedRef) => {
63
- const scrollerRef = useRef<HTMLDivElement>(null);
68
+ ({ children, pin, behavior: behaviorProp = 'smooth' }, forwardedRef) => {
69
+ const scrollerRef = useRef<HTMLDivElement | null>(null);
64
70
  const autoScrollRef = useRef(false);
65
- const [overflow, setOverflow] = useState(false);
66
71
  const [pinned, setPinned] = useState(pin);
72
+ const [overflow, setOverflow] = useState(false);
67
73
 
68
74
  const timeoutRef = useRef<NodeJS.Timeout>(undefined);
69
- const scrollToBottom = useCallback((behavior: ScrollBehavior = behaviorProp) => {
70
- if (scrollerRef.current) {
71
- // Temporarily hide scrollbar to prevent flickering.
72
- autoScrollRef.current = true;
73
- scrollerRef.current.classList.add('scrollbar-none');
74
- scrollerRef.current.scrollTo({
75
- top: scrollerRef.current.scrollHeight,
76
- behavior,
77
- });
78
-
79
- clearTimeout(timeoutRef.current);
80
- if (behavior !== 'instant') {
81
- timeoutRef.current = setTimeout(() => {
82
- scrollerRef.current?.classList.remove('scrollbar-none');
83
- autoScrollRef.current = false;
84
- }, 500);
75
+ const scrollToBottom = useCallback(
76
+ (behavior: ScrollBehavior = behaviorProp) => {
77
+ if (scrollerRef.current) {
78
+ if (behavior !== 'instant') {
79
+ // Temporarily hide scrollbar to prevent flickering during smooth scroll.
80
+ // For instant scrolling we skip this — there's no animation to hide,
81
+ // and adding the class changes element size which re-fires the ResizeObserver.
82
+ autoScrollRef.current = true;
83
+ scrollerRef.current.classList.add('scrollbar-none');
84
+ clearTimeout(timeoutRef.current);
85
+ timeoutRef.current = setTimeout(() => {
86
+ scrollerRef.current?.classList.remove('scrollbar-none');
87
+ autoScrollRef.current = false;
88
+ }, 500);
89
+ }
90
+
91
+ scrollerRef.current.scrollTo({
92
+ top: scrollerRef.current.scrollHeight,
93
+ behavior,
94
+ });
95
+
96
+ setPinned(true);
85
97
  }
98
+ },
99
+ [behaviorProp],
100
+ );
86
101
 
87
- setPinned(true);
88
- }
89
- }, []);
90
-
91
- const controller = useMemo(
102
+ const controller = useMemo<ScrollController>(
92
103
  () => ({
93
- viewport: scrollerRef.current,
104
+ get viewport() {
105
+ return scrollerRef.current;
106
+ },
94
107
  scrollToTop: () => {
95
108
  invariant(scrollerRef.current);
96
109
  scrollerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
97
110
  setPinned(false);
98
111
  },
99
- scrollToBottom: () => {
100
- scrollToBottom('smooth');
112
+ scrollToBottom: (behavior = 'smooth' as ScrollBehavior) => {
113
+ scrollToBottom(behavior);
101
114
  },
102
115
  }),
103
- [scrollToBottom, scrollerRef.current],
116
+ [scrollToBottom],
104
117
  );
105
118
 
106
119
  // Scroll controller imperative ref.
107
120
  useImperativeHandle(forwardedRef, () => controller, [controller]);
108
121
 
109
- // Listen for scroll events.
110
- useEffect(() => {
111
- if (!scrollerRef.current) {
112
- return;
113
- }
114
-
115
- return combine(
116
- // Check if user scrolls.
117
- addEventListener(scrollerRef.current, 'wheel', () => {
118
- setPinned(isBottom(scrollerRef.current));
119
- }),
120
- // Check if scrolls.
121
- addEventListener(scrollerRef.current, 'scroll', () => {
122
- setOverflow((scrollerRef.current?.scrollTop ?? 0) > 0);
123
- }),
124
- );
122
+ // Called by Viewport when the scroll element mounts/unmounts.
123
+ const setViewport = useCallback((el: HTMLDivElement | null) => {
124
+ scrollerRef.current = el;
125
125
  }, []);
126
126
 
127
127
  return (
128
- <ScrollContainerProvider pinned={pinned} controller={controller} scrollToBottom={scrollToBottom}>
129
- <div className='relative grid dx-container overflow-hidden'>
130
- {fade && (
131
- <div
132
- role='none'
133
- data-visible={overflow}
134
- className={mx(
135
- // NOTE: Gradients may not be visible with dark reader extensions.
136
- 'z-10 absolute top-0 inset-x-0 h-24 w-full',
137
- 'opacity-0 duration-200 transition-opacity data-[visible="true"]:opacity-100',
138
- 'bg-gradient-to-b from-(--surface-bg) to-transparent pointer-events-none',
139
- )}
140
- />
141
- )}
142
- <ScrollArea.Root classNames={mx('min-h-0', classNames)} thin>
143
- <ScrollArea.Viewport ref={scrollerRef}>{children}</ScrollArea.Viewport>
144
- </ScrollArea.Root>
145
- </div>
128
+ <ScrollContainerProvider
129
+ pinned={pinned}
130
+ overflow={overflow}
131
+ controller={controller}
132
+ setViewport={setViewport}
133
+ setPinned={setPinned}
134
+ setOverflow={setOverflow}
135
+ >
136
+ {children}
146
137
  </ScrollContainerProvider>
147
138
  );
148
139
  },
@@ -150,41 +141,155 @@ const Root = forwardRef<ScrollController, RootProps>(
150
141
 
151
142
  Root.displayName = 'ScrollContainer.Root';
152
143
 
144
+ //
145
+ // Content
146
+ //
147
+
148
+ type ContentProps = Pick<ScrollAreaRootProps, 'thin' | 'padding' | 'centered'>;
149
+
150
+ /**
151
+ * Composable wrapper around ScrollArea.Root.
152
+ * Provides the DOM structure for the scroll container.
153
+ */
154
+ const Content = composable<HTMLDivElement, ContentProps>(
155
+ ({ children, thin, padding, centered, ...props }, forwardedRef) => {
156
+ return (
157
+ <ScrollArea.Root
158
+ {...composableProps(props, { classNames: 'relative' })}
159
+ thin={thin}
160
+ padding={padding}
161
+ centered={centered}
162
+ ref={forwardedRef}
163
+ >
164
+ {children}
165
+ </ScrollArea.Root>
166
+ );
167
+ },
168
+ );
169
+
170
+ Content.displayName = 'ScrollContainer.Content';
171
+
153
172
  //
154
173
  // Viewport
155
174
  //
156
175
 
157
176
  const VIEWPORT_NAME = 'ScrollContainer.Viewport';
158
177
 
159
- type ViewportProps = ThemedClassName<PropsWithChildren<Omit<HTMLAttributes<HTMLDivElement>, 'className'>>>;
178
+ type ViewportProps = SlottableProps;
160
179
 
161
- const Viewport = forwardRef<HTMLDivElement, ViewportProps>(({ classNames, children, ...props }, forwardedRef) => {
162
- const contentRef = useForwardedRef(forwardedRef);
163
- const { pinned, scrollToBottom } = useScrollContainerContext(VIEWPORT_NAME);
180
+ const Viewport = slottable<HTMLDivElement, ViewportProps>(({ children, asChild, ...props }, forwardedRef) => {
181
+ const scrollerRef = useRef<HTMLDivElement>(null);
182
+ const mergedRef = useMergeRefs([forwardedRef, scrollerRef]);
183
+ const { setViewport, setPinned, setOverflow } = useScrollContainerContext(VIEWPORT_NAME);
164
184
 
185
+ // Register the scroll element with Root and set up wheel/scroll listeners.
165
186
  useEffect(() => {
166
- if (!pinned || !contentRef.current) {
187
+ const el = scrollerRef.current;
188
+ if (!el) {
167
189
  return;
168
190
  }
169
191
 
170
- // Scroll instantly otherwise it might move while we're scrolling.
171
- scrollToBottom();
192
+ setViewport(el);
172
193
 
173
- // Setup resize observer to detect content changes.
174
- const resizeObserver = new ResizeObserver(() => scrollToBottom());
175
- resizeObserver.observe(contentRef.current);
176
- return () => resizeObserver.disconnect();
177
- }, [pinned, scrollToBottom]);
194
+ return combine(
195
+ addEventListener(el, 'wheel', () => setPinned(isBottom(el))),
196
+ addEventListener(el, 'scroll', () => setOverflow((el.scrollTop ?? 0) > 0)),
197
+ () => setViewport(null),
198
+ );
199
+ }, [setViewport, setPinned, setOverflow]);
178
200
 
179
201
  return (
180
- <div className={mx('w-full', classNames)} {...props} ref={contentRef}>
181
- {children}
182
- </div>
202
+ <>
203
+ <ScrollArea.Viewport asChild={asChild} {...composableProps(props)} ref={mergedRef}>
204
+ {children}
205
+ </ScrollArea.Viewport>
206
+ <PinEffect scrollerRef={scrollerRef} />
207
+ </>
183
208
  );
184
209
  });
185
210
 
186
211
  Viewport.displayName = VIEWPORT_NAME;
187
212
 
213
+ /**
214
+ * Isolated component that consumes pinned/controller from context.
215
+ * Kept separate so that Viewport does not re-render when pinned changes.
216
+ */
217
+ const PIN_EFFECT_NAME = 'ScrollContainer.PinEffect';
218
+
219
+ const PinEffect = ({ scrollerRef }: { scrollerRef: RefObject<HTMLDivElement | null> }) => {
220
+ const { pinned, controller } = useScrollContainerContext(PIN_EFFECT_NAME);
221
+
222
+ // Pin scroll to bottom when content changes.
223
+ useEffect(() => {
224
+ const viewport = scrollerRef.current;
225
+ if (!pinned || !viewport) {
226
+ return;
227
+ }
228
+
229
+ // Scroll instantly so we don't visually jump while content is being added.
230
+ controller?.scrollToBottom('instant');
231
+
232
+ // Setup resize observer on content children to detect size changes (e.g. streaming).
233
+ // We observe children rather than the viewport itself, because the viewport's size
234
+ // stays fixed — only its content grows.
235
+ // Use instant scroll in the callback — smooth scrolling adds/removes the
236
+ // scrollbar-none class, which changes the element size and re-fires the
237
+ // observer, creating an infinite loop.
238
+ const resizeObserver = new ResizeObserver(() => controller?.scrollToBottom('smooth'));
239
+ Array.from(viewport.children).forEach((child) => {
240
+ resizeObserver.observe(child);
241
+ });
242
+
243
+ // Watch for added/removed children.
244
+ const mutationObserver = new MutationObserver((mutations) => {
245
+ mutations.forEach((mutation) => {
246
+ mutation.addedNodes.forEach((node) => {
247
+ if (node instanceof Element) {
248
+ resizeObserver.observe(node);
249
+ }
250
+ });
251
+ });
252
+
253
+ controller?.scrollToBottom('smooth');
254
+ });
255
+ mutationObserver.observe(viewport, { childList: true });
256
+
257
+ return () => {
258
+ resizeObserver.disconnect();
259
+ mutationObserver.disconnect();
260
+ };
261
+ }, [pinned, controller, scrollerRef]);
262
+
263
+ return null;
264
+ };
265
+
266
+ //
267
+ // Fade
268
+ //
269
+
270
+ const FADE_NAME = 'ScrollContainer.Fade';
271
+
272
+ type FadeProps = {};
273
+
274
+ const Fade = () => {
275
+ const { overflow } = useScrollContainerContext(FADE_NAME);
276
+
277
+ return (
278
+ <div
279
+ role='none'
280
+ data-visible={overflow}
281
+ className={mx(
282
+ // NOTE: Gradients may not be visible with dark reader extensions.
283
+ 'z-10 absolute top-0 inset-x-0 h-24 w-full',
284
+ 'opacity-0 duration-200 transition-opacity data-[visible="true"]:opacity-100',
285
+ 'bg-gradient-to-b from-(--surface-bg) to-transparent pointer-events-none',
286
+ )}
287
+ />
288
+ );
289
+ };
290
+
291
+ Fade.displayName = FADE_NAME;
292
+
188
293
  //
189
294
  // ScrollDownButton
190
295
  //
@@ -194,7 +299,7 @@ const SCROLL_DOWN_BUTTON_NAME = 'ScrollContainer.ScrollDownButton';
194
299
  type ScrollDownButtonProps = ThemedClassName;
195
300
 
196
301
  const ScrollDownButton = ({ classNames }: ScrollDownButtonProps) => {
197
- const { pinned, scrollToBottom } = useScrollContainerContext(SCROLL_DOWN_BUTTON_NAME);
302
+ const { pinned, controller } = useScrollContainerContext(SCROLL_DOWN_BUTTON_NAME);
198
303
 
199
304
  return (
200
305
  <div
@@ -211,7 +316,7 @@ const ScrollDownButton = ({ classNames }: ScrollDownButtonProps) => {
211
316
  iconOnly
212
317
  size={4}
213
318
  label='Scroll down'
214
- onClick={() => scrollToBottom()}
319
+ onClick={() => controller?.scrollToBottom()}
215
320
  />
216
321
  </div>
217
322
  );
@@ -227,12 +332,16 @@ export { useScrollContainerContext };
227
332
 
228
333
  export const ScrollContainer = {
229
334
  Root,
335
+ Content,
230
336
  Viewport,
337
+ Fade,
231
338
  ScrollDownButton,
232
339
  };
233
340
 
234
341
  export type {
235
342
  RootProps as ScrollContainerRootProps,
343
+ ContentProps as ScrollContainerContentProps,
236
344
  ViewportProps as ScrollContainerViewportProps,
345
+ FadeProps as ScrollContainerFadeProps,
237
346
  ScrollDownButtonProps as ScrollContainerScrollDownButtonProps,
238
347
  };
@@ -5,20 +5,19 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useState } from 'react';
7
7
 
8
- import { faker } from '@dxos/random';
8
+ import { random } from '@dxos/random';
9
9
 
10
10
  import { withTheme } from '../../testing';
11
11
  import { withLayoutVariants } from '../../testing';
12
-
13
12
  import { Select } from './Select';
14
13
 
15
- faker.seed(1234);
14
+ random.seed(1234);
16
15
 
17
16
  type ItemProps = { id: string; text: string };
18
17
 
19
- type StoryProps = { items: ItemProps[] };
18
+ type DefaultStoryProps = { items: ItemProps[] };
20
19
 
21
- const DefaultStory = ({ items = [] }: StoryProps) => {
20
+ const DefaultStory = ({ items = [] }: DefaultStoryProps) => {
22
21
  const [value, setValue] = useState<string>();
23
22
  return (
24
23
  <Select.Root value={value} onValueChange={setValue}>
@@ -53,6 +52,6 @@ type Story = StoryObj<typeof meta>;
53
52
 
54
53
  export const Default: Story = {
55
54
  args: {
56
- items: Array.from({ length: 16 }).map((_, i) => ({ id: `item-${i}`, text: faker.lorem.word() })),
55
+ items: Array.from({ length: 16 }).map((_, i) => ({ id: `item-${i}`, text: random.lorem.word() })),
57
56
  },
58
57
  };
@@ -1,24 +1,21 @@
1
1
  //
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
- import {
5
- Separator as SeparatorPrimitive,
6
- type SeparatorProps as SeparatorPrimitiveProps,
7
- } from '@radix-ui/react-separator';
4
+ import * as SeparatorPrimitive from '@radix-ui/react-separator';
8
5
  import React, { forwardRef } from 'react';
9
6
 
10
7
  import { useThemeContext } from '../../hooks';
11
8
  import { type ThemedClassName } from '../../util';
12
9
 
13
- type SeparatorProps = ThemedClassName<SeparatorPrimitiveProps> & { subdued?: boolean };
10
+ type SeparatorProps = ThemedClassName<SeparatorPrimitive.SeparatorProps> & { subdued?: boolean };
14
11
 
15
12
  const Separator = forwardRef<HTMLDivElement, SeparatorProps>(
16
13
  ({ classNames, orientation = 'horizontal', subdued, ...props }, forwardedRef) => {
17
14
  const { tx } = useThemeContext();
18
15
  return (
19
- <SeparatorPrimitive
20
- orientation={orientation}
16
+ <SeparatorPrimitive.Root
21
17
  {...props}
18
+ orientation={orientation}
22
19
  className={tx('separator.root', { orientation, subdued }, classNames)}
23
20
  ref={forwardedRef}
24
21
  />
@@ -5,7 +5,6 @@
5
5
  import React from 'react';
6
6
 
7
7
  import { withTheme } from '../../testing';
8
-
9
8
  import { Skeleton } from './Skeleton';
10
9
 
11
10
  export default {
@@ -9,50 +9,50 @@ import { Panel } from '../../primitives';
9
9
  import { withLayout, withTheme } from '../../testing';
10
10
  import { ScrollArea } from '../ScrollArea';
11
11
  import { Toolbar } from '../Toolbar';
12
-
13
12
  import { Splitter, type SplitterRootProps } from './Splitter';
14
13
 
15
14
  const PanelContent = forwardRef<HTMLDivElement, ComponentPropsWithoutRef<'div'> & { label: string }>(
16
- ({ label, ...props }, ref) => (
17
- <div ref={ref} {...props}>
18
- <Panel.Root>
19
- <Panel.Toolbar asChild>
20
- <Toolbar.Root>{label}</Toolbar.Root>
21
- </Panel.Toolbar>
22
- <Panel.Content asChild>
23
- <ScrollArea.Root orientation='vertical'>
24
- <ScrollArea.Viewport>
25
- {Array.from({ length: 100 }).map((_, i) => (
26
- <div key={i} className='p-1'>
27
- {label}-{i}
28
- </div>
29
- ))}
30
- </ScrollArea.Viewport>
31
- </ScrollArea.Root>
32
- </Panel.Content>
33
- </Panel.Root>
34
- </div>
15
+ ({ label, ...props }, forwardedRef) => (
16
+ <Panel.Root {...props} ref={forwardedRef}>
17
+ <Panel.Toolbar asChild>
18
+ <Toolbar.Root>{label}</Toolbar.Root>
19
+ </Panel.Toolbar>
20
+ <Panel.Content asChild>
21
+ <ScrollArea.Root orientation='vertical'>
22
+ <ScrollArea.Viewport>
23
+ {Array.from({ length: 100 }).map((_, i) => (
24
+ <div key={i} className='p-1'>
25
+ {label}-{i}
26
+ </div>
27
+ ))}
28
+ </ScrollArea.Viewport>
29
+ </ScrollArea.Root>
30
+ </Panel.Content>
31
+ </Panel.Root>
35
32
  ),
36
33
  );
37
34
 
38
35
  const DefaultStory = (props: SplitterRootProps) => {
39
- const [mode, setMode] = useState(props.mode ?? 'both');
36
+ const [mode, setMode] = useState(props.mode ?? 'split');
37
+ const [ratio, setRatio] = useState(props.ratio ?? 0.5);
40
38
 
41
39
  return (
42
40
  <Panel.Root>
43
41
  <Panel.Toolbar asChild>
44
42
  <Toolbar.Root>
45
- <Toolbar.Button onClick={() => setMode('upper')}>A</Toolbar.Button>
46
- <Toolbar.Button onClick={() => setMode('both')}>A + B</Toolbar.Button>
47
- <Toolbar.Button onClick={() => setMode('lower')}>B</Toolbar.Button>
43
+ <Toolbar.Button onClick={() => setMode('top')}>A</Toolbar.Button>
44
+ <Toolbar.Button onClick={() => setMode('split')}>A+B</Toolbar.Button>
45
+ <Toolbar.Button onClick={() => setMode('bottom')}>B</Toolbar.Button>
46
+ <Toolbar.Separator />
47
+ <Toolbar.Button onClick={() => setRatio((r) => 1 - r)}>Toggle</Toolbar.Button>
48
48
  </Toolbar.Root>
49
49
  </Panel.Toolbar>
50
50
  <Panel.Content asChild>
51
- <Splitter.Root mode={mode} ratio={props.ratio}>
52
- <Splitter.Panel asChild position='upper'>
51
+ <Splitter.Root mode={mode} ratio={ratio}>
52
+ <Splitter.Panel asChild position='top'>
53
53
  <PanelContent label='A' />
54
54
  </Splitter.Panel>
55
- <Splitter.Panel asChild position='lower'>
55
+ <Splitter.Panel asChild position='bottom'>
56
56
  <PanelContent label='B' />
57
57
  </Splitter.Panel>
58
58
  </Splitter.Root>
@@ -77,7 +77,7 @@ type Story = StoryObj<SplitterRootProps>;
77
77
 
78
78
  export const Default: Story = {
79
79
  args: {
80
- mode: 'both',
81
- ratio: 0.5,
80
+ mode: 'split',
81
+ ratio: 0.4,
82
82
  },
83
83
  };