@dxos/react-ui 0.8.4-main.548089c → 0.8.4-main.59c2e9b

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 (245) hide show
  1. package/dist/lib/browser/chunk-CEKVHJ27.mjs +774 -0
  2. package/dist/lib/browser/chunk-CEKVHJ27.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +3067 -64
  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 +34 -46
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/chunk-2NHEX4AD.mjs +776 -0
  9. package/dist/lib/node-esm/chunk-2NHEX4AD.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +3067 -64
  11. package/dist/lib/node-esm/index.mjs.map +4 -4
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs +34 -46
  14. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  15. package/dist/types/src/components/{Buttons → Button}/Button.d.ts +1 -1
  16. package/dist/types/src/components/Button/Button.d.ts.map +1 -0
  17. package/dist/types/src/components/Button/Button.stories.d.ts.map +1 -0
  18. package/dist/types/src/components/{Buttons → Button}/IconButton.d.ts +2 -1
  19. package/dist/types/src/components/Button/IconButton.d.ts.map +1 -0
  20. package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -0
  21. package/dist/types/src/components/Button/Toggle.d.ts.map +1 -0
  22. package/dist/types/src/components/Button/Toggle.stories.d.ts +16 -0
  23. package/dist/types/src/components/Button/Toggle.stories.d.ts.map +1 -0
  24. package/dist/types/src/components/{Buttons → Button}/ToggleGroup.d.ts +4 -4
  25. package/dist/types/src/components/Button/ToggleGroup.d.ts.map +1 -0
  26. package/dist/types/src/components/{Buttons → Button}/ToggleGroup.stories.d.ts +4 -4
  27. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts.map +1 -0
  28. package/dist/types/src/components/Button/index.d.ts.map +1 -0
  29. package/dist/types/src/components/Clipboard/CopyButton.d.ts +1 -1
  30. package/dist/types/src/components/Clipboard/CopyButton.d.ts.map +1 -1
  31. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts +1 -1
  32. package/dist/types/src/components/DensityProvider/DensityProvider.d.ts.map +1 -1
  33. package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -0
  34. package/dist/types/src/components/Dialog/AlertDialog.stories.d.ts.map +1 -0
  35. package/dist/types/src/components/Dialog/Dialog.d.ts +40 -0
  36. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -0
  37. package/dist/types/src/components/{Dialogs → Dialog}/Dialog.stories.d.ts +7 -5
  38. package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -0
  39. package/dist/types/src/components/Dialog/index.d.ts.map +1 -0
  40. package/dist/types/src/components/ElevationProvider/ElevationProvider.d.ts +1 -1
  41. package/dist/types/src/components/ElevationProvider/ElevationProvider.d.ts.map +1 -1
  42. package/dist/types/src/components/Icon/Icon.d.ts +1 -1
  43. package/dist/types/src/components/Icon/Icon.d.ts.map +1 -1
  44. package/dist/types/src/components/Input/Input.d.ts +5 -2
  45. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  46. package/dist/types/src/components/Input/Input.stories.d.ts +1 -1
  47. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  48. package/dist/types/src/components/{Lists → List}/List.d.ts +1 -1
  49. package/dist/types/src/components/List/List.d.ts.map +1 -0
  50. package/dist/types/src/components/List/List.stories.d.ts.map +1 -0
  51. package/dist/types/src/components/List/ListDropIndicator.d.ts.map +1 -0
  52. package/dist/types/src/components/List/Tree.d.ts.map +1 -0
  53. package/dist/types/src/components/List/Tree.stories.d.ts.map +1 -0
  54. package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +1 -0
  55. package/dist/types/src/components/List/Treegrid.d.ts.map +1 -0
  56. package/dist/types/src/components/List/Treegrid.stories.d.ts.map +1 -0
  57. package/dist/types/src/components/List/index.d.ts.map +1 -0
  58. package/dist/types/src/components/{Menus → Menu}/ContextMenu.d.ts +6 -6
  59. package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -0
  60. package/dist/types/src/components/Menu/ContextMenu.stories.d.ts.map +1 -0
  61. package/dist/types/src/components/{Menus → Menu}/DropdownMenu.d.ts +3 -4
  62. package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -0
  63. package/dist/types/src/components/Menu/DropdownMenu.stories.d.ts.map +1 -0
  64. package/dist/types/src/components/Menu/index.d.ts.map +1 -0
  65. package/dist/types/src/components/Message/Message.d.ts +1 -1
  66. package/dist/types/src/components/Message/Message.d.ts.map +1 -1
  67. package/dist/types/src/components/Message/Message.stories.d.ts +1 -1
  68. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  69. package/dist/types/src/components/Popover/Popover.d.ts +1 -1
  70. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  71. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +9 -7
  72. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  73. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  74. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  75. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +39 -0
  76. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -0
  77. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +19 -0
  78. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -0
  79. package/dist/types/src/components/ScrollContainer/index.d.ts +2 -0
  80. package/dist/types/src/components/ScrollContainer/index.d.ts.map +1 -0
  81. package/dist/types/src/components/Select/Select.d.ts +10 -10
  82. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  83. package/dist/types/src/components/Separator/Separator.d.ts +1 -1
  84. package/dist/types/src/components/Tag/Tag.d.ts +1 -1
  85. package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
  86. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +1 -2
  87. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  88. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +1 -8
  89. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  90. package/dist/types/src/components/ThemeProvider/index.d.ts +2 -1
  91. package/dist/types/src/components/ThemeProvider/index.d.ts.map +1 -1
  92. package/dist/types/src/components/Toast/Toast.d.ts +4 -4
  93. package/dist/types/src/components/Toolbar/Toolbar.d.ts +7 -7
  94. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  95. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  96. package/dist/types/src/components/index.d.ts +5 -4
  97. package/dist/types/src/components/index.d.ts.map +1 -1
  98. package/dist/types/src/hooks/useDensityContext.d.ts +1 -1
  99. package/dist/types/src/hooks/useDensityContext.d.ts.map +1 -1
  100. package/dist/types/src/hooks/useElevationContext.d.ts +1 -1
  101. package/dist/types/src/hooks/useElevationContext.d.ts.map +1 -1
  102. package/dist/types/src/index.d.ts +1 -1
  103. package/dist/types/src/index.d.ts.map +1 -1
  104. package/dist/types/src/testing/decorators/index.d.ts +1 -1
  105. package/dist/types/src/testing/decorators/index.d.ts.map +1 -1
  106. package/dist/types/src/testing/decorators/withLayout.d.ts +3 -3
  107. package/dist/types/src/testing/decorators/withLayout.d.ts.map +1 -1
  108. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts +12 -0
  109. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts.map +1 -0
  110. package/dist/types/src/testing/decorators/withTheme.d.ts.map +1 -1
  111. package/dist/types/src/util/index.d.ts +1 -2
  112. package/dist/types/src/util/index.d.ts.map +1 -1
  113. package/dist/types/tsconfig.tsbuildinfo +1 -1
  114. package/package.json +32 -25
  115. package/src/components/Avatars/Avatar.stories.tsx +2 -2
  116. package/src/components/Avatars/Avatar.tsx +1 -1
  117. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  118. package/src/components/{Buttons → Button}/Button.stories.tsx +2 -2
  119. package/src/components/{Buttons → Button}/Button.tsx +1 -1
  120. package/src/components/{Buttons → Button}/IconButton.tsx +9 -3
  121. package/src/components/{Buttons → Button}/Toggle.stories.tsx +5 -4
  122. package/src/components/Clipboard/CopyButton.tsx +4 -4
  123. package/src/components/DensityProvider/DensityProvider.tsx +1 -1
  124. package/src/components/{Dialogs → Dialog}/AlertDialog.stories.tsx +1 -1
  125. package/src/components/Dialog/Dialog.stories.tsx +97 -0
  126. package/src/components/{Dialogs → Dialog}/Dialog.tsx +140 -40
  127. package/src/components/ElevationProvider/ElevationProvider.tsx +1 -1
  128. package/src/components/Icon/Icon.stories.tsx +1 -1
  129. package/src/components/Icon/Icon.tsx +1 -1
  130. package/src/components/Input/Input.stories.tsx +2 -2
  131. package/src/components/Input/Input.tsx +13 -4
  132. package/src/components/{Lists → List}/List.stories.tsx +16 -12
  133. package/src/components/{Lists → List}/List.tsx +1 -1
  134. package/src/components/{Lists → List}/ListDropIndicator.tsx +1 -1
  135. package/src/components/Main/Main.stories.tsx +1 -1
  136. package/src/components/{Menus → Menu}/DropdownMenu.stories.tsx +1 -1
  137. package/src/components/{Menus → Menu}/DropdownMenu.tsx +61 -57
  138. package/src/components/Message/Message.stories.tsx +1 -1
  139. package/src/components/Message/Message.tsx +30 -5
  140. package/src/components/Popover/Popover.stories.tsx +1 -1
  141. package/src/components/Popover/Popover.tsx +35 -33
  142. package/src/components/ScrollArea/ScrollArea.stories.tsx +54 -4
  143. package/src/components/ScrollArea/ScrollArea.tsx +50 -4
  144. package/src/components/ScrollContainer/ScrollContainer.stories.tsx +70 -0
  145. package/src/components/ScrollContainer/ScrollContainer.tsx +233 -0
  146. package/src/components/ScrollContainer/index.ts +5 -0
  147. package/src/components/Select/Select.stories.tsx +2 -2
  148. package/src/components/Select/Select.tsx +3 -3
  149. package/src/components/Tag/Tag.stories.tsx +2 -2
  150. package/src/components/Tag/Tag.tsx +1 -1
  151. package/src/components/ThemeProvider/ThemeProvider.tsx +1 -3
  152. package/src/components/ThemeProvider/TranslationsProvider.tsx +1 -16
  153. package/src/components/ThemeProvider/index.ts +3 -3
  154. package/src/components/Toast/Toast.stories.tsx +1 -1
  155. package/src/components/Toolbar/Toolbar.stories.tsx +1 -1
  156. package/src/components/Toolbar/Toolbar.tsx +21 -7
  157. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  158. package/src/components/Tooltip/Tooltip.tsx +22 -20
  159. package/src/components/index.ts +5 -4
  160. package/src/hooks/useDensityContext.ts +1 -1
  161. package/src/hooks/useElevationContext.ts +1 -1
  162. package/src/index.ts +1 -1
  163. package/src/playground/Controls.stories.tsx +2 -2
  164. package/src/testing/decorators/index.ts +1 -1
  165. package/src/testing/decorators/withLayout.tsx +22 -15
  166. package/src/testing/decorators/{withSurfaceVariantsLayout.tsx → withLayoutVariants.tsx} +4 -4
  167. package/src/testing/decorators/withTheme.tsx +3 -2
  168. package/src/util/index.ts +2 -2
  169. package/dist/lib/browser/chunk-YG7RAH7A.mjs +0 -4543
  170. package/dist/lib/browser/chunk-YG7RAH7A.mjs.map +0 -7
  171. package/dist/lib/node-esm/chunk-FL2ZT5KB.mjs +0 -4545
  172. package/dist/lib/node-esm/chunk-FL2ZT5KB.mjs.map +0 -7
  173. package/dist/types/src/components/Buttons/Button.d.ts.map +0 -1
  174. package/dist/types/src/components/Buttons/Button.stories.d.ts.map +0 -1
  175. package/dist/types/src/components/Buttons/IconButton.d.ts.map +0 -1
  176. package/dist/types/src/components/Buttons/IconButton.stories.d.ts.map +0 -1
  177. package/dist/types/src/components/Buttons/Toggle.d.ts.map +0 -1
  178. package/dist/types/src/components/Buttons/Toggle.stories.d.ts +0 -13
  179. package/dist/types/src/components/Buttons/Toggle.stories.d.ts.map +0 -1
  180. package/dist/types/src/components/Buttons/ToggleGroup.d.ts.map +0 -1
  181. package/dist/types/src/components/Buttons/ToggleGroup.stories.d.ts.map +0 -1
  182. package/dist/types/src/components/Buttons/index.d.ts.map +0 -1
  183. package/dist/types/src/components/Dialogs/AlertDialog.d.ts.map +0 -1
  184. package/dist/types/src/components/Dialogs/AlertDialog.stories.d.ts.map +0 -1
  185. package/dist/types/src/components/Dialogs/Dialog.d.ts +0 -31
  186. package/dist/types/src/components/Dialogs/Dialog.d.ts.map +0 -1
  187. package/dist/types/src/components/Dialogs/Dialog.stories.d.ts.map +0 -1
  188. package/dist/types/src/components/Dialogs/index.d.ts.map +0 -1
  189. package/dist/types/src/components/Lists/List.d.ts.map +0 -1
  190. package/dist/types/src/components/Lists/List.stories.d.ts.map +0 -1
  191. package/dist/types/src/components/Lists/ListDropIndicator.d.ts.map +0 -1
  192. package/dist/types/src/components/Lists/Tree.d.ts.map +0 -1
  193. package/dist/types/src/components/Lists/Tree.stories.d.ts.map +0 -1
  194. package/dist/types/src/components/Lists/TreeDropIndicator.d.ts.map +0 -1
  195. package/dist/types/src/components/Lists/Treegrid.d.ts.map +0 -1
  196. package/dist/types/src/components/Lists/Treegrid.stories.d.ts.map +0 -1
  197. package/dist/types/src/components/Lists/index.d.ts.map +0 -1
  198. package/dist/types/src/components/Menus/ContextMenu.d.ts.map +0 -1
  199. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts.map +0 -1
  200. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +0 -1
  201. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts.map +0 -1
  202. package/dist/types/src/components/Menus/index.d.ts.map +0 -1
  203. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts +0 -12
  204. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts.map +0 -1
  205. package/dist/types/src/util/ThemedClassName.d.ts +0 -5
  206. package/dist/types/src/util/ThemedClassName.d.ts.map +0 -1
  207. package/dist/types/src/util/domino.d.ts +0 -18
  208. package/dist/types/src/util/domino.d.ts.map +0 -1
  209. package/src/components/Dialogs/Dialog.stories.tsx +0 -67
  210. package/src/util/ThemedClassName.ts +0 -7
  211. package/src/util/domino.ts +0 -53
  212. /package/dist/types/src/components/{Buttons → Button}/Button.stories.d.ts +0 -0
  213. /package/dist/types/src/components/{Buttons → Button}/IconButton.stories.d.ts +0 -0
  214. /package/dist/types/src/components/{Buttons → Button}/Toggle.d.ts +0 -0
  215. /package/dist/types/src/components/{Buttons → Button}/index.d.ts +0 -0
  216. /package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.d.ts +0 -0
  217. /package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.stories.d.ts +0 -0
  218. /package/dist/types/src/components/{Dialogs → Dialog}/index.d.ts +0 -0
  219. /package/dist/types/src/components/{Lists → List}/List.stories.d.ts +0 -0
  220. /package/dist/types/src/components/{Lists → List}/ListDropIndicator.d.ts +0 -0
  221. /package/dist/types/src/components/{Lists → List}/Tree.d.ts +0 -0
  222. /package/dist/types/src/components/{Lists → List}/Tree.stories.d.ts +0 -0
  223. /package/dist/types/src/components/{Lists → List}/TreeDropIndicator.d.ts +0 -0
  224. /package/dist/types/src/components/{Lists → List}/Treegrid.d.ts +0 -0
  225. /package/dist/types/src/components/{Lists → List}/Treegrid.stories.d.ts +0 -0
  226. /package/dist/types/src/components/{Lists → List}/index.d.ts +0 -0
  227. /package/dist/types/src/components/{Menus → Menu}/ContextMenu.stories.d.ts +0 -0
  228. /package/dist/types/src/components/{Menus → Menu}/DropdownMenu.stories.d.ts +0 -0
  229. /package/dist/types/src/components/{Menus → Menu}/index.d.ts +0 -0
  230. /package/src/components/{Buttons → Button}/IconButton.stories.tsx +0 -0
  231. /package/src/components/{Buttons → Button}/Toggle.tsx +0 -0
  232. /package/src/components/{Buttons → Button}/ToggleGroup.stories.tsx +0 -0
  233. /package/src/components/{Buttons → Button}/ToggleGroup.tsx +0 -0
  234. /package/src/components/{Buttons → Button}/index.ts +0 -0
  235. /package/src/components/{Dialogs → Dialog}/AlertDialog.tsx +0 -0
  236. /package/src/components/{Dialogs → Dialog}/index.ts +0 -0
  237. /package/src/components/{Lists → List}/Tree.stories.tsx +0 -0
  238. /package/src/components/{Lists → List}/Tree.tsx +0 -0
  239. /package/src/components/{Lists → List}/TreeDropIndicator.tsx +0 -0
  240. /package/src/components/{Lists → List}/Treegrid.stories.tsx +0 -0
  241. /package/src/components/{Lists → List}/Treegrid.tsx +0 -0
  242. /package/src/components/{Lists → List}/index.ts +0 -0
  243. /package/src/components/{Menus → Menu}/ContextMenu.stories.tsx +0 -0
  244. /package/src/components/{Menus → Menu}/ContextMenu.tsx +0 -0
  245. /package/src/components/{Menus → Menu}/index.ts +0 -0
