@dxos/plugin-simple-layout 0.8.4-main.52d7546f51 → 0.8.4-main.6fa680abb7

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 (119) hide show
  1. package/dist/lib/browser/{chunk-O3BQBYMW.mjs → chunk-MDPEKLKR.mjs} +181 -183
  2. package/dist/lib/browser/chunk-MDPEKLKR.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-7VLT3S46.mjs → chunk-MRR7PXSM.mjs} +3 -3
  4. package/dist/lib/browser/{chunk-7VLT3S46.mjs.map → chunk-MRR7PXSM.mjs.map} +1 -1
  5. package/dist/lib/browser/index.mjs +6 -6
  6. package/dist/lib/browser/meta.json +1 -1
  7. package/dist/lib/browser/{operation-resolver-BYRIQOQT.mjs → operation-resolver-VTZ6HZ4B.mjs} +24 -35
  8. package/dist/lib/browser/operation-resolver-VTZ6HZ4B.mjs.map +7 -0
  9. package/dist/lib/browser/{react-root-GPTKI5H2.mjs → react-root-WVQYY2JA.mjs} +3 -3
  10. package/dist/lib/browser/{react-surface-LT5JJTPR.mjs → react-surface-VLBR37ED.mjs} +11 -8
  11. package/dist/lib/browser/{react-surface-LT5JJTPR.mjs.map → react-surface-VLBR37ED.mjs.map} +3 -3
  12. package/dist/lib/browser/{state-A3PGDWWZ.mjs → state-TXSMUWYI.mjs} +2 -2
  13. package/dist/lib/browser/{url-handler-HTIUY6WL.mjs → url-handler-RBRONH7S.mjs} +18 -19
  14. package/dist/lib/browser/url-handler-RBRONH7S.mjs.map +7 -0
  15. package/dist/lib/node-esm/{chunk-UAWM4B2S.mjs → chunk-DCKASLMP.mjs} +181 -183
  16. package/dist/lib/node-esm/chunk-DCKASLMP.mjs.map +7 -0
  17. package/dist/lib/node-esm/{chunk-VIDE5UMB.mjs → chunk-WMNTJ2MK.mjs} +3 -3
  18. package/dist/lib/node-esm/{chunk-VIDE5UMB.mjs.map → chunk-WMNTJ2MK.mjs.map} +1 -1
  19. package/dist/lib/node-esm/index.mjs +6 -6
  20. package/dist/lib/node-esm/meta.json +1 -1
  21. package/dist/lib/node-esm/{operation-resolver-BDTFNCS2.mjs → operation-resolver-R7CQ6ERU.mjs} +24 -35
  22. package/dist/lib/node-esm/operation-resolver-R7CQ6ERU.mjs.map +7 -0
  23. package/dist/lib/node-esm/{react-root-GRG2OAI2.mjs → react-root-XBNDM7BE.mjs} +3 -3
  24. package/dist/lib/node-esm/{react-surface-TCUSDIN2.mjs → react-surface-U5NHA367.mjs} +11 -8
  25. package/dist/lib/node-esm/{react-surface-TCUSDIN2.mjs.map → react-surface-U5NHA367.mjs.map} +3 -3
  26. package/dist/lib/node-esm/{state-ZCFZTTPL.mjs → state-JMX6FAG4.mjs} +2 -2
  27. package/dist/lib/node-esm/{url-handler-WBVVKVPC.mjs → url-handler-QSMCH3JB.mjs} +18 -19
  28. package/dist/lib/node-esm/url-handler-QSMCH3JB.mjs.map +7 -0
  29. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +1 -1
  30. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/react-root/react-root.d.ts +1 -1
  32. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +1 -1
  33. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
  34. package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts +1 -1
  35. package/dist/types/src/capabilities/spotlight-dismiss/index.d.ts.map +1 -1
  36. package/dist/types/src/capabilities/spotlight-dismiss/spotlight-dismiss.d.ts +1 -1
  37. package/dist/types/src/capabilities/state/index.d.ts +1 -1
  38. package/dist/types/src/capabilities/state/state.d.ts +1 -1
  39. package/dist/types/src/capabilities/url-handler/url-handler.d.ts +3 -3
  40. package/dist/types/src/capabilities/url-handler/url-handler.d.ts.map +1 -1
  41. package/dist/types/src/components/ContentError.stories.d.ts +1 -3
  42. package/dist/types/src/components/ContentError.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/ContentLoading/ContentLoading.d.ts.map +1 -0
  44. package/dist/types/src/components/ContentLoading/ContentLoading.stories.d.ts.map +1 -0
  45. package/dist/types/src/components/ContentLoading/index.d.ts +2 -0
  46. package/dist/types/src/components/ContentLoading/index.d.ts.map +1 -0
  47. package/dist/types/src/components/Home/Home.d.ts.map +1 -1
  48. package/dist/types/src/components/MobileLayout/MobileLayout.stories.d.ts.map +1 -1
  49. package/dist/types/src/components/NavBranch/NavBranch.d.ts +11 -0
  50. package/dist/types/src/components/NavBranch/NavBranch.d.ts.map +1 -0
  51. package/dist/types/src/components/NavBranch/index.d.ts +2 -0
  52. package/dist/types/src/components/NavBranch/index.d.ts.map +1 -0
  53. package/dist/types/src/components/Popover/Popover.d.ts.map +1 -1
  54. package/dist/types/src/components/SimpleLayout/AppBar.d.ts.map +1 -1
  55. package/dist/types/src/components/SimpleLayout/Drawer.d.ts.map +1 -1
  56. package/dist/types/src/components/SimpleLayout/Main.d.ts.map +1 -1
  57. package/dist/types/src/components/SimpleLayout/NavBar.d.ts.map +1 -1
  58. package/dist/types/src/components/hooks.d.ts +4 -2
  59. package/dist/types/src/components/hooks.d.ts.map +1 -1
  60. package/dist/types/src/components/index.d.ts +1 -1
  61. package/dist/types/src/hooks/actions.d.ts +3 -4
  62. package/dist/types/src/hooks/actions.d.ts.map +1 -1
  63. package/dist/types/src/hooks/useAppBarProps.d.ts.map +1 -1
  64. package/dist/types/src/hooks/useDrawerActions.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useNavbarActions.d.ts.map +1 -1
  66. package/dist/types/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +29 -28
  68. package/src/capabilities/operation-resolver/operation-resolver.ts +19 -34
  69. package/src/capabilities/react-surface/react-surface.tsx +8 -6
  70. package/src/capabilities/url-handler/url-handler.ts +11 -35
  71. package/src/components/ContentError.stories.tsx +7 -6
  72. package/src/components/{ContentLoading.stories.tsx → ContentLoading/ContentLoading.stories.tsx} +1 -1
  73. package/src/components/{ContentLoading.tsx → ContentLoading/ContentLoading.tsx} +1 -1
  74. package/src/components/ContentLoading/index.ts +5 -0
  75. package/src/components/Dialog/Dialog.tsx +3 -3
  76. package/src/components/Home/Home.tsx +30 -24
  77. package/src/components/MobileLayout/MobileLayout.stories.tsx +23 -19
  78. package/src/components/MobileLayout/MobileLayout.tsx +2 -2
  79. package/src/components/{Workspace/Workspace.tsx → NavBranch/NavBranch.tsx} +43 -32
  80. package/src/components/{Workspace → NavBranch}/index.ts +1 -1
  81. package/src/components/Popover/Popover.tsx +14 -4
  82. package/src/components/SimpleLayout/AppBar.stories.tsx +2 -2
  83. package/src/components/SimpleLayout/AppBar.tsx +14 -21
  84. package/src/components/SimpleLayout/Drawer.tsx +15 -21
  85. package/src/components/SimpleLayout/Main.tsx +11 -10
  86. package/src/components/SimpleLayout/NavBar.stories.tsx +8 -8
  87. package/src/components/SimpleLayout/NavBar.tsx +4 -10
  88. package/src/components/SimpleLayout/SimpleLayout.stories.tsx +2 -2
  89. package/src/components/SimpleLayout/SimpleLayout.tsx +1 -1
  90. package/src/components/hooks.ts +8 -8
  91. package/src/components/index.ts +1 -1
  92. package/src/hooks/actions.ts +15 -17
  93. package/src/hooks/useAppBarProps.ts +8 -5
  94. package/src/hooks/useCompanions.ts +1 -1
  95. package/src/hooks/useDrawerActions.ts +9 -7
  96. package/src/hooks/useNavbarActions.ts +10 -9
  97. package/src/meta.ts +1 -1
  98. package/src/types/capabilities.ts +1 -1
  99. package/dist/lib/browser/chunk-O3BQBYMW.mjs.map +0 -7
  100. package/dist/lib/browser/operation-resolver-BYRIQOQT.mjs.map +0 -7
  101. package/dist/lib/browser/url-handler-HTIUY6WL.mjs.map +0 -7
  102. package/dist/lib/node-esm/chunk-UAWM4B2S.mjs.map +0 -7
  103. package/dist/lib/node-esm/operation-resolver-BDTFNCS2.mjs.map +0 -7
  104. package/dist/lib/node-esm/url-handler-WBVVKVPC.mjs.map +0 -7
  105. package/dist/types/src/components/ContentError.d.ts +0 -5
  106. package/dist/types/src/components/ContentError.d.ts.map +0 -1
  107. package/dist/types/src/components/ContentLoading.d.ts.map +0 -1
  108. package/dist/types/src/components/ContentLoading.stories.d.ts.map +0 -1
  109. package/dist/types/src/components/Workspace/Workspace.d.ts +0 -11
  110. package/dist/types/src/components/Workspace/Workspace.d.ts.map +0 -1
  111. package/dist/types/src/components/Workspace/index.d.ts +0 -2
  112. package/dist/types/src/components/Workspace/index.d.ts.map +0 -1
  113. package/src/components/ContentError.tsx +0 -23
  114. /package/dist/lib/browser/{react-root-GPTKI5H2.mjs.map → react-root-WVQYY2JA.mjs.map} +0 -0
  115. /package/dist/lib/browser/{state-A3PGDWWZ.mjs.map → state-TXSMUWYI.mjs.map} +0 -0
  116. /package/dist/lib/node-esm/{react-root-GRG2OAI2.mjs.map → react-root-XBNDM7BE.mjs.map} +0 -0
  117. /package/dist/lib/node-esm/{state-ZCFZTTPL.mjs.map → state-JMX6FAG4.mjs.map} +0 -0
  118. /package/dist/types/src/components/{ContentLoading.d.ts → ContentLoading/ContentLoading.d.ts} +0 -0
  119. /package/dist/types/src/components/{ContentLoading.stories.d.ts → ContentLoading/ContentLoading.stories.d.ts} +0 -0
