@dxos/plugin-navtree 0.8.4-main.f9ba587 → 0.8.4-main.fcfe5033a5

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 (221) hide show
  1. package/dist/lib/browser/index.mjs +168 -115
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  5. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  6. package/dist/types/src/capabilities/app-graph-builder.d.ts +4 -2
  7. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  8. package/dist/types/src/capabilities/index.d.ts +7 -105
  9. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  10. package/dist/types/src/capabilities/keyboard.d.ts +3 -2
  11. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  12. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  13. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  14. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  15. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  16. package/dist/types/src/capabilities/state.d.ts +4 -18
  17. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  18. package/dist/types/src/components/NavTree/NavTree.d.ts +12 -5
  19. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  20. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +26 -5
  21. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  23. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  24. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  25. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  26. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +13 -9
  27. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  28. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +33 -4
  29. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +2 -2
  31. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  32. package/dist/types/src/components/Sidebar/L0Menu.d.ts +10 -5
  33. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  34. package/dist/types/src/components/Sidebar/L1Panel.d.ts +4 -8
  35. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  36. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +13 -0
  37. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
  38. package/dist/types/src/components/Sidebar/index.d.ts +1 -0
  39. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -1
  40. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  41. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  42. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/index.d.ts +1 -4
  44. package/dist/types/src/components/index.d.ts.map +1 -1
  45. package/dist/types/src/components/types.d.ts +8 -16
  46. package/dist/types/src/components/types.d.ts.map +1 -1
  47. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts +6 -0
  48. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -0
  49. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts +29 -0
  50. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts.map +1 -0
  51. package/dist/types/src/containers/CommandsDialogContent/index.d.ts +2 -0
  52. package/dist/types/src/containers/CommandsDialogContent/index.d.ts.map +1 -0
  53. package/dist/types/src/containers/CommandsTrigger/CommandsTrigger.d.ts.map +1 -0
  54. package/dist/types/src/containers/CommandsTrigger/index.d.ts +2 -0
  55. package/dist/types/src/containers/CommandsTrigger/index.d.ts.map +1 -0
  56. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts +9 -0
  57. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -0
  58. package/dist/types/src/containers/NavTreeContainer/index.d.ts +3 -0
  59. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -0
  60. package/dist/types/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.d.ts +1 -1
  61. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -0
  62. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts +2 -0
  63. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts.map +1 -0
  64. package/dist/types/src/containers/index.d.ts +7 -0
  65. package/dist/types/src/containers/index.d.ts.map +1 -0
  66. package/dist/types/src/experimental/Tree.d.ts +4 -5
  67. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  68. package/dist/types/src/experimental/Tree.stories.d.ts +23 -6
  69. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  70. package/dist/types/src/hooks/index.d.ts +5 -0
  71. package/dist/types/src/hooks/index.d.ts.map +1 -0
  72. package/dist/types/src/hooks/useActions.d.ts +5 -0
  73. package/dist/types/src/hooks/useActions.d.ts.map +1 -0
  74. package/dist/types/src/hooks/useLoadDescendents.d.ts +6 -0
  75. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -0
  76. package/dist/types/src/hooks/useNavTreeModel.d.ts +7 -0
  77. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -0
  78. package/dist/types/src/hooks/useNavTreeState.d.ts +22 -0
  79. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -0
  80. package/dist/types/src/index.d.ts +1 -2
  81. package/dist/types/src/index.d.ts.map +1 -1
  82. package/dist/types/src/meta.d.ts +3 -4
  83. package/dist/types/src/meta.d.ts.map +1 -1
  84. package/dist/types/src/operations/expose.d.ts +5 -0
  85. package/dist/types/src/operations/expose.d.ts.map +1 -0
  86. package/dist/types/src/operations/index.d.ts +3 -0
  87. package/dist/types/src/operations/index.d.ts.map +1 -0
  88. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  89. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  90. package/dist/types/src/testing/generator.d.ts +5 -6
  91. package/dist/types/src/testing/generator.d.ts.map +1 -1
  92. package/dist/types/src/testing/update-state.d.ts +2 -2
  93. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  94. package/dist/types/src/translations.d.ts +9 -9
  95. package/dist/types/src/translations.d.ts.map +1 -1
  96. package/dist/types/src/types/capabilities.d.ts +16 -0
  97. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  98. package/dist/types/src/types/events.d.ts +5 -0
  99. package/dist/types/src/types/events.d.ts.map +1 -0
  100. package/dist/types/src/types/index.d.ts +4 -0
  101. package/dist/types/src/types/index.d.ts.map +1 -0
  102. package/dist/types/src/{types.d.ts → types/schema.d.ts} +10 -9
  103. package/dist/types/src/types/schema.d.ts.map +1 -0
  104. package/dist/types/src/util.d.ts +12 -9
  105. package/dist/types/src/util.d.ts.map +1 -1
  106. package/dist/types/tsconfig.tsbuildinfo +1 -1
  107. package/package.json +67 -56
  108. package/src/NavTreePlugin.tsx +66 -85
  109. package/src/capabilities/app-graph-builder.ts +34 -44
  110. package/src/capabilities/index.ts +10 -8
  111. package/src/capabilities/keyboard.ts +58 -51
  112. package/src/capabilities/operation-handler.ts +16 -0
  113. package/src/capabilities/react-surface.tsx +46 -38
  114. package/src/capabilities/state.ts +129 -84
  115. package/src/components/NavTree/NavTree.stories.tsx +110 -102
  116. package/src/components/NavTree/NavTree.tsx +71 -35
  117. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  118. package/src/components/NavTreeContext/index.ts +5 -0
  119. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +25 -19
  120. package/src/components/NavTreeItem/NavTreeItemAction.tsx +73 -49
  121. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +70 -38
  122. package/src/components/Sidebar/L0Menu.tsx +176 -210
  123. package/src/components/Sidebar/L1Panel.tsx +271 -135
  124. package/src/components/Sidebar/L1Tabs.tsx +42 -0
  125. package/src/components/Sidebar/index.ts +1 -0
  126. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -9
  127. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  128. package/src/components/index.ts +2 -4
  129. package/src/components/types.ts +15 -19
  130. package/src/containers/CommandsDialogContent/CommandsDialogContent.stories.tsx +45 -0
  131. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +127 -0
  132. package/src/containers/CommandsDialogContent/index.ts +5 -0
  133. package/src/containers/CommandsTrigger/CommandsTrigger.tsx +28 -0
  134. package/src/containers/CommandsTrigger/index.ts +5 -0
  135. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +280 -0
  136. package/src/containers/NavTreeContainer/index.ts +6 -0
  137. package/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.tsx +5 -4
  138. package/src/containers/NavTreeDocumentTitle/index.ts +5 -0
  139. package/src/containers/index.ts +12 -0
  140. package/src/experimental/Tree.stories.tsx +110 -115
  141. package/src/experimental/Tree.tsx +16 -34
  142. package/src/hooks/index.ts +8 -0
  143. package/src/hooks/useActions.ts +34 -0
  144. package/src/hooks/useLoadDescendents.ts +23 -0
  145. package/src/hooks/useNavTreeModel.ts +118 -0
  146. package/src/hooks/useNavTreeState.ts +42 -0
  147. package/src/index.ts +1 -2
  148. package/src/meta.ts +11 -8
  149. package/src/operations/expose.ts +42 -0
  150. package/src/operations/index.ts +7 -0
  151. package/src/testing/app-graph-builder.ts +167 -238
  152. package/src/testing/generator.ts +41 -39
  153. package/src/testing/update-state.ts +6 -6
  154. package/src/translations.ts +9 -9
  155. package/src/types/capabilities.ts +21 -0
  156. package/src/types/events.ts +12 -0
  157. package/src/types/index.ts +7 -0
  158. package/src/{types.ts → types/schema.ts} +9 -8
  159. package/src/util.ts +26 -33
  160. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs +0 -45
  161. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
  162. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  163. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  164. package/dist/lib/browser/chunk-TZA65AKW.mjs +0 -1428
  165. package/dist/lib/browser/chunk-TZA65AKW.mjs.map +0 -7
  166. package/dist/lib/browser/chunk-WCARAZ7J.mjs +0 -15
  167. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  168. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs +0 -47
  169. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs.map +0 -7
  170. package/dist/lib/browser/keyboard-QRS6WOQH.mjs +0 -51
  171. package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
  172. package/dist/lib/browser/react-surface-N2HPMYCU.mjs +0 -51
  173. package/dist/lib/browser/react-surface-N2HPMYCU.mjs.map +0 -7
  174. package/dist/lib/browser/state-54DY3DOY.mjs +0 -117
  175. package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
  176. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs +0 -46
  177. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs.map +0 -7
  178. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs +0 -1429
  179. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs.map +0 -7
  180. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs +0 -18
  181. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs.map +0 -7
  182. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs +0 -16
  183. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs.map +0 -7
  184. package/dist/lib/node-esm/index.mjs +0 -150
  185. package/dist/lib/node-esm/index.mjs.map +0 -7
  186. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs +0 -48
  187. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs.map +0 -7
  188. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs +0 -52
  189. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs.map +0 -7
  190. package/dist/lib/node-esm/meta.json +0 -1
  191. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs +0 -52
  192. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs.map +0 -7
  193. package/dist/lib/node-esm/state-Z3OTRC2I.mjs +0 -118
  194. package/dist/lib/node-esm/state-Z3OTRC2I.mjs.map +0 -7
  195. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  196. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  197. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  198. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  199. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +0 -5
  200. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  201. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  202. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  203. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  204. package/dist/types/src/components/NavTreeContainer.d.ts +0 -9
  205. package/dist/types/src/components/NavTreeContainer.d.ts.map +0 -1
  206. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  207. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +0 -1
  208. package/dist/types/src/events.d.ts +0 -4
  209. package/dist/types/src/events.d.ts.map +0 -1
  210. package/dist/types/src/hooks.d.ts +0 -3
  211. package/dist/types/src/hooks.d.ts.map +0 -1
  212. package/dist/types/src/types.d.ts.map +0 -1
  213. package/src/capabilities/capabilities.ts +0 -20
  214. package/src/capabilities/intent-resolver.ts +0 -37
  215. package/src/components/CommandsDialog/CommandsDialog.tsx +0 -118
  216. package/src/components/CommandsDialog/CommandsTrigger.tsx +0 -37
  217. package/src/components/CommandsDialog/index.ts +0 -6
  218. package/src/components/NavTreeContainer.tsx +0 -314
  219. package/src/events.ts +0 -11
  220. package/src/hooks.ts +0 -21
  221. /package/dist/types/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.d.ts +0 -0
