@dxos/plugin-navtree 0.8.2-main.fbd8ed0 → 0.8.2-staging.7ac8446

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 (149) hide show
  1. package/dist/lib/browser/{app-graph-builder-P7AY3K72.mjs → app-graph-builder-26D3MUCA.mjs} +6 -7
  2. package/dist/lib/browser/app-graph-builder-26D3MUCA.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-TLQLMZXU.mjs → chunk-4RQJXI4D.mjs} +1 -1
  4. package/dist/lib/browser/chunk-4RQJXI4D.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-UM2C5XK3.mjs → chunk-DN43QXSX.mjs} +365 -444
  6. package/dist/lib/browser/chunk-DN43QXSX.mjs.map +7 -0
  7. package/dist/lib/browser/index.mjs +10 -5
  8. package/dist/lib/browser/index.mjs.map +3 -3
  9. package/dist/lib/browser/{intent-resolver-PW7B76HO.mjs → intent-resolver-BZMUS24C.mjs} +7 -7
  10. package/dist/lib/browser/intent-resolver-BZMUS24C.mjs.map +7 -0
  11. package/dist/lib/browser/keyboard-IQXHKUWM.mjs +46 -0
  12. package/dist/lib/browser/keyboard-IQXHKUWM.mjs.map +7 -0
  13. package/dist/lib/browser/meta.json +1 -1
  14. package/dist/lib/browser/{react-surface-M3CAS7ZP.mjs → react-surface-3HCM3SHJ.mjs} +3 -3
  15. package/dist/lib/browser/{state-3UBYOTQT.mjs → state-M5UJ3MHL.mjs} +12 -18
  16. package/dist/lib/browser/state-M5UJ3MHL.mjs.map +7 -0
  17. package/dist/types/src/capabilities/app-graph-builder.d.ts +179 -2
  18. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  19. package/dist/types/src/capabilities/capabilities.d.ts +4 -7
  20. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  21. package/dist/types/src/capabilities/index.d.ts +183 -38
  22. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  23. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  24. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  25. package/dist/types/src/capabilities/keyboard.d.ts +2 -2
  26. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  27. package/dist/types/src/capabilities/state.d.ts +6 -9
  28. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  29. package/dist/types/src/components/CommandsDialog.d.ts.map +1 -0
  30. package/dist/types/src/components/CommandsTrigger.d.ts.map +1 -0
  31. package/dist/types/src/components/L0Menu.d.ts +10 -0
  32. package/dist/types/src/components/L0Menu.d.ts.map +1 -0
  33. package/dist/types/src/components/L1Panels.d.ts +14 -0
  34. package/dist/types/src/components/L1Panels.d.ts.map +1 -0
  35. package/dist/types/src/components/NavTree.d.ts +5 -0
  36. package/dist/types/src/components/NavTree.d.ts.map +1 -0
  37. package/dist/types/src/components/NavTree.stories.d.ts +7 -0
  38. package/dist/types/src/components/NavTree.stories.d.ts.map +1 -0
  39. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  40. package/dist/types/src/components/NavTreeContext.d.ts.map +1 -1
  41. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
  42. package/dist/types/src/components/{NavTreeItem/NavTreeItemAction.d.ts → NavTreeItemAction.d.ts} +3 -2
  43. package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -0
  44. package/dist/types/src/components/NavTreeItemAction.stories.d.ts +41 -0
  45. package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -0
  46. package/dist/types/src/components/NavTreeItemColumns.d.ts +4 -0
  47. package/dist/types/src/components/NavTreeItemColumns.d.ts.map +1 -0
  48. package/dist/types/src/components/{UserAccountAvatar/UserAccountAvatar.d.ts → UserAccountAvatar.d.ts} +1 -1
  49. package/dist/types/src/components/UserAccountAvatar.d.ts.map +1 -0
  50. package/dist/types/src/components/{UserAccountAvatar/UserAccountAvatar.stories.d.ts → UserAccountAvatar.stories.d.ts} +2 -1
  51. package/dist/types/src/components/UserAccountAvatar.stories.d.ts.map +1 -0
  52. package/dist/types/src/components/index.d.ts +1 -0
  53. package/dist/types/src/components/index.d.ts.map +1 -1
  54. package/dist/types/src/components/types.d.ts +14 -20
  55. package/dist/types/src/components/types.d.ts.map +1 -1
  56. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  57. package/dist/types/src/hooks.d.ts.map +1 -1
  58. package/dist/types/src/stories/Graph.stories.d.ts +7 -0
  59. package/dist/types/src/stories/Graph.stories.d.ts.map +1 -0
  60. package/dist/types/src/stories/StorybookNavTree.d.ts +4 -0
  61. package/dist/types/src/stories/StorybookNavTree.d.ts.map +1 -0
  62. package/dist/types/src/testing/generator.d.ts +6 -7
  63. package/dist/types/src/testing/generator.d.ts.map +1 -1
  64. package/dist/types/src/testing/index.d.ts +0 -1
  65. package/dist/types/src/testing/index.d.ts.map +1 -1
  66. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  67. package/dist/types/src/translations.d.ts +0 -1
  68. package/dist/types/src/translations.d.ts.map +1 -1
  69. package/dist/types/src/util.d.ts +10 -7
  70. package/dist/types/src/util.d.ts.map +1 -1
  71. package/dist/types/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +31 -40
  73. package/src/NavTreePlugin.tsx +2 -2
  74. package/src/capabilities/app-graph-builder.ts +28 -38
  75. package/src/capabilities/capabilities.ts +4 -5
  76. package/src/capabilities/intent-resolver.ts +7 -8
  77. package/src/capabilities/keyboard.ts +32 -37
  78. package/src/capabilities/state.ts +17 -23
  79. package/src/components/{CommandsDialog/CommandsDialog.tsx → CommandsDialog.tsx} +7 -8
  80. package/src/components/{CommandsDialog/CommandsTrigger.tsx → CommandsTrigger.tsx} +1 -1
  81. package/src/components/{Sidebar/L0Menu.tsx → L0Menu.tsx} +131 -188
  82. package/src/components/L1Panels.tsx +115 -0
  83. package/src/components/NavTree.stories.tsx +107 -0
  84. package/src/components/NavTree.tsx +51 -0
  85. package/src/components/NavTreeContainer.tsx +74 -94
  86. package/src/components/NavTreeContext.tsx +1 -1
  87. package/src/components/{NavTreeItem/NavTreeItemAction.stories.tsx → NavTreeItemAction.stories.tsx} +6 -12
  88. package/src/components/{NavTreeItem/NavTreeItemAction.tsx → NavTreeItemAction.tsx} +18 -23
  89. package/src/components/NavTreeItemColumns.tsx +71 -0
  90. package/src/components/{UserAccountAvatar/UserAccountAvatar.stories.tsx → UserAccountAvatar.stories.tsx} +5 -5
  91. package/src/components/{UserAccountAvatar/UserAccountAvatar.tsx → UserAccountAvatar.tsx} +7 -7
  92. package/src/components/index.ts +1 -0
  93. package/src/components/types.ts +15 -20
  94. package/src/hooks.ts +11 -12
  95. package/src/stories/Graph.stories.tsx +130 -0
  96. package/src/stories/StorybookNavTree.tsx +63 -0
  97. package/src/testing/generator.ts +12 -15
  98. package/src/testing/index.ts +0 -1
  99. package/src/translations.ts +0 -1
  100. package/src/util.ts +32 -10
  101. package/dist/lib/browser/app-graph-builder-P7AY3K72.mjs.map +0 -7
  102. package/dist/lib/browser/chunk-TLQLMZXU.mjs.map +0 -7
  103. package/dist/lib/browser/chunk-UM2C5XK3.mjs.map +0 -7
  104. package/dist/lib/browser/intent-resolver-PW7B76HO.mjs.map +0 -7
  105. package/dist/lib/browser/keyboard-DDFATRUG.mjs +0 -51
  106. package/dist/lib/browser/keyboard-DDFATRUG.mjs.map +0 -7
  107. package/dist/lib/browser/state-3UBYOTQT.mjs.map +0 -7
  108. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  109. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  110. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  111. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  112. package/dist/types/src/components/NavTree/NavTree.d.ts +0 -8
  113. package/dist/types/src/components/NavTree/NavTree.d.ts.map +0 -1
  114. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +0 -8
  115. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +0 -1
  116. package/dist/types/src/components/NavTree/index.d.ts +0 -2
  117. package/dist/types/src/components/NavTree/index.d.ts.map +0 -1
  118. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +0 -1
  119. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +0 -8
  120. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +0 -1
  121. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +0 -4
  122. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +0 -1
  123. package/dist/types/src/components/NavTreeItem/index.d.ts +0 -3
  124. package/dist/types/src/components/NavTreeItem/index.d.ts.map +0 -1
  125. package/dist/types/src/components/Sidebar/L0Menu.d.ts +0 -13
  126. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +0 -1
  127. package/dist/types/src/components/Sidebar/L1Panel.d.ts +0 -15
  128. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +0 -1
  129. package/dist/types/src/components/Sidebar/index.d.ts +0 -3
  130. package/dist/types/src/components/Sidebar/index.d.ts.map +0 -1
  131. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +0 -1
  132. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +0 -1
  133. package/dist/types/src/components/UserAccountAvatar/index.d.ts +0 -2
  134. package/dist/types/src/components/UserAccountAvatar/index.d.ts.map +0 -1
  135. package/dist/types/src/testing/app-graph-builder.d.ts +0 -3
  136. package/dist/types/src/testing/app-graph-builder.d.ts.map +0 -1
  137. package/src/components/CommandsDialog/index.ts +0 -6
  138. package/src/components/NavTree/NavTree.stories.tsx +0 -159
  139. package/src/components/NavTree/NavTree.tsx +0 -62
  140. package/src/components/NavTree/index.ts +0 -5
  141. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +0 -73
  142. package/src/components/NavTreeItem/index.ts +0 -6
  143. package/src/components/Sidebar/L1Panel.tsx +0 -175
  144. package/src/components/Sidebar/index.ts +0 -6
  145. package/src/components/UserAccountAvatar/index.ts +0 -5
  146. package/src/testing/app-graph-builder.ts +0 -224
  147. /package/dist/lib/browser/{react-surface-M3CAS7ZP.mjs.map → react-surface-3HCM3SHJ.mjs.map} +0 -0
  148. /package/dist/types/src/components/{CommandsDialog/CommandsDialog.d.ts → CommandsDialog.d.ts} +0 -0
  149. /package/dist/types/src/components/{CommandsDialog/CommandsTrigger.d.ts → CommandsTrigger.d.ts} +0 -0
