@dxos/plugin-simple-layout 0.8.4-main.9735255 → 0.8.4-main.9be5663bfe

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 (246) hide show
  1. package/dist/lib/browser/index.mjs +44 -64
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +44 -63
  5. package/dist/lib/node-esm/index.mjs.map +4 -4
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/SimpleLayoutPlugin.d.ts +1 -1
  8. package/dist/types/src/SimpleLayoutPlugin.d.ts.map +1 -1
  9. package/dist/types/src/capabilities/app-graph-builder.d.ts +6 -0
  10. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  11. package/dist/types/src/capabilities/index.d.ts +21 -6
  12. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  13. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  14. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  15. package/dist/types/src/capabilities/{react-root/react-root.d.ts → react-root.d.ts} +1 -1
  16. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  17. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  18. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  19. package/dist/types/src/capabilities/{spotlight-dismiss/spotlight-dismiss.d.ts → spotlight-dismiss.d.ts} +1 -1
  20. package/dist/types/src/capabilities/spotlight-dismiss.d.ts.map +1 -0
  21. package/dist/types/src/capabilities/{state/state.d.ts → state.d.ts} +2 -2
  22. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  23. package/dist/types/src/capabilities/url-handler.d.ts +12 -0
  24. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -0
  25. package/dist/types/src/components/ContentError.stories.d.ts +26 -21
  26. package/dist/types/src/components/ContentError.stories.d.ts.map +1 -1
  27. package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts +19 -0
  28. package/dist/types/src/components/DebugOverlay/DebugOverlay.d.ts.map +1 -0
  29. package/dist/types/src/components/DebugOverlay/index.d.ts +2 -0
  30. package/dist/types/src/components/DebugOverlay/index.d.ts.map +1 -0
  31. package/dist/types/src/components/Home/Home.d.ts.map +1 -1
  32. package/dist/types/src/components/Loading/Loading.d.ts +3 -0
  33. package/dist/types/src/components/Loading/Loading.d.ts.map +1 -0
  34. package/dist/types/src/components/{ContentLoading.stories.d.ts → Loading/Loading.stories.d.ts} +1 -1
  35. package/dist/types/src/components/Loading/Loading.stories.d.ts.map +1 -0
  36. package/dist/types/src/components/Loading/index.d.ts +2 -0
  37. package/dist/types/src/components/Loading/index.d.ts.map +1 -0
  38. package/dist/types/src/components/MobileLayout/MobileLayout.d.ts +35 -0
  39. package/dist/types/src/components/MobileLayout/MobileLayout.d.ts.map +1 -0
  40. package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts +7 -0
  41. package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts.map +1 -0
  42. package/dist/types/src/components/MobileLayout/index.d.ts +2 -0
  43. package/dist/types/src/components/MobileLayout/index.d.ts.map +1 -0
  44. package/dist/types/src/components/NavBranch/NavBranch.d.ts +11 -0
  45. package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -0
  46. package/dist/types/src/components/NavBranch/index.d.ts +2 -0
  47. package/dist/types/src/components/NavBranch/index.d.ts.map +1 -0
  48. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  49. package/dist/types/src/components/SimpleLayout/AppBar.d.ts +24 -0
  50. package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -0
  51. package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts +54 -0
  52. package/dist/types/src/components/SimpleLayout/AppBar.stories.d.ts.map +1 -0
  53. package/dist/types/src/components/SimpleLayout/Drawer.d.ts +1 -1
  54. package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -1
  55. package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -1
  56. package/dist/types/src/components/SimpleLayout/NavBar.d.ts +13 -8
  57. package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -1
  58. package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts +31 -25
  59. package/dist/types/src/components/SimpleLayout/NavBar.stories.d.ts.map +1 -1
  60. package/dist/types/src/components/SimpleLayout/SimpleLayout.d.ts.map +1 -1
  61. package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts +26 -25
  62. package/dist/types/src/components/SimpleLayout/SimpleLayout.stories.d.ts.map +1 -1
  63. package/dist/types/src/components/SimpleLayout/index.d.ts +3 -0
  64. package/dist/types/src/components/SimpleLayout/index.d.ts.map +1 -1
  65. package/dist/types/src/components/hooks.d.ts +4 -2
  66. package/dist/types/src/components/hooks.d.ts.map +1 -1
  67. package/dist/types/src/components/index.d.ts +4 -2
  68. package/dist/types/src/components/index.d.ts.map +1 -1
  69. package/dist/types/src/hooks/actions.d.ts +19 -0
  70. package/dist/types/src/hooks/actions.d.ts.map +1 -0
  71. package/dist/types/src/hooks/index.d.ts +4 -0
  72. package/dist/types/src/hooks/index.d.ts.map +1 -1
  73. package/dist/types/src/hooks/useAppBarProps.d.ts +7 -0
  74. package/dist/types/src/hooks/useAppBarProps.d.ts.map +1 -0
  75. package/dist/types/src/hooks/useCompanions.d.ts +5 -1
  76. package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
  77. package/dist/types/src/hooks/useDrawerActions.d.ts +13 -0
  78. package/dist/types/src/hooks/useDrawerActions.d.ts.map +1 -0
  79. package/dist/types/src/hooks/useNavbarActions.d.ts +14 -0
  80. package/dist/types/src/hooks/useNavbarActions.d.ts.map +1 -0
  81. package/dist/types/src/hooks/useSimpleLayoutState.d.ts +3 -3
  82. package/dist/types/src/hooks/useSimpleLayoutState.d.ts.map +1 -1
  83. package/dist/types/src/operations/close.d.ts +5 -0
  84. package/dist/types/src/operations/close.d.ts.map +1 -0
  85. package/dist/types/src/operations/index.d.ts +3 -0
  86. package/dist/types/src/operations/index.d.ts.map +1 -0
  87. package/dist/types/src/operations/open.d.ts +5 -0
  88. package/dist/types/src/operations/open.d.ts.map +1 -0
  89. package/dist/types/src/operations/revert-workspace.d.ts +5 -0
  90. package/dist/types/src/operations/revert-workspace.d.ts.map +1 -0
  91. package/dist/types/src/operations/set-layout-mode.d.ts +5 -0
  92. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  93. package/dist/types/src/operations/set.d.ts +5 -0
  94. package/dist/types/src/operations/set.d.ts.map +1 -0
  95. package/dist/types/src/operations/state-access.d.ts +8 -0
  96. package/dist/types/src/operations/state-access.d.ts.map +1 -0
  97. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  98. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  99. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  100. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  101. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  102. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  103. package/dist/types/src/operations/update-popover.d.ts +5 -0
  104. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  105. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  106. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  107. package/dist/types/src/translations.d.ts +26 -19
  108. package/dist/types/src/translations.d.ts.map +1 -1
  109. package/dist/types/src/types/capabilities.d.ts +17 -8
  110. package/dist/types/src/types/capabilities.d.ts.map +1 -1
  111. package/dist/types/src/types/events.d.ts.map +1 -1
  112. package/dist/types/tsconfig.tsbuildinfo +1 -1
  113. package/package.json +46 -30
  114. package/src/SimpleLayoutPlugin.ts +24 -13
  115. package/src/capabilities/app-graph-builder.ts +21 -0
  116. package/src/capabilities/index.ts +13 -6
  117. package/src/capabilities/operation-handler.ts +14 -0
  118. package/src/capabilities/{react-root/react-root.tsx → react-root.tsx} +4 -4
  119. package/src/capabilities/react-surface.tsx +50 -0
  120. package/src/capabilities/{spotlight-dismiss/spotlight-dismiss.ts → spotlight-dismiss.ts} +2 -2
  121. package/src/capabilities/{state/state.tsx → state.tsx} +6 -5
  122. package/src/capabilities/url-handler.ts +161 -0
  123. package/src/components/ContentError.stories.tsx +8 -7
  124. package/src/components/DebugOverlay/DebugOverlay.tsx +96 -0
  125. package/src/components/DebugOverlay/index.ts +5 -0
  126. package/src/components/Dialog/Dialog.tsx +6 -6
  127. package/src/components/Home/Home.tsx +51 -43
  128. package/src/components/{ContentLoading.stories.tsx → Loading/Loading.stories.tsx} +5 -5
  129. package/src/components/{ContentLoading.tsx → Loading/Loading.tsx} +2 -2
  130. package/src/components/Loading/index.ts +5 -0
  131. package/src/components/MobileLayout/MobileLayout.stories.tsx +133 -0
  132. package/src/components/MobileLayout/MobileLayout.tsx +374 -0
  133. package/src/components/MobileLayout/index.ts +5 -0
  134. package/src/components/NavBranch/NavBranch.tsx +128 -0
  135. package/src/components/{Workspace → NavBranch}/index.ts +1 -1
  136. package/src/components/Popover/Popover.tsx +10 -9
  137. package/src/components/SimpleLayout/AppBar.stories.tsx +143 -0
  138. package/src/components/SimpleLayout/AppBar.tsx +94 -0
  139. package/src/components/SimpleLayout/Drawer.tsx +39 -92
  140. package/src/components/SimpleLayout/Main.tsx +40 -34
  141. package/src/components/SimpleLayout/NavBar.stories.tsx +131 -24
  142. package/src/components/SimpleLayout/NavBar.tsx +18 -51
  143. package/src/components/SimpleLayout/SimpleLayout.stories.tsx +45 -59
  144. package/src/components/SimpleLayout/SimpleLayout.tsx +41 -23
  145. package/src/components/SimpleLayout/index.ts +3 -0
  146. package/src/components/hooks.ts +9 -9
  147. package/src/components/index.ts +4 -2
  148. package/src/hooks/actions.ts +84 -0
  149. package/src/hooks/index.ts +4 -0
  150. package/src/hooks/useAppBarProps.ts +115 -0
  151. package/src/hooks/useCompanions.ts +8 -5
  152. package/src/hooks/useDrawerActions.ts +100 -0
  153. package/src/hooks/useNavbarActions.ts +87 -0
  154. package/src/hooks/useSimpleLayoutState.ts +5 -5
  155. package/src/meta.ts +1 -1
  156. package/src/operations/close.ts +34 -0
  157. package/src/operations/index.ts +16 -0
  158. package/src/operations/open.ts +63 -0
  159. package/src/operations/revert-workspace.ts +22 -0
  160. package/src/operations/set-layout-mode.ts +12 -0
  161. package/src/operations/set.ts +23 -0
  162. package/src/operations/state-access.ts +19 -0
  163. package/src/operations/switch-workspace.ts +26 -0
  164. package/src/operations/update-complementary.ts +35 -0
  165. package/src/operations/update-dialog.ts +28 -0
  166. package/src/operations/update-popover.ts +35 -0
  167. package/src/operations/update-sidebar.ts +12 -0
  168. package/src/translations.ts +21 -19
  169. package/src/types/capabilities.ts +14 -10
  170. package/src/types/events.ts +3 -2
  171. package/dist/lib/browser/chunk-LR3EE3VB.mjs +0 -789
  172. package/dist/lib/browser/chunk-LR3EE3VB.mjs.map +0 -7
  173. package/dist/lib/browser/chunk-P77G4YTR.mjs +0 -29
  174. package/dist/lib/browser/chunk-P77G4YTR.mjs.map +0 -7
  175. package/dist/lib/browser/operation-resolver-775UYAC2.mjs +0 -203
  176. package/dist/lib/browser/operation-resolver-775UYAC2.mjs.map +0 -7
  177. package/dist/lib/browser/react-root-KM55OMGJ.mjs +0 -21
  178. package/dist/lib/browser/react-root-KM55OMGJ.mjs.map +0 -7
  179. package/dist/lib/browser/react-surface-BABGAWGY.mjs +0 -39
  180. package/dist/lib/browser/react-surface-BABGAWGY.mjs.map +0 -7
  181. package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs +0 -66
  182. package/dist/lib/browser/spotlight-dismiss-VSNOPETH.mjs.map +0 -7
  183. package/dist/lib/browser/state-OUFTC2KV.mjs +0 -47
  184. package/dist/lib/browser/state-OUFTC2KV.mjs.map +0 -7
  185. package/dist/lib/browser/url-handler-DOUFQIAC.mjs +0 -54
  186. package/dist/lib/browser/url-handler-DOUFQIAC.mjs.map +0 -7
  187. package/dist/lib/node-esm/chunk-F5TEKVJG.mjs +0 -31
  188. package/dist/lib/node-esm/chunk-F5TEKVJG.mjs.map +0 -7
  189. package/dist/lib/node-esm/chunk-HB2B3LLG.mjs +0 -790
  190. package/dist/lib/node-esm/chunk-HB2B3LLG.mjs.map +0 -7
  191. package/dist/lib/node-esm/operation-resolver-LDNYS3DI.mjs +0 -204
  192. package/dist/lib/node-esm/operation-resolver-LDNYS3DI.mjs.map +0 -7
  193. package/dist/lib/node-esm/react-root-36UYFEEB.mjs +0 -22
  194. package/dist/lib/node-esm/react-root-36UYFEEB.mjs.map +0 -7
  195. package/dist/lib/node-esm/react-surface-CGHFVWU3.mjs +0 -40
  196. package/dist/lib/node-esm/react-surface-CGHFVWU3.mjs.map +0 -7
  197. package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs +0 -68
  198. package/dist/lib/node-esm/spotlight-dismiss-L5PCWIJG.mjs.map +0 -7
  199. package/dist/lib/node-esm/state-Q2ZA26W5.mjs +0 -48
  200. package/dist/lib/node-esm/state-Q2ZA26W5.mjs.map +0 -7
  201. package/dist/lib/node-esm/url-handler-DVAZZEUO.mjs +0 -55
  202. package/dist/lib/node-esm/url-handler-DVAZZEUO.mjs.map +0 -7
  203. package/dist/types/src/capabilities/operation-resolver/index.d.ts +0 -3
  204. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +0 -1
  205. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +0 -5
  206. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +0 -1
  207. package/dist/types/src/capabilities/react-root/index.d.ts +0 -6
  208. package/dist/types/src/capabilities/react-root/index.d.ts.map +0 -1
  209. package/dist/types/src/capabilities/react-root/react-root.d.ts.map +0 -1
  210. package/dist/types/src/capabilities/react-surface/index.d.ts +0 -3
  211. package/dist/types/src/capabilities/react-surface/index.d.ts.map +0 -1
  212. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +0 -5
  213. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +0 -1
  214. package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts +0 -3
  215. package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts.map +0 -1
  216. package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts.map +0 -1
  217. package/dist/types/src/capabilities/state/index.d.ts +0 -13
  218. package/dist/types/src/capabilities/state/index.d.ts.map +0 -1
  219. package/dist/types/src/capabilities/state/state.d.ts.map +0 -1
  220. package/dist/types/src/capabilities/url-handler/index.d.ts +0 -3
  221. package/dist/types/src/capabilities/url-handler/index.d.ts.map +0 -1
  222. package/dist/types/src/capabilities/url-handler/url-handler.d.ts +0 -10
  223. package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +0 -1
  224. package/dist/types/src/components/ContentError.d.ts +0 -5
  225. package/dist/types/src/components/ContentError.d.ts.map +0 -1
  226. package/dist/types/src/components/ContentLoading.d.ts +0 -3
  227. package/dist/types/src/components/ContentLoading.d.ts.map +0 -1
  228. package/dist/types/src/components/ContentLoading.stories.d.ts.map +0 -1
  229. package/dist/types/src/components/SimpleLayout/Banner.d.ts +0 -8
  230. package/dist/types/src/components/SimpleLayout/Banner.d.ts.map +0 -1
  231. package/dist/types/src/components/Workspace/Workspace.d.ts +0 -9
  232. package/dist/types/src/components/Workspace/Workspace.d.ts.map +0 -1
  233. package/dist/types/src/components/Workspace/index.d.ts +0 -2
  234. package/dist/types/src/components/Workspace/index.d.ts.map +0 -1
  235. package/src/capabilities/operation-resolver/index.ts +0 -10
  236. package/src/capabilities/operation-resolver/operation-resolver.ts +0 -215
  237. package/src/capabilities/react-root/index.ts +0 -7
  238. package/src/capabilities/react-surface/index.ts +0 -7
  239. package/src/capabilities/react-surface/react-surface.tsx +0 -40
  240. package/src/capabilities/spotlight-dismiss/index.ts +0 -7
  241. package/src/capabilities/state/index.ts +0 -9
  242. package/src/capabilities/url-handler/index.ts +0 -7
  243. package/src/capabilities/url-handler/url-handler.ts +0 -80
  244. package/src/components/ContentError.tsx +0 -23
  245. package/src/components/SimpleLayout/Banner.tsx +0 -113
  246. package/src/components/Workspace/Workspace.tsx +0 -115