@@ -2,52 +2,36 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React, { useMemo } from 'react';
5
+ import React, { forwardRef, useMemo } from 'react';
6
6
 
7
- import { type TreeProps } from '@dxos/react-ui-list';
7
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
8
+ import { Node, useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
8
9
  import { Tabs } from '@dxos/react-ui-tabs';
10
+ import { byPosition } from '@dxos/util';
11
+
12
+ import { useLoadDescendents } from '#hooks';
9
13
 
10
- import { useLoadDescendents } from '../../hooks';
11
- import { type NavTreeItemGraphNode } from '../../types';
12
14
  import { useNavTreeContext } from '../NavTreeContext';
13
- import { L0Menu, L1Panels, type L1PanelsProps } from '../Sidebar';
15
+ import { L0Menu, L1Tabs, type L1TabsProps } from '../Sidebar';
14
16
 
15
17
  export const NAV_TREE_ITEM = 'NavTreeItem';
16
18
 
17
- export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<L1PanelsProps, 'open'>;
18
-
19
- export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
20
- const { tab, useItems, onBack } = useNavTreeContext();
21
- const topLevelActions = useItems(root, { disposition: 'menu', sort: true });
22
- const topLevelCollections = useItems(root, { disposition: 'collection' });
23
- const topLevelWorkspaces = useItems(root, { disposition: 'workspace' });
24
- const topLevelNavigation = useItems(root, { disposition: 'navigation' });
25
- const l0Items = useMemo(
26
- () => [
27
- // prettier-ignore
28
- ...topLevelCollections,
29
- ...topLevelWorkspaces,
30
- ...topLevelNavigation,
31
- ],
32
- [topLevelCollections, topLevelWorkspaces, topLevelNavigation],
33
- );
34
- const pinnedItems = useItems(root, { disposition: 'pin-end', sort: true });
35
- const userAccountItem = useItems(root, { disposition: 'user-account' })[0];
36
- const topLevelItems = useMemo(
37
- () => [
38
- // prettier-ignore
39
- ...l0Items,
40
- ...pinnedItems,
41
- ...(userAccountItem ? [userAccountItem] : []),
42
- ],
43
- [l0Items, pinnedItems, userAccountItem],
44
- );
19
+ export type NavTreeProps = { id: string; root?: Node.Node; tab: string } & Pick<L1TabsProps, 'open'>;
20
+
21
+ // TODO(wittjosiah): Refactor to Radix-style.
22
+ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, tab, ...props }, forwardedRef) => {
23
+ const { onBack, onItemHover } = useNavTreeContext();
24
+ const { topLevelActions, l0Items, pinnedItems, userAccountItem, topLevelItems } = useTopLevelNavItems(root);
45
25
 
46
26
  useLoadDescendents(root);
47
27
  const path = useMemo(() => [id], [id]);
48
28
 
49
29
  return (
50
- <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
30
+ // TODO(thure): `Tabs.Root` forces all items that should be able to receive focus to use `Tabs.Tab(Primitive)` since
31
+ // it uses RovingFocus and doesn't support moving focus to an item that is not a tab. Assess whether this situation
32
+ // should change including whether it should motivate a change in the design/taxonomy, or if this means this should
33
+ // not use `react-ui-tabs` at all.
34
+ <Tabs.Root value={tab} orientation='vertical' classNames='relative' ref={forwardedRef}>
51
35
  <L0Menu
52
36
  menuActions={topLevelActions}
53
37
  topLevelItems={l0Items}
@@ -55,8 +39,60 @@ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
55
39
  userAccountItem={userAccountItem}
56
40
  path={path}
57
41
  parent={root}
42
+ onItemHover={onItemHover}
58
43
  />
59
- <L1Panels topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
44
+ <L1Tabs topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
60
45
  </Tabs.Root>
61
46
  );
47
+ });
48
+
49
+ /**
50
+ * Partitions root children into workspaces, pinned items, user-account, and top-level actions.
51
+ */
52
+ const useTopLevelNavItems = (root?: Node.Node) => {
53
+ const { graph } = useAppGraph();
54
+ const rootId = root?.id ?? Node.RootId;
55
+ const rootOutboundItems = useConnections(graph, rootId, 'child');
56
+ const rootActions = useGraphActions(graph, rootId);
57
+
58
+ const { topLevelActions, l0Items, pinnedItems, userAccountItem } = useMemo(() => {
59
+ const topLevelWorkspaces: Node.Node[] = [];
60
+ const outboundPinnedItems: Node.Node[] = [];
61
+ let userAccountItem: Node.Node | undefined;
62
+ for (const node of rootOutboundItems) {
63
+ switch (node.properties.disposition) {
64
+ case 'workspace':
65
+ topLevelWorkspaces.push(node);
66
+ break;
67
+ case 'pin-end':
68
+ outboundPinnedItems.push(node);
69
+ break;
70
+ case 'user-account':
71
+ userAccountItem ??= node;
72
+ break;
73
+ }
74
+ }
75
+
76
+ const topLevelActions = rootActions
77
+ .filter((action) => action.properties.disposition === 'menu')
78
+ .toSorted((a, b) => byPosition(a.properties, b.properties));
79
+ const pinnedItems = [
80
+ ...outboundPinnedItems,
81
+ ...rootActions.filter((action) => action.properties.disposition === 'pin-end'),
82
+ ].toSorted((a, b) => byPosition(a.properties, b.properties));
83
+
84
+ return {
85
+ topLevelActions,
86
+ l0Items: topLevelWorkspaces,
87
+ pinnedItems,
88
+ userAccountItem,
89
+ };
90
+ }, [rootActions, rootOutboundItems]);
91
+
92
+ const topLevelItems = useMemo(
93
+ () => [...l0Items, ...pinnedItems, ...(userAccountItem ? [userAccountItem] : [])],
94
+ [l0Items, pinnedItems, userAccountItem],
95
+ );
96
+
97
+ return { topLevelActions, l0Items, pinnedItems, userAccountItem, topLevelItems };
62
98
  };