@@ -0,0 +1,51 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { Tabs } from '@dxos/react-ui-tabs';
8
+ import { byPosition } from '@dxos/util';
9
+
10
+ import { L0Menu } from './L0Menu';
11
+ import { L1Panels } from './L1Panels';
12
+ import { useNavTreeContext } from './NavTreeContext';
13
+ import { type NavTreeProps } from './types';
14
+ import { useLoadDescendents } from '../hooks';
15
+
16
+ export const NAV_TREE_ITEM = 'NavTreeItem';
17
+
18
+ export const NavTree = ({ id, root }: NavTreeProps) => {
19
+ const { tab, getItems, onBack } = useNavTreeContext();
20
+ const topLevelActions = getItems(root, 'item').toSorted((a, b) => byPosition(a.properties, b.properties));
21
+ const topLevelCollections = getItems(root, 'collection');
22
+ const topLevelWorkspaces = getItems(root, 'workspace');
23
+ const topLevelNavigation = getItems(root, 'navigation');
24
+ const l0Items = useMemo(
25
+ () => [...topLevelActions, ...topLevelCollections, ...topLevelWorkspaces, ...topLevelNavigation],
26
+ [topLevelActions, topLevelCollections, topLevelWorkspaces, topLevelNavigation],
27
+ );
28
+ const pinnedItems = getItems(root, 'pin-end').toSorted((a, b) => byPosition(a.properties, b.properties));
29
+ const userAccountItem = getItems(root, 'user-account')[0];
30
+ const topLevelItems = useMemo(
31
+ () => [...l0Items, ...pinnedItems, userAccountItem],
32
+ [l0Items, pinnedItems, userAccountItem],
33
+ );
34
+
35
+ useLoadDescendents(root);
36
+ const path = useMemo(() => [id], [id]);
37
+
38
+ return (
39
+ // NOTE(thure): 74px (rather than rem) is intentional in order to match the size of macOS windowing controls
40
+ <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
41
+ <L0Menu
42
+ topLevelItems={l0Items}
43
+ pinnedItems={pinnedItems}
44
+ userAccountItem={userAccountItem}
45
+ path={path}
46
+ parent={root}
47
+ />
48
+ <L1Panels topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} />
49
+ </Tabs.Root>
50
+ );
51
+ };
@@ -16,22 +16,28 @@ import {
16
16
  useIntentDispatcher,
17
17
  useLayout,
18
18
  } from '@dxos/app-framework';
