@dxos/react-ui 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d

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-P333G32W.mjs → chunk-N5GDJTT2.mjs} +632 -299
  2. package/dist/lib/browser/chunk-N5GDJTT2.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +12 -1
  4. package/dist/lib/browser/index.mjs.map +2 -2
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +57 -29
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node-esm/{chunk-JRCE5UVS.mjs → chunk-SP7VQH72.mjs} +632 -299
  9. package/dist/lib/node-esm/chunk-SP7VQH72.mjs.map +7 -0
  10. package/dist/lib/node-esm/index.mjs +12 -1
  11. package/dist/lib/node-esm/index.mjs.map +2 -2
  12. package/dist/lib/node-esm/meta.json +1 -1
  13. package/dist/lib/node-esm/testing/index.mjs +57 -29
  14. package/dist/lib/node-esm/testing/index.mjs.map +4 -4
  15. package/dist/types/src/components/Avatars/Avatar.stories.d.ts +0 -6
  16. package/dist/types/src/components/Avatars/Avatar.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Avatars/AvatarGroup.stories.d.ts +0 -6
  18. package/dist/types/src/components/Avatars/AvatarGroup.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/Breadcrumb/Breadcrumb.stories.d.ts +0 -6
  20. package/dist/types/src/components/Breadcrumb/Breadcrumb.stories.d.ts.map +1 -1
  21. package/dist/types/src/components/{Buttons → Button}/Button.d.ts +1 -1
  22. package/dist/types/src/components/Button/Button.d.ts.map +1 -0
  23. package/dist/types/src/components/{Buttons → Button}/Button.stories.d.ts +1 -7
  24. package/dist/types/src/components/Button/Button.stories.d.ts.map +1 -0
  25. package/dist/types/src/components/{Buttons → Button}/IconButton.d.ts +1 -2
  26. package/dist/types/src/components/Button/IconButton.d.ts.map +1 -0
  27. package/dist/types/src/components/{Buttons → Button}/IconButton.stories.d.ts +0 -6
  28. package/dist/types/src/components/Button/IconButton.stories.d.ts.map +1 -0
  29. package/dist/types/src/components/Button/Toggle.d.ts.map +1 -0
  30. package/dist/types/src/components/Button/Toggle.stories.d.ts +16 -0
  31. package/dist/types/src/components/Button/Toggle.stories.d.ts.map +1 -0
  32. package/dist/types/src/components/{Buttons → Button}/ToggleGroup.d.ts +6 -7
  33. package/dist/types/src/components/Button/ToggleGroup.d.ts.map +1 -0
  34. package/dist/types/src/components/{Buttons → Button}/ToggleGroup.stories.d.ts +2 -12
  35. package/dist/types/src/components/Button/ToggleGroup.stories.d.ts.map +1 -0
  36. package/dist/types/src/components/Button/index.d.ts.map +1 -0
  37. package/dist/types/src/components/Clipboard/CopyButton.d.ts +1 -1
  38. package/dist/types/src/components/Clipboard/CopyButton.d.ts.map +1 -1
  39. package/dist/types/src/components/Dialog/AlertDialog.d.ts.map +1 -0
  40. package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.stories.d.ts +0 -6
  41. package/dist/types/src/components/Dialog/AlertDialog.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -0
  43. package/dist/types/src/components/{Dialogs → Dialog}/Dialog.stories.d.ts +0 -1
  44. package/dist/types/src/components/Dialog/Dialog.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/Dialog/index.d.ts.map +1 -0
  46. package/dist/types/src/components/Icon/Icon.d.ts +1 -1
  47. package/dist/types/src/components/Icon/Icon.d.ts.map +1 -1
  48. package/dist/types/src/components/Icon/Icon.stories.d.ts +17 -0
  49. package/dist/types/src/components/Icon/Icon.stories.d.ts.map +1 -0
  50. package/dist/types/src/components/Input/Input.d.ts +0 -2
  51. package/dist/types/src/components/Input/Input.d.ts.map +1 -1
  52. package/dist/types/src/components/Input/Input.stories.d.ts +0 -6
  53. package/dist/types/src/components/Input/Input.stories.d.ts.map +1 -1
  54. package/dist/types/src/components/Link/Link.stories.d.ts +0 -6
  55. package/dist/types/src/components/Link/Link.stories.d.ts.map +1 -1
  56. package/dist/types/src/components/List/List.d.ts.map +1 -0
  57. package/dist/types/src/components/{Lists → List}/List.stories.d.ts +0 -6
  58. package/dist/types/src/components/List/List.stories.d.ts.map +1 -0
  59. package/dist/types/src/components/List/ListDropIndicator.d.ts.map +1 -0
  60. package/dist/types/src/components/List/Tree.d.ts.map +1 -0
  61. package/dist/types/src/components/{Lists → List}/Tree.stories.d.ts +0 -6
  62. package/dist/types/src/components/List/Tree.stories.d.ts.map +1 -0
  63. package/dist/types/src/components/List/TreeDropIndicator.d.ts.map +1 -0
  64. package/dist/types/src/components/{Lists → List}/Treegrid.d.ts.map +1 -1
  65. package/dist/types/src/components/List/Treegrid.stories.d.ts.map +1 -0
  66. package/dist/types/src/components/List/index.d.ts.map +1 -0
  67. package/dist/types/src/components/Main/Main.d.ts +9 -18
  68. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  69. package/dist/types/src/components/Main/Main.stories.d.ts +1 -2
  70. package/dist/types/src/components/Main/Main.stories.d.ts.map +1 -1
  71. package/dist/types/src/components/Menus/ContextMenu.d.ts.map +1 -1
  72. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts +0 -6
  73. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts.map +1 -1
  74. package/dist/types/src/components/Menus/DropdownMenu.d.ts +7 -7
  75. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +1 -1
  76. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts +0 -6
  77. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts.map +1 -1
  78. package/dist/types/src/components/Message/Message.stories.d.ts +0 -1
  79. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  80. package/dist/types/src/components/Popover/Popover.d.ts +1 -1
  81. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  82. package/dist/types/src/components/Popover/Popover.stories.d.ts +0 -6
  83. package/dist/types/src/components/Popover/Popover.stories.d.ts.map +1 -1
  84. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +0 -6
  85. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  86. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +37 -0
  87. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -0
  88. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +18 -0
  89. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -0
  90. package/dist/types/src/components/ScrollContainer/index.d.ts +2 -0
  91. package/dist/types/src/components/ScrollContainer/index.d.ts.map +1 -0
  92. package/dist/types/src/components/Select/Select.d.ts +1 -1
  93. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  94. package/dist/types/src/components/Select/Select.stories.d.ts +0 -6
  95. package/dist/types/src/components/Select/Select.stories.d.ts.map +1 -1
  96. package/dist/types/src/components/Status/Status.stories.d.ts +0 -6
  97. package/dist/types/src/components/Status/Status.stories.d.ts.map +1 -1
  98. package/dist/types/src/components/Tag/Tag.stories.d.ts +8 -8
  99. package/dist/types/src/components/Tag/Tag.stories.d.ts.map +1 -1
  100. package/dist/types/src/components/Toast/Toast.stories.d.ts +0 -6
  101. package/dist/types/src/components/Toast/Toast.stories.d.ts.map +1 -1
  102. package/dist/types/src/components/Toolbar/Toolbar.d.ts +12 -12
  103. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  104. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts +0 -6
  105. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  106. package/dist/types/src/components/Tooltip/Tooltip.d.ts +1 -3
  107. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  108. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts +0 -6
  109. package/dist/types/src/components/Tooltip/Tooltip.stories.d.ts.map +1 -1
  110. package/dist/types/src/components/index.d.ts +4 -3
  111. package/dist/types/src/components/index.d.ts.map +1 -1
  112. package/dist/types/src/hooks/useSafeArea.d.ts.map +1 -1
  113. package/dist/types/src/hooks/useVisualViewport.d.ts +2 -2
  114. package/dist/types/src/hooks/useVisualViewport.d.ts.map +1 -1
  115. package/dist/types/src/index.d.ts +1 -1
  116. package/dist/types/src/index.d.ts.map +1 -1
  117. package/dist/types/src/playground/Controls.stories.d.ts +0 -6
  118. package/dist/types/src/playground/Controls.stories.d.ts.map +1 -1
  119. package/dist/types/src/playground/Custom.stories.d.ts +1 -2
  120. package/dist/types/src/playground/Custom.stories.d.ts.map +1 -1
  121. package/dist/types/src/playground/Typography.stories.d.ts +0 -6
  122. package/dist/types/src/playground/Typography.stories.d.ts.map +1 -1
  123. package/dist/types/src/testing/decorators/index.d.ts +2 -1
  124. package/dist/types/src/testing/decorators/index.d.ts.map +1 -1
  125. package/dist/types/src/testing/decorators/withLayout.d.ts +15 -0
  126. package/dist/types/src/testing/decorators/withLayout.d.ts.map +1 -0
  127. package/dist/types/src/testing/decorators/{withSurfaceVariantsLayout.d.ts → withLayoutVariants.d.ts} +2 -2
  128. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts.map +1 -0
  129. package/dist/types/src/testing/decorators/withTheme.d.ts +3 -0
  130. package/dist/types/src/testing/decorators/withTheme.d.ts.map +1 -1
  131. package/dist/types/src/util/domino.d.ts +18 -0
  132. package/dist/types/src/util/domino.d.ts.map +1 -0
  133. package/dist/types/src/util/index.d.ts +2 -0
  134. package/dist/types/src/util/index.d.ts.map +1 -1
  135. package/dist/types/src/util/usePx.d.ts +8 -0
  136. package/dist/types/src/util/usePx.d.ts.map +1 -0
  137. package/dist/types/tsconfig.tsbuildinfo +1 -1
  138. package/package.json +24 -23
  139. package/src/components/Avatars/Avatar.stories.tsx +0 -2
  140. package/src/components/Avatars/AvatarGroup.stories.tsx +0 -3
  141. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -4
  142. package/src/components/{Buttons → Button}/Button.stories.tsx +3 -5
  143. package/src/components/{Buttons → Button}/IconButton.stories.tsx +0 -3
  144. package/src/components/{Buttons → Button}/IconButton.tsx +14 -13
  145. package/src/components/{Buttons → Button}/Toggle.stories.tsx +5 -7
  146. package/src/components/{Buttons → Button}/ToggleGroup.stories.tsx +0 -3
  147. package/src/components/{Buttons → Button}/ToggleGroup.tsx +14 -1
  148. package/src/components/Clipboard/CopyButton.tsx +1 -1
  149. package/src/components/{Dialogs → Dialog}/AlertDialog.stories.tsx +1 -4
  150. package/src/components/{Dialogs → Dialog}/Dialog.stories.tsx +6 -4
  151. package/src/components/Icon/Icon.stories.tsx +113 -0
  152. package/src/components/Icon/Icon.tsx +1 -1
  153. package/src/components/Input/Input.stories.tsx +0 -3
  154. package/src/components/Input/Input.tsx +3 -3
  155. package/src/components/Link/Link.stories.tsx +0 -3
  156. package/src/components/{Lists → List}/List.stories.tsx +2 -6
  157. package/src/components/{Lists → List}/ListDropIndicator.tsx +1 -1
  158. package/src/components/{Lists → List}/Tree.stories.tsx +0 -3
  159. package/src/components/{Lists → List}/Treegrid.tsx +57 -16
  160. package/src/components/Main/Main.stories.tsx +4 -4
  161. package/src/components/Main/Main.tsx +28 -19
  162. package/src/components/Menus/ContextMenu.stories.tsx +0 -3
  163. package/src/components/Menus/ContextMenu.tsx +1 -0
  164. package/src/components/Menus/DropdownMenu.stories.tsx +1 -4
  165. package/src/components/Menus/DropdownMenu.tsx +37 -8
  166. package/src/components/Message/Message.stories.tsx +0 -2
  167. package/src/components/Popover/Popover.stories.tsx +1 -4
  168. package/src/components/Popover/Popover.tsx +22 -5
  169. package/src/components/ScrollArea/ScrollArea.stories.tsx +1 -4
  170. package/src/components/ScrollContainer/ScrollContainer.stories.tsx +69 -0
  171. package/src/components/ScrollContainer/ScrollContainer.tsx +231 -0
  172. package/src/components/ScrollContainer/index.ts +5 -0
  173. package/src/components/Select/Select.stories.tsx +3 -5
  174. package/src/components/Select/Select.tsx +5 -4
  175. package/src/components/Status/Status.stories.tsx +0 -3
  176. package/src/components/Tag/Tag.stories.tsx +8 -7
  177. package/src/components/Toast/Toast.stories.tsx +1 -4
  178. package/src/components/Toolbar/Toolbar.stories.tsx +2 -7
  179. package/src/components/Toolbar/Toolbar.tsx +18 -6
  180. package/src/components/Tooltip/Tooltip.stories.tsx +1 -4
  181. package/src/components/Tooltip/Tooltip.tsx +2 -1
  182. package/src/components/index.ts +4 -3
  183. package/src/hooks/useSafeArea.ts +3 -2
  184. package/src/hooks/useVisualViewport.ts +4 -4
  185. package/src/index.ts +1 -1
  186. package/src/playground/Controls.stories.tsx +3 -5
  187. package/src/playground/Custom.stories.tsx +9 -20
  188. package/src/playground/Typography.stories.tsx +0 -3
  189. package/src/testing/decorators/index.ts +2 -1
  190. package/src/testing/decorators/withLayout.tsx +56 -0
  191. package/src/testing/decorators/{withSurfaceVariantsLayout.tsx → withLayoutVariants.tsx} +2 -2
  192. package/src/testing/decorators/withTheme.tsx +31 -0
  193. package/src/util/domino.ts +53 -0
  194. package/src/util/index.ts +2 -0
  195. package/src/util/usePx.ts +61 -0
  196. package/dist/lib/browser/chunk-P333G32W.mjs.map +0 -7
  197. package/dist/lib/node-esm/chunk-JRCE5UVS.mjs.map +0 -7
  198. package/dist/types/src/components/Buttons/Button.d.ts.map +0 -1
  199. package/dist/types/src/components/Buttons/Button.stories.d.ts.map +0 -1
  200. package/dist/types/src/components/Buttons/IconButton.d.ts.map +0 -1
  201. package/dist/types/src/components/Buttons/IconButton.stories.d.ts.map +0 -1
  202. package/dist/types/src/components/Buttons/Toggle.d.ts.map +0 -1
  203. package/dist/types/src/components/Buttons/Toggle.stories.d.ts +0 -19
  204. package/dist/types/src/components/Buttons/Toggle.stories.d.ts.map +0 -1
  205. package/dist/types/src/components/Buttons/ToggleGroup.d.ts.map +0 -1
  206. package/dist/types/src/components/Buttons/ToggleGroup.stories.d.ts.map +0 -1
  207. package/dist/types/src/components/Buttons/index.d.ts.map +0 -1
  208. package/dist/types/src/components/Dialogs/AlertDialog.d.ts.map +0 -1
  209. package/dist/types/src/components/Dialogs/AlertDialog.stories.d.ts.map +0 -1
  210. package/dist/types/src/components/Dialogs/Dialog.d.ts.map +0 -1
  211. package/dist/types/src/components/Dialogs/Dialog.stories.d.ts.map +0 -1
  212. package/dist/types/src/components/Dialogs/index.d.ts.map +0 -1
  213. package/dist/types/src/components/Lists/List.d.ts.map +0 -1
  214. package/dist/types/src/components/Lists/List.stories.d.ts.map +0 -1
  215. package/dist/types/src/components/Lists/ListDropIndicator.d.ts.map +0 -1
  216. package/dist/types/src/components/Lists/Tree.d.ts.map +0 -1
  217. package/dist/types/src/components/Lists/Tree.stories.d.ts.map +0 -1
  218. package/dist/types/src/components/Lists/TreeDropIndicator.d.ts.map +0 -1
  219. package/dist/types/src/components/Lists/Treegrid.stories.d.ts.map +0 -1
  220. package/dist/types/src/components/Lists/index.d.ts.map +0 -1
  221. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts.map +0 -1
  222. package/src/testing/decorators/withTheme.ts +0 -25
  223. /package/dist/types/src/components/{Buttons → Button}/Toggle.d.ts +0 -0
  224. /package/dist/types/src/components/{Buttons → Button}/index.d.ts +0 -0
  225. /package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.d.ts +0 -0
  226. /package/dist/types/src/components/{Dialogs → Dialog}/Dialog.d.ts +0 -0
  227. /package/dist/types/src/components/{Dialogs → Dialog}/index.d.ts +0 -0
  228. /package/dist/types/src/components/{Lists → List}/List.d.ts +0 -0
  229. /package/dist/types/src/components/{Lists → List}/ListDropIndicator.d.ts +0 -0
  230. /package/dist/types/src/components/{Lists → List}/Tree.d.ts +0 -0
  231. /package/dist/types/src/components/{Lists → List}/TreeDropIndicator.d.ts +0 -0
  232. /package/dist/types/src/components/{Lists → List}/Treegrid.d.ts +0 -0
  233. /package/dist/types/src/components/{Lists → List}/Treegrid.stories.d.ts +0 -0
  234. /package/dist/types/src/components/{Lists → List}/index.d.ts +0 -0
  235. /package/src/components/{Buttons → Button}/Button.tsx +0 -0
  236. /package/src/components/{Buttons → Button}/Toggle.tsx +0 -0
  237. /package/src/components/{Buttons → Button}/index.ts +0 -0
  238. /package/src/components/{Dialogs → Dialog}/AlertDialog.tsx +0 -0
  239. /package/src/components/{Dialogs → Dialog}/Dialog.tsx +0 -0
  240. /package/src/components/{Dialogs → Dialog}/index.ts +0 -0
  241. /package/src/components/{Lists → List}/List.tsx +0 -0
  242. /package/src/components/{Lists → List}/Tree.tsx +0 -0
  243. /package/src/components/{Lists → List}/TreeDropIndicator.tsx +0 -0
  244. /package/src/components/{Lists → List}/Treegrid.stories.tsx +0 -0
  245. /package/src/components/{Lists → List}/index.ts +0 -0
