@dxos/plugin-navtree 0.8.4-main.dedc0f3 → 0.8.4-main.e8ec1fe

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.
Files changed (91) hide show
  1. package/dist/lib/browser/{app-graph-builder-6JDBQ25U.mjs → app-graph-builder-OHSCN3GX.mjs} +11 -10
  2. package/dist/lib/browser/app-graph-builder-OHSCN3GX.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-WCARAZ7J.mjs → chunk-5VLWX4PK.mjs} +4 -4
  4. package/dist/lib/browser/chunk-5VLWX4PK.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-ZKVY35IG.mjs → chunk-7D2MPHXV.mjs} +125 -111
  6. package/dist/lib/browser/chunk-7D2MPHXV.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-E6MZBCU5.mjs +19 -0
  8. package/dist/lib/browser/chunk-E6MZBCU5.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +5 -7
  10. package/dist/lib/browser/index.mjs.map +3 -3
  11. package/dist/lib/browser/{intent-resolver-N35ZIHFD.mjs → intent-resolver-YUVXVPHG.mjs} +6 -6
  12. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +7 -0
  13. package/dist/lib/browser/{keyboard-HMTMA33N.mjs → keyboard-YYTYJW4M.mjs} +4 -4
  14. package/dist/lib/browser/{keyboard-HMTMA33N.mjs.map → keyboard-YYTYJW4M.mjs.map} +1 -1
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/{react-surface-5PZ4XJT7.mjs → react-surface-J7MWWAGD.mjs} +10 -10
  17. package/dist/lib/browser/react-surface-J7MWWAGD.mjs.map +7 -0
  18. package/dist/lib/browser/{state-P5DGL7KS.mjs → state-EUSVT6BI.mjs} +7 -7
  19. package/dist/lib/browser/state-EUSVT6BI.mjs.map +7 -0
  20. package/dist/types/src/NavTreePlugin.d.ts +1 -1
  21. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  23. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  24. package/dist/types/src/capabilities/index.d.ts +12 -9
  25. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  26. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  27. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
  28. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  29. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  30. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +1 -2
  31. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  32. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  33. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  34. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +1 -2
  35. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  36. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +1 -1
  37. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  38. package/dist/types/src/components/Sidebar/L0Menu.d.ts +2 -0
  39. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  40. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  41. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  42. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +1 -2
  43. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  44. package/dist/types/src/events.d.ts.map +1 -1
  45. package/dist/types/src/experimental/Tree.d.ts +1 -2
  46. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  47. package/dist/types/src/experimental/Tree.stories.d.ts +2 -3
  48. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  49. package/dist/types/src/meta.d.ts +2 -3
  50. package/dist/types/src/meta.d.ts.map +1 -1
  51. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  52. package/dist/types/src/testing/generator.d.ts +1 -1
  53. package/dist/types/src/testing/generator.d.ts.map +1 -1
  54. package/dist/types/src/types.d.ts +4 -3
  55. package/dist/types/src/types.d.ts.map +1 -1
  56. package/dist/types/tsconfig.tsbuildinfo +1 -1
  57. package/package.json +48 -48
  58. package/src/NavTreePlugin.tsx +70 -71
  59. package/src/capabilities/app-graph-builder.ts +8 -7
  60. package/src/capabilities/capabilities.ts +2 -2
  61. package/src/capabilities/intent-resolver.ts +1 -1
  62. package/src/capabilities/react-surface.tsx +4 -4
  63. package/src/capabilities/state.ts +2 -2
  64. package/src/components/CommandsDialog/CommandsDialog.tsx +14 -6
  65. package/src/components/CommandsDialog/CommandsTrigger.tsx +4 -3
  66. package/src/components/NavTree/NavTree.stories.tsx +63 -49
  67. package/src/components/NavTree/NavTree.tsx +4 -0
  68. package/src/components/NavTreeContainer.tsx +51 -21
  69. package/src/components/NavTreeDocumentTitle.tsx +2 -2
  70. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +2 -4
  71. package/src/components/NavTreeItem/NavTreeItemAction.tsx +5 -4
  72. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +27 -21
  73. package/src/components/Sidebar/L0Menu.tsx +54 -53
  74. package/src/components/Sidebar/L1Panel.tsx +7 -10
  75. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +3 -4
  76. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  77. package/src/events.ts +2 -2
  78. package/src/experimental/Tree.stories.tsx +26 -30
  79. package/src/experimental/Tree.tsx +6 -23
  80. package/src/meta.ts +10 -7
  81. package/src/testing/app-graph-builder.ts +19 -18
  82. package/src/testing/generator.ts +2 -2
  83. package/src/types.ts +4 -3
  84. package/dist/lib/browser/app-graph-builder-6JDBQ25U.mjs.map +0 -7
  85. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  86. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  87. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  88. package/dist/lib/browser/chunk-ZKVY35IG.mjs.map +0 -7
  89. package/dist/lib/browser/intent-resolver-N35ZIHFD.mjs.map +0 -7
  90. package/dist/lib/browser/react-surface-5PZ4XJT7.mjs.map +0 -7
  91. package/dist/lib/browser/state-P5DGL7KS.mjs.map +0 -7