19
- import { isAction, isActionLike, type ReadableGraph, ROOT_ID, type Node } from '@dxos/app-graph';
19
+ import { isAction, isActionLike, type Node } from '@dxos/app-graph';
20
20
  import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
21
- import { useConnections } from '@dxos/plugin-graph';
22
21
  import { isEchoObject, isSpace } from '@dxos/react-client/echo';
23
- import { useMediaQuery, useSidebars } from '@dxos/react-ui';
22
+ import { useMediaQuery } from '@dxos/react-ui';
24
23
  import { isTreeData, type TreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
25
24
  import { mx } from '@dxos/react-ui-theme';
26
- import { arrayMove, byPosition } from '@dxos/util';
25
+ import { arrayMove } from '@dxos/util';
27
26
 
28
27
  import { NAV_TREE_ITEM, NavTree } from './NavTree';
29
28
  import { NavTreeContext } from './NavTreeContext';
30
29
  import { type NavTreeContextValue } from './types';
31
30
  import { NavTreeCapabilities } from '../capabilities';
32
- import { NAVTREE_PLUGIN } from '../meta';
33
31
  import { type NavTreeItemGraphNode } from '../types';
34
- import { getActions as naturalGetActions, getChildren, getParent, resolveMigrationOperation } from '../util';
32
+ import {
33
+ expandActions,
34
+ expandChildren,
35
+ getActions as naturalGetActions,
36
+ getChildren,
37
+ getParent,
38
+ resolveMigrationOperation,
39
+ expandChildrenAndActions,
40
+ } from '../util';
35
41
 
36
42
  // TODO(thure): Is NavTree truly authoritative in this regard?
37
43
  export const NODE_TYPE = 'dxos/app-graph/node';
@@ -43,29 +49,6 @@ const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
43
49
  const getChildrenFilter = (node: Node): node is Node =>
44
50
  untracked(() => !isActionLike(node) && node.type !== PLANK_COMPANION_TYPE);
45
51
 
46
- const filterItems = (node: Node, disposition?: string) => {
47
- if (!disposition && (node.properties.disposition === 'hidden' || node.properties.disposition === 'alternate-tree')) {
48
- return false;
49
- } else if (!disposition) {
50
- const action = isAction(node);
51
- return !action || node.properties.disposition === 'item';
52
- } else {
53
- return node.properties.disposition === disposition;
54
- }
55
- };
56
-
57
- const getItems = (graph: ReadableGraph, node?: Node, disposition?: string) => {
58
- return graph.getConnections(node?.id ?? ROOT_ID, 'outbound').filter((node) => filterItems(node, disposition));
59
- };
60
-
61
- const useItems = (node?: Node, options?: { disposition?: string; sort?: boolean }) => {
62
- const { graph } = useAppGraph();
63
- const connections = useConnections(graph, node?.id ?? ROOT_ID).filter((node) =>
64
- filterItems(node, options?.disposition),
65
- );
66
- return options?.sort ? connections.toSorted((a, b) => byPosition(a.properties, b.properties)) : connections;
67
- };
68
-
69
52
  export type NavTreeContainerProps = {
70
53
  popoverAnchorId?: string;
71
54
  topbar?: boolean;
@@ -75,15 +58,34 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
75
58
  const [isLg] = useMediaQuery('lg', { ssr: false });
76
59
  const { dispatchPromise: dispatch } = useIntentDispatcher();
77
60
  const { graph } = useAppGraph();
78
- const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
79
61
  const layout = useLayout();
80
- const { navigationSidebarState } = useSidebars(NAVTREE_PLUGIN);
62
+ const { isOpen, isCurrent, setItem } = useCapability(NavTreeCapabilities.State);
81
63
 
82
64
  const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
83
65
 
66
+ const getItems = useCallback(
67
+ (node?: Node, disposition?: string) => {
68
+ return graph.nodes(node ?? graph.root, {
69
+ filter: (node): node is Node => {
70
+ return untracked(() => {
71
+ if (node.properties.disposition === 'hidden') {
72
+ return false;
73
+ } else if (!disposition) {
74
+ const action = isAction(node);
75
+ return !action || node.properties.disposition === 'item';
76
+ } else {
77
+ return node.properties.disposition === disposition;
78
+ }
79
+ });
80
+ },
81
+ });
82
+ },
83
+ [graph],
84
+ );
85
+
84
86
  const getProps = useCallback(
85
87
  (node: Node, path: string[]): PropsFromTreeItem => {
86
- const children = getChildren(graph, node, path).filter(getChildrenFilter);
88
+ const children = getChildren(graph, node, getChildrenFilter, path);
87
89
  const parentOf =
88
90
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
89
91
  return {
@@ -100,63 +102,51 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
100
102
  [graph],
101
103
  );
102
104
 
103
- // TODO(wittjosiah): Graph is currently expanded eagerly. See graph plugin.
104
- // const loadDescendents = useCallback(
105
- // (node: Node) => {
106
- // graph.expand(node.id, 'outbound');
107
- // // Load one level deeper, which resolves some juddering observed on open/close.
108
- // graph.getConnections(node.id, 'outbound').forEach((child) => {
109
- // graph.expand(child.id, 'outbound');
110
- // });
111
- // },
112
- // [graph],
113
- // );
105
+ const loadDescendents = useCallback(
106
+ (node: Node) => {
107
+ void expandActions(graph, node);
108
+ if (!isActionLike(node)) {
109
+ void expandChildren(graph, node).then(() =>
110
+ // Load one level deeper, which resolves some juddering observed on open/close.
111
+ Promise.all(
112
+ getChildren(graph, node).flatMap((child) => [expandActions(graph, child), expandChildren(graph, child)]),
113
+ ),
114
+ );
115
+ }
116
+ },
117
+ [graph],
118
+ );
114
119
 
115
- const handleOpenChange = useCallback(
120
+ const onOpenChange = useCallback(
116
121
  ({ item: { id }, path, open }: { item: Node; path: string[]; open: boolean }) => {
117
122
  // TODO(thure): This might become a localstorage leak; openItemIds that no longer exist should be removed from this map.
118
123
  setItem(path, 'open', open);
119
- // TODO(wittjosiah): Graph is currently expanded eagerly. See graph plugin.
120
- // graph.expand(id, 'outbound');
124
+
125
+ if (graph) {
126
+ const node = graph.findNode(id);
127
+ return node && expandChildrenAndActions(graph, node as NavTreeItemGraphNode);
128
+ }
121
129
  },
122
130
  [graph],
123
131
  );
124
132
 
125
- const handleTabChange = useCallback(
133
+ const onTabChange = useCallback(
126
134
  async (node: NavTreeItemGraphNode) => {
127
- await dispatch(
128
- createIntent(LayoutAction.UpdateSidebar, {
129
- part: 'sidebar',
130
- options: {
131
- state:
132
- node.id === tab
133
- ? navigationSidebarState === 'expanded'
134
- ? isLg
135
- ? 'collapsed'
136
- : 'closed'
137
- : 'expanded'
138
- : 'expanded',
139
- },
140
- }),
141
- );
142
-
143
135
  await dispatch(createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: node.id }));
144
-
145
136
  // Open the first item if the workspace is empty.
146
137
  if (layout.active.length === 0) {
147
- const [item] = getItems(graph, node).filter((node) => !isActionLike(node));
138
+ const [item] = getItems(node).filter((node) => !isActionLike(node));
148
139
  if (item && item.data) {
149
140
  await dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [item.id] }));
150
141
  }
151
142
  }
152
143
  },
153
- [dispatch, layout.active, tab, navigationSidebarState, isLg],
144
+ [dispatch, layout],
154
145
  );
155
146
 
156
147
  const canDrop = useCallback((source: TreeData, target: TreeData) => {
157
148
  const sourceNode = source.item as Node;
158
149
  const targetNode = target.item as Node;
159
- // TODO(wittjosiah): This shouldn't depend directly on client, there should be a graph-level abstraction.
160
150
  if (isSpace(targetNode.data)) {
161
151
  // TODO(wittjosiah): Find a way to only allow space as source for rearranging.
162
152
  return isEchoObject(sourceNode.data) || isSpace(sourceNode.data);
@@ -174,8 +164,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
174
164
  }
175
165
 
176
166
  if (isAction(node)) {
177
- const [parent] = graph.getConnections(node.id, 'inbound');
178
- void (parent && node.data({ parent, caller: NAV_TREE_ITEM }));
167
+ const [parent] = graph.nodes(node, { relation: 'inbound' });
168
+ void (parent && node.data({ node: parent, caller: NAV_TREE_ITEM }));
179
169
  return;
180
170
  }
181
171
 
@@ -196,7 +186,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
196
186
  void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: node.id }));