@@ -6,9 +6,9 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { type PropsWithChildren } from 'react';
7
7
 
8
8
  import { faker } from '@dxos/random';
9
- import { activeSurface, surfaceShadow } from '@dxos/react-ui-theme';
9
+ import { activeSurface, surfaceShadow } from '@dxos/ui-theme';
10
10
 
11
- import { withTheme } from '../../testing';
11
+ import { withLayout, withTheme } from '../../testing';
12
12
 
13
13
  import { ScrollArea } from './ScrollArea';
14
14
 
@@ -34,10 +34,13 @@ const DefaultStory = ({ children }: PropsWithChildren<{}>) => {
34
34
  };
35
35
 
36
36
  const meta = {
37
- title: 'ui/react-ui-core/Scroll area',
37
+ title: 'ui/react-ui-core/ScrollArea',
38
38
  component: ScrollArea as any,
39
39
  render: DefaultStory,
40
- decorators: [withTheme],
40
+ decorators: [withTheme, withLayout({ layout: 'fullscreen' })],
41
+ parameters: {
42
+ layout: 'fullscreen',
43
+ },
41
44
  } satisfies Meta<typeof DefaultStory>;
42
45
 
43
46
  export default meta;
@@ -49,3 +52,50 @@ export const Default: Story = {
49
52
  children: faker.lorem.paragraphs(5),
50
53
  },
