@dxos/plugin-navtree 0.6.14-main.2b6a0f3 → 0.6.14-main.f49f251

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 (46) hide show
  1. package/dist/lib/browser/index.mjs +576 -386
  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.map +1 -1
  5. package/dist/types/src/components/NavTree.d.ts +20 -0
  6. package/dist/types/src/components/NavTree.d.ts.map +1 -0
  7. package/dist/types/src/components/NavTree.stories.d.ts +7 -0
  8. package/dist/types/src/components/NavTree.stories.d.ts.map +1 -0
  9. package/dist/types/src/components/NavTreeContainer.d.ts +8 -8
  10. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  11. package/dist/types/src/components/NavTreeItemAction.d.ts +23 -0
  12. package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -0
  13. package/dist/types/src/components/NavTreeItemAction.stories.d.ts +45 -0
  14. package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -0
  15. package/dist/types/src/components/index.d.ts +1 -0
  16. package/dist/types/src/components/index.d.ts.map +1 -1
  17. package/dist/types/src/stories/Graph.stories.d.ts +1 -6
  18. package/dist/types/src/stories/Graph.stories.d.ts.map +1 -1
  19. package/dist/types/src/testing/generator.d.ts +86 -0
  20. package/dist/types/src/testing/generator.d.ts.map +1 -0
  21. package/dist/types/src/testing/index.d.ts +3 -0
  22. package/dist/types/src/testing/index.d.ts.map +1 -0
  23. package/dist/types/src/testing/visitor.d.ts +18 -0
  24. package/dist/types/src/testing/visitor.d.ts.map +1 -0
  25. package/dist/types/src/translations.d.ts +5 -10
  26. package/dist/types/src/translations.d.ts.map +1 -1
  27. package/dist/types/src/types.d.ts +41 -0
  28. package/dist/types/src/types.d.ts.map +1 -0
  29. package/dist/types/src/util.d.ts +8 -9
  30. package/dist/types/src/util.d.ts.map +1 -1
  31. package/package.json +36 -33
  32. package/src/NavTreePlugin.tsx +88 -33
  33. package/src/components/NavTree.stories.tsx +104 -0
  34. package/src/components/NavTree.tsx +127 -0
  35. package/src/components/NavTreeContainer.tsx +141 -325
  36. package/src/components/NavTreeItemAction.stories.tsx +57 -0
  37. package/src/components/NavTreeItemAction.tsx +367 -0
  38. package/src/components/index.ts +1 -0
  39. package/src/experimental/Tree.stories.tsx +1 -1
  40. package/src/stories/Graph.stories.tsx +104 -98
  41. package/src/testing/generator.ts +199 -0
  42. package/src/testing/index.ts +6 -0
  43. package/src/testing/visitor.ts +152 -0
  44. package/src/translations.ts +3 -3
  45. package/src/types.ts +50 -0
  46. package/src/util.ts +52 -63
@@ -2,79 +2,59 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import React, { type ReactNode, useCallback, useMemo } from 'react';
5
+ import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
6
+ import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
7
+ import React, { useCallback, useEffect } from 'react';
6
8
 
7
9
  import { NavigationAction, Surface, useIntentDispatcher } from '@dxos/app-framework';
8
- import { getGraph, isAction, isActionLike } from '@dxos/app-graph';
10
+ import { isAction, isActionLike, type Node } from '@dxos/app-graph';
11
+ import { useGraph } from '@dxos/plugin-graph';
12
+ import { isEchoObject, isSpace } from '@dxos/react-client/echo';
9
13
  import { ElevationProvider, useMediaQuery, useSidebars } from '@dxos/react-ui';
10
- import { List } from '@dxos/react-ui-list';
11
- import { type MosaicDropEvent, type MosaicMoveEvent, Mosaic, Path } from '@dxos/react-ui-mosaic';
12
- import {
13
- getLevel,
14
- NavTree,
15
- type NavTreeActionsNode,
16
- type NavTreeItemMoveDetails,
17
- type NavTreeItemNode,
18
- type NavTreeNode,
19
- type NavTreeProps,
20
- } from '@dxos/react-ui-navtree';
21
- import { ghostHover, mx } from '@dxos/react-ui-theme';
14
+ import { isItem } from '@dxos/react-ui-list';
22
15
  import { arrayMove } from '@dxos/util';