@@ -6,7 +6,7 @@ import { createContext, useContext } from 'react';
6
6
 
7
7
  import { raise } from '@dxos/debug';
8
8
 
9
- import { type NavTreeContextValue } from './types';
9
+ import { type NavTreeContextValue } from '../types';
10
10
 
11
11
  export const NavTreeContext = createContext<NavTreeContextValue | null>(null);
12
12
 
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './NavTreeContext';
@@ -2,57 +2,63 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import * as Effect from 'effect/Effect';
8
7
 
9
- import { type NodeArg } from '@dxos/app-graph';
10
- import { faker } from '@dxos/random';
11
- import { withLayout, withTheme } from '@dxos/storybook-utils';
8
+ import { RuntimePlugin } from '@dxos/app-framework';
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { type Node } from '@dxos/app-graph';
11
+ import { corePlugins } from '@dxos/plugin-testing';
12
+ import { random } from '@dxos/random';
12
13
 
13
14
  import { NavTreeItemAction, type NavTreeItemActionMenuProps } from './NavTreeItemAction';
14
15
 
15
16
  const parent = {
16
- id: faker.string.uuid(),
17
+ id: random.string.uuid(),
17
18
  type: 'node',
18
19
  data: null,
19
20
  properties: {
20
- label: faker.lorem.words(2),
21
+ label: random.lorem.words(2),
21
22
  icon: 'ph--circle--regular',
22
23
  },
23
- } satisfies NodeArg<any>;
24
+ } satisfies Node.NodeArg<any>;
24
25
 
