@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
@@ -0,0 +1,164 @@
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, createGapSeparator, createMenuAction, createMenuItemGroup } 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 { NavBar } from './NavBar';
17
+
18
+ const MAIN_MENU_GROUP_ID = 'navbar-main-menu';
19
+
20
+ const buildEmptyActions = (): ActionGraphProps => ({ nodes: [], edges: [] });
21
+
22
+ const buildCompanionOnlyActions = (): ActionGraphProps => {
23
+ const result: ActionGraphProps = { nodes: [], edges: [] };
24
+ const companions = [
25
+ createMenuAction('companion-browse', () => console.log('Browse'), {
26
+ icon: 'ph--house--regular',
27
+ label: 'Browse',
28
+ iconOnly: true,
29
+ }),
30
+ createMenuAction('companion-notifications', () => console.log('Notifications'), {
31
+ icon: 'ph--bell--regular',
32
+ label: 'Notifications',
33
+ iconOnly: true,
34
+ }),
35
+ createMenuAction('companion-profile', () => console.log('Profile'), {
36
+ icon: 'ph--user--regular',
37
+ label: 'Profile',
38
+ iconOnly: true,
39
+ }),
40
+ ];
41
+ result.nodes.push(...companions);
42
+ result.edges.push(...companions.map((c) => ({ source: 'root', target: c.id, relation: 'child' })));
43
+ return result;
44
+ };
45
+
46
+ const buildDefaultActions = (): ActionGraphProps => {
47
+ const result: ActionGraphProps = { nodes: [], edges: [] };
48
+ const gapSeparator = createGapSeparator('navbar-gap');
49
+ const mainMenuGroup = createMenuItemGroup(MAIN_MENU_GROUP_ID, {
50
+ variant: 'dropdownMenu',
51
+ icon: 'ph--plus--regular',
52
+ iconOnly: true,
53
+ label: 'Main menu',
54
+ testId: 'simpleLayoutPlugin.addSpace',
55
+ });
56
+ const companions = [
57
+ createMenuAction('companion-browse', () => console.log('Browse'), {
58
+ icon: 'ph--house--regular',
59
+ label: 'Browse',
60
+ iconOnly: true,
61
+ }),
62
+ createMenuAction('companion-notifications', () => console.log('Notifications'), {
63
+ icon: 'ph--bell--regular',
64
+ label: 'Notifications',
65
+ iconOnly: true,
66
+ }),
67
+ ];
68
+ const menuActions = [
69
+ createMenuAction('action-create-space', () => console.log('Create space'), {
70
+ icon: 'ph--planet--regular',
71
+ label: 'Create space',
72
+ }),
73
+ createMenuAction('action-join-space', () => console.log('Join space'), {
74
+ icon: 'ph--sign-in--regular',
75
+ label: 'Join space',
76
+ }),
77
+ createMenuAction('action-settings', () => console.log('Settings'), {
78
+ icon: 'ph--gear--regular',
79
+ label: 'Settings',
80
+ }),
81
+ ];
82
+ result.nodes.push(...companions, ...gapSeparator.nodes, mainMenuGroup, ...menuActions);
83
+ result.edges.push(
84
+ ...companions.map((c) => ({ source: 'root', target: c.id, relation: 'child' })),
85
+ ...gapSeparator.edges,
86
+ { source: 'root', target: mainMenuGroup.id, relation: 'child' },
87
+ ...menuActions.map((action) => ({ source: MAIN_MENU_GROUP_ID, target: action.id, relation: 'child' })),
88
+ );
89
+ return result;
90
+ };
91
+
92
+ const meta = {
93
+ title: 'plugins/plugin-simple-layout/components/NavBar',
94
+ component: NavBar,
95
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withRegistry],
96
+ parameters: {
97
+ layout: 'fullscreen',
98
+ translations,
99
+ },
100
+ } satisfies Meta<typeof NavBar>;
101
+
102
+ export default meta;
103
+
104
+ type Story = StoryObj<typeof meta>;
105
+
106
+ const DefaultStory = ({ onAction }: { onAction: (action: { id: string }) => void }) => {
107
+ const actions = useMemo(() => Atom.make(buildDefaultActions()).pipe(Atom.keepAlive), []);
108
+
109
+ return <NavBar classNames='border-y border-separator' actions={actions} onAction={onAction} />;
110
+ };
111
+
112
+ export const Default: Story = {
113
+ tags: ['test'],
114
+ args: {
115
+ onAction: fn(),
116
+ } as any,
117
+ render: (args: any) => <DefaultStory onAction={args.onAction} />,
118
+ play: async ({ args, canvasElement }) => {
119
+ const canvas = within(canvasElement);
120
+
121
+ // Verify the navbar renders with the toolbar.
122
+ await expect(canvas.getByRole('toolbar')).toBeInTheDocument();
123
+
124
+ // Test companion action click (Browse button).
125
+ const browseButton = canvas.getByRole('button', { name: /browse/i });
126
+ await expect(browseButton).toBeInTheDocument();
127
+ await userEvent.click(browseButton);
128
+ await expect(args.onAction).toHaveBeenCalledTimes(1);
129
+ await expect((args.onAction as Mock).mock.calls[0][0]).toHaveProperty('id', 'companion-browse');
130
+
131
+ // Test dropdown menu opens and action fires.
132
+ const menuTrigger = canvas.getByRole('button', { name: /main menu/i });
133
+ await expect(menuTrigger).toBeInTheDocument();
134
+ await userEvent.click(menuTrigger);
135
+
136
+ // Wait for menu to open and click an action (menu items render in a portal).
137
+ const createSpaceAction = await screen.findByRole('menuitem', { name: /create space/i });
138
+ await userEvent.click(createSpaceAction);
139
+ await expect(args.onAction).toHaveBeenCalledTimes(2);
140
+ await expect((args.onAction as Mock).mock.calls[1][0]).toHaveProperty('id', 'action-create-space');
141
+ },
142
+ };
143
+
144
+ const CompanionsOnlyStory = () => {
145
+ const actions = useMemo(() => Atom.make(buildCompanionOnlyActions()).pipe(Atom.keepAlive), []);
146
+
147
+ return <NavBar actions={actions} onAction={(action) => console.log('Action:', action.id)} />;
148
+ };
149
+
150
+ export const CompanionsOnly: Story = {
151
+ args: {} as any,
152
+ render: () => <CompanionsOnlyStory />,
153
+ };
154
+
155
+ const EmptyStory = () => {
156
+ const actions = useMemo(() => Atom.make(buildEmptyActions()).pipe(Atom.keepAlive), []);
157
+
158
+ return <NavBar actions={actions} onAction={(action) => console.log('Action:', action.id)} />;
159
+ };
160
+
161
+ export const Empty: Story = {
162
+ args: {} as any,
163
+ render: () => <EmptyStory />,
164
+ };
@@ -2,96 +2,32 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { type Atom } from '@effect-atom/atom-react';
5
6
  import React from 'react';