@@ -0,0 +1,143 @@
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
+ import { MobileLayout } from '../MobileLayout';
16
+ import { AppBar, type AppBarProps } from './AppBar';
17
+
18
+ const buildEmptyActions = (): ActionGraphProps => ({ nodes: [], edges: [] });
19
+
20
+ const buildDefaultActions = (): ActionGraphProps => {
21
+ const result: ActionGraphProps = { nodes: [], edges: [] };
22
+ const actions = [
23
+ createMenuAction('action-edit.menu', () => console.log('Edit'), {
24
+ icon: 'ph--pencil--regular',
25
+ label: 'Edit',
26
+ }),
27
+ createMenuAction('action-share.menu', () => console.log('Share'), {
28
+ icon: 'ph--share--regular',
29
+ label: 'Share',
30
+ }),
31
+ createMenuAction('action-delete.menu', () => console.log('Delete'), {
32
+ icon: 'ph--trash--regular',
33
+ label: 'Delete',
34
+ }),
35
+ ];
36
+ result.nodes.push(...actions);
37
+ result.edges.push(...actions.map((a) => ({ source: 'root', target: a.id, relation: 'child' })));
38
+ return result;
39
+ };
40
+
41
+ type DefaultStoryProps = Omit<AppBarProps, 'actions'> & {
42
+ actions: ActionGraphProps;
43
+ };
44
+
45
+ const DefaultStory = ({ actions: actionsProp, ...props }: DefaultStoryProps) => {
46
+ const actions = useMemo(() => Atom.make(actionsProp).pipe(Atom.keepAlive), [actionsProp]);
47
+ return (
48
+ <MobileLayout.Root>
49
+ <AppBar {...props} actions={actions} />
50
+ </MobileLayout.Root>
51
+ );
52
+ };
53
+
54
+ const meta = {
55
+ title: 'plugins/plugin-simple-layout/components/AppBar',
56
+ render: DefaultStory,
57
+ decorators: [
58
+ withTheme(),
59
+ withLayout({
60
+ layout: 'column',
61
+ classNames: 'relative',
62
+ }),
63
+ withRegistry,
64
+ ],
65
+ parameters: {
66
+ layout: 'fullscreen',
67
+ translations,
68
+ },
69
+ } satisfies Meta<typeof DefaultStory>;
70
+
71
+ export default meta;
72
+
73
+ type Story = StoryObj<DefaultStoryProps>;
74
+
75
+ export const Default: Story = {
76
+ tags: ['test'],
77
+ args: {
78
+ actions: buildDefaultActions(),
79
+ title: 'Document Title',
80
+ showBackButton: true,
81
+ onAction: fn(),
82
+ onBack: fn(),
83
+ },
84
+ play: async ({ args, canvasElement }) => {
85
+ const canvas = within(canvasElement);
86
+
87
+ // Verify the banner renders with the correct title.
88
+ await expect(canvas.getByRole('banner')).toBeInTheDocument();
89
+ await expect(canvas.getByText('Document Title')).toBeInTheDocument();
90
+
91
+ // Test back button click.
92
+ const backButton = canvas.getByRole('button', { name: /back/i });
93
+ await expect(backButton).toBeInTheDocument();
94
+ await userEvent.click(backButton);
95
+ await expect(args.onBack).toHaveBeenCalledTimes(1);
96
+
97
+ // Test actions menu opens and action fires.
98
+ const menuTrigger = canvas.getByRole('button', { name: /actions/i });
99
+ await expect(menuTrigger).toBeInTheDocument();
100
+ await userEvent.click(menuTrigger);
101
+
102
+ // Wait for menu to open and click an action (menu items render in a portal).
103
+ const editAction = await screen.findByRole('menuitem', { name: /edit/i });
104
+ await userEvent.click(editAction);
105
+ await expect(args.onAction).toHaveBeenCalledTimes(1);
106
+ await expect((args.onAction as Mock).mock.calls[0][0]).toHaveProperty('id', 'action-edit.menu');
107
+ },
108
+ };
109
+
110
+ export const NoBackButton: Story = {
111
+ args: {
112
+ actions: buildDefaultActions(),
113
+ title: 'Home',
114
+ showBackButton: false,
115
+ onAction: (action) => console.log('Action:', action.id),
116
+ },
117
+ };
118
+
119
+ export const LongTitle: Story = {
120
+ args: {
121
+ actions: buildDefaultActions(),
122
+ title: 'This is a very long document title that should be truncated when it exceeds the available space',
123
+ showBackButton: true,
124
+ onBack: () => console.log('Back clicked'),
125
+ onAction: (action) => console.log('Action:', action.id),
126
+ },
127
+ };
128
+
129
+ export const NoActions: Story = {
130
+ args: {
131
+ actions: buildEmptyActions(),
132
+ title: 'Empty Document',
133
+ showBackButton: true,
134
+ onBack: () => console.log('Back clicked'),
135
+ onAction: (action) => console.log('Action:', action.id),
136
+ },
137
+ };
138
+
139
+ export const Empty: Story = {
140
+ args: {
141
+ actions: buildEmptyActions(),
142
+ },
143
+ };
@@ -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;
@@ -2,17 +2,18 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useMemo } from 'react';
5
+ import React, { useMemo } from 'react';
6
6
 