51
54
  };
55
+
56
+ export const NestedScrollAreas: Story = {
57
+ render: () => {
58
+ const columns = Array.from({ length: 3 }).map((_, index) => ({
59
+ id: String(index),
60
+ itemCount: 20,
61
+ }));
62
+
63
+ return (
64
+ <div className='p-4 bs-full is-full overflow-hidden'>
65
+ <div className='flex bs-full is-full overflow-hidden border border-sky-500'>
66
+ <ScrollArea.Root>
67
+ <ScrollArea.Viewport>
68
+ <div className='flex gap-4 p-3 bs-full overflow-hidden'>
69
+ {columns.map((column) => (
70
+ <div key={column.id} className='flex flex-col gap-1 bs-full overflow-hidden is-[300px]'>
71
+ <div className='flex shrink-0 p-2 border border-separator'>Column {column.id}</div>
72
+ <ScrollArea.Expander classNames='border border-rose-500'>
73
+ <ScrollArea.Root>
74
+ <ScrollArea.Viewport>
75
+ <div className='flex flex-col p-3 space-y-2'>
76
+ {Array.from({ length: column.itemCount }, (_, i) => (
77
+ <div key={i} className={`p-3 border border-separator rounded-sm`}>
78
+ Item {i + 1}
79
+ </div>
80
+ ))}
81
+ </div>
82
+ </ScrollArea.Viewport>
83
+ <ScrollArea.Scrollbar orientation='vertical'>
84
+ <ScrollArea.Thumb />
85
+ </ScrollArea.Scrollbar>
86
+ </ScrollArea.Root>
87
+ </ScrollArea.Expander>
88
+ <div className={`p-2 border border-separator`}>Footer</div>
89
+ </div>
90
+ ))}
91
+ </div>
92
+ </ScrollArea.Viewport>
93
+ <ScrollArea.Scrollbar orientation='horizontal'>
94
+ <ScrollArea.Thumb />
95
+ </ScrollArea.Scrollbar>
96
+ </ScrollArea.Root>
97
+ </div>
98
+ </div>
99
+ );
100
+ },
101
+ };
@@ -14,18 +14,21 @@ import {
14
14
  Viewport as ScrollAreaPrimitiveViewport,
15
15
  type ScrollAreaViewportProps as ScrollAreaPrimitiveViewportProps,
16
16
  } from '@radix-ui/react-scroll-area';
