@dxos/plugin-navtree 0.6.13 → 0.6.14-main.69511f5
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-U7MTKDFQ.mjs → chunk-PSVCPL4T.mjs} +1 -1
- package/dist/lib/browser/chunk-PSVCPL4T.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +595 -392
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/meta.mjs +1 -1
- package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
- package/dist/types/src/components/CommandsDialog.d.ts.map +1 -1
- package/dist/types/src/components/HaloButton.stories.d.ts +5 -9
- package/dist/types/src/components/HaloButton.stories.d.ts.map +1 -1
- package/dist/types/src/components/NavBarStart.d.ts.map +1 -1
- package/dist/types/src/components/NavTree.d.ts +20 -0
- package/dist/types/src/components/NavTree.d.ts.map +1 -0
- package/dist/types/src/components/NavTree.stories.d.ts +7 -0
- package/dist/types/src/components/NavTree.stories.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeContainer.d.ts +8 -8
- package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
- package/dist/types/src/components/NavTreeItemAction.d.ts +23 -0
- package/dist/types/src/components/NavTreeItemAction.d.ts.map +1 -0
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts +45 -0
- package/dist/types/src/components/NavTreeItemAction.stories.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/experimental/Tree.stories.d.ts +3 -7
- package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/stories/Graph.stories.d.ts +4 -12
- package/dist/types/src/stories/Graph.stories.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +86 -0
- package/dist/types/src/testing/generator.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +3 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/visitor.d.ts +18 -0
- package/dist/types/src/testing/visitor.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -10
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +41 -0
- package/dist/types/src/types.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +8 -9
- package/dist/types/src/util.d.ts.map +1 -1
- package/package.json +39 -34
- package/src/NavTreePlugin.tsx +89 -36
- package/src/components/CommandsDialog.tsx +2 -4
- package/src/components/HaloButton.stories.tsx +8 -6
- package/src/components/NavBarStart.tsx +2 -4
- package/src/components/NavTree.stories.tsx +104 -0
- package/src/components/NavTree.tsx +127 -0
- package/src/components/NavTreeContainer.tsx +132 -302
- package/src/components/NavTreeFooter.tsx +1 -1
- package/src/components/NavTreeItemAction.stories.tsx +57 -0
- package/src/components/NavTreeItemAction.tsx +367 -0
- package/src/components/index.ts +1 -0
- package/src/experimental/Tree.stories.tsx +11 -8
- package/src/experimental/Tree.tsx +2 -2
- package/src/meta.ts +3 -1
- package/src/stories/Graph.stories.tsx +111 -102
- package/src/testing/generator.ts +199 -0
- package/src/testing/index.ts +6 -0
- package/src/testing/visitor.ts +152 -0
- package/src/translations.ts +3 -3
- package/src/types.ts +50 -0
- package/src/util.ts +52 -63
- package/dist/lib/browser/chunk-U7MTKDFQ.mjs.map +0 -7
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { type NodeArg } from '@dxos/app-graph';
|
|
8
|
+
import { faker } from '@dxos/random';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
10
|
+
|
|
11
|
+
import { NavTreeItemAction, type NavTreeItemActionMenuProps } from './NavTreeItemAction';
|
|
12
|
+
|
|
13
|
+
const parent = {
|
|
14
|
+
id: faker.string.uuid(),
|
|
15
|
+
type: 'node',
|
|
16
|
+
data: null,
|
|
17
|
+
properties: {
|
|
18
|
+
label: faker.lorem.words(2),
|
|
19
|
+
icon: 'ph--circle--regular',
|
|
20
|
+
},
|
|
21
|
+
} satisfies NodeArg<any>;
|
|
22
|
+
|
|
23
|
+
// TODO(burdon): Factor out across tests.
|
|
24
|
+
const menuActions = faker.helpers.multiple(
|
|
25
|
+
() =>
|
|
26
|
+
({
|
|
27
|
+
id: faker.string.uuid(),
|
|
28
|
+
type: 'action',
|
|
29
|
+
data: () => {
|
|
30
|
+
console.log('invoke');
|
|
31
|
+
},
|
|
32
|
+
properties: {
|
|
33
|
+
label: faker.lorem.words(2),
|
|
34
|
+
icon: 'ph--circle--regular',
|
|
35
|
+
},
|
|
36
|
+
}) satisfies NodeArg<any>,
|
|
37
|
+
{ count: 20 },
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export default {
|
|
41
|
+
title: 'plugins/plugin-navtree/NavTreeItemAction',
|
|
42
|
+
component: NavTreeItemAction,
|
|
43
|
+
decorators: [withTheme, withLayout({ fullscreen: true, tooltips: true })],
|
|
44
|
+
args: {
|
|
45
|
+
icon: 'ph--list--regular',
|
|
46
|
+
parent,
|
|
47
|
+
menuActions,
|
|
48
|
+
label: 'Select action',
|
|
49
|
+
onAction: (action) => {
|
|
50
|
+
console.log(action);
|
|
51
|
+
},
|
|
52
|
+
} satisfies Partial<NavTreeItemActionMenuProps>,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Default = {};
|
|
56
|
+
|
|
57
|
+
export const SearchList = { args: { menuType: 'searchList' } };
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useControllableState } from '@radix-ui/react-use-controllable-state';
|
|
6
|
+
import React, { type MutableRefObject, type PropsWithChildren, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { type Action, type ActionLike, type Node } from '@dxos/app-graph';
|
|
9
|
+
import { keySymbols } from '@dxos/keyboard';
|
|
10
|
+
import {
|
|
11
|
+
Button,
|
|
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';
|
|
23
|
+
|
|
24
|
+
import { NAVTREE_PLUGIN } from '../meta';
|
|
25
|
+
import { type ActionProperties } from '../types';
|
|
26
|
+
|
|
27
|
+
export type NavTreeItemActionMenuProps = ActionProperties & {
|
|
28
|
+
parent: Node;
|
|
29
|
+
caller?: string;
|
|
30
|
+
monolithic?: boolean;
|
|
31
|
+
menuActions?: Action[];
|
|
32
|
+
suppressNextTooltip?: MutableRefObject<boolean>;
|
|
33
|
+
menuOpen?: boolean;
|
|
34
|
+
defaultMenuOpen?: boolean;
|
|
35
|
+
onChangeMenuOpen?: (nextOpen: boolean) => void;
|
|
36
|
+
onAction?: (action: Action) => void;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const getShortcut = (action: ActionLike) => {
|
|
40
|
+
return typeof action.properties?.keyBinding === 'string'
|
|
41
|
+
? action.properties.keyBinding
|
|
42
|
+
: action.properties?.keyBinding?.[getHostPlatform()];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const fallbackIcon = 'ph--placeholder--regular';
|
|
46
|
+
|
|
47
|
+
export const NavTreeItemActionDropdownMenu = ({
|
|
48
|
+
label,
|
|
49
|
+
icon,
|
|
50
|
+
testId,
|
|
51
|
+
defaultMenuOpen,
|
|
52
|
+
menuActions,
|
|
53
|
+
menuOpen,
|
|
54
|
+
suppressNextTooltip,
|
|
55
|
+
onChangeMenuOpen,
|
|
56
|
+
onAction,
|
|
57
|
+
}: NavTreeItemActionMenuProps) => {
|
|
58
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
59
|
+
|
|
60
|
+
const [optionsMenuOpen, setOptionsMenuOpen] = useControllableState({
|
|
61
|
+
prop: menuOpen,
|
|
62
|
+
defaultProp: defaultMenuOpen,
|
|
63
|
+
onChange: onChangeMenuOpen,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<DropdownMenu.Root
|
|
68
|
+
{...{
|
|
69
|
+
open: optionsMenuOpen,
|
|
70
|
+
onOpenChange: (nextOpen: boolean) => {
|
|
71
|
+
if (!nextOpen && suppressNextTooltip) {
|
|
72
|
+
suppressNextTooltip.current = true;
|
|
73
|
+
}
|
|
74
|
+
return setOptionsMenuOpen(nextOpen);
|
|
75
|
+
},
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<Tooltip.Trigger asChild>
|
|
79
|
+
<DropdownMenu.Trigger asChild>
|
|
80
|
+
<Button
|
|
81
|
+
variant='ghost'
|
|
82
|
+
density='fine'
|
|
83
|
+
classNames={mx('shrink-0 !pli-2 pointer-fine:!pli-1', hoverableControlItem, hoverableOpenControlItem)}
|
|
84
|
+
data-testid={testId}
|
|
85
|
+
aria-label={t('tree item actions label')}
|
|
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>
|
|
126
|
+
</DropdownMenu.Root>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
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
|
+
export const NavTreeItemMonolithicAction = ({
|
|
291
|
+
parent,
|
|
292
|
+
properties: { disabled, caller, testId, label, icon } = { label: 'never' },
|
|
293
|
+
data: invoke,
|
|
294
|
+
}: Action & { parent: Node; onAction?: (action: Action) => void }) => {
|
|
295
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
296
|
+
return (
|
|
297
|
+
<Tooltip.Trigger asChild>
|
|
298
|
+
<Button
|
|
299
|
+
variant='ghost'
|
|
300
|
+
density='fine'
|
|
301
|
+
classNames={mx('shrink-0 !pli-2 pointer-fine:!pli-1', hoverableControlItem, hoverableOpenControlItem)}
|
|
302
|
+
disabled={disabled}
|
|
303
|
+
onClick={(event) => {
|
|
304
|
+
if (disabled) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
event.stopPropagation();
|
|
308
|
+
void invoke?.(caller ? { node: parent, caller } : { node: parent });
|
|
309
|
+
}}
|
|
310
|
+
data-testid={testId}
|
|
311
|
+
>
|
|
312
|
+
<span className='sr-only'>{toLocalizedString(label, t)}</span>
|
|
313
|
+
<Icon icon={icon ?? fallbackIcon} size={4} />
|
|
314
|
+
</Button>
|
|
315
|
+
</Tooltip.Trigger>
|
|
316
|
+
);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
|
|
320
|
+
const { t } = useTranslation(NAVTREE_PLUGIN);
|
|
321
|
+
const suppressNextTooltip = useRef<boolean>(false);
|
|
322
|
+
const [triggerTooltipOpen, setTriggerTooltipOpen] = useState(false);
|
|
323
|
+
|
|
324
|
+
const monolithicAction = menuActions?.length === 1 && menuActions[0];
|
|
325
|
+
const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
|
|
326
|
+
|
|
327
|
+
return (
|
|
328
|
+
<Tooltip.Root
|
|
329
|
+
open={triggerTooltipOpen}
|
|
330
|
+
onOpenChange={(nextOpen) => {
|
|
331
|
+
if (suppressNextTooltip.current) {
|
|
332
|
+
setTriggerTooltipOpen(false);
|
|
333
|
+
suppressNextTooltip.current = false;
|
|
334
|
+
} else {
|
|
335
|
+
setTriggerTooltipOpen(nextOpen);
|
|
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>
|
|
366
|
+
);
|
|
367
|
+
};
|
package/src/components/index.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import '@dxos-theme';
|
|
6
6
|
|
|
7
7
|
import { House, List, Planet, PlusCircle, Sailboat } from '@phosphor-icons/react';
|
|
8
|
+
import { type Meta } from '@storybook/react';
|
|
8
9
|
import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
|
|
9
10
|
|
|
10
11
|
import { faker } from '@dxos/random';
|
|
@@ -15,12 +16,6 @@ import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visi
|
|
|
15
16
|
|
|
16
17
|
faker.seed(1234);
|
|
17
18
|
|
|
18
|
-
export default {
|
|
19
|
-
title: 'plugin-navtree/Tree',
|
|
20
|
-
component: Tree,
|
|
21
|
-
decorators: [withTheme, withLayout({ fullscreen: true, classNames: modalSurface })],
|
|
22
|
-
};
|
|
23
|
-
|
|
24
19
|
/**
|
|
25
20
|
* Space:
|
|
26
21
|
* - Space panels are separated from each other to give them more weight than folders.
|
|
@@ -47,11 +42,11 @@ const Container = ({ children, sidebar }: PropsWithChildren<{ sidebar: JSX.Eleme
|
|
|
47
42
|
const data: TreeNodeData[] = [
|
|
48
43
|
{
|
|
49
44
|
id: 'root',
|
|
50
|
-
title: 'Root',
|
|
45
|
+
title: 'plugins/Root',
|
|
51
46
|
children: [
|
|
52
47
|
{
|
|
53
48
|
id: faker.string.uuid(),
|
|
54
|
-
title: 'Personal Space',
|
|
49
|
+
title: 'plugins/Personal Space',
|
|
55
50
|
color: 'text-green-400',
|
|
56
51
|
Icon: House,
|
|
57
52
|
children: [
|
|
@@ -227,3 +222,11 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
|
|
|
227
222
|
</div>
|
|
228
223
|
);
|
|
229
224
|
};
|
|
225
|
+
|
|
226
|
+
const meta: Meta = {
|
|
227
|
+
title: 'plugins/plugin-navtree/experimental/Tree',
|
|
228
|
+
component: Tree,
|
|
229
|
+
decorators: [withTheme, withLayout({ fullscreen: true, classNames: modalSurface })],
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export default meta;
|
|
@@ -6,7 +6,7 @@ import { CaretRight, Circle, File, Folder, type Icon, type IconProps, Plus, User
|
|
|
6
6
|
import React, { type HTMLAttributes, type PropsWithChildren } from 'react';
|
|
7
7
|
|
|
8
8
|
import { type ClassNameValue, type Size } from '@dxos/react-ui';
|
|
9
|
-
import { mx } from '@dxos/react-ui-theme';
|
|
9
|
+
import { getSize, mx } from '@dxos/react-ui-theme';
|
|
10
10
|
|
|
11
11
|
// TODO(burdon): Rounded border if first/last.
|
|
12
12
|
const styles = {
|
|
@@ -29,7 +29,7 @@ export const IconButton = ({
|
|
|
29
29
|
// TODO(burdon): Density aware.
|
|
30
30
|
return (
|
|
31
31
|
<div className={mx('flex w-6 h-6 items-center justify-center select-none', classNames)} onClick={onClick}>
|
|
32
|
-
<Icon
|
|
32
|
+
<Icon className={mx('cursor-pointer', getSize(size))} {...props} />
|
|
33
33
|
</div>
|
|
34
34
|
);
|
|
35
35
|
};
|
package/src/meta.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type PluginMeta } from '@dxos/app-framework';
|
|
6
|
+
|
|
5
7
|
export const NAVTREE_PLUGIN = 'dxos.org/plugin/navtree';
|
|
6
8
|
|
|
7
9
|
// TODO(wittjosiah): Factor out.
|
|
@@ -9,4 +11,4 @@ export const KEY_BINDING = 'KeyBinding';
|
|
|
9
11
|
|
|
10
12
|
export default {
|
|
11
13
|
id: NAVTREE_PLUGIN,
|
|
12
|
-
};
|
|
14
|
+
} satisfies PluginMeta;
|