7
- import { Surface, useAppGraph } from '@dxos/app-framework/react';
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { type AppSurface, useAppGraph } from '@dxos/app-toolkit/ui';
8
9
  import { type Node, useNode } from '@dxos/plugin-graph';
9
- import { IconButton, Main as NaturalMain, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
- import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
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';
11
13
 
12
- import { useCompanions, useSimpleLayoutState } from '../../hooks';
13
- import { meta } from '../../meta';
14
- import { ContentError } from '../ContentError';
15
- import { ContentLoading } from '../ContentLoading';
14
+ import { useCompanions, useDrawerActions, useSimpleLayoutState } from '#hooks';
15
+
16
+ import { Loading } from '../Loading';
16
17
 
17
18
  const DRAWER_NAME = 'SimpleLayout.Drawer';
18
19
 
@@ -20,105 +21,54 @@ const DRAWER_NAME = 'SimpleLayout.Drawer';
20
21
  * Companion drawer component.
21
22
  */
22
23
  export const Drawer = () => {
23
- const { t } = useTranslation(meta.id);
24
- const { state, updateState } = useSimpleLayoutState();
25
24
  const { graph } = useAppGraph();
25
+ const { state: layoutState } = useSimpleLayoutState();
26
26
 
27
- const placeholder = useMemo(() => <ContentLoading />, []);
27
+ const placeholder = useMemo(() => <Loading />, []);
28
28
 
29
29
  // Get all companions for the current active (primary) item.
30
- const activeId = state.active ?? state.workspace;
30
+ const activeId = layoutState.active ?? layoutState.workspace;
31
31
  const companions = useCompanions(activeId);
32
- const { companionId, variant } = useSelectedCompanion(companions, state.companionVariant);
32
+ const { companionId, variant } = useSelectedCompanion(companions, layoutState.companionVariant);
33
33
 
34
34
  // Get node for the selected companion.
35
35
  const node = useNode(graph, companionId);
36
36
  const parentNode = useNode(graph, activeId);
37
37
 
38
38
  // Build Surface data for the companion content.
39
- const data = useMemo(() => {
40
- return (
41
- node && {
42
- attendableId: companionId,
43
- subject: node.data,
44
- companionTo: parentNode?.data,
45
- properties: node.properties,
46
- variant,
47
- }
48
- );
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
+ };
49
50
  }, [companionId, node, parentNode, variant]);
50
51
 
51
- // Handle tab click to switch companions.
52
- const handleTabClick = useCallback(
53
- (companion: Node.Node) => {
54
- const [, companionVariant] = companion.id.split(ATTENDABLE_PATH_SEPARATOR);
55
- updateState((s) => ({ ...s, companionVariant }));
56
- },
57
- [updateState],
58
- );
59
-
60
- // Handle expand/collapse toggle.
61
- const handleToggleExpand = useCallback(() => {
62
- updateState((s) => ({
63
- ...s,
64
- drawerState: s.drawerState === 'full' ? 'expanded' : 'full',
65
- }));
66
- }, [updateState]);
67
-
68
- // Handle close.
69
- const handleClose = useCallback(() => {
70
- updateState((s) => ({ ...s, drawerState: 'closed' }));
71
- }, [updateState]);
72
-
73
- const drawerState = state.drawerState ?? 'closed';
74
- if (drawerState === 'closed') {
75
- return null;
76
- }
77
-
78
- const isFullyExpanded = drawerState === 'full';
52
+ // Get drawer actions (tabs + toolbar buttons).
53
+ const { actions, onAction } = useDrawerActions(DRAWER_NAME);
54
+ const menuActions = useMenuActions(actions);
79
55
 
80
56
  return (
81
- <NaturalMain.Drawer label={t('drawer label')}>
82
- <Toolbar.Root>
83
- {/* TODO(thure): IMPORTANT: This is a tablist; it should be implemented as such. */}
84
- <div role='tablist' className='flex-1 min-is-0 overflow-x-auto scrollbar-none flex gap-1'>
85
- {/* TODO(burdon): Factor out in common with NavBar. */}
86
- {companions.map((companion) => (
87
- <IconButton
88
- key={companion.id}
89
- role='tab'
90
- aria-selected={companionId === companion.id}
91
- icon={companion.properties.icon}
92
- iconOnly
93
- label={toLocalizedString(companion.properties.label, t)}
94
- variant={companionId === companion.id ? 'primary' : 'ghost'}
95
- onClick={() => handleTabClick(companion)}
96
- />
97
- ))}
98
- </div>
99
- <Toolbar.Separator variant='gap' />
100
- <Toolbar.IconButton
101
- icon={isFullyExpanded ? 'ph--arrow-down--regular' : 'ph--arrow-up--regular'}
102
- iconOnly
103
- label={isFullyExpanded ? t('collapse drawer label') : t('expand drawer label')}
104
- onClick={handleToggleExpand}
105
- />
106
- <Toolbar.IconButton icon='ph--x--regular' iconOnly label={t('close drawer label')} onClick={handleClose} />
107
- </Toolbar.Root>
108
- {/* TODO(burdon): Fix containment. */}
109
- <Surface role='article' data={data} limit={1} fallback={ContentError} placeholder={placeholder} />
110
- </NaturalMain.Drawer>
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 role='article' data={data} limit={1} fallback={ErrorFallback} placeholder={placeholder} />
65
+ </Panel.Content>
66
+ </Panel.Root>
111
67
  );
112
68
  };
