@dxos/react-ui 0.8.4-main.ae835ea → 0.8.4-main.bc674ce

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 (253) 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 +3163 -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 +3163 -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 -2
  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/Main/Main.d.ts +44 -19
  59. package/dist/types/src/components/Main/Main.d.ts.map +1 -1
  60. package/dist/types/src/components/Main/Main.stories.d.ts +2 -1
  61. package/dist/types/src/components/Main/Main.stories.d.ts.map +1 -1
  62. package/dist/types/src/components/{Menus → Menu}/ContextMenu.d.ts +6 -6
  63. package/dist/types/src/components/Menu/ContextMenu.d.ts.map +1 -0
  64. package/dist/types/src/components/Menu/ContextMenu.stories.d.ts.map +1 -0
  65. package/dist/types/src/components/{Menus → Menu}/DropdownMenu.d.ts +3 -4
  66. package/dist/types/src/components/Menu/DropdownMenu.d.ts.map +1 -0
  67. package/dist/types/src/components/Menu/DropdownMenu.stories.d.ts.map +1 -0
  68. package/dist/types/src/components/Menu/index.d.ts.map +1 -0
  69. package/dist/types/src/components/Message/Message.d.ts +1 -1
  70. package/dist/types/src/components/Message/Message.d.ts.map +1 -1
  71. package/dist/types/src/components/Message/Message.stories.d.ts +1 -1
  72. package/dist/types/src/components/Message/Message.stories.d.ts.map +1 -1
  73. package/dist/types/src/components/Popover/Popover.d.ts +1 -1
  74. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  75. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts +9 -7
  76. package/dist/types/src/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  77. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts +4 -0
  78. package/dist/types/src/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  79. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts +39 -0
  80. package/dist/types/src/components/ScrollContainer/ScrollContainer.d.ts.map +1 -0
  81. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts +19 -0
  82. package/dist/types/src/components/ScrollContainer/ScrollContainer.stories.d.ts.map +1 -0
  83. package/dist/types/src/components/ScrollContainer/index.d.ts +2 -0
  84. package/dist/types/src/components/ScrollContainer/index.d.ts.map +1 -0
  85. package/dist/types/src/components/Select/Select.d.ts +10 -10
  86. package/dist/types/src/components/Select/Select.d.ts.map +1 -1
  87. package/dist/types/src/components/Separator/Separator.d.ts +1 -1
  88. package/dist/types/src/components/Tag/Tag.d.ts +1 -1
  89. package/dist/types/src/components/Tag/Tag.d.ts.map +1 -1
  90. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts +1 -2
  91. package/dist/types/src/components/ThemeProvider/ThemeProvider.d.ts.map +1 -1
  92. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts +1 -8
  93. package/dist/types/src/components/ThemeProvider/TranslationsProvider.d.ts.map +1 -1
  94. package/dist/types/src/components/ThemeProvider/index.d.ts +2 -1
  95. package/dist/types/src/components/ThemeProvider/index.d.ts.map +1 -1
  96. package/dist/types/src/components/Toast/Toast.d.ts +4 -4
  97. package/dist/types/src/components/Toolbar/Toolbar.d.ts +11 -11
  98. package/dist/types/src/components/Toolbar/Toolbar.d.ts.map +1 -1
  99. package/dist/types/src/components/Toolbar/Toolbar.stories.d.ts.map +1 -1
  100. package/dist/types/src/components/Tooltip/Tooltip.d.ts.map +1 -1
  101. package/dist/types/src/components/index.d.ts +5 -4
  102. package/dist/types/src/components/index.d.ts.map +1 -1
  103. package/dist/types/src/hooks/useDensityContext.d.ts +1 -1
  104. package/dist/types/src/hooks/useDensityContext.d.ts.map +1 -1
  105. package/dist/types/src/hooks/useElevationContext.d.ts +1 -1
  106. package/dist/types/src/hooks/useElevationContext.d.ts.map +1 -1
  107. package/dist/types/src/index.d.ts +1 -1
  108. package/dist/types/src/index.d.ts.map +1 -1
  109. package/dist/types/src/playground/Custom.stories.d.ts.map +1 -1
  110. package/dist/types/src/testing/decorators/index.d.ts +1 -1
  111. package/dist/types/src/testing/decorators/index.d.ts.map +1 -1
  112. package/dist/types/src/testing/decorators/withLayout.d.ts +3 -3
  113. package/dist/types/src/testing/decorators/withLayout.d.ts.map +1 -1
  114. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts +12 -0
  115. package/dist/types/src/testing/decorators/withLayoutVariants.d.ts.map +1 -0
  116. package/dist/types/src/testing/decorators/withTheme.d.ts.map +1 -1
  117. package/dist/types/src/util/index.d.ts +1 -2
  118. package/dist/types/src/util/index.d.ts.map +1 -1
  119. package/dist/types/tsconfig.tsbuildinfo +1 -1
  120. package/package.json +32 -25
  121. package/src/components/Avatars/Avatar.stories.tsx +2 -2
  122. package/src/components/Avatars/Avatar.tsx +1 -1
  123. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +1 -1
  124. package/src/components/{Buttons → Button}/Button.stories.tsx +2 -2
  125. package/src/components/{Buttons → Button}/Button.tsx +1 -1
  126. package/src/components/{Buttons → Button}/IconButton.tsx +19 -13
  127. package/src/components/{Buttons → Button}/Toggle.stories.tsx +5 -4
  128. package/src/components/Clipboard/CopyButton.tsx +4 -4
  129. package/src/components/DensityProvider/DensityProvider.tsx +1 -1
  130. package/src/components/{Dialogs → Dialog}/AlertDialog.stories.tsx +1 -1
  131. package/src/components/Dialog/Dialog.stories.tsx +97 -0
  132. package/src/components/{Dialogs → Dialog}/Dialog.tsx +140 -40
  133. package/src/components/ElevationProvider/ElevationProvider.tsx +1 -1
  134. package/src/components/Icon/Icon.stories.tsx +1 -1
  135. package/src/components/Icon/Icon.tsx +1 -1
  136. package/src/components/Input/Input.stories.tsx +2 -2
  137. package/src/components/Input/Input.tsx +13 -4
  138. package/src/components/{Lists → List}/List.stories.tsx +17 -13
  139. package/src/components/{Lists → List}/List.tsx +1 -1
  140. package/src/components/{Lists → List}/ListDropIndicator.tsx +1 -1
  141. package/src/components/Main/Main.stories.tsx +127 -20
  142. package/src/components/Main/Main.tsx +265 -58
  143. package/src/components/{Menus → Menu}/DropdownMenu.stories.tsx +1 -1
  144. package/src/components/{Menus → Menu}/DropdownMenu.tsx +79 -58
  145. package/src/components/Message/Message.stories.tsx +1 -1
  146. package/src/components/Message/Message.tsx +30 -5
  147. package/src/components/Popover/Popover.stories.tsx +1 -1
  148. package/src/components/Popover/Popover.tsx +52 -33
  149. package/src/components/ScrollArea/ScrollArea.stories.tsx +54 -4
  150. package/src/components/ScrollArea/ScrollArea.tsx +50 -4
  151. package/src/components/ScrollContainer/ScrollContainer.stories.tsx +70 -0
  152. package/src/components/ScrollContainer/ScrollContainer.tsx +233 -0
  153. package/src/components/ScrollContainer/index.ts +5 -0
  154. package/src/components/Select/Select.stories.tsx +2 -2
  155. package/src/components/Select/Select.tsx +4 -4
  156. package/src/components/Tag/Tag.stories.tsx +2 -2
  157. package/src/components/Tag/Tag.tsx +1 -1
  158. package/src/components/ThemeProvider/ThemeProvider.tsx +1 -3
  159. package/src/components/ThemeProvider/TranslationsProvider.tsx +1 -16
  160. package/src/components/ThemeProvider/index.ts +3 -3
  161. package/src/components/Toast/Toast.stories.tsx +1 -1
  162. package/src/components/Toolbar/Toolbar.stories.tsx +2 -4
  163. package/src/components/Toolbar/Toolbar.tsx +24 -9
  164. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  165. package/src/components/Tooltip/Tooltip.tsx +22 -20
  166. package/src/components/index.ts +5 -4
  167. package/src/hooks/useDensityContext.ts +1 -1
  168. package/src/hooks/useElevationContext.ts +1 -1
  169. package/src/index.ts +1 -1
  170. package/src/playground/Controls.stories.tsx +2 -2
  171. package/src/playground/Custom.stories.tsx +6 -8
  172. package/src/testing/decorators/index.ts +1 -1
  173. package/src/testing/decorators/withLayout.tsx +22 -15
  174. package/src/testing/decorators/{withSurfaceVariantsLayout.tsx → withLayoutVariants.tsx} +5 -5
  175. package/src/testing/decorators/withTheme.tsx +3 -2
  176. package/src/util/index.ts +2 -2
  177. package/dist/lib/browser/chunk-HUZZ56DW.mjs +0 -4509
  178. package/dist/lib/browser/chunk-HUZZ56DW.mjs.map +0 -7
  179. package/dist/lib/node-esm/chunk-OJLL6E2Z.mjs +0 -4511
  180. package/dist/lib/node-esm/chunk-OJLL6E2Z.mjs.map +0 -7
  181. package/dist/types/src/components/Buttons/Button.d.ts.map +0 -1
  182. package/dist/types/src/components/Buttons/Button.stories.d.ts.map +0 -1
  183. package/dist/types/src/components/Buttons/IconButton.d.ts.map +0 -1
  184. package/dist/types/src/components/Buttons/IconButton.stories.d.ts.map +0 -1
  185. package/dist/types/src/components/Buttons/Toggle.d.ts.map +0 -1
  186. package/dist/types/src/components/Buttons/Toggle.stories.d.ts +0 -13
  187. package/dist/types/src/components/Buttons/Toggle.stories.d.ts.map +0 -1
  188. package/dist/types/src/components/Buttons/ToggleGroup.d.ts.map +0 -1
  189. package/dist/types/src/components/Buttons/ToggleGroup.stories.d.ts.map +0 -1
  190. package/dist/types/src/components/Buttons/index.d.ts.map +0 -1
  191. package/dist/types/src/components/Dialogs/AlertDialog.d.ts.map +0 -1
  192. package/dist/types/src/components/Dialogs/AlertDialog.stories.d.ts.map +0 -1
  193. package/dist/types/src/components/Dialogs/Dialog.d.ts +0 -31
  194. package/dist/types/src/components/Dialogs/Dialog.d.ts.map +0 -1
  195. package/dist/types/src/components/Dialogs/Dialog.stories.d.ts.map +0 -1
  196. package/dist/types/src/components/Dialogs/index.d.ts.map +0 -1
  197. package/dist/types/src/components/Lists/List.d.ts.map +0 -1
  198. package/dist/types/src/components/Lists/List.stories.d.ts.map +0 -1
  199. package/dist/types/src/components/Lists/ListDropIndicator.d.ts.map +0 -1
  200. package/dist/types/src/components/Lists/Tree.d.ts.map +0 -1
  201. package/dist/types/src/components/Lists/Tree.stories.d.ts.map +0 -1
  202. package/dist/types/src/components/Lists/TreeDropIndicator.d.ts.map +0 -1
  203. package/dist/types/src/components/Lists/Treegrid.d.ts.map +0 -1
  204. package/dist/types/src/components/Lists/Treegrid.stories.d.ts.map +0 -1
  205. package/dist/types/src/components/Lists/index.d.ts.map +0 -1
  206. package/dist/types/src/components/Menus/ContextMenu.d.ts.map +0 -1
  207. package/dist/types/src/components/Menus/ContextMenu.stories.d.ts.map +0 -1
  208. package/dist/types/src/components/Menus/DropdownMenu.d.ts.map +0 -1
  209. package/dist/types/src/components/Menus/DropdownMenu.stories.d.ts.map +0 -1
  210. package/dist/types/src/components/Menus/index.d.ts.map +0 -1
  211. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts +0 -12
  212. package/dist/types/src/testing/decorators/withSurfaceVariantsLayout.d.ts.map +0 -1
  213. package/dist/types/src/util/ThemedClassName.d.ts +0 -5
  214. package/dist/types/src/util/ThemedClassName.d.ts.map +0 -1
  215. package/dist/types/src/util/domino.d.ts +0 -18
  216. package/dist/types/src/util/domino.d.ts.map +0 -1
  217. package/src/components/Dialogs/Dialog.stories.tsx +0 -67
  218. package/src/util/ThemedClassName.ts +0 -7
  219. package/src/util/domino.ts +0 -53
  220. /package/dist/types/src/components/{Buttons → Button}/Button.stories.d.ts +0 -0
  221. /package/dist/types/src/components/{Buttons → Button}/IconButton.stories.d.ts +0 -0
  222. /package/dist/types/src/components/{Buttons → Button}/Toggle.d.ts +0 -0
  223. /package/dist/types/src/components/{Buttons → Button}/index.d.ts +0 -0
  224. /package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.d.ts +0 -0
  225. /package/dist/types/src/components/{Dialogs → Dialog}/AlertDialog.stories.d.ts +0 -0
  226. /package/dist/types/src/components/{Dialogs → Dialog}/index.d.ts +0 -0
  227. /package/dist/types/src/components/{Lists → List}/List.stories.d.ts +0 -0
  228. /package/dist/types/src/components/{Lists → List}/ListDropIndicator.d.ts +0 -0
  229. /package/dist/types/src/components/{Lists → List}/Tree.d.ts +0 -0
  230. /package/dist/types/src/components/{Lists → List}/Tree.stories.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/dist/types/src/components/{Menus → Menu}/ContextMenu.stories.d.ts +0 -0
  236. /package/dist/types/src/components/{Menus → Menu}/DropdownMenu.stories.d.ts +0 -0
  237. /package/dist/types/src/components/{Menus → Menu}/index.d.ts +0 -0
  238. /package/src/components/{Buttons → Button}/IconButton.stories.tsx +0 -0
  239. /package/src/components/{Buttons → Button}/Toggle.tsx +0 -0
  240. /package/src/components/{Buttons → Button}/ToggleGroup.stories.tsx +0 -0
  241. /package/src/components/{Buttons → Button}/ToggleGroup.tsx +0 -0
  242. /package/src/components/{Buttons → Button}/index.ts +0 -0
  243. /package/src/components/{Dialogs → Dialog}/AlertDialog.tsx +0 -0
  244. /package/src/components/{Dialogs → Dialog}/index.ts +0 -0
  245. /package/src/components/{Lists → List}/Tree.stories.tsx +0 -0
  246. /package/src/components/{Lists → List}/Tree.tsx +0 -0
  247. /package/src/components/{Lists → List}/TreeDropIndicator.tsx +0 -0
  248. /package/src/components/{Lists → List}/Treegrid.stories.tsx +0 -0
  249. /package/src/components/{Lists → List}/Treegrid.tsx +0 -0
  250. /package/src/components/{Lists → List}/index.ts +0 -0
  251. /package/src/components/{Menus → Menu}/ContextMenu.stories.tsx +0 -0
  252. /package/src/components/{Menus → Menu}/ContextMenu.tsx +0 -0
  253. /package/src/components/{Menus → Menu}/index.ts +0 -0
