@dxos/plugin-navtree 0.8.4-main.67995b8 → 0.8.4-main.69d29f4

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 (178) hide show
  1. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs +43 -0
  2. package/dist/lib/browser/app-graph-builder-RSMA6I22.mjs.map +7 -0
  3. package/dist/lib/browser/chunk-6YPQ625I.mjs +1293 -0
  4. package/dist/lib/browser/chunk-6YPQ625I.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-AGQDZBPL.mjs +23 -0
  6. package/dist/lib/browser/chunk-AGQDZBPL.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-YKTYPINZ.mjs +19 -0
  8. package/dist/lib/browser/chunk-YKTYPINZ.mjs.map +7 -0
  9. package/dist/lib/browser/index.mjs +82 -95
  10. package/dist/lib/browser/index.mjs.map +4 -4
  11. package/dist/lib/browser/{keyboard-QRS6WOQH.mjs → keyboard-32EH3N4K.mjs} +20 -19
  12. package/dist/lib/browser/keyboard-32EH3N4K.mjs.map +7 -0
  13. package/dist/lib/browser/meta.json +1 -1
  14. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs +52 -0
  15. package/dist/lib/browser/operation-resolver-IB75DC5O.mjs.map +7 -0
  16. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs +58 -0
  17. package/dist/lib/browser/react-surface-F6CVMS3Y.mjs.map +7 -0
  18. package/dist/lib/browser/state-EVYQOUI3.mjs +120 -0
  19. package/dist/lib/browser/state-EVYQOUI3.mjs.map +7 -0
  20. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  21. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  22. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts +6 -0
  23. package/dist/types/src/capabilities/app-graph-builder/app-graph-builder.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/app-graph-builder/index.d.ts +3 -0
  25. package/dist/types/src/capabilities/app-graph-builder/index.d.ts.map +1 -0
  26. package/dist/types/src/capabilities/index.d.ts +5 -105
  27. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  28. package/dist/types/src/capabilities/keyboard/index.d.ts +3 -0
  29. package/dist/types/src/capabilities/keyboard/index.d.ts.map +1 -0
  30. package/dist/types/src/capabilities/keyboard/keyboard.d.ts +5 -0
  31. package/dist/types/src/capabilities/keyboard/keyboard.d.ts.map +1 -0
  32. package/dist/types/src/capabilities/operation-resolver/index.d.ts +3 -0
  33. package/dist/types/src/capabilities/operation-resolver/index.d.ts.map +1 -0
  34. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts +5 -0
  35. package/dist/types/src/capabilities/operation-resolver/operation-resolver.d.ts.map +1 -0
  36. package/dist/types/src/capabilities/react-surface/index.d.ts +3 -0
  37. package/dist/types/src/capabilities/react-surface/index.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/react-surface/react-surface.d.ts +5 -0
  39. package/dist/types/src/capabilities/react-surface/react-surface.d.ts.map +1 -0
  40. package/dist/types/src/capabilities/state/index.d.ts +3 -0
  41. package/dist/types/src/capabilities/state/index.d.ts.map +1 -0
  42. package/dist/types/src/capabilities/state/state.d.ts +6 -0
  43. package/dist/types/src/capabilities/state/state.d.ts.map +1 -0
  44. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +3 -2
  45. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +1 -1
  46. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +1 -1
  47. package/dist/types/src/components/NavTree/NavTree.d.ts +30 -1
  48. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  49. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +29 -4
  50. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  51. package/dist/types/src/components/NavTreeContainer.d.ts +8 -1
  52. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  53. package/dist/types/src/components/NavTreeDocumentTitle.d.ts +1 -1
  54. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +1 -1
  55. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +6 -6
  56. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  57. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +33 -4
  58. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  59. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +1 -1
  60. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/L0Menu.d.ts +6 -4
  62. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  63. package/dist/types/src/components/Sidebar/L1Panel.d.ts +1 -1
  64. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  65. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +2 -2
  66. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -1
  67. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  68. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  69. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  70. package/dist/types/src/components/index.d.ts.map +1 -1
  71. package/dist/types/src/components/types.d.ts +8 -8
  72. package/dist/types/src/components/types.d.ts.map +1 -1
  73. package/dist/types/src/experimental/Tree.d.ts +1 -2
  74. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  75. package/dist/types/src/experimental/Tree.stories.d.ts +7 -4
  76. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  77. package/dist/types/src/hooks.d.ts +32 -1
  78. package/dist/types/src/hooks.d.ts.map +1 -1
  79. package/dist/types/src/index.d.ts +1 -2
  80. package/dist/types/src/index.d.ts.map +1 -1
  81. package/dist/types/src/meta.d.ts +3 -4
  82. package/dist/types/src/meta.d.ts.map +1 -1
  83. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  84. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  85. package/dist/types/src/testing/generator.d.ts +5 -6
  86. package/dist/types/src/testing/generator.d.ts.map +1 -1
  87. package/dist/types/src/testing/update-state.d.ts +2 -2
  88. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  89. package/dist/types/src/types/capabilities.d.ts +16 -0
  90. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  91. package/dist/types/src/types/events.d.ts +5 -0
  92. package/dist/types/src/types/events.d.ts.map +1 -0
  93. package/dist/types/src/types/index.d.ts +4 -0
  94. package/dist/types/src/types/index.d.ts.map +1 -0
  95. package/dist/types/src/{types.d.ts → types/schema.d.ts} +10 -9
  96. package/dist/types/src/types/schema.d.ts.map +1 -0
  97. package/dist/types/src/util.d.ts +8 -9
  98. package/dist/types/src/util.d.ts.map +1 -1
  99. package/dist/types/tsconfig.tsbuildinfo +1 -1
  100. package/package.json +56 -55
  101. package/src/NavTreePlugin.tsx +65 -83
  102. package/src/capabilities/app-graph-builder/app-graph-builder.ts +41 -0
  103. package/src/capabilities/app-graph-builder/index.ts +7 -0
  104. package/src/capabilities/index.ts +5 -9
  105. package/src/capabilities/keyboard/index.ts +7 -0
  106. package/src/capabilities/keyboard/keyboard.ts +65 -0
  107. package/src/capabilities/operation-resolver/index.ts +7 -0
  108. package/src/capabilities/operation-resolver/operation-resolver.ts +41 -0
  109. package/src/capabilities/react-surface/index.ts +7 -0
  110. package/src/capabilities/react-surface/react-surface.tsx +54 -0
  111. package/src/capabilities/state/index.ts +7 -0
  112. package/src/capabilities/state/state.ts +121 -0
  113. package/src/components/CommandsDialog/CommandsDialog.tsx +109 -98
  114. package/src/components/CommandsDialog/CommandsTrigger.tsx +6 -15
  115. package/src/components/NavTree/NavTree.stories.tsx +97 -92
  116. package/src/components/NavTree/NavTree.tsx +9 -15
  117. package/src/components/NavTreeContainer.tsx +280 -256
  118. package/src/components/NavTreeDocumentTitle.tsx +5 -4
  119. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +20 -12
  120. package/src/components/NavTreeItem/NavTreeItemAction.tsx +25 -18
  121. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +36 -29
  122. package/src/components/Sidebar/L0Menu.tsx +114 -139
  123. package/src/components/Sidebar/L1Panel.tsx +32 -35
  124. package/src/components/Sidebar/L1Tabs.tsx +7 -31
  125. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -8
  126. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  127. package/src/components/index.ts +1 -0
  128. package/src/components/types.ts +9 -8
  129. package/src/experimental/Tree.stories.tsx +28 -30
  130. package/src/experimental/Tree.tsx +7 -24
  131. package/src/hooks.ts +54 -2
  132. package/src/index.ts +1 -2
  133. package/src/meta.ts +11 -8
  134. package/src/testing/app-graph-builder.ts +159 -234
  135. package/src/testing/generator.ts +20 -18
  136. package/src/testing/update-state.ts +6 -6
  137. package/src/types/capabilities.ts +21 -0
  138. package/src/types/events.ts +11 -0
  139. package/src/types/index.ts +7 -0
  140. package/src/{types.ts → types/schema.ts} +9 -8
  141. package/src/util.ts +14 -35
  142. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs +0 -45
  143. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
  144. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  145. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  146. package/dist/lib/browser/chunk-WCARAZ7J.mjs +0 -15
  147. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  148. package/dist/lib/browser/chunk-WEMCSQI7.mjs +0 -1469
  149. package/dist/lib/browser/chunk-WEMCSQI7.mjs.map +0 -7
  150. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs +0 -47
  151. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs.map +0 -7
  152. package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
  153. package/dist/lib/browser/react-surface-JSJ5SQX5.mjs +0 -51
  154. package/dist/lib/browser/react-surface-JSJ5SQX5.mjs.map +0 -7
  155. package/dist/lib/browser/state-54DY3DOY.mjs +0 -117
  156. package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
  157. package/dist/types/src/capabilities/app-graph-builder.d.ts +0 -4
  158. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +0 -1
  159. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  160. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  161. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  162. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  163. package/dist/types/src/capabilities/keyboard.d.ts +0 -4
  164. package/dist/types/src/capabilities/keyboard.d.ts.map +0 -1
  165. package/dist/types/src/capabilities/react-surface.d.ts +0 -4
  166. package/dist/types/src/capabilities/react-surface.d.ts.map +0 -1
  167. package/dist/types/src/capabilities/state.d.ts +0 -20
  168. package/dist/types/src/capabilities/state.d.ts.map +0 -1
  169. package/dist/types/src/events.d.ts +0 -4
  170. package/dist/types/src/events.d.ts.map +0 -1
  171. package/dist/types/src/types.d.ts.map +0 -1
  172. package/src/capabilities/app-graph-builder.ts +0 -52
  173. package/src/capabilities/capabilities.ts +0 -20
  174. package/src/capabilities/intent-resolver.ts +0 -37
  175. package/src/capabilities/keyboard.ts +0 -59
  176. package/src/capabilities/react-surface.tsx +0 -45
  177. package/src/capabilities/state.ts +0 -104
  178. package/src/events.ts +0 -11