113
69
 
114
70
  Drawer.displayName = DRAWER_NAME;
115
71
 
116
- /** Parse entry ID to extract primary ID and variant. */
117
- const parseEntryId = (entryId: string) => {
118
- const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
119
- return { id, variant };
120
- };
121
-
122
72
  /**
123
73
  * Resolves which companion to show based on variant preference.
124
74
  * Falls back to first available if preferred variant not available.
@@ -131,10 +81,7 @@ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string
131
81
 
132
82
  // Try to find companion matching the preferred variant.
133
83
  if (preferredVariant) {
134
- const preferred = companions.find((c) => {
135
- const { variant } = parseEntryId(c.id);
136
- return variant === preferredVariant;
137
- });
84
+ const preferred = companions.find((c) => getLinkedVariant(c.id) === preferredVariant);
138
85
  if (preferred) {
139
86
  return preferred;
140
87
  }
@@ -145,7 +92,7 @@ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string
145
92
  }, [companions, preferredVariant]);
146
93
 
147
94
  const companionId = selectedCompanion?.id;
148
- const { variant } = parseEntryId(companionId ?? '');
95
+ const variant = companionId ? getLinkedVariant(companionId) : undefined;
149
96
 
150
97
  return { selectedCompanion, companionId, variant };
151
98
  };
@@ -4,17 +4,18 @@
4
4
 
5
5
  import React, { useMemo } from 'react';
6
6
 
7
- import { Surface, useAppGraph } from '@dxos/app-framework/react';
7
+ import { Surface } from '@dxos/app-framework/ui';
8
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
8
9
  import { useNode } from '@dxos/plugin-graph';
9
- import { Main as NaturalMain, useSidebars } from '@dxos/react-ui';
10
- import { mx } from '@dxos/ui-theme';
10
+ import { ErrorFallback, Panel } from '@dxos/react-ui';
11
+ import { useAttentionAttributes } from '@dxos/react-ui-attention';
11
12
 
12
- import { useSimpleLayoutState } from '../../hooks';
13
- import { ContentError } from '../ContentError';
14
- import { ContentLoading } from '../ContentLoading';
15
- import { useLoadDescendents } from '../hooks';
13
+ import { useAppBarProps, useNavbarActions, useSimpleLayoutState } from '#hooks';
16
14
 
17
- import { Banner } from './Banner';
15
+ import { useExpandPath } from '../hooks';
16
+ import { Loading } from '../Loading';
17
+ import { useMobileLayout } from '../MobileLayout';
18
+ import { AppBar } from './AppBar';
18
19
  import { NavBar } from './NavBar';
19
20
 
20
21
  const MAIN_NAME = 'SimpleLayout.Main';
@@ -24,15 +25,16 @@ const MAIN_NAME = 'SimpleLayout.Main';
24
25
  */
