@dxos/plugin-navtree 0.8.3-main.672df60 → 0.8.3-staging.0fa589b
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/{chunk-MFAWMFJC.mjs → chunk-ZJPL4XJD.mjs} +83 -72
- package/dist/lib/browser/chunk-ZJPL4XJD.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +10 -4
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/{react-surface-HGA3TAQU.mjs → react-surface-H3JRTVNU.mjs} +2 -2
- package/dist/lib/browser/{state-R2UMYAHN.mjs → state-J6AKZRB4.mjs} +7 -4
- package/dist/lib/browser/state-J6AKZRB4.mjs.map +7 -0
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/state.d.ts +1 -1
- package/dist/types/src/capabilities/state.d.ts.map +1 -1
- package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
- package/dist/types/src/components/NavTree/NavTree.stories.d.ts +1 -0
- 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/Sidebar/L0Menu.d.ts +1 -1
- package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
- package/dist/types/src/components/types.d.ts +5 -6
- package/dist/types/src/components/types.d.ts.map +1 -1
- package/dist/types/src/testing/app-graph-builder.d.ts +2 -1
- package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +1 -1
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/dist/types/src/util.d.ts +1 -1
- package/package.json +36 -34
- package/src/NavTreePlugin.tsx +8 -2
- package/src/capabilities/state.ts +16 -15
- package/src/components/CommandsDialog/CommandsDialog.tsx +16 -10
- package/src/components/NavTree/NavTree.stories.tsx +43 -2
- package/src/components/NavTreeContainer.tsx +6 -17
- package/src/components/NavTreeItem/NavTreeItemColumns.tsx +1 -1
- package/src/components/Sidebar/L0Menu.tsx +58 -49
- package/src/components/types.ts +3 -7
- package/src/testing/app-graph-builder.ts +249 -203
- package/src/testing/generator.ts +6 -3
- package/dist/lib/browser/chunk-MFAWMFJC.mjs.map +0 -7
- package/dist/lib/browser/state-R2UMYAHN.mjs.map +0 -7
- /package/dist/lib/browser/{react-surface-HGA3TAQU.mjs.map → react-surface-H3JRTVNU.mjs.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-navtree",
|
|
3
|
-
"version": "0.8.3-
|
|
3
|
+
"version": "0.8.3-staging.0fa589b",
|
|
4
4
|
"description": "DXOS Surface plugin for a tree view visualization of the graph.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -31,29 +31,29 @@
|
|
|
31
31
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
32
32
|
"date-fns": "^3.3.1",
|
|
33
33
|
"effect": "^3.13.3",
|
|
34
|
-
"@dxos/app-framework": "0.8.3-
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/echo": "0.8.3-
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/keyboard": "0.8.3-
|
|
42
|
-
"@dxos/live-object": "0.8.3-
|
|
43
|
-
"@dxos/local-storage": "0.8.3-
|
|
44
|
-
"@dxos/log": "0.8.3-
|
|
45
|
-
"@dxos/plugin-graph": "0.8.3-
|
|
46
|
-
"@dxos/plugin-deck": "0.8.3-
|
|
47
|
-
"@dxos/plugin-observability": "0.8.3-
|
|
48
|
-
"@dxos/protocols": "0.8.3-
|
|
49
|
-
"@dxos/react-client": "0.8.3-
|
|
50
|
-
"@dxos/react-ui-
|
|
51
|
-
"@dxos/react-ui-
|
|
52
|
-
"@dxos/react-ui-menu": "0.8.3-
|
|
53
|
-
"@dxos/react-ui-searchlist": "0.8.3-
|
|
54
|
-
"@dxos/react-ui-stack": "0.8.3-
|
|
55
|
-
"@dxos/react-ui-tabs": "0.8.3-
|
|
56
|
-
"@dxos/util": "0.8.3-
|
|
34
|
+
"@dxos/app-framework": "0.8.3-staging.0fa589b",
|
|
35
|
+
"@dxos/app-graph": "0.8.3-staging.0fa589b",
|
|
36
|
+
"@dxos/async": "0.8.3-staging.0fa589b",
|
|
37
|
+
"@dxos/debug": "0.8.3-staging.0fa589b",
|
|
38
|
+
"@dxos/echo-schema": "0.8.3-staging.0fa589b",
|
|
39
|
+
"@dxos/invariant": "0.8.3-staging.0fa589b",
|
|
40
|
+
"@dxos/echo": "0.8.3-staging.0fa589b",
|
|
41
|
+
"@dxos/keyboard": "0.8.3-staging.0fa589b",
|
|
42
|
+
"@dxos/live-object": "0.8.3-staging.0fa589b",
|
|
43
|
+
"@dxos/local-storage": "0.8.3-staging.0fa589b",
|
|
44
|
+
"@dxos/log": "0.8.3-staging.0fa589b",
|
|
45
|
+
"@dxos/plugin-graph": "0.8.3-staging.0fa589b",
|
|
46
|
+
"@dxos/plugin-deck": "0.8.3-staging.0fa589b",
|
|
47
|
+
"@dxos/plugin-observability": "0.8.3-staging.0fa589b",
|
|
48
|
+
"@dxos/protocols": "0.8.3-staging.0fa589b",
|
|
49
|
+
"@dxos/react-client": "0.8.3-staging.0fa589b",
|
|
50
|
+
"@dxos/react-ui-attention": "0.8.3-staging.0fa589b",
|
|
51
|
+
"@dxos/react-ui-list": "0.8.3-staging.0fa589b",
|
|
52
|
+
"@dxos/react-ui-menu": "0.8.3-staging.0fa589b",
|
|
53
|
+
"@dxos/react-ui-searchlist": "0.8.3-staging.0fa589b",
|
|
54
|
+
"@dxos/react-ui-stack": "0.8.3-staging.0fa589b",
|
|
55
|
+
"@dxos/react-ui-tabs": "0.8.3-staging.0fa589b",
|
|
56
|
+
"@dxos/util": "0.8.3-staging.0fa589b"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@effect-rx/rx-react": "^0.34.1",
|
|
@@ -64,14 +64,16 @@
|
|
|
64
64
|
"react": "~18.2.0",
|
|
65
65
|
"react-dom": "~18.2.0",
|
|
66
66
|
"vite": "5.4.7",
|
|
67
|
-
"@dxos/echo-signals": "0.8.3-
|
|
68
|
-
"@dxos/plugin-
|
|
69
|
-
"@dxos/plugin-
|
|
70
|
-
"@dxos/plugin-
|
|
71
|
-
"@dxos/storybook-
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/
|
|
67
|
+
"@dxos/echo-signals": "0.8.3-staging.0fa589b",
|
|
68
|
+
"@dxos/plugin-attention": "0.8.3-staging.0fa589b",
|
|
69
|
+
"@dxos/plugin-client": "0.8.3-staging.0fa589b",
|
|
70
|
+
"@dxos/plugin-space": "0.8.3-staging.0fa589b",
|
|
71
|
+
"@dxos/plugin-storybook-layout": "0.8.3-staging.0fa589b",
|
|
72
|
+
"@dxos/plugin-theme": "0.8.3-staging.0fa589b",
|
|
73
|
+
"@dxos/random": "0.8.3-staging.0fa589b",
|
|
74
|
+
"@dxos/react-ui": "0.8.3-staging.0fa589b",
|
|
75
|
+
"@dxos/react-ui-theme": "0.8.3-staging.0fa589b",
|
|
76
|
+
"@dxos/storybook-utils": "0.8.3-staging.0fa589b"
|
|
75
77
|
},
|
|
76
78
|
"peerDependencies": {
|
|
77
79
|
"@effect-rx/rx-react": "^0.34.1",
|
|
@@ -80,8 +82,8 @@
|
|
|
80
82
|
"effect": "^3.13.3",
|
|
81
83
|
"react": "~18.2.0",
|
|
82
84
|
"react-dom": "~18.2.0",
|
|
83
|
-
"@dxos/react-ui": "0.8.3-
|
|
84
|
-
"@dxos/react-ui-theme": "0.8.3-
|
|
85
|
+
"@dxos/react-ui": "0.8.3-staging.0fa589b",
|
|
86
|
+
"@dxos/react-ui-theme": "0.8.3-staging.0fa589b"
|
|
85
87
|
},
|
|
86
88
|
"publishConfig": {
|
|
87
89
|
"access": "public"
|
package/src/NavTreePlugin.tsx
CHANGED
|
@@ -55,12 +55,18 @@ export const NavTreePlugin = () =>
|
|
|
55
55
|
}),
|
|
56
56
|
defineModule({
|
|
57
57
|
id: `${meta.id}/module/expose`,
|
|
58
|
-
activatesOn: allOf(Events.DispatcherReady, Events.LayoutReady, NavTreeEvents.StateReady),
|
|
58
|
+
activatesOn: allOf(Events.DispatcherReady, Events.AppGraphReady, Events.LayoutReady, NavTreeEvents.StateReady),
|
|
59
59
|
activate: async (context) => {
|
|
60
60
|
const layout = context.getCapability(Capabilities.Layout);
|
|
61
61
|
const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);
|
|
62
|
+
const { graph } = context.getCapability(Capabilities.AppGraph);
|
|
62
63
|
if (dispatch && layout.active.length === 1) {
|
|
63
|
-
|
|
64
|
+
// TODO(wittjosiah): This should really be fired once the navtree renders for the first time.
|
|
65
|
+
// That is the point at which the graph is expanded and the path should be available.
|
|
66
|
+
void graph
|
|
67
|
+
.waitForPath({ target: layout.active[0] }, { timeout: 30_000 })
|
|
68
|
+
.then(() => dispatch(createIntent(LayoutAction.Expose, { part: 'navigation', subject: layout.active[0] })))
|
|
69
|
+
.catch(() => {});
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
return [];
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { effect } from '@preact/signals-core';
|
|
5
|
+
import { effect, untracked } from '@preact/signals-core';
|
|
6
6
|
|
|
7
7
|
import { Capabilities, contributes, type PluginContext } from '@dxos/app-framework';
|
|
8
|
-
import { type Live
|
|
8
|
+
import { type Live } from '@dxos/echo';
|
|
9
|
+
import { live } from '@dxos/live-object';
|
|
9
10
|
import { Path } from '@dxos/react-ui-list';
|
|
10
11
|
|
|
11
12
|
import { NavTreeCapabilities } from './capabilities';
|
|
@@ -21,15 +22,10 @@ const getInitialState = () => {
|
|
|
21
22
|
|
|
22
23
|
try {
|
|
23
24
|
const cached: [string, { open: boolean; current: boolean }][] = JSON.parse(stringified);
|
|
24
|
-
return cached.map(
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
open: boolean;
|
|
29
|
-
current: boolean;
|
|
30
|
-
}>,
|
|
31
|
-
] => [key, live({ open: value.open, current: false })],
|
|
32
|
-
);
|
|
25
|
+
return cached.map(([key, value]): [string, Live<{ open: boolean; current: boolean }>] => [
|
|
26
|
+
key,
|
|
27
|
+
live({ open: value.open, current: false }),
|
|
28
|
+
]);
|
|
33
29
|
} catch {}
|
|
34
30
|
};
|
|
35
31
|
|
|
@@ -78,9 +74,7 @@ export default (context: PluginContext) => {
|
|
|
78
74
|
const removed = previous.filter((id) => !layout.active.includes(id));
|
|
79
75
|
previous = layout.active;
|
|
80
76
|
|
|
81
|
-
|
|
82
|
-
// This could be avoided if the location was a path as well and not just an id.
|
|
83
|
-
setTimeout(() => {
|
|
77
|
+
const handleUpdate = () => {
|
|
84
78
|
removed.forEach((id) => {
|
|
85
79
|
const keys = Array.from(state.keys()).filter((key) => Path.last(key) === id);
|
|
86
80
|
keys.forEach((key) => {
|
|
@@ -94,7 +88,14 @@ export default (context: PluginContext) => {
|
|
|
94
88
|
setItem(Path.parts(key), 'current', true);
|
|
95
89
|
});
|
|
96
90
|
});
|
|
97
|
-
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// TODO(wittjosiah): This is setTimeout because there's a race between the keys be initialized.
|
|
94
|
+
// Keys are initialized on the first render of an item in the navtree.
|
|
95
|
+
// This could be avoided if the location was a path as well and not just an id.
|
|
96
|
+
const timeout = setTimeout(handleUpdate, 500);
|
|
97
|
+
untracked(() => handleUpdate());
|
|
98
|
+
return () => clearTimeout(timeout);
|
|
98
99
|
});
|
|
99
100
|
|
|
100
101
|
return contributes(NavTreeCapabilities.State, { state, getItem, setItem, isOpen, isCurrent, isAlternateTree }, () =>
|
|
@@ -10,6 +10,12 @@ import { Keyboard, keySymbols } from '@dxos/keyboard';
|
|
|
10
10
|
import { useActions } from '@dxos/plugin-graph';
|
|
11
11
|
import { Button, Dialog, Icon, useTranslation, toLocalizedString } from '@dxos/react-ui';
|
|
12
12
|
import { SearchList } from '@dxos/react-ui-searchlist';
|
|
13
|
+
import {
|
|
14
|
+
cardDialogContent,
|
|
15
|
+
cardDialogHeader,
|
|
16
|
+
cardDialogPaddedOverflow,
|
|
17
|
+
cardDialogSearchListRoot,
|
|
18
|
+
} from '@dxos/react-ui-stack';
|
|
13
19
|
import { descriptionText, mx } from '@dxos/react-ui-theme';
|
|
14
20
|
import { getHostPlatform } from '@dxos/util';
|
|
15
21
|
|
|
@@ -56,13 +62,13 @@ export const CommandsDialogContent = ({ selected: initial }: { selected?: string
|
|
|
56
62
|
const actions = isActionGroup(group) ? groupActions : allActions;
|
|
57
63
|
|
|
58
64
|
return (
|
|
59
|
-
<Dialog.Content classNames={
|
|
60
|
-
<Dialog.Title>{t('commands dialog title', { ns: NAVTREE_PLUGIN })}</Dialog.Title>
|
|
65
|
+
<Dialog.Content classNames={cardDialogContent}>
|
|
66
|
+
<Dialog.Title classNames={cardDialogHeader}>{t('commands dialog title', { ns: NAVTREE_PLUGIN })}</Dialog.Title>
|
|
61
67
|
|
|
62
68
|
{/* TODO(burdon): BUG: Overscrolls container. */}
|
|
63
|
-
<SearchList.Root label={t('command list input placeholder')} classNames=
|
|
64
|
-
<SearchList.Input placeholder={t('command list input placeholder')}
|
|
65
|
-
<SearchList.Content classNames=
|
|
69
|
+
<SearchList.Root label={t('command list input placeholder')} classNames={cardDialogSearchListRoot}>
|
|
70
|
+
<SearchList.Input placeholder={t('command list input placeholder')} />
|
|
71
|
+
<SearchList.Content classNames={cardDialogPaddedOverflow}>
|
|
66
72
|
{actions?.map((action) => {
|
|
67
73
|
const label = toLocalizedString(action.properties.label, t);
|
|
68
74
|
const shortcut =
|
|
@@ -102,11 +108,11 @@ export const CommandsDialogContent = ({ selected: initial }: { selected?: string
|
|
|
102
108
|
</SearchList.Content>
|
|
103
109
|
</SearchList.Root>
|
|
104
110
|
|
|
105
|
-
<
|
|
106
|
-
<
|
|
107
|
-
{t('close label', { ns: 'os' })}
|
|
108
|
-
</
|
|
109
|
-
</
|
|
111
|
+
<div role='none' className='pli-cardSpacingInline pbe-cardSpacingBlock'>
|
|
112
|
+
<Dialog.Close asChild>
|
|
113
|
+
<Button classNames='is-full'>{t('close label', { ns: 'os' })}</Button>
|
|
114
|
+
</Dialog.Close>
|
|
115
|
+
</div>
|
|
110
116
|
</Dialog.Content>
|
|
111
117
|
);
|
|
112
118
|
};
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
import { withPluginManager } from '@dxos/app-framework/testing';
|
|
22
22
|
import { live } from '@dxos/live-object';
|
|
23
23
|
import { AttentionPlugin } from '@dxos/plugin-attention';
|
|
24
|
+
import { ClientPlugin } from '@dxos/plugin-client';
|
|
24
25
|
import { GraphPlugin } from '@dxos/plugin-graph';
|
|
26
|
+
import { SpacePlugin } from '@dxos/plugin-space';
|
|
25
27
|
import { StorybookLayoutPlugin } from '@dxos/plugin-storybook-layout';
|
|
26
28
|
import { ThemePlugin } from '@dxos/plugin-theme';
|
|
27
29
|
import { faker } from '@dxos/random';
|
|
@@ -76,7 +78,7 @@ const StoryPlank = ({ attendableId }: { attendableId: string }) => {
|
|
|
76
78
|
>
|
|
77
79
|
<StoryPlankHeading attendableId={attendableId} />
|
|
78
80
|
<StackItem.Content toolbar>
|
|
79
|
-
<Toolbar.Root classNames='border-b border-
|
|
81
|
+
<Toolbar.Root classNames='border-b border-subduedSeparator'>
|
|
80
82
|
<Toolbar.Button>Test</Toolbar.Button>
|
|
81
83
|
</Toolbar.Root>
|
|
82
84
|
|
|
@@ -131,7 +133,7 @@ const meta: Meta<typeof NavTreeContainer> = {
|
|
|
131
133
|
],
|
|
132
134
|
capabilities: (context) => [
|
|
133
135
|
contributes(StoryState, live({ tab: 'space-0' })),
|
|
134
|
-
contributes(Capabilities.AppGraphBuilder, storybookGraphBuilders),
|
|
136
|
+
contributes(Capabilities.AppGraphBuilder, storybookGraphBuilders(context)),
|
|
135
137
|
contributes(Capabilities.IntentResolver, [
|
|
136
138
|
createResolver({
|
|
137
139
|
intent: LayoutAction.UpdateLayout,
|
|
@@ -157,3 +159,42 @@ export default meta;
|
|
|
157
159
|
type Story = StoryObj<typeof NavTreeContainer>;
|
|
158
160
|
|
|
159
161
|
export const Default: Story = {};
|
|
162
|
+
|
|
163
|
+
// TODO(wittjosiah): Deduplicate plugins/capabilities with default story.
|
|
164
|
+
export const WithClient: Story = {
|
|
165
|
+
decorators: [
|
|
166
|
+
withPluginManager({
|
|
167
|
+
plugins: [
|
|
168
|
+
ThemePlugin({ tx: defaultTx }),
|
|
169
|
+
GraphPlugin(),
|
|
170
|
+
IntentPlugin(),
|
|
171
|
+
SettingsPlugin(),
|
|
172
|
+
AttentionPlugin(),
|
|
173
|
+
NavTreePlugin(),
|
|
174
|
+
ClientPlugin({
|
|
175
|
+
onClientInitialized: async (_, client) => {
|
|
176
|
+
await client.halo.createIdentity();
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
SpacePlugin(),
|
|
180
|
+
// Needs to be last so that the dialog and popovers have access to all contexts.
|
|
181
|
+
StorybookLayoutPlugin({ initialState: { sidebarState: 'expanded' } }),
|
|
182
|
+
],
|
|
183
|
+
capabilities: (context) => [
|
|
184
|
+
contributes(StoryState, live({ tab: 'space-0' })),
|
|
185
|
+
contributes(Capabilities.IntentResolver, [
|
|
186
|
+
createResolver({
|
|
187
|
+
intent: LayoutAction.UpdateLayout,
|
|
188
|
+
filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
|
|
189
|
+
Schema.is(LayoutAction.SwitchWorkspace.fields.input)(data),
|
|
190
|
+
resolve: ({ subject }) => {
|
|
191
|
+
const state = context.getCapability(StoryState);
|
|
192
|
+
state.tab = subject;
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
195
|
+
]),
|
|
196
|
+
],
|
|
197
|
+
}),
|
|
198
|
+
withLayout({ fullscreen: true }),
|
|
199
|
+
],
|
|
200
|
+
};
|
|
@@ -16,12 +16,11 @@ import {
|
|
|
16
16
|
useIntentDispatcher,
|
|
17
17
|
useLayout,
|
|
18
18
|
} from '@dxos/app-framework';
|
|
19
|
-
import { isAction, isActionLike,
|
|
19
|
+
import { isAction, isActionLike, ROOT_ID, type Node, type ReadableGraph } from '@dxos/app-graph';
|
|
20
20
|
import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
|
|
21
21
|
import { useConnections } from '@dxos/plugin-graph';
|
|
22
|
-
import { isEchoObject, isSpace } from '@dxos/react-client/echo';
|
|
23
22
|
import { useMediaQuery, useSidebars } from '@dxos/react-ui';
|
|
24
|
-
import { isTreeData, type
|
|
23
|
+
import { isTreeData, type TreeItemDataProps, type TreeData } from '@dxos/react-ui-list';
|
|
25
24
|
import { mx } from '@dxos/react-ui-theme';
|
|
26
25
|
import { arrayMove, byPosition } from '@dxos/util';
|
|
27
26
|
|
|
@@ -31,7 +30,7 @@ import { type NavTreeContextValue } from './types';
|
|
|
31
30
|
import { NavTreeCapabilities } from '../capabilities';
|
|
32
31
|
import { NAVTREE_PLUGIN } from '../meta';
|
|
33
32
|
import { type NavTreeItemGraphNode } from '../types';
|
|
34
|
-
import { getActions as naturalGetActions,
|
|
33
|
+
import { getChildren, getParent, getActions as naturalGetActions, resolveMigrationOperation } from '../util';
|
|
35
34
|
|
|
36
35
|
// TODO(thure): Is NavTree truly authoritative in this regard?
|
|
37
36
|
export const NODE_TYPE = 'dxos/app-graph/node';
|
|
@@ -82,7 +81,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
82
81
|
const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
|
|
83
82
|
|
|
84
83
|
const getProps = useCallback(
|
|
85
|
-
(node: Node, path: string[]):
|
|
84
|
+
(node: Node, path: string[]): TreeItemDataProps => {
|
|
86
85
|
const children = getChildren(graph, node, path).filter(getChildrenFilter);
|
|
87
86
|
const parentOf =
|
|
88
87
|
children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
|
|
@@ -151,18 +150,8 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
|
|
|
151
150
|
[dispatch, layout.active, tab, navigationSidebarState, isLg],
|
|
152
151
|
);
|
|
153
152
|
|
|
154
|
-
const canDrop = useCallback((source: TreeData
|
|
155
|
-
|
|
156
|
-
const targetNode = target.item as Node;
|
|
157
|
-
// TODO(wittjosiah): This shouldn't depend directly on client, there should be a graph-level abstraction.
|
|
158
|
-
if (isSpace(targetNode.data)) {
|
|
159
|
-
// TODO(wittjosiah): Find a way to only allow space as source for rearranging.
|
|
160
|
-
return isEchoObject(sourceNode.data) || isSpace(sourceNode.data);
|
|
161
|
-
} else if (isEchoObject(targetNode.data)) {
|
|
162
|
-
return isEchoObject(sourceNode.data);
|
|
163
|
-
} else {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
153
|
+
const canDrop = useCallback(({ source, target }: { source: TreeData; target: TreeData }) => {
|
|
154
|
+
return target.item.properties.canDrop?.(source) ?? false;
|
|
166
155
|
}, []);
|
|
167
156
|
|
|
168
157
|
const handleSelect = useCallback(
|
|
@@ -27,7 +27,7 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
|
|
|
27
27
|
|
|
28
28
|
const actions = (primaryAction?.properties?.disposition === 'list-item-primary' ? secondaryActions : _actions)
|
|
29
29
|
.flatMap((action) => (isAction(action) ? [action] : []))
|
|
30
|
-
.filter((
|
|
30
|
+
.filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
|
|
31
31
|
|
|
32
32
|
const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
|
|
33
33
|
|
|
@@ -24,16 +24,7 @@ import React, {
|
|
|
24
24
|
|
|
25
25
|
import { type Node } from '@dxos/app-graph';
|
|
26
26
|
import { invariant } from '@dxos/invariant';
|
|
27
|
-
import {
|
|
28
|
-
Icon,
|
|
29
|
-
IconButton,
|
|
30
|
-
ListItem,
|
|
31
|
-
ScrollArea,
|
|
32
|
-
Tooltip,
|
|
33
|
-
toLocalizedString,
|
|
34
|
-
useMediaQuery,
|
|
35
|
-
useTranslation,
|
|
36
|
-
} from '@dxos/react-ui';
|
|
27
|
+
import { Icon, ListItem, ScrollArea, Tooltip, toLocalizedString, useMediaQuery, useTranslation } from '@dxos/react-ui';
|
|
37
28
|
import { MenuProvider, DropdownMenu, type MenuItem } from '@dxos/react-ui-menu';
|
|
38
29
|
import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
|
|
39
30
|
import { Tabs } from '@dxos/react-ui-tabs';
|
|
@@ -100,6 +91,11 @@ const l0Breakpoints: Record<string, string> = {
|
|
|
100
91
|
lg: 'hidden lg:grid',
|
|
101
92
|
};
|
|
102
93
|
|
|
94
|
+
const l0ItemRoot =
|
|
95
|
+
'group/l0item flex w-full justify-center items-center relative data[type!="collection"]:cursor-pointer app-no-drag dx-focus-ring-group';
|
|
96
|
+
|
|
97
|
+
const l0ItemContent = 'flex justify-center items-center dx-focus-ring-group-indicator transition-colors rounded-sm';
|
|
98
|
+
|
|
103
99
|
const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
|
|
104
100
|
({ item, parent, path, children }, forwardedRef) => {
|
|
105
101
|
const { getProps } = useNavTreeContext();
|
|
@@ -125,10 +121,7 @@ const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
|
|
|
125
121
|
<Root
|
|
126
122
|
{...(rootProps as any)}
|
|
127
123
|
data-type={type}
|
|
128
|
-
className={mx(
|
|
129
|
-
'group/l0item flex w-full justify-center items-center relative data[type!="collection"]:cursor-pointer app-no-drag dx-focus-ring-group',
|
|
130
|
-
l0Breakpoints[item.properties.l0Breakpoint],
|
|
131
|
-
)}
|
|
124
|
+
className={mx(l0ItemRoot, l0Breakpoints[item.properties.l0Breakpoint])}
|
|
132
125
|
ref={forwardedRef}
|
|
133
126
|
>
|
|
134
127
|
{children}
|
|
@@ -138,7 +131,7 @@ const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
|
|
|
138
131
|
},
|
|
139
132
|
);
|
|
140
133
|
|
|
141
|
-
const
|
|
134
|
+
const L0Avatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
|
|
142
135
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
143
136
|
const type = l0ItemType(item);
|
|
144
137
|
const hue = item.properties.hue ?? null;
|
|
@@ -161,10 +154,8 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
|
|
|
161
154
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
162
155
|
const itemElement = useRef<HTMLElement | null>(null);
|
|
163
156
|
const [closestEdge, setEdge] = useState<Edge | null>(null);
|
|
164
|
-
const type = l0ItemType(item);
|
|
165
157
|
const localizedString = toLocalizedString(item.properties.label, t);
|
|
166
158
|
const hue = item.properties.hue ?? null;
|
|
167
|
-
const hueFgStyle = hue && { style: { color: `var(--dx-${hue}SurfaceText)` } };
|
|
168
159
|
|
|
169
160
|
useLayoutEffect(() => {
|
|
170
161
|
// NOTE(thure): This is copied from StackItem, perhaps this should be DRYed out.
|
|
@@ -227,30 +218,43 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
|
|
|
227
218
|
<div
|
|
228
219
|
role='none'
|
|
229
220
|
data-frame={true}
|
|
221
|
+
{...(hue && { style: { background: `var(--dx-${hue}Surface)` } })}
|
|
230
222
|
className={mx(
|
|
231
|
-
|
|
232
|
-
// TODO(burdon): Create reusable button/component and/or create var for size.
|
|
223
|
+
l0ItemContent,
|
|
233
224
|
pinned
|
|
234
|
-
? '
|
|
235
|
-
: '
|
|
225
|
+
? 'p-2 group-hover/l0item:bg-activeSurface'
|
|
226
|
+
: 'is-[--l0-avatar-size] bs-[--l0-avatar-size] bg-activeSurface',
|
|
236
227
|
)}
|
|
237
|
-
{...(hue && { style: { background: `var(--dx-${hue}Surface)` } })}
|
|
238
228
|
>
|
|
239
|
-
|
|
240
|
-
(type === 'tab' && item.properties.disposition !== 'pin-end' && <L0Avator item={item} />)}
|
|
229
|
+
<ItemAvatar item={item} />
|
|
241
230
|
</div>
|
|
242
231
|
<div
|
|
243
232
|
role='none'
|
|
244
|
-
className='hidden group-aria-selected/l0item:block absolute inline-start-0 inset-block-
|
|
233
|
+
className='hidden group-aria-selected/l0item:block absolute inline-start-0 inset-block-2 is-1 bg-accentSurface rounded-ie'
|
|
245
234
|
/>
|
|
246
235
|
<span id={`${item.id}__label`} className='sr-only'>
|
|
247
236
|
{localizedString}
|
|
248
237
|
</span>
|
|
249
|
-
{
|
|
238
|
+
{closestEdge && <ListItem.DropIndicator edge={closestEdge} />}
|
|
250
239
|
</L0ItemRoot>
|
|
251
240
|
);
|
|
252
241
|
};
|
|
253
242
|
|
|
243
|
+
const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
|
|
244
|
+
const type = l0ItemType(item);
|
|
245
|
+
if (item.properties.icon) {
|
|
246
|
+
const hue = item.properties.hue ?? null;
|
|
247
|
+
const hueFgStyle = hue && { style: { color: `var(--dx-${hue}SurfaceText)` } };
|
|
248
|
+
return <Icon icon={item.properties.icon} size={5} {...hueFgStyle} />;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (type === 'tab' && item.properties.disposition !== 'pin-end') {
|
|
252
|
+
return <L0Avatar item={item} />;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return null;
|
|
256
|
+
};
|
|
257
|
+
|
|
254
258
|
//
|
|
255
259
|
// L0Collection
|
|
256
260
|
//
|
|
@@ -261,6 +265,7 @@ const L0Collection = ({ item, path }: L0ItemProps) => {
|
|
|
261
265
|
const collectionItems = navTreeContext.useItems(item);
|
|
262
266
|
const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
|
|
263
267
|
const { id, testId } = navTreeContext.getProps?.(item, path) ?? {};
|
|
268
|
+
|
|
264
269
|
const handleRearrange = useCallback<StackItemRearrangeHandler<L0ItemData>>(
|
|
265
270
|
(source, target, closestEdge) => {
|
|
266
271
|
invariant(item.properties.onRearrangeChildren);
|
|
@@ -306,7 +311,7 @@ export type L0MenuProps = {
|
|
|
306
311
|
menuActions: MenuItem[];
|
|
307
312
|
topLevelItems: Node<any>[];
|
|
308
313
|
pinnedItems: Node<any>[];
|
|
309
|
-
userAccountItem
|
|
314
|
+
userAccountItem?: Node<any>;
|
|
310
315
|
parent?: Node<any>;
|
|
311
316
|
path: string[];
|
|
312
317
|
};
|
|
@@ -317,37 +322,40 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
|
|
|
317
322
|
return (
|
|
318
323
|
<Tabs.Tablist
|
|
319
324
|
classNames={[
|
|
320
|
-
'group/l0 absolute z-[1] inset-block-0 inline-start-0 rounded-is
|
|
321
|
-
'grid grid-cols-[var(--l0-size)] grid-rows-[var(--rail-size)_1fr_min-content_var(--l0-size)]
|
|
325
|
+
'group/l0 absolute z-[1] inset-block-0 inline-start-0 rounded-is',
|
|
326
|
+
'grid grid-cols-[var(--l0-size)] grid-rows-[var(--rail-size)_1fr_min-content_var(--l0-size)] contain-layout',
|
|
322
327
|
'!is-[--l0-size] bg-baseSurface border-ie border-subduedSeparator app-drag pbe-[env(safe-area-inset-bottom)]',
|
|
323
328
|
]}
|
|
324
329
|
>
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
330
|
+
{/* TODO(wittjosiah): Use L0Item trigger. */}
|
|
331
|
+
<MenuProvider>
|
|
332
|
+
<DropdownMenu.Root group={parent} items={menuActions}>
|
|
333
|
+
<Tooltip.Trigger content={t('app menu label')} side='right' asChild>
|
|
334
|
+
<DropdownMenu.Trigger
|
|
335
|
+
data-testid='spacePlugin.addSpace'
|
|
336
|
+
className={mx(l0ItemRoot, 'grid place-items-center')}
|
|
337
|
+
>
|
|
338
|
+
<div
|
|
339
|
+
role='none'
|
|
340
|
+
className={mx(
|
|
341
|
+
l0ItemContent,
|
|
342
|
+
'is-[--rail-action] bs-[--rail-action] group-hover/l0item:bg-hoverSurface',
|
|
343
|
+
)}
|
|
344
|
+
>
|
|
345
|
+
<Icon icon='ph--list--regular' size={5} />
|
|
346
|
+
</div>
|
|
340
347
|
</DropdownMenu.Trigger>
|
|
341
|
-
</
|
|
342
|
-
</
|
|
343
|
-
</
|
|
348
|
+
</Tooltip.Trigger>
|
|
349
|
+
</DropdownMenu.Root>
|
|
350
|
+
</MenuProvider>
|
|
344
351
|
|
|
352
|
+
{/* Space list. */}
|
|
345
353
|
<ScrollArea.Root>
|
|
346
354
|
<ScrollArea.Viewport>
|
|
347
355
|
<div
|
|
348
356
|
role='none'
|
|
349
357
|
className={mx([
|
|
350
|
-
'flex flex-col gap-
|
|
358
|
+
'flex flex-col gap-1 pbs-1',
|
|
351
359
|
'[body[data-platform="darwin"]_&]:pbs-[calc(30px+0.25rem)]',
|
|
352
360
|
'[body[data-platform="ios"]_&]:pbs-[max(env(safe-area-inset-top),0.25rem)]',
|
|
353
361
|
])}
|
|
@@ -366,6 +374,7 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
|
|
|
366
374
|
</ScrollArea.Viewport>
|
|
367
375
|
</ScrollArea.Root>
|
|
368
376
|
|
|
377
|
+
{/* Actions. */}
|
|
369
378
|
<div role='none' className='grid grid-cols-1 auto-rows-[--rail-action] pbs-2'>
|
|
370
379
|
{pinnedItems
|
|
371
380
|
.filter((item) => l0ItemType(item) !== 'collection')
|
|
@@ -382,7 +391,7 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
|
|
|
382
391
|
hue={userAccountItem.properties.hue}
|
|
383
392
|
emoji={userAccountItem.properties.emoji}
|
|
384
393
|
status={userAccountItem.properties.status}
|
|
385
|
-
size={
|
|
394
|
+
size={10}
|
|
386
395
|
/>
|
|
387
396
|
</L0ItemRoot>
|
|
388
397
|
</div>
|
package/src/components/types.ts
CHANGED
|
@@ -9,22 +9,18 @@ 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';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import type { L1PanelProps } from './Sidebar';
|
|
13
13
|
import type { FlattenedActions, NavTreeItemGraphNode } from '../types';
|
|
14
14
|
|
|
15
15
|
export type NavTreeContextValue = Pick<
|
|
16
|
-
TreeProps<NavTreeItemGraphNode>,
|
|
17
|
-
'getProps' | 'isCurrent' | 'isOpen' | 'canDrop' | 'onOpenChange' | 'onSelect'
|
|
16
|
+
TreeProps<NavTreeItemGraphNode, { disposition?: string; sort?: boolean }>,
|
|
17
|
+
'useItems' | 'getProps' | 'isCurrent' | 'isOpen' | 'canDrop' | 'onOpenChange' | 'onSelect'
|
|
18
18
|
> &
|
|
19
19
|
Pick<L1PanelProps, 'onBack'> & {
|
|
20
20
|
tab: string;
|
|
21
21
|
topbar?: boolean;
|
|
22
22
|
popoverAnchorId?: string;
|
|
23
23
|
renderItemEnd?: FC<{ node: Node; open: boolean }>;
|
|
24
|
-
useItems: (
|
|
25
|
-
node?: NavTreeItemGraphNode,
|
|
26
|
-
options?: { disposition?: string; sort?: boolean },
|
|
27
|
-
) => NavTreeItemGraphNode[];
|
|
28
24
|
getActions: (node: Node) => FlattenedActions;
|
|
29
25
|
loadDescendents?: (node: Node) => MaybePromise<void>;
|
|
30
26
|
isAlternateTree?: (path: string[], item: NavTreeItemGraphNode) => boolean;
|