@@ -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,
@@ -23,16 +23,26 @@ import React, {
23
23
  } from 'react';
24
24
 
25
25
  import { type Node } from '@dxos/app-graph';
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';
26
+ import { DxAvatar } from '@dxos/lit-ui/react';
27
+ import { useActionRunner } from '@dxos/plugin-graph';
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
- import { mx } from '@dxos/react-ui-theme';
32
- import { arrayMove, getFirstTwoRenderableChars } from '@dxos/util';
41
+ import { mx } from '@dxos/ui-theme';
42
+ import { arrayMove } from '@dxos/util';
33
43
 
34
- import { useLoadDescendents } from '../../hooks';
35
- import { NAVTREE_PLUGIN } from '../../meta';
44
+ import { useNavTreeState } from '../../hooks';
45
+ import { meta } from '../../meta';
36
46
  import { l0ItemType } from '../../util';
37
47
  import { useNavTreeContext } from '../NavTreeContext';
38
48
  import { UserAccountAvatar } from '../UserAccountAvatar';
@@ -47,43 +57,41 @@ type L0ItemData = {
47
57
  };
48
58
 
49
59
  type L0ItemRootProps = {
50
- item: Node<any>;
51
- parent?: Node<any>;
60
+ item: Node.Node;
61
+ parent?: Node.Node;
52
62
  path: string[];
53
63
  };
