@dxos/plugin-navtree 0.8.4-main.69d29f4 → 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 (165) hide show
  1. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs +113 -0
  2. package/dist/lib/browser/CommandsDialogContent-2DKIOLJL.mjs.map +7 -0
  3. package/dist/lib/browser/CommandsTrigger-ABODVVMP.mjs +33 -0
  4. package/dist/lib/browser/CommandsTrigger-ABODVVMP.mjs.map +7 -0
  5. package/dist/lib/browser/NavTreeContainer-TLSYS4KK.mjs +12 -0
  6. package/dist/lib/browser/NavTreeContainer-TLSYS4KK.mjs.map +7 -0
  7. package/dist/lib/browser/NavTreeDocumentTitle-L6NLLWLD.mjs +26 -0
  8. package/dist/lib/browser/NavTreeDocumentTitle-L6NLLWLD.mjs.map +7 -0
  9. package/dist/lib/browser/{app-graph-builder-RSMA6I22.mjs → app-graph-builder-Z43OTNI2.mjs} +6 -5
  10. package/dist/lib/browser/app-graph-builder-Z43OTNI2.mjs.map +7 -0
  11. package/dist/lib/browser/{chunk-YKTYPINZ.mjs → chunk-A4VDCIJC.mjs} +3 -3
  12. package/dist/lib/browser/{chunk-YKTYPINZ.mjs.map → chunk-A4VDCIJC.mjs.map} +1 -1
  13. package/dist/lib/browser/chunk-AYF2TX65.mjs +14 -0
  14. package/dist/lib/browser/chunk-AYF2TX65.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-JT37R5HS.mjs +1468 -0
  16. package/dist/lib/browser/chunk-JT37R5HS.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-AGQDZBPL.mjs → chunk-KF26WA4O.mjs} +5 -5
  18. package/dist/lib/browser/{chunk-AGQDZBPL.mjs.map → chunk-KF26WA4O.mjs.map} +3 -3
  19. package/dist/lib/browser/index.mjs +35 -31
  20. package/dist/lib/browser/index.mjs.map +3 -3
  21. package/dist/lib/browser/{keyboard-32EH3N4K.mjs → keyboard-HYGFQO7T.mjs} +11 -6
  22. package/dist/lib/browser/keyboard-HYGFQO7T.mjs.map +7 -0
  23. package/dist/lib/browser/meta.json +1 -1
  24. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs +49 -0
  25. package/dist/lib/browser/operation-resolver-VDWVUII5.mjs.map +7 -0
  26. package/dist/lib/browser/{react-surface-F6CVMS3Y.mjs → react-surface-D2RLMV4Z.mjs} +15 -14
  27. package/dist/lib/browser/react-surface-D2RLMV4Z.mjs.map +7 -0
  28. package/dist/lib/browser/{state-EVYQOUI3.mjs → state-CWMNNF7K.mjs} +34 -23
  29. package/dist/lib/browser/state-CWMNNF7K.mjs.map +7 -0
  30. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +1 -1
  32. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -1
  33. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +1 -1
  34. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/operation-resolver/index.d.ts +1 -1
  36. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +2 -2
  38. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/react-surface/index.d.ts +1 -1
  40. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -1
  41. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +2 -2
  42. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -1
  43. package/dist/types/src/capabilities/state/state.d.ts +1 -1
  44. package/dist/types/src/capabilities/state/state.d.ts.map +1 -1
  45. package/dist/types/src/components/NavTree/NavTree.d.ts +10 -32
  46. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  47. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +2 -5
  48. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  49. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  50. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  51. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  52. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  53. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +4 -2
  54. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -1
  56. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  57. package/dist/types/src/components/Sidebar/L0Menu.d.ts +4 -1
  58. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  59. package/dist/types/src/components/Sidebar/L1Panel.d.ts +3 -6
  60. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +3 -2
  62. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  63. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  64. package/dist/types/src/components/index.d.ts +0 -3
  65. package/dist/types/src/components/index.d.ts.map +1 -1
  66. package/dist/types/src/components/types.d.ts +5 -13
  67. package/dist/types/src/components/types.d.ts.map +1 -1
  68. package/dist/types/src/{components/CommandsDialog/CommandsDialog.d.ts → containers/CommandsDialogContent/CommandsDialogContent.d.ts} +1 -1
  69. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -0
  70. package/dist/types/src/containers/CommandsDialogContent/index.d.ts +3 -0
  71. package/dist/types/src/containers/CommandsDialogContent/index.d.ts.map +1 -0
  72. package/dist/types/src/containers/CommandsTrigger/CommandsTrigger.d.ts.map +1 -0
  73. package/dist/types/src/containers/CommandsTrigger/index.d.ts +3 -0
  74. package/dist/types/src/containers/CommandsTrigger/index.d.ts.map +1 -0
  75. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts +9 -0
  76. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -0
  77. package/dist/types/src/containers/NavTreeContainer/index.d.ts +3 -0
  78. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -0
  79. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -0
  80. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts +3 -0
  81. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts.map +1 -0
  82. package/dist/types/src/containers/index.d.ts +7 -0
  83. package/dist/types/src/containers/index.d.ts.map +1 -0
  84. package/dist/types/src/experimental/Tree.stories.d.ts +18 -4
  85. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  86. package/dist/types/src/hooks/index.d.ts +5 -0
  87. package/dist/types/src/hooks/index.d.ts.map +1 -0
  88. package/dist/types/src/hooks/useActions.d.ts +5 -0
  89. package/dist/types/src/hooks/useActions.d.ts.map +1 -0
  90. package/dist/types/src/hooks/useLoadDescendents.d.ts +6 -0
  91. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -0
  92. package/dist/types/src/hooks/useNavTreeModel.d.ts +7 -0
  93. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -0
  94. package/dist/types/src/{hooks.d.ts → hooks/useNavTreeState.d.ts} +2 -14
  95. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -0
  96. package/dist/types/src/meta.d.ts.map +1 -1
  97. package/dist/types/src/testing/generator.d.ts +3 -3
  98. package/dist/types/src/testing/generator.d.ts.map +1 -1
  99. package/dist/types/src/types/events.d.ts.map +1 -1
  100. package/dist/types/src/util.d.ts +5 -1
  101. package/dist/types/src/util.d.ts.map +1 -1
  102. package/dist/types/tsconfig.tsbuildinfo +1 -1
  103. package/package.json +43 -42
  104. package/src/NavTreePlugin.tsx +22 -24
  105. package/src/capabilities/app-graph-builder/app-graph-builder.ts +4 -3
  106. package/src/capabilities/keyboard/keyboard.ts +6 -5
  107. package/src/capabilities/operation-resolver/operation-resolver.ts +23 -19
  108. package/src/capabilities/react-surface/react-surface.tsx +12 -13
  109. package/src/capabilities/state/state.ts +41 -13
  110. package/src/components/NavTree/NavTree.stories.tsx +21 -19
  111. package/src/components/NavTree/NavTree.tsx +61 -20
  112. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  113. package/src/components/NavTreeContext/index.ts +5 -0
  114. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +2 -2
  115. package/src/components/NavTreeItem/NavTreeItemAction.tsx +40 -23
  116. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +40 -14
  117. package/src/components/Sidebar/L0Menu.tsx +90 -74
  118. package/src/components/Sidebar/L1Panel.tsx +244 -90
  119. package/src/components/Sidebar/L1Tabs.tsx +13 -3
  120. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +2 -3
  121. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +1 -1
  122. package/src/components/index.ts +0 -3
  123. package/src/components/types.ts +12 -17
  124. package/src/{components/CommandsDialog/CommandsDialog.tsx → containers/CommandsDialogContent/CommandsDialogContent.tsx} +19 -13
  125. package/src/containers/CommandsDialogContent/index.ts +7 -0
  126. package/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.tsx +4 -4
  127. package/src/containers/CommandsTrigger/index.ts +7 -0
  128. package/src/{components → containers/NavTreeContainer}/NavTreeContainer.tsx +75 -141
  129. package/src/containers/NavTreeContainer/index.ts +6 -0
  130. package/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.tsx +1 -1
  131. package/src/containers/NavTreeDocumentTitle/index.ts +7 -0
  132. package/src/containers/index.ts +12 -0
  133. package/src/experimental/Tree.stories.tsx +54 -57
  134. package/src/experimental/Tree.tsx +3 -3
  135. package/src/hooks/index.ts +8 -0
  136. package/src/hooks/useActions.ts +34 -0
  137. package/src/hooks/useLoadDescendents.ts +23 -0
  138. package/src/hooks/useNavTreeModel.ts +118 -0
  139. package/src/{hooks.ts → hooks/useNavTreeState.ts} +3 -34
  140. package/src/meta.ts +2 -2
  141. package/src/testing/app-graph-builder.ts +5 -5
  142. package/src/testing/generator.ts +4 -4
  143. package/src/types/capabilities.ts +1 -1
  144. package/src/types/events.ts +3 -2
  145. package/src/util.ts +18 -4
  146. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs.map +0 -7
  147. package/dist/lib/browser/chunk-6YPQ625I.mjs +0 -1293
  148. package/dist/lib/browser/chunk-6YPQ625I.mjs.map +0 -7
  149. package/dist/lib/browser/keyboard-32EH3N4K.mjs.map +0 -7
  150. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs +0 -52
  151. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs.map +0 -7
  152. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs.map +0 -7
  153. package/dist/lib/browser/state-EVYQOUI3.mjs.map +0 -7
  154. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  155. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  156. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  157. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  158. package/dist/types/src/components/NavTreeContainer.d.ts +0 -16
  159. package/dist/types/src/components/NavTreeContainer.d.ts.map +0 -1
  160. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  161. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +0 -1
  162. package/dist/types/src/hooks.d.ts.map +0 -1
  163. package/src/components/CommandsDialog/index.ts +0 -6
  164. /package/dist/types/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.d.ts +0 -0
  165. /package/dist/types/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.d.ts +0 -0
