@dxos/plugin-navtree 0.8.4-main.1f223c7 → 0.8.4-main.2244d791bb
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-YKTYPINZ.mjs +19 -0
- package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +83 -93
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/{keyboard-HMTMA33N.mjs → keyboard-46XQHY2O.mjs} +21 -19
- 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-APSEG23H.mjs +59 -0
- 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 -105
- 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 +3 -2
- 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 +30 -1
- package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts +5 -3
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts +8 -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 +3 -3
- package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +1 -1
- package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
- package/dist/types/src/components/Sidebar/L0Menu.d.ts +6 -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/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
- package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +1 -2
- package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.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.d.ts +1 -2
- package/dist/types/src/experimental/Tree.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.stories.d.ts +18 -5
- 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 +3 -4
- 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} +10 -9
- 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 +55 -53
- package/src/NavTreePlugin.tsx +63 -83
- 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 +108 -98
- package/src/components/CommandsDialog/CommandsTrigger.tsx +6 -15
- package/src/components/NavTree/NavTree.stories.tsx +95 -89
- package/src/components/NavTree/NavTree.tsx +9 -15
- package/src/components/NavTreeContainer.tsx +256 -257
- package/src/components/NavTreeDocumentTitle.tsx +5 -4
- package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +18 -10
- package/src/components/NavTreeItem/NavTreeItemAction.tsx +23 -16
- package/src/components/NavTreeItem/NavTreeItemColumns.tsx +32 -26
- package/src/components/Sidebar/L0Menu.tsx +113 -141
- package/src/components/Sidebar/L1Panel.tsx +155 -77
- package/src/components/Sidebar/L1Tabs.tsx +6 -31
- package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +3 -4
- package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
- package/src/components/index.ts +1 -0
- package/src/components/types.ts +7 -7
- package/src/experimental/Tree.stories.tsx +67 -77
- package/src/experimental/Tree.tsx +7 -24
- package/src/hooks.ts +54 -2
- package/src/index.ts +1 -2
- package/src/meta.ts +11 -8
- package/src/testing/app-graph-builder.ts +159 -234
- package/src/testing/generator.ts +20 -18
- 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} +9 -8
- package/src/util.ts +13 -16
- package/dist/lib/browser/app-graph-builder-6JDBQ25U.mjs +0 -45
- package/dist/lib/browser/app-graph-builder-6JDBQ25U.mjs.map +0 -7
- package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
- package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
- package/dist/lib/browser/chunk-WCARAZ7J.mjs +0 -15
- package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
- package/dist/lib/browser/chunk-ZKVY35IG.mjs +0 -1469
- package/dist/lib/browser/chunk-ZKVY35IG.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-N35ZIHFD.mjs +0 -47
- package/dist/lib/browser/intent-resolver-N35ZIHFD.mjs.map +0 -7
- package/dist/lib/browser/keyboard-HMTMA33N.mjs.map +0 -7
- package/dist/lib/browser/react-surface-5PZ4XJT7.mjs +0 -51
- package/dist/lib/browser/react-surface-5PZ4XJT7.mjs.map +0 -7
- package/dist/lib/browser/state-P5DGL7KS.mjs +0 -117
- package/dist/lib/browser/state-P5DGL7KS.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 -52
- package/src/capabilities/capabilities.ts +0 -20
- package/src/capabilities/intent-resolver.ts +0 -37
- package/src/capabilities/keyboard.ts +0 -59
- package/src/capabilities/react-surface.tsx +0 -45
- package/src/capabilities/state.ts +0 -105
- package/src/events.ts +0 -11
|
@@ -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,17 +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
|
|
31
|
-
// NOTE(burdon): I'm very uneasy about this pattern of declaring hooks as context props.
|
|
32
|
-
const { useItems } = navTreeContext;
|
|
35
|
+
const navTreeContext = useNavTreeContext();
|
|
33
36
|
const title = toLocalizedString(item.properties.label, t);
|
|
34
37
|
|
|
35
38
|
// TODO(wittjosiah): Support multiple alternate trees.
|
|
39
|
+
const { useItems } = navTreeContext;
|
|
36
40
|
const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
|
|
37
41
|
const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
38
|
-
const isAlternate =
|
|
42
|
+
const isAlternate = useIsAlternateTree(alternatePath, item);
|
|
39
43
|
const useAlternateItems = useCallback(
|
|
40
|
-
(node?: Node, { disposition }: { disposition?: string } = {}) => {
|
|
44
|
+
(node?: Node.Node, { disposition }: { disposition?: string } = {}) => {
|
|
41
45
|
// TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
|
|
42
46
|
return useItems(node, { disposition, sort: node?.id === alternateTree.id });
|
|
43
47
|
},
|
|
@@ -56,37 +60,39 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
|
|
|
56
60
|
]}
|
|
57
61
|
tabIndex={-1}
|
|
58
62
|
aria-label={title}
|
|
59
|
-
{...(!open && { inert:
|
|
63
|
+
{...(!open && { inert: true })}
|
|
60
64
|
>
|
|
61
65
|
{item.id === currentItemId && (
|
|
62
|
-
|
|
66
|
+
<DensityProvider density='fine'>
|
|
63
67
|
<L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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>
|
|
95
|
+
</DensityProvider>
|
|
90
96
|
)}
|
|
91
97
|
</Tabs.Tabpanel>
|
|
92
98
|
);
|
|
@@ -97,56 +103,128 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
|
|
|
97
103
|
*/
|
|
98
104
|
const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
|
|
99
105
|
const { t } = useTranslation(meta.id);
|
|
100
|
-
const {
|
|
106
|
+
const { renderItemEnd: ItemEnd } = useNavTreeContext();
|
|
101
107
|
const title = toLocalizedString(item.properties.label, t);
|
|
102
108
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
106
|
-
const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
|
|
107
|
-
const backCapable = item.id.startsWith('!') || isAlternate;
|
|
108
|
-
|
|
109
|
-
const handleOpen = useCallback(() => {
|
|
110
|
-
setAlternateTree?.(alternatePath, true);
|
|
111
|
-
}, [alternatePath, setAlternateTree]);
|
|
112
|
-
|
|
113
|
-
const handleBack = useCallback(() => {
|
|
114
|
-
if (isAlternate) {
|
|
115
|
-
setAlternateTree?.(alternatePath, false);
|
|
116
|
-
} else {
|
|
117
|
-
onBack?.();
|
|
118
|
-
}
|
|
119
|
-
}, [isAlternate, alternatePath, setAlternateTree, onBack]);
|
|
109
|
+
const { primaryAction, groupedActions, menuActions, onAction } = useL1MenuActions({ item, path });
|
|
110
|
+
useLoadDescendents(item);
|
|
120
111
|
|
|
121
|
-
// TODO(burdon): Reuse same grid as tree?
|
|
122
112
|
return (
|
|
123
113
|
<div className='flex is-full items-center border-be border-subduedSeparator app-drag density-coarse'>
|
|
124
114
|
<div className='is-6' />
|
|
125
115
|
<h2 className='flex-1 truncate min-is-0'>{title}</h2>
|
|
126
|
-
{(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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 && (
|
|
138
131
|
<IconButton
|
|
139
|
-
|
|
132
|
+
size={5}
|
|
133
|
+
density='coarse'
|
|
134
|
+
classNames={['shrink-0 pli-2 pointer-fine:pli-1', hoverableControlItem, hoverableOpenControlItem]}
|
|
140
135
|
variant='ghost'
|
|
141
|
-
icon={
|
|
136
|
+
icon={menuActions[0].properties?.icon ?? 'ph--placeholder--regular'}
|
|
142
137
|
iconOnly
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
onClick={handleOpen}
|
|
138
|
+
label={toLocalizedString(menuActions[0].properties?.label, t)}
|
|
139
|
+
data-testid={menuActions[0].properties?.testId}
|
|
140
|
+
onClick={() => onAction(menuActions[0] as Node.Action)}
|
|
147
141
|
/>
|
|
148
|
-
)
|
|
149
|
-
|
|
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>
|
|
150
163
|
</div>
|
|
151
164
|
);
|
|
152
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
|
-
};
|
|
@@ -2,18 +2,17 @@
|
|
|
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';
|
|
8
6
|
|
|
9
|
-
import { withTheme } from '@dxos/
|
|
7
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
10
8
|
|
|
11
9
|
import { UserAccountAvatar } from './UserAccountAvatar';
|
|
12
10
|
|
|
13
11
|
const meta = {
|
|
14
12
|
title: 'plugins/plugin-navtree/UserAccountAvatar',
|
|
13
|
+
|
|
14
|
+
decorators: [withTheme()],
|
|
15
15
|
component: UserAccountAvatar,
|
|
16
|
-
decorators: [withTheme],
|
|
17
16
|
} satisfies Meta<typeof UserAccountAvatar>;
|
|
18
17
|
|
|
19
18
|
export default meta;
|
|
@@ -7,6 +7,8 @@ import React from 'react';
|
|
|
7
7
|
import { Avatar, type AvatarStatus, type Size } from '@dxos/react-ui';
|
|
8
8
|
import { hexToFallback } from '@dxos/util';
|
|
9
9
|
|
|
10
|
+
import { L0ItemActiveTabIndicator } from '../Sidebar';
|
|
11
|
+
|
|
10
12
|
export type UserAccountAvatarProps = {
|
|
11
13
|
size?: Size;
|
|
12
14
|
userId?: string;
|
|
@@ -19,17 +21,23 @@ export const UserAccountAvatar = ({ size, userId, hue, emoji, status }: UserAcco
|
|
|
19
21
|
const fallbackValue = hexToFallback(userId ?? '0');
|
|
20
22
|
|
|
21
23
|
return (
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
<>
|
|
25
|
+
<L0ItemActiveTabIndicator classNames='inset-block-6' />
|
|
26
|
+
<div
|
|
27
|
+
className='grid place-items-center dx-focus-ring-group-indicator rounded-full'
|
|
28
|
+
data-joyride='welcome/account'
|
|
29
|
+
>
|
|
30
|
+
<Avatar.Root>
|
|
31
|
+
<Avatar.Content
|
|
32
|
+
variant='circle'
|
|
33
|
+
size={size ?? 12}
|
|
34
|
+
status={status ?? 'active'}
|
|
35
|
+
hue={hue || fallbackValue.hue}
|
|
36
|
+
fallback={emoji || fallbackValue.emoji}
|
|
37
|
+
data-testid='treeView.userAccount'
|
|
38
|
+
/>
|
|
39
|
+
</Avatar.Root>
|
|
40
|
+
</div>
|
|
41
|
+
</>
|
|
34
42
|
);
|
|
35
43
|
};
|
package/src/components/index.ts
CHANGED
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
|
};
|
|
@@ -2,42 +2,18 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
|
|
5
|
+
import { type Meta } from '@storybook/react-vite';
|
|
6
|
+
import React, { useEffect, useState } from 'react';
|
|
9
7
|
|
|
10
8
|
import { faker } from '@dxos/random';
|
|
11
|
-
import { Icon, IconButton } from '@dxos/react-ui';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { Icon, IconButton, Layout, ScrollArea } from '@dxos/react-ui';
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
import { mx } from '@dxos/ui-theme';
|
|
14
12
|
|
|
15
13
|
import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visitor } from './Tree';
|
|
16
14
|
|
|
17
15
|
faker.seed(1234);
|
|
18
16
|
|
|
19
|
-
/**
|
|
20
|
-
* Space:
|
|
21
|
-
* - Space panels are separated from each other to give them more weight than folders.
|
|
22
|
-
* - Private space isn't different (just first, different icon).
|
|
23
|
-
* - Edit titles in place.
|
|
24
|
-
*
|
|
25
|
-
* Navigation:
|
|
26
|
-
* - up/down across spaces/folder peers
|
|
27
|
-
* - right to enter space/folder children
|
|
28
|
-
* - left to parent
|
|
29
|
-
*/
|
|
30
|
-
const Container = ({ children, sidebar }: PropsWithChildren<{ sidebar: JSX.Element }>) => {
|
|
31
|
-
return (
|
|
32
|
-
<div className='flex'>
|
|
33
|
-
{/* TODO(burdon): Custom thin scrollbar. */}
|
|
34
|
-
{/* TODO(burdon): Horizontal scrolling within navtree? */}
|
|
35
|
-
<div className='flex flex-col overflow-y-auto w-[300px] bg-neutral-100 dark:bg-neutral-950'>{sidebar}</div>
|
|
36
|
-
<div className='flex flex-col grow overflow-hidden'>{children}</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
17
|
// TODO(burdon): Generate.
|
|
42
18
|
const data: TreeNodeData[] = [
|
|
43
19
|
{
|
|
@@ -105,27 +81,6 @@ const data: TreeNodeData[] = [
|
|
|
105
81
|
},
|
|
106
82
|
];
|
|
107
83
|
|
|
108
|
-
export const Default = () => {
|
|
109
|
-
return <Container sidebar={<Sidebar mutate />} />;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
export const Visitor = () => {
|
|
113
|
-
const [items] = useState(data);
|
|
114
|
-
const root = items[0];
|
|
115
|
-
const [openItems] = useState<ItemMap>({ [root.children![0].id]: true });
|
|
116
|
-
return (
|
|
117
|
-
<div className='flex flex-col'>
|
|
118
|
-
{Array.from(visitor(root, openItems)).map(({ node: { id, title }, depth }) => (
|
|
119
|
-
<div key={id} className='grid grid-cols-[40px_400px_400px] font-mono'>
|
|
120
|
-
<div>{depth}</div>
|
|
121
|
-
<div>{id}</div>
|
|
122
|
-
<div>{title}</div>
|
|
123
|
-
</div>
|
|
124
|
-
))}
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
84
|
const Sidebar = ({ mutate }: { mutate?: boolean }) => {
|
|
130
85
|
const [items, setItems] = useState(data);
|
|
131
86
|
const root = items[0];
|
|
@@ -157,7 +112,7 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
|
|
|
157
112
|
}
|
|
158
113
|
}, []);
|
|
159
114
|
|
|
160
|
-
const handleCreateItem: TreeProps['onMenuAction'] = (id
|
|
115
|
+
const handleCreateItem: TreeProps['onMenuAction'] = (id) => {
|
|
161
116
|
setItems((items) => {
|
|
162
117
|
let parent: TreeNodeData | undefined;
|
|
163
118
|
visitNodes(root, (node) => {
|
|
@@ -190,45 +145,80 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
|
|
|
190
145
|
};
|
|
191
146
|
|
|
192
147
|
return (
|
|
193
|
-
<
|
|
194
|
-
<
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
148
|
+
<Layout.Main>
|
|
149
|
+
<ScrollArea.Root orientation='vertical' thin>
|
|
150
|
+
<ScrollArea.Viewport>
|
|
151
|
+
<Tree
|
|
152
|
+
className='p-0.5 gap-1'
|
|
153
|
+
node={root}
|
|
154
|
+
open={openItems}
|
|
155
|
+
selected={selectedItems}
|
|
156
|
+
active={activeItems}
|
|
157
|
+
onChangeOpen={(id, open) => setOpenItems((items) => ({ ...items, [id]: open }))}
|
|
158
|
+
onChangeSelected={(id, open) => setSelectedItems((items) => ({ ...items, [id]: open }))}
|
|
159
|
+
onMenuAction={handleCreateItem}
|
|
160
|
+
getSlots={(node, open, depth) => {
|
|
161
|
+
if (depth === 1) {
|
|
162
|
+
return {
|
|
163
|
+
root: 'rounded bg-white dark:bg-neutral-850',
|
|
164
|
+
header: mx('rounded-t bg-neutral-50 dark:bg-neutral-900', !open && 'rounded-b'),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
</ScrollArea.Viewport>
|
|
170
|
+
</ScrollArea.Root>
|
|
171
|
+
|
|
172
|
+
<div className='flex items-center my-2 pli-2 gap-2'>
|
|
216
173
|
<IconButton icon='ph--plus-circle--regular' iconOnly label='Create space' onClick={handleCreateSpace} />
|
|
217
174
|
<span className='grow text-sm' onClick={handleCreateSpace}>
|
|
218
175
|
New space
|
|
219
176
|
</span>
|
|
220
177
|
<Icon icon='ph--list--regular' />
|
|
221
178
|
</div>
|
|
222
|
-
</
|
|
179
|
+
</Layout.Main>
|
|
223
180
|
);
|
|
224
181
|
};
|
|
225
182
|
|
|
226
183
|
const meta = {
|
|
227
184
|
title: 'plugins/plugin-navtree/experimental/Tree',
|
|
228
185
|
component: Tree,
|
|
229
|
-
decorators: [withTheme, withLayout({
|
|
186
|
+
decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'is-[20rem]' })],
|
|
187
|
+
parameters: {
|
|
188
|
+
layout: 'fullscreen',
|
|
189
|
+
},
|
|
230
190
|
} satisfies Meta<typeof Tree>;
|
|
231
191
|
|
|
232
192
|
export default meta;
|
|
233
193
|
|
|
234
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Space:
|
|
196
|
+
* - Space panels are separated from each other to give them more weight than folders.
|
|
197
|
+
* - Private space isn't different (just first, different icon).
|
|
198
|
+
* - Edit titles in place.
|
|
199
|
+
*
|
|
200
|
+
* Navigation:
|
|
201
|
+
* - up/down across spaces/folder peers
|
|
202
|
+
* - right to enter space/folder children
|
|
203
|
+
* - left to parent
|
|
204
|
+
*/
|
|
205
|
+
export const Default = () => {
|
|
206
|
+
return <Sidebar mutate />;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
export const Visitor = () => {
|
|
210
|
+
const [items] = useState(data);
|
|
211
|
+
const root = items[0];
|
|
212
|
+
const [openItems] = useState<ItemMap>({ [root.children![0].id]: true });
|
|
213
|
+
return (
|
|
214
|
+
<div className='flex flex-col'>
|
|
215
|
+
{Array.from(visitor(root, openItems)).map(({ node: { id, title }, depth }) => (
|
|
216
|
+
<div key={id} className='grid grid-cols-[40px_400px_400px] font-mono'>
|
|
217
|
+
<div>{depth}</div>
|
|
218
|
+
<div>{id}</div>
|
|
219
|
+
<div>{title}</div>
|
|
220
|
+
</div>
|
|
221
|
+
))}
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
};
|