54
64
 
55
65
  type L0ItemProps = L0ItemRootProps & {
56
- item: Node<any>;
57
- parent?: Node<any>;
66
+ item: Node.Node;
67
+ parent?: Node.Node;
58
68
  path: string[];
59
69
  pinned?: boolean;
60
70
  onRearrange?: StackItemRearrangeHandler<L0ItemData>;
61
71
  };
62
72
 
63
73
  const useL0ItemClick = ({ item, parent, path }: L0ItemProps, type: string) => {
64
- const { tab, isCurrent, onSelect, onTabChange } = useNavTreeContext();
65
- const [isLg] = useMediaQuery('lg', { ssr: false });
74
+ const { tab, onSelect, onTabChange } = useNavTreeContext();
75
+ const { getItem } = useNavTreeState();
76
+ const [isLg] = useMediaQuery('lg');
77
+ const runAction = useActionRunner();
66
78
 
67
79
  return useCallback(
68
80
  (event: MouseEvent) => {
69
81
  switch (type) {
70
82
  case 'action': {
71
- const {
72
- data: invoke,
73
- properties: { caller },
74
- } = item;
75
-
76
- return invoke?.(caller ? { node: parent, caller } : { node: parent });
83
+ const { properties: { caller } = {} } = item;
84
+ return void runAction(item as Node.Action, caller ? { parent, caller } : { parent });
77
85
  }
78
86
 
79
87
  case 'tab':
80
88
  return onTabChange?.(item);
81
89
 
82
90
  case 'link':
83
- return onSelect?.({ item, path, current: !isCurrent(path, item), option: event.altKey });
91
+ return onSelect?.({ item, path, current: !getItem(path).current, option: event.altKey });
84
92
  }
85
93
  },
86
- [item, parent, type, tab, isCurrent, onSelect, onTabChange, isLg],
94
+ [item, parent, type, tab, getItem, onSelect, onTabChange, isLg, runAction],
87
95
  );
88
96
  };