197
187
  }
198
188
 
199
- const defaultAction = graph.getActions(node.id).find((action) => action.properties?.disposition === 'default');
189
+ const defaultAction = graph.actions(node).find((action) => action.properties?.disposition === 'default');
200
190
  if (isAction(defaultAction)) {
201
191
  void (defaultAction.data as () => void)();
202
192
  }
@@ -208,7 +198,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
208
198
  [graph, dispatch, isCurrent, isLg],
209
199
  );
210
200
 
211
- const handleBack = useCallback(
201
+ const onBack = useCallback(
212
202
  () => dispatch(createIntent(LayoutAction.RevertWorkspace, { part: 'workspace', options: { revert: true } })),
213
203
  [dispatch],
214
204
  );
@@ -235,8 +225,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
235
225
  : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
236
226
  const sourceParent = getParent(graph, sourceNode, sourcePath);
237
227
  const targetParent = getParent(graph, targetNode, targetPath);
238
- const sourceItems = getItems(graph, sourceParent);
239
- const targetItems = getItems(graph, targetParent);
228
+ const sourceItems = getItems(sourceParent);
229
+ const targetItems = getItems(targetParent);
240
230
  const sourceIndex = sourceItems.findIndex(({ id }) => id === sourceNode.id);
241
231
  const targetIndex = targetItems.findIndex(({ id }) => id === targetNode.id);
242
232
  const migrationIndex =
@@ -272,56 +262,46 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
272
262
  });
