@dxos/plugin-navtree 0.8.4-main.3c1ae3b → 0.8.4-main.3eb6e50203
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.
- package/dist/lib/browser/app-graph-builder-XGA4GPGL.mjs +44 -0
- package/dist/lib/browser/app-graph-builder-XGA4GPGL.mjs.map +7 -0
- package/dist/lib/browser/chunk-63YLDLLE.mjs +1373 -0
- package/dist/lib/browser/chunk-63YLDLLE.mjs.map +7 -0
- package/dist/lib/browser/chunk-X2HDG62A.mjs +23 -0
- package/dist/lib/browser/chunk-X2HDG62A.mjs.map +7 -0
- package/dist/lib/browser/{chunk-E6MZBCU5.mjs → chunk-YKTYPINZ.mjs} +1 -1
- package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +83 -91
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/{keyboard-YJEK2HTH.mjs → keyboard-46XQHY2O.mjs} +18 -14
- package/dist/lib/browser/keyboard-46XQHY2O.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/operation-resolver-NNNDII45.mjs +53 -0
- package/dist/lib/browser/operation-resolver-NNNDII45.mjs.map +7 -0
- package/dist/lib/browser/{react-surface-RPXGIV45.mjs → react-surface-APSEG23H.mjs} +16 -14
- package/dist/lib/browser/react-surface-APSEG23H.mjs.map +7 -0
- package/dist/lib/browser/state-M5YUCIQU.mjs +121 -0
- package/dist/lib/browser/state-M5YUCIQU.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts +2 -1
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
- package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
- package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +5 -108
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/keyboard/index.d.ts +3 -0
- package/dist/types/src/capabilities/keyboard/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/keyboard/keyboard.d.ts +5 -0
- package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
- package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
- package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
- package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/state/index.d.ts +3 -0
- package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/state/state.d.ts +6 -0
- package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
- package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
- package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.d.ts +2 -2
- package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeDocumentTitle.d.ts +1 -1
- package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +6 -6
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +2 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L0Menu.d.ts +4 -4
- package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L1Panel.d.ts +2 -2
- package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L1Tabs.d.ts +2 -2
- package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +7 -7
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.stories.d.ts +18 -4
- package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/hooks.d.ts +32 -1
- package/dist/types/src/hooks.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +2 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
- package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +5 -6
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/testing/update-state.d.ts +2 -2
- package/dist/types/src/testing/update-state.d.ts.map +1 -1
- package/dist/types/src/types/capabilities.d.ts +16 -0
- package/dist/types/src/types/capabilities.d.ts.map +1 -0
- package/dist/types/src/types/events.d.ts +5 -0
- package/dist/types/src/types/events.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +4 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/{types.d.ts → types/schema.d.ts} +6 -6
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +7 -7
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +54 -52
- package/src/NavTreePlugin.tsx +53 -72
- package/src/capabilities/app-graph-builder/app-graph-builder.ts +42 -0
- package/src/capabilities/app-graph-builder/index.ts +7 -0
- package/src/capabilities/index.ts +5 -9
- package/src/capabilities/keyboard/index.ts +7 -0
- package/src/capabilities/keyboard/keyboard.ts +66 -0
- package/src/capabilities/operation-resolver/index.ts +7 -0
- package/src/capabilities/operation-resolver/operation-resolver.ts +42 -0
- package/src/capabilities/react-surface/index.ts +7 -0
- package/src/capabilities/react-surface/react-surface.tsx +55 -0
- package/src/capabilities/state/index.ts +7 -0
- package/src/capabilities/state/state.ts +122 -0
- package/src/components/CommandsDialog/CommandsDialog.tsx +62 -64
- package/src/components/CommandsDialog/CommandsTrigger.tsx +4 -14
- package/src/components/NavTree/NavTree.stories.tsx +35 -43
- package/src/components/NavTree/NavTree.tsx +1 -11
- package/src/components/NavTreeContainer.tsx +70 -114
- package/src/components/NavTreeDocumentTitle.tsx +3 -2
- package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +17 -7
- package/src/components/NavTreeItem/NavTreeItemAction.tsx +18 -12
- package/src/components/NavTreeItem/NavTreeItemColumns.tsx +5 -5
- package/src/components/Sidebar/L0Menu.tsx +67 -96
- package/src/components/Sidebar/L1Panel.tsx +151 -70
- package/src/components/Sidebar/L1Tabs.tsx +6 -31
- package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +1 -1
- package/src/components/types.ts +7 -7
- package/src/experimental/Tree.stories.tsx +44 -50
- package/src/experimental/Tree.tsx +1 -1
- package/src/hooks.ts +54 -2
- package/src/index.ts +1 -2
- package/src/meta.ts +2 -2
- package/src/testing/app-graph-builder.ts +158 -234
- package/src/testing/generator.ts +12 -12
- package/src/testing/update-state.ts +6 -6
- package/src/types/capabilities.ts +21 -0
- package/src/types/events.ts +12 -0
- package/src/types/index.ts +7 -0
- package/src/{types.ts → types/schema.ts} +5 -5
- package/src/util.ts +13 -16
- package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs +0 -46
- package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs.map +0 -7
- package/dist/lib/browser/chunk-5DWDYPGQ.mjs +0 -1492
- package/dist/lib/browser/chunk-5DWDYPGQ.mjs.map +0 -7
- package/dist/lib/browser/chunk-5VLWX4PK.mjs +0 -15
- package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +0 -7
- package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs +0 -47
- package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +0 -7
- package/dist/lib/browser/keyboard-YJEK2HTH.mjs.map +0 -7
- package/dist/lib/browser/react-surface-RPXGIV45.mjs.map +0 -7
- package/dist/lib/browser/state-BFD5KJCV.mjs +0 -117
- package/dist/lib/browser/state-BFD5KJCV.mjs.map +0 -7
- package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
- package/dist/types/src/capabilities/capabilities.d.ts +0 -21
- package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
- package/dist/types/src/capabilities/keyboard.d.ts +0 -4
- package/dist/types/src/capabilities/keyboard.d.ts.map +0 -1
- package/dist/types/src/capabilities/react-surface.d.ts +0 -4
- package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
- package/dist/types/src/capabilities/state.d.ts +0 -20
- package/dist/types/src/capabilities/state.d.ts.map +0 -1
- package/dist/types/src/events.d.ts +0 -4
- package/dist/types/src/events.d.ts.map +0 -1
- package/dist/types/src/types.d.ts.map +0 -1
- package/src/capabilities/app-graph-builder.ts +0 -53
- package/src/capabilities/capabilities.ts +0 -20
- package/src/capabilities/intent-resolver.ts +0 -37
- package/src/capabilities/keyboard.ts +0 -57
- package/src/capabilities/react-surface.tsx +0 -48
- package/src/capabilities/state.ts +0 -104
- package/src/events.ts +0 -11
|
@@ -23,8 +23,8 @@ import React, {
|
|
|
23
23
|
} from 'react';
|
|
24
24
|
|
|
25
25
|
import { type Node } from '@dxos/app-graph';
|
|
26
|
-
import { invariant } from '@dxos/invariant';
|
|
27
26
|
import { DxAvatar } from '@dxos/lit-ui/react';
|
|
27
|
+
import { useActionRunner } from '@dxos/plugin-graph';
|
|
28
28
|
import {
|
|
29
29
|
Icon,
|
|
30
30
|
ListItem,
|
|
@@ -38,10 +38,10 @@ import {
|
|
|
38
38
|
import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
|
|
39
39
|
import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
|
|
40
40
|
import { Tabs } from '@dxos/react-ui-tabs';
|
|
41
|
-
import { mx } from '@dxos/
|
|
41
|
+
import { mx } from '@dxos/ui-theme';
|
|
42
42
|
import { arrayMove } from '@dxos/util';
|
|
43
43
|
|
|
44
|
-
import {
|
|
44
|
+
import { useNavTreeState } from '../../hooks';
|
|
45
45
|
import { meta } from '../../meta';
|
|
46
46
|
import { l0ItemType } from '../../util';
|
|
47
47
|
import { useNavTreeContext } from '../NavTreeContext';
|
|
@@ -57,43 +57,41 @@ type L0ItemData = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
type L0ItemRootProps = {
|
|
60
|
-
item: Node
|
|
61
|
-
parent?: Node
|
|
60
|
+
item: Node.Node;
|
|
61
|
+
parent?: Node.Node;
|
|
62
62
|
path: string[];
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
type L0ItemProps = L0ItemRootProps & {
|
|
66
|
-
item: Node
|
|
67
|
-
parent?: Node
|
|
66
|
+
item: Node.Node;
|
|
67
|
+
parent?: Node.Node;
|
|
68
68
|
path: string[];
|
|
69
69
|
pinned?: boolean;
|
|
70
70
|
onRearrange?: StackItemRearrangeHandler<L0ItemData>;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
73
|
const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
|
|
74
|
-
const { tab,
|
|
74
|
+
const { tab, onSelect, onTabChange } = useNavTreeContext();
|
|
75
|
+
const { getItem } = useNavTreeState();
|
|
75
76
|
const [isLg] = useMediaQuery('lg');
|
|
77
|
+
const runAction = useActionRunner();
|
|
76
78
|
|
|
77
79
|
return useCallback(
|
|
78
80
|
(event: MouseEvent) => {
|
|
79
81
|
switch (type) {
|
|
80
82
|
case 'action': {
|
|
81
|
-
const {
|
|
82
|
-
|
|
83
|
-
properties: { caller },
|
|
84
|
-
} = item;
|
|
85
|
-
|
|
86
|
-
return invoke?.(caller ? { node: parent, caller } : { node: parent });
|
|
83
|
+
const { properties: { caller } = {} } = item;
|
|
84
|
+
return void runAction(item as Node.Action, caller ? { parent, caller } : { parent });
|
|
87
85
|
}
|
|
88
86
|
|
|
89
87
|
case 'tab':
|
|
90
88
|
return onTabChange?.(item);
|
|
91
89
|
|
|
92
90
|
case 'link':
|
|
93
|
-
return onSelect?.({ item, path, current: !
|
|
91
|
+
return onSelect?.({ item, path, current: !getItem(path).current, option: event.altKey });
|
|
94
92
|
}
|
|
95
93
|
},
|
|
96
|
-
[item, parent, type, tab,
|
|
94
|
+
[item, parent, type, tab, getItem, onSelect, onTabChange, isLg, runAction],
|
|
97
95
|
);
|
|
98
96
|
};
|
|
99
97
|
|
|
@@ -120,9 +118,7 @@ const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
|
|
|
120
118
|
const rootProps =
|
|
121
119
|
type === 'tab'
|
|
122
120
|
? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
|
|
123
|
-
:
|
|
124
|
-
? { onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
|
|
125
|
-
: { onClick: handleClick, role: 'button' };
|
|
121
|
+
: { onClick: handleClick, 'data-testid': testId, 'data-object-id': id };
|
|
126
122
|
|
|
127
123
|
return (
|
|
128
124
|
<Tooltip.Trigger asChild delayDuration={0} side='right' content={localizedString}>
|
|
@@ -256,68 +252,50 @@ const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
|
|
|
256
252
|
};
|
|
257
253
|
|
|
258
254
|
//
|
|
259
|
-
//
|
|
255
|
+
// L0Menu
|
|
260
256
|
//
|
|
261
257
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
258
|
+
export type L0MenuProps = {
|
|
259
|
+
menuActions: MenuItem[];
|
|
260
|
+
topLevelItems: Node.Node[];
|
|
261
|
+
pinnedItems: Node.Node[];
|
|
262
|
+
userAccountItem?: Node.Node;
|
|
263
|
+
parent?: Node.Node;
|
|
264
|
+
path: string[];
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
|
|
268
|
+
const { t } = useTranslation(meta.id);
|
|
269
|
+
const runAction = useActionRunner();
|
|
270
|
+
const handleAction = useCallback(
|
|
271
|
+
(action: Node.Action, params: Node.InvokeProps) => {
|
|
272
|
+
void runAction(action, params);
|
|
273
|
+
},
|
|
274
|
+
[runAction],
|
|
275
|
+
);
|
|
268
276
|
|
|
269
277
|
const handleRearrange = useCallback<StackItemRearrangeHandler<L0ItemData>>(
|
|
270
278
|
(source, target, closestEdge) => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
279
|
+
const sourceItem = topLevelItems.find((i) => i.id === source.id);
|
|
280
|
+
if (!sourceItem?.properties.onRearrange) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const sourceIndex = topLevelItems.findIndex((i) => i.id === source.id);
|
|
285
|
+
const targetIndex = topLevelItems.findIndex((i) => i.id === target.id);
|
|
274
286
|
const insertIndex =
|
|
275
287
|
source.id === target.id
|
|
276
288
|
? sourceIndex
|
|
277
289
|
: targetIndex +
|
|
278
290
|
(sourceIndex < targetIndex ? (closestEdge === 'top' ? -1 : 0) : closestEdge === 'bottom' ? 1 : 0);
|
|
279
|
-
const nextOrder = arrayMove([...
|
|
280
|
-
return
|
|
291
|
+
const nextOrder = arrayMove([...topLevelItems], sourceIndex, insertIndex);
|
|
292
|
+
return sourceItem.properties.onRearrange(nextOrder.map((item) => item.data));
|
|
281
293
|
},
|
|
282
|
-
[
|
|
294
|
+
[topLevelItems],
|
|
283
295
|
);
|
|
284
296
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
role='group'
|
|
288
|
-
className='contents group/l0c'
|
|
289
|
-
aria-labelledby={`${item.id}__label`}
|
|
290
|
-
data-object-id={id}
|
|
291
|
-
data-testid={testId}
|
|
292
|
-
>
|
|
293
|
-
{collectionItems.map((collectionItem) => (
|
|
294
|
-
<L0Item
|
|
295
|
-
key={collectionItem.id}
|
|
296
|
-
item={collectionItem}
|
|
297
|
-
parent={item}
|
|
298
|
-
path={groupPath}
|
|
299
|
-
{...(item.properties.onRearrangeChildren && { onRearrange: handleRearrange })}
|
|
300
|
-
/>
|
|
301
|
-
))}
|
|
302
|
-
</div>
|
|
303
|
-
);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
//
|
|
307
|
-
// L0Menu
|
|
308
|
-
//
|
|
309
|
-
|
|
310
|
-
export type L0MenuProps = {
|
|
311
|
-
menuActions: MenuItem[];
|
|
312
|
-
topLevelItems: Node<any>[];
|
|
313
|
-
pinnedItems: Node<any>[];
|
|
314
|
-
userAccountItem?: Node<any>;
|
|
315
|
-
parent?: Node<any>;
|
|
316
|
-
path: string[];
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
|
|
320
|
-
const { t } = useTranslation(meta.id);
|
|
297
|
+
// Check if any items have onRearrange to enable drag-and-drop.
|
|
298
|
+
const hasRearrangeableItems = topLevelItems.some((item) => item.properties.onRearrange);
|
|
321
299
|
|
|
322
300
|
return (
|
|
323
301
|
<Tabs.Tablist
|
|
@@ -328,19 +306,23 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
|
|
|
328
306
|
]}
|
|
329
307
|
>
|
|
330
308
|
{/* TODO(wittjosiah): Use L0Item trigger. */}
|
|
331
|
-
<MenuProvider>
|
|
309
|
+
<MenuProvider onAction={handleAction}>
|
|
332
310
|
<DropdownMenu.Root group={parent} items={menuActions}>
|
|
333
311
|
<Tooltip.Trigger content={t('app menu label')} side='right' asChild>
|
|
334
312
|
<Tabs.TabPrimitive value='options' asChild role='button'>
|
|
335
313
|
<DropdownMenu.Trigger
|
|
336
314
|
data-testid='spacePlugin.addSpace'
|
|
337
|
-
className={mx(
|
|
315
|
+
className={mx(
|
|
316
|
+
l0ItemRoot,
|
|
317
|
+
'grid place-items-center dx-focus-ring-group',
|
|
318
|
+
'[body[data-platform="macos"]_&]:mt-[30px]',
|
|
319
|
+
)}
|
|
338
320
|
>
|
|
339
321
|
<div
|
|
340
322
|
role='none'
|
|
341
323
|
className={mx(
|
|
342
324
|
l0ItemContent,
|
|
343
|
-
'
|
|
325
|
+
'bs-[--rail-action] is-[--rail-action] group-hover/l0item:bg-hoverSurface',
|
|
344
326
|
)}
|
|
345
327
|
>
|
|
346
328
|
<Icon icon='ph--list--regular' size={5} />
|
|
@@ -352,37 +334,34 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
|
|
|
352
334
|
</MenuProvider>
|
|
353
335
|
|
|
354
336
|
{/* Space list. */}
|
|
355
|
-
<ScrollArea.Root>
|
|
337
|
+
<ScrollArea.Root thin orientation='vertical'>
|
|
356
338
|
<ScrollArea.Viewport>
|
|
357
339
|
<div
|
|
358
340
|
role='none'
|
|
359
341
|
className={mx([
|
|
360
342
|
'flex flex-col gap-1 pbs-1',
|
|
361
|
-
'[body[data-platform="
|
|
343
|
+
'[body[data-platform="macos"]_&]:pbs-[30px]',
|
|
362
344
|
'[body[data-platform="ios"]_&]:pbs-[max(env(safe-area-inset-top),0.25rem)]',
|
|
363
345
|
])}
|
|
364
346
|
>
|
|
365
|
-
{topLevelItems.map((item) =>
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
347
|
+
{topLevelItems.map((item) => (
|
|
348
|
+
<L0Item
|
|
349
|
+
key={item.id}
|
|
350
|
+
item={item}
|
|
351
|
+
parent={parent}
|
|
352
|
+
path={path}
|
|
353
|
+
{...(hasRearrangeableItems && { onRearrange: handleRearrange })}
|
|
354
|
+
/>
|
|
355
|
+
))}
|
|
372
356
|
</div>
|
|
373
|
-
<ScrollArea.Scrollbar orientation='vertical'>
|
|
374
|
-
<ScrollArea.Thumb />
|
|
375
|
-
</ScrollArea.Scrollbar>
|
|
376
357
|
</ScrollArea.Viewport>
|
|
377
358
|
</ScrollArea.Root>
|
|
378
359
|
|
|
379
360
|
{/* Actions. */}
|
|
380
361
|
<div role='none' className='grid grid-cols-1 auto-rows-[--rail-action] pbs-2'>
|
|
381
|
-
{pinnedItems
|
|
382
|
-
.
|
|
383
|
-
|
|
384
|
-
<L0Item key={item.id} item={item} parent={parent} path={path} pinned />
|
|
385
|
-
))}
|
|
362
|
+
{pinnedItems.map((item) => (
|
|
363
|
+
<L0Item key={item.id} item={item} parent={parent} path={path} pinned />
|
|
364
|
+
))}
|
|
386
365
|
</div>
|
|
387
366
|
|
|
388
367
|
{userAccountItem && (
|
|
@@ -398,14 +377,6 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
|
|
|
398
377
|
</L0ItemRoot>
|
|
399
378
|
</div>
|
|
400
379
|
)}
|
|
401
|
-
<div
|
|
402
|
-
role='none'
|
|
403
|
-
className='hidden [body[data-platform="darwin"]_&]:block absolute block-start-0 is-[calc(var(--l0-size)-1px)] bs-[calc(40px+0.25rem)]'
|
|
404
|
-
style={{
|
|
405
|
-
background:
|
|
406
|
-
'linear-gradient(to bottom, var(--dx-sidebarSurface) 0%, var(--dx-sidebarSurface) 70%, transparent 100%)',
|
|
407
|
-
}}
|
|
408
|
-
/>
|
|
409
380
|
</Tabs.Tablist>
|
|
410
381
|
);
|
|
411
382
|
};
|
|
@@ -2,22 +2,27 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
5
6
|
import React, { useCallback, useMemo } from 'react';
|
|
6
7
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
8
|
+
import { Node } from '@dxos/app-graph';
|
|
9
|
+
import { useActionRunner } from '@dxos/plugin-graph';
|
|
10
|
+
import { DensityProvider, IconButton, ScrollArea, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
9
11
|
import { Tree } from '@dxos/react-ui-list';
|
|
12
|
+
import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
|
|
10
13
|
import { Tabs } from '@dxos/react-ui-tabs';
|
|
11
|
-
import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/
|
|
14
|
+
import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
|
|
12
15
|
|
|
16
|
+
import { useIsAlternateTree, useLoadDescendents } from '../../hooks';
|
|
13
17
|
import { meta } from '../../meta';
|
|
18
|
+
import { NAV_TREE_ITEM } from '../NavTree';
|
|
14
19
|
import { useNavTreeContext } from '../NavTreeContext';
|
|
15
|
-
import { NavTreeItemColumns } from '../NavTreeItem';
|
|
20
|
+
import { NavTreeItemAction, NavTreeItemColumns } from '../NavTreeItem';
|
|
16
21
|
|
|
17
22
|
export type L1PanelProps = {
|
|
18
23
|
open?: boolean;
|
|
19
24
|
path: string[];
|
|
20
|
-
item: Node
|
|
25
|
+
item: Node.Node;
|
|
21
26
|
currentItemId: string;
|
|
22
27
|
onBack?: () => void;
|
|
23
28
|
};
|
|
@@ -27,16 +32,16 @@ export type L1PanelProps = {
|
|
|
27
32
|
*/
|
|
28
33
|
export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProps) => {
|
|
29
34
|
const { t } = useTranslation(meta.id);
|
|
30
|
-
const
|
|
35
|
+
const navTreeContext = useNavTreeContext();
|
|
31
36
|
const title = toLocalizedString(item.properties.label, t);
|
|
32
37
|
|
|
33
38
|
// TODO(wittjosiah): Support multiple alternate trees.
|
|
34
39
|
const { useItems } = navTreeContext;
|
|
35
40
|
const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
|
|
36
41
|
const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
37
|
-
const isAlternate =
|
|
42
|
+
const isAlternate = useIsAlternateTree(alternatePath, item);
|
|
38
43
|
const useAlternateItems = useCallback(
|
|
39
|
-
(node?: Node, { disposition }: { disposition?: string } = {}) => {
|
|
44
|
+
(node?: Node.Node, { disposition }: { disposition?: string } = {}) => {
|
|
40
45
|
// TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
|
|
41
46
|
return useItems(node, { disposition, sort: node?.id === alternateTree.id });
|
|
42
47
|
},
|
|
@@ -60,31 +65,33 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
|
|
|
60
65
|
{item.id === currentItemId && (
|
|
61
66
|
<DensityProvider density='fine'>
|
|
62
67
|
<L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
68
|
+
<ScrollArea.Root thin orientation='vertical'>
|
|
69
|
+
<ScrollArea.Viewport>
|
|
70
|
+
{isAlternate ? (
|
|
71
|
+
<Tree
|
|
72
|
+
{...navTreeContext}
|
|
73
|
+
id={alternateTree.id}
|
|
74
|
+
root={alternateTree}
|
|
75
|
+
path={alternatePath}
|
|
76
|
+
levelOffset={5}
|
|
77
|
+
gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
|
|
78
|
+
renderColumns={NavTreeItemColumns}
|
|
79
|
+
useItems={useAlternateItems}
|
|
80
|
+
/>
|
|
81
|
+
) : (
|
|
82
|
+
<Tree
|
|
83
|
+
{...navTreeContext}
|
|
84
|
+
id={item.id}
|
|
85
|
+
root={item}
|
|
86
|
+
path={path}
|
|
87
|
+
levelOffset={5}
|
|
88
|
+
gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
|
|
89
|
+
renderColumns={NavTreeItemColumns}
|
|
90
|
+
draggable
|
|
91
|
+
/>
|
|
92
|
+
)}
|
|
93
|
+
</ScrollArea.Viewport>
|
|
94
|
+
</ScrollArea.Root>
|
|
88
95
|
</DensityProvider>
|
|
89
96
|
)}
|
|
90
97
|
</Tabs.Tabpanel>
|
|
@@ -96,54 +103,128 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
|
|
|
96
103
|
*/
|
|
97
104
|
const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
|
|
98
105
|
const { t } = useTranslation(meta.id);
|
|
99
|
-
const {
|
|
106
|
+
const { renderItemEnd: ItemEnd } = useNavTreeContext();
|
|
100
107
|
const title = toLocalizedString(item.properties.label, t);
|
|
101
108
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
105
|
-
const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
|
|
106
|
-
const backCapable = item.id.startsWith('!') || isAlternate;
|
|
107
|
-
|
|
108
|
-
const handleOpen = useCallback(() => {
|
|
109
|
-
setAlternateTree?.(alternatePath, true);
|
|
110
|
-
}, [alternatePath, setAlternateTree]);
|
|
111
|
-
|
|
112
|
-
const handleBack = useCallback(() => {
|
|
113
|
-
if (isAlternate) {
|
|
114
|
-
setAlternateTree?.(alternatePath, false);
|
|
115
|
-
} else {
|
|
116
|
-
onBack?.();
|
|
117
|
-
}
|
|
118
|
-
}, [isAlternate, alternatePath, setAlternateTree, onBack]);
|
|
109
|
+
const { primaryAction, groupedActions, menuActions, onAction } = useL1MenuActions({ item, path });
|
|
110
|
+
useLoadDescendents(item);
|
|
119
111
|
|
|
120
|
-
// TODO(burdon): Reuse same grid as tree?
|
|
121
112
|
return (
|
|
122
113
|
<div className='flex is-full items-center border-be border-subduedSeparator app-drag density-coarse'>
|
|
123
114
|
<div className='is-6' />
|
|
124
115
|
<h2 className='flex-1 truncate min-is-0'>{title}</h2>
|
|
125
|
-
{(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
116
|
+
{/* TODO(wittjosiah): Reconcile with NavTreeItemColumns. */}
|
|
117
|
+
<div role='none' className='contents app-no-drag'>
|
|
118
|
+
{primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled && (
|
|
119
|
+
<NavTreeItemAction
|
|
120
|
+
testId={primaryAction.properties?.testId}
|
|
121
|
+
label={toLocalizedString(primaryAction.properties?.label, t)}
|
|
122
|
+
icon={primaryAction.properties?.icon ?? 'ph--placeholder--regular'}
|
|
123
|
+
parent={item}
|
|
124
|
+
monolithic={Node.isAction(primaryAction)}
|
|
125
|
+
menuActions={Node.isAction(primaryAction) ? [primaryAction] : groupedActions[primaryAction?.id ?? '']}
|
|
126
|
+
menuType={primaryAction.properties?.menuType}
|
|
127
|
+
caller={NAV_TREE_ITEM}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
{menuActions.length === 1 && (
|
|
136
131
|
<IconButton
|
|
137
|
-
|
|
132
|
+
size={5}
|
|
133
|
+
density='coarse'
|
|
134
|
+
classNames={['shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem]}
|
|
138
135
|
variant='ghost'
|
|
139
|
-
icon={
|
|
136
|
+
icon={menuActions[0].properties?.icon ?? 'ph--placeholder--regular'}
|
|
140
137
|
iconOnly
|
|
141
|
-
label={toLocalizedString(
|
|
142
|
-
|
|
143
|
-
onClick={
|
|
138
|
+
label={toLocalizedString(menuActions[0].properties?.label, t)}
|
|
139
|
+
data-testid={menuActions[0].properties?.testId}
|
|
140
|
+
onClick={() => onAction(menuActions[0] as Node.Action)}
|
|
144
141
|
/>
|
|
145
|
-
)
|
|
146
|
-
|
|
142
|
+
)}
|
|
143
|
+
{menuActions.length > 1 && (
|
|
144
|
+
<MenuProvider onAction={onAction}>
|
|
145
|
+
<DropdownMenu.Root group={item} items={menuActions as MenuItem[]} caller={NAV_TREE_ITEM}>
|
|
146
|
+
<DropdownMenu.Trigger asChild>
|
|
147
|
+
<IconButton
|
|
148
|
+
size={5}
|
|
149
|
+
density='coarse'
|
|
150
|
+
classNames={['shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem]}
|
|
151
|
+
variant='ghost'
|
|
152
|
+
icon='ph--dots-three-vertical--regular'
|
|
153
|
+
iconOnly
|
|
154
|
+
label={t('tree item actions label')}
|
|
155
|
+
data-testid='navtree.treeItem.actionsLevel0'
|
|
156
|
+
/>
|
|
157
|
+
</DropdownMenu.Trigger>
|
|
158
|
+
</DropdownMenu.Root>
|
|
159
|
+
</MenuProvider>
|
|
160
|
+
)}
|
|
161
|
+
{ItemEnd && <ItemEnd node={item} open />}
|
|
162
|
+
</div>
|
|
147
163
|
</div>
|
|
148
164
|
);
|
|
149
165
|
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Builds the menu actions for the L1 panel header, combining graph actions with a synthetic settings/back action.
|
|
169
|
+
*/
|
|
170
|
+
const useL1MenuActions = ({ item, path }: Pick<L1PanelProps, 'item' | 'path'>) => {
|
|
171
|
+
const { t } = useTranslation(meta.id);
|
|
172
|
+
const { setAlternateTree, useActions, ...navTreeContext } = useNavTreeContext();
|
|
173
|
+
const runAction = useActionRunner();
|
|
174
|
+
|
|
175
|
+
// TODO(wittjosiah): Support multiple alternate trees.
|
|
176
|
+
const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
|
|
177
|
+
const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
178
|
+
const isAlternate = useIsAlternateTree(alternatePath, item);
|
|
179
|
+
|
|
180
|
+
// Graph actions.
|
|
181
|
+
const { actions: _actions, groupedActions } = useActions(item);
|
|
182
|
+
const [primaryAction, ...secondaryActions] = _actions.toSorted((a, _b) =>
|
|
183
|
+
a.properties?.disposition === 'list-item-primary' ? -1 : 1,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const graphMenuActions = (
|
|
187
|
+
primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions
|
|
188
|
+
)
|
|
189
|
+
.flatMap((action) => (Node.isAction(action) ? [action] : []))
|
|
190
|
+
.filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
|
|
191
|
+
|
|
192
|
+
// Synthetic settings/back action.
|
|
193
|
+
const settingsActionId = `${item.id}~settings`;
|
|
194
|
+
const settingsAction = useMemo((): Node.Action | undefined => {
|
|
195
|
+
if (!alternateTree) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
id: settingsActionId,
|
|
200
|
+
type: Node.ActionType,
|
|
201
|
+
data: () => Effect.void,
|
|
202
|
+
properties: {
|
|
203
|
+
label: isAlternate ? ['button back', { ns: meta.id }] : (alternateTree.properties.label ?? alternateTree.id),
|
|
204
|
+
icon: isAlternate
|
|
205
|
+
? 'ph--arrow-u-down-left--regular'
|
|
206
|
+
: (alternateTree.properties.icon ?? 'ph--placeholder--regular'),
|
|
207
|
+
disposition: 'list-item',
|
|
208
|
+
testId: isAlternate ? 'navtree.backToSpace' : 'navtree.spaceSettings',
|
|
209
|
+
},
|
|
210
|
+
} as Node.Action;
|
|
211
|
+
}, [alternateTree, isAlternate, settingsActionId, t]);
|
|
212
|
+
|
|
213
|
+
const menuActions = useMemo(
|
|
214
|
+
() => [...graphMenuActions, ...(settingsAction ? [settingsAction] : [])],
|
|
215
|
+
[graphMenuActions, settingsAction],
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const onAction = useCallback(
|
|
219
|
+
(action: Node.Action, params?: Node.InvokeProps) => {
|
|
220
|
+
if (action.id === settingsActionId) {
|
|
221
|
+
setAlternateTree?.(alternatePath, !isAlternate);
|
|
222
|
+
} else {
|
|
223
|
+
void runAction(action, params);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
[settingsActionId, setAlternateTree, alternatePath, isAlternate, runAction],
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
return { primaryAction, groupedActions, menuActions, onAction };
|
|
230
|
+
};
|
|
@@ -2,57 +2,32 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import React
|
|
5
|
+
import React from 'react';
|
|
6
6
|
|
|
7
7
|
import { type Node } from '@dxos/app-graph';
|
|
8
8
|
|
|
9
|
-
import { useLoadDescendents } from '../../hooks';
|
|
10
9
|
import { l0ItemType } from '../../util';
|
|
11
|
-
import { useNavTreeContext } from '../NavTreeContext';
|
|
12
10
|
|
|
13
11
|
import { L1Panel, type L1PanelProps } from './L1Panel';
|
|
14
12
|
|
|
15
13
|
export type L1TabsProps = Pick<L1PanelProps, 'open' | 'currentItemId' | 'onBack'> & {
|
|
16
14
|
path: string[];
|
|
17
|
-
topLevelItems: Node
|
|
15
|
+
topLevelItems: Node.Node[];
|
|
18
16
|
};
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
|
-
* Each
|
|
19
|
+
* Each workspace is an L1 tab.
|
|
22
20
|
*/
|
|
23
21
|
export const L1Tabs = ({ topLevelItems, onBack, ...props }: L1TabsProps) => {
|
|
24
22
|
return (
|
|
25
23
|
<>
|
|
26
24
|
{topLevelItems.map((item) => {
|
|
27
25
|
const type = l0ItemType(item);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
|
|
31
|
-
case 'collection':
|
|
32
|
-
return <L1PanelCollection key={item.id} item={item} {...props} />;
|
|
33
|
-
default:
|
|
34
|
-
return null;
|
|
26
|
+
if (type === 'tab') {
|
|
27
|
+
return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
|
|
35
28
|
}
|
|
29
|
+
return null;
|
|
36
30
|
})}
|
|
37
31
|
</>
|
|
38
32
|
);
|
|
39
33
|
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
*
|
|
43
|
-
*/
|
|
44
|
-
const L1PanelCollection = ({ item, path, ...props }: L1PanelProps) => {
|
|
45
|
-
const { useItems } = useNavTreeContext();
|
|
46
|
-
useLoadDescendents(item);
|
|
47
|
-
const collectionItems = useItems(item);
|
|
48
|
-
const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
49
|
-
return (
|
|
50
|
-
<>
|
|
51
|
-
{collectionItems
|
|
52
|
-
.filter((item) => l0ItemType(item) === 'tab')
|
|
53
|
-
.map((item) => (
|
|
54
|
-
<L1Panel key={item.id} item={item} path={groupPath} {...props} />
|
|
55
|
-
))}
|
|
56
|
-
</>
|
|
57
|
-
);
|
|
58
|
-
};
|
|
@@ -11,7 +11,7 @@ import { UserAccountAvatar } from './UserAccountAvatar';
|
|
|
11
11
|
const meta = {
|
|
12
12
|
title: 'plugins/plugin-navtree/UserAccountAvatar',
|
|
13
13
|
|
|
14
|
-
decorators: [withTheme],
|
|
14
|
+
decorators: [withTheme()],
|
|
15
15
|
component: UserAccountAvatar,
|
|
16
16
|
} satisfies Meta<typeof UserAccountAvatar>;
|
|
17
17
|
|
package/src/components/types.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import type { FC } from 'react';
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { type Node } from '@dxos/app-graph';
|
|
8
8
|
import type { Density } from '@dxos/react-ui';
|
|
9
9
|
import type { TreeProps } from '@dxos/react-ui-list';
|
|
10
10
|
import type { MaybePromise } from '@dxos/util';
|
|
@@ -15,23 +15,23 @@ import type { L1PanelProps } from './Sidebar';
|
|
|
15
15
|
|
|
16
16
|
export type NavTreeContextValue = Pick<
|
|
17
17
|
TreeProps<NavTreeItemGraphNode, { disposition?: string; sort?: boolean }>,
|
|
18
|
-
'useItems' | 'getProps' | '
|
|
18
|
+
'useItems' | 'getProps' | 'useIsCurrent' | 'useIsOpen' | 'blockInstruction' | 'canDrop' | 'onOpenChange' | 'onSelect'
|
|
19
19
|
> &
|
|
20
20
|
Pick<L1PanelProps, 'onBack'> & {
|
|
21
21
|
tab: string;
|
|
22
22
|
topbar?: boolean;
|
|
23
23
|
popoverAnchorId?: string;
|
|
24
|
-
renderItemEnd?: FC<{ node: Node; open: boolean }>;
|
|
25
|
-
useActions: (node: Node) => FlattenedActions;
|
|
26
|
-
loadDescendents?: (node: Node) => MaybePromise<void>;
|
|
27
|
-
|
|
24
|
+
renderItemEnd?: FC<{ node: Node.Node; open: boolean }>;
|
|
25
|
+
useActions: (node: Node.Node) => FlattenedActions;
|
|
26
|
+
loadDescendents?: (node: Node.Node) => MaybePromise<void>;
|
|
27
|
+
useIsAlternateTree?: (path: string[], item: NavTreeItemGraphNode) => boolean;
|
|
28
28
|
setAlternateTree?: (path: string[], open: boolean) => void;
|
|
29
29
|
onTabChange?: (node: NavTreeItemGraphNode) => void;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export type NavTreeItemColumnsProps = {
|
|
33
33
|
path: string[];
|
|
34
|
-
item: Node;
|
|
34
|
+
item: Node.Node;
|
|
35
35
|
open: boolean;
|
|
36
36
|
density?: Density;
|
|
37
37
|
};
|