@@ -22,8 +22,10 @@ import React, {
22
22
  useState,
23
23
  } from 'react';
24
24
 
25
+ import { addEventListener } from '@dxos/async';
25
26
  import { log } from '@dxos/log';
26
- import { useForwardedRef, useMediaQuery } from '@dxos/react-hooks';
27
+ import { useDynamicRef, useForwardedRef, useMediaQuery, useViewportResize } from '@dxos/react-hooks';
28
+ import { type MainStyleProps } from '@dxos/ui-theme';
27
29
 
28
30
  import { useThemeContext } from '../../hooks';
29
31
  import { type ThemedClassName } from '../../util';
@@ -31,22 +33,20 @@ import { type Label, toLocalizedString, useTranslation } from '../ThemeProvider'
31
33
 
32
34
  import { useSwipeToDismiss } from './useSwipeToDismiss';
33
35
 
36
+ const MAIN_NAME = 'Main';
34
37
  const MAIN_ROOT_NAME = 'MainRoot';
35
38
  const NAVIGATION_SIDEBAR_NAME = 'NavigationSidebar';
36
39
  const COMPLEMENTARY_SIDEBAR_NAME = 'ComplementarySidebar';
37
- const MAIN_NAME = 'Main';
38
- const GENERIC_CONSUMER_NAME = 'GenericConsumer';
40
+ const DRAWER_NAME = 'Drawer';
39
41
 