@@ -4,41 +4,30 @@
4
4
 
5
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';
9
11
 
10
12
  import { useLoadDescendents } from '../../hooks';
11
- import { type NavTreeItemGraphNode } from '../../types';
12
13
  import { useNavTreeContext } from '../NavTreeContext';
13
14
  import { L0Menu, L1Tabs, type L1TabsProps } from '../Sidebar';
14
15
 
15
16
  export const NAV_TREE_ITEM = 'NavTreeItem';
16
17
 
17
- export type NavTreeProps = Pick<TreeProps<NavTreeItemGraphNode>, 'id' | 'root'> & Pick<L1TabsProps, 'open'>;
18
+ export type NavTreeProps = { id: string; root?: Node.Node; tab: string } & Pick<L1TabsProps, 'open'>;
18
19
 
19
- export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, ...props }, forwardedRef) => {
20
- const { tab, useItems, onBack } = useNavTreeContext();
21
- const topLevelActions = useItems(root, { disposition: 'menu', sort: true });
22
- const topLevelWorkspaces = useItems(root, { disposition: 'workspace' });
23
- const l0Items = topLevelWorkspaces;
24
- const pinnedItems = useItems(root, { disposition: 'pin-end', sort: true });
25
- const userAccountItem = useItems(root, { disposition: 'user-account' })[0];
26
- const topLevelItems = useMemo(
27
- () => [
28
- // prettier-ignore
29
- ...l0Items,
30
- ...pinnedItems,
31
- ...(userAccountItem ? [userAccountItem] : []),
32
- ],
33
- [l0Items, pinnedItems, userAccountItem],
34
- );
20
+ // TODO(wittjosiah): Refactor to Radix-style.
21
+ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, tab, ...props }, forwardedRef) => {
22
+ const { onBack, onItemHover } = useNavTreeContext();
23
+ const { topLevelActions, l0Items, pinnedItems, userAccountItem, topLevelItems } = useTopLevelNavItems(root);
35
24
 
36
25
  useLoadDescendents(root);
37
26
  const path = useMemo(() => [id], [id]);
38
27
 
39
28
  return (
40
29
  // TODO(thure): `Tabs.Root` forces all items that should be able to receive focus to use `Tabs.Tab(Primitive)` since
41
- // it uses RovingFocus and doesn’t support moving focus to an item that is not a tab. Assess whether this situation
30
+ // it uses RovingFocus and doesn't support moving focus to an item that is not a tab. Assess whether this situation
42
31
  // should change including whether it should motivate a change in the design/taxonomy, or if this means this should
43
32
  // not use `react-ui-tabs` at all.
44
33
  <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative' ref={forwardedRef}>
@@ -49,8 +38,60 @@ export const NavTree = forwardRef<HTMLDivElement, NavTreeProps>(({ id, root, ...
49
38
  userAccountItem={userAccountItem}
50
39
  path={path}
51
40
  parent={root}
41
+ onItemHover={onItemHover}
52
42
  />
53
43
  <L1Tabs topLevelItems={topLevelItems} path={path} currentItemId={tab} onBack={onBack} {...props} />
54
44
  </Tabs.Root>
55
45
  );
56
46
  });
47
+
48
+ /**
49
+ * Partitions root children into workspaces, pinned items, user-account, and top-level actions.
50
+ */
51
+ const useTopLevelNavItems = (root?: Node.Node) => {
52
+ const { graph } = useAppGraph();
53
+ const rootId = root?.id ?? Node.RootId;
54
+ const rootOutboundItems = useConnections(graph, rootId, 'child');
55
+ const rootActions = useGraphActions(graph, rootId);
56
+
57
+ const { topLevelActions, l0Items, pinnedItems, userAccountItem } = useMemo(() => {
58
+ const topLevelWorkspaces: Node.Node[] = [];
59
+ const outboundPinnedItems: Node.Node[] = [];
60
+ let userAccountItem: Node.Node | undefined;
61
+ for (const node of rootOutboundItems) {
62
+ switch (node.properties.disposition) {
63
+ case 'workspace':
64
+ topLevelWorkspaces.push(node);
65
+ break;
66
+ case 'pin-end':
67
+ outboundPinnedItems.push(node);
68
+ break;
69
+ case 'user-account':
70
+ userAccountItem ??= node;
71
+ break;
72
+ }
73
+ }
74
+
75
+ const topLevelActions = rootActions
76
+ .filter((action) => action.properties.disposition === 'menu')
77
+ .toSorted((a, b) => byPosition(a.properties, b.properties));
78
+ const pinnedItems = [
79
+ ...outboundPinnedItems,
80
+ ...rootActions.filter((action) => action.properties.disposition === 'pin-end'),
81
+ ].toSorted((a, b) => byPosition(a.properties, b.properties));
82
+
83
+ return {
84
+ topLevelActions,
85
+ l0Items: topLevelWorkspaces,
86
+ pinnedItems,
87
+ userAccountItem,
88
+ };
89
+ }, [rootActions, rootOutboundItems]);
90
+
91
+ const topLevelItems = useMemo(
92
+ () => [...l0Items, ...pinnedItems, ...(userAccountItem ? [userAccountItem] : [])],
93
+ [l0Items, pinnedItems, userAccountItem],
94
+ );
95
+
96
+ return { topLevelActions, l0Items, pinnedItems, userAccountItem, topLevelItems };
97
+ };
@@ -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';
@@ -43,7 +43,7 @@ const menuActions = faker.helpers.multiple(
43
43
  );
44
44
 
45
45
  const meta = {
46
- title: 'plugins/plugin-navtree/NavTreeItemAction',
46
+ title: 'plugins/plugin-navtree/components/NavTreeItemAction',
47
47
  component: NavTreeItemAction,
48
48
  args: {
49
49
  icon: 'ph--list--regular',
@@ -52,7 +52,7 @@ const meta = {
52
52
  label: 'Select action',
53
53
  } satisfies Partial<NavTreeItemActionMenuProps>,
54
54
  decorators: [
55
- withTheme,
55
+ withTheme(),
56
56
  withPluginManager({
57
57
  plugins: [...corePlugins(), RuntimePlugin()],
58
58
  }),
@@ -2,12 +2,12 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useCallback } from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
8
  import { useActionRunner } from '@dxos/plugin-graph';
9
9
  import { IconButton, toLocalizedString, useDensityContext, useTranslation } from '@dxos/react-ui';
10
- import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
10
+ import { Menu, type MenuItem } from '@dxos/react-ui-menu';
11
11
  import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
12
12
 
13
13
  import { meta } from '../../meta';
@@ -15,6 +15,7 @@ import { type ActionProperties } from '../../types';
15
15
 
16
16
  export type NavTreeItemActionMenuProps = ActionProperties & {
17
17
  parent: Node.Node;
18
+ path?: string[];
18
19
  caller?: string;
19
20
  monolithic?: boolean;
20
21
  menuActions?: Node.Action[];
@@ -33,6 +34,7 @@ const coarseActionButtonProps = {
33
34
 
34
35
  export const NavTreeItemActionDropdownMenu = ({
35
36
  parent,
37
+ path,
36
38
  label,
37
39
  icon,
38
40
  testId,
@@ -42,31 +44,40 @@ export const NavTreeItemActionDropdownMenu = ({
42
44
  const { t } = useTranslation(meta.id);
43
45
  const density = useDensityContext();
44
46
  const runAction = useActionRunner();
47
+ const handleAction = useCallback(
48
+ (action: Node.Action, params: Node.InvokeProps = {}) => runAction(action, { ...params, path }),
49
+ [runAction, path],
50
+ );
45
51
 
46
52
  return (
47
- <MenuProvider onAction={runAction}>
48
- <DropdownMenu.Root group={parent} items={menuActions as MenuItem[]} caller={caller}>
49
- <DropdownMenu.Trigger asChild>
50
- <IconButton
51
- {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
52
- classNames={['shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem]}
53
- variant='ghost'
54
- icon={icon ?? fallbackIcon}
55
- iconOnly
56
- label={toLocalizedString(label, t)}
57
- data-testid={testId}
58
- />
59
- </DropdownMenu.Trigger>
60
- </DropdownMenu.Root>
61
- </MenuProvider>
53
+ <Menu.Root caller={caller} onAction={handleAction}>
54
+ <Menu.Trigger asChild>
55
+ <IconButton
56
+ {...(density === 'coarse' ? coarseActionButtonProps : fineActionButtonProps)}
57
+ classNames={['shrink-0 px-2 pointer-fine:px-1', hoverableControlItem, hoverableOpenControlItem]}
58
+ variant='ghost'
59
+ icon={icon ?? fallbackIcon}
60
+ iconOnly
61
+ label={toLocalizedString(label, t)}
62
+ data-testid={testId}
63
+ />
64
+ </Menu.Trigger>
65
+ <Menu.Content group={parent} items={menuActions as MenuItem[]} />
66
+ </Menu.Root>
62
67
  );
63
68
  };
64
69
 
65
70
  export const NavTreeItemMonolithicAction = (
66
- props: Node.Action & { parent: Node.Node; onAction?: (action: Node.Action) => void; baseLabel: string },
71
+ props: Node.Action & {
72
+ parent: Node.Node;
73
+ path?: string[];
74
+ onAction?: (action: Node.Action) => void;
75
+ baseLabel: string;
76
+ },
67
77
  ) => {
68
78
  const {
69
79
  parent,
80
+ path,
70
81
  properties: { disabled, caller, testId, icon, variant = 'ghost', iconOnly = true } = { label: 'never' },
71
82
  baseLabel,
72
83
  } = props;
@@ -80,7 +91,7 @@ export const NavTreeItemMonolithicAction = (
80
91
  'shrink-0',
81
92
  hoverableControlItem,
82
93
  hoverableOpenControlItem,
83
- iconOnly ? 'pli-2 pointer-fine:pli-1' : 'p-2 pointer-fine:p-2 mie-1',
94
+ iconOnly ? 'px-2 pointer-fine:px-1' : 'p-2 pointer-fine:p-2 me-1',
84
95
  ]}
85
96
  icon={icon ?? fallbackIcon}
86
97
  iconOnly={iconOnly}
@@ -92,22 +103,28 @@ export const NavTreeItemMonolithicAction = (
92
103
  return;
93
104
  }
94
105
 
95
- void runAction(props, caller ? { parent, caller } : { parent });
106
+ void runAction(props, caller ? { parent, caller, path } : { parent, path });
96
107
  }}
97
108
  data-testid={testId}
98
109
  />
99
110
  );
100
111
  };
101
112
 
102
- export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
113
+ export const NavTreeItemAction = ({
114
+ monolithic,
115
+ menuActions,
116
+ parent: node,
117
+ path,
118
+ ...props
119
+ }: NavTreeItemActionMenuProps) => {
103
120
  const { t } = useTranslation(meta.id);
104
121
 
105
122
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
106
123
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
107
124
 
108
125
  return monolithic && menuActions?.length === 1 ? (
109
- <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
126
+ <NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} path={path} {...menuActions[0]} />
110
127
  ) : (
111
- <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} menuActions={menuActions} />
128
+ <NavTreeItemActionDropdownMenu {...props} label={baseLabel} parent={node} path={path} menuActions={menuActions} />
112
129
  );
113
130
  };
@@ -2,12 +2,12 @@
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
7
  import { Node } from '@dxos/app-graph';
8
8
  import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { useLoadDescendents } from '../../hooks';
10
+ import { useActions } from '../../hooks';
11
11
  import { meta } from '../../meta';
12
12
  import { NAV_TREE_ITEM } from '../NavTree';
13
13
  import { useNavTreeContext } from '../NavTreeContext';
@@ -17,25 +17,49 @@ import { NavTreeItemAction } from './NavTreeItemAction';
17
17
 
18
18
  export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
19
  const { t } = useTranslation(meta.id);
20
- const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
20
+ const { renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
21
21
 
22
22
  const level = path.length - 2;
23
- const { actions: _actions, groupedActions } = useActions(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) => (Node.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
- useLoadDescendents(item);
33
- useLoadDescendents(primaryAction && !Node.isAction(primaryAction) ? (primaryAction as Node.Node) : undefined);
49
+ const primaryMenuActions = useMemo(
50
+ () =>
51
+ primaryAction
52
+ ? Node.isAction(primaryAction)
53
+ ? [primaryAction]
54
+ : groupedActions[primaryAction?.id ?? '']
55
+ : undefined,
56
+ [primaryAction, groupedActions],
57
+ );
34
58
 
35
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
59
+ const ActionRoot = popoverAnchorId === `${NAV_TREE_ITEM}:${item.id}` ? Popover.Anchor : Fragment;
36
60
 
37
61
  return (
38
- <div role='none' className='contents app-no-drag'>
62
+ <div role='none' className='contents dx-app-no-drag'>
39
63
  {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
64
  <Treegrid.Cell classNames='contents'>
41
65
  <NavTreeItemAction
@@ -43,8 +67,9 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
43
67
  label={toLocalizedString(primaryAction.properties?.label, t)}
44
68
  icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
45
69
  parent={item}
70
+ path={path}
46
71
  monolithic={Node.isAction(primaryAction)}
47
- menuActions={Node.isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction?.id ?? '']}
72
+ menuActions={primaryMenuActions}
48
73
  menuType={primaryAction.properties?.menuType}
49
74
  caller={NAV_TREE_ITEM}
50
75
  />
@@ -60,6 +85,7 @@ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumns
60
85
  label={t('tree item actions label')}
61
86
  icon='ph--dots-three-vertical--regular'
62
87
  parent={item}
88
+ path={path}
63
89
  menuActions={actions}
64
90
  menuType='dropdown'
65
91
  caller={NAV_TREE_ITEM}