89
97
 
@@ -92,7 +100,7 @@ const l0Breakpoints: Record<string, string> = {
92
100
  };
93
101
 
94
102
  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';
103
+ 'group/l0item flex is-full justify-center items-center relative data[type!="collection"]:cursor-pointer app-no-drag dx-focus-ring-group';
96
104
 
97
105
  const l0ItemContent = 'flex justify-center items-center dx-focus-ring-group-indicator transition-colors rounded-sm';
98
106
 
@@ -103,55 +111,43 @@ const L0ItemRoot = forwardRef<HTMLElement, PropsWithChildren<L0ItemRootProps>>(
103
111
  const type = l0ItemType(item);
104
112
  const itemPath = useMemo(() => [...path, item.id], [item.id, path]);
105
113
 
106
- const { t } = useTranslation(NAVTREE_PLUGIN);
114
+ const { t } = useTranslation(meta.id);
107
115
  const localizedString = toLocalizedString(item.properties.label, t);
108
116
 
109
117
  const handleClick = useL0ItemClick({ item, parent, path: itemPath }, type);
110
118
  const rootProps =
111
119
  type === 'tab'
112
- ? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-itemid': id }
113
- : 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';
120
+ ? { value: item.id, tabIndex: 0, onClick: handleClick, 'data-testid': testId, 'data-object-id': id }
121
+ : { onClick: handleClick, 'data-testid': testId, 'data-object-id': id };
118
122
 
119
123
  return (
120
124
  <Tooltip.Trigger asChild delayDuration={0} side='right' content={localizedString}>
121
- <Root
125
+ <Tabs.TabPrimitive
122
126
  {...(rootProps as any)}
123
127
  data-type={type}
124
128
  className={mx(l0ItemRoot, l0Breakpoints[item.properties.l0Breakpoint])}
125
129
  ref={forwardedRef}
126
130
  >
127
131
  {children}
128
- </Root>
132
+ </Tabs.TabPrimitive>
129
133
  </Tooltip.Trigger>
130
134
  );
131
135
  },
132
136
  );
133
137
 
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
- };
138
+ export const L0ItemActiveTabIndicator = ({ classNames }: ThemedClassName<{}>) => (
139
+ <div
140
+ role='none'
141
+ className={mx(
142
+ 'hidden group-aria-selected/l0item:block absolute inline-start-0 inset-block-2 is-1 bg-accentSurface rounded-ie',
143
+ classNames,
144
+ )}
145
+ />
146
+ );
151
147
 
152
148
  // TODO(burdon): Factor out pinned (non-draggable) items.
153
149
  const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
154
- const { t } = useTranslation(NAVTREE_PLUGIN);
150
+ const { t } = useTranslation(meta.id);
155
151
  const itemElement = useRef<HTMLElement | null>(null);
156
152
  const [closestEdge, setEdge] = useState<Edge | null>(null);
157
153
  const localizedString = toLocalizedString(item.properties.label, t);
@@ -228,10 +224,7 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
228
224
  >
229
225
  <ItemAvatar item={item} />
230
226
  </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
- />
227
+ <L0ItemActiveTabIndicator />
235
228
  <span id={`${item.id}__label`} className='sr-only'>
236
229
  {localizedString}
237
230
  </span>
@@ -241,83 +234,68 @@ const L0Item = ({ item, parent, path, pinned, onRearrange }: L0ItemProps) => {
241
234
  };
242
235
 