40
- type SidebarState = 'expanded' | 'collapsed' | 'closed';
41
-
42
- type MainContextValue = {
43
- resizing: boolean;
44
- navigationSidebarState: SidebarState;
45
- setNavigationSidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
46
- complementarySidebarState: SidebarState;
47
- setComplementarySidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
42
+ const handleOpenAutoFocus = (event: Event) => {
43
+ !document.body.hasAttribute('data-is-keyboard') && event.preventDefault();
48
44
  };
49
45
 
46
+ //
47
+ // Landmark
48
+ //
49
+
50
50
  const landmarkAttr = 'data-main-landmark';
51
51
 
52
52
  /**
@@ -72,35 +72,104 @@ const useLandmarkMover = (propsOnKeyDown: ComponentPropsWithoutRef<'div'>['onKey
72
72
  [propsOnKeyDown],
73
73
  );
74
74
 
75
- // TODO(thure): This was disconnected once before in #8818, if this should change again to support the browser
76
- // extension, please ensure the change doesn’t break web, desktop and mobile.
75
+ // TODO(thure): This was disconnected once before in #8818;
76
+ // if this should change again to support the browser extension, please ensure the change doesn’t break web, desktop and mobile.
77
77
  const focusableGroupAttrs = useFocusableGroup({ tabBehavior: 'limited', ignoreDefaultKeydown: { Tab: true } });
78
78
 
79
79
  return {
80
- onKeyDown: handleKeyDown,
81
80
  [landmarkAttr]: landmark,
82
81
  tabIndex: 0,
82
+ onKeyDown: handleKeyDown,
83
83
  ...focusableGroupAttrs,
84
84
  };
85
85
  };
86
86
 
87
+ // TODO(burdon): Better way to detect software keyboard on mobile?
88
+ const isFullscreen = () => {
89
+ const isMobile = window.innerHeight <= 1000;
90
+ return window.visualViewport && isMobile ? window.visualViewport.height < 700 : false;
91
+ };
92
+
93
+ /**
94
+ * Detects if drawer should be in full mode based on:
95
+ * - Device is mobile (width)
96
+ * - Visual viewport is constrained (keyboard visible)
97
+ */
98
+ const useDynamicDrawer = (consumerName: string) => {
99
+ const { drawerState, setDrawerState } = useSidebars(consumerName);
100
+ const drawersStateRef = useDynamicRef(drawerState);
101
+
102
+ const checkViewport = useCallback(() => {
103
+ if (window.visualViewport) {
104
+ document.documentElement.style.setProperty('--visual-viewport-height', `${window.visualViewport.height}px`);
105
+ }
106
+
107
+ if (drawersStateRef.current !== 'closed') {
108
+ setDrawerState(isFullscreen() ? 'full' : 'expanded');
109
+ }
110
+ }, [setDrawerState]);
111
+
112
+ // Check resize.
113
+ useViewportResize(checkViewport, [], 100);
114
+
115
+ // Check on window resize (for device orientation changes).
116
+ useEffect(() => addEventListener(window, 'resize', checkViewport), [checkViewport]);
117
+
118
+ return drawerState;
119
+ };
120
+
121
+ //
122
+ // Context
123
+ //
124
+
125
+ // TODO(burdon): Define collapsed state.
126
+ type SidebarState = 'expanded' | 'collapsed' | 'closed';
127
+ type DrawerState = 'expanded' | 'full' | 'closed';
128
+
129
+ type MainContextValue = {
130
+ resizing: boolean;
131
+
132
+ // Navigation
133
+ navigationSidebarState: SidebarState;
134
+ setNavigationSidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
135
+
136
+ // Complementary
137
+ complementarySidebarState: SidebarState;
138
+ setComplementarySidebarState: Dispatch<SetStateAction<SidebarState | undefined>>;
139
+
140
+ // Drawer
141
+ drawerState: DrawerState;
142
+ setDrawerState: Dispatch<SetStateAction<DrawerState | undefined>>;
143
+ };
144
+
87
145
  const [MainProvider, useMainContext] = createContext<MainContextValue>(MAIN_NAME, {
88
146
  resizing: false,
147
+
89
148
  navigationSidebarState: 'closed',
90
149
  setNavigationSidebarState: (_nextState) => {
91
- // TODO(burdon): Standardize with other context missing errors using raise.
92
- log.warn('Attempt to set sidebar state without initializing `MainRoot`');
150
+ log.warn('Not initialized');
93
151
  },
152
+
94
153
  complementarySidebarState: 'closed',
95
154
  setComplementarySidebarState: (_nextState) => {
96
- // TODO(burdon): Standardize with other context missing errors using raise.
97
- log.warn('Attempt to set sidebar state without initializing `MainRoot`');
155
+ log.warn('Not initialized');
156
+ },
157
+
158
+ drawerState: 'closed',
159
+ setDrawerState: (_nextState) => {
160
+ log.warn('Not initialized');
98
161
  },
99
162
  });
100
163
 
101
- const useSidebars = (consumerName = GENERIC_CONSUMER_NAME) => {
102
- const { setNavigationSidebarState, navigationSidebarState, setComplementarySidebarState, complementarySidebarState } =
103
- useMainContext(consumerName);
164
+ const useSidebars = (consumerName: string) => {
165
+ const {
166
+ navigationSidebarState,
167
+ setNavigationSidebarState,
168
+ complementarySidebarState,
169
+ setComplementarySidebarState,
170
+ drawerState,
171
+ setDrawerState,
172
+ } = useMainContext(consumerName);
104
173
 
105
174
  return {
106
175
  navigationSidebarState,
@@ -112,6 +181,7 @@ const useSidebars = (consumerName = GENERIC_CONSUMER_NAME) => {
112
181
  openNavigationSidebar: useCallback(() => setNavigationSidebarState('expanded'), []),
113
182
  collapseNavigationSidebar: useCallback(() => setNavigationSidebarState('collapsed'), []),
114
183
  closeNavigationSidebar: useCallback(() => setNavigationSidebarState('closed'), []),
184
+
115
185
  complementarySidebarState,
116
186
  setComplementarySidebarState,
117
187
  toggleComplementarySidebar: useCallback(
@@ -121,27 +191,49 @@ const useSidebars = (consumerName = GENERIC_CONSUMER_NAME) => {
121
191
  openComplementarySidebar: useCallback(() => setComplementarySidebarState('expanded'), []),
122
192
  collapseComplementarySidebar: useCallback(() => setComplementarySidebarState('collapsed'), []),
123
193
  closeComplementarySidebar: useCallback(() => setComplementarySidebarState('closed'), []),
194
+
195
+ drawerState,
196
+ setDrawerState,
197
+ toggleDrawer: useCallback(
198
+ () => setDrawerState(drawerState === 'closed' ? (isFullscreen() ? 'full' : 'expanded') : 'closed'),
199
+ [drawerState, setDrawerState],
200
+ ),
201
+ openDrawer: useCallback(() => setDrawerState('expanded'), []),
202
+ closeDrawer: useCallback(() => setDrawerState('closed'), []),
124
203
  };
125
204
  };
126
205
 
206
+ //
207
+ // Root
208
+ //
209
+
127
210
  type MainRootProps = PropsWithChildren<{
128
211
  navigationSidebarState?: SidebarState;
129
212
  defaultNavigationSidebarState?: SidebarState;
130
213
  onNavigationSidebarStateChange?: (nextState: SidebarState) => void;
214
+
131
215
  complementarySidebarState?: SidebarState;
132
216
  defaultComplementarySidebarState?: SidebarState;
133
217
  onComplementarySidebarStateChange?: (nextState: SidebarState) => void;
134
- }>;
135
218
 
136
- const resizeDebounce = 3000;
219
+ drawerState?: DrawerState;
220
+ defaultDrawerState?: DrawerState;
221
+ onDrawerStateChange?: (nextState: DrawerState) => void;
222
+ }>;
137
223
 
138
224
  const MainRoot = ({
139
225
  navigationSidebarState: propsNavigationSidebarState,
140
- defaultNavigationSidebarState,
226
+ defaultNavigationSidebarState = 'closed',
141
227
  onNavigationSidebarStateChange,
228
+
142
229
  complementarySidebarState: propsComplementarySidebarState,
143
- defaultComplementarySidebarState,
230
+ defaultComplementarySidebarState = 'closed',
144
231
  onComplementarySidebarStateChange,
232
+
233
+ drawerState: propsDrawerState,
234
+ defaultDrawerState = 'closed',
235
+ onDrawerStateChange,
236
+
145
237
  children,
146
238
  ...props
147
239
  }: MainRootProps) => {
@@ -158,25 +250,29 @@ const MainRoot = ({
158
250
  defaultProp: defaultComplementarySidebarState,
159
251
  onChange: onComplementarySidebarStateChange,
160
252
  });
253
+ const [drawerState = 'closed', setDrawerState] = useControllableState<DrawerState>({
254
+ prop: propsDrawerState,
255
+ defaultProp: defaultDrawerState,
256
+ onChange: onDrawerStateChange,
257
+ });
161
258
 
162
259
  const [resizing, setResizing] = useState(false);
163
260
  const resizeInterval = useRef<ReturnType<typeof setTimeout> | null>(null);
261
+ useEffect(
262
+ () =>
263
+ addEventListener(window, 'resize', () => {
264
+ setResizing(true);
265
+ if (resizeInterval.current) {
266
+ clearTimeout(resizeInterval.current);
267
+ }
164
268
 
165
- const handleResize = useCallback(() => {
166
- setResizing(true);
167
- if (resizeInterval.current) {
168
- clearTimeout(resizeInterval.current);
169
- }
170
- resizeInterval.current = setTimeout(() => {
171
- setResizing(false);
172
- resizeInterval.current = null;
173
- }, resizeDebounce);
174
- }, []);
175
-
176
- useEffect(() => {
177
- window.addEventListener('resize', handleResize);
178
- return () => window.removeEventListener('resize', handleResize);
179
- }, [handleResize]);
269
+ resizeInterval.current = setTimeout(() => {
270
+ setResizing(false);
271
+ resizeInterval.current = null;
272
+ }, 3_000);
273
+ }),
274
+ [],
275
+ );
180
276
 
181
277
  return (
182
278
  <MainProvider
@@ -186,6 +282,8 @@ const MainRoot = ({
186
282
  setNavigationSidebarState,
187
283
  complementarySidebarState,
188
284
  setComplementarySidebarState,
285
+ drawerState,
286
+ setDrawerState,
189
287
  }}
190
288
  resizing={resizing}
191
289
  >
@@ -196,9 +294,9 @@ const MainRoot = ({
196
294
 
197
295
  MainRoot.displayName = MAIN_ROOT_NAME;
198
296
 
199
- const handleOpenAutoFocus = (event: Event) => {
200
- !document.body.hasAttribute('data-is-keyboard') && event.preventDefault();
201
- };
297
+ //
298
+ // Sidebar
299
+ //
202
300
 
203
301
  type MainSidebarProps = ThemedClassName<ComponentPropsWithRef<typeof DialogContent>> & {
204
302
  swipeToDismiss?: boolean;
@@ -219,9 +317,11 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
219
317
  const { t } = useTranslation();
220
318
  const ref = useForwardedRef(forwardedRef);
221
319
  const noopRef = useRef(null);
320
+
222
321
  useSwipeToDismiss(swipeToDismiss ? ref : noopRef, {
223
322
  onDismiss: () => onStateChange?.('closed'),
224
323
  });
324
+
225
325
  // NOTE(thure): This is a workaround for something further down the tree grabbing focus on Escape. Adding this
226
326
  // intervention to `Tabs.Root` or `Tabs.Tabpenel` instances is somehow ineffectual.
227
327
  const handleKeyDown = useCallback(
@@ -236,6 +336,7 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
236
336
  },
237
337
  [props.onKeyDown],
238
338
  );
339
+
239
340
  const Root = isLg ? Primitive.div : DialogContent;
240
341
 
241
342
  return (
@@ -243,13 +344,13 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
243
344
  {!isLg && <DialogTitle className='sr-only'>{toLocalizedString(label, t)}</DialogTitle>}
244
345
  <Root
245
346
  {...(!isLg && { forceMount: true, tabIndex: -1, onOpenAutoFocus: onOpenAutoFocus ?? handleOpenAutoFocus })}
347
+ {...(state === 'closed' && { inert: true })}
246
348
  {...props}
247
349
  data-side={side === 'inline-end' ? 'ie' : 'is'}
248
350
  data-state={state}
249
351
  data-resizing={resizing ? 'true' : 'false'}
250
352
  className={tx('main.sidebar', 'main__sidebar', {}, classNames)}
251
353
  onKeyDownCapture={handleKeyDown}
252
- {...(state === 'closed' && { inert: true })}
253
354
  ref={ref}
254
355
  >
255
356
  {children}
@@ -259,6 +360,10 @@ const MainSidebar = forwardRef<HTMLDivElement, MainSidebarProps>(
259
360
  },
260
361
  );
261
362
 
363
+ //
364
+ // Navigation Sidebar
365
+ //
366
+
262
367
  type MainNavigationSidebarProps = Omit<MainSidebarProps, 'expanded' | 'side'>;
263
368
 
264
369
  const MainNavigationSidebar = forwardRef<HTMLDivElement, MainNavigationSidebarProps>((props, forwardedRef) => {
@@ -280,6 +385,10 @@ const MainNavigationSidebar = forwardRef<HTMLDivElement, MainNavigationSidebarPr
280
385
 
281
386
  MainNavigationSidebar.displayName = NAVIGATION_SIDEBAR_NAME;
282
387
 
388
+ //
389
+ // Complementary Sidebar
390
+ //
391
+
283
392
  type MainComplementarySidebarProps = Omit<MainSidebarProps, 'expanded' | 'side'>;
284
393
 
285
394
  const MainComplementarySidebar = forwardRef<HTMLDivElement, MainComplementarySidebarProps>((props, forwardedRef) => {
@@ -300,20 +409,104 @@ const MainComplementarySidebar = forwardRef<HTMLDivElement, MainComplementarySid
300
409
  );
301
410
  });
302
411
 
303
- MainNavigationSidebar.displayName = NAVIGATION_SIDEBAR_NAME;
412
+ MainComplementarySidebar.displayName = COMPLEMENTARY_SIDEBAR_NAME;
304
413
 
305
- type MainProps = ThemedClassName<ComponentPropsWithRef<typeof Primitive.div>> & {
306
- asChild?: boolean;
307
- bounce?: boolean;
308
- handlesFocus?: boolean;
414
+ //
415
+ // Drawer
416
+ //
417
+
418
+ type MainDrawerProps = ThemedClassName<ComponentPropsWithRef<typeof DialogContent>> & {
419
+ swipeToDismiss?: boolean;
420
+ state?: DrawerState;
421
+ resizing?: boolean;
422
+ onStateChange?: (nextState: DrawerState) => void;
423
+ label: Label;
309
424
  };
310
425
 
311
- const MainContent = forwardRef<HTMLDivElement, MainProps>(
312
- ({ asChild, classNames, bounce, handlesFocus, children, role, ...props }: MainProps, forwardedRef) => {
313
- const { navigationSidebarState, complementarySidebarState } = useMainContext(MAIN_NAME);
426
+ const MainDrawer = forwardRef<HTMLDivElement, MainDrawerProps>(
427
+ (
428
+ { classNames, children, swipeToDismiss, onOpenAutoFocus, state, resizing, onStateChange, label, ...props },
429
+ forwardedRef,
430
+ ) => {
431
+ const [isLg] = useMediaQuery('lg');
314
432
  const { tx } = useThemeContext();
315
- const Root = asChild ? Slot : role ? 'div' : 'main';
433
+ const { t } = useTranslation();
434
+ const ref = useForwardedRef(forwardedRef);
435
+ const noopRef = useRef(null);
436
+
437
+ // TODO(burdon): Implement vertical swipe-to-dismiss for drawer.
438
+ useSwipeToDismiss(swipeToDismiss ? ref : noopRef, {
439
+ onDismiss: () => onStateChange?.('closed'),
440
+ });
441
+
442
+ const handleKeyDown = useCallback(
443
+ (event: KeyboardEvent<HTMLDivElement>) => {
444
+ const focusGroupParent = (event.target as HTMLElement).closest('[data-tabster]');
445
+ if (event.key === 'Escape' && focusGroupParent) {
446
+ event.preventDefault();
447
+ event.stopPropagation();
448
+ (focusGroupParent as HTMLElement).focus();
449
+ }
450
+ props.onKeyDown?.(event);
451
+ },
452
+ [props.onKeyDown],
453
+ );
316
454
 
455
+ return (
456
+ <Primitive.div
457
+ {...(state === 'closed' && { inert: true })}
458
+ {...(!isLg && { forceMount: true, tabIndex: -1, onOpenAutoFocus: onOpenAutoFocus ?? handleOpenAutoFocus })}
459
+ {...props}
460
+ role='region'
461
+ aria-label={toLocalizedString(label, t)}
462
+ data-state={state}
463
+ data-resizing={resizing ? 'true' : 'false'}
464
+ className={tx('main.drawer', 'main__drawer', {}, classNames)}
465
+ onKeyDownCapture={handleKeyDown}
466
+ ref={ref}
467
+ >
468
+ {children}
469
+ </Primitive.div>
470
+ );
471
+ },
472
+ );
473
+
474
+ type MainDrawerRootProps = Omit<MainDrawerProps, 'state' | 'resizing' | 'onStateChange'>;
475
+
476
+ const MainDrawerRoot = forwardRef<HTMLDivElement, MainDrawerRootProps>((props, forwardedRef) => {
477
+ const { drawerState, setDrawerState, resizing } = useMainContext(DRAWER_NAME);
478
+ const mover = useLandmarkMover(props.onKeyDown, '3');
479
+
480
+ return (
481
+ <MainDrawer
482
+ {...mover}
483
+ {...props}
484
+ resizing={resizing}
485
+ state={drawerState}
486
+ onStateChange={setDrawerState}
487
+ ref={forwardedRef}
488
+ />
489
+ );
490
+ });
491
+
492
+ MainDrawerRoot.displayName = DRAWER_NAME;
493
+
494
+ //
495
+ // Content
496
+ //
497
+
498
+ type MainContentProps = ThemedClassName<
499
+ ComponentPropsWithRef<typeof Primitive.div> &
500
+ MainStyleProps & {
501
+ asChild?: boolean;
502
+ }
503
+ >;
504
+
505
+ const MainContent = forwardRef<HTMLDivElement, MainContentProps>(
506
+ ({ asChild, classNames, bounce, handlesFocus, children, role, ...props }: MainContentProps, forwardedRef) => {
507
+ const { navigationSidebarState, complementarySidebarState, drawerState } = useMainContext(MAIN_NAME);
508
+ const { tx } = useThemeContext();
509
+ const Root = asChild ? Slot : role ? 'div' : 'main';
317
510
  const mover = useLandmarkMover(props.onKeyDown, '1');
318
511
 
319
512
  return (
@@ -323,6 +516,7 @@ const MainContent = forwardRef<HTMLDivElement, MainProps>(
323
516
  {...props}
324
517
  data-sidebar-inline-start-state={navigationSidebarState}
325
518
  data-sidebar-inline-end-state={complementarySidebarState}
519
+ data-drawer-state={drawerState}
326
520
  data-handles-focus={handlesFocus}
327
521
  className={tx('main.content', 'main', { bounce, handlesFocus }, classNames)}
328
522
  ref={forwardedRef}
@@ -335,7 +529,7 @@ const MainContent = forwardRef<HTMLDivElement, MainProps>(
335
529
 
336
530
  MainContent.displayName = MAIN_NAME;
337
531
 
338
- type MainOverlayProps = ThemedClassName<Omit<ComponentPropsWithRef<typeof Primitive.div>, 'children'>>;
532
+ type MainOverlayProps = ThemedClassName<Omit<ComponentPropsWithRef<typeof Primitive.div>, 'children' | 'onClick'>>;
339
533
 
340
534
  const MainOverlay = forwardRef<HTMLDivElement, MainOverlayProps>(({ classNames, ...props }, forwardedRef) => {
341
535
  const [isLg] = useMediaQuery('lg');
@@ -344,15 +538,19 @@ const MainOverlay = forwardRef<HTMLDivElement, MainOverlayProps>(({ classNames,
344
538
  const { tx } = useThemeContext();
345
539
  return (
346
540
  <div
541
+ {...props}
347
542
  onClick={() => {
348
543
  setNavigationSidebarState('collapsed');
349
544
  setComplementarySidebarState('collapsed');
350
545
  }}
351
- {...props}
352
546
  className={tx(
353
547
  'main.overlay',
354
548
  'main__overlay',
355
- { isLg, inlineStartSidebarOpen: navigationSidebarState, inlineEndSidebarOpen: complementarySidebarState },
549
+ {
550
+ isLg,
551
+ inlineStartSidebarOpen: navigationSidebarState,
552
+ inlineEndSidebarOpen: complementarySidebarState,
553
+ },
356
554
  classNames,
357
555
  )}
358
556
  data-state={navigationSidebarState === 'expanded' || complementarySidebarState === 'expanded' ? 'open' : 'closed'}
@@ -368,8 +566,17 @@ export const Main = {
368
566
  Overlay: MainOverlay,
369
567
  NavigationSidebar: MainNavigationSidebar,
370
568
  ComplementarySidebar: MainComplementarySidebar,
569
+ Drawer: MainDrawerRoot,
371
570
  };
372
571
 
373
- export { useMainContext, useSidebars, useLandmarkMover };
572
+ export { useMainContext, useSidebars, useLandmarkMover, useDynamicDrawer };
374
573
 
375
- export type { MainRootProps, MainProps, MainOverlayProps, MainNavigationSidebarProps, SidebarState };
574
+ export type {
575
+ MainRootProps,
576
+ MainContentProps,
577
+ MainOverlayProps,
578
+ MainNavigationSidebarProps,
579
+ MainDrawerRootProps,
580
+ SidebarState,
581
+ DrawerState,
582
+ };
@@ -6,7 +6,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import React, { useRef, 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 { DropdownMenu } from './DropdownMenu';
12
12