25
26
  export const Main = () => {
26
27
  const { state } = useSimpleLayoutState();
27
- const { graph } = useAppGraph();
28
28
  const id = state.active ?? state.workspace;
29
- const node = useNode(graph, id);
30
-
31
- // Ensures that children are loaded so that they are available to navigate to.
32
- useLoadDescendents(id);
29
+ const attentionAttrs = useAttentionAttributes(id);
30
+ const { keyboardOpen } = useMobileLayout(MAIN_NAME);
31
+ const { actions, onAction } = useNavbarActions();
32
+ const appBarProps = useAppBarProps();
33
33
 
34
- const placeholder = useMemo(() => <ContentLoading />, []);
34
+ const placeholder = useMemo(() => <Loading />, []);
35
35
 
36
+ const { graph } = useAppGraph();
37
+ const node = useNode(graph, id);
36
38
  const data = useMemo(() => {
37
39
  return (
38
40
  node && {
@@ -44,28 +46,32 @@ export const Main = () => {
44
46
  );
45
47
  }, [id, node, node?.data, node?.properties, state.popoverAnchorId]);
46
48
 
47
- const { drawerState } = useSidebars(MAIN_NAME);
48
- const showNavBar = !state.isPopover && drawerState === 'closed';
49
+ useExpandPath(id);
50
+
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';
49
53
 
50
54
  return (
51
- <NaturalMain.Content
52
- bounce
53
- classNames={mx('bs-full', 'pbs-[env(safe-area-inset-top)] pbe-[env(safe-area-inset-bottom)]')}
54
- >
55
- <div
56
- role='none'
57
- className={mx(
58
- 'grid bs-full overflow-hidden',
59
- showNavBar ? 'grid-rows-[min-content_1fr_min-content]' : 'grid-rows-[min-content_1fr]',
60
- )}
61
- >
62
- <Banner classNames='border-be border-separator' node={node} />
63
- <article className='bs-full overflow-hidden'>
64
- <Surface key={id} role='article' data={data} limit={1} fallback={ContentError} placeholder={placeholder} />
65
- </article>
66
- {showNavBar && <NavBar classNames='border-bs border-separator' activeId={id} />}
67
- </div>
68
- </NaturalMain.Content>
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
+ role='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>
69
75
  );
70
76
  };
71
77