@@ -47,6 +47,10 @@ export const NavTree = ({ id, root, ...props }: NavTreeProps) => {
47
47
  const path = useMemo(() => [id], [id]);
48
48
 
49
49
  return (
50
+ // TODO(thure): `Tabs.Root` forces all items that should be able to receive focus to use `Tabs.Tab(Primitive)` since
51
+ // it uses RovingFocus and doesn’t support moving focus to an item that is not a tab. Assess whether this situation
52
+ // should change including whether it should motivate a change in the design/taxonomy, or if this means this should
53
+ // not use `react-ui-tabs` at all.
50
54
  <Tabs.Root value={tab} orientation='vertical' verticalVariant='stateless' classNames='relative'>
51
55
  <L0Menu
52
56
  menuActions={topLevelActions}
@@ -7,15 +7,8 @@ import { type Instruction, extractInstruction } from '@atlaskit/pragmatic-drag-a
7
7
  import { untracked } from '@preact/signals-core';
8
8
  import React, { memo, useCallback, useEffect, useMemo } from 'react';
9
9
 
10
- import {
11
- LayoutAction,
12
- Surface,
13
- createIntent,
14
- useAppGraph,
15
- useCapability,
16
- useIntentDispatcher,
17
- useLayout,
18
- } from '@dxos/app-framework';
10
+ import { LayoutAction, createIntent } from '@dxos/app-framework';
11
+ import { Surface, useAppGraph, useCapability, useIntentDispatcher, useLayout } from '@dxos/app-framework/react';
19
12
  import { type Node, ROOT_ID, type ReadableGraph, isAction, isActionLike } from '@dxos/app-graph';
20
13
  import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
21
14
  import { useConnections, useActions as useGraphActions } from '@dxos/plugin-graph';
@@ -25,7 +18,7 @@ import { mx } from '@dxos/react-ui-theme';
25
18
  import { arrayMove, byPosition } from '@dxos/util';
26
19
 
27
20
  import { NavTreeCapabilities } from '../capabilities';
28
- import { NAVTREE_PLUGIN } from '../meta';
21
+ import { meta } from '../meta';
29
22
  import { type FlattenedActions, type NavTreeItemGraphNode } from '../types';
30
23
  import { getChildren, getParent, resolveMigrationOperation } from '../util';
31
24
 
@@ -98,12 +91,12 @@ export type NavTreeContainerProps = {
98
91
  } & Pick<NavTreeContextValue, 'tab'>;
99
92
 
100
93
  export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeContainerProps) => {
101
- const [isLg] = useMediaQuery('lg', { ssr: false });
94
+ const [isLg] = useMediaQuery('lg');
102
95
  const { dispatchPromise: dispatch } = useIntentDispatcher();
103
96
  const { graph } = useAppGraph();
104
97
  const { isOpen, isCurrent, isAlternateTree, setItem } = useCapability(NavTreeCapabilities.State);
105
98
  const layout = useLayout();
106
- const { navigationSidebarState } = useSidebars(NAVTREE_PLUGIN);
99
+ const { navigationSidebarState } = useSidebars(meta.id);
107
100
 
108
101
  const getProps = useCallback(
109
102
  (node: Node, path: string[]): TreeItemDataProps => {
@@ -112,12 +105,13 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
112
105
  children.length > 0 ? children.map(({ id }) => id) : node.properties.role === 'branch' ? [] : undefined;
113
106
  return {
114
107
  id: node.id,
115
- label: node.properties.label ?? node.id,
116
108
  parentOf,
117
- icon: node.properties.icon,
118
109
  disabled: node.properties.disabled,
119
- className: mx(node.properties.modified && 'italic', node.properties.className),
110
+ label: node.properties.label ?? node.id,
111
+ className: mx(node.properties.className, node.properties.modified && 'italic'), // TODO(burdon): Italic?
120
112
  headingClassName: node.properties.headingClassName,
113
+ icon: node.properties.icon,
114
+ iconHue: node.properties.iconHue,
121
115
  testId: node.properties.testId,
122
116
  };
123
117
  },
@@ -162,13 +156,23 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
162
156
  }),
163
157
  );
