@dxos/plugin-simple-layout 0.0.0 → 0.8.4-main.03d5cd7b56

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 (261) hide show
  1. package/dist/lib/neutral/SimpleLayoutPlugin.mjs +52 -0
  2. package/dist/lib/neutral/SimpleLayoutPlugin.mjs.map +7 -0
  3. package/dist/lib/neutral/app-graph-builder-EYQKLRRP.mjs +21 -0
  4. package/dist/lib/neutral/app-graph-builder-EYQKLRRP.mjs.map +7 -0
  5. package/dist/lib/neutral/capabilities/index.mjs +21 -0
  6. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  7. package/dist/lib/neutral/chunk-7UDV3JDT.mjs +22 -0
  8. package/dist/lib/neutral/chunk-7UDV3JDT.mjs.map +7 -0
  9. package/dist/lib/neutral/chunk-AMTEDJHG.mjs +19 -0
  10. package/dist/lib/neutral/chunk-AMTEDJHG.mjs.map +7 -0
  11. package/dist/lib/neutral/chunk-FD2CAY4Q.mjs +26 -0
  12. package/dist/lib/neutral/chunk-FD2CAY4Q.mjs.map +7 -0
  13. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  14. package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
  15. package/dist/lib/neutral/chunk-XVUAQHKU.mjs +8 -0
  16. package/dist/lib/neutral/chunk-XVUAQHKU.mjs.map +7 -0
  17. package/dist/lib/neutral/close-WKMURGUB.mjs +35 -0
  18. package/dist/lib/neutral/close-WKMURGUB.mjs.map +7 -0
  19. package/dist/lib/neutral/components/index.mjs +924 -0
  20. package/dist/lib/neutral/components/index.mjs.map +7 -0
  21. package/dist/lib/neutral/hooks/index.mjs +332 -0
  22. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  23. package/dist/lib/neutral/index.mjs +14 -0
  24. package/dist/lib/neutral/index.mjs.map +7 -0
  25. package/dist/lib/neutral/meta.json +1 -0
  26. package/dist/lib/neutral/meta.mjs +8 -0
  27. package/dist/lib/neutral/meta.mjs.map +7 -0
  28. package/dist/lib/neutral/open-XI2T7D5O.mjs +49 -0
  29. package/dist/lib/neutral/open-XI2T7D5O.mjs.map +7 -0
  30. package/dist/lib/neutral/operation-handler-EAIE7KPR.mjs +13 -0
  31. package/dist/lib/neutral/operation-handler-EAIE7KPR.mjs.map +7 -0
  32. package/dist/lib/neutral/operations/index.mjs +8 -0
  33. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  34. package/dist/lib/neutral/plugin.mjs +16 -0
  35. package/dist/lib/neutral/plugin.mjs.map +7 -0
  36. package/dist/lib/neutral/react-root-VE265VX4.mjs +18 -0
  37. package/dist/lib/neutral/react-root-VE265VX4.mjs.map +7 -0
  38. package/dist/lib/neutral/react-surface-REZMYKQV.mjs +46 -0
  39. package/dist/lib/neutral/react-surface-REZMYKQV.mjs.map +7 -0
  40. package/dist/lib/neutral/revert-workspace-ST6NZUNG.mjs +22 -0
  41. package/dist/lib/neutral/revert-workspace-ST6NZUNG.mjs.map +7 -0
  42. package/dist/lib/neutral/set-6ZRLWPJS.mjs +22 -0
  43. package/dist/lib/neutral/set-6ZRLWPJS.mjs.map +7 -0
  44. package/dist/lib/neutral/set-layout-mode-L22HRCKS.mjs +13 -0
  45. package/dist/lib/neutral/set-layout-mode-L22HRCKS.mjs.map +7 -0
  46. package/dist/lib/neutral/spotlight-dismiss-EIYW5E7M.mjs +58 -0
  47. package/dist/lib/neutral/spotlight-dismiss-EIYW5E7M.mjs.map +7 -0
  48. package/dist/lib/neutral/state-7NXKBLPY.mjs +47 -0
  49. package/dist/lib/neutral/state-7NXKBLPY.mjs.map +7 -0
  50. package/dist/lib/neutral/switch-workspace-PYWPTMFO.mjs +25 -0
  51. package/dist/lib/neutral/switch-workspace-PYWPTMFO.mjs.map +7 -0
  52. package/dist/lib/neutral/translations.mjs +36 -0
  53. package/dist/lib/neutral/translations.mjs.map +7 -0
  54. package/dist/lib/neutral/types/index.mjs +10 -0
  55. package/dist/lib/neutral/types/index.mjs.map +7 -0
  56. package/dist/lib/neutral/update-complementary-HKWF5OXT.mjs +33 -0
  57. package/dist/lib/neutral/update-complementary-HKWF5OXT.mjs.map +7 -0
  58. package/dist/lib/neutral/update-dialog-P4ASXCE7.mjs +30 -0
  59. package/dist/lib/neutral/update-dialog-P4ASXCE7.mjs.map +7 -0
  60. package/dist/lib/neutral/update-popover-REAKC2GN.mjs +34 -0
  61. package/dist/lib/neutral/update-popover-REAKC2GN.mjs.map +7 -0
  62. package/dist/lib/neutral/update-sidebar-O5SQPR6Q.mjs +12 -0
  63. package/dist/lib/neutral/update-sidebar-O5SQPR6Q.mjs.map +7 -0
  64. package/dist/lib/neutral/url-handler-GZXUUAHD.mjs +129 -0
  65. package/dist/lib/neutral/url-handler-GZXUUAHD.mjs.map +7 -0
  66. package/dist/types/src/SimpleLayoutPlugin.d.ts +8 -0
  67. package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -0
  68. package/dist/types/src/capabilities/app-graph-builder.d.ts +6 -0
  69. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  70. package/dist/types/src/capabilities/index.d.ts +22 -0
  71. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  72. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  73. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  74. package/dist/types/src/capabilities/react-root.d.ts +9 -0
  75. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  76. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  77. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  78. package/dist/types/src/capabilities/spotlight-dismiss.d.ts +14 -0
  79. package/dist/types/src/capabilities/spotlight-dismiss.d.ts.map +1 -0
  80. package/dist/types/src/capabilities/state.d.ts +19 -0
  81. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  82. package/dist/types/src/capabilities/url-handler.d.ts +12 -0
  83. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -0
  84. package/dist/types/src/components/ContentError.stories.d.ts +46 -0
  85. package/dist/types/src/components/ContentError.stories.d.ts.map +1 -0
  86. package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts +19 -0
  87. package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts.map +1 -0
  88. package/dist/types/src/components/DebugOverlay/index.d.ts +2 -0
  89. package/dist/types/src/components/DebugOverlay/index.d.ts.map +1 -0
  90. package/dist/types/src/components/Dialog/Dialog.d.ts +3 -0
  91. package/dist/types/src/components/Dialog/Dialog.d.ts.map +1 -0
  92. package/dist/types/src/components/Dialog/index.d.ts +2 -0
  93. package/dist/types/src/components/Dialog/index.d.ts.map +1 -0
  94. package/dist/types/src/components/Home/Home.d.ts +7 -0
  95. package/dist/types/src/components/Home/Home.d.ts.map +1 -0
  96. package/dist/types/src/components/Home/index.d.ts +2 -0
  97. package/dist/types/src/components/Home/index.d.ts.map +1 -0
  98. package/dist/types/src/components/Loading/Loading.d.ts +3 -0
  99. package/dist/types/src/components/Loading/Loading.d.ts.map +1 -0
  100. package/dist/types/src/components/Loading/Loading.stories.d.ts +13 -0
  101. package/dist/types/src/components/Loading/Loading.stories.d.ts.map +1 -0
  102. package/dist/types/src/components/Loading/index.d.ts +2 -0
  103. package/dist/types/src/components/Loading/index.d.ts.map +1 -0
  104. package/dist/types/src/components/MobileLayout/MobileLayout.d.ts +35 -0
  105. package/dist/types/src/components/MobileLayout/MobileLayout.d.ts.map +1 -0
  106. package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts +7 -0
  107. package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts.map +1 -0
  108. package/dist/types/src/components/MobileLayout/index.d.ts +2 -0
  109. package/dist/types/src/components/MobileLayout/index.d.ts.map +1 -0
  110. package/dist/types/src/components/NavBranch/NavBranch.d.ts +11 -0
  111. package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -0
  112. package/dist/types/src/components/NavBranch/index.d.ts +2 -0
  113. package/dist/types/src/components/NavBranch/index.d.ts.map +1 -0
  114. package/dist/types/src/components/Popover/Popover.d.ts +4 -0
  115. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -0
  116. package/dist/types/src/components/Popover/index.d.ts +2 -0
  117. package/dist/types/src/components/Popover/index.d.ts.map +1 -0
  118. package/dist/types/src/components/SimpleLayout/AppBar.d.ts +24 -0
  119. package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -0
  120. package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts +54 -0
  121. package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts.map +1 -0
  122. package/dist/types/src/components/SimpleLayout/Drawer.d.ts +6 -0
  123. package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -0
  124. package/dist/types/src/components/SimpleLayout/Main.d.ts +6 -0
  125. package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -0
  126. package/dist/types/src/components/SimpleLayout/NavBar.d.ts +16 -0
  127. package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -0
  128. package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +49 -0
  129. package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts.map +1 -0
  130. package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts +3 -0
  131. package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts.map +1 -0
  132. package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts +48 -0
  133. package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -0
  134. package/dist/types/src/components/SimpleLayout/index.d.ts +5 -0
  135. package/dist/types/src/components/SimpleLayout/index.d.ts.map +1 -0
  136. package/dist/types/src/components/hooks.d.ts +7 -0
  137. package/dist/types/src/components/hooks.d.ts.map +1 -0
  138. package/dist/types/src/components/index.d.ts +8 -0
  139. package/dist/types/src/components/index.d.ts.map +1 -0
  140. package/dist/types/src/hooks/actions.d.ts +19 -0
  141. package/dist/types/src/hooks/actions.d.ts.map +1 -0
  142. package/dist/types/src/hooks/index.d.ts +7 -0
  143. package/dist/types/src/hooks/index.d.ts.map +1 -0
  144. package/dist/types/src/hooks/useAppBarProps.d.ts +7 -0
  145. package/dist/types/src/hooks/useAppBarProps.d.ts.map +1 -0
  146. package/dist/types/src/hooks/useCompanions.d.ts +12 -0
  147. package/dist/types/src/hooks/useCompanions.d.ts.map +1 -0
  148. package/dist/types/src/hooks/useDrawerActions.d.ts +13 -0
  149. package/dist/types/src/hooks/useDrawerActions.d.ts.map +1 -0
  150. package/dist/types/src/hooks/useNavbarActions.d.ts +14 -0
  151. package/dist/types/src/hooks/useNavbarActions.d.ts.map +1 -0
  152. package/dist/types/src/hooks/useSimpleLayoutState.d.ts +7 -0
  153. package/dist/types/src/hooks/useSimpleLayoutState.d.ts.map +1 -0
  154. package/dist/types/src/index.d.ts +3 -0
  155. package/dist/types/src/index.d.ts.map +1 -0
  156. package/dist/types/src/meta.d.ts +3 -0
  157. package/dist/types/src/meta.d.ts.map +1 -0
  158. package/dist/types/src/operations/close.d.ts +5 -0
  159. package/dist/types/src/operations/close.d.ts.map +1 -0
  160. package/dist/types/src/operations/index.d.ts +3 -0
  161. package/dist/types/src/operations/index.d.ts.map +1 -0
  162. package/dist/types/src/operations/open.d.ts +5 -0
  163. package/dist/types/src/operations/open.d.ts.map +1 -0
  164. package/dist/types/src/operations/revert-workspace.d.ts +5 -0
  165. package/dist/types/src/operations/revert-workspace.d.ts.map +1 -0
  166. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  167. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  168. package/dist/types/src/operations/set.d.ts +5 -0
  169. package/dist/types/src/operations/set.d.ts.map +1 -0
  170. package/dist/types/src/operations/state-access.d.ts +8 -0
  171. package/dist/types/src/operations/state-access.d.ts.map +1 -0
  172. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  173. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  174. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  175. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  176. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  177. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  178. package/dist/types/src/operations/update-popover.d.ts +5 -0
  179. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  180. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  181. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  182. package/dist/types/src/plugin.d.ts +4 -0
  183. package/dist/types/src/plugin.d.ts.map +1 -0
  184. package/dist/types/src/translations.d.ts +32 -0
  185. package/dist/types/src/translations.d.ts.map +1 -0
  186. package/dist/types/src/types/SimpleLayoutCapabilities.d.ts +38 -0
  187. package/dist/types/src/types/SimpleLayoutCapabilities.d.ts.map +1 -0
  188. package/dist/types/src/types/SimpleLayoutEvents.d.ts +4 -0
  189. package/dist/types/src/types/SimpleLayoutEvents.d.ts.map +1 -0
  190. package/dist/types/src/types/index.d.ts +3 -0
  191. package/dist/types/src/types/index.d.ts.map +1 -0
  192. package/dist/types/tsconfig.tsbuildinfo +1 -0
  193. package/package.json +94 -29
  194. package/src/SimpleLayoutPlugin.ts +39 -11
  195. package/src/capabilities/app-graph-builder.ts +21 -0
  196. package/src/capabilities/index.ts +13 -3
  197. package/src/capabilities/operation-handler.ts +14 -0
  198. package/src/capabilities/{react-root/react-root.tsx → react-root.tsx} +4 -4
  199. package/src/capabilities/react-surface.tsx +50 -0
  200. package/src/{hooks/useSpotlightDismiss.ts → capabilities/spotlight-dismiss.ts} +31 -40
  201. package/src/capabilities/state.tsx +51 -0
  202. package/src/capabilities/url-handler.ts +164 -0
  203. package/src/components/ContentError.stories.tsx +9 -8
  204. package/src/components/DebugOverlay/DebugOverlay.tsx +96 -0
  205. package/src/components/DebugOverlay/index.ts +5 -0
  206. package/src/components/Dialog/Dialog.tsx +26 -15
  207. package/src/components/Home/Home.tsx +80 -80
  208. package/src/components/{ContentLoading.stories.tsx → Loading/Loading.stories.tsx} +5 -5
  209. package/src/components/{ContentLoading.tsx → Loading/Loading.tsx} +2 -2
  210. package/src/components/Loading/index.ts +5 -0
  211. package/src/components/MobileLayout/MobileLayout.stories.tsx +133 -0
  212. package/src/components/MobileLayout/MobileLayout.tsx +372 -0
  213. package/src/components/MobileLayout/index.ts +5 -0
  214. package/src/components/NavBranch/NavBranch.tsx +124 -0
  215. package/src/components/NavBranch/index.ts +5 -0
  216. package/src/components/Popover/Popover.tsx +49 -27
  217. package/src/components/SimpleLayout/AppBar.stories.tsx +144 -0
  218. package/src/components/SimpleLayout/AppBar.tsx +94 -0
  219. package/src/components/SimpleLayout/Drawer.tsx +104 -0
  220. package/src/components/SimpleLayout/Main.tsx +54 -61
  221. package/src/components/SimpleLayout/NavBar.stories.tsx +164 -0
  222. package/src/components/SimpleLayout/NavBar.tsx +19 -83
  223. package/src/components/SimpleLayout/SimpleLayout.stories.tsx +46 -63
  224. package/src/components/SimpleLayout/SimpleLayout.tsx +45 -8
  225. package/src/components/SimpleLayout/index.ts +3 -0
  226. package/src/components/hooks.ts +26 -0
  227. package/src/components/index.ts +4 -1
  228. package/src/hooks/actions.ts +86 -0
  229. package/src/hooks/index.ts +6 -1
  230. package/src/hooks/useAppBarProps.ts +95 -0
  231. package/src/hooks/useCompanions.ts +22 -0
  232. package/src/hooks/useDrawerActions.ts +100 -0
  233. package/src/hooks/useNavbarActions.ts +87 -0
  234. package/src/hooks/useSimpleLayoutState.ts +32 -0
  235. package/src/index.ts +2 -1
  236. package/src/meta.ts +2 -1
  237. package/src/operations/close.ts +34 -0
  238. package/src/operations/index.ts +16 -0
  239. package/src/operations/open.ts +63 -0
  240. package/src/operations/revert-workspace.ts +22 -0
  241. package/src/operations/set-layout-mode.ts +12 -0
  242. package/src/operations/set.ts +23 -0
  243. package/src/operations/state-access.ts +21 -0
  244. package/src/operations/switch-workspace.ts +26 -0
  245. package/src/operations/update-complementary.ts +35 -0
  246. package/src/operations/update-dialog.ts +28 -0
  247. package/src/operations/update-popover.ts +35 -0
  248. package/src/operations/update-sidebar.ts +12 -0
  249. package/src/plugin.ts +11 -0
  250. package/src/translations.ts +21 -13
  251. package/src/types/{capabilities.ts → SimpleLayoutCapabilities.ts} +19 -15
  252. package/src/types/SimpleLayoutEvents.ts +15 -0
  253. package/src/types/index.ts +2 -1
  254. package/src/capabilities/operation-resolver/index.ts +0 -10
  255. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -135
  256. package/src/capabilities/react-root/index.ts +0 -7
  257. package/src/capabilities/state/index.ts +0 -9
  258. package/src/capabilities/state/state.tsx +0 -60
  259. package/src/components/ContentError.tsx +0 -23
  260. package/src/components/SimpleLayout/Banner.tsx +0 -60
  261. package/src/components/SimpleLayout/NavBarstories.tsx +0 -59
