@dxos/plugin-navtree 0.8.4-main.f9ba587 → 0.8.4-main.fffef41

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 (138) hide show
  1. package/dist/lib/browser/{app-graph-builder-5J4LRWDX.mjs → app-graph-builder-OHSCN3GX.mjs} +13 -12
  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-TZA65AKW.mjs → chunk-7D2MPHXV.mjs} +317 -262
  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 +8 -11
  10. package/dist/lib/browser/index.mjs.map +3 -3
  11. package/dist/lib/browser/{intent-resolver-3XYN2CBU.mjs → intent-resolver-YUVXVPHG.mjs} +7 -7
  12. package/dist/lib/browser/intent-resolver-YUVXVPHG.mjs.map +7 -0
  13. package/dist/lib/browser/{keyboard-QRS6WOQH.mjs → keyboard-YYTYJW4M.mjs} +4 -4
  14. package/dist/lib/{node-esm/keyboard-MOZWHFUM.mjs.map → browser/keyboard-YYTYJW4M.mjs.map} +2 -2
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/{react-surface-N2HPMYCU.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-54DY3DOY.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 +1 -1
  23. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  24. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  25. package/dist/types/src/capabilities/index.d.ts +18 -15
  26. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  27. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  28. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/keyboard.d.ts +1 -1
  30. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  31. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  32. package/dist/types/src/capabilities/state.d.ts +2 -2
  33. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  34. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
  35. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  36. package/dist/types/src/components/NavTree/NavTree.d.ts +2 -2
  37. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  38. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +26 -4
  39. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  40. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  41. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  42. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +32 -4
  43. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  44. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +2 -2
  45. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  46. package/dist/types/src/components/Sidebar/L0Menu.d.ts +2 -0
  47. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  48. package/dist/types/src/components/Sidebar/L1Panel.d.ts +5 -6
  49. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  50. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +12 -0
  51. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
  52. package/dist/types/src/components/Sidebar/index.d.ts +1 -0
  53. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -1
  54. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  55. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  56. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  57. package/dist/types/src/components/index.d.ts +1 -1
  58. package/dist/types/src/components/index.d.ts.map +1 -1
  59. package/dist/types/src/components/types.d.ts +2 -2
  60. package/dist/types/src/components/types.d.ts.map +1 -1
  61. package/dist/types/src/events.d.ts.map +1 -1
  62. package/dist/types/src/experimental/Tree.d.ts +4 -5
  63. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  64. package/dist/types/src/experimental/Tree.stories.d.ts +7 -4
  65. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  66. package/dist/types/src/meta.d.ts +2 -3
  67. package/dist/types/src/meta.d.ts.map +1 -1
  68. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  69. package/dist/types/src/testing/generator.d.ts +1 -1
  70. package/dist/types/src/testing/generator.d.ts.map +1 -1
  71. package/dist/types/src/translations.d.ts +1 -0
  72. package/dist/types/src/translations.d.ts.map +1 -1
  73. package/dist/types/src/types.d.ts +4 -3
  74. package/dist/types/src/types.d.ts.map +1 -1
  75. package/dist/types/src/util.d.ts +1 -2
  76. package/dist/types/src/util.d.ts.map +1 -1
  77. package/dist/types/tsconfig.tsbuildinfo +1 -1
  78. package/package.json +49 -50
  79. package/src/NavTreePlugin.tsx +75 -76
  80. package/src/capabilities/app-graph-builder.ts +10 -9
  81. package/src/capabilities/capabilities.ts +2 -2
  82. package/src/capabilities/intent-resolver.ts +2 -2
  83. package/src/capabilities/keyboard.ts +2 -2
  84. package/src/capabilities/react-surface.tsx +5 -5
  85. package/src/capabilities/state.ts +4 -3
  86. package/src/components/CommandsDialog/CommandsDialog.tsx +15 -7
  87. package/src/components/CommandsDialog/CommandsTrigger.tsx +6 -7
  88. package/src/components/NavTree/NavTree.stories.tsx +67 -53
  89. package/src/components/NavTree/NavTree.tsx +7 -3
  90. package/src/components/NavTreeContainer.tsx +91 -36
  91. package/src/components/NavTreeDocumentTitle.tsx +2 -2
  92. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +4 -6
  93. package/src/components/NavTreeItem/NavTreeItemAction.tsx +11 -10
  94. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +33 -27
  95. package/src/components/Sidebar/L0Menu.tsx +56 -55
  96. package/src/components/Sidebar/L1Panel.tsx +72 -97
  97. package/src/components/Sidebar/L1Tabs.tsx +58 -0
  98. package/src/components/Sidebar/index.ts +1 -0
  99. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -8
  100. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  101. package/src/components/index.ts +1 -1
  102. package/src/components/types.ts +3 -2
  103. package/src/events.ts +2 -2
  104. package/src/experimental/Tree.stories.tsx +36 -38
  105. package/src/experimental/Tree.tsx +17 -35
  106. package/src/meta.ts +10 -7
  107. package/src/testing/app-graph-builder.ts +20 -19
  108. package/src/testing/generator.ts +2 -2
  109. package/src/translations.ts +1 -0
  110. package/src/types.ts +4 -3
  111. package/src/util.ts +2 -20
  112. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
  113. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  114. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  115. package/dist/lib/browser/chunk-TZA65AKW.mjs.map +0 -7
  116. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  117. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs.map +0 -7
  118. package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
  119. package/dist/lib/browser/react-surface-N2HPMYCU.mjs.map +0 -7
  120. package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
  121. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs +0 -46
  122. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs.map +0 -7
  123. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs +0 -1429
  124. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs.map +0 -7
  125. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs +0 -18
  126. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs.map +0 -7
  127. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs +0 -16
  128. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs.map +0 -7
  129. package/dist/lib/node-esm/index.mjs +0 -150
  130. package/dist/lib/node-esm/index.mjs.map +0 -7
  131. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs +0 -48
  132. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs.map +0 -7
  133. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs +0 -52
  134. package/dist/lib/node-esm/meta.json +0 -1
  135. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs +0 -52
  136. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs.map +0 -7
  137. package/dist/lib/node-esm/state-Z3OTRC2I.mjs +0 -118
  138. package/dist/lib/node-esm/state-Z3OTRC2I.mjs.map +0 -7
@@ -5,23 +5,23 @@
5
5
  import React, { Fragment, memo } from 'react';
6
6
 
7
7
  import { isAction } from '@dxos/app-graph';
8
- import { DensityProvider, Popover, toLocalizedString, Treegrid, useTranslation } from '@dxos/react-ui';
8
+ import { Popover, Treegrid, toLocalizedString, useTranslation } from '@dxos/react-ui';
9
9
 
10
- import { NavTreeItemAction } from './NavTreeItemAction';
11
10
  import { useLoadDescendents } from '../../hooks';
12
- import { NAVTREE_PLUGIN } from '../../meta';
11
+ import { meta } from '../../meta';
13
12
  import { NAV_TREE_ITEM } from '../NavTree';
14
13
  import { useNavTreeContext } from '../NavTreeContext';
15
- import type { NavTreeItemColumnsProps } from '../types';
14
+ import { type NavTreeItemColumnsProps } from '../types';
16
15
 
17
- export const NavTreeItemColumns = memo(({ path, item, open, density = 'fine' }: NavTreeItemColumnsProps) => {
18
- const { t } = useTranslation(NAVTREE_PLUGIN);
19
- const { getActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
16
+ import { NavTreeItemAction } from './NavTreeItemAction';
20
17
 
21
- const level = path.length - 2;
18
+ export const NavTreeItemColumns = memo(({ path, item, open }: NavTreeItemColumnsProps) => {
19
+ const { t } = useTranslation(meta.id);
20
+ const { useActions, renderItemEnd: ItemEnd, popoverAnchorId } = useNavTreeContext();
22
21
 
23
- const { actions: _actions, groupedActions } = getActions(item);
24
- const [primaryAction, ...secondaryActions] = _actions.toSorted((a, b) =>
22
+ const level = path.length - 2;
23
+ const { actions: _actions, groupedActions } = useActions(item);
24
+ const [primaryAction, ...secondaryActions] = _actions.toSorted((a, _b) =>
25
25
  a.properties?.disposition === 'list-item-primary' ? -1 : 1,
26
26
  );
27
27
 
@@ -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
  });
@@ -12,9 +12,9 @@ import {
12
12
  extractClosestEdge,
13
13
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
14
14
  import React, {
15
- forwardRef,
16
15
  type MouseEvent,
17
16
  type PropsWithChildren,
17
+ forwardRef,
18
18
  useCallback,
19
19
  useLayoutEffect,
20
20
  useMemo,
@@ -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';
28
- import { MenuProvider, DropdownMenu, type MenuItem } from '@dxos/react-ui-menu';
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';
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,68 +5,44 @@
5
5
  import React, { useCallback, useMemo } from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
- import { Button, type ButtonProps, Icon, 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
- import { hoverableControlItem, hoverableOpenControlItem, mx } from '@dxos/react-ui-theme';
11
+ import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
12
12
 
13
- import { useLoadDescendents } from '../../hooks';
14
- import { NAVTREE_PLUGIN } from '../../meta';
15
- import { l0ItemType } from '../../util';
13
+ import { meta } from '../../meta';
16
14
  import { useNavTreeContext } from '../NavTreeContext';
17
15
  import { NavTreeItemColumns } from '../NavTreeItem';
18
16
 
19
- const headingBackButtonLabel = 'inset-0 min-is-0 truncate flex items-center pis-2';
20
-
21
- const TitleButton = ({ title, onClick }: { title: string } & Pick<ButtonProps, 'onClick'>) => {
22
- return (
23
- <Button
24
- variant='ghost'
25
- classNames='pli-1 flex-1 group text-start justify-start bs-[--rail-action] font-normal'
26
- onClick={onClick}
27
- >
28
- <Icon icon='ph--caret-left--bold' size={3} />
29
- <span className={mx(headingBackButtonLabel)}>{title}</span>
30
- </Button>
31
- );
32
- };
33
-
34
17
  export type L1PanelProps = {
35
18
  open?: boolean;
36
- item: Node<any>;
37
19
  path: string[];
20
+ item: Node<any>;
38
21
  currentItemId: string;
39
22
  onBack?: () => void;
40
23
  };
41
24
 
42
- const L1Panel = ({ open, item, path, currentItemId, onBack }: L1PanelProps) => {
43
- const { isAlternateTree, setAlternateTree, ...navTreeContext } = useNavTreeContext();
44
- const { t } = useTranslation(NAVTREE_PLUGIN);
25
+ /**
26
+ * Space or settings panel.
27
+ */
28
+ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProps) => {
29
+ const { t } = useTranslation(meta.id);
30
+ const { isAlternateTree, ...navTreeContext } = useNavTreeContext();
31
+ const title = toLocalizedString(item.properties.label, t);
45
32
 
46
33
  // TODO(wittjosiah): Support multiple alternate trees.
47
- const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
34
+ const { useItems } = navTreeContext;
35
+ const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
48
36
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
49
- const handleOpen = useCallback(() => setAlternateTree?.(alternatePath, true), [alternatePath, setAlternateTree]);
50
37
  const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
51
38
  const useAlternateItems = useCallback(
52
39
  (node?: Node, { disposition }: { disposition?: string } = {}) => {
53
40
  // TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
54
- return navTreeContext.useItems(node, { disposition, sort: node?.id === alternateTree.id });
41
+ return useItems(node, { disposition, sort: node?.id === alternateTree.id });
55
42
  },
56
- [navTreeContext, alternateTree],
43
+ [alternateTree, useItems],
57
44
  );
58
45
 
59
- const handleBack = useCallback(() => {
60
- if (isAlternate) {
61
- setAlternateTree?.(alternatePath, false);
62
- } else {
63
- onBack?.();
64
- }
65
- }, [isAlternate, onBack, alternatePath, setAlternateTree]);
66
-
67
- const title = toLocalizedString(item.properties.label, t);
68
- const backCapable = item.id.startsWith('!') || isAlternate;
69
-
70
46
  return (
71
47
  <Tabs.Tabpanel
72
48
  key={item.id}
@@ -79,41 +55,22 @@ const L1Panel = ({ open, item, path, currentItemId, onBack }: L1PanelProps) => {
79
55
  ]}
80
56
  tabIndex={-1}
81
57
  aria-label={title}
82
- {...(!open && { inert: 'true' })}
58
+ {...(!open && { inert: true })}
83
59
  >
84
60
  {item.id === currentItemId && (
85
- <>
86
- <h2 className='flex items-center border-be border-subduedSeparator app-drag pli-1 density-coarse'>
87
- {backCapable ? (
88
- <TitleButton title={title} onClick={handleBack} />
89
- ) : (
90
- <span className='flex-1 truncate min-is-0 pis-6'>{title}</span>
91
- )}
92
- {alternateTree && !isAlternate && (
93
- <IconButton
94
- variant='ghost'
95
- classNames={mx('shrink-0', hoverableControlItem, hoverableOpenControlItem, 'pli-2 pointer-fine:pli-1')}
96
- iconOnly
97
- size={5}
98
- icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
99
- label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
100
- data-testid='treeView.alternateTreeButton'
101
- onClick={handleOpen}
102
- />
103
- )}
104
- <NavTreeItemColumns path={path} item={item} open density='coarse' />
105
- </h2>
61
+ <DensityProvider density='fine'>
62
+ <L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
106
63
  <div role='none' className='overflow-y-auto'>
107
64
  {isAlternate ? (
108
65
  <Tree
109
66
  {...navTreeContext}
110
- useItems={useAlternateItems}
111
67
  id={alternateTree.id}
112
68
  root={alternateTree}
113
69
  path={alternatePath}
114
70
  levelOffset={5}
115
71
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
116
72
  renderColumns={NavTreeItemColumns}
73
+ useItems={useAlternateItems}
117
74
  />
118
75
  ) : (
119
76
  <Tree
@@ -122,53 +79,71 @@ const L1Panel = ({ open, item, path, currentItemId, onBack }: L1PanelProps) => {
122
79
  root={item}
123
80
  path={path}
124
81
  levelOffset={5}
125
- draggable
126
82
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
127
83
  renderColumns={NavTreeItemColumns}
84
+ draggable
128
85
  />
129
86
  )}
130
87
  </div>
131
- </>
88
+ </DensityProvider>
132
89
  )}
133
90
  </Tabs.Tabpanel>
134
91
  );
135
92
  };
136
93
 
137
- const L1PanelCollection = ({ item, path, ...props }: L1PanelProps) => {
138
- const { useItems } = useNavTreeContext();
139
- useLoadDescendents(item);
140
- const collectionItems = useItems(item);
141
- const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
142
- return (
143
- <>
144
- {collectionItems
145
- .filter((item) => l0ItemType(item) === 'tab')
146
- .map((item) => (
147
- <L1Panel key={item.id} item={item} path={groupPath} {...props} />
148
- ))}
149
- </>
150
- );
151
- };
94
+ /**
95
+ * Header row.
96
+ */
97
+ const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
98
+ const { t } = useTranslation(meta.id);
99
+ const { isAlternateTree, setAlternateTree, ...navTreeContext } = useNavTreeContext();
100
+ const title = toLocalizedString(item.properties.label, t);
152
101
 
153
- export type L1PanelsProps = Pick<L1PanelProps, 'open' | 'currentItemId' | 'onBack'> & {
154
- topLevelItems: Node<any>[];
155
- path: string[];
156
- };
102
+ // TODO(wittjosiah): Support multiple alternate trees.
103
+ const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
104
+ const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
105
+ const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
106
+ const backCapable = item.id.startsWith('!') || isAlternate;
107
+
108
+ const handleOpen = useCallback(() => {
109
+ setAlternateTree?.(alternatePath, true);
110
+ }, [alternatePath, setAlternateTree]);
111
+
112
+ const handleBack = useCallback(() => {
113
+ if (isAlternate) {
114
+ setAlternateTree?.(alternatePath, false);
115
+ } else {
116
+ onBack?.();
117
+ }
118
+ }, [isAlternate, alternatePath, setAlternateTree, onBack]);
157
119
 
158
- export const L1Panels = ({ topLevelItems, onBack, ...props }: L1PanelsProps) => {
120
+ // TODO(burdon): Reuse same grid as tree?
159
121
  return (
160
- <>
161
- {topLevelItems.map((item) => {
162
- const type = l0ItemType(item);
163
- switch (type) {
164
- case 'collection':
165
- return <L1PanelCollection key={item.id} item={item} {...props} />;
166
- case 'tab':
167
- return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
168
- default:
169
- return null;
170
- }
171
- })}
172
- </>
122
+ <div className='flex is-full items-center border-be border-subduedSeparator app-drag density-coarse'>
123
+ <div className='is-6' />
124
+ <h2 className='flex-1 truncate min-is-0'>{title}</h2>
125
+ {(backCapable && (
126
+ <IconButton
127
+ variant='ghost'
128
+ icon='ph--arrow-u-down-left--regular'
129
+ iconOnly
130
+ label={t('button back')}
131
+ classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
132
+ onClick={handleBack}
133
+ />
134
+ )) ||
135
+ (alternateTree && !isAlternate && (
136
+ <IconButton
137
+ data-testid='treeView.alternateTreeButton'
138
+ variant='ghost'
139
+ icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
140
+ iconOnly
141
+ label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
142
+ classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
143
+ onClick={handleOpen}
144
+ />
145
+ ))}
146
+ <NavTreeItemColumns open path={path} item={item} density='coarse' />
147
+ </div>
173
148
  );
174
149
  };
@@ -0,0 +1,58 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { useMemo } from 'react';
6
+
7
+ import { type Node } from '@dxos/app-graph';
8
+
9
+ import { useLoadDescendents } from '../../hooks';
10
+ import { l0ItemType } from '../../util';
11
+ import { useNavTreeContext } from '../NavTreeContext';
12
+
13
+ import { L1Panel, type L1PanelProps } from './L1Panel';
14
+
15
+ export type L1TabsProps = Pick<L1PanelProps, 'open' | 'currentItemId' | 'onBack'> & {
16
+ path: string[];
17
+ topLevelItems: Node<any>[];
18
+ };
19
+
20
+ /**
21
+ * Each space is an L1 tab.
22
+ */
23
+ export const L1Tabs = ({ topLevelItems, onBack, ...props }: L1TabsProps) => {
24
+ return (
25
+ <>
26
+ {topLevelItems.map((item) => {
27
+ const type = l0ItemType(item);
28
+ switch (type) {
29
+ case 'tab':
30
+ return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
31
+ case 'collection':
32
+ return <L1PanelCollection key={item.id} item={item} {...props} />;
33
+ default:
34
+ return null;
35
+ }
36
+ })}
37
+ </>
38
+ );
39
+ };
40
+
41
+ /**
42
+ *
43
+ */
44
+ const L1PanelCollection = ({ item, path, ...props }: L1PanelProps) => {
45
+ const { useItems } = useNavTreeContext();
46
+ useLoadDescendents(item);
47
+ const collectionItems = useItems(item);
48
+ const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
49
+ return (
50
+ <>
51
+ {collectionItems
52
+ .filter((item) => l0ItemType(item) === 'tab')
53
+ .map((item) => (
54
+ <L1Panel key={item.id} item={item} path={groupPath} {...props} />
55
+ ))}
56
+ </>
57
+ );
58
+ };
@@ -4,3 +4,4 @@
4
4
 
5
5
  export * from './L0Menu';
6
6
  export * from './L1Panel';
7
+ export * from './L1Tabs';
@@ -2,20 +2,21 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
6
 
7
- import { type Meta } from '@storybook/react-vite';
8
-
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
- const meta: Meta<typeof UserAccountAvatar> = {
11
+ const meta = {
14
12
  title: 'plugins/plugin-navtree/UserAccountAvatar',
15
- component: UserAccountAvatar,
13
+
16
14
  decorators: [withTheme],
17
- };
15
+ component: UserAccountAvatar,
16
+ } satisfies Meta<typeof UserAccountAvatar>;
18
17
 
19
18
  export default meta;
20
19
 
21
- export const Default = {};
20
+ type Story = StoryObj<typeof meta>;
21
+
22
+ export const Default: Story = {};