164
158
 
165
- await dispatch(createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: node.id }));
159
+ await dispatch(
160
+ createIntent(LayoutAction.SwitchWorkspace, {
161
+ part: 'workspace',
162
+ subject: node.id,
163
+ }),
164
+ );
166
165
 
167
166
  // Open the first item if the workspace is empty.
168
167
  if (layout.active.length === 0) {
169
168
  const [item] = getItems(graph, node).filter((node) => !isActionLike(node));
170
169
  if (item && item.data) {
171
- await dispatch(createIntent(LayoutAction.Open, { part: 'main', subject: [item.id] }));
170
+ await dispatch(
171
+ createIntent(LayoutAction.Open, {
172
+ part: 'main',
173
+ subject: [item.id],
174
+ }),
175
+ );
172
176
  }
173
177
  }
174
178
  },
@@ -179,6 +183,10 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
179
183
  return target.item.properties.canDrop?.(source) ?? false;
180
184
  }, []);
181
185
 
186
+ const canSelect = useCallback(({ item }: { item: Node }) => {
187
+ return item.properties.selectable ?? true;
188
+ }, []);
189
+
182
190
  const handleSelect = useCallback(
183
191
  ({ item: node, path, option }: { item: Node; path: string[]; option: boolean }) => {
184
192
  if (!node.data) {
@@ -202,10 +210,19 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
202
210
  );
203
211
  } else if (option) {
204
212
  void dispatch(
205
- createIntent(LayoutAction.Close, { part: 'main', subject: [node.id], options: { state: false } }),
213
+ createIntent(LayoutAction.Close, {
214
+ part: 'main',
215
+ subject: [node.id],
216
+ options: { state: false },
217
+ }),
206
218
  );
207
219
  } else {
208
- void dispatch(createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: node.id }));
220
+ void dispatch(
221
+ createIntent(LayoutAction.ScrollIntoView, {
222
+ part: 'current',
223
+ subject: node.id,
224
+ }),
225
+ );
209
226
  }
210
227
 
211
228
  const defaultAction = graph.getActions(node.id).find((action) => action.properties?.disposition === 'default');
@@ -214,14 +231,25 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
214
231
  }
215
232
 
216
233
  if (!isLg) {
217
- void dispatch(createIntent(LayoutAction.UpdateSidebar, { part: 'sidebar', options: { state: 'closed' } }));
234
+ void dispatch(
235
+ createIntent(LayoutAction.UpdateSidebar, {
236
+ part: 'sidebar',
237
+ options: { state: 'closed' },
238
+ }),
239
+ );
218
240
  }
219
241
  },
220
242
  [graph, dispatch, isCurrent, isLg],
221
243
  );
222
244
 
223
245
  const handleBack = useCallback(
224
- () => dispatch(createIntent(LayoutAction.RevertWorkspace, { part: 'workspace', options: { revert: true } })),
246
+ () =>
247
+ dispatch(
248
+ createIntent(LayoutAction.RevertWorkspace, {
249
+ part: 'workspace',
250
+ options: { revert: true },
251
+ }),
252
+ ),
225
253
  [dispatch],
226
254
  );