17
- import React, { forwardRef } from 'react';
17
+ import React, { type PropsWithChildren, forwardRef } from 'react';
18
+
19
+ import { mx } from '@dxos/ui-theme';
18
20
 
19
21
  import { useThemeContext } from '../../hooks';
20
22
  import { type ThemedClassName } from '../../util';
21
23
 
22
24
  type ScrollAreaVariant = 'coarse' | 'fine';
23
25
 
26
+ //
27
+ // Root
28
+ //
29
+
24
30
  type ScrollAreaRootProps = ThemedClassName<ScrollAreaPrimitiveRootProps>;
25
31
 
26
- /**
27
- * @deprecated
28
- */
29
32
  const ScrollAreaRoot = forwardRef<HTMLDivElement, ScrollAreaRootProps>(({ classNames, ...props }, forwardedRef) => {
30
33
  const { tx } = useThemeContext();
31
34
  return (
@@ -37,6 +40,10 @@ const ScrollAreaRoot = forwardRef<HTMLDivElement, ScrollAreaRootProps>(({ classN
37
40
  );
38
41
  });
39
42
 
43
+ //
44
+ // Viewport
45
+ //
46
+
40
47
  type ScrollAreaViewportProps = ThemedClassName<ScrollAreaPrimitiveViewportProps>;
41
48
 
42
49
  const ScrollAreaViewport = forwardRef<HTMLDivElement, ScrollAreaViewportProps>(
@@ -52,6 +59,10 @@ const ScrollAreaViewport = forwardRef<HTMLDivElement, ScrollAreaViewportProps>(
52
59
  },
53
60
  );
54
61
 
62
+ //
63
+ // Scrollbar
64
+ //
65
+
55
66
  type ScrollAreaScrollbarProps = ThemedClassName<ScrollAreaPrimitiveScrollbarProps> & { variant?: ScrollAreaVariant };
56
67
 
57
68
  const ScrollAreaScrollbar = forwardRef<HTMLDivElement, ScrollAreaScrollbarProps>(
@@ -68,6 +79,10 @@ const ScrollAreaScrollbar = forwardRef<HTMLDivElement, ScrollAreaScrollbarProps>
68
79
  },
69
80
  );
70
81
 
82
+ //
83
+ // Thumb
84
+ //
85
+
71
86
  type ScrollAreaThumbProps = ThemedClassName<ScrollAreaPrimitiveThumbProps>;
72
87
 
73
88
  const ScrollAreaThumb = forwardRef<HTMLDivElement, ScrollAreaThumbProps>(({ classNames, ...props }, forwardedRef) => {
@@ -81,6 +96,10 @@ const ScrollAreaThumb = forwardRef<HTMLDivElement, ScrollAreaThumbProps>(({ clas
81
96
  );
82
97
  });
83
98
 
99
+ //
100
+ // Corner
101
+ //
102
+
84
103
  type ScrollAreaCornerProps = ThemedClassName<ScrollAreaPrimitiveCornerProps>;
85
104
 
86
105
  const ScrollAreaCorner = forwardRef<HTMLDivElement, ScrollAreaCornerProps>(({ classNames, ...props }, forwardedRef) => {
@@ -94,12 +113,38 @@ const ScrollAreaCorner = forwardRef<HTMLDivElement, ScrollAreaCornerProps>(({ cl
94
113
  );
95
114
  });
96
115
 
116
+ //
117
+ // Expander
118
+ //
119
+
120
+ type ScrollAreaExpanderProps = ThemedClassName<PropsWithChildren>;
121
+
122
+ /**
123
+ * Size-locking wrapper required for inner ScrollArea to function correctly.
124
+ * NOTE: Radix ScrollArea.Viewport applies `display: table` to its immediate child,
125
+ * causing the content to participate in table layout and escape the intended fixed-size viewport.
126
+ */
127
+ const ScrollAreaExpander = ({ classNames, children }: ScrollAreaExpanderProps) => {
128
+ return (
129
+ <div role='none' className={mx('relative bs-full is-full overflow-hidden', classNames)}>
130
+ <div role='none' className='absolute inset-0 overflow-hidden'>
131
+ {children}
132
+ </div>
133
+ </div>
134
+ );
135
+ };
136
+
137
+ //
138
+ // ScrollArea
139
+ //
140
+
97
141
  export const ScrollArea = {
98
142
  Root: ScrollAreaRoot,
99
143
  Viewport: ScrollAreaViewport,
100
144
  Scrollbar: ScrollAreaScrollbar,
101
145
  Thumb: ScrollAreaThumb,
102
146
  Corner: ScrollAreaCorner,
147
+ Expander: ScrollAreaExpander,
103
148
  };
104
149
 
105
150
  export type {
@@ -108,4 +153,5 @@ export type {
108
153
  ScrollAreaScrollbarProps,
109
154
  ScrollAreaThumbProps,
110
155
  ScrollAreaCornerProps,
156
+ ScrollAreaExpanderProps,
111
157
  };
@@ -0,0 +1,70 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useEffect, useRef, useState } from 'react';
7
+
8
+ import { faker } from '@dxos/random';
9
+
10
+ import { withLayout, withTheme } from '../../testing';
11
+ import { Button } from '../Button';
12
+ import { Toolbar } from '../Toolbar';
13
+
14
+ import { ScrollContainer, type ScrollContainerRootProps, type ScrollController } from './ScrollContainer';
15
+
16
+ const DefaultStory = (props: ScrollContainerRootProps) => {
17
+ const [lines, setLines] = useState<string[]>([]);
18
+ const [running, setRunning] = useState(true);
19
+ const scroller = useRef<ScrollController>(null);
20
+ useEffect(() => {
21
+ if (!running) {
22
+ return;
23
+ }
24
+
25
+ const i = setInterval(() => {
26
+ setLines((lines) => [...lines, faker.lorem.paragraph()]);
27
+ }, 500);
28
+
29
+ return () => clearInterval(i);
30
+ }, [running]);
31
+
32
+ return (
33
+ <div className='flex flex-col bs-full overflow-hidden'>
34
+ <Toolbar.Root>
35
+ <Button onClick={() => setRunning((running) => !running)}>{running ? 'Stop' : 'Start'}</Button>
36
+ <Button onClick={() => scroller.current?.scrollToBottom()}>Scroll to bottom</Button>
37
+ <div className='flex-1' />
38
+ <div>{lines.length}</div>
39
+ </Toolbar.Root>
40
+ <ScrollContainer.Root {...props} ref={scroller}>
41
+ <ScrollContainer.Viewport>
42
+ {lines.map((line, index) => (
43
+ <div key={index} className='p-2'>
44
+ {line}
45
+ </div>
46
+ ))}
47
+ </ScrollContainer.Viewport>
48
+ <ScrollContainer.ScrollDownButton />
49
+ </ScrollContainer.Root>
50
+ </div>
51
+ );
52
+ };
53
+
54
+ const meta = {
55
+ title: 'ui/react-ui-core/ScrollContainer',
56
+ component: ScrollContainer.Root,
57
+ render: DefaultStory,
58
+ decorators: [withTheme, withLayout({ layout: 'column', classNames: 'is-[30rem]' })],
59
+ } satisfies Meta<typeof DefaultStory>;
60
+
61
+ export default meta;
62
+
63
+ type Story = StoryObj<typeof meta>;
64
+
65
+ export const Default: Story = {
66
+ args: {
67
+ pin: true,
68
+ fade: true,
69
+ },
70
+ };
@@ -0,0 +1,233 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { createContext } from '@radix-ui/react-context';
6
+ import React, {
7
+ type HTMLAttributes,
8
+ type PropsWithChildren,
9
+ forwardRef,
10
+ useCallback,
11
+ useEffect,
12
+ useImperativeHandle,
13
+ useMemo,
14
+ useRef,
15
+ useState,
16
+ } from 'react';
17
+
18
+ // TODO(burdon): Move these deps to @dxos/dom-util.
19
+ import { addEventListener, combine } from '@dxos/async';
20
+ import { invariant } from '@dxos/invariant';
21
+ import { useForwardedRef } from '@dxos/react-hooks';
22
+ import { mx } from '@dxos/ui-theme';
23
+
24
+ import { type ThemedClassName } from '../../util';
25
+ import { IconButton } from '../Button';
26
+
27
+ const isBottom = (el: HTMLElement | null) => {
28
+ return !!(el && el.scrollHeight - el.scrollTop === el.clientHeight);
29
+ };
30
+
31
+ export interface ScrollController {
32
+ viewport: HTMLDivElement | null;
33
+ scrollToTop: (behavior?: ScrollBehavior) => void;
34
+ scrollToBottom: (behavior?: ScrollBehavior) => void;
35
+ }
36
+
37
+ type ScrollContainerContextValue = {
38
+ scrollToBottom: (behavior?: ScrollBehavior) => void;
39
+ controller?: ScrollController;
40
+ pinned?: boolean;
41
+ };
42
+
43
+ const [ScrollContainerProvider, useScrollContainerContext] =
44
+ createContext<ScrollContainerContextValue>('ScrollContainer');
45
+
46
+ //
47
+ // Root
48
+ //
49
+
50
+ type RootProps = ThemedClassName<
51
+ PropsWithChildren<{
52
+ pin?: boolean;
53
+ fade?: boolean;
54
+ behavior?: ScrollBehavior;
55
+ }>
56
+ >;
57
+
58
+ /**
59
+ * Scroll container that automatically scrolls to the bottom when new content is added.
60
+ */
61
+ const Root = forwardRef<ScrollController, RootProps>(
62
+ ({ children, classNames, pin, fade, behavior: behaviorProp = 'smooth' }, forwardedRef) => {
63
+ const scrollerRef = useRef<HTMLDivElement>(null);
64
+ const autoScrollRef = useRef(false);
65
+ const [overflow, setOverflow] = useState(false);
66
+ const [pinned, setPinned] = useState(pin);
67
+
68
+ const timeoutRef = useRef<NodeJS.Timeout>(undefined);
69
+ const scrollToBottom = useCallback((behavior: ScrollBehavior = behaviorProp) => {
70
+ if (scrollerRef.current) {
71
+ // Temporarily hide scrollbar to prevent flicker.
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);
85
+ }
86
+ setPinned(true);
87
+ }
88
+ }, []);
89
+
90
+ const controller = useMemo(
91
+ () => ({
92
+ viewport: scrollerRef.current,
93
+ scrollToTop: () => {
94
+ invariant(scrollerRef.current);
95
+ scrollerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
96
+ setPinned(false);
97
+ },
98
+ scrollToBottom: () => {
99
+ scrollToBottom('smooth');
100
+ },
101
+ }),
102
+ [scrollToBottom, scrollerRef.current],
103
+ );
104
+
105
+ // Scroll controller imperative ref.
106
+ useImperativeHandle(forwardedRef, () => controller, [controller]);
107
+
108
+ // Listen for scroll events.
109
+ useEffect(() => {
110
+ if (!scrollerRef.current) {
111
+ return;
112
+ }
113
+
114
+ return combine(
115
+ // Check if user scrolls.
116
+ addEventListener(scrollerRef.current, 'wheel', () => {
117
+ setPinned(isBottom(scrollerRef.current));
118
+ }),
119
+ // Check if scrolls.
120
+ addEventListener(scrollerRef.current, 'scroll', () => {
121
+ setOverflow((scrollerRef.current?.scrollTop ?? 0) > 0);
122
+ }),
123
+ );
124
+ }, []);
125
+
126
+ return (
127
+ <ScrollContainerProvider pinned={pinned} controller={controller} scrollToBottom={scrollToBottom}>
128
+ <div className='relative grid flex-1 min-bs-0 overflow-hidden'>
129
+ {fade && (
130
+ <div
131
+ role='none'
132
+ data-visible={overflow}
133
+ className={mx(
134
+ // NOTE: Gradients may not be visible with dark reader extensions.
135
+ 'z-10 absolute block-start-0 inset-inline-0 bs-24 is-full',
136
+ 'opacity-0 duration-200 transition-opacity data-[visible="true"]:opacity-100',
137
+ 'bg-gradient-to-b from-[--surface-bg] to-transparent pointer-events-none',
138
+ )}
139
+ />
140
+ )}
141
+ <div className={mx('flex flex-col min-bs-0 overflow-y-auto scrollbar-thin', classNames)} ref={scrollerRef}>
142
+ {children}
143
+ </div>
144
+ </div>
145
+ </ScrollContainerProvider>
146
+ );
147
+ },
148
+ );
149
+
150
+ Root.displayName = 'ScrollContainer.Root';
151
+
152
+ //
153
+ // Viewport
154
+ //
155
+
156
+ type ViewportProps = ThemedClassName<PropsWithChildren<Omit<HTMLAttributes<HTMLDivElement>, 'className'>>>;
157
+
158
+ const Viewport = forwardRef<HTMLDivElement, ViewportProps>(({ classNames, children, ...props }, forwardedRef) => {
159
+ const contentRef = useForwardedRef(forwardedRef);
160
+ const { pinned, scrollToBottom } = useScrollContainerContext(Viewport.displayName!);
161
+
162
+ useEffect(() => {
163
+ if (!pinned || !contentRef.current) {
164
+ return;
165
+ }
166
+
167
+ // Scroll instantly otherwise it might move while we're scrolling.
168
+ scrollToBottom();
169
+
170
+ // Setup resize observer to detect content changes.
171
+ const resizeObserver = new ResizeObserver(() => scrollToBottom());
172
+ resizeObserver.observe(contentRef.current);
173
+ return () => resizeObserver.disconnect();
174
+ }, [pinned, scrollToBottom]);
175
+
176
+ return (
177
+ <div className={mx('is-full', classNames)} {...props} ref={contentRef}>
178
+ {children}
179
+ </div>
180
+ );
181
+ });
182
+
183
+ Viewport.displayName = 'ScrollContainer.Viewport';
184
+
185
+ //
186
+ // ScrollDownButton
187
+ //
188
+
189
+ type ScrollDownButtonProps = ThemedClassName;
190
+
191
+ const ScrollDownButton = ({ classNames }: ScrollDownButtonProps) => {
192
+ const { pinned, scrollToBottom } = useScrollContainerContext(ScrollDownButton.displayName!);
193
+
194
+ return (
195
+ <div
196
+ role='none'
197
+ className={mx(
198
+ 'absolute bottom-2 right-4 opacity-100 transition-opacity duration-300',
199
+ pinned && 'opacity-0',
200
+ classNames,
201
+ )}
202
+ >
203
+ <IconButton
204
+ variant='primary'
205
+ icon='ph--arrow-down--regular'
206
+ iconOnly
207
+ size={4}
208
+ label='Scroll down'
209
+ onClick={() => scrollToBottom()}
210
+ />
211
+ </div>
212
+ );
213
+ };
214
+
215
+ ScrollDownButton.displayName = 'ScrollContainer.ScrollDownButton';
216
+
217
+ //
218
+ // ScrollContainer
219
+ //
220
+
221
+ export { useScrollContainerContext };
222
+
223
+ export const ScrollContainer = {
224
+ Root,
225
+ Viewport,
226
+ ScrollDownButton,
227
+ };
228
+
229
+ export type {
230
+ RootProps as ScrollContainerRootProps,
231
+ ViewportProps as ScrollContainerViewportProps,
232
+ ScrollDownButtonProps as ScrollContainerScrollDownButtonProps,
233
+ };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './ScrollContainer';
@@ -8,7 +8,7 @@ import React, { useState } from 'react';
8
8
  import { faker } from '@dxos/random';
9
9
 
10
10
  import { withTheme } from '../../testing';
11
- import { withSurfaceVariantsLayout } from '../../testing';
11
+ import { withLayoutVariants } from '../../testing';
12
12
 
13
13
  import { Select } from './Select';
14
14
 
@@ -44,7 +44,7 @@ const DefaultStory = ({ items = [] }: StoryProps) => {
44
44
  const meta = {
45
45
  title: 'ui/react-ui-core/Select',
46
46
  render: DefaultStory,
47
- decorators: [withTheme, withSurfaceVariantsLayout()],
47
+ decorators: [withTheme, withLayoutVariants()],
48
48
  } satisfies Meta<typeof DefaultStory>;
49
49
 
50
50
  export default meta;
@@ -8,7 +8,7 @@ import React, { forwardRef } from 'react';
8
8
  import { useElevationContext, useThemeContext } from '../../hooks';
9
9
  import { useSafeCollisionPadding } from '../../hooks/useSafeCollisionPadding';
10
10
  import { type ThemedClassName } from '../../util';
11
- import { Button, type ButtonProps } from '../Buttons';
11
+ import { Button, type ButtonProps } from '../Button';
12
12
  import { Icon } from '../Icon';
13
13
 
14
14
  type SelectRootProps = SelectPrimitive.SelectProps;
@@ -39,7 +39,7 @@ const SelectTriggerButton = forwardRef<HTMLButtonElement, SelectTriggerButtonPro
39
39
  <SelectPrimitive.Trigger asChild ref={forwardedRef}>
40
40
  <Button {...props}>
41
41
  <SelectPrimitive.Value placeholder={placeholder}>{children}</SelectPrimitive.Value>
42
- <span className='w-1 flex-1' />
42
+ <span className='is-1 flex-1' />
43
43
  <SelectPrimitive.Icon asChild>
44
44
  <Icon size={3} icon='ph--caret-down--bold' />
45
45
  </SelectPrimitive.Icon>
@@ -158,7 +158,7 @@ const SelectOption = forwardRef<HTMLDivElement, SelectItemProps>(({ children, cl
158
158
  return (
159
159
  <SelectPrimitive.Item {...props} className={tx('select.item', 'option', {}, classNames)} ref={forwardedRef}>
160
160
  <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
161
- <span className='grow w-1' />
161
+ <span className='grow is-1' />
162
162
  {/* <SelectPrimitive.ItemIndicator className={tx('select.itemIndicator', 'option__indicator', {})}> */}
163
163
  <Icon icon='ph--check--regular' />
164
164
  {/* </SelectPrimitive.ItemIndicator> */}
@@ -5,8 +5,8 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React from 'react';
7
7
 
8
- import { hues } from '@dxos/react-ui-theme';
9
- import { type ChromaticPalette, type MessageValence } from '@dxos/react-ui-types';
8
+ import { hues } from '@dxos/ui-theme';
9
+ import { type ChromaticPalette, type MessageValence } from '@dxos/ui-types';
10
10
 
11
11
  import { withTheme } from '../../testing';
12
12
 
@@ -6,7 +6,7 @@ import { Primitive } from '@radix-ui/react-primitive';
6
6
  import { Slot } from '@radix-ui/react-slot';
7
7
  import React, { type ComponentPropsWithRef, forwardRef } from 'react';
8
8
 
9
- import { type ChromaticPalette, type MessageValence, type NeutralPalette } from '@dxos/react-ui-types';
9
+ import { type ChromaticPalette, type MessageValence, type NeutralPalette } from '@dxos/ui-types';
10
10
 
11
11
  import { useThemeContext } from '../../hooks';
12
12
  import { type ThemedClassName } from '../../util';
@@ -5,7 +5,7 @@
5
5
  import { createKeyborg } from 'keyborg';
6
6
  import React, { type PropsWithChildren, createContext, useEffect, useMemo } from 'react';
7
7
 
8
- import { type Density, type Elevation, type ThemeFunction } from '@dxos/react-ui-types';
8
+ import { type Density, type Elevation, type ThemeFunction, type ThemeMode } from '@dxos/ui-types';
9
9
 
10
10
  import { type SafeAreaPadding, useSafeArea } from '../../hooks';
11
11
  import { hasIosKeyboard } from '../../util';
@@ -14,8 +14,6 @@ import { ElevationProvider } from '../ElevationProvider';
14
14
 
15
15
  import { TranslationsProvider, type TranslationsProviderProps } from './TranslationsProvider';
16
16
 
17
- export type ThemeMode = 'dark' | 'light';
18
-
19
17
  export type ThemeContextValue = {
20
18
  tx: ThemeFunction<any>;
21
19
  themeMode: ThemeMode;
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { type Locale, enUS as dtLocaleEnUs } from 'date-fns/locale';
6
- import i18Next, { type Resource, type TFunction } from 'i18next';
6
+ import i18Next, { type Resource } from 'i18next';
7
7
  import React, { type ReactNode, Suspense, createContext, useContext, useEffect, useState } from 'react';
8
8
  import { initReactI18next, useTranslation as useI18NextTranslation } from 'react-i18next';
9
9
 
@@ -11,21 +11,6 @@ const initialLng = 'en-US';
11
11
  const initialNs = 'dxos-common';
12
12
  const initialDtLocale = dtLocaleEnUs;
13
13
 
14
- // TODO(thure): `Parameters<TFunction>` causes typechecking issues because `TFunction` has so many signatures.
15
- export type Label = string | [string, { ns: string; count?: number; defaultValue?: string }];
16
-
17
- export const isLabel = (o: any): o is Label =>
18
- typeof o === 'string' ||
19
- (Array.isArray(o) &&
20
- o.length === 2 &&
21
- typeof o[0] === 'string' &&
22
- !!o[1] &&
23
- typeof o[1] === 'object' &&
24
- 'ns' in o[1] &&
25
- typeof o[1].ns === 'string');
26
-
27
- export const toLocalizedString = (label: Label, t: TFunction) => (Array.isArray(label) ? t(...label) : label);
28
-
29
14
  export const resources = {
30
15
  [initialLng]: {
31
16
  [initialNs]: {
@@ -2,7 +2,7 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- export * from './ThemeProvider';
6
- export { type Label, isLabel, toLocalizedString, useTranslation } from './TranslationsProvider';
5
+ export { type Label, isLabel, toLocalizedString } from '@dxos/ui-types';
7
6
 
8
- // TODO(burdon): Use `@internal` with barrel exports (rather than picking individual exports?)
7
+ export * from './ThemeProvider';
8
+ export { useTranslation } from './TranslationsProvider';
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { type ReactNode, useState } from 'react';
7
7
 
8
8
  import { withTheme } from '../../testing';
9
- import { Button } from '../Buttons';
9
+ import { Button } from '../Button';
10
10
 
11
11
  import { Toast } from './Toast';
12
12
 
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React from 'react';
7
7
 
8
8
  import { withTheme } from '../../testing';
9
- import { Toggle } from '../Buttons';
9
+ import { Toggle } from '../Button';
10
10
  import { Icon } from '../Icon';
11
11
  import { Select } from '../Select';
12
12