6
7
 
7
- import { useAppGraph } from '@dxos/app-framework/react';
8
- import { Node, useActionRunner, useConnections } from '@dxos/plugin-graph';
9
- import {
10
- Avatar,
11
- Button,
12
- ButtonGroup,
13
- DensityProvider,
14
- IconButton,
15
- type Size,
16
- Tooltip,
17
- useTranslation,
18
- } from '@dxos/react-ui';
19
- import { DropdownMenu, MenuProvider } from '@dxos/react-ui-menu';
20
- import { mx, surfaceZIndex } from '@dxos/ui-theme';
8
+ import { type ActionExecutor, type ActionGraphProps, Menu, useMenuActions } from '@dxos/react-ui-menu';
9
+ import { composable, composableProps } from '@dxos/ui-theme';
21
10
 
22
- import { meta } from '../../meta';
23
-
24
- const buttonProps = {
25
- iconOnly: true,
26
- size: 6 as Size,
27
- classNames: 'aspect-square',
28
- };
11
+ const NAVBAR_NAME = 'SimpleLayout.NavBar';
29
12
 
30
13
  export type NavBarProps = {
31
- activeId?: string;
32
- onActiveIdChange?: (nextActiveId: string | null) => void;
14
+ /** Action graph atom for the toolbar. */
15
+ actions: Atom.Atom<ActionGraphProps>;
16
+ /** Action executor callback. */
17
+ onAction?: ActionExecutor;
33
18
  };
