@dxos/plugin-navtree 0.7.4 → 0.7.5-main.937ce75
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-PSVCPL4T.mjs → chunk-VAXG4OAG.mjs} +3 -1
- package/dist/lib/browser/chunk-VAXG4OAG.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +165 -352
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +3 -1
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/components/CommandsTrigger.d.ts.map +1 -1
- package/dist/types/src/components/NavTree.d.ts +2 -2
- package/dist/types/src/components/NavTree.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeFooter.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.d.ts +4 -8
- package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts +0 -5
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -0
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +0 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +1 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/package.json +30 -29
- package/src/NavTreePlugin.tsx +67 -62
- package/src/components/CommandsDialog.tsx +3 -3
- package/src/components/CommandsTrigger.tsx +10 -7
- package/src/components/NavTree.tsx +6 -11
- package/src/components/NavTreeContainer.tsx +32 -54
- package/src/components/NavTreeFooter.tsx +3 -2
- package/src/components/NavTreeItemAction.stories.tsx +0 -2
- package/src/components/NavTreeItemAction.tsx +53 -316
- package/src/components/NotchStart.tsx +4 -4
- package/src/meta.ts +2 -0
- package/src/translations.ts +0 -1
- package/src/types.ts +1 -1
- package/dist/lib/browser/chunk-PSVCPL4T.mjs.map +0 -7
|
@@ -5,24 +5,27 @@
|
|
|
5
5
|
import { MagnifyingGlass } from '@phosphor-icons/react';
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
|
-
import { LayoutAction,
|
|
8
|
+
import { createIntent, LayoutAction, useIntentDispatcher } from '@dxos/app-framework';
|
|
9
9
|
import { Button, useTranslation } from '@dxos/react-ui';
|
|
10
10
|
import { getSize } from '@dxos/react-ui-theme';
|
|
11
11
|
|
|
12
|
-
import { NAVTREE_PLUGIN } from '../meta';
|
|
12
|
+
import { COMMANDS_DIALOG, NAVTREE_PLUGIN } from '../meta';
|
|
13
13
|
|
|
14
14
|
// TODO(thure): Refactor to be handled by a more appropriate plugin.
|
|
15
15
|
export const CommandsTrigger = () => {
|
|
16
|
-
const { dispatch } =
|
|
16
|
+
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
17
17
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
18
18
|
return (
|
|
19
19
|
<Button
|
|
20
20
|
classNames='w-full h-full !rounded-none'
|
|
21
21
|
onClick={() =>
|
|
22
|
-
dispatch(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
dispatch(
|
|
23
|
+
createIntent(LayoutAction.SetLayout, {
|
|
24
|
+
element: 'dialog',
|
|
25
|
+
component: COMMANDS_DIALOG,
|
|
26
|
+
dialogBlockAlign: 'start',
|
|
27
|
+
}),
|
|
28
|
+
)
|
|
26
29
|
}
|
|
27
30
|
>
|
|
28
31
|
<span className='text-description font-normal grow text-start'>{t('command list input placeholder')}</span>
|
|
@@ -23,14 +23,13 @@ export type NavTreeProps = Omit<
|
|
|
23
23
|
|
|
24
24
|
export const NavTree = ({ getActions, loadDescendents, renderItemEnd, popoverAnchorId, ...props }: NavTreeProps) => {
|
|
25
25
|
const renderColumns = useCallback<NonNullable<TreeProps<NavTreeItemGraphNode>['renderColumns']>>(
|
|
26
|
-
({ item, path,
|
|
26
|
+
({ item, path, open }) => {
|
|
27
27
|
return (
|
|
28
28
|
<NavTreeColumns
|
|
29
29
|
path={path}
|
|
30
30
|
node={item}
|
|
31
31
|
getActions={getActions}
|
|
32
|
-
|
|
33
|
-
setMenuOpen={setMenuOpen}
|
|
32
|
+
open={open}
|
|
34
33
|
loadDescendents={loadDescendents}
|
|
35
34
|
renderItemEnd={renderItemEnd}
|
|
36
35
|
popoverAnchorId={popoverAnchorId}
|
|
@@ -54,10 +53,9 @@ type NavTreeColumnsProps = {
|
|
|
54
53
|
path: string[];
|
|
55
54
|
node: Node;
|
|
56
55
|
getActions: (node: Node) => FlattenedActions;
|
|
57
|
-
|
|
58
|
-
setMenuOpen: (open: boolean) => void;
|
|
56
|
+
open: boolean;
|
|
59
57
|
loadDescendents?: (node: Node) => MaybePromise<void>;
|
|
60
|
-
renderItemEnd?: FC<{ node: Node }>;
|
|
58
|
+
renderItemEnd?: FC<{ node: Node; open: boolean }>;
|
|
61
59
|
popoverAnchorId?: string;
|
|
62
60
|
};
|
|
63
61
|
|
|
@@ -65,8 +63,7 @@ const NavTreeColumns = ({
|
|
|
65
63
|
path,
|
|
66
64
|
node,
|
|
67
65
|
getActions,
|
|
68
|
-
|
|
69
|
-
setMenuOpen,
|
|
66
|
+
open,
|
|
70
67
|
loadDescendents,
|
|
71
68
|
renderItemEnd: ItemEnd,
|
|
72
69
|
popoverAnchorId,
|
|
@@ -123,14 +120,12 @@ const NavTreeColumns = ({
|
|
|
123
120
|
menuActions={actions}
|
|
124
121
|
menuType='dropdown'
|
|
125
122
|
caller={NAV_TREE_ITEM}
|
|
126
|
-
menuOpen={menuOpen}
|
|
127
|
-
onChangeMenuOpen={setMenuOpen}
|
|
128
123
|
/>
|
|
129
124
|
) : (
|
|
130
125
|
<Treegrid.Cell />
|
|
131
126
|
)}
|
|
132
127
|
</ActionRoot>
|
|
133
|
-
{ItemEnd && <ItemEnd node={node} />}
|
|
128
|
+
{ItemEnd && <ItemEnd node={node} open={open} />}
|
|
134
129
|
</>
|
|
135
130
|
);
|
|
136
131
|
};
|
|
@@ -7,11 +7,11 @@ import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-a
|
|
|
7
7
|
import { untracked } from '@preact/signals-core';
|
|
8
8
|
import React, { memo, useCallback, useEffect } from 'react';
|
|
9
9
|
|
|
10
|
-
import { LayoutAction, NavigationAction, Surface, useIntentDispatcher } from '@dxos/app-framework';
|
|
10
|
+
import { createIntent, LayoutAction, NavigationAction, Surface, useIntentDispatcher } from '@dxos/app-framework';
|
|
11
11
|
import { isAction, isActionLike, type Node } from '@dxos/app-graph';
|
|
12
12
|
import { useGraph } from '@dxos/plugin-graph';
|
|
13
13
|
import { isEchoObject, isSpace } from '@dxos/react-client/echo';
|
|
14
|
-
import {
|
|
14
|
+
import { useMediaQuery, useSidebars } from '@dxos/react-ui';
|
|
15
15
|
import { isTreeData, type TreeData, type PropsFromTreeItem } from '@dxos/react-ui-list';
|
|
16
16
|
import { mx } from '@dxos/react-ui-theme';
|
|
17
17
|
import { arrayMove } from '@dxos/util';
|
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
// TODO(thure): Is NavTree truly authoritative in this regard?
|
|
33
33
|
export const NODE_TYPE = 'dxos/app-graph/node';
|
|
34
34
|
|
|
35
|
-
const renderItemEnd = ({ node }: { node: Node }) => (
|
|
36
|
-
<Surface role='navtree-item-end' data={{
|
|
35
|
+
const renderItemEnd = ({ node, open }: { node: Node; open: boolean }) => (
|
|
36
|
+
<Surface role='navtree-item-end' data={{ id: node.id, subject: node.data, open }} limit={1} />
|
|
37
37
|
);
|
|
38
38
|
|
|
39
39
|
export type NavTreeContainerProps = {
|
|
@@ -43,7 +43,7 @@ export type NavTreeContainerProps = {
|
|
|
43
43
|
export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }: NavTreeContainerProps) => {
|
|
44
44
|
const { closeNavigationSidebar } = useSidebars(NAVTREE_PLUGIN);
|
|
45
45
|
const [isLg] = useMediaQuery('lg', { ssr: false });
|
|
46
|
-
const dispatch = useIntentDispatcher();
|
|
46
|
+
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
47
47
|
const { graph } = useGraph();
|
|
48
48
|
|
|
49
49
|
const getActions = useCallback((node: Node) => naturalGetActions(graph, node), [graph]);
|
|
@@ -124,28 +124,11 @@ export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }:
|
|
|
124
124
|
|
|
125
125
|
const current = isCurrent(path, node);
|
|
126
126
|
if (!current) {
|
|
127
|
-
void dispatch({
|
|
128
|
-
action: NavigationAction.OPEN,
|
|
129
|
-
data: {
|
|
130
|
-
activeParts: {
|
|
131
|
-
main: [node.id],
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
});
|
|
127
|
+
void dispatch(createIntent(NavigationAction.Open, { activeParts: { main: [node.id] } }));
|
|
135
128
|
} else if (option) {
|
|
136
|
-
void dispatch({
|
|
137
|
-
action: NavigationAction.CLOSE,
|
|
138
|
-
data: {
|
|
139
|
-
activeParts: {
|
|
140
|
-
main: [node.id],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
});
|
|
129
|
+
void dispatch(createIntent(NavigationAction.Close, { activeParts: { main: [node.id] } }));
|
|
144
130
|
} else {
|
|
145
|
-
void dispatch({
|
|
146
|
-
action: LayoutAction.SCROLL_INTO_VIEW,
|
|
147
|
-
data: { id: node.id },
|
|
148
|
-
});
|
|
131
|
+
void dispatch(createIntent(LayoutAction.ScrollIntoView, { id: node.id }));
|
|
149
132
|
}
|
|
150
133
|
|
|
151
134
|
const defaultAction = graph.actions(node).find((action) => action.properties?.disposition === 'default');
|
|
@@ -220,35 +203,30 @@ export const NavTreeContainer = memo(({ isCurrent, popoverAnchorId, ...props }:
|
|
|
220
203
|
}, [graph]);
|
|
221
204
|
|
|
222
205
|
return (
|
|
223
|
-
<
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
>
|
|
228
|
-
{/* TODO(wittjosiah): Factor out surfaces to layout? */}
|
|
229
|
-
<div role='none' className='border-be border-separator'>
|
|
230
|
-
<Surface role='search-input' limit={1} />
|
|
231
|
-
</div>
|
|
232
|
-
|
|
233
|
-
{/* TODO(thure): What gives this an inline `overflow: initial`? */}
|
|
234
|
-
<div role='none' className='border-be border-separator !overflow-y-auto'>
|
|
235
|
-
<NavTree
|
|
236
|
-
id={graph.root.id}
|
|
237
|
-
getActions={getActions}
|
|
238
|
-
getItems={getItems}
|
|
239
|
-
getProps={getProps}
|
|
240
|
-
isCurrent={isCurrent}
|
|
241
|
-
loadDescendents={loadDescendents}
|
|
242
|
-
renderItemEnd={renderItemEnd}
|
|
243
|
-
popoverAnchorId={popoverAnchorId}
|
|
244
|
-
canDrop={canDrop}
|
|
245
|
-
onSelect={handleSelect}
|
|
246
|
-
{...props}
|
|
247
|
-
/>
|
|
248
|
-
</div>
|
|
249
|
-
|
|
250
|
-
<NavTreeFooter />
|
|
206
|
+
<div role='none' className='grid grid-cols-1 grid-rows-[var(--rail-size)_1fr_min-content] bs-full overflow-hidden'>
|
|
207
|
+
{/* TODO(wittjosiah): Factor out surfaces to layout? */}
|
|
208
|
+
<div role='none' className='border-be border-separator'>
|
|
209
|
+
<Surface role='search-input' limit={1} />
|
|
251
210
|
</div>
|
|
252
|
-
|
|
211
|
+
|
|
212
|
+
{/* TODO(thure): What gives this an inline `overflow: initial`? */}
|
|
213
|
+
<div role='none' className='border-be border-separator !overflow-y-auto'>
|
|
214
|
+
<NavTree
|
|
215
|
+
id={graph.root.id}
|
|
216
|
+
getActions={getActions}
|
|
217
|
+
getItems={getItems}
|
|
218
|
+
getProps={getProps}
|
|
219
|
+
isCurrent={isCurrent}
|
|
220
|
+
loadDescendents={loadDescendents}
|
|
221
|
+
renderItemEnd={renderItemEnd}
|
|
222
|
+
popoverAnchorId={popoverAnchorId}
|
|
223
|
+
canDrop={canDrop}
|
|
224
|
+
onSelect={handleSelect}
|
|
225
|
+
{...props}
|
|
226
|
+
/>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
<NavTreeFooter />
|
|
230
|
+
</div>
|
|
253
231
|
);
|
|
254
232
|
});
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
parseNavigationPlugin,
|
|
13
13
|
useResolvePlugin,
|
|
14
14
|
useIntentDispatcher,
|
|
15
|
+
createIntent,
|
|
15
16
|
} from '@dxos/app-framework';
|
|
16
17
|
import { useConfig } from '@dxos/react-client';
|
|
17
18
|
import {
|
|
@@ -41,7 +42,7 @@ export const NavTreeFooter = (props: { layoutPart?: LayoutPart }) => {
|
|
|
41
42
|
const config = useConfig();
|
|
42
43
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
43
44
|
const { navigationSidebarOpen } = useSidebars(NAVTREE_PLUGIN);
|
|
44
|
-
const dispatch = useIntentDispatcher();
|
|
45
|
+
const { dispatchPromise: dispatch } = useIntentDispatcher();
|
|
45
46
|
const { version, timestamp, commitHash } = config.values.runtime?.app?.build ?? {};
|
|
46
47
|
const navigationPlugin = useResolvePlugin(parseNavigationPlugin);
|
|
47
48
|
const [_, v] = version?.match(VERSION_REGEX) ?? [];
|
|
@@ -126,7 +127,7 @@ export const NavTreeFooter = (props: { layoutPart?: LayoutPart }) => {
|
|
|
126
127
|
data-testid='treeView.openSettings'
|
|
127
128
|
data-joyride='welcome/settings'
|
|
128
129
|
{...(!navigationSidebarOpen && { tabIndex: -1 })}
|
|
129
|
-
onClick={() => dispatch(
|
|
130
|
+
onClick={() => dispatch(createIntent(SettingsAction.Open))}
|
|
130
131
|
>
|
|
131
132
|
<span className='sr-only'>{t('open settings label')}</span>
|
|
132
133
|
<GearSix className={mx(getSize(5), 'rotate-90')} />
|
|
@@ -2,24 +2,12 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import React, { type MutableRefObject, type PropsWithChildren, useRef, useState } from 'react';
|
|
5
|
+
import React, { type MutableRefObject, useRef } from 'react';
|
|
7
6
|
|
|
8
|
-
import { type Action, type
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
Dialog,
|
|
13
|
-
DropdownMenu,
|
|
14
|
-
ContextMenu,
|
|
15
|
-
Tooltip,
|
|
16
|
-
useTranslation,
|
|
17
|
-
toLocalizedString,
|
|
18
|
-
Icon,
|
|
19
|
-
} from '@dxos/react-ui';
|
|
20
|
-
import { SearchList } from '@dxos/react-ui-searchlist';
|
|
21
|
-
import { descriptionText, hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
|
|
22
|
-
import { getHostPlatform } from '@dxos/util';
|
|
7
|
+
import { type Action, type Node } from '@dxos/app-graph';
|
|
8
|
+
import { useTranslation, toLocalizedString, IconButton } from '@dxos/react-ui';
|
|
9
|
+
import { DropdownMenu, type MenuAction } from '@dxos/react-ui-menu';
|
|
10
|
+
import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
|
|
23
11
|
|
|
24
12
|
import { NAVTREE_PLUGIN } from '../meta';
|
|
25
13
|
import { type ActionProperties } from '../types';
|
|
@@ -30,338 +18,87 @@ export type NavTreeItemActionMenuProps = ActionProperties & {
|
|
|
30
18
|
monolithic?: boolean;
|
|
31
19
|
menuActions?: Action[];
|
|
32
20
|
suppressNextTooltip?: MutableRefObject<boolean>;
|
|
33
|
-
menuOpen?: boolean;
|
|
34
|
-
defaultMenuOpen?: boolean;
|
|
35
|
-
onChangeMenuOpen?: (nextOpen: boolean) => void;
|
|
36
21
|
onAction?: (action: Action) => void;
|
|
37
22
|
};
|
|
38
23
|
|
|
39
|
-
const getShortcut = (action: ActionLike) => {
|
|
40
|
-
return typeof action.properties?.keyBinding === 'string'
|
|
41
|
-
? action.properties.keyBinding
|
|
42
|
-
: action.properties?.keyBinding?.[getHostPlatform()];
|
|
43
|
-
};
|
|
44
|
-
|
|
45
24
|
const fallbackIcon = 'ph--placeholder--regular';
|
|
46
25
|
|
|
26
|
+
const actionButtonProps = {
|
|
27
|
+
iconOnly: true,
|
|
28
|
+
size: 4 as const,
|
|
29
|
+
variant: 'ghost' as const,
|
|
30
|
+
density: 'fine' as const,
|
|
31
|
+
classNames: mx('shrink-0 !pli-2 pointer-fine:!pli-1', hoverableControlItem, hoverableOpenControlItem),
|
|
32
|
+
};
|
|
33
|
+
|
|
47
34
|
export const NavTreeItemActionDropdownMenu = ({
|
|
48
35
|
label,
|
|
49
36
|
icon,
|
|
50
37
|
testId,
|
|
51
|
-
defaultMenuOpen,
|
|
52
38
|
menuActions,
|
|
53
|
-
menuOpen,
|
|
54
|
-
suppressNextTooltip,
|
|
55
|
-
onChangeMenuOpen,
|
|
56
39
|
onAction,
|
|
57
40
|
}: NavTreeItemActionMenuProps) => {
|
|
58
41
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
59
|
-
|
|
60
|
-
const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
|
|
61
|
-
prop: menuOpen,
|
|
62
|
-
defaultProp: defaultMenuOpen,
|
|
63
|
-
onChange: onChangeMenuOpen,
|
|
64
|
-
});
|
|
42
|
+
const suppressNextTooltip = useRef<boolean>(false);
|
|
65
43
|
|
|
66
44
|
return (
|
|
67
45
|
<DropdownMenu.Root
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!nextOpen && suppressNextTooltip) {
|
|
72
|
-
suppressNextTooltip.current = true;
|
|
73
|
-
}
|
|
74
|
-
return setOptionsMenuOpen(nextOpen);
|
|
75
|
-
},
|
|
76
|
-
}}
|
|
46
|
+
actions={menuActions as MenuAction[]}
|
|
47
|
+
onAction={onAction}
|
|
48
|
+
suppressNextTooltip={suppressNextTooltip}
|
|
77
49
|
>
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<span className='sr-only'>{toLocalizedString(label, t)}</span>
|
|
88
|
-
<Icon icon={icon ?? fallbackIcon} size={4} />
|
|
89
|
-
</Button>
|
|
90
|
-
</DropdownMenu.Trigger>
|
|
91
|
-
</Tooltip.Trigger>
|
|
92
|
-
<DropdownMenu.Portal>
|
|
93
|
-
<DropdownMenu.Content classNames='z-[31]'>
|
|
94
|
-
<DropdownMenu.Viewport>
|
|
95
|
-
{menuActions?.map((action) => {
|
|
96
|
-
const shortcut = getShortcut(action);
|
|
97
|
-
return (
|
|
98
|
-
<DropdownMenu.Item
|
|
99
|
-
key={action.id}
|
|
100
|
-
onClick={(event) => {
|
|
101
|
-
if (action.properties?.disabled) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
event.stopPropagation();
|
|
105
|
-
// TODO(thure): Why does Dialog’s modal-ness cause issues if we don’t explicitly close the menu here?
|
|
106
|
-
if (suppressNextTooltip) {
|
|
107
|
-
suppressNextTooltip.current = true;
|
|
108
|
-
}
|
|
109
|
-
setOptionsMenuOpen(false);
|
|
110
|
-
onAction?.(action);
|
|
111
|
-
}}
|
|
112
|
-
classNames='gap-2'
|
|
113
|
-
disabled={action.properties?.disabled}
|
|
114
|
-
{...(action.properties?.testId && { 'data-testid': action.properties.testId })}
|
|
115
|
-
>
|
|
116
|
-
{action.properties?.icon && <Icon icon={action.properties!.icon} size={4} />}
|
|
117
|
-
<span className='grow truncate'>{toLocalizedString(action.properties!.label, t)}</span>
|
|
118
|
-
{shortcut && <span className={mx('shrink-0', descriptionText)}>{keySymbols(shortcut).join('')}</span>}
|
|
119
|
-
</DropdownMenu.Item>
|
|
120
|
-
);
|
|
121
|
-
})}
|
|
122
|
-
</DropdownMenu.Viewport>
|
|
123
|
-
<DropdownMenu.Arrow />
|
|
124
|
-
</DropdownMenu.Content>
|
|
125
|
-
</DropdownMenu.Portal>
|
|
50
|
+
<DropdownMenu.Trigger asChild>
|
|
51
|
+
<IconButton
|
|
52
|
+
{...actionButtonProps}
|
|
53
|
+
icon={icon ?? fallbackIcon}
|
|
54
|
+
label={toLocalizedString(label, t)}
|
|
55
|
+
data-testid={testId}
|
|
56
|
+
suppressNextTooltip={suppressNextTooltip}
|
|
57
|
+
/>
|
|
58
|
+
</DropdownMenu.Trigger>
|
|
126
59
|
</DropdownMenu.Root>
|
|
127
60
|
);
|
|
128
61
|
};
|
|
129
62
|
|
|
130
|
-
export const NavTreeItemActionContextMenu = (
|
|
131
|
-
props: PropsWithChildren<Pick<NavTreeItemActionMenuProps, 'menuActions' | 'onAction'>>,
|
|
132
|
-
) => {
|
|
133
|
-
return (props.menuActions?.length ?? 0) > 0 ? <NavTreeItemActionContextMenuImpl {...props} /> : <>{props.children}</>;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const NavTreeItemActionContextMenuImpl = ({
|
|
137
|
-
menuActions,
|
|
138
|
-
onAction,
|
|
139
|
-
children,
|
|
140
|
-
}: PropsWithChildren<Pick<NavTreeItemActionMenuProps, 'menuActions' | 'onAction'>>) => {
|
|
141
|
-
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
142
|
-
|
|
143
|
-
return (
|
|
144
|
-
<ContextMenu.Root>
|
|
145
|
-
<ContextMenu.Trigger asChild>{children}</ContextMenu.Trigger>
|
|
146
|
-
<ContextMenu.Portal>
|
|
147
|
-
<ContextMenu.Content classNames='z-[31]'>
|
|
148
|
-
<ContextMenu.Viewport>
|
|
149
|
-
{menuActions?.map((action) => {
|
|
150
|
-
const shortcut = getShortcut(action);
|
|
151
|
-
return (
|
|
152
|
-
<ContextMenu.Item
|
|
153
|
-
key={action.id}
|
|
154
|
-
onClick={(event) => {
|
|
155
|
-
if (action.properties?.disabled) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
event.stopPropagation();
|
|
159
|
-
onAction?.(action);
|
|
160
|
-
}}
|
|
161
|
-
classNames='gap-2'
|
|
162
|
-
disabled={action.properties?.disabled}
|
|
163
|
-
{...(action.properties?.testId && { 'data-testid': action.properties.testId })}
|
|
164
|
-
>
|
|
165
|
-
{action.properties?.icon && <Icon icon={action.properties?.icon} size={4} />}
|
|
166
|
-
<span className='grow truncate'>{toLocalizedString(action.properties!.label, t)}</span>
|
|
167
|
-
{shortcut && <span className={mx('shrink-0', descriptionText)}>{keySymbols(shortcut).join('')}</span>}
|
|
168
|
-
</ContextMenu.Item>
|
|
169
|
-
);
|
|
170
|
-
})}
|
|
171
|
-
</ContextMenu.Viewport>
|
|
172
|
-
<ContextMenu.Arrow />
|
|
173
|
-
</ContextMenu.Content>
|
|
174
|
-
</ContextMenu.Portal>
|
|
175
|
-
</ContextMenu.Root>
|
|
176
|
-
);
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
export const NavTreeItemActionSearchList = ({
|
|
180
|
-
menuActions,
|
|
181
|
-
icon,
|
|
182
|
-
label,
|
|
183
|
-
testId,
|
|
184
|
-
suppressNextTooltip,
|
|
185
|
-
onAction,
|
|
186
|
-
}: Pick<
|
|
187
|
-
NavTreeItemActionMenuProps,
|
|
188
|
-
'icon' | 'menuActions' | 'testId' | 'label' | 'onAction' | 'suppressNextTooltip'
|
|
189
|
-
>) => {
|
|
190
|
-
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
191
|
-
|
|
192
|
-
const [optionsMenuOpen, setOptionsMenuOpen] = useState(false);
|
|
193
|
-
const button = useRef<HTMLButtonElement | null>(null);
|
|
194
|
-
|
|
195
|
-
// TODO(burdon): Optionally sort.
|
|
196
|
-
const sortedActions = menuActions?.sort(({ properties: p1 }, { properties: p2 }) => {
|
|
197
|
-
const t1 = toLocalizedString(p1!.label, t).toLowerCase();
|
|
198
|
-
const t2 = toLocalizedString(p2!.label, t).toLowerCase();
|
|
199
|
-
return t1.localeCompare(t2);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// TODO(thure): Use LayoutPlugin’s global Dialog.
|
|
203
|
-
return (
|
|
204
|
-
<Dialog.Root
|
|
205
|
-
{...{
|
|
206
|
-
open: optionsMenuOpen,
|
|
207
|
-
onOpenChange: (nextOpen: boolean) => {
|
|
208
|
-
if (!nextOpen && suppressNextTooltip) {
|
|
209
|
-
suppressNextTooltip.current = true;
|
|
210
|
-
}
|
|
211
|
-
return setOptionsMenuOpen(nextOpen);
|
|
212
|
-
},
|
|
213
|
-
}}
|
|
214
|
-
>
|
|
215
|
-
<Tooltip.Trigger asChild>
|
|
216
|
-
<Dialog.Trigger asChild>
|
|
217
|
-
<Button
|
|
218
|
-
variant='ghost'
|
|
219
|
-
density='fine'
|
|
220
|
-
classNames={mx('shrink-0 !pli-2 pointer-fine:!pli-1', hoverableControlItem, hoverableOpenControlItem)}
|
|
221
|
-
data-testid={testId}
|
|
222
|
-
onKeyDownCapture={(event) => {
|
|
223
|
-
// TODO(thure): Why is only this `Button` affected and not `DropdownMenu.Trigger`’s?
|
|
224
|
-
if (button.current && button.current === event.target) {
|
|
225
|
-
switch (event.key) {
|
|
226
|
-
case 'Enter':
|
|
227
|
-
case ' ':
|
|
228
|
-
// Capture this event and stop propagation to the drag start handler.
|
|
229
|
-
event.stopPropagation();
|
|
230
|
-
setOptionsMenuOpen(true);
|
|
231
|
-
break;
|
|
232
|
-
default:
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
}}
|
|
236
|
-
ref={button}
|
|
237
|
-
>
|
|
238
|
-
<Icon icon={icon ?? fallbackIcon} size={4} />
|
|
239
|
-
</Button>
|
|
240
|
-
</Dialog.Trigger>
|
|
241
|
-
</Tooltip.Trigger>
|
|
242
|
-
<Dialog.Portal>
|
|
243
|
-
<Dialog.Overlay>
|
|
244
|
-
<Dialog.Content classNames='z-[31] is-full max-is-[24rem] pli-2 plb-1'>
|
|
245
|
-
<SearchList.Root label={t('tree item searchlist input placeholder')}>
|
|
246
|
-
<SearchList.Input placeholder={t('tree item searchlist input placeholder')} classNames='pli-3' />
|
|
247
|
-
<SearchList.Content classNames='min-bs-[12rem] bs-[50dvh] max-bs-[30rem] overflow-auto'>
|
|
248
|
-
{sortedActions?.map((action) => {
|
|
249
|
-
const label = toLocalizedString(action.properties!.label, t);
|
|
250
|
-
const shortcut = getShortcut(action);
|
|
251
|
-
return (
|
|
252
|
-
<SearchList.Item
|
|
253
|
-
// TODO(burdon): Value should be id since label is not unique.
|
|
254
|
-
value={label}
|
|
255
|
-
key={action.id}
|
|
256
|
-
onSelect={() => {
|
|
257
|
-
if (action.properties?.disabled) {
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
if (suppressNextTooltip) {
|
|
261
|
-
suppressNextTooltip.current = true;
|
|
262
|
-
}
|
|
263
|
-
setOptionsMenuOpen(false);
|
|
264
|
-
onAction?.(action);
|
|
265
|
-
}}
|
|
266
|
-
classNames='flex items-center gap-2 pli-2'
|
|
267
|
-
disabled={action.properties?.disabled}
|
|
268
|
-
{...(action.properties?.testId && { 'data-testid': action.properties.testId })}
|
|
269
|
-
>
|
|
270
|
-
{action.properties?.icon && <Icon icon={action.properties?.icon} size={4} />}
|
|
271
|
-
<span className='grow truncate'>{label}</span>
|
|
272
|
-
{shortcut && (
|
|
273
|
-
<span className={mx('shrink-0', descriptionText)}>{keySymbols(shortcut).join('')}</span>
|
|
274
|
-
)}
|
|
275
|
-
</SearchList.Item>
|
|
276
|
-
);
|
|
277
|
-
})}
|
|
278
|
-
</SearchList.Content>
|
|
279
|
-
<div role='none' className='flex items-center plb-2 pli-3'>
|
|
280
|
-
<span className={descriptionText}>{toLocalizedString(label, t)}</span>
|
|
281
|
-
</div>
|
|
282
|
-
</SearchList.Root>
|
|
283
|
-
</Dialog.Content>
|
|
284
|
-
</Dialog.Overlay>
|
|
285
|
-
</Dialog.Portal>
|
|
286
|
-
</Dialog.Root>
|
|
287
|
-
);
|
|
288
|
-
};
|
|
289
|
-
|
|
290
63
|
export const NavTreeItemMonolithicAction = ({
|
|
291
64
|
parent,
|
|
292
|
-
properties: { disabled, caller, testId,
|
|
65
|
+
properties: { disabled, caller, testId, icon } = { label: 'never' },
|
|
293
66
|
data: invoke,
|
|
294
|
-
|
|
295
|
-
|
|
67
|
+
baseLabel,
|
|
68
|
+
}: Action & { parent: Node; onAction?: (action: Action) => void; baseLabel: string }) => {
|
|
296
69
|
return (
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
>
|
|
312
|
-
<span className='sr-only'>{toLocalizedString(label, t)}</span>
|
|
313
|
-
<Icon icon={icon ?? fallbackIcon} size={4} />
|
|
314
|
-
</Button>
|
|
315
|
-
</Tooltip.Trigger>
|
|
70
|
+
<IconButton
|
|
71
|
+
{...actionButtonProps}
|
|
72
|
+
icon={icon ?? fallbackIcon}
|
|
73
|
+
label={baseLabel}
|
|
74
|
+
disabled={disabled}
|
|
75
|
+
onClick={(event) => {
|
|
76
|
+
if (disabled) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
event.stopPropagation();
|
|
80
|
+
void invoke?.(caller ? { node: parent, caller } : { node: parent });
|
|
81
|
+
}}
|
|
82
|
+
data-testid={testId}
|
|
83
|
+
/>
|
|
316
84
|
);
|
|
317
85
|
};
|
|
318
86
|
|
|
319
87
|
export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
|
|
320
88
|
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
321
|
-
const suppressNextTooltip = useRef<boolean>(false);
|
|
322
|
-
const [triggerTooltipOpen, setTriggerTooltipOpen] = useState(false);
|
|
323
89
|
|
|
324
90
|
const monolithicAction = menuActions?.length === 1 && menuActions[0];
|
|
325
91
|
const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
|
|
326
92
|
|
|
327
|
-
return (
|
|
328
|
-
<
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}}
|
|
338
|
-
>
|
|
339
|
-
{baseLabel && (
|
|
340
|
-
<Tooltip.Portal>
|
|
341
|
-
<Tooltip.Content classNames='z-[31]' side='bottom'>
|
|
342
|
-
{baseLabel}
|
|
343
|
-
<Tooltip.Arrow />
|
|
344
|
-
</Tooltip.Content>
|
|
345
|
-
</Tooltip.Portal>
|
|
346
|
-
)}
|
|
347
|
-
{monolithic && menuActions?.length === 1 ? (
|
|
348
|
-
<NavTreeItemMonolithicAction parent={node} {...menuActions[0]} />
|
|
349
|
-
) : props.menuType === 'searchList' ? (
|
|
350
|
-
<NavTreeItemActionSearchList
|
|
351
|
-
{...props}
|
|
352
|
-
menuActions={menuActions}
|
|
353
|
-
suppressNextTooltip={suppressNextTooltip}
|
|
354
|
-
onAction={(action) => action.data?.(props.caller ? { node, caller: props.caller } : { node })}
|
|
355
|
-
/>
|
|
356
|
-
) : (
|
|
357
|
-
<NavTreeItemActionDropdownMenu
|
|
358
|
-
{...props}
|
|
359
|
-
parent={node}
|
|
360
|
-
menuActions={menuActions}
|
|
361
|
-
suppressNextTooltip={suppressNextTooltip}
|
|
362
|
-
onAction={(action) => action.data?.(props.caller ? { node, caller: props.caller } : { node })}
|
|
363
|
-
/>
|
|
364
|
-
)}
|
|
365
|
-
</Tooltip.Root>
|
|
93
|
+
return monolithic && menuActions?.length === 1 ? (
|
|
94
|
+
<NavTreeItemMonolithicAction baseLabel={baseLabel} parent={node} {...menuActions[0]} />
|
|
95
|
+
) : (
|
|
96
|
+
<NavTreeItemActionDropdownMenu
|
|
97
|
+
{...props}
|
|
98
|
+
label={baseLabel}
|
|
99
|
+
parent={node}
|
|
100
|
+
menuActions={menuActions}
|
|
101
|
+
onAction={(action) => action.data?.(props.caller ? { node, caller: props.caller } : { node })}
|
|
102
|
+
/>
|
|
366
103
|
);
|
|
367
104
|
};
|