@@ -2,12 +2,18 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { useArrowNavigationGroup, useFocusableGroup } from '@fluentui/react-tabster';
5
+ import { useFocusFinders } from '@fluentui/react-tabster';
6
6
  import { type Scope, createContextScope } from '@radix-ui/react-context';
7
7
  import { Primitive } from '@radix-ui/react-primitive';
8
8
  import { Slot } from '@radix-ui/react-slot';
9
9
  import { useControllableState } from '@radix-ui/react-use-controllable-state';
10
- import React, { type CSSProperties, type ComponentPropsWithRef, forwardRef } from 'react';
10
+ import React, {
11
+ type CSSProperties,
12
+ type ComponentPropsWithRef,
13
+ type KeyboardEvent,
14
+ forwardRef,
15
+ useCallback,
16
+ } from 'react';
11
17
 
12
18
  import { useThemeContext } from '../../hooks';
13
19
  import { type ThemedClassName } from '../../util';
@@ -40,12 +46,58 @@ const TreegridRoot = forwardRef<HTMLDivElement, TreegridRootProps>(
40
46
  ({ asChild, classNames, children, style, gridTemplateColumns, ...props }, forwardedRef) => {
41
47
  const { tx } = useThemeContext();
42
48
  const Root = asChild ? Slot : Primitive.div;
43
- const arrowNavigationAttrs = useArrowNavigationGroup({ axis: 'vertical', tabbable: false, circular: true });
49
+ const { findFirstFocusable } = useFocusFinders();
50
+
51
+ const handleKeyDown = useCallback(
52
+ (event: KeyboardEvent<HTMLDivElement>) => {
53
+ switch (event.key) {
54
+ case 'ArrowDown':
55
+ case 'ArrowUp': {
56
+ const direction = event.key === 'ArrowDown' ? 'down' : 'up';
57
+ const target = event.target as HTMLElement;
58
+
59
+ // Find ancestor with data-arrow-keys containing the relevant direction.
60
+ const ancestorWithArrowKeys = target.closest(`[data-arrow-keys*="${direction}"], [data-arrow-keys="all"]`);
61
+
62
+ // If no ancestor with data-arrow-keys found, proceed with row navigation.
63
+ if (!ancestorWithArrowKeys) {
64
+ // Find the closest row
65
+ const currentRow = target.closest('[role="row"]');
66
+ if (currentRow) {
67
+ // Find the treegrid container.
68
+ const treegrid = currentRow.closest('[role="treegrid"]');
69
+ if (treegrid) {
70
+ // Get all rows in the treegrid.
71
+ const rows = Array.from(treegrid.querySelectorAll('[role="row"]'));
72
+ const currentIndex = rows.indexOf(currentRow as Element);
73
+
74
+ // Find next or previous row.
75
+ const nextIndex = direction === 'down' ? currentIndex + 1 : currentIndex - 1;
76
+ const targetRow = rows[nextIndex];
77
+
78
+ if (targetRow) {
79
+ // Focus the first focusable element in the target row.
80
+ const firstFocusable = findFirstFocusable(targetRow as HTMLElement);
81
+ if (firstFocusable) {
82
+ event.preventDefault();
83
+ firstFocusable.focus();
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ break;
90
+ }
91
+ }
92
+ props.onKeyDown?.(event);
93
+ },
94
+ [findFirstFocusable],
95
+ );
44
96
 
45
97
  return (
46
98
  <Root
47
99
  role='treegrid'
48
- {...arrowNavigationAttrs}
100
+ onKeyDown={handleKeyDown}
49
101
  {...props}
50
102
  className={tx('treegrid.root', 'treegrid', {}, classNames)}
51
103
  style={{ ...style, gridTemplateColumns }}
@@ -91,13 +143,6 @@ const TreegridRow = forwardRef<HTMLDivElement, TreegridRowScopedProps<TreegridRo
91
143
  onChange: propsOnOpenChange,
92
144
  defaultProp: defaultOpen,
93
145
  });
94
- const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited' });
95
- const arrowGroupAttrs = useArrowNavigationGroup({
96
- axis: 'horizontal',
97
- tabbable: false,
98
- circular: false,
99
- memorizeCurrent: false,
100
- });
101
146
 
102
147
  return (
103
148
  <TreegridRowProvider open={open} onOpenChange={onOpenChange} scope={__treegridRowScope}>
@@ -106,15 +151,11 @@ const TreegridRow = forwardRef<HTMLDivElement, TreegridRowScopedProps<TreegridRo
106
151
  aria-level={level}
107
152
  className={tx('treegrid.row', 'treegrid__row', { level }, classNames)}
108
153
  {...(parentOf && { 'aria-expanded': open, 'aria-owns': parentOf })}
109
- tabIndex={0}
110
- {...focusableGroupAttrs}
111
154
  {...props}
112
155
  id={id}
113
156
  ref={forwardedRef}
114
157
  >
115
- <div role='none' className='contents' {...arrowGroupAttrs}>
116
- {children}
117
- </div>
158
+ {children}
118
159
  </Root>
119
160
  </TreegridRowProvider>
120
161
  );
@@ -2,13 +2,11 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
10
8
  import { withTheme } from '../../testing';
11
- import { Button } from '../Buttons';
9
+ import { Button } from '../Button';
12
10
 
13
11
  import { Main, useSidebars } from './Main';
14
12
 
@@ -53,8 +51,10 @@ const meta = {
53
51
  render: DefaultStory,
54
52
  decorators: [withTheme],
55
53
  parameters: {
56
- chromatic: { disableSnapshot: false },
57
54
  layout: 'fullscreen',
55
+ chromatic: {
56
+ disableSnapshot: false,
57
+ },
58
58
  },
59
59
  } satisfies Meta<typeof DefaultStory>;
60
60
 
@@ -2,6 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
+ import { useFocusableGroup } from '@fluentui/react-tabster';
5
6
  import { createContext } from '@radix-ui/react-context';
6
7
  import { DialogContent, Root as DialogRoot, DialogTitle } from '@radix-ui/react-dialog';
7
8
  import { Primitive } from '@radix-ui/react-primitive';
@@ -30,22 +31,12 @@ import { type Label, toLocalizedString, useTranslation } from '../ThemeProvider'
30
31
 
31
32
  import { useSwipeToDismiss } from './useSwipeToDismiss';
32
33
 
34
+ const MAIN_NAME = 'Main';
33
35
  const MAIN_ROOT_NAME = 'MainRoot';
34
36
  const NAVIGATION_SIDEBAR_NAME = 'NavigationSidebar';
35
37
  const COMPLEMENTARY_SIDEBAR_NAME = 'ComplementarySidebar';
36
- const MAIN_NAME = 'Main';
37
38
  const GENERIC_CONSUMER_NAME = 'GenericConsumer';
38
39
 
39
- type SidebarState = 'expanded' | 'collapsed' | 'closed';
40
-
41
- type MainContextValue = {
42
- resizing: boolean;
43
- navigationSidebarState: SidebarState;
44
- setNavigationSidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
45
- complementarySidebarState: SidebarState;
46
- setComplementarySidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
47
- };
48
-
49
40
  const landmarkAttr = 'data-main-landmark';
50
41
 
51
42
  /**
@@ -70,7 +61,10 @@ const useLandmarkMover = (propsOnKeyDown: ComponentPropsWithoutRef<'div'>['onKey
70
61
  },
71
62
  [propsOnKeyDown],
72
63
  );
73
- const focusableGroupAttrs = window ? {} : { tabBehavior: 'limited', ignoreDefaultKeydown: { Tab: true } };
64
+
65
+ // TODO(thure): This was disconnected once before in #8818;
66
+ // if this should change again to support the browser extension, please ensure the change doesn’t break web, desktop and mobile.
67
+ const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited', ignoreDefaultKeydown: { Tab: true } });
74
68
 
75
69
  return {
76
70
  onKeyDown: handleKeyDown,
@@ -80,6 +74,16 @@ const useLandmarkMover = (propsOnKeyDown: ComponentPropsWithoutRef<'div'>['onKey
80
74
  };
81
75
  };
82
76
 
77
+ type SidebarState = 'expanded' | 'collapsed' | 'closed';
78
+
79
+ type MainContextValue = {
80
+ resizing: boolean;
81
+ navigationSidebarState: SidebarState;
82
+ setNavigationSidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
83
+ complementarySidebarState: SidebarState;
84
+ setComplementarySidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
85
+ };
86
+
83
87
  const [MainProvider, useMainContext] = createContext<MainContextValue>(MAIN_NAME, {
84
88
  resizing: false,
85
89
  navigationSidebarState: 'closed',
@@ -141,7 +145,7 @@ const MainRoot = ({
141
145
  children,
142
146
  ...props
143
147
  }: MainRootProps) => {
144
- const [isLg] = useMediaQuery('lg', { ssr: false });
148
+ const [isLg] = useMediaQuery('lg');
145
149
  const [navigationSidebarState = isLg ? 'expanded' : 'collapsed', setNavigationSidebarState] =
146
150
  useControllableState<SidebarState>({
147
151
  prop: propsNavigationSidebarState,
@@ -210,7 +214,7 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
210
214
  { classNames, children, swipeToDismiss, onOpenAutoFocus, state, resizing, onStateChange, side, label, ...props },
211
215
  forwardedRef,
212
216
  ) => {
213
- const [isLg] = useMediaQuery('lg', { ssr: false });
217
+ const [isLg] = useMediaQuery('lg');
214
218
  const { tx } = useThemeContext();
215
219
  const { t } = useTranslation();
216
220
  const ref = useForwardedRef(forwardedRef);
@@ -218,10 +222,15 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
218
222
  useSwipeToDismiss(swipeToDismiss ? ref : noopRef, {
219
223
  onDismiss: () => onStateChange?.('closed'),
220
224
  });
225
+ // NOTE(thure): This is a workaround for something further down the tree grabbing focus on Escape. Adding this
226
+ // intervention to `Tabs.Root` or `Tabs.Tabpenel` instances is somehow ineffectual.
221
227
  const handleKeyDown = useCallback(
222
228
  (event: KeyboardEvent<HTMLDivElement>) => {
223
- if (event.key === 'Escape') {
224
- ((event.target as HTMLDivElement).closest('[data-tabster]') as HTMLDivElement)?.focus();
229
+ const focusGroupParent = (event.target as HTMLElement).closest('[data-tabster]');
230
+ if (event.key === 'Escape' && focusGroupParent) {
231
+ event.preventDefault();
232
+ event.stopPropagation();
233
+ (focusGroupParent as HTMLElement).focus();
225
234
  }
226
235
  props.onKeyDown?.(event);
227
236
  },
@@ -239,8 +248,8 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
239
248
  data-state={state}
240
249
  data-resizing={resizing ? 'true' : 'false'}
241
250
  className={tx('main.sidebar', 'main__sidebar', {}, classNames)}
242
- onKeyDown={handleKeyDown}
243
- {...(state === 'closed' && { inert: 'true' })}
251
+ onKeyDownCapture={handleKeyDown}
252
+ {...(state === 'closed' && { inert: true })}
244
253
  ref={ref}
245
254
  >
246
255
  {children}
@@ -329,7 +338,7 @@ MainContent.displayName = MAIN_NAME;
329
338
  type MainOverlayProps = ThemedClassName<Omit<ComponentPropsWithRef<typeof Primitive.div>, 'children'>>;
330
339
 
331
340
  const MainOverlay = forwardRef<HTMLDivElement, MainOverlayProps>(({ classNames, ...props }, forwardedRef) => {
332
- const [isLg] = useMediaQuery('lg', { ssr: false });
341
+ const [isLg] = useMediaQuery('lg');
333
342
  const { navigationSidebarState, setNavigationSidebarState, complementarySidebarState, setComplementarySidebarState } =
334
343
  useMainContext(MAIN_NAME);
335
344
  const { tx } = useThemeContext();
@@ -2,8 +2,6 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
@@ -98,7 +96,6 @@ const meta = {
98
96
  component: ContextMenu.Root as any,
99
97
  render: DefaultStory,
100
98
  decorators: [withTheme],
101
- parameters: { chromatic: { disableSnapshot: false } },
102
99
  } satisfies Meta<typeof DefaultStory>;
103
100
 
104
101
  export default meta;
@@ -34,6 +34,7 @@ const ContextMenuContent = forwardRef<HTMLDivElement, ContextMenuContentProps>(
34
34
  return (
35
35
  <ContextMenuPrimitive.Content
36
36
  {...props}
37
+ data-arrow-keys='up down'
37
38
  collisionPadding={safeCollisionPadding}
38
39
  className={tx('menu.content', 'menu', { elevation }, classNames)}
39
40
  ref={forwardedRef}
@@ -2,13 +2,11 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { useRef, useState } from 'react';
9
7
 
10
8
  import { withTheme } from '../../testing';
11
- import { Button } from '../Buttons';
9
+ import { Button } from '../Button';
12
10
 
13
11
  import { DropdownMenu } from './DropdownMenu';
14
12
 
@@ -98,7 +96,6 @@ const meta = {
98
96
  component: DropdownMenu.Root,
99
97
  render: DefaultStory,
100
98
  decorators: [withTheme],
101
- parameters: { chromatic: { disableSnapshot: false } },
102
99
  } satisfies Meta<typeof DefaultStory>;
103
100
 
104
101
  export default meta;
@@ -25,6 +25,7 @@ import React, {
25
25
  forwardRef,
26
26
  useCallback,
27
27
  useEffect,
28
+ useMemo,
28
29
  useRef,
29
30
  } from 'react';
30
31
 
@@ -42,7 +43,7 @@ const DROPDOWN_MENU_NAME = 'DropdownMenu';
42
43
 
43
44
  type ScopedProps<P> = P & { __scopeDropdownMenu?: Scope };
44
45
  const [createDropdownMenuContext, createDropdownMenuScope] = createContextScope(DROPDOWN_MENU_NAME, [createMenuScope]);
45
- const useMenuScope = createMenuScope();
46
+ const useMenuScope: (scope?: Scope) => any = createMenuScope();
46
47
 
47
48
  type DropdownMenuContextValue = {
48
49
  triggerId: string;
@@ -124,6 +125,7 @@ const DropdownMenuTrigger = forwardRef<DropdownMenuTriggerElement, DropdownMenuT
124
125
  disabled={disabled}
125
126
  {...triggerProps}
126
127
  ref={composeRefs(forwardedRef, context.triggerRef)}
128
+ data-arrow-keys='down'
127
129
  onPointerDown={composeEventHandlers(props.onPointerDown, (event) => {
128
130
  // only call handler if it's the left button (mousedown gets triggered by all mouse buttons)
129
131
  // but not when the control key is pressed (avoiding MacOS right click)
@@ -167,7 +169,7 @@ DropdownMenuTrigger.displayName = TRIGGER_NAME;
167
169
  const VIRTUAL_TRIGGER_NAME = 'DropdownMenuVirtualTrigger';
168
170
 
169
171
  interface DropdownMenuVirtualTriggerProps {
170
- virtualRef: RefObject<DropdownMenuTriggerElement>;
172
+ virtualRef: RefObject<DropdownMenuTriggerElement | null>;
171
173
  }
172
174
 
173
175
  const DropdownMenuVirtualTrigger = (props: ScopedProps<DropdownMenuVirtualTriggerProps>) => {
@@ -179,7 +181,7 @@ const DropdownMenuVirtualTrigger = (props: ScopedProps<DropdownMenuVirtualTrigge
179
181
  context.triggerRef.current = virtualRef.current;
180
182
  }
181
183
  });
182
- return <MenuPrimitive.Anchor {...menuScope} virtualRef={virtualRef} />;
184
+ return <MenuPrimitive.Anchor {...menuScope} virtualRef={virtualRef as RefObject<DropdownMenuTriggerElement>} />;
183
185
  };
184
186
 
185
187
  DropdownMenuVirtualTrigger.displayName = VIRTUAL_TRIGGER_NAME;
@@ -233,19 +235,35 @@ interface DropdownMenuContentProps extends Omit<MenuContentProps, 'onEntryFocus'
233
235
 
234
236
  const DropdownMenuContent = forwardRef<DropdownMenuContentElement, DropdownMenuContentProps>(
235
237
  (props: ScopedProps<DropdownMenuContentProps>, forwardedRef) => {
236
- const { __scopeDropdownMenu, classNames, collisionPadding = 8, ...contentProps } = props;
238
+ const { __scopeDropdownMenu, classNames, collisionPadding = 8, collisionBoundary, ...contentProps } = props;
237
239
  const { tx } = useThemeContext();
238
240
  const context = useDropdownMenuContext(CONTENT_NAME, __scopeDropdownMenu);
239
241
  const elevation = useElevationContext();
240
242
  const menuScope = useMenuScope(__scopeDropdownMenu);
241
243
  const hasInteractedOutsideRef = useRef(false);
242
244
  const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
245
+
246
+ // Check for the closest annotated collision boundary in the DOM tree.
247
+ const computedCollisionBoundary = useMemo(() => {
248
+ const closestBoundary = context.triggerRef.current?.closest(
249
+ '[data-popover-collision-boundary]',
250
+ ) as HTMLElement | null;
251
+ return closestBoundary
252
+ ? Array.isArray(collisionBoundary)
253
+ ? [closestBoundary, ...collisionBoundary]
254
+ : collisionBoundary
255
+ ? [closestBoundary, collisionBoundary]
256
+ : [closestBoundary]
257
+ : collisionBoundary;
258
+ }, [context.open, collisionBoundary, context.triggerRef.current]);
259
+
243
260
  return (
244
261
  <MenuPrimitive.Content
245
262
  id={context.contentId}
246
263
  aria-labelledby={context.triggerId}
247
264
  {...menuScope}
248
265
  {...contentProps}
266
+ collisionBoundary={computedCollisionBoundary}
249
267
  collisionPadding={safeCollisionPadding}
250
268
  ref={forwardedRef}
251
269
  onCloseAutoFocus={composeEventHandlers(props.onCloseAutoFocus, (event) => {
@@ -264,6 +282,7 @@ const DropdownMenuContent = forwardRef<DropdownMenuContentElement, DropdownMenuC
264
282
  hasInteractedOutsideRef.current = true;
265
283
  }
266
284
  })}
285
+ data-arrow-keys='up down'
267
286
  className={tx('menu.content', 'menu', { elevation }, classNames)}
268
287
  style={{
269
288
  ...props.style,
@@ -415,13 +434,21 @@ const RADIO_ITEM_NAME = 'DropdownMenuRadioItem';
415
434
 
416
435
  type DropdownMenuRadioItemElement = ElementRef<typeof MenuPrimitive.RadioItem>;
417
436
  type MenuRadioItemProps = ComponentPropsWithoutRef<typeof MenuPrimitive.RadioItem>;
418
- interface DropdownMenuRadioItemProps extends MenuRadioItemProps {}
437
+ type DropdownMenuRadioItemProps = ThemedClassName<MenuRadioItemProps>;
419
438
 
420
439
  const DropdownMenuRadioItem = forwardRef<DropdownMenuRadioItemElement, DropdownMenuRadioItemProps>(
421
440
  (props: ScopedProps<DropdownMenuRadioItemProps>, forwardedRef) => {
422
- const { __scopeDropdownMenu, ...radioItemProps } = props;
441
+ const { __scopeDropdownMenu, classNames, ...itemProps } = props;
423
442
  const menuScope = useMenuScope(__scopeDropdownMenu);
424
- return <MenuPrimitive.RadioItem {...menuScope} {...radioItemProps} ref={forwardedRef} />;
443
+ const { tx } = useThemeContext();
444
+ return (
445
+ <MenuPrimitive.Item
446
+ {...menuScope}
447
+ {...itemProps}
448
+ className={tx('menu.item', 'menu__item', {}, classNames)}
449
+ ref={forwardedRef}
450
+ />
451
+ );
425
452
  },
426
453
  );
427
454
 
@@ -611,7 +638,9 @@ export const DropdownMenu = {
611
638
  SubContent: DropdownMenuSubContent,
612
639
  };
613
640
 
614
- const useDropdownMenuMenuScope = useMenuScope;
641
+ type DropdownMenuScope = Scope;
642
+
643
+ const useDropdownMenuMenuScope: (scope?: DropdownMenuScope) => any = useMenuScope;
615
644
 
616
645
  export { createDropdownMenuScope, useDropdownMenuContext, useDropdownMenuMenuScope };
617
646
 
@@ -2,8 +2,6 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
@@ -2,15 +2,13 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { type PropsWithChildren, type ReactNode, useRef, useState } from 'react';
9
7
 
10
8
  import { faker } from '@dxos/random';
11
9
 
12
10
  import { withTheme } from '../../testing';
13
- import { Button } from '../Buttons';
11
+ import { Button } from '../Button';
14
12
 
15
13
  import { Popover } from './Popover';
16
14
 
@@ -35,7 +33,6 @@ const meta = {
35
33
  component: Popover.Root,
36
34
  render: DefaultStory,
37
35
  decorators: [withTheme],
38
- parameters: { chromatic: { disableSnapshot: false } },
39
36
  } satisfies Meta<typeof DefaultStory>;
40
37
 
41
38
  export default meta;
@@ -31,6 +31,7 @@ import React, {
31
31
  forwardRef,
32
32
  useCallback,
33
33
  useEffect,
34
+ useMemo,
34
35
  useRef,
35
36
  useState,
36
37
  } from 'react';
@@ -182,7 +183,7 @@ PopoverTrigger.displayName = TRIGGER_NAME;
182
183
  const VIRTUAL_TRIGGER_NAME = 'PopoverVirtualTrigger';
183
184
 
184
185
  interface PopoverVirtualTriggerProps {
185
- virtualRef: RefObject<PopoverTriggerElement>;
186
+ virtualRef: RefObject<PopoverTriggerElement | null>;
186
187
  }
187
188
 
188
189
  const PopoverVirtualTrigger = (props: ScopedProps<PopoverVirtualTriggerProps>) => {
@@ -194,7 +195,7 @@ const PopoverVirtualTrigger = (props: ScopedProps<PopoverVirtualTriggerProps>) =
194
195
  context.triggerRef.current = virtualRef.current;
195
196
  }
196
197
  });
197
- return <PopperPrimitive.Anchor {...popperScope} virtualRef={virtualRef} />;
198
+ return <PopperPrimitive.Anchor {...popperScope} virtualRef={virtualRef as RefObject<PopoverTriggerElement>} />;
198
199
  };
199
200
 
200
201
  PopoverVirtualTrigger.displayName = VIRTUAL_TRIGGER_NAME;
@@ -396,6 +397,7 @@ type PopoverContentImplElement = ElementRef<typeof PopperPrimitive.Content>;
396
397
  type FocusScopeProps = ComponentPropsWithoutRef<typeof FocusScope>;
397
398
  type DismissableLayerProps = ComponentPropsWithoutRef<typeof DismissableLayer>;
398
399
  type PopperContentProps = ThemedClassName<ComponentPropsWithoutRef<typeof PopperPrimitive.Content>>;
400
+
399
401
  interface PopoverContentImplProps
400
402
  extends Omit<PopperContentProps, 'onPlaced'>,
401
403
  Omit<DismissableLayerProps, 'onDismiss'> {
@@ -431,6 +433,7 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
431
433
  onFocusOutside,
432
434
  onInteractOutside,
433
435
  collisionPadding = 8,
436
+ collisionBoundary,
434
437
  classNames,
435
438
  ...contentProps
436
439
  } = props;
@@ -440,10 +443,23 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
440
443
  const elevation = useElevationContext();
441
444
  const safeCollisionPadding = useSafeCollisionPadding(collisionPadding);
442
445
 
443
- // Make sure the whole tree has focus guards as our `Popover` may be
444
- // the last element in the DOM (because of the `Portal`)
446
+ // Make sure the whole tree has focus guards as our `Popover` may be the last element in the DOM (because of the `Portal`)
445
447
  useFocusGuards();
446
448
 
449
+ // Check for the closest annotated collision boundary in the DOM tree.
450
+ const computedCollisionBoundary = useMemo(() => {
451
+ const closestBoundary = context.triggerRef.current?.closest(
452
+ '[data-popover-collision-boundary]',
453
+ ) as HTMLElement | null;
454
+ return closestBoundary
455
+ ? Array.isArray(collisionBoundary)
456
+ ? [closestBoundary, ...collisionBoundary]
457
+ : collisionBoundary
458
+ ? [closestBoundary, collisionBoundary]
459
+ : [closestBoundary]
460
+ : collisionBoundary;
461
+ }, [context.open, collisionBoundary, context.triggerRef.current]);
462
+
447
463
  return (
448
464
  <FocusScope
449
465
  asChild
@@ -468,11 +484,12 @@ const PopoverContentImpl = forwardRef<PopoverContentImplElement, PopoverContentI
468
484
  {...popperScope}
469
485
  {...contentProps}
470
486
  collisionPadding={safeCollisionPadding}
487
+ collisionBoundary={computedCollisionBoundary}
471
488
  className={tx('popover.content', 'popover', { elevation }, classNames)}
472
489
  ref={forwardedRef}
473
490
  style={{
474
491
  ...contentProps.style,
475
- // re-namespace exposed content custom properties
492
+ // Re-namespace exposed content custom properties.
476
493
  ...{
477
494
  '--radix-popover-content-transform-origin': 'var(--radix-popper-transform-origin)',
478
495
  '--radix-popover-content-available-width': 'var(--radix-popper-available-width)',
@@ -2,8 +2,6 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React, { type PropsWithChildren } from 'react';
9
7
 
@@ -36,11 +34,10 @@ const DefaultStory = ({ children }: PropsWithChildren<{}>) => {
36
34
  };
37
35
 
38
36
  const meta = {
39
- title: 'ui/react-ui-core/Scroll area',
37
+ title: 'ui/react-ui-core/ScrollArea',
40
38
  component: ScrollArea as any,
41
39
  render: DefaultStory,
42
40
  decorators: [withTheme],
43
- parameters: { chromatic: { disableSnapshot: false } },
44
41
  } satisfies Meta<typeof DefaultStory>;
45
42
 
46
43
  export default meta;
@@ -0,0 +1,69 @@
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.Root>
49
+ </div>
50
+ );
51
+ };
52
+
53
+ const meta = {
54
+ title: 'ui/react-ui-core/ScrollContainer',
55
+ component: ScrollContainer.Root,
56
+ render: DefaultStory,
57
+ decorators: [withTheme, withLayout({ container: 'column', classNames: 'is-[30rem]' })],
58
+ } satisfies Meta<typeof DefaultStory>;
59
+
60
+ export default meta;
61
+
62
+ type Story = StoryObj<typeof meta>;
63
+
64
+ export const Default: Story = {
65
+ args: {
66
+ pin: true,
67
+ fade: true,
68
+ },
69
+ };