34
19
 
35
- export const NavBar = ({ activeId, onActiveIdChange }: NavBarProps) => {
36
- const { t } = useTranslation(meta.id);
37
- const { graph } = useAppGraph();
38
- const runAction = useActionRunner();
39
-
40
- const connections = useConnections(graph, Node.RootId);
41
- const menuActions = connections.filter((node) => node.properties.disposition === 'menu');
42
-
43
- const isBrowseActive = activeId !== 'notifications' && activeId !== 'profile';
20
+ /**
21
+ * Presentational navbar component that renders a toolbar from an action graph.
22
+ */
23
+ export const NavBar = composable<HTMLDivElement, NavBarProps>(({ actions, onAction, ...props }, forwardedRef) => {
24
+ const menuActions = useMenuActions(actions);
44
25
 
45
26
  return (
46
- <DensityProvider density='coarse'>
47
- <nav
48
- className={mx(
49
- 'fixed inset-inline-0',
50
- 'grid grid-cols-[min-content_min-content] gap-2 place-content-center',
51
- 'block-end-[--dx-mobile-bottombar-inset-bottom,0px] bs-[--dx-mobile-bottombar-content-height,64px]',
52
- 'bg-baseSurface border-bs border-separator',
53
- surfaceZIndex({ level: 'menu' }),
54
- )}
55
- >
56
- <ButtonGroup>
57
- <IconButton
58
- {...buttonProps}
59
- label={t('browse label')}
60
- icon='ph--squares-four--regular'
61
- onClick={() => onActiveIdChange?.(null)}
62
- variant={isBrowseActive ? 'primary' : 'default'}
63
- {...(isBrowseActive && { 'aria-current': 'location' })}
64
- />
65
- <IconButton
66
- {...buttonProps}
67
- label={t('notifications label')}
68
- icon='ph--bell-simple--regular'
69
- onClick={() => onActiveIdChange?.('notifications')}
70
- variant={activeId === 'notifications' ? 'primary' : 'default'}
71
- {...(activeId === 'notifications' && { 'aria-current': 'location' })}
72
- />
73
- <Button
74
- variant={activeId === 'profile' ? 'primary' : 'default'}
75
- onClick={() => onActiveIdChange?.('profile')}
76
- classNames={buttonProps.classNames}
77
- >
78
- <span className='sr-only'>{t('profile label')}</span>
79
- <Avatar.Root>
80
- <Avatar.Label classNames='sr-only'>Profile display name</Avatar.Label>
81
- <Avatar.Content size={8} status='active' hue='cyan' fallback='🗿' />
82
- </Avatar.Root>
83
- </Button>
84
- </ButtonGroup>
85
- <MenuProvider onAction={runAction}>
86
- <DropdownMenu.Root items={menuActions}>
87
- <Tooltip.Trigger asChild content={t('app menu label')} side='right'>
88
- <DropdownMenu.Trigger asChild data-testid='spacePlugin.addSpace'>
89
- <IconButton {...buttonProps} icon='ph--plus--regular' label={t('main menu label')} />
90
- </DropdownMenu.Trigger>
91
- </Tooltip.Trigger>
92
- </DropdownMenu.Root>
93
- </MenuProvider>
94
- </nav>
95
- </DensityProvider>
27
+ <Menu.Root {...menuActions} alwaysActive onAction={onAction}>
28
+ <Menu.Toolbar {...composableProps(props)} ref={forwardedRef} />
29
+ </Menu.Root>
96
30
  );
97
- };
31
+ });
32
+
33
+ NavBar.displayName = NAVBAR_NAME;
@@ -5,88 +5,71 @@
5
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
  import * as Effect from 'effect/Effect';