@@ -5,10 +5,13 @@
5
5
  import { createContext } from '@radix-ui/react-context';
6
6
  import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
7
 
8
- import { Surface, useCapability } from '@dxos/app-framework/react';
9
- import { Popover, type PopoverContentInteractOutsideEvent } from '@dxos/react-ui';
8
+ import { Surface } from '@dxos/app-framework/ui';
9
+ import { AppSurface } from '@dxos/app-toolkit/ui';
10
+ import { Popover, type PopoverContentInteractOutsideEvent, toLocalizedString, useTranslation } from '@dxos/react-ui';
11
+ import { Card } from '@dxos/react-ui';
10
12
 
11
- import { SimpleLayoutState } from '../../types';
13
+ import { useSimpleLayoutState } from '#hooks';
14
+ import { meta } from '#meta';
12
15
 
13
16
  const DEBOUNCE_DELAY = 40;
14
17
 
@@ -19,7 +22,7 @@ type LayoutPopoverContextValue = {
19
22
  const [LayoutPopoverProvider, useLayoutPopoverContext] = createContext<LayoutPopoverContextValue>('LayoutPopover');
20
23
 
21
24
  export const PopoverRoot = ({ children }: PropsWithChildren) => {
22
- const layout = useCapability(SimpleLayoutState);
25
+ const { state } = useSimpleLayoutState();
23
26
  const [open, setOpen] = useState(false);
24
27
  const virtualRef = useRef<HTMLButtonElement | null>(null);
25
28
  const [virtualIter, setVirtualIter] = useState(0);
@@ -29,22 +32,22 @@ export const PopoverRoot = ({ children }: PropsWithChildren) => {
29
32
  // the anchor further down the tree or measuring the virtual trigger's client rect.
30
33
  useEffect(() => {
31
34
  setOpen(false);
32
- if (layout.popoverOpen) {
35
+ if (state.popoverOpen) {
33
36
  if (debounceRef.current) {
34
37
  clearTimeout(debounceRef.current);
35
38
  }
36
- if (layout.popoverAnchor && virtualRef.current !== layout.popoverAnchor) {
37
- virtualRef.current = layout.popoverAnchor ?? null;
39
+ if (state.popoverAnchor && virtualRef.current !== state.popoverAnchor) {
40
+ virtualRef.current = state.popoverAnchor ?? null;
38
41
  setVirtualIter((iter) => iter + 1);
39
42
  }
40
43
  debounceRef.current = setTimeout(() => setOpen(true), DEBOUNCE_DELAY);
41
44
  }
42
- }, [layout.popoverOpen, layout.popoverAnchorId, layout.popoverAnchor, layout.popoverContent]);
45
+ }, [state.popoverOpen, state.popoverAnchorId, state.popoverAnchor, state.popoverContent]);
43
46
 
44
47
  return (
45
48
  <LayoutPopoverProvider setOpen={setOpen}>
46
49
  <Popover.Root modal={false} open={open}>
47
- {layout.popoverAnchor && <Popover.VirtualTrigger key={virtualIter} virtualRef={virtualRef} />}
50
+ {state.popoverAnchor && <Popover.VirtualTrigger key={virtualIter} virtualRef={virtualRef} />}
48
51
  {children}
49
52
  </Popover.Root>
50
53
  </LayoutPopoverProvider>
@@ -52,10 +55,21 @@ export const PopoverRoot = ({ children }: PropsWithChildren) => {
52
55
  };
53
56
 
54
57
  export const PopoverContent = () => {
55
- const layout = useCapability(SimpleLayoutState);
58
+ const { t } = useTranslation(meta.id);
59
+ const { state, updateState } = useSimpleLayoutState();
56
60
  const { setOpen } = useLayoutPopoverContext('PopoverContent');
61
+ const handleClose = useCallback(() => {
62
+ setOpen(false);
63
+ updateState((s) => ({
64
+ ...s,
65
+ popoverOpen: false,
66
+ popoverAnchor: undefined,
67
+ popoverAnchorId: undefined,
68
+ popoverSide: undefined,
69
+ }));
70
+ }, [setOpen, updateState]);
57
71
 
58
- const handleClose = useCallback(
72
+ const handleInteractOutside = useCallback(
59
73
  (event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
60
74
  if (
61
75
  // TODO(thure): CodeMirror should not focus itself when it updates.
@@ -64,36 +78,44 @@ export const PopoverContent = () => {
64
78
  ) {
65
79
  event.preventDefault();
66
80
  } else {
67
- setOpen(false);
68
- layout.popoverOpen = false;
69
- layout.popoverAnchor = undefined;
70
- layout.popoverAnchorId = undefined;
71
- layout.popoverSide = undefined;
81
+ handleClose();
72
82
  }
73
83
  },
74
- [setOpen],
84
+ [handleClose],
75
85
  );
76
86
 
77
87
  const collisionBoundaries: HTMLElement[] = useMemo(() => {
78
- const closest = layout.popoverAnchor?.closest('[data-popover-collision-boundary]') as
79
- | HTMLElement
80
- | null
81
- | undefined;
88
+ const closest = state.popoverAnchor?.closest('[data-popover-collision-boundary]') as HTMLElement | null | undefined;
82
89
  return closest ? [closest] : [];
83
- }, [layout.popoverAnchor]);
90
+ }, [state.popoverAnchor]);
84
91
 
85
92
  return (
86
93
  <Popover.Portal>
87
94
  <Popover.Content
88
- side={layout.popoverSide}
89
- onInteractOutside={handleClose}
90
- onEscapeKeyDown={handleClose}
91
- collisionBoundary={collisionBoundaries}
95
+ side={state.popoverSide}
92
96
  sticky='always'
93
97
  hideWhenDetached
98
+ collisionBoundary={collisionBoundaries}
99
+ onInteractOutside={handleInteractOutside}
100
+ onEscapeKeyDown={handleInteractOutside}
94
101
  >
95
102
  <Popover.Viewport>
96
- <Surface role='card--popover' data={layout.popoverContent} limit={1} />
103
+ {state.popoverKind === 'base' && state.popoverContent && 'component' in state.popoverContent && (
104
+ <Surface.Surface type={AppSurface.Popover} data={state.popoverContent} limit={1} />
105
+ )}
106
+ {state.popoverKind === 'card' && (
107
+ <Card.Root border={false} classNames='dx-card-popover'>
108
+ <Card.Toolbar>
109
+ {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
110
+ <span />
111
+ {state.popoverTitle ? <Card.Title>{toLocalizedString(state.popoverTitle, t)}</Card.Title> : <span />}
112
+ <Card.CloseIconButton onClick={handleClose} />
113
+ </Card.Toolbar>
114
+ {state.popoverContent && 'subject' in state.popoverContent && (
115
+ <Surface.Surface type={AppSurface.Card} data={state.popoverContent} limit={1} />
116
+ )}
117
+ </Card.Root>
118
+ )}
97
119
  </Popover.Viewport>
98
120
  <Popover.Arrow />
99
121
  </Popover.Content>
@@ -0,0 +1,144 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
+ import React, { useMemo } from 'react';
8
+ import { type Mock, expect, fn, screen, userEvent, within } from 'storybook/test';
9
+
10
+ import { type ActionGraphProps, createMenuAction } from '@dxos/react-ui-menu';
11
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
+ import { withRegistry } from '@dxos/storybook-utils';
13
+
14
+ import { translations } from '#translations';
15
+
16
+ import { MobileLayout } from '../MobileLayout';
17
+ import { AppBar, type AppBarProps } from './AppBar';
18
+
19
+ const buildEmptyActions = (): ActionGraphProps => ({ nodes: [], edges: [] });
20
+
21
+ const buildDefaultActions = (): ActionGraphProps => {
22
+ const result: ActionGraphProps = { nodes: [], edges: [] };
23
+ const actions = [
24
+ createMenuAction('action-edit.menu', () => console.log('Edit'), {
25
+ icon: 'ph--pencil--regular',
26
+ label: 'Edit',
27
+ }),
28
+ createMenuAction('action-share.menu', () => console.log('Share'), {
29
+ icon: 'ph--share--regular',
30
+ label: 'Share',
31
+ }),
32
+ createMenuAction('action-delete.menu', () => console.log('Delete'), {
33
+ icon: 'ph--trash--regular',
34
+ label: 'Delete',
35
+ }),
36
+ ];
37
+ result.nodes.push(...actions);
38
+ result.edges.push(...actions.map((a) => ({ source: 'root', target: a.id, relation: 'child' })));
39
+ return result;
40
+ };
41
+
42
+ type DefaultStoryProps = Omit<AppBarProps, 'actions'> & {
43
+ actions: ActionGraphProps;
44
+ };
45
+
46
+ const DefaultStory = ({ actions: actionsProp, ...props }: DefaultStoryProps) => {
47
+ const actions = useMemo(() => Atom.make(actionsProp).pipe(Atom.keepAlive), [actionsProp]);
48
+ return (
49
+ <MobileLayout.Root>
50
+ <AppBar {...props} actions={actions} />
51
+ </MobileLayout.Root>
52
+ );
53
+ };
54
+
55
+ const meta = {
56
+ title: 'plugins/plugin-simple-layout/components/AppBar',
57
+ render: DefaultStory,
58
+ decorators: [
59
+ withTheme(),
60
+ withLayout({
61
+ layout: 'column',
62
+ classNames: 'relative',
63
+ }),
64
+ withRegistry,
65
+ ],
66
+ parameters: {
67
+ layout: 'fullscreen',
68
+ translations,
69
+ },
70
+ } satisfies Meta<typeof DefaultStory>;
71
+
72
+ export default meta;
73
+
74
+ type Story = StoryObj<DefaultStoryProps>;
75
+
76
+ export const Default: Story = {
77
+ tags: ['test'],
78
+ args: {
79
+ actions: buildDefaultActions(),
80
+ title: 'Document Title',
81
+ showBackButton: true,
82
+ onAction: fn(),
83
+ onBack: fn(),
84
+ },
85
+ play: async ({ args, canvasElement }) => {
86
+ const canvas = within(canvasElement);
87
+
88
+ // Verify the banner renders with the correct title.
89
+ await expect(canvas.getByRole('banner')).toBeInTheDocument();
90
+ await expect(canvas.getByText('Document Title')).toBeInTheDocument();
91
+
92
+ // Test back button click.
93
+ const backButton = canvas.getByRole('button', { name: /back/i });
94
+ await expect(backButton).toBeInTheDocument();
95
+ await userEvent.click(backButton);
96
+ await expect(args.onBack).toHaveBeenCalledTimes(1);
97
+
98
+ // Test actions menu opens and action fires.
99
+ const menuTrigger = canvas.getByRole('button', { name: /actions/i });
100
+ await expect(menuTrigger).toBeInTheDocument();
101
+ await userEvent.click(menuTrigger);
102
+
103
+ // Wait for menu to open and click an action (menu items render in a portal).
104
+ const editAction = await screen.findByRole('menuitem', { name: /edit/i });
105
+ await userEvent.click(editAction);
106
+ await expect(args.onAction).toHaveBeenCalledTimes(1);
107
+ await expect((args.onAction as Mock).mock.calls[0][0]).toHaveProperty('id', 'action-edit.menu');
108
+ },
109
+ };
110
+
111
+ export const NoBackButton: Story = {
112
+ args: {
113
+ actions: buildDefaultActions(),
114
+ title: 'Home',
115
+ showBackButton: false,
116
+ onAction: (action) => console.log('Action:', action.id),
117
+ },
118
+ };
119
+
120
+ export const LongTitle: Story = {
121
+ args: {
122
+ actions: buildDefaultActions(),
123
+ title: 'This is a very long document title that should be truncated when it exceeds the available space',
124
+ showBackButton: true,
125
+ onBack: () => console.log('Back clicked'),
126
+ onAction: (action) => console.log('Action:', action.id),
127
+ },
128
+ };
129
+
130
+ export const NoActions: Story = {
131
+ args: {
132
+ actions: buildEmptyActions(),
133
+ title: 'Empty Document',
134
+ showBackButton: true,
135
+ onBack: () => console.log('Back clicked'),
136
+ onAction: (action) => console.log('Action:', action.id),
137
+ },
138
+ };
139
+
140
+ export const Empty: Story = {
141
+ args: {
142
+ actions: buildEmptyActions(),
143
+ },
144
+ };
@@ -0,0 +1,94 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Atom, useAtomValue } from '@effect-atom/atom-react';
6
+ import React, { Fragment } from 'react';
7
+
8
+ import { IconButton, Popover, Toolbar, useTranslation } from '@dxos/react-ui';
9
+ import { type ActionExecutor, type ActionGraphProps, Menu, useMenuActions } from '@dxos/react-ui-menu';
10
+ import { composable, composableProps, osTranslations } from '@dxos/ui-theme';
11
+
12
+ import { meta } from '#meta';
13
+
14
+ import { useMobileLayout } from '../MobileLayout';
15
+
16
+ const APP_BAR_NAME = 'SimpleLayout.AppBar';
17
+
18
+ export type AppBarProps = {
19
+ /** Title/label to display in the banner. */
20
+ title?: string;
21
+ /** Action graph atom for the dropdown menu. */
22
+ actions: Atom.Atom<ActionGraphProps>;
23
+ /** Whether to show the back button. */
24
+ showBackButton?: boolean;
25
+ /** Popover anchor ID for the dropdown trigger. */
26
+ popoverAnchorId?: string;
27
+ /** Action executor callback. */
28
+ onAction?: ActionExecutor;
29
+ /** Callback when back button is clicked. */
30
+ onBack?: () => void;
31
+ };
32
+
33
+ /**
34
+ * AppBar component that renders a title, optional back button, and actions dropdown.
35
+ */
36
+ export const AppBar = composable<HTMLDivElement, AppBarProps>(
37
+ ({ classNames, title, actions, showBackButton, popoverAnchorId, onAction, onBack, ...props }, forwardedRef) => {
38
+ const { t } = useTranslation(meta.id);
39
+ const menuActions = useMenuActions(actions);
40
+ const actionsValue = useAtomValue(actions);
41
+ const hasActions = actionsValue.nodes.length > 0;
42
+ const { keyboardOpen } = useMobileLayout(APP_BAR_NAME);
43
+
44
+ // Fall back to app name if no title provided.
45
+ const displayTitle = title ?? t('current-app.name', { ns: osTranslations });
46
+
47
+ // Wrap the menu trigger with Popover.Anchor when the popoverAnchorId is set.
48
+ const AnchorRoot = popoverAnchorId ? Popover.Anchor : Fragment;
49
+
50
+ return (
51
+ <Toolbar.Root
52
+ {...composableProps(props, {
53
+ role: 'banner',
54
+ classNames: 'grid grid-cols-[var(--dx-rail-size)_1fr_var(--dx-rail-size)] items-center dx-density-fine',
55
+ })}
56
+ ref={forwardedRef}
57
+ >
58
+ {keyboardOpen ? (
59
+ <IconButton variant='ghost' icon='ph--x--regular' iconOnly label={t('done.label')} />
60
+ ) : showBackButton ? (
61
+ <IconButton
62
+ variant='ghost'
63
+ icon='ph--caret-left--regular'
64
+ iconOnly
65
+ label={t('back.label')}
66
+ onClick={onBack}
67
+ />
68
+ ) : (
69
+ <div />
70
+ )}
71
+ <h1 className='text-center truncate font-thin uppercase'>{displayTitle}</h1>
72
+ {hasActions ? (
73
+ <AnchorRoot>
74
+ <Menu.Root {...menuActions} caller={meta.id} onAction={onAction}>
75
+ <Menu.Trigger asChild>
76
+ <IconButton
77
+ variant='ghost'
78
+ icon='ph--dots-three-vertical--regular'
79
+ iconOnly
80
+ label={t('actions-menu.label')}
81
+ />
82
+ </Menu.Trigger>
83
+ <Menu.Content />
84
+ </Menu.Root>
85
+ </AnchorRoot>
86
+ ) : (
87
+ <span />
88
+ )}
89
+ </Toolbar.Root>
90
+ );
91
+ },
92
+ );
93
+
94
+ AppBar.displayName = APP_BAR_NAME;
@@ -0,0 +1,104 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { AppSurface, useAppGraph } from '@dxos/app-toolkit/ui';
9
+ import { type Node, useNode } from '@dxos/plugin-graph';
10
+ import { ErrorFallback, Panel } from '@dxos/react-ui';
11
+ import { getLinkedVariant } from '@dxos/react-ui-attention';
12
+ import { Menu, useMenuActions } from '@dxos/react-ui-menu';
13
+
14
+ import { useCompanions, useDrawerActions, useSimpleLayoutState } from '#hooks';
15
+
16
+ import { Loading } from '../Loading';
17
+
18
+ const DRAWER_NAME = 'SimpleLayout.Drawer';
19
+
20
+ /**
21
+ * Companion drawer component.
22
+ */
23
+ export const Drawer = () => {
24
+ const { graph } = useAppGraph();
25
+ const { state: layoutState } = useSimpleLayoutState();
26
+
27
+ const placeholder = useMemo(() => <Loading />, []);
28
+
29
+ // Get all companions for the current active (primary) item.
30
+ const activeId = layoutState.active ?? layoutState.workspace;
31
+ const companions = useCompanions(activeId);
32
+ const { companionId, variant } = useSelectedCompanion(companions, layoutState.companionVariant);
33
+
34
+ // Get node for the selected companion.
35
+ const node = useNode(graph, companionId);
36
+ const parentNode = useNode(graph, activeId);
37
+
38
+ // Build Surface data for the companion content.
39
+ const data = useMemo<AppSurface.ArticleData | undefined>(() => {
40
+ if (!node || !companionId) {
41
+ return undefined;
42
+ }
43
+ return {
44
+ attendableId: companionId,
45
+ subject: node.data,
46
+ companionTo: parentNode?.data,
47
+ properties: node.properties,
48
+ variant,
49
+ };
50
+ }, [companionId, node, parentNode, variant]);
51
+
52
+ // Get drawer actions (tabs + toolbar buttons).
53
+ const { actions, onAction } = useDrawerActions(DRAWER_NAME);
54
+ const menuActions = useMenuActions(actions);
55
+
56
+ return (
57
+ <Panel.Root>
58
+ <Panel.Toolbar>
59
+ <Menu.Root {...menuActions} alwaysActive onAction={onAction}>
60
+ <Menu.Toolbar />
61
+ </Menu.Root>
62
+ </Panel.Toolbar>
63
+ <Panel.Content>
64
+ <Surface.Surface
65
+ type={AppSurface.Article}
66
+ data={data}
67
+ limit={1}
68
+ fallback={ErrorFallback}
69
+ placeholder={placeholder}
70
+ />
71
+ </Panel.Content>
72
+ </Panel.Root>
73
+ );
74
+ };
75
+
76
+ Drawer.displayName = DRAWER_NAME;
77
+
78
+ /**
79
+ * Resolves which companion to show based on variant preference.
80
+ * Falls back to first available if preferred variant not available.
81
+ */
82
+ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string) => {
83
+ const selectedCompanion = useMemo(() => {
84
+ if (companions.length === 0) {
85
+ return undefined;
86
+ }
87
+
88
+ // Try to find companion matching the preferred variant.
89
+ if (preferredVariant) {
90
+ const preferred = companions.find((c) => getLinkedVariant(c.id) === preferredVariant);
91
+ if (preferred) {
92
+ return preferred;
93
+ }
94
+ }
95
+
96
+ // Fallback to first companion.
97
+ return companions[0];
98
+ }, [companions, preferredVariant]);
99
+
100
+ const companionId = selectedCompanion?.id;
101
+ const variant = companionId ? getLinkedVariant(companionId) : undefined;
102
+
103
+ return { selectedCompanion, companionId, variant };
104
+ };
@@ -2,84 +2,77 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { Activity, useMemo } from 'react';
5
+ import React, { useMemo } from 'react';
6
6
 
7
- import { Surface, useAppGraph, useCapability } from '@dxos/app-framework/react';
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { AppSurface, useAppGraph } from '@dxos/app-toolkit/ui';
8
9
  import { useNode } from '@dxos/plugin-graph';
9
- import { Main as NaturalMain } from '@dxos/react-ui';
10
- import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
11
- import { mx } from '@dxos/ui-theme';
10
+ import { ErrorFallback, Panel } from '@dxos/react-ui';
11
+ import { useAttentionAttributes } from '@dxos/react-ui-attention';
12
12
 
13
- import { SimpleLayoutState } from '../../types';
14
- import { ContentError } from '../ContentError';
15
- import { ContentLoading } from '../ContentLoading';
16
- import { Home } from '../Home';
13
+ import { useAppBarProps, useNavbarActions, useSimpleLayoutState } from '#hooks';
17
14
 
18
- import { Banner } from './Banner';
15
+ import { useExpandPath } from '../hooks';
16
+ import { Loading } from '../Loading';
17
+ import { useMobileLayout } from '../MobileLayout';
18
+ import { AppBar } from './AppBar';
19
19
  import { NavBar } from './NavBar';
20
20
 
21
+ const MAIN_NAME = 'SimpleLayout.Main';
22
+
23
+ /**
24
+ * Main content component.
25
+ */
21
26
  export const Main = () => {
22
- const layout = useCapability(SimpleLayoutState);
23
- const id = layout.active ?? layout.workspace;
24
- const { graph } = useAppGraph();
25
- const node = useNode(graph, id);
27
+ const { state } = useSimpleLayoutState();
28
+ const id = state.active ?? state.workspace;
29
+ const attentionAttrs = useAttentionAttributes(id);
30
+ const { keyboardOpen } = useMobileLayout(MAIN_NAME);
31
+ const { actions, onAction } = useNavbarActions();
32
+ const appBarProps = useAppBarProps();
26
33
 
27
- const placeholder = useMemo(() => <ContentLoading />, []);
34
+ const placeholder = useMemo(() => <Loading />, []);
28
35
 
29
- const { variant } = parseEntryId(id);
30
- const data = useMemo(
31
- () =>
36
+ const { graph } = useAppGraph();
37
+ const node = useNode(graph, id);
38
+ const data = useMemo(() => {
39
+ return (
32
40
  node && {
33
41
  attendableId: id,
34
42
  subject: node.data,
35
43
  properties: node.properties,
36
- variant,
37
- popoverAnchorId: layout.popoverAnchorId,
38
- },
39
- [node, node?.data, node?.properties, layout.popoverAnchorId, variant, id],
40
- );
44
+ popoverAnchorId: state.popoverAnchorId,
45
+ }
46
+ );
47
+ }, [id, node, node?.data, node?.properties, state.popoverAnchorId]);
41
48
 
42
- const handleActiveIdChange = (nextActiveId: string | null) => {
43
- // eslint-disable-next-line no-console
44
- console.log('[navigate]', nextActiveId);
45
- };
49
+ useExpandPath(id);
46
50
 
47
- const showNavBar = !layout.isPopover;
51
+ // TODO(burdon): BUG: When showing ANY statusbar the size progressively shrinks when the keyboard opens/closes.
52
+ const showNavBar = !keyboardOpen && !state.isPopover && state.drawerState === 'closed';
48
53
 
49
54
  return (
50
- <NaturalMain.Root complementarySidebarState='closed' navigationSidebarState='closed'>
51
- <NaturalMain.Content bounce classNames='dx-mobile-main dx-mobile-main-scroll-area--flush !overflow-y-auto'>
52
- <div
53
- className={mx(
54
- 'bs-full overflow-hidden grid',
55
- showNavBar ? 'grid-rows-[min-content_1fr_min-content]' : 'grid-rows-[min-content_1fr]',
56
- )}
57
- >
58
- <Banner node={node} />
59
- <Activity mode={id === 'default' ? 'visible' : 'hidden'}>
60
- <Home />
61
- </Activity>
62
- <Activity mode={id !== 'default' ? 'visible' : 'hidden'}>
63
- <section>
64
- <Surface
65
- key={id}
66
- role='article'
67
- data={data}
68
- limit={1}
69
- fallback={ContentError}
70
- placeholder={placeholder}
71
- />
72
- </section>
73
- </Activity>
74
- {showNavBar && <NavBar activeId={id} onActiveIdChange={handleActiveIdChange} />}
75
- </div>
76
- </NaturalMain.Content>
77
- </NaturalMain.Root>
55
+ <Panel.Root {...attentionAttrs} className='dx-document'>
56
+ <Panel.Toolbar asChild>
57
+ <AppBar {...appBarProps} />
58
+ </Panel.Toolbar>
59
+ <Panel.Content role='article' className='bg-base-surface'>
60
+ <Surface.Surface
61
+ key={id}
62
+ type={AppSurface.Article}
63
+ data={data}
64
+ limit={1}
65
+ fallback={ErrorFallback}
66
+ placeholder={placeholder}
67
+ />
68
+ </Panel.Content>
69
+ {showNavBar && (
70
+ <Panel.Statusbar asChild>
71
+ <NavBar actions={actions} onAction={onAction} />
72
+ </Panel.Statusbar>
73
+ )}
74
+ </Panel.Root>
78
75
  );
79
76
  };
80
77
 
81
- // TODO(wittjosiah): Factor out. Copied from deck plugin.
82
- const parseEntryId = (entryId: string) => {
83
- const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
84
- return { id, variant };
85
- };
78
+ Main.displayName = MAIN_NAME;