243
236
  const ItemAvatar = ({ item }: Pick<L0ItemProps, 'item'>) => {
237
+ const { t } = useTranslation(meta.id);
244
238
  const type = l0ItemType(item);
245
239
  if (item.properties.icon) {
246
240
  const hue = item.properties.hue ?? null;
247
241
  const hueFgStyle = hue && { style: { color: `var(--dx-${hue}SurfaceText)` } };
248
- return <Icon icon={item.properties.icon} size={5} {...hueFgStyle} />;
242
+ return <Icon icon={item.properties.icon} size={6} {...hueFgStyle} />;
249
243
  }
250
244
 
251
245
  if (type === 'tab' && item.properties.disposition !== 'pin-end') {
252
- return <L0Avatar item={item} />;
246
+ const hue = item.properties.hue ?? null;
247
+ const localizedString = toLocalizedString(item.properties.label, t);
248
+ return <DxAvatar hue={hue} hueVariant='surface' variant='square' size={12} fallback={localizedString} />;
253
249
  }
254
250
 
255
251
  return null;
256
252
  };
257
253
 
258
254
  //
259
- // L0Collection
255
+ // L0Menu
260
256
  //
261
257
 
262
- const L0Collection = ({ item, path }: L0ItemProps) => {
263
- const navTreeContext = useNavTreeContext();
264
- useLoadDescendents(item);
265
- const collectionItems = navTreeContext.useItems(item);
266
- const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
267
- const { id, testId } = navTreeContext.getProps?.(item, path) ?? {};
258
+ export type L0MenuProps = {
259
+ menuActions: MenuItem[];
260
+ topLevelItems: Node.Node[];
261
+ pinnedItems: Node.Node[];
262
+ userAccountItem?: Node.Node;
263
+ parent?: Node.Node;
264
+ path: string[];
265
+ };
266
+
267
+ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
268
+ const { t } = useTranslation(meta.id);
269
+ const runAction = useActionRunner();
270
+ const handleAction = useCallback(
271
+ (action: Node.Action, params: Node.InvokeProps) => {
272
+ void runAction(action, params);
273
+ },
274
+ [runAction],
275
+ );
268
276
 
269
277
  const handleRearrange = useCallback<StackItemRearrangeHandler<L0ItemData>>(
270
278
  (source, target, closestEdge) => {
271
- invariant(item.properties.onRearrangeChildren);
272
- const sourceIndex = collectionItems.findIndex((i) => i.id === source.id);
273
- const targetIndex = collectionItems.findIndex((i) => i.id === target.id);
279
+ const sourceItem = topLevelItems.find((i) => i.id === source.id);
280
+ if (!sourceItem?.properties.onRearrange) {
281
+ return;
282
+ }
283
+
284
+ const sourceIndex = topLevelItems.findIndex((i) => i.id === source.id);
285
+ const targetIndex = topLevelItems.findIndex((i) => i.id === target.id);
274
286
  const insertIndex =
275
287
  source.id === target.id
276
288
  ? sourceIndex
277
289
  : targetIndex +
278
290
  (sourceIndex < targetIndex ? (closestEdge === 'top' ? -1 : 0) : closestEdge === 'bottom' ? 1 : 0);
279
- const nextOrder = arrayMove([...collectionItems], sourceIndex, insertIndex);
280
- return item.properties.onRearrangeChildren(nextOrder);
291
+ const nextOrder = arrayMove([...topLevelItems], sourceIndex, insertIndex);
292
+ return sourceItem.properties.onRearrange(nextOrder.map((item) => item.data));
281
293
  },
282
- [collectionItems, item.properties.onRearrangeChildren],
294
+ [topLevelItems],
283
295
  );
284
296
 
285
- return (
286
- <div
287
- role='group'
288
- className='contents group/l0c'
289
- aria-labelledby={`${item.id}__label`}
290
- data-itemid={id}
291
- data-testid={testId}
292
- >
293
- {collectionItems.map((collectionItem) => (
294
- <L0Item
295
- key={collectionItem.id}
296
- item={collectionItem}
297
- parent={item}
298
- path={groupPath}
299
- {...(item.properties.onRearrangeChildren && { onRearrange: handleRearrange })}
300
- />
301
- ))}
302
- </div>
303
- );
304
- };
305
-
306
- //
307
- // L0Menu
308
- //
309
-
310
- export type L0MenuProps = {
311
- menuActions: MenuItem[];
312
- topLevelItems: Node<any>[];
313
- pinnedItems: Node<any>[];
314
- userAccountItem?: Node<any>;
315
- parent?: Node<any>;
316
- path: string[];
317
- };
318
-
319
- export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountItem, parent, path }: L0MenuProps) => {
320
- const { t } = useTranslation(NAVTREE_PLUGIN);
297
+ // Check if any items have onRearrange to enable drag-and-drop.
298
+ const hasRearrangeableItems = topLevelItems.some((item) => item.properties.onRearrange);
321
299
 
322
300
  return (
323
301
  <Tabs.Tablist
@@ -328,23 +306,29 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
328
306
  ]}