273
263
  }, [graph]);
274
264
 
275
- const setAlternateTree = useCallback(
276
- (path: string[], open: boolean) => {
277
- setItem(path, 'alternateTree', open);
278
- },
279
- [setItem],
280
- );
281
-
282
265
  const navTreeContextValue = useMemo(
283
266
  () => ({
284
- useItems,
285
267
  tab,
268
+ onTabChange,
269
+ onBack,
286
270
  getActions,
287
- // loadDescendents,
271
+ loadDescendents,
288
272
  renderItemEnd,
289
273
  popoverAnchorId,
290
274
  topbar,
275
+ getItems,
291
276
  getProps,
292
277
  isCurrent,
293
278
  isOpen,
279
+ onOpenChange,
294
280
  canDrop,
295
- isAlternateTree,
296
- setAlternateTree,
297
- onTabChange: handleTabChange,
298
- onOpenChange: handleOpenChange,
299
281
  onSelect: handleSelect,
300
- onBack: handleBack,
301
282
  }),
302
283
  [
303
284
  tab,
285
+ onTabChange,
286
+ onBack,
304
287
  getActions,
305
- // loadDescendents,
288
+ loadDescendents,
306
289
  renderItemEnd,
307
290
  popoverAnchorId,
308
291
  topbar,
292
+ getItems,
309
293
  getProps,
310
294
  isCurrent,
311
295
  isOpen,
296
+ onOpenChange,
312
297
  canDrop,
313
- isAlternateTree,
314
- setAlternateTree,
315
- handleTabChange,
316
- handleOpenChange,
317
298
  handleSelect,
318
- handleBack,
319
299
  ],
320
300
  );