7
7
 
8
- import { Capability, Common, Plugin } from '@dxos/app-framework';
8
+ import { ActivationEvents, Capability, Plugin } from '@dxos/app-framework';
9
9
  import { withPluginManager } from '@dxos/app-framework/testing';
10
- import { ClientOperation, ClientPlugin } from '@dxos/plugin-client';
11
- import { SearchPlugin } from '@dxos/plugin-search';
12
- import { SpacePlugin } from '@dxos/plugin-space';
13
- import { SpaceOperation } from '@dxos/plugin-space/types';
10
+ import { AppActivationEvents } from '@dxos/app-toolkit';
11
+ import { Collection } from '@dxos/echo';
12
+ import { ClientPlugin } from '@dxos/plugin-client/plugin';
13
+ import { SearchPlugin } from '@dxos/plugin-search/plugin';
14
+ import { SpacePlugin } from '@dxos/plugin-space/plugin';
14
15
  import { corePlugins } from '@dxos/plugin-testing';
15
- import { withTheme } from '@dxos/react-ui/testing';
16
- import { translations as searchTranslation } from '@dxos/react-ui-searchlist';
17
- import { Collection } from '@dxos/schema';
16
+ import { translations as searchTranslation } from '@dxos/react-ui-search/translations';
17
+ import { withLayout } from '@dxos/react-ui/testing';
18
18
 
19
- import { OperationResolver, type SimpleLayoutStateOptions, State } from '../../capabilities';
20
- import { meta as pluginMeta } from '../../meta';
21
- import { type SimpleLayoutPluginOptions } from '../../SimpleLayoutPlugin';
22
- import { translations } from '../../translations';
19
+ import { ReactRoot, ReactSurface, State } from '#capabilities';
20
+ import { meta as pluginMeta } from '#meta';
21
+ import { translations } from '#translations';
22
+ import { SimpleLayoutEvents } from '#types';
23
23
 
24
+ import { type SimpleLayoutPluginOptions } from '../../SimpleLayoutPlugin';
24
25
  import { SimpleLayout } from './SimpleLayout';
25
26
 