329
307
  >
330
308
  {/* TODO(wittjosiah): Use L0Item trigger. */}
331
- <MenuProvider>
309
+ <MenuProvider onAction={handleAction}>
332
310
  <DropdownMenu.Root group={parent} items={menuActions}>
333
311
  <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'
312
+ <Tabs.TabPrimitive value='options' asChild role='button'>
313
+ <DropdownMenu.Trigger
314
+ data-testid='spacePlugin.addSpace'
340
315
  className={mx(
341
- l0ItemContent,
342
- 'is-[--rail-action] bs-[--rail-action] group-hover/l0item:bg-hoverSurface',
316
+ l0ItemRoot,
317
+ 'grid place-items-center dx-focus-ring-group',
318
+ '[body[data-platform="macos"]_&]:mt-[30px]',
343
319
  )}
344
320
  >
345
- <Icon icon='ph--list--regular' size={5} />
346
- </div>
347
- </DropdownMenu.Trigger>
321
+ <div
322
+ role='none'
323
+ className={mx(
324
+ l0ItemContent,
325
+ 'is-[--rail-action] bs-[--rail-action] group-hover/l0item:bg-hoverSurface',
326
+ )}
327
+ >
328
+ <Icon icon='ph--list--regular' size={5} />
329
+ </div>
330
+ </DropdownMenu.Trigger>
331
+ </Tabs.TabPrimitive>
348
332
  </Tooltip.Trigger>
349
333
  </DropdownMenu.Root>
350
334
  </MenuProvider>
@@ -356,17 +340,19 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
356
340
  role='none'
357
341
  className={mx([
358
342
  'flex flex-col gap-1 pbs-1',
359
- '[body[data-platform="darwin"]_&]:pbs-[calc(30px+0.25rem)]',
343
+ '[body[data-platform="macos"]_&]:pbs-[30px]',
360
344
  '[body[data-platform="ios"]_&]:pbs-[max(env(safe-area-inset-top),0.25rem)]',
361
345
  ])}
362
346
  >
363
- {topLevelItems.map((item) => {
364
- if (l0ItemType(item) === 'collection') {
365
- return <L0Collection key={item.id} item={item} parent={parent} path={path} />;
366
- } else {
367
- return <L0Item key={item.id} item={item} parent={parent} path={path} />;
368
- }
369
- })}
347
+ {topLevelItems.map((item) => (
348
+ <L0Item
349
+ key={item.id}
350
+ item={item}
351
+ parent={parent}
352
+ path={path}
353
+ {...(hasRearrangeableItems && { onRearrange: handleRearrange })}
354
+ />
355
+ ))}
370
356
  </div>
371
357
  <ScrollArea.Scrollbar orientation='vertical'>
372
358
  <ScrollArea.Thumb />
@@ -376,11 +362,9 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
376
362
 
377
363
  {/* Actions. */}
378
364
  <div role='none' className='grid grid-cols-1 auto-rows-[--rail-action] pbs-2'>
379
- {pinnedItems
380
- .filter((item) => l0ItemType(item) !== 'collection')
381
- .map((item) => (
382
- <L0Item key={item.id} item={item} parent={parent} path={path} pinned />
383
- ))}
365
+ {pinnedItems.map((item) => (
366
+ <L0Item key={item.id} item={item} parent={parent} path={path} pinned />
367
+ ))}
384
368
  </div>
385
369
 