@@ -2,65 +2,76 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useCallback, useEffect, useRef } from 'react';
5
+ import React, { useCallback, useEffect, useMemo, useRef } from 'react';
6
6
 
7
7
  import { useOperationInvoker } from '@dxos/app-framework/ui';
8
8
  import { LayoutOperation } from '@dxos/app-toolkit';
9
9
  import { useAppGraph } from '@dxos/app-toolkit/ui';
10
10
  import { type Node, useConnections } from '@dxos/plugin-graph';
11
- import { Avatar, Icon, Layout, ScrollArea, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
12
- import { Card, Mosaic, type StackTileComponent } from '@dxos/react-ui-mosaic';
11
+ import { Avatar, Icon, Panel, ScrollArea, Toolbar, toLocalizedString, useTranslation } from '@dxos/react-ui';
12
+ import { Card } from '@dxos/react-ui';
13
+ import { Mosaic, type MosaicStackTileComponent } from '@dxos/react-ui-mosaic';
13
14
  import { SearchList, useSearchListItem, useSearchListResults } from '@dxos/react-ui-searchlist';
14
15
  import { mx } from '@dxos/ui-theme';
15
16
 
16
17
  import { meta } from '../../meta';
17
- import { useLoadDescendents } from '../hooks';
18
+ import { useExpandPath } from '../hooks';
18
19
 
19
- export type WorkspaceProps = {
20
+ export type NavBranchProps = {
20
21
  id: string;
21
22
  };
22
23
 
23
24
  /**
24
- * Displays the contents of a workspace disposition graph node as a searchable list.
25
- * Shows direct children of the workspace with icons and labels,
26
- * allowing users to filter via search and navigate by selecting an item.
25
+ * Renders the children of a graph branch node as a searchable mosaic list.
26
+ * Used for any node with `role: 'branch'` or a workspace disposition, including
27
+ * spaces, collection sections, type sections, and schema nodes.
27
28
  */
28
- export const Workspace = ({ id }: WorkspaceProps) => {
29
+ export const NavBranch = ({ id }: NavBranchProps) => {
29
30
  const { t } = useTranslation(meta.id);
30
31
  const { graph } = useAppGraph();
31
32
 
32
- // Expand the workspace node to load its children.
33
- useLoadDescendents(id);
33
+ useExpandPath(id);
34
34
 
35
- // Get direct children of the workspace node.
36
- const children = useConnections(graph, id, 'outbound');
35
+ const children = useConnections(graph, id, 'child');
36
+
37
+ // TODO(wittjosiah): Move alternate-tree nodes to a non-child relation so they don't need filtering.
38
+ const visibleChildren = useMemo(
39
+ () => children.filter((node) => node.properties.disposition !== 'alternate-tree'),
40
+ [children],
41
+ );
37
42
 
38
43
  const { results, handleSearch } = useSearchListResults({
39
- items: children,
44
+ items: visibleChildren,
40
45
  extract: (child) => toLocalizedString(child.properties.label, t),
41
46
  });
42
47
 
43
48
  return (
44
- <Layout.Main toolbar>
45
- <SearchList.Root onSearch={handleSearch}>
46
- <Toolbar.Root>
47
- <SearchList.Input placeholder={t('search placeholder')} autoFocus />
48
- </Toolbar.Root>
49
- <SearchList.Content>
50
- <Mosaic.Container asChild>
51
- <ScrollArea.Root orientation='vertical'>
52
- <ScrollArea.Viewport classNames='p-2'>
53
- <Mosaic.Stack items={results} getId={(child) => child.id} Tile={WorkspaceChildTile} />
54
- </ScrollArea.Viewport>
55
- </ScrollArea.Root>
56
- </Mosaic.Container>
57
- </SearchList.Content>
58
- </SearchList.Root>
59
- </Layout.Main>
49
+ <SearchList.Root onSearch={handleSearch}>
50
+ <Panel.Root>
51
+ <Panel.Toolbar asChild>
52
+ <Toolbar.Root>
53
+ {/* TODO(wittjosiah): Search should be pluggable. Must support searching via ECHO query inside a space. */}
54
+ <SearchList.Input placeholder={t('search placeholder')} autoFocus />
55
+ </Toolbar.Root>
56
+ </Panel.Toolbar>
57
+ <Panel.Content asChild>
58
+ <SearchList.Content>
59
+ <Mosaic.Container asChild>
60
+ <ScrollArea.Root orientation='vertical'>
61
+ <ScrollArea.Viewport classNames='p-2'>
62
+ <Mosaic.Stack items={results} getId={(child) => child.id} Tile={NavBranchTile} />
63
+ </ScrollArea.Viewport>
64
+ </ScrollArea.Root>
65
+ </Mosaic.Container>
66
+ </SearchList.Content>
67
+ </Panel.Content>
68
+ </Panel.Root>
69
+ </SearchList.Root>
60
70
  );
61
71
  };
62
72
 
63
- const WorkspaceChildTile: StackTileComponent<Node.Node> = ({ data }) => {
73
+ const NavBranchTile: MosaicStackTileComponent<Node.Node> = (props) => {
74
+ const data = props.data;
64
75
  const { t } = useTranslation(meta.id);
65
76
  const { invokeSync } = useOperationInvoker();
66
77
  const ref = useRef<HTMLDivElement>(null);
@@ -97,7 +108,7 @@ const WorkspaceChildTile: StackTileComponent<Node.Node> = ({ data }) => {
97
108
  fullWidth
98
109
  tabIndex={-1} // TODO(burdon): Use Mosaic.Focus.
99
110
  data-selected={isSelected}
100
- classNames={mx('dx-focus-ring', isSelected && 'bg-hoverOverlay')}
111
+ classNames={mx('dx-focus-ring', isSelected && 'bg-hover-overlay')}
101
112
  onClick={handleSelect}
102
113
  >
103
114
  <Card.Toolbar density='coarse'>
@@ -2,4 +2,4 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- export * from './Workspace';
5
+ export * from './NavBranch';
@@ -6,8 +6,9 @@ import { createContext } from '@radix-ui/react-context';
6
6
  import React, { type PropsWithChildren, useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
7
 
8
8
  import { Surface } from '@dxos/app-framework/ui';
9
+ import { useObjectNavigate } from '@dxos/app-toolkit/ui';
9
10
  import { Popover, type PopoverContentInteractOutsideEvent, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
- import { Card } from '@dxos/react-ui-mosaic';
11
+ import { Card } from '@dxos/react-ui';
11
12
 
12
13
  import { useSimpleLayoutState } from '../../hooks';
13
14
  import { meta } from '../../meta';
@@ -53,10 +54,15 @@ export const PopoverRoot = ({ children }: PropsWithChildren) => {
53
54
  );
54
55
  };
55
56
 
57
+ // Extracts the subject from popover content if it has one, otherwise returns the content as-is.
58
+ const getPopoverSubject = (content: unknown): unknown =>
59
+ content && typeof content === 'object' && 'subject' in content ? (content as { subject: unknown }).subject : content;
60
+
56
61
  export const PopoverContent = () => {
57
62
  const { t } = useTranslation(meta.id);
58
63
  const { state, updateState } = useSimpleLayoutState();
59
64
  const { setOpen } = useLayoutPopoverContext('PopoverContent');
65
+ const handleNavigate = useObjectNavigate(getPopoverSubject(state.popoverContent));
60
66
 
61
67
  const handleClose = useCallback(() => {
62
68
  setOpen(false);
@@ -102,12 +108,16 @@ export const PopoverContent = () => {
102
108
  <Popover.Viewport>
103
109
  {state.popoverKind === 'base' && <Surface.Surface role='popover' data={state.popoverContent} limit={1} />}
104
110
  {state.popoverKind === 'card' && (
105
- <Card.Root border={false} classNames='popover-card-max-width'>
111
+ <Card.Root border={false} classNames='dx-card-popover'>
106
112
  <Card.Toolbar>
107
113
  {/* TODO(wittjosiah): Cleaner way to handle no drag handle in toolbar? */}
108
114
  <span />
109
- {state.popoverTitle ? <Card.Title>{toLocalizedString(state.popoverTitle, t)}</Card.Title> : <span />}
110
- <Card.Close onClick={handleClose} />
115
+ {state.popoverTitle ? (
116
+ <Card.Title onClick={handleNavigate}>{toLocalizedString(state.popoverTitle, t)}</Card.Title>
117
+ ) : (
118
+ <span />
119
+ )}
120
+ <Card.CloseIconButton onClick={handleClose} />
111
121
  </Card.Toolbar>
112
122
  <Surface.Surface role='card--content' data={state.popoverContent} limit={1} />
113
123
  </Card.Root>
@@ -35,7 +35,7 @@ const buildDefaultActions = (): ActionGraphProps => {
35
35
  }),
36
36
  ];
37
37
  result.nodes.push(...actions);
38
- result.edges.push(...actions.map((a) => ({ source: 'root', target: a.id })));
38
+ result.edges.push(...actions.map((a) => ({ source: 'root', target: a.id, relation: 'child' })));
39
39
  return result;
40
40
  };
41
41
 
@@ -53,7 +53,7 @@ const DefaultStory = ({ actions: actionsProp, ...props }: StoryProps) => {
53
53
  };
54
54
 
55
55
  const meta = {
56
- title: 'plugins/plugin-simple-layout/AppBar',
56
+ title: 'plugins/plugin-simple-layout/components/AppBar',
57
57
  render: DefaultStory,
58
58
  decorators: [
59
59
  withTheme(),
@@ -6,13 +6,7 @@ import { type Atom, useAtomValue } from '@effect-atom/atom-react';
6
6
  import React, { Fragment } from 'react';
7
7
 
8
8
  import { IconButton, Popover, type ThemedClassName, Toolbar, useTranslation } from '@dxos/react-ui';
9
- import {
10
- type ActionExecutor,
11
- type ActionGraphProps,
12
- DropdownMenu,
13
- MenuProvider,
14
- useMenuActions,
15
- } from '@dxos/react-ui-menu';
9
+ import { type ActionExecutor, type ActionGraphProps, Menu, useMenuActions } from '@dxos/react-ui-menu';
16
10
  import { mx, osTranslations } from '@dxos/ui-theme';
17
11
 
18
12
  import { meta } from '../../meta';
@@ -63,8 +57,8 @@ export const AppBar = ({
63
57
  <Toolbar.Root
64
58
  role='banner'
65
59
  classNames={mx(
66
- 'grid grid-cols-[var(--rail-size)_1fr_var(--rail-size)] bs-[var(--rail-action)] items-center',
67
- 'density-fine',
60
+ 'grid grid-cols-[var(--dx-rail-size)_1fr_var(--dx-rail-size)] items-center',
61
+ 'dx-density-fine',
68
62
  classNames,
69
63
  )}
70
64
  >
@@ -78,18 +72,17 @@ export const AppBar = ({
78
72
  <h1 className='text-center truncate font-thin uppercase'>{displayTitle}</h1>
79
73
  {hasActions ? (
80
74
  <AnchorRoot>
81
- <MenuProvider {...menu} onAction={onAction}>
82
- <DropdownMenu.Root caller={meta.id}>
83
- <DropdownMenu.Trigger asChild>
84
- <IconButton
85
- variant='ghost'
86
- icon='ph--dots-three-vertical--regular'
87
- iconOnly
88
- label={t('actions menu label')}
89
- />
90
- </DropdownMenu.Trigger>
91
- </DropdownMenu.Root>
92
- </MenuProvider>
75
+ <Menu.Root {...menu} caller={meta.id} onAction={onAction}>
76
+ <Menu.Trigger asChild>
77
+ <IconButton
78
+ variant='ghost'
79
+ icon='ph--dots-three-vertical--regular'
80
+ iconOnly
81
+ label={t('actions menu label')}
82
+ />
83
+ </Menu.Trigger>
84
+ <Menu.Content />
85
+ </Menu.Root>
93
86
  </AnchorRoot>
94
87
  ) : (
95
88
  <span />
@@ -5,14 +5,13 @@
5
5
  import React, { useMemo } from 'react';
6
6
 
7
7
  import { Surface } from '@dxos/app-framework/ui';
8
+ import { getCompanionVariant } from '@dxos/app-toolkit';
8
9
  import { useAppGraph } from '@dxos/app-toolkit/ui';
9
10
  import { type Node, useNode } from '@dxos/plugin-graph';
10
- import { Layout } from '@dxos/react-ui';
11
- import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
12
- import { MenuProvider, ToolbarMenu, useMenuActions } from '@dxos/react-ui-menu';
11
+ import { ErrorFallback, Panel } from '@dxos/react-ui';
12
+ import { Menu, useMenuActions } from '@dxos/react-ui-menu';
13
13
 
14
14
  import { useCompanions, useDrawerActions, useSimpleLayoutState } from '../../hooks';
15
- import { ContentError } from '../ContentError';
16
15
  import { ContentLoading } from '../ContentLoading';
17
16
 
18
17
  const DRAWER_NAME = 'SimpleLayout.Drawer';
@@ -53,23 +52,21 @@ export const Drawer = () => {
53
52
  const menu = useMenuActions(actions);
54
53
 
55
54
  return (
56
- <Layout.Main toolbar>
57
- <MenuProvider {...menu} onAction={onAction} alwaysActive>
58
- <ToolbarMenu density='coarse' />
59
- </MenuProvider>
60
- <Surface.Surface role='article' data={data} limit={1} fallback={ContentError} placeholder={placeholder} />
61
- </Layout.Main>
55
+ <Panel.Root>
56
+ <Panel.Toolbar>
57
+ <Menu.Root {...menu} alwaysActive onAction={onAction}>
58
+ <Menu.Toolbar density='coarse' />
59
+ </Menu.Root>
60
+ </Panel.Toolbar>
61
+ <Panel.Content asChild>
62
+ <Surface.Surface role='article' data={data} limit={1} fallback={ErrorFallback} placeholder={placeholder} />
63
+ </Panel.Content>
64
+ </Panel.Root>
62
65
  );
63
66
  };
64
67
 
65
68
  Drawer.displayName = DRAWER_NAME;
66
69
 
67
- /** Parse entry ID to extract primary ID and variant. */
68
- const parseEntryId = (entryId: string) => {
69
- const [id, variant] = entryId.split(ATTENDABLE_PATH_SEPARATOR);
70
- return { id, variant };
71
- };
72
-
73
70
  /**
74
71
  * Resolves which companion to show based on variant preference.
75
72
  * Falls back to first available if preferred variant not available.
@@ -82,10 +79,7 @@ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string
82
79
 
83
80
  // Try to find companion matching the preferred variant.
84
81
  if (preferredVariant) {
85
- const preferred = companions.find((c) => {
86
- const { variant } = parseEntryId(c.id);
87
- return variant === preferredVariant;
88
- });
82
+ const preferred = companions.find((c) => getCompanionVariant(c.id) === preferredVariant);
89
83
  if (preferred) {
90
84
  return preferred;
91
85
  }
@@ -96,7 +90,7 @@ const useSelectedCompanion = (companions: Node.Node[], preferredVariant?: string
96
90
  }, [companions, preferredVariant]);
97
91
 
98
92
  const companionId = selectedCompanion?.id;
99
- const { variant } = parseEntryId(companionId ?? '');
93
+ const variant = companionId ? getCompanionVariant(companionId) : undefined;
100
94
 
101
95
  return { selectedCompanion, companionId, variant };
102
96
  };
@@ -7,14 +7,14 @@ import React, { useMemo } from 'react';
7
7
  import { Surface } from '@dxos/app-framework/ui';
8
8
  import { useAppGraph } from '@dxos/app-toolkit/ui';
9
9
  import { useNode } from '@dxos/plugin-graph';
10
+ import { ErrorFallback } from '@dxos/react-ui';
10
11
  import { useAttentionAttributes } from '@dxos/react-ui-attention';
11
12
  import { mx } from '@dxos/ui-theme';
12
13
 
13
14
  import { useAppBarProps, useNavbarActions, useSimpleLayoutState } from '../../hooks';
14
- import { ContentError } from '../ContentError';
15
15
  import { ContentLoading } from '../ContentLoading';
16
- import { useLoadDescendents } from '../hooks';
17
- import { useMobileLayout } from '../MobileLayout/MobileLayout';
16
+ import { useExpandPath } from '../hooks';
17
+ import { useMobileLayout } from '../MobileLayout';
18
18
 
19
19
  import { AppBar } from './AppBar';
20
20
  import { NavBar } from './NavBar';
@@ -47,8 +47,7 @@ export const Main = () => {
47
47
  );
48
48
  }, [id, node, node?.data, node?.properties, state.popoverAnchorId]);
49
49
 
50
- // Ensures that children are loaded so that they are available to navigate to.
51
- useLoadDescendents(id);
50
+ useExpandPath(id);
52
51
 
53
52
  // TODO(burdon): BUG: When showing ANY statusbar the size progressively shrinks when the keyboard opens/closes.
54
53
  const showNavBar = !keyboardOpen && !state.isPopover && state.drawerState === 'closed';
@@ -57,23 +56,25 @@ export const Main = () => {
57
56
  <div
58
57
  role='none'
59
58
  className={mx(
60
- 'bs-full grid overflow-hidden bg-toolbarSurface',
61
- showNavBar ? 'grid-rows-[var(--rail-action)_1fr_var(--toolbar-size)]' : 'grid-rows-[var(--rail-action)_1fr]',
59
+ 'h-full grid overflow-hidden bg-toolbar-surface',
60
+ showNavBar
61
+ ? 'grid-rows-[var(--dx-rail-action)_1fr_var(--dx-toolbar-size)]'
62
+ : 'grid-rows-[var(--dx-rail-action)_1fr]',
62
63
  )}
63
64
  {...attentionAttrs}
64
65
  >
65
66
  <AppBar {...appBarProps} />
66
- <article className='bs-full overflow-hidden bg-baseSurface'>
67
+ <article className='h-full overflow-hidden bg-base-surface'>
67
68
  <Surface.Surface
68
69
  key={id}
69
70
  role='article'
70
71
  data={data}
71
72
  limit={1}
72
- fallback={ContentError}
73
+ fallback={ErrorFallback}
73
74
  placeholder={placeholder}
74
75
  />
75
76
  </article>
76
- {showNavBar && <NavBar classNames='border-bs border-subduedSeparator' actions={actions} onAction={onAction} />}
77
+ {showNavBar && <NavBar classNames='border-y border-subdued-separator' actions={actions} onAction={onAction} />}
77
78
  </div>
78
79
  );
79
80
  };
@@ -7,7 +7,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite';
7
7
  import React, { useMemo } from 'react';
8
8
  import { type Mock, expect, fn, screen, userEvent, within } from 'storybook/test';
9
9
 
10
- import { withTheme } from '@dxos/react-ui/testing';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
11
  import { type ActionGraphProps, createGapSeparator, createMenuAction, createMenuItemGroup } from '@dxos/react-ui-menu';
12
12
  import { withRegistry } from '@dxos/storybook-utils';
13
13
 
@@ -39,7 +39,7 @@ const buildCompanionOnlyActions = (): ActionGraphProps => {
39
39
  }),
40
40
  ];
41
41
  result.nodes.push(...companions);
42
- result.edges.push(...companions.map((c) => ({ source: 'root', target: c.id })));
42
+ result.edges.push(...companions.map((c) => ({ source: 'root', target: c.id, relation: 'child' })));
43
43
  return result;
44
44
  };
45
45
 
@@ -81,18 +81,18 @@ const buildDefaultActions = (): ActionGraphProps => {
81
81
  ];
82
82
  result.nodes.push(...companions, ...gapSeparator.nodes, mainMenuGroup, ...menuActions);
83
83
  result.edges.push(
84
- ...companions.map((c) => ({ source: 'root', target: c.id })),
84
+ ...companions.map((c) => ({ source: 'root', target: c.id, relation: 'child' })),
85
85
  ...gapSeparator.edges,
86
- { source: 'root', target: mainMenuGroup.id },
87
- ...menuActions.map((action) => ({ source: MAIN_MENU_GROUP_ID, target: action.id })),
86
+ { source: 'root', target: mainMenuGroup.id, relation: 'child' },
87
+ ...menuActions.map((action) => ({ source: MAIN_MENU_GROUP_ID, target: action.id, relation: 'child' })),
88
88
  );
89
89
  return result;
90
90
  };
91
91
 
92
92
  const meta = {
93
- title: 'plugins/plugin-simple-layout/NavBar',
93
+ title: 'plugins/plugin-simple-layout/components/NavBar',
94
94
  component: NavBar,
95
- decorators: [withTheme(), withRegistry],
95
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withRegistry],
96
96
  parameters: {
97
97
  layout: 'fullscreen',
98
98
  translations,
@@ -106,7 +106,7 @@ type Story = StoryObj<typeof meta>;
106
106
  const DefaultStory = ({ onAction }: { onAction: (action: { id: string }) => void }) => {
107
107
  const actions = useMemo(() => Atom.make(buildDefaultActions()).pipe(Atom.keepAlive), []);
108
108
 
109
- return <NavBar classNames='border-bs border-separator' actions={actions} onAction={onAction} />;
109
+ return <NavBar classNames='border-y border-separator' actions={actions} onAction={onAction} />;
110
110
  };
111
111
 
112
112
  export const Default: Story = {
@@ -6,13 +6,7 @@ import { type Atom } from '@effect-atom/atom-react';
6
6
  import React from 'react';
7
7
 
8
8
  import { type ThemedClassName } from '@dxos/react-ui';
9
- import {
10
- type ActionExecutor,
11
- type ActionGraphProps,
12
- MenuProvider,
13
- ToolbarMenu,
14
- useMenuActions,
15
- } from '@dxos/react-ui-menu';
9
+ import { type ActionExecutor, type ActionGraphProps, Menu, useMenuActions } from '@dxos/react-ui-menu';
16
10
  import { mx } from '@dxos/ui-theme';
17
11
 
18
12
  const NAVBAR_NAME = 'SimpleLayout.NavBar';
@@ -31,9 +25,9 @@ export const NavBar = ({ classNames, actions, onAction }: NavBarProps) => {
31
25
  const menu = useMenuActions(actions);
32
26
 
33
27
  return (
34
- <MenuProvider {...menu} onAction={onAction} alwaysActive>
35
- <ToolbarMenu density='coarse' classNames={mx(classNames)} />
36
- </MenuProvider>
28
+ <Menu.Root {...menu} alwaysActive onAction={onAction}>
29
+ <Menu.Toolbar density='coarse' classNames={mx(classNames)} />
30
+ </Menu.Root>
37
31
  );
38
32
  };
39
33
 
@@ -8,6 +8,7 @@ import * as Effect from 'effect/Effect';
8
8
  import { ActivationEvents, Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
9
  import { withPluginManager } from '@dxos/app-framework/testing';
10
10
  import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
11
+ import { Collection } from '@dxos/echo';
11
12
  import { ClientOperation, ClientPlugin } from '@dxos/plugin-client';
12
13
  import { SearchPlugin } from '@dxos/plugin-search';
13
14
  import { SpacePlugin } from '@dxos/plugin-space';
@@ -15,7 +16,6 @@ import { SpaceOperation } from '@dxos/plugin-space/types';
15
16
  import { corePlugins } from '@dxos/plugin-testing';
16
17
  import { withLayout, withTheme } from '@dxos/react-ui/testing';
17
18
  import { translations as searchTranslation } from '@dxos/react-ui-searchlist';
18
- import { Collection } from '@dxos/schema';
19
19
 
20
20
  import { OperationResolver, type SimpleLayoutStateOptions, State } from '../../capabilities';
21
21
  import { meta as pluginMeta } from '../../meta';
@@ -80,7 +80,7 @@ const createPluginManager = ({ isPopover }: { isPopover: boolean }) => {
80
80
  };
81
81
 
82
82
  const meta = {
83
- title: 'plugins/plugin-simple-layout/SimpleLayout',
83
+ title: 'plugins/plugin-simple-layout/components/SimpleLayout',
84
84
  component: SimpleLayout,
85
85
  parameters: {
86
86
  layout: 'fullscreen',
@@ -36,7 +36,7 @@ export const SimpleLayout = () => {
36
36
  return (
37
37
  <Mosaic.Root classNames='contents'>
38
38
  <MobileLayout.Root
39
- classNames='bg-toolbarSurface'
39
+ classNames='bg-toolbar-surface'
40
40
  onKeyboardOpenChange={(keyboardOpen: boolean) => setKeyboardOpen(keyboardOpen)}
41
41
  >
42
42
  <MobileLayout.Panel safe={{ top: true, bottom: splitterMode === 'upper' }}>
@@ -4,23 +4,23 @@
4
4
 
5
5
  import { useEffect } from 'react';
6
6
 
7
+ import { expandAttendableId } from '@dxos/react-ui-attention';
7
8
  import { useAppGraph } from '@dxos/app-toolkit/ui';
8
9
  import { Graph } from '@dxos/plugin-graph';
9
10
 
10
11
  /**
11
- * Hook to expand graph nodes two levels deep when directly linked to.
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.
12
15
  */
13
- export const useLoadDescendents = (nodeId?: string) => {
16
+ export const useExpandPath = (nodeId?: string) => {
14
17
  const { graph } = useAppGraph();
15
18
 
16
19
  useEffect(() => {
17
20
  if (nodeId) {
18
- // First level: expand the node itself.
19
- Graph.expand(graph, nodeId, 'outbound');
20
- // Second level: expand each child.
21
- Graph.getConnections(graph, nodeId, 'outbound').forEach((child) => {
22
- Graph.expand(graph, child.id, 'outbound');
23
- });
21
+ for (const prefix of expandAttendableId(nodeId)) {
22
+ Graph.expand(graph, prefix, 'child');
23
+ }
24
24
  }
25
25
  }, [nodeId, graph]);
26
26
  };
@@ -7,4 +7,4 @@ export * from './Home';
7
7
  export * from './MobileLayout';
8
8
  export * from './Popover';
9
9
  export * from './SimpleLayout';
10
- export * from './Workspace';
10
+ export * from './NavBranch';
@@ -5,25 +5,23 @@
5
5
  import { type Atom } from '@effect-atom/atom-react';
6
6
  import * as Effect from 'effect/Effect';
7
7
 
8
- import { type Capabilities } from '@dxos/app-framework';
9
- import { type AppCapabilities, LayoutOperation } from '@dxos/app-toolkit';
8
+ import { type AppCapabilities, getCompanionVariant } from '@dxos/app-toolkit';
10
9
  import { Node } from '@dxos/plugin-graph';
11
- import { ATTENDABLE_PATH_SEPARATOR } from '@dxos/react-ui-attention';
12
10
  import { type ActionGraphProps } from '@dxos/react-ui-menu';
13
11
  import { byPosition } from '@dxos/util';
14
12
 
15
13
  import { type SimpleLayoutState } from '../types';
16
14
 
17
15
  // TODO(wittjosiah): Factor out to shared location with plugin-deck.
18
- export const PLANK_COMPANION_TYPE = 'dxos.org/plugin/deck/plank-companion';
16
+ export const PLANK_COMPANION_TYPE = 'org.dxos.plugin.deck.plank-companion';
19
17
 
20
18
  export type CompanionActionsConfig = {
21
19
  /** Prefix for companion action IDs (e.g. 'navbar' or 'drawer') */
22
20
  idPrefix: string;
23
21
  /** Optional: highlight companion with this variant */
24
22
  selectedVariant?: string;
25
- /** invokeSync function for dispatching operations */
26
- invokeSync: Capabilities.OperationInvoker['invokeSync'];
23
+ /** State updater for toggling the drawer. */
24
+ updateState: (fn: (state: SimpleLayoutState) => SimpleLayoutState) => void;
27
25
  };
28
26
 
29
27
  /**
@@ -37,14 +35,14 @@ export const createCompanionActions = (
37
35
  get: (atom: Atom.Atom<any>) => any,
38
36
  config: CompanionActionsConfig,
39
37
  ): Pick<ActionGraphProps, 'nodes' | 'edges'> => {
40
- const { idPrefix, selectedVariant, invokeSync } = config;
38
+ const { idPrefix, selectedVariant, updateState } = config;
41
39
 
42
40
  // Derive activeId from state atom.
43
41
  const state = get(stateAtom);
44
42
  const activeId = state.active ?? state.workspace;
45
43
 
46
44
  // Get companions from graph connections for activeId.
47
- const activeConnections = activeId ? get(graph.connections(activeId)) : [];
45
+ const activeConnections = activeId ? get(graph.connections(activeId, 'child')) : [];
48
46
  const companions = activeConnections
49
47
  .filter((node: Node.Node) => node.type === PLANK_COMPANION_TYPE)
50
48
  .toSorted((a: Node.Node, b: Node.Node) => byPosition(a.properties, b.properties));
@@ -52,12 +50,8 @@ export const createCompanionActions = (
52
50
  const nodes: ActionGraphProps['nodes'] = [];
53
51
  const edges: ActionGraphProps['edges'] = [];
54
52
 
55
- // Add companion actions.
56
- // TODO(burdon): Cap at 6 items.
57
53
  companions.forEach((companion: Node.Node) => {
58
- // Extract variant for highlighting if needed.
59
- const [, companionVariant] = companion.id.split(ATTENDABLE_PATH_SEPARATOR);
60
-
54
+ const companionVariant = getCompanionVariant(companion.id);
61
55
  const companionAction = {
62
56
  id: `${idPrefix}-companion-${companion.id}`,
63
57
  type: Node.ActionType,
@@ -65,20 +59,24 @@ export const createCompanionActions = (
65
59
  icon: companion.properties.icon ?? 'ph--placeholder--regular',
66
60
  label: companion.properties.label,
67
61
  iconOnly: true,
68
- // Conditionally add variant highlighting.
69
62
  ...(selectedVariant !== undefined && {
70
63
  variant: selectedVariant === companionVariant ? 'primary' : 'ghost',
71
64
  }),
72
65
  },
73
66
  data: () =>
74
67
  Effect.sync(() =>
75
- invokeSync(LayoutOperation.Open, {
76
- subject: [companion.id],
68
+ updateState((current) => {
69
+ const closing = current.companionVariant === companionVariant && current.drawerState !== 'closed';
70
+ return {
71
+ ...current,
72
+ companionVariant: closing ? undefined : companionVariant,
73
+ drawerState: closing ? 'closed' : 'open',
74
+ };
77
75
  }),
78
76
  ),
79
77
  };
80
78
  nodes.push(companionAction);
81
- edges.push({ source: 'root', target: companionAction.id });
79
+ edges.push({ source: 'root', target: companionAction.id, relation: 'child' });
82
80
  });
83
81
 
84
82
  return { nodes, edges };