25
26
  // TODO(burdon): Factor out across tests.
26
- const menuActions = faker.helpers.multiple(
27
+ const menuActions = random.helpers.multiple(
27
28
  () =>
28
29
  ({
29
- id: faker.string.uuid(),
30
+ id: random.string.uuid(),
30
31
  type: 'action',
31
- data: () => {
32
- console.log('invoke');
33
- },
32
+ data: () =>
33
+ Effect.sync(() => {
34
+ console.log('invoke');
35
+ }),
34
36
  properties: {
35
- label: faker.lorem.words(2),
37
+ label: random.lorem.words(2),
36
38
  icon: 'ph--circle--regular',
37
39
  },
38
- }) satisfies NodeArg<any>,
40
+ }) satisfies Node.NodeArg<any>,
39
41
  { count: 20 },
40
42
  );
41
43
 
42
- const meta: Meta<typeof NavTreeItemAction> = {
43
- title: 'plugins/plugin-navtree/NavTreeItemAction',
44
+ const meta = {
45
+ title: 'plugins/plugin-navtree/components/NavTreeItemAction',
44
46
  component: NavTreeItemAction,
45
- decorators: [withTheme, withLayout()],
46
47
  args: {
47
48
  icon: 'ph--list--regular',
48
49
  parent,
49
50
  menuActions,
50
51
  label: 'Select action',
51
52
  } satisfies Partial<NavTreeItemActionMenuProps>,
53
+ decorators: [
54
+ withPluginManager({
55
+ plugins: [...corePlugins(), RuntimePlugin()],
56
+ }),
57
+ ],
52
58
  parameters: {
53
59
  layout: 'centered',
54
60
  },
55
- };
61
+ } satisfies Meta<typeof NavTreeItemAction>;
56
62
 