386
370
  {userAccountItem && (
@@ -396,15 +380,6 @@ export const L0Menu = ({ menuActions, topLevelItems, pinnedItems, userAccountIte
396
380
  </L0ItemRoot>
397
381
  </div>
398
382
  )}
399
-
400
- <div
401
- role='none'
402
- className='hidden [body[data-platform="darwin"]_&]:block absolute block-start-0 is-[calc(var(--l0-size)-1px)] bs-[calc(40px+0.25rem)]'
403
- style={{
404
- background:
405
- 'linear-gradient(to bottom, var(--dx-sidebarSurface) 0%, var(--dx-sidebarSurface) 70%, transparent 100%)',
406
- }}
407
- />
408
383
  </Tabs.Tablist>
409
384
  );
410
385
  };
@@ -5,11 +5,12 @@
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
- import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/react-ui-theme';
11
+ import { hoverableControlItem, hoverableOpenControlItem } from '@dxos/ui-theme';
12
12
 
13
+ import { useIsAlternateTree } from '../../hooks';
13
14
  import { meta } from '../../meta';
14
15
  import { useNavTreeContext } from '../NavTreeContext';
15
16
  import { NavTreeItemColumns } from '../NavTreeItem';
@@ -17,7 +18,7 @@ import { NavTreeItemColumns } from '../NavTreeItem';
17
18
  export type L1PanelProps = {
18
19
  open?: boolean;
19
20
  path: string[];
20
- item: Node<any>;
21
+ item: Node.Node;
21
22
  currentItemId: string;
22
23
  onBack?: () => void;
23
24
  };
@@ -27,17 +28,16 @@ export type L1PanelProps = {
27
28
  */
28
29
  export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProps) => {
29
30
  const { t } = useTranslation(meta.id);
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;
31
+ const navTreeContext = useNavTreeContext();
33
32
  const title = toLocalizedString(item.properties.label, t);
34
33
 
35
34
  // TODO(wittjosiah): Support multiple alternate trees.
35
+ const { useItems } = navTreeContext;
36
36
  const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
37
37
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
38
- const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
38
+ const isAlternate = useIsAlternateTree(alternatePath, item);
39
39
  const useAlternateItems = useCallback(
40
- (node?: Node, { disposition }: { disposition?: string } = {}) => {
40
+ (node?: Node.Node, { disposition }: { disposition?: string } = {}) => {
41
41
  // TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
42
42
  return useItems(node, { disposition, sort: node?.id === alternateTree.id });
43
43
  },
@@ -56,22 +56,22 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
56
56
  ]}
57
57
  tabIndex={-1}
58
58
  aria-label={title}
59
- {...(!open && { inert: 'true' })}
59
+ {...(!open && { inert: true })}
60
60
  >
61
61
  {item.id === currentItemId && (
62
- <>
62
+ <DensityProvider density='fine'>
63
63
  <L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
64
64
  <div role='none' className='overflow-y-auto'>
65
65
  {isAlternate ? (
66
66
  <Tree
67
67
  {...navTreeContext}
68
- useItems={useAlternateItems}
69
68
  id={alternateTree.id}
70
69
  root={alternateTree}
71
70
  path={alternatePath}
72
71
  levelOffset={5}
73
72
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
74
73
  renderColumns={NavTreeItemColumns}
74
+ useItems={useAlternateItems}
75
75
  />
76
76
  ) : (
77
77
  <Tree
@@ -80,13 +80,13 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
80
80
  root={item}
81
81
  path={path}
82
82
  levelOffset={5}
83
- draggable
84
83
  gridTemplateColumns='[tree-row-start] 1fr min-content min-content min-content [tree-row-end]'
85
84
  renderColumns={NavTreeItemColumns}
85
+ draggable
86
86
  />
87
87
  )}
88
88
  </div>
89
- </>
89
+ </DensityProvider>
90
90
  )}
91
91
  </Tabs.Tabpanel>
92
92
  );