321
301
 
322
302
  return (
323
303
  <NavTreeContext.Provider value={navTreeContextValue}>
324
- <NavTree id={ROOT_ID} root={graph.root} open={layout.sidebarOpen} />
304
+ <NavTree root={graph.root} id={graph.root.id} />
325
305
  </NavTreeContext.Provider>
326
306
  );
327
307
  });
@@ -10,4 +10,4 @@ import { type NavTreeContextValue } from './types';
10
10
 
11
11
  export const NavTreeContext = createContext<NavTreeContextValue | null>(null);
12
12
 
13
- export const useNavTreeContext = () => useContext(NavTreeContext) ?? raise(new Error('Missing NavTreeContext'));
13
+ export const useNavTreeContext = () => useContext(NavTreeContext) ?? raise(new Error('NavTreeContext not found'));
@@ -4,8 +4,6 @@
4
4
 
5
5
  import '@dxos-theme';
6
6
 
7
- import { type Meta, type StoryObj } from '@storybook/react';
8
-
9
7
  import { type NodeArg } from '@dxos/app-graph';
10
8
  import { faker } from '@dxos/random';
11
9
  import { withLayout, withTheme } from '@dxos/storybook-utils';
@@ -39,23 +37,19 @@ const menuActions = faker.helpers.multiple(
39
37
  { count: 20 },
40
38
  );
41
39
 