57
63
  export default meta;
58
64
 
@@ -2,22 +2,16 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback } from 'react';
6
6
 
7
- import { type Action, type Node } from '@dxos/app-graph';
8
- import { useTranslation, toLocalizedString, IconButton, useDensityContext } from '@dxos/react-ui';
9
- import { DropdownMenu, MenuProvider, type MenuItem } from '@dxos/react-ui-menu';
10
- import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
7
+ import { type Node } from '@dxos/app-graph';
8
+ import { useActionRunner } from '@dxos/plugin-graph';
9
+ import { IconButton, toLocalizedString, useDensityContext, useTranslation } from '@dxos/react-ui';
10
+ import { Menu, type MenuItem } from '@dxos/react-ui-menu';
11
+ import { composable, composableProps, hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
11
12
 
12
- import { NAVTREE_PLUGIN } from '../../meta';
13
- import { type ActionProperties } from '../../types';
14
-
15
- export type NavTreeItemActionMenuProps = ActionProperties & {
16
- parent: Node;
17
- caller?: string;
18
- monolithic?: boolean;
19
- menuActions?: Action[];
20
- };
13
+ import { meta } from '#meta';
14
+ import { type ActionProperties } from '#types';
21
15
 
22
16
  const fallbackIcon = 'ph--placeholder--regular';
23
17
 
@@ -25,82 +19,112 @@ const fineActionButtonProps = {
25
19
  size: 4 as const,
26
20
  density: 'fine' as const,
27
21
  };
22
+
28
23
  const coarseActionButtonProps = {
29
24
  size: 5 as const,
30
25
  density: 'coarse' as const,
31
26
  };
32
27
 
33
- export const NavTreeItemActionDropdownMenu = ({
34
- parent,
35
- label,
36
- icon,
37
- testId,
38
- menuActions,
39
- caller,
40
- }: NavTreeItemActionMenuProps) => {
41
- const { t } = useTranslation(NAVTREE_PLUGIN);
42
- const density = useDensityContext();
43
- return (
44
- <MenuProvider>
45
- <DropdownMenu.Root group={parent} items={menuActions as MenuItem[]} caller={caller}>
46
- <DropdownMenu.Trigger asChild>
28
+ export type NavTreeItemActionMenuProps = ActionProperties & {
29
+ parent: Node.Node;
30
+ path?: string[];
31
+ caller?: string;
32
+ monolithic?: boolean;
33
+ menuActions?: Node.Action[];
34
+ };
35
+
36
+ export const NavTreeItemActionDropdownMenu = composable<HTMLButtonElement, NavTreeItemActionMenuProps>(
37
+ ({ parent, path, label, icon, testId, menuActions, caller, ...props }, forwardedRef) => {
38
+ const { t } = useTranslation(meta.id);
39
+ const density = useDensityContext();
40
+ const runAction = useActionRunner();
41
+ const handleAction = useCallback(
42
+ (action: Node.Action, params: Node.InvokeProps = {}) => runAction(action, { ...params, path }),
43
+ [runAction, path],
44
+ );
45
+
46
+ return (
47
+ <Menu.Root caller={caller} onAction={handleAction}>
48
+ <Menu.Trigger asChild>
47
49
  <IconButton
48
50
  {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
49
- classNames={mx('shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem)}
51
+ {...composableProps(props)}
52
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
50
53
  variant='ghost'
51
54
  icon={icon ?? fallbackIcon}
52
55
  iconOnly
53
56
  label={toLocalizedString(label, t)}
54
57
  data-testid={testId}
58
+ ref={forwardedRef}
55
59
  />
56
- </DropdownMenu.Trigger>
57
- </DropdownMenu.Root>
58
- </MenuProvider>
59
- );
60
- };
60
+ </Menu.Trigger>
61
+ <Menu.Content group={parent} items={menuActions as MenuItem[]} />
62
+ </Menu.Root>
63
+ );
64
+ },
65
+ );
61
66
 
62
- export const NavTreeItemMonolithicAction = ({
63
- parent,
64
- properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
65
- data: invoke,
66
- baseLabel,
67
- }: Action & { parent: Node; onAction?: (action: Action) => void; baseLabel: string }) => {
67
+ NavTreeItemActionDropdownMenu.displayName = 'NavTreeItemActionDropdownMenu';
68
+
69
+ export const NavTreeItemMonolithicAction = (
70
+ props: Node.Action & {
71
+ parent: Node.Node;
72
+ path?: string[];
73
+ onAction?: (action: Node.Action) => void;
74
+ baseLabel: string;
75
+ },
76
+ ) => {
77
+ const {
78
+ parent,
79
+ path,
80
+ properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
81
+ baseLabel,
82
+ } = props;
68
83
  const density = useDensityContext();
84
+ const runAction = useActionRunner();
69
85
  return (
70
86
  <IconButton
71
87
  {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
72
88
  variant={variant}
73
- classNames={mx(
89
+ classNames={[
74
90
  'shrink-0',
75
91
  hoverableControlItem,
76
92
  hoverableOpenControlItem,
77
- iconOnly ? 'pli-2 pointer-fine:pli-1' : 'p-2 pointer-fine:p-2 mie-1',
78
- )}
93
+ iconOnly ? 'px-2 pointer-fine:px-1' : 'p-2 pointer-fine:p-2 me-1',
94
+ ]}
79
95
  icon={icon ?? fallbackIcon}
80
96
  iconOnly={iconOnly}
81
97
  label={baseLabel}
82
98
  disabled={disabled}
83
99
  onClick={(event) => {
100
+ event.stopPropagation();
84
101
  if (disabled) {
85
102
  return;
86
103
  }
87
- event.stopPropagation();
88
- void invoke?.(caller ? { parent, caller } : { parent });
104
+
105
+ void runAction(props, caller ? { parent, caller, path } : { parent, path });
89
106
  }}
90
107
  data-testid={testId}
91
108
  />
92
109
  );
93
110
  };
94
111
 
95
- export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
96
- const { t } = useTranslation(NAVTREE_PLUGIN);
112
+ export const NavTreeItemAction = ({
113
+ monolithic,
114
+ menuActions,
115
+ menuType,
116
+ parent: node,
117
+ path,
118
+ ...props
119
+ }: NavTreeItemActionMenuProps) => {
120
+ const { t } = useTranslation(meta.id);
97
121
 
98
122
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
99
123
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
100
124
 
101
125
  return monolithic && menuActions?.length === 1 ? (
102
- <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
126
+ <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} path={path} {...menuActions[0]} />
103
127
  ) : (
104
- <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} menuActions={menuActions} />
128
+ <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} path={path} menuActions={menuActions} />
105
129
  );
106
130
  };
@@ -2,72 +2,104 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { Fragment, memo } from 'react';
5
+ import React, { Fragment, memo, useMemo } from 'react';
6
6
 
7
- import { isAction } from '@dxos/app-graph';
8
- import { DensityProvider, Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
7
+ import { Node } from '@dxos/app-graph';
8
+ import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
+
10
+ import { useActions } from '#hooks';
11
+ import { meta } from '#meta';
9
12
 
10
- import { NavTreeItemAction } from './NavTreeItemAction';
11
- import { useLoadDescendents } from '../../hooks';
12
- import { NAVTREE_PLUGIN } from '../../meta';
13
13
  import { NAV_TREE_ITEM } from '../NavTree';
14
14
  import { useNavTreeContext } from '../NavTreeContext';
15
- import type { NavTreeItemColumnsProps } from '../types';
15
+ import { type NavTreeItemColumnsProps } from '../types';
16
+ import { NavTreeItemAction } from './NavTreeItemAction';
16
17
 
17
- export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }: NavTreeItemColumnsProps) => {
18
- const { t } = useTranslation(NAVTREE_PLUGIN);
19
- const { getActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
18
+ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
+ const { t } = useTranslation(meta.id);
20
+ const { renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
20
21
 
21
22
  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,
23
+ const { actions: actionsProp, groupedActions } = useActions(item);
24
+ const sortedActions = useMemo(
25
+ () =>
26
+ actionsProp.toSorted((actionA, actionB) => {
27
+ const primaryA = actionA.properties?.disposition === 'list-item-primary';
28
+ const primaryB = actionB.properties?.disposition === 'list-item-primary';
29
+ if (primaryA && !primaryB) {
30
+ return -1;
31
+ }
32
+ if (primaryB && !primaryA) {
33
+ return 1;
34
+ }
35
+ return 0;
36
+ }),
37
+ [actionsProp],
26
38
  );
39
+ const [primaryAction, ...secondaryActions] = sortedActions;
27
40
 
28
- const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
29
- .flatMap((action) => (isAction(action) ? [action] : []))
30
- .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
41
+ const actions = useMemo(
42
+ () =>
43
+ (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : sortedActions)
44
+ .flatMap((action) => (Node.isAction(action) ? [action] : []))
45
+ .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition)),
46
+ [sortedActions, primaryAction],
47
+ );
31
48
 
32
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
49
+ const primaryMenuActions = useMemo(
50
+ () =>
51
+ primaryAction
52
+ ? Node.isAction(primaryAction)
53
+ ? [primaryAction]
54
+ : groupedActions[primaryAction?.id ?? '']
55
+ : undefined,
56
+ [primaryAction, groupedActions],
57
+ );
33
58
 
34
- useLoadDescendents(item);
35
- useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
59
+ const ActionRoot = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}` ? Popover.Anchor : Fragment;
36
60
 
37
61
  return (
38
- <DensityProvider density={density}>
39
- <div role='none' className='contents app-no-drag'>
40
- {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
62
+ <div role='none' className='contents dx-app-no-drag'>
63
+ {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
64
+ <Treegrid.Cell classNames='contents'>
41
65
  <NavTreeItemAction
42
66
  testId={primaryAction.properties?.testId}
43
67
  label={toLocalizedString(primaryAction.properties?.label, t)}
44
68
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
45
69
  parent={item}
46
- monolithic={isAction(primaryAction)}
47
- menuActions={isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction.id]}
70
+ path={path}
71
+ monolithic={Node.isAction(primaryAction)}
72
+ menuActions={primaryMenuActions}
48
73
  menuType={primaryAction.properties?.menuType}
49
74
  caller={NAV_TREE_ITEM}
50
75
  />
51
- ) : (
52
- <Treegrid.Cell />
53
- )}
54
- <ActionRoot>
55
- {actions.length > 0 ? (
76
+ </Treegrid.Cell>
77
+ ) : (
78
+ <Treegrid.Cell role='none' />
79
+ )}
80
+ <ActionRoot>
81
+ {actions.length > 0 ? (
82
+ <Treegrid.Cell classNames='contents'>
56
83
  <NavTreeItemAction
57
84
  testId={`navtree.treeItem.actionsLevel${level}`}
58
- label={t('tree item actions label')}
85
+ label={t('tree-item-actions.label')}
59
86
  icon='ph--dots-three-vertical--regular'
60
87
  parent={item}
88
+ path={path}
61
89
  menuActions={actions}
62
90
  menuType='dropdown'
63
91
  caller={NAV_TREE_ITEM}
64
92
  />
65
- ) : (
66
- <Treegrid.Cell />
67
- )}
68
- </ActionRoot>
69
- {ItemEnd && <ItemEnd node={item} open={open} />}
70
- </div>
71
- </DensityProvider>
93
+ </Treegrid.Cell>
94
+ ) : (
95
+ <Treegrid.Cell role='none' />
96
+ )}
97
+ </ActionRoot>
98
+ {ItemEnd && (
99
+ <Treegrid.Cell classNames='contents'>
100
+ <ItemEnd node={item} open={open} />
101
+ </Treegrid.Cell>
102
+ )}
103
+ </div>
72
104
  );
73
105
  });