227
255
 
@@ -304,6 +332,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
304
332
  isCurrent,
305
333
  isOpen,
306
334
  canDrop,
335
+ canSelect,
307
336
  isAlternateTree,
308
337
  setAlternateTree,
309
338
  onTabChange: handleTabChange,
@@ -322,6 +351,7 @@ export const NavTreeContainer = memo(({ tab, popoverAnchorId, topbar }: NavTreeC
322
351
  isCurrent,
323
352
  isOpen,
324
353
  canDrop,
354
+ canSelect,
325
355
  isAlternateTree,
326
356
  setAlternateTree,
327
357
  handleTabChange,
@@ -6,10 +6,10 @@ import { useEffect } from 'react';
6
6
  import { type Node } from '@dxos/app-graph';
7
7
  import { toLocalizedString, useTranslation } from '@dxos/react-ui';
8
8
 
9
- import { NAVTREE_PLUGIN } from '../meta';
9
+ import { meta } from '../meta';
10
10
 
11
11
  export const NavTreeDocumentTitle = ({ node }: { node?: Node }) => {
12
- const { t } = useTranslation(NAVTREE_PLUGIN);
12
+ const { t } = useTranslation(meta.id);
13
13
  useEffect(() => {
14
14
  document.title = node ? toLocalizedString(node.properties.label, t) : t('current app name', { ns: 'appkit' });
15
15
  }, [node?.properties?.label]);
@@ -2,13 +2,11 @@
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
7
  import { type NodeArg } from '@dxos/app-graph';
10
8
  import { faker } from '@dxos/random';
11
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { withTheme } from '@dxos/react-ui/testing';
12
10
 
13
11
  import { NavTreeItemAction, type NavTreeItemActionMenuProps } from './NavTreeItemAction';
14
12
 
@@ -42,13 +40,13 @@ const menuActions = faker.helpers.multiple(
42
40
  const meta = {
43
41
  title: 'plugins/plugin-navtree/NavTreeItemAction',
44
42
  component: NavTreeItemAction,
45
- decorators: [withTheme, withLayout()],
46
43
  args: {
47
44
  icon: 'ph--list--regular',
48
45
  parent,
49
46
  menuActions,
50
47
  label: 'Select action',
51
48
  } satisfies Partial<NavTreeItemActionMenuProps>,
49
+ decorators: [withTheme],
52
50
  parameters: {
53
51
  layout: 'centered',
54
52
  },
@@ -9,7 +9,7 @@ import { IconButton, toLocalizedString, useDensityContext, useTranslation } from
9
9
  import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
10
10
  import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
11
11
 
12
- import { NAVTREE_PLUGIN } from '../../meta';
12
+ import { meta } from '../../meta';
13
13
  import { type ActionProperties } from '../../types';
14
14
 
15
15
  export type NavTreeItemActionMenuProps = ActionProperties & {
@@ -38,7 +38,7 @@ export const NavTreeItemActionDropdownMenu = ({
38
38
  menuActions,
39
39
  caller,
40
40
  }: NavTreeItemActionMenuProps) => {
41
- const { t } = useTranslation(NAVTREE_PLUGIN);
41
+ const { t } = useTranslation(meta.id);
42
42
  const density = useDensityContext();
43
43
  return (
44
44
  <MenuProvider>
@@ -81,10 +81,11 @@ export const NavTreeItemMonolithicAction = ({
81
81
  label={baseLabel}
82
82
  disabled={disabled}
83
83
  onClick={(event) => {
84
+ event.stopPropagation();
84
85
  if (disabled) {
85
86
  return;
86
87
  }
87
- event.stopPropagation();
88
+
88
89
  void invoke?.(caller ? { parent, caller } : { parent });
89
90
  }}
90
91
  data-testid={testId}
@@ -93,7 +94,7 @@ export const NavTreeItemMonolithicAction = ({
93
94
  };
94
95
 
95
96
  export const NavTreeItemAction = ({ monolithic, menuActions, parent: node, ...props }: NavTreeItemActionMenuProps) => {
96
- const { t } = useTranslation(NAVTREE_PLUGIN);
97
+ const { t } = useTranslation(meta.id);
97
98
 
98
99
  const monolithicAction = menuActions?.length === 1 && menuActions[0];
99
100
  const baseLabel = toLocalizedString(monolithicAction ? monolithicAction.properties!.label : props.label, t);
@@ -5,18 +5,18 @@
5
5
  import React, { Fragment, memo } from 'react';
6
6
 
7
7
  import { isAction } from '@dxos/app-graph';
8
- import { DensityProvider, Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
+ import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
10
  import { useLoadDescendents } from '../../hooks';
11
- import { NAVTREE_PLUGIN } from '../../meta';
11
+ import { meta } from '../../meta';
12
12
  import { NAV_TREE_ITEM } from '../NavTree';
13
13
  import { useNavTreeContext } from '../NavTreeContext';
14
14
  import { type NavTreeItemColumnsProps } from '../types';
15
15
 
16
16
  import { NavTreeItemAction } from './NavTreeItemAction';
17
17
 
18
- export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }: NavTreeItemColumnsProps) => {
19
- const { t } = useTranslation(NAVTREE_PLUGIN);
18
+ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
+ const { t } = useTranslation(meta.id);
20
20
  const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
21
21
 
22
22
  const level = path.length - 2;
@@ -29,15 +29,15 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
29
29
  .flatMap((action) => (isAction(action) ? [action] : []))
30
30
  .filter((a) => ['list-item', 'list-item-primary'].includes(a.properties?.disposition));
31
31
 
32
- const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
33
-
34
32
  useLoadDescendents(item);
35
33
  useLoadDescendents(primaryAction && !isAction(primaryAction) ? primaryAction : undefined);
36
34
 
35
+ const ActionRoot = popoverAnchorId === `dxos.org/ui/${NAV_TREE_ITEM}/${item.id}` ? Popover.Anchor : Fragment;
36
+
37
37
  return (
38
- <DensityProvider density={density}>
39
- <div role='none' className='contents app-no-drag'>
40
- {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
38
+ <div role='none' className='contents app-no-drag'>
39
+ {primaryAction?.properties?.disposition === 'list-item-primary' && !primaryAction?.properties?.disabled ? (
40
+ <Treegrid.Cell classNames='contents'>
41
41
  <NavTreeItemAction
42
42
  testId={primaryAction.properties?.testId}
43
43
  label={toLocalizedString(primaryAction.properties?.label, t)}
@@ -48,11 +48,13 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
48
48
  menuType={primaryAction.properties?.menuType}
49
49
  caller={NAV_TREE_ITEM}
50
50
  />
51
- ) : (
52
- <Treegrid.Cell />
53
- )}
54
- <ActionRoot>
55
- {actions.length > 0 ? (
51
+ </Treegrid.Cell>
52
+ ) : (
53
+ <Treegrid.Cell role='none' />
54
+ )}
55
+ <ActionRoot>
56
+ {actions.length > 0 ? (
57
+ <Treegrid.Cell classNames='contents'>
56
58
  <NavTreeItemAction
57
59
  testId={`navtree.treeItem.actionsLevel${level}`}
58
60
  label={t('tree item actions label')}
@@ -62,12 +64,16 @@ export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }:
62
64
  menuType='dropdown'
63
65
  caller={NAV_TREE_ITEM}
64
66
  />
65
- ) : (
66
- <Treegrid.Cell />
67
- )}
68
- </ActionRoot>
69
- {ItemEnd && <ItemEnd node={item} open={open} />}
70
- </div>
71
- </DensityProvider>
67
+ </Treegrid.Cell>
68
+ ) : (
69
+ <Treegrid.Cell role='none' />
70
+ )}
71
+ </ActionRoot>
72
+ {ItemEnd && (
73
+ <Treegrid.Cell classNames='contents'>
74
+ <ItemEnd node={item} open={open} />
75
+ </Treegrid.Cell>
76
+ )}
77
+ </div>
72
78
  );
73
79
  });
@@ -24,15 +24,25 @@ import React, {
24
24
 
25
25
  import { type Node } from '@dxos/app-graph';
26
26
  import { invariant } from '@dxos/invariant';
27
- import { Icon, ListItem, ScrollArea, Tooltip, toLocalizedString, useMediaQuery, useTranslation } from '@dxos/react-ui';
27
+ import { DxAvatar } from '@dxos/lit-ui/react';
28
+ import {
29
+ Icon,
30
+ ListItem,
31
+ ScrollArea,
32
+ type ThemedClassName,
33
+ Tooltip,
34
+ toLocalizedString,
35
+ useMediaQuery,
36
+ useTranslation,
37
+ } from '@dxos/react-ui';
28
38
  import { DropdownMenu, type MenuItem, MenuProvider } from '@dxos/react-ui-menu';
29
39
  import type { StackItemRearrangeHandler } from '@dxos/react-ui-stack';
30
40
  import { Tabs } from '@dxos/react-ui-tabs';
31
41
  import { mx } from '@dxos/react-ui-theme';
32
- import { arrayMove, getFirstTwoRenderableChars } from '@dxos/util';
42
+ import { arrayMove } from '@dxos/util';
33
43
 
34
44
  import { useLoadDescendents } from '../../hooks';
35
- import { NAVTREE_PLUGIN } from '../../meta';
45
+ import { meta } from '../../meta';
36
46
  import { l0ItemType } from '../../util';
37
47
  import { useNavTreeContext } from '../NavTreeContext';
38
48
  import { UserAccountAvatar } from '../UserAccountAvatar';
@@ -62,7 +72,7 @@ type L0ItemProps = L0ItemRootProps & {
62
72
 
63
73
  const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
64
74
  const { tab, isCurrent, onSelect, onTabChange } = useNavTreeContext();
65
- const [isLg] = useMediaQuery('lg', { ssr: false });
75
+ const [isLg] = useMediaQuery('lg');
66
76
 
67
77
  return useCallback(
68
78
  (event: MouseEvent) => {
@@ -92,7 +102,7 @@ const l0Breakpoints: Record<string, string> = {
92
102
  };
93
103
 
94
104
  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';
105
+ 'group/l0item flex is-full justify-center items-center relative data[type!="collection"]:cursor-pointer app-no-drag dx-focus-ring-group';
96
106
 
97
107
  const l0ItemContent = 'flex justify-center items-center dx-focus-ring-group-indicator transition-colors rounded-sm';
98
108
 
@@ -103,55 +113,45 @@ const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
103
113
  const type = l0ItemType(item);
104
114
  const itemPath = useMemo(() => [...path, item.id], [item.id, path]);
105
115
 
106
- const { t } = useTranslation(NAVTREE_PLUGIN);
116
+ const { t } = useTranslation(meta.id);
107
117
  const localizedString = toLocalizedString(item.properties.label, t);
108
118
 
109
119
  const handleClick = useL0ItemClick({ item, parent, path: itemPath }, type);
110
120
  const rootProps =
111
121
  type === 'tab'
112
- ? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-itemid': id }
122
+ ? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
113
123
  : type !== 'collection'
114
- ? { onClick: handleClick, 'data-testid': testId, 'data-itemid': id }
115
- : { onClick: handleClick };
116
-
117
- const Root = type === 'collection' ? 'h2' : type === 'tab' ? Tabs.TabPrimitive : 'button';
124
+ ? { onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
125
+ : { onClick: handleClick, role: 'button' };
118
126
 
119
127
  return (
120
128
  <Tooltip.Trigger asChild delayDuration={0} side='right' content={localizedString}>
121
- <Root
129
+ <Tabs.TabPrimitive
122
130
  {...(rootProps as any)}
123
131
  data-type={type}
124
132
  className={mx(l0ItemRoot, l0Breakpoints[item.properties.l0Breakpoint])}
125
133
  ref={forwardedRef}
126
134
  >
127
135
  {children}
128
- </Root>
136
+ </Tabs.TabPrimitive>
129
137
  </Tooltip.Trigger>
130
138
  );
131
139
  },
132
140
  );
133
141
 
134
- const L0Avatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
135
- const { t } = useTranslation(NAVTREE_PLUGIN);
136
- const type = l0ItemType(item);
137
- const hue = item.properties.hue ?? null;
138
- const hueFgStyle = hue && { style: { color: `var(--dx-${hue}SurfaceText)` } };
139
- const localizedString = toLocalizedString(item.properties.label, t);
140
- const avatarValue = useMemo(
141
- () => (type === 'tab' ? getFirstTwoRenderableChars(localizedString).join('').toUpperCase() : []),
142
- [type, item.properties.label, t],
143
- );
144
-
145
- return (
146
- <span role='img' className='place-self-center text-xl font-light' {...hueFgStyle}>
147
- {avatarValue}
148
- </span>
149
- );
150
- };
142
+ export const L0ItemActiveTabIndicator = ({ classNames }: ThemedClassName<{}>) => (
143
+ <div
144
+ role='none'
145
+ className={mx(
146
+ 'hidden group-aria-selected/l0item:block absolute inline-start-0 inset-block-2 is-1 bg-accentSurface rounded-ie',
147
+ classNames,
148
+ )}
149
+ />
150
+ );
151
151
 
152
152
  // TODO(burdon): Factor out pinned (non-draggable) items.
153
153
  const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
154
- const { t } = useTranslation(NAVTREE_PLUGIN);
154
+ const { t } = useTranslation(meta.id);
155
155
  const itemElement = useRef<HTMLElement | null>(null);
156
156
  const [closestEdge, setEdge] = useState<Edge | null>(null);
157
157
  const localizedString = toLocalizedString(item.properties.label, t);
@@ -228,10 +228,7 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
228
228
  >
229
229
  <ItemAvatar item={item} />
230
230
  </div>
231
- <div
232
- role='none'
233
- className='hidden group-aria-selected/l0item:block absolute inline-start-0 inset-block-2 is-1 bg-accentSurface rounded-ie'
234
- />
231
+ <L0ItemActiveTabIndicator />
235
232
  <span id={`${item.id}__label`} className='sr-only'>
236
233
  {localizedString}
237
234
  </span>
@@ -241,15 +238,18 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
241
238
  };
242
239
 
243
240
  const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
241
+ const { t } = useTranslation(meta.id);
244
242
  const type = l0ItemType(item);
245
243
  if (item.properties.icon) {
246
244
  const hue = item.properties.hue ?? null;
247
245
  const hueFgStyle = hue && { style: { color: `var(--dx-${hue}SurfaceText)` } };
248
- return <Icon icon={item.properties.icon} size={5} {...hueFgStyle} />;
246
+ return <Icon icon={item.properties.icon} size={6} {...hueFgStyle} />;
249
247
  }
250
248
 
251
249
  if (type === 'tab' && item.properties.disposition !== 'pin-end') {
252
- return <L0Avatar item={item} />;
250
+ const hue = item.properties.hue ?? null;
251
+ const localizedString = toLocalizedString(item.properties.label, t);
252
+ return <DxAvatar hue={hue} hueVariant='surface' variant='square' size={12} fallback={localizedString} />;
253
253
  }
254
254
 
255
255
  return null;
@@ -287,7 +287,7 @@ const L0Collection = ({ item, path }: L0ItemProps) => {
287
287
  role='group'
288
288
  className='contents group/l0c'
289
289
  aria-labelledby={`${item.id}__label`}
290
- data-itemid={id}
290
+ data-object-id={id}
291
291
  data-testid={testId}
292
292
  >
293
293
  {collectionItems.map((collectionItem) => (
@@ -317,7 +317,7 @@ export type L0MenuProps = {
317
317
  };
318
318
 
319
319
  export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
320
- const { t } = useTranslation(NAVTREE_PLUGIN);
320
+ const { t } = useTranslation(meta.id);
321
321
 
322
322
  return (
323
323
  <Tabs.Tablist
@@ -331,20 +331,22 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
331
331
  <MenuProvider>
332
332
  <DropdownMenu.Root group={parent} items={menuActions}>
333
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
- )}
334
+ <Tabs.TabPrimitive value='options' asChild role='button'>
335
+ <DropdownMenu.Trigger
336
+ data-testid='spacePlugin.addSpace'
337
+ className={mx(l0ItemRoot, 'grid place-items-center dx-focus-ring-group')}
344
338
  >
345
- <Icon icon='ph--list--regular' size={5} />
346
- </div>
347
- </DropdownMenu.Trigger>
339
+ <div
340
+ role='none'
341
+ className={mx(
342
+ l0ItemContent,
343
+ 'is-[--rail-action] bs-[--rail-action] group-hover/l0item:bg-hoverSurface',
344
+ )}
345
+ >
346
+ <Icon icon='ph--list--regular' size={5} />
347
+ </div>
348
+ </DropdownMenu.Trigger>
349
+ </Tabs.TabPrimitive>
348
350
  </Tooltip.Trigger>
349
351
  </DropdownMenu.Root>
350
352
  </MenuProvider>
@@ -396,7 +398,6 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
396
398
  </L0ItemRoot>
397
399
  </div>
398
400
  )}
399
-
400
401
  <div
401
402
  role='none'
402
403
  className='hidden [body[data-platform="darwin"]_&]:block absolute block-start-0 is-[calc(var(--l0-size)-1px)] bs-[calc(40px+0.25rem)]'
@@ -5,7 +5,7 @@
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
- import { IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
+ import { DensityProvider, IconButton, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
  import { Tree } from '@dxos/react-ui-list';
10
10
  import { Tabs } from '@dxos/react-ui-tabs';
11
11
  import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
@@ -28,11 +28,10 @@ export type L1PanelProps = {
28
28
  export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProps) => {
29
29
  const { t } = useTranslation(meta.id);
30
30
  const { isAlternateTree, ...navTreeContext } = useNavTreeContext();
31
- // NOTE(burdon): I'm very uneasy about this pattern of declaring hooks as context props.
32
- const { useItems } = navTreeContext;
33
31
  const title = toLocalizedString(item.properties.label, t);
34
32
 
35
33
  // TODO(wittjosiah): Support multiple alternate trees.
34
+ const { useItems } = navTreeContext;
36
35
  const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
37
36
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
38
37
  const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
@@ -56,22 +55,22 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
56
55
  ]}
57
56
  tabIndex={-1}
58
57
  aria-label={title}
59
- {...(!open && { inert: 'true' })}
58
+ {...(!open && { inert: true })}
60
59
  >
61
60
  {item.id === currentItemId && (
62
- <>
61
+ <DensityProvider density='fine'>
63
62
  <L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
64
63
  <div role='none' className='overflow-y-auto'>
65
64
  {isAlternate ? (
66
65
  <Tree
67
66
  {...navTreeContext}
68
- useItems={useAlternateItems}
69
67
  id={alternateTree.id}
70
68
  root={alternateTree}
71
69
  path={alternatePath}
72
70
  levelOffset={5}
73
71
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
74
72
  renderColumns={NavTreeItemColumns}
73
+ useItems={useAlternateItems}
75
74
  />
76
75
  ) : (
77
76
  <Tree
@@ -80,13 +79,13 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
80
79
  root={item}
81
80
  path={path}
82
81
  levelOffset={5}
83
- draggable
84
82
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
85
83
  renderColumns={NavTreeItemColumns}
84
+ draggable
86
85
  />
87
86
  )}
88
87
  </div>
89
- </>
88
+ </DensityProvider>
90
89
  )}
91
90
  </Tabs.Tabpanel>
92
91
  );
@@ -128,7 +127,6 @@ const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
128
127
  variant='ghost'
129
128
  icon='ph--arrow-u-down-left--regular'
130
129
  iconOnly
131
- size={5}
132
130
  label={t('button back')}
133
131
  classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
134
132
  onClick={handleBack}
@@ -140,7 +138,6 @@ const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
140
138
  variant='ghost'
141
139
  icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
142
140
  iconOnly
143
- size={5}
144
141
  label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
145
142
  classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
146
143
  onClick={handleOpen}
@@ -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/storybook-utils';
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',
15
- component: UserAccountAvatar,
13
+
16
14
  decorators: [withTheme],
15
+ component: UserAccountAvatar,
17
16
  } satisfies Meta<typeof UserAccountAvatar>;
18
17
 
19
18
  export default meta;