42
- const meta: Meta<typeof NavTreeItemAction> = {
40
+ export default {
43
41
  title: 'plugins/plugin-navtree/NavTreeItemAction',
44
42
  component: NavTreeItemAction,
45
- decorators: [withTheme, withLayout()],
43
+ decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
46
44
  args: {
47
45
  icon: 'ph--list--regular',
48
46
  parent,
49
47
  menuActions,
50
48
  label: 'Select action',
49
+ onAction: (action) => {
50
+ console.log(action);
51
+ },
51
52
  } satisfies Partial<NavTreeItemActionMenuProps>,
52
- parameters: {
53
- layout: 'centered',
54
- },
55
53
  };
56
54
 
57
- export default meta;
58
-
59
- type Story = StoryObj<typeof meta>;
60
-
61
- export const Default: Story = {};
55
+ export const Default = {};
@@ -5,12 +5,12 @@
5
5
  import React, { type MutableRefObject, useRef } from 'react';
6
6
 
7
7
  import { type Action, type Node } from '@dxos/app-graph';
8
- import { useTranslation, toLocalizedString, IconButton, useDensityContext } from '@dxos/react-ui';
8
+ import { useTranslation, toLocalizedString, IconButton } from '@dxos/react-ui';
9
9
  import { DropdownMenu, MenuProvider, type MenuItem } from '@dxos/react-ui-menu';
10
10
  import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
11
11
 
12
- import { NAVTREE_PLUGIN } from '../../meta';
13
- import { type ActionProperties } from '../../types';
12
+ import { NAVTREE_PLUGIN } from '../meta';
13
+ import { type ActionProperties } from '../types';
14
14
 
15
15
  export type NavTreeItemActionMenuProps = ActionProperties & {
16
16
  parent: Node;
@@ -18,41 +18,31 @@ export type NavTreeItemActionMenuProps = ActionProperties & {
18
18
  monolithic?: boolean;
19
19
  menuActions?: Action[];
20
20
  suppressNextTooltip?: MutableRefObject<boolean>;
21
+ onAction?: (action: Action) => void;
21
22
  };
22
23
 
23
24
  const fallbackIcon = 'ph--placeholder--regular';
24
25
 
25
- const fineActionButtonProps = {
26
+ const actionButtonProps = {
26
27
  size: 4 as const,
27
28
  density: 'fine' as const,
28
29
  };
29
- const coarseActionButtonProps = {
30
- size: 5 as const,
31
- density: 'coarse' as const,
32
- };
33
30
 
34
31
  export const NavTreeItemActionDropdownMenu = ({
35
- parent,
36
32
  label,
37
33
  icon,
38
34
  testId,
39
35
  menuActions,
40
- caller,
36
+ onAction,
41
37
  }: NavTreeItemActionMenuProps) => {
42
38
  const { t } = useTranslation(NAVTREE_PLUGIN);
43
39
  const suppressNextTooltip = useRef<boolean>(false);
44
- const density = useDensityContext();
45
40
  return (
46
- <MenuProvider>
47
- <DropdownMenu.Root
48
- group={parent}
49
- items={menuActions as MenuItem[]}
50
- caller={caller}
51
- suppressNextTooltip={suppressNextTooltip}
52
- >
41
+ <MenuProvider onAction={onAction}>
42
+ <DropdownMenu.Root items={menuActions as MenuItem[]} suppressNextTooltip={suppressNextTooltip}>
53
43
  <DropdownMenu.Trigger asChild>
54
44
  <IconButton
55
- {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
45
+ {...actionButtonProps}
56
46
  classNames={mx('shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem)}
57
47
  variant='ghost'
58
48
  icon={icon ?? fallbackIcon}
@@ -73,10 +63,9 @@ export const NavTreeItemMonolithicAction = ({
73
63
  data: invoke,
74
64
  baseLabel,
75
65
  }: Action & { parent: Node; onAction?: (action: Action) => void; baseLabel: string }) => {
76
- const density = useDensityContext();
77
66
  return (
78
67
  <IconButton
79
- {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
68
+ {...actionButtonProps}
80
69
  variant={variant}
81
70
  classNames={mx(
82
71
  'shrink-0',
@@ -93,7 +82,7 @@ export const NavTreeItemMonolithicAction = ({
93
82
  return;
94
83
  }
95
84
  event.stopPropagation();
96
- void invoke?.(caller ? { parent, caller } : { parent });
85
+ void invoke?.(caller ? { node: parent, caller } : { node: parent });
97
86
  }}
98
87
  data-testid={testId}
99
88
  />
@@ -109,6 +98,12 @@ export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...pr
109
98
  return monolithic && menuActions?.length === 1 ? (
110
99
  <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
111
100
  ) : (
112
- <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} menuActions={menuActions} />
101
+ <NavTreeItemActionDropdownMenu
102
+ {...props}
103
+ label={baseLabel}
104
+ parent={node}
105
+ menuActions={menuActions}
106
+ onAction={(action) => action.data?.(props.caller ? { node, caller: props.caller } : { node })}
107
+ />
113
108
  );
114
109
  };
@@ -0,0 +1,71 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { Fragment, memo } from 'react';
6
+
7
+ import { isAction } from '@dxos/app-graph';
8
+ import { Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { NAV_TREE_ITEM } from './NavTree';
11
+ import { useNavTreeContext } from './NavTreeContext';
12
+ import { NavTreeItemAction } from './NavTreeItemAction';
13
+ import type { NavTreeItemColumnsProps } from './types';
14
+ import { useLoadDescendents } from '../hooks';
15
+ import { NAVTREE_PLUGIN } from '../meta';
16
+
17
+ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
18
+ const { t } = useTranslation(NAVTREE_PLUGIN);
19
+ const { getActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
20
+
21
+ const level = path.length - 2;
22
+
23
+ const { actions: _actions, groupedActions } = getActions(item);
24
+ const [primaryAction, ...secondaryActions] = _actions.toSorted((a, b) =>
25
+ a.properties?.disposition === 'list-item-primary' ? -1 : 1,
26
+ );
27
+
28
+ const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
29
+ .flatMap((action) => (isAction(action) ? [action] : []))
30
+ .filter((action) => !action.properties?.hidden);
31
+
32
+ const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
33
+
34
+ useLoadDescendents(item);
35
+ useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
36
+
37
+ return (
38
+ <div role='none' className='contents app-no-drag'>
39
+ {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
+ <NavTreeItemAction
41
+ testId={primaryAction.properties?.testId}
42
+ label={toLocalizedString(primaryAction.properties?.label, t)}
43
+ icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
44
+ parent={item}
45
+ monolithic={isAction(primaryAction)}
46
+ menuActions={isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction.id]}
47
+ menuType={primaryAction.properties?.menuType}
48
+ caller={NAV_TREE_ITEM}
49
+ />
50
+ ) : (
51
+ <Treegrid.Cell />
52
+ )}
53
+ <ActionRoot>
54
+ {actions.length > 0 ? (
55
+ <NavTreeItemAction
56
+ testId={`navtree.treeItem.actionsLevel${level}`}
57
+ label={t('tree item actions label')}
58
+ icon='ph--dots-three-vertical--regular'
59
+ parent={item}
60
+ menuActions={actions}
61
+ menuType='dropdown'
62
+ caller={NAV_TREE_ITEM}
63
+ />
64
+ ) : (
65
+ <Treegrid.Cell />
66
+ )}
67
+ </ActionRoot>
68
+ {ItemEnd && <ItemEnd node={item} open={open} />}
69
+ </div>
70
+ );
71
+ });
@@ -5,17 +5,17 @@
5
5
  import '@dxos-theme';
6
6
 
7
7
  import { type Meta } from '@storybook/react';
8
-
9
- import { withTheme } from '@dxos/storybook-utils';
8
+ import React from 'react';
10
9
 
11
10
  import { UserAccountAvatar } from './UserAccountAvatar';
12
11
 
12
+ export const Default = (props: any) => {
13
+ return <UserAccountAvatar {...props} />;
14
+ };
15
+
13
16
  const meta: Meta<typeof UserAccountAvatar> = {
14
17
  title: 'plugins/plugin-navtree/UserAccountAvatar',
15
18
  component: UserAccountAvatar,
16
- decorators: [withTheme],
17
19
  };
18
20
 
19
21
  export default meta;
20
-
21
- export const Default = {};
@@ -15,19 +15,19 @@ export type UserAccountAvatarProps = {
15
15
  status?: AvatarStatus;
16
16
  };
17
17
 
18
- export const UserAccountAvatar = ({ size, userId, hue, emoji, status }: UserAccountAvatarProps) => {
19
- const fallbackValue = hexToFallback(userId ?? '0');
18
+ export const UserAccountAvatar = (props: UserAccountAvatarProps) => {
19
+ const fallbackValue = hexToFallback(props.userId ?? '0');
20
20
 
21
21
  return (
22
22
  <div className='grid place-items-center' data-joyride='welcome/account'>
23
23
  <Avatar.Root>
24
24
  <Avatar.Content
25
25
  variant='circle'
26
- size={size ?? 12}
27
- status={status ?? 'active'}
28
- hue={hue || fallbackValue.hue}
29
- fallback={emoji || fallbackValue.emoji}
30
- data-testid='treeView.userAccount'
26
+ size={props.size ?? 12}
27
+ status={props.status ?? 'active'}
28
+ hue={props.hue || fallbackValue.hue}
29
+ fallback={props.emoji || fallbackValue.emoji}
30
+ data-testid='treeview.userAccount'
31
31
  />
32
32
  </Avatar.Root>
33
33
  </div>
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './CommandsDialog';
6
+ export * from './CommandsTrigger';
6
7
  export * from './NavTree';
7
8
  export * from './NavTreeContainer';
8
9
  export * from './NavTreeContext';
@@ -5,36 +5,31 @@
5
5
  import type { FC } from 'react';
6
6
 
7
7
  import type { Node } from '@dxos/app-graph';
8
- import type { Density } from '@dxos/react-ui';
9
8
  import type { TreeProps } from '@dxos/react-ui-list';
10
9
  import type { MaybePromise } from '@dxos/util';
11
10
 
12
- import { type L1PanelProps } from './Sidebar';
11
+ import { type L1PanelProps } from './L1Panels';
13
12
  import type { FlattenedActions, NavTreeItemGraphNode } from '../types';
14
13
 
14
+ export type NavTreeItemColumnsProps = {
15
+ path: string[];
16
+ item: Node;
17
+ open: boolean;
18
+ };
19
+
20
+ export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'>;
21
+
15
22
  export type NavTreeContextValue = Pick<
16
23
  TreeProps<NavTreeItemGraphNode>,
17
- 'getProps' | 'isCurrent' | 'isOpen' | 'canDrop' | 'onOpenChange' | 'onSelect'
24
+ 'getProps' | 'isCurrent' | 'isOpen' | 'onOpenChange' | 'canDrop' | 'onSelect'
18
25
  > &
19
26
  Pick<L1PanelProps, 'onBack'> & {
20
27
  tab: string;
21
- topbar?: boolean;
22
- popoverAnchorId?: string;
23
- renderItemEnd?: FC<{ node: Node; open: boolean }>;
24
- useItems: (
25
- node?: NavTreeItemGraphNode,
26
- options?: { disposition?: string; sort?: boolean },
27
- ) => NavTreeItemGraphNode[];
28
+ onTabChange?: (node: NavTreeItemGraphNode) => void;
29
+ getItems: (node?: NavTreeItemGraphNode, disposition?: string) => NavTreeItemGraphNode[];
28
30
  getActions: (node: Node) => FlattenedActions;
29
31
  loadDescendents?: (node: Node) => MaybePromise<void>;
30
- isAlternateTree?: (path: string[], item: NavTreeItemGraphNode) => boolean;
31
- setAlternateTree?: (path: string[], open: boolean) => void;
32
- onTabChange?: (node: NavTreeItemGraphNode) => void;
32
+ renderItemEnd?: FC<{ node: Node; open: boolean }>;
33
+ popoverAnchorId?: string;
34
+ topbar?: boolean;
33
35
  };
34
-
35
- export type NavTreeItemColumnsProps = {
36
- path: string[];
37
- item: Node;
38
- open: boolean;
39
- density?: Density;
40
- };