23
16
 
17
+ import { NAV_TREE_ITEM, NavTree, type NavTreeProps } from './NavTree';
24
18
  import { NavTreeFooter } from './NavTreeFooter';
25
19
  import { NAVTREE_PLUGIN } from '../meta';
20
+ import { type NavTreeItem } from '../types';
26
21
  import {
27
22
  expandActions,
28
23
  expandChildren,
29
24
  getChildren,
30
25
  getParent,
31
26
  resolveMigrationOperation,
32
- treeItemsFromRootNode,
33
- type NavTreeItem,
34
27
  type NavTreeItemGraphNode,
35
28
  } from '../util';
36
29
 
37
30
  // TODO(thure): Is NavTree truly authoritative in this regard?
38
31
  export const NODE_TYPE = 'dxos/app-graph/node';
39
32
 
40
- const renderPresence = (node: NavTreeNode): ReactNode => (
41
- <Surface role='presence--glyph' data={{ object: node.data }} />
33
+ const renderPresence = ({ item }: { item: NavTreeItem }) => (
34
+ <Surface role='presence--glyph' data={{ object: item.node.data, id: item.node.id }} />
42
35
  );
43
36
 
44
- export const NavTreeContainer = ({
45
- root,
46
- activeIds,
47
- openItemIds,
48
- onOpenItemIdsChange,
49
- attended,
50
- popoverAnchorId,
51
- }: {
52
- root: NavTreeItemGraphNode;
53
- activeIds: Set<string>;
54
- openItemIds: Record<string, true>;
55
- onOpenItemIdsChange: (nextOpenItemIds: Record<string, true>) => void;
56
- // TODO(wittjosiah): Use attention context directly?
57
- attended: string[];
37
+ export type NavTreeContainerProps = {
38
+ items: NavTreeItem[];
39
+ current: string[];
40
+ open: string[];
58
41
  popoverAnchorId?: string;
59
- }) => {
42
+ } & Pick<NavTreeProps, 'onOpenChange'>;
43
+
44
+ export const NavTreeContainer = ({ items, current, open, popoverAnchorId, ...props }: NavTreeContainerProps) => {
60
45
  const { closeNavigationSidebar } = useSidebars(NAVTREE_PLUGIN);
61
46
  const [isLg] = useMediaQuery('lg', { ssr: false });
62
47
  const dispatch = useIntentDispatcher();
63
-
64
- const graph = useMemo(() => getGraph(root), [root]);
65
- const items = treeItemsFromRootNode(graph, root, openItemIds);
48
+ const { graph } = useGraph();
66
49
 
67
50
  const loadDescendents = useCallback(
68
- (node: NavTreeNode | NavTreeActionsNode) => {
69
- void expandActions(graph, node as NavTreeItemGraphNode);
51
+ (node: Node) => {
52
+ void expandActions(graph, node);
70
53
  if (!isActionLike(node)) {
71
- void expandChildren(graph, node as NavTreeItemGraphNode).then(() =>
54
+ void expandChildren(graph, node).then(() =>
72
55
  // Load one level deeper, which resolves some juddering observed on open/close.
73
56
  Promise.all(
74
- getChildren(graph, node as NavTreeItemGraphNode).flatMap((child) => [
75
- expandActions(graph, child),
76
- expandChildren(graph, child),
77
- ]),
57
+ getChildren(graph, node).flatMap((child) => [expandActions(graph, child), expandChildren(graph, child)]),
78
58
  ),
79
59
  );
80
60
  }
@@ -82,311 +62,147 @@ export const NavTreeContainer = ({
82
62
  [graph],
83
63
  );
84
64
 
85
- const handleNavigate = async ({ node, actions }: NavTreeItemNode) => {
86
- if (!node.data) {
87
- return;
88
- }
89
-
90
- // TODO(thure): Refactor opening related planks (comments in this case) to a generalized approach.
91
- await dispatch({
92
- action: NavigationAction.OPEN,
93
- data: {
94
- activeParts: {
95
- main: [node.id],
96
- },
97
- },
98
- });
99
-
100
- const defaultAction = actions?.find((action) => action.properties?.disposition === 'default');
101
- if (isAction(defaultAction)) {
102
- void (defaultAction.data as () => void)();
65
+ const canDrop = useCallback((source: NavTreeItem, target: NavTreeItem) => {
66
+ if (isSpace(target.node.data)) {
67
+ // TODO(wittjosiah): Find a way to only allow space as source for rearranging.
68
+ return isEchoObject(source.node.data) || isSpace(source.node.data);
69
+ } else if (isEchoObject(target.node.data)) {
70
+ return isEchoObject(source.node.data);
71
+ } else {
72
+ return false;
103
73
  }
104
- if (!isLg) {
105
- closeNavigationSidebar();
106
- }
107
- };
74
+ }, []);
108
75
 
109
- const handleItemOpenChange = ({ id, actions, path }: NavTreeItemNode, nextOpen: boolean) => {
110
- if (path) {
111
- if (nextOpen) {
112
- onOpenItemIdsChange({ ...openItemIds, [id]: true });
113
- } else {
114
- // TODO(thure): Filter vs single-remove, make setting?
115
- const { [id]: _, ...nextOpenItemIds } = openItemIds;
116
- onOpenItemIdsChange(nextOpenItemIds);
76
+ const handleSelect = useCallback(
77
+ ({ node, actions, path }: NavTreeItem) => {
78
+ if (!node.data) {
79
+ return;
117
80
  }
118
- }
119
-
120
- // TODO(wittjosiah): This is a temporary solution to ensure spaces get enabled when they are expanded.
121
- const defaultAction = actions?.find((action) => action.properties?.disposition === 'default');
122
- if (isAction(defaultAction)) {
123
- void (defaultAction.data as () => void)();
124
- }
125
- };
126
81
 
127
- const resolveItemLevel = useCallback(
128
- (overPosition: number | undefined, activeId: string | undefined, levelOffset: number) => {
129
- if (!(typeof overPosition === 'number' && activeId)) {
130
- return 1;
131
- } else {
132
- const nextItems = arrayMove(
133
- items,
134
- items.findIndex(({ id }) => id === activeId),
135
- overPosition,
136
- );
137
- const previousItem: NavTreeItem | undefined = nextItems[overPosition - 1];
138
- const nextItem: NavTreeItem | undefined = nextItems[overPosition + 1];
139
- return Math.min(
140
- previousItem ? getLevel(previousItem?.path) + 1 : 1,
141
- Math.max(nextItem ? getLevel(nextItem?.path) : 1, getLevel(nextItems[overPosition].path) + levelOffset),
142
- );
82
+ if (isAction(node)) {
83
+ const [parentId] = path.slice(1);
84
+ const parent = items.find(({ id }) => id === parentId)?.node;
85
+ void (parent && node.data({ node: parent, caller: NAV_TREE_ITEM }));
86
+ return;
143
87
  }
144
- },
145
- [items],
146
- );
147
88
 
148
- const handleMove: NavTreeProps['onMove'] = useCallback(
149
- ({ active, over, details }: MosaicMoveEvent<number, NavTreeItemMoveDetails>) => {
150
- const levelOffset = Math.floor((details?.delta?.x ?? 0) / 16);
151
- const overPosition = over.position ?? 0;
152
- const nextItems = arrayMove(
153
- items,
154
- items.findIndex(({ id }) => id === active.item.id),
155
- overPosition,
156
- );
89
+ // TODO(thure): Refactor opening related planks (comments in this case) to a generalized approach.
90
+ void dispatch({
91
+ action: NavigationAction.OPEN,
92
+ data: {
93
+ activeParts: {
94
+ main: [node.id],
95
+ },
96
+ },
97
+ });
157
98
 
158
- const previousItem: NavTreeItem | undefined = nextItems[overPosition - 1];
159
- const nextItem: NavTreeItem | undefined = nextItems[overPosition + 1];
160
- const activeNode = 'node' in active.item ? (active.item as NavTreeItem).node : undefined;
161
- if (!activeNode || !previousItem || !previousItem.path) {
162
- // console.log('[reject]', !activeNode, !previousItem, !previousItem.path);
163
- // log.warn('Top-level rearrange before the first item of the NavTree is unsupported at this time.');
164
- return { operation: 'reject' as const, details: { levelOffset } };
99
+ const defaultAction = actions?.find((action) => action.properties?.disposition === 'default');
100
+ if (isAction(defaultAction)) {
101
+ void (defaultAction.data as () => void)();
165
102
  }
166
103
 
167
- const overLevel = resolveItemLevel(overPosition, active.item.id, levelOffset);
168
- const previousLevel = getLevel(previousItem.path);
169
- // console.log('[over]', overLevel, previousLevel, levelOffset);
170
- if (previousLevel === overLevel - 1) {
171
- if (Path.hasChild(previousItem.id, active.item.id)) {
172
- // Previous is already parent of Active, rearrange.
173
- const operation = previousItem.node.properties.onRearrangeChildren
174
- ? ('rearrange' as const)
175
- : ('reject' as const);
176
- return { operation, details: { levelOffset } };
177
- } else {
178
- // Previous is not yet parent of Active, check transfer or copy.
179
- const operation = resolveMigrationOperation(graph, activeNode, previousItem.path, previousItem.node);
180
- // console.log('[migration result]', operation, 'Previous is not yet parent of Active, check transfer or copy.');
181
- return { operation, details: { levelOffset } };
182
- }
183
- } else if (previousLevel === overLevel) {
184
- const parent = getParent(graph, previousItem.node, previousItem.path);
185
- const parentPath = previousItem.path.slice(0, previousItem.path.length - 1);
186
- if (Path.siblings(previousItem.id, active.item.id)) {
187
- // Previous is already a sibling of Active, rearrange.
188
- const operation = parent?.properties.onRearrangeChildren ? ('rearrange' as const) : ('reject' as const);
189
- return { operation, details: { levelOffset } };
190
- } else {
191
- // Previous is not yet a sibling of Active, transfer/copy to Previous’s parent.
192
- const operation = resolveMigrationOperation(graph, activeNode, parentPath, parent);
193
- // console.log(
194
- // '[migration result]',
195
- // operation,
196
- // 'Previous is not yet a sibling of Active, transfer/copy to Previous’s parent.',
197
- // );
198
- return { operation, details: { levelOffset } };
199
- }
200
- } else if (nextItem && nextItem.path) {
201
- const nextLevel = getLevel(nextItem.path);
202
- if (nextLevel === overLevel) {
203
- const parent = getParent(graph, nextItem.node, nextItem.path);
204
- const parentPath = nextItem.path.slice(0, nextItem.path.length - 1);
205
- if (Path.siblings(nextItem.id, active.item.id)) {
206
- // Next is already a sibling of Active, rearrange.
207
- const operation = parent?.properties.onRearrangeChildren ? ('rearrange' as const) : ('reject' as const);
208
- return { operation, details: { levelOffset } };
209
- } else {
210
- // Next is not yet a sibling of Active, transfer to Next’s parent.
211
- const operation = resolveMigrationOperation(graph, activeNode, parentPath, parent);
212
- // console.log(
213
- // '[migration result]',
214
- // operation,
215
- // 'Next is not yet a sibling of Active, transfer to Next’s parent.',
216
- // );
217
- return { operation, details: { levelOffset } };
218
- }
219
- } else {
220
- // console.log('[migration result]', 'reject');
221
- return { operation: 'reject' as const, details: { levelOffset } };
222
- }
223
- } else {
224
- // console.log('[migration result]', 'reject');
225
- return { operation: 'reject' as const, details: { levelOffset } };
104
+ if (!isLg) {
105
+ closeNavigationSidebar();
226
106
  }
227
107
  },
228
- [root, items],
108
+ [items, dispatch, isLg, closeNavigationSidebar],
229
109
  );
230
110
 
231
- const handleDrop = useCallback(
232
- ({ operation, active, over, details = {} }: MosaicDropEvent<number, NavTreeItemMoveDetails>) => {
233
- if (Path.first(over.path) !== root.id) {
234
- return undefined;
235
- }
236
-
237
- const { levelOffset = 0 } = details;
238
- const overPosition = over.position ?? 0;
239
- const nextItems = arrayMove(
240
- items,
241
- items.findIndex(({ id }) => id === active.item.id),
242
- overPosition,
243
- );
244
-
245
- const activeNode = 'node' in active.item ? (active.item as NavTreeItem).node : undefined;
246
- const activeParentId = Path.parent(active.item.id);
247
- if (!activeNode) {
248
- return undefined;
249
- }
111
+ // TODO(wittjosiah): Factor out hook.
112
+ useEffect(() => {
113
+ return monitorForElements({
114
+ canMonitor: ({ source }) => isItem(source.data),
115
+ onDrop: ({ location, source }) => {
116
+ // Didn't drop on anything.
117
+ if (!location.current.dropTargets.length) {
118
+ return;
119
+ }
250
120
 
251
- const activeParent = getParent(graph, activeNode, (active.item as NavTreeItem).path ?? []);
252
- if (operation === 'rearrange') {
253
- void activeParent?.properties.onRearrangeChildren?.(
254
- nextItems.filter(({ id }) => Path.hasChild(activeParentId, id)).map(({ node }) => node.data),
255
- );
256
- return null;
257
- } else {
258
- const previousItem: NavTreeItem | undefined = nextItems[overPosition - 1];
259
- const nextItem: NavTreeItem | undefined = nextItems[overPosition + 1];
260
- const overLevel = resolveItemLevel(overPosition, active.item.id, levelOffset);
261
- const previousLevel = getLevel(previousItem.path);
262
- if (operation === 'copy') {
263
- if (previousLevel === overLevel - 1) {
264
- void previousItem.node.properties.onCopy?.(activeNode, 0);
265
- return null;
266
- } else if (previousLevel === overLevel) {
267
- const parent = getParent(graph, previousItem.node, previousItem.path ?? []);
268
- void parent?.properties.onCopy?.(
269
- activeNode,
270
- getChildren(graph, parent).findIndex(({ id }) => id === previousItem.node.id) + 1,
271
- );
272
- return null;
273
- } else if (nextItem && nextItem.path) {
274
- const parent = getParent(graph, nextItem.node, nextItem.path ?? []);
275
- void parent?.properties.onCopy?.(
276
- activeNode,
277
- getChildren(graph, parent).findIndex(({ id }) => id === nextItem.node.id),
278
- );
279
- return null;
280
- }
281
- } else if (operation === 'transfer') {
282
- const onTransferEnd = activeParent?.properties.onTransferEnd;
283
- if (!onTransferEnd) {
284
- return undefined;
285
- } else if (previousLevel === overLevel - 1) {
286
- const onTransferStart = previousItem.node.properties.onTransferStart;
287
- if (onTransferStart) {
288
- void onTransferEnd(activeNode, previousItem.node);
289
- void onTransferStart(activeNode, 0);
290
- return null;
121
+ const target = location.current.dropTargets[0];
122
+ const instruction: Instruction | null = extractInstruction(target.data);
123
+ if (instruction !== null && instruction.type !== 'instruction-blocked') {
124
+ const sourceNode = source.data.node as NavTreeItemGraphNode;
125
+ const targetNode = target.data.node as NavTreeItemGraphNode;
126
+
127
+ const sourcePath = source.data.path as string[];
128
+ const targetPath = target.data.path as string[];
129
+
130
+ const nextItems = instruction.type.startsWith('reorder')
131
+ ? items.filter((item) => item.path.slice(0, -1).join() === targetPath.slice(0, -1).join())
132
+ : items.filter((item) => item.path.slice(0, -1).join() === targetPath.join());
133
+ const operation =
134
+ sourcePath.slice(0, -1).join() === targetPath.slice(0, -1).join() && instruction.type !== 'make-child'
135
+ ? 'rearrange'
136
+ : resolveMigrationOperation(graph, sourceNode, targetPath, targetNode);
137
+
138
+ const sourceParent = getParent(graph, sourceNode, sourcePath);
139
+ const targetParent = getParent(graph, targetNode, targetPath);
140
+
141
+ const sourceIndex = nextItems.findIndex(({ id }) => id === sourceNode.id);
142
+ const targetIndex = nextItems.findIndex(({ id }) => id === targetNode.id);
143
+ const migrationIndex =
144
+ instruction.type === 'make-child'
145
+ ? nextItems.length > 0
146
+ ? nextItems.length - 1
147
+ : 0
148
+ : instruction.type === 'reorder-below'
149
+ ? targetIndex + 1
150
+ : targetIndex;
151
+
152
+ switch (operation) {
153
+ case 'rearrange': {
154
+ arrayMove(nextItems, sourceIndex, targetIndex);
155
+ void sourceParent?.properties.onRearrangeChildren?.(nextItems.map((item) => item.node.data));
156
+ break;
291
157
  }
292
- } else if (previousLevel === overLevel && previousItem.path) {
293
- const parent = getParent(graph, previousItem.node, previousItem.path);
294
- if (parent?.properties.onTransferStart) {
295
- void onTransferEnd(activeNode, previousItem.node);
296
- void parent.properties.onTransferStart(
297
- activeNode,
298
- getChildren(graph, parent).findIndex(({ id }) => id === previousItem.node.id) + 1,
299
- );
300
- return null;
158
+
159
+ case 'copy': {
160
+ const target = instruction.type === 'make-child' ? targetNode : targetParent;
161
+ void target?.properties.onCopy?.(sourceNode, migrationIndex);
162
+ break;
301
163
  }
302
- } else if (nextItem && nextItem.path) {
303
- const parent = getParent(graph, nextItem.node, nextItem.path);
304
- if (parent?.properties.onTransferStart) {
305
- void onTransferEnd(activeNode, previousItem.node);
306
- void parent.properties.onTransferStart(
307
- activeNode,
308
- getChildren(graph, parent).findIndex(({ id }) => id === nextItem.node.id),
309
- );
310
- return null;
164
+
165
+ case 'transfer': {
166
+ const target = instruction.type === 'make-child' ? targetNode : targetParent;
167
+ if (!target?.properties.onTransferStart || !sourceParent?.properties.onTransferEnd) {
168
+ break;
169
+ }
170
+
171
+ void target?.properties.onTransferStart(sourceNode, migrationIndex);
172
+ void sourceParent?.properties.onTransferEnd?.(sourceNode, target);
173
+ break;
311
174
  }
312
175
  }
313
176
  }
314
- }
315
- return undefined;
316
- },
317
- [root, items],
318
- );
319
-
320
- const handleDragEnd = useCallback(() => {
321
- onOpenItemIdsChange({ ...openItemIds });
322
- }, [onOpenItemIdsChange, openItemIds]);
323
-
324
- // TODO(burdon): Remove.
325
- // Debug list.
326
- const debug = false;
327
- if (debug) {
328
- const getLabel = (item: NavTreeItem) => {
329
- const label = item.node?.properties?.label;
330
- return typeof label === 'string' ? label : item.id;
331
- };
177
+ },
178
+ });
179
+ }, [items]);
332
180
 
333
- return (
334
- <div className='bs-full overflow-hidden row-span-3 grid grid-cols-1 grid-rows-[min-content_1fr_min-content]'>
335
- <div className='flex items-center p-2'>Items: {items.length}</div>
181
+ return (
182
+ <ElevationProvider elevation='chrome'>
183
+ <div
184
+ role='none'
185
+ className='bs-full overflow-hidden row-span-3 grid grid-cols-1 grid-rows-[min-content_1fr_min-content]'
186
+ >
187
+ <Surface role='search-input' limit={1} />
188
+
189
+ {/* TODO(thure): What gives this an inline `overflow: initial`? */}
336
190
  <div role='none' className='!overflow-y-auto'>
337
- <List.Root<NavTreeItem> items={items} isItem={() => true}>
338
- {({ items }) =>
339
- items.map((item) => (
340
- <List.Item<NavTreeItem> key={item.id} item={item} classNames={mx('p-2', ghostHover)}>
341
- <List.ItemDragHandle />
342
- <List.ItemTitle classNames='p-2 text-sm' onClick={() => handleNavigate(item)}>
343
- {getLabel(item)}
344
- </List.ItemTitle>
345
- <List.IconButton icon='ph--caret-down--regular' onClick={() => handleItemOpenChange(item, true)} />
346
- </List.Item>
347
- ))
348
- }
349
- </List.Root>
191
+ <NavTree
192
+ items={items}
193
+ open={open}
194
+ current={current}
195
+ loadDescendents={loadDescendents}
196
+ renderPresence={renderPresence}
197
+ popoverAnchorId={popoverAnchorId}
198
+ canDrop={canDrop}
199
+ onSelect={handleSelect}
200
+ {...props}
201
+ />
350
202
  </div>
203
+
351
204
  <NavTreeFooter />
352
205
  </div>
353
- );
354
- }
355
-
356
- return (
357
- <Mosaic.Root>
358
- <Mosaic.DragOverlay />
359
- <ElevationProvider elevation='chrome'>
360
- <div
361
- role='none'
362
- className='bs-full overflow-hidden row-span-3 grid grid-cols-1 grid-rows-[min-content_1fr_min-content]'
363
- >
364
- <Surface role='search-input' limit={1} />
365
-
366
- {/* TODO(thure): what gives this an inline `overflow: initial`? */}
367
- <div role='none' className='!overflow-y-auto'>
368
- <NavTree
369
- id={root.id}
370
- items={items}
371
- current={activeIds}
372
- attended={attended}
373
- type={NODE_TYPE}
374
- loadDescendents={loadDescendents}
375
- popoverAnchorId={popoverAnchorId}
376
- renderPresence={renderPresence}
377
- resolveItemLevel={resolveItemLevel}
378
- open={openItemIds}
379
- onNavigate={handleNavigate}
380
- onItemOpenChange={handleItemOpenChange}
381
- onMove={handleMove}
382
- onDrop={handleDrop}
383
- onDragEnd={handleDragEnd}
384
- />
385
- </div>
386
-
387
- <NavTreeFooter />
388
- </div>
389
- </ElevationProvider>
390
- </Mosaic.Root>
206
+ </ElevationProvider>
391
207
  );
392
208
  };
@@ -0,0 +1,57 @@
1
+ //
2
+ // Copyright 2023 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type NodeArg } from '@dxos/app-graph';
8
+ import { faker } from '@dxos/random';
9
+ import { withLayout, withTheme } from '@dxos/storybook-utils';
10
+
11
+ import { NavTreeItemAction, type NavTreeItemActionMenuProps } from './NavTreeItemAction';
12
+
13
+ const parent = {
14
+ id: faker.string.uuid(),
15
+ type: 'node',
16
+ data: null,
17
+ properties: {
18
+ label: faker.lorem.words(2),
19
+ icon: 'ph--circle--regular',
20
+ },
21
+ } satisfies NodeArg<any>;
22
+
23
+ // TODO(burdon): Factor out across tests.
24
+ const menuActions = faker.helpers.multiple(
25
+ () =>
26
+ ({
27
+ id: faker.string.uuid(),
28
+ type: 'action',
29
+ data: () => {
30
+ console.log('invoke');
31
+ },
32
+ properties: {
33
+ label: faker.lorem.words(2),
34
+ icon: 'ph--circle--regular',
35
+ },
36
+ }) satisfies NodeArg<any>,
37
+ { count: 20 },
38
+ );
39
+
40
+ export default {
41
+ title: 'plugins/plugin-navtree/NavTreeItemAction',
42
+ component: NavTreeItemAction,
43
+ decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
44
+ args: {
45
+ icon: 'ph--list--regular',
46
+ parent,
47
+ menuActions,
48
+ label: 'Select action',
49
+ onAction: (action) => {
50
+ console.log(action);
51
+ },
52
+ } satisfies Partial<NavTreeItemActionMenuProps>,
53
+ };
54
+
55
+ export const Default = {};
56
+
57
+ export const SearchList = { args: { menuType: 'searchList' } };