26
- const TestPlugin = Plugin.define<SimpleLayoutPluginOptions>(pluginMeta).pipe(
27
- Plugin.addModule(({ isPopover = false }) => ({
28
- id: Capability.getModuleTag(State),
29
- activatesOn: Common.ActivationEvent.Startup,
30
- activatesAfter: [Common.ActivationEvent.LayoutReady],
31
- activate: () => State({ initialState: { isPopover } } satisfies SimpleLayoutStateOptions),
32
- })),
33
- Common.Plugin.addOperationResolverModule({ activate: OperationResolver }),
34
- Plugin.addModule({
35
- id: 'setup',
36
- activatesOn: Common.ActivationEvent.OperationInvokerReady,
37
- activate: Effect.fnUntraced(function* () {
38
- const { invoke } = yield* Capability.get(Common.Capability.OperationInvoker);
39
- yield* invoke(ClientOperation.CreateIdentity, {});
40
- const { space: work } = yield* invoke(SpaceOperation.Create, { name: 'Work Space' });
41
- const { space: sharedProject } = yield* invoke(SpaceOperation.Create, { name: 'Shared Project' });
42
-
43
- // Add collections to Work Space.
44
- yield* invoke(SpaceOperation.AddObject, {
45
- target: work.db,
46
- object: Collection.make({ name: 'Projects', objects: [] }),
47
- });
48
- yield* invoke(SpaceOperation.AddObject, {
49
- target: work.db,
50
- object: Collection.make({ name: 'Documents', objects: [] }),
51
- });
52
-
53
- // Add collections to Shared Project.
54
- yield* invoke(SpaceOperation.AddObject, {
55
- target: sharedProject.db,
56
- object: Collection.make({ name: 'Tasks', objects: [] }),
57
- });
58
- yield* invoke(SpaceOperation.AddObject, {
59
- target: sharedProject.db,
60
- object: Collection.make({ name: 'Notes', objects: [] }),
61
- });
62
- }),
63
- }),
64
- Plugin.make,
65
- );
66
-
67
- const createPluginManager = ({ isPopover }: { isPopover: boolean }) => {
27
+ const createPluginManager = ({ isPopover }: { isPopover?: boolean }) => {
68
28
  return withPluginManager({
29
+ setupEvents: [AppActivationEvents.SetupSettings],
69
30
  plugins: [
70
31
  ...corePlugins(),
71
32
  ClientPlugin({
33
+ types: [Collection.Collection],
72
34
  onClientInitialized: ({ client }) =>
73
35
  Effect.gen(function* () {
74
36
  yield* Effect.promise(() => client.halo.createIdentity());
75
- yield* Effect.promise(async () => {
76
- await client.spaces.create({ name: 'Work Space' });
77
- await client.spaces.create({ name: 'Shared Project' });
78
- });
79
37
  }),
80
38
  }),
81
- SpacePlugin({}),
39
+
82
40
  SearchPlugin(),
83
- TestPlugin({ isPopover }),
41
+ SpacePlugin({}),
42
+
43
+ // TODO(burdon): This should be factored ouf from SimpleLayoutPlugin.
44
+ Plugin.define<SimpleLayoutPluginOptions>(pluginMeta).pipe(
45
+ Plugin.addModule(({ isPopover = false }) => ({
46
+ id: Capability.getModuleTag(State),
47
+ activatesOn: ActivationEvents.Startup,
48
+ firesAfterActivation: [SimpleLayoutEvents.StateReady, AppActivationEvents.LayoutReady],
49
+ activate: () => State({ initialState: { isPopover } }),
50
+ })),
51
+ Plugin.addModule({
52
+ id: Capability.getModuleTag(ReactRoot),
53
+ activatesOn: ActivationEvents.Startup,
54
+ activate: ReactRoot,
55
+ }),
56
+ Plugin.addModule({
57
+ id: Capability.getModuleTag(ReactSurface),
58
+ activatesOn: ActivationEvents.Startup,
59
+ activate: ReactSurface,
60
+ }),
61
+ Plugin.make,
62
+ )({ isPopover }),
84
63
  ],
85
64
  });
86
65
  };
87
66
 
67
+ /**
68
+ * NOTE: To expose to iphone on network:
69
+ * `moon run storybook-react:serve dev -H 0.0.0.0`
70
+ */
88
71
  const meta = {
89
- title: 'plugins/plugin-simple-layout/SimpleLayout',
72
+ title: 'plugins/plugin-simple-layout/components/SimpleLayout',
90
73
  component: SimpleLayout,
91
74
  parameters: {
92
75
  layout: 'fullscreen',
@@ -99,9 +82,9 @@ export default meta;
99
82
  type Story = StoryObj<typeof meta>;
100
83
 
101
84
  export const Default: Story = {
102
- decorators: [withTheme, createPluginManager({ isPopover: false })],
85
+ decorators: [withLayout({ layout: 'column', classNames: 'relative' }), createPluginManager({})],
103
86
  };
104
87
 
105
88
  export const Popover: Story = {
106
- decorators: [withTheme, createPluginManager({ isPopover: true })],
89
+ decorators: [withLayout({ layout: 'column', classNames: 'relative' }), createPluginManager({ isPopover: true })],
107
90
  };
@@ -2,20 +2,57 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useLayoutEffect, useRef, useState } from 'react';
6
6
 
7
+ import { Splitter, type SplitterMode } from '@dxos/react-ui';
8
+ import { Mosaic } from '@dxos/react-ui-mosaic';
9
+
10
+ import { useSimpleLayoutState } from '#hooks';
11
+
12
+ import { DebugOverlay } from '../DebugOverlay';
7
13
  import { Dialog } from '../Dialog';
14
+ import { MobileLayout } from '../MobileLayout';
8
15
  import { PopoverContent, PopoverRoot } from '../Popover';
9
-
16
+ import { Drawer } from './Drawer';
10
17
  import { Main } from './Main';
11
18
 
12
- // TODO(wittjosiah): Support toast.
13
19
  export const SimpleLayout = () => {
20
+ const { state } = useSimpleLayoutState();
21
+ const [keyboardOpen, setKeyboardOpen] = useState(false);
22
+ const [splitterMode, setSplitterMode] = useState<SplitterMode>('top');
23
+
24
+ const drawerRef = useRef<HTMLDivElement>(null);
25
+
26
+ // Restore Splitter mode when keyboard closes.
27
+ useLayoutEffect(() => {
28
+ if (!keyboardOpen) {
29
+ setSplitterMode(state.drawerState === 'closed' ? 'top' : state.drawerState === 'open' ? 'split' : 'bottom');
30
+ }
31
+ }, [state.drawerState, keyboardOpen]);
32
+
14
33
  return (
15
- <PopoverRoot>
16
- <Main />
17
- <Dialog />
18
- <PopoverContent />
19
- </PopoverRoot>
34
+ <DebugOverlay.Root enabled={false}>
35
+ <PopoverRoot>
36
+ <Mosaic.Root>
37
+ <MobileLayout.Root
38
+ classNames='dx-container grid relative bg-toolbar-surface'
39
+ onKeyboardOpenChange={(nextKeyboardOpen) => setKeyboardOpen(nextKeyboardOpen)}
40
+ >
41
+ <MobileLayout.Panel safe={{ top: true, bottom: splitterMode === 'top' }}>
42
+ <Splitter.Root mode={splitterMode} ratio={0.55}>
43
+ <Splitter.Panel position='top'>
44
+ <Main />
45
+ </Splitter.Panel>
46
+ <Splitter.Panel position='bottom' ref={drawerRef}>
47
+ <Drawer />
48
+ </Splitter.Panel>
49
+ </Splitter.Root>
50
+ <Dialog />
51
+ <PopoverContent />
52
+ </MobileLayout.Panel>
53
+ </MobileLayout.Root>
54
+ </Mosaic.Root>
55
+ </PopoverRoot>
56
+ </DebugOverlay.Root>
20
57
  );
21
58
  };
@@ -2,4 +2,7 @@
2
2
  // Copyright 2026 DXOS.org
3
3
  //
4
4
 
5
+ export * from './AppBar';
6
+ export * from './Main';
7
+ export * from './NavBar';
5
8
  export * from './SimpleLayout';
@@ -0,0 +1,26 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useEffect } from 'react';
6
+
7
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
8
+ import { Graph } from '@dxos/plugin-graph';
9
+ import { expandAttendableId } from '@dxos/react-ui-attention';
10
+
11
+ /**
12
+ * Expand graph nodes along the full path from root to the given node ID.
13
+ * Walks each progressive prefix, ensuring ancestor nodes are materialized
14
+ * before attempting to access their children.
15
+ */
16
+ export const useExpandPath = (nodeId?: string) => {
17
+ const { graph } = useAppGraph();
18
+
19
+ useEffect(() => {
20
+ if (nodeId) {
21
+ for (const prefix of expandAttendableId(nodeId)) {
22
+ Graph.expand(graph, prefix, 'child');
23
+ }
24
+ }
25
+ }, [nodeId, graph]);
26
+ };
@@ -2,7 +2,10 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './ContentLoading';
5
+ export * from './DebugOverlay';
6
6
  export * from './Home';
7
+ export * from './Loading';
8
+ export * from './MobileLayout';
7
9
  export * from './Popover';
8
10
  export * from './SimpleLayout';
11
+ export * from './NavBranch';
@@ -0,0 +1,86 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Atom } from '@effect-atom/atom-react';
6
+ import * as Effect from 'effect/Effect';
7
+
8
+ import { type AppCapabilities } from '@dxos/app-toolkit';
9
+ import { Node } from '@dxos/plugin-graph';
10
+ import { getLinkedVariant } from '@dxos/react-ui-attention';
11
+ import { type ActionGraphProps } from '@dxos/react-ui-menu';
12
+ import { byPosition } from '@dxos/util';
13
+
14
+ import { SimpleLayoutCapabilities } from '#types';
15
+
16
+ // TODO(wittjosiah): Factor out to shared location with plugin-deck.
17
+ export const PLANK_COMPANION_TYPE = 'org.dxos.plugin.deck.plank-companion';
18
+
19
+ export type CompanionActionsConfig = {
20
+ /** Prefix for companion action IDs (e.g. 'navbar' or 'drawer') */
21
+ idPrefix: string;
22
+ /** Optional: highlight companion with this variant */
23
+ selectedVariant?: string;
24
+ /** State updater for toggling the drawer. */
25
+ updateState: (
26
+ fn: (state: SimpleLayoutCapabilities.SimpleLayoutState) => SimpleLayoutCapabilities.SimpleLayoutState,
27
+ ) => void;
28
+ };
29
+
30
+ /**
31
+ * Creates action graph nodes and edges for companion actions.
32
+ * Shared logic between useNavbarActions and useDrawerActions.
33
+ */
34
+ // TODO(burdon): Use builder pattern.
35
+ export const createCompanionActions = (
36
+ graph: AppCapabilities.AppGraph['graph'],
37
+ stateAtom: Atom.Atom<SimpleLayoutCapabilities.SimpleLayoutState>,
38
+ get: (atom: Atom.Atom<any>) => any,
39
+ config: CompanionActionsConfig,
40
+ ): Pick<ActionGraphProps, 'nodes' | 'edges'> => {
41
+ const { idPrefix, selectedVariant, updateState } = config;
42
+
43
+ // Derive activeId from state atom.
44
+ const state = get(stateAtom);
45
+ const activeId = state.active ?? state.workspace;
46
+
47
+ // Get companions from graph connections for activeId.
48
+ const activeConnections = activeId ? get(graph.connections(activeId, 'child')) : [];
49
+ const companions = activeConnections
50
+ .filter((node: Node.Node) => node.type === PLANK_COMPANION_TYPE)
51
+ .toSorted((a: Node.Node, b: Node.Node) => byPosition(a.properties, b.properties));
52
+
53
+ const nodes: ActionGraphProps['nodes'] = [];
54
+ const edges: ActionGraphProps['edges'] = [];
55
+
56
+ companions.forEach((companion: Node.Node) => {
57
+ const companionVariant = getLinkedVariant(companion.id);
58
+ const companionAction = {
59
+ id: `${idPrefix}-companion-${companion.id}`,
60
+ type: Node.ActionType,
61
+ properties: {
62
+ icon: companion.properties.icon ?? 'ph--placeholder--regular',
63
+ label: companion.properties.label,
64
+ iconOnly: true,
65
+ ...(selectedVariant !== undefined && {
66
+ variant: selectedVariant === companionVariant ? 'primary' : 'ghost',
67
+ }),
68
+ },
69
+ data: () =>
70
+ Effect.sync(() =>
71
+ updateState((current) => {
72
+ const closing = current.companionVariant === companionVariant && current.drawerState !== 'closed';
73
+ return {
74
+ ...current,
75
+ companionVariant: closing ? undefined : companionVariant,
76
+ drawerState: closing ? 'closed' : 'open',
77
+ };
78
+ }),
79
+ ),
80
+ };
81
+ nodes.push(companionAction);
82
+ edges.push({ source: 'root', target: companionAction.id, relation: 'child' });
83
+ });
84
+
85
+ return { nodes, edges };
86
+ };
@@ -2,4 +2,9 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './useSpotlightDismiss';
5
+ export * from './useAppBarProps';
6
+ export * from './useCompanions';
7
+ export * from './actions';
8
+ export * from './useDrawerActions';
9
+ export * from './useNavbarActions';
10
+ export * from './useSimpleLayoutState';