@@ -97,13 +97,13 @@ export const L1Panel = ({ open, path, item, currentItemId, onBack }: L1PanelProp
97
97
  */
98
98
  const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
99
99
  const { t } = useTranslation(meta.id);
100
- const { isAlternateTree, setAlternateTree, ...navTreeContext } = useNavTreeContext();
100
+ const { setAlternateTree, ...navTreeContext } = useNavTreeContext();
101
101
  const title = toLocalizedString(item.properties.label, t);
102
102
 
103
103
  // TODO(wittjosiah): Support multiple alternate trees.
104
104
  const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
105
105
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
106
- const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
106
+ const isAlternate = useIsAlternateTree(alternatePath, item);
107
107
  const backCapable = item.id.startsWith('!') || isAlternate;
108
108
 
109
109
  const handleOpen = useCallback(() => {
@@ -123,31 +123,28 @@ const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
123
123
  <div className='flex is-full items-center border-be border-subduedSeparator app-drag density-coarse'>
124
124
  <div className='is-6' />
125
125
  <h2 className='flex-1 truncate min-is-0'>{title}</h2>
126
- <div>
127
- {(backCapable && (
126
+ {(backCapable && (
127
+ <IconButton
128
+ data-testid='treeView.primaryTreeButton'
129
+ variant='ghost'
130
+ icon='ph--arrow-u-down-left--regular'
131
+ iconOnly
132
+ label={t('button back')}
133
+ classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
134
+ onClick={handleBack}
135
+ />
136
+ )) ||
137
+ (alternateTree && !isAlternate && (
128
138
  <IconButton
139
+ data-testid='treeView.alternateTreeButton'
129
140
  variant='ghost'
130
- icon='ph--arrow-u-down-left--regular'
141
+ icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
131
142
  iconOnly
132
- size={5}
133
- label={t('button back')}
143
+ label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
134
144
  classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
135
- onClick={handleBack}
145
+ onClick={handleOpen}
136
146
  />
137
- )) ||
138
- (alternateTree && !isAlternate && (
139
- <IconButton
140
- data-testid='treeView.alternateTreeButton'
141
- variant='ghost'
142
- icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
143
- iconOnly
144
- size={5}
145
- label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
146
- classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
147
- onClick={handleOpen}
148
- />
149
- )) || <div />}
150
- </div>
147
+ ))}
151
148
  <NavTreeItemColumns open path={path} item={item} density='coarse' />
152
149
  </div>
153
150
  );
@@ -2,56 +2,32 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import React, { useMemo } from 'react';
5
+ import React from 'react';
6
6
 
7
7
  import { type Node } from '@dxos/app-graph';
8
8
 
9
- import { useLoadDescendents } from '../../hooks';
10
9
  import { l0ItemType } from '../../util';
11
- import { useNavTreeContext } from '../NavTreeContext';
10
+
12
11
  import { L1Panel, type L1PanelProps } from './L1Panel';
13
12
 
14
13
  export type L1TabsProps = Pick<L1PanelProps, 'open' | 'currentItemId' | 'onBack'> & {
15
14
  path: string[];
16
- topLevelItems: Node<any>[];
15
+ topLevelItems: Node.Node[];
17
16
  };
18
17
 
19
18
  /**
20
- * Each space is an L1 tab.
19
+ * Each workspace is an L1 tab.
21
20
  */
22
21
  export const L1Tabs = ({ topLevelItems, onBack, ...props }: L1TabsProps) => {
23
22
  return (
24
23
  <>
25
24
  {topLevelItems.map((item) => {
26
25
  const type = l0ItemType(item);
27
- switch (type) {
28
- case 'tab':
29
- return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
30
- case 'collection':
31
- return <L1PanelCollection key={item.id} item={item} {...props} />;
32
- default:
33
- return null;
26
+ if (type === 'tab') {
27
+ return <L1Panel key={item.id} item={item} {...props} onBack={onBack} />;
34
28
  }
29
+ return null;
35
30
  })}
36
31
  </>
37
32
  );
38
33
  };
39
-
40
- /**
41
- *
42
- */
43
- const L1PanelCollection = ({ item, path, ...props }: L1PanelProps) => {
44
- const { useItems } = useNavTreeContext();
45
- useLoadDescendents(item);
46
- const collectionItems = useItems(item);
47
- const groupPath = useMemo(() => [...path, item.id], [item.id, path]);
48
- return (
49
- <>
50
- {collectionItems
51
- .filter((item) => l0ItemType(item) === 'tab')
52
- .map((item) => (
53
- <L1Panel key={item.id} item={item} path={groupPath} {...props} />
54
- ))}
55
- </>
56
- );
57
- };
@@ -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 = {};