@dxos/plugin-navtree 0.8.4-main.84f28bd → 0.8.4-main.a4bbb77

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 (129) hide show
  1. package/dist/lib/browser/{app-graph-builder-5J4LRWDX.mjs → app-graph-builder-BCXDEDN7.mjs} +9 -9
  2. package/dist/lib/browser/app-graph-builder-BCXDEDN7.mjs.map +7 -0
  3. package/dist/lib/browser/{chunk-TZA65AKW.mjs → chunk-6AZKY5WZ.mjs} +279 -237
  4. package/dist/lib/browser/chunk-6AZKY5WZ.mjs.map +7 -0
  5. package/dist/lib/browser/{chunk-WCARAZ7J.mjs → chunk-LPAREX6J.mjs} +4 -4
  6. package/dist/lib/browser/chunk-LPAREX6J.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-ZENLR72O.mjs +14 -0
  8. package/dist/lib/browser/chunk-ZENLR72O.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-G6COWRWW.mjs} +6 -6
  12. package/dist/lib/browser/{intent-resolver-3XYN2CBU.mjs.map → intent-resolver-G6COWRWW.mjs.map} +3 -3
  13. package/dist/lib/browser/{keyboard-QRS6WOQH.mjs → keyboard-AHSAWKXA.mjs} +4 -4
  14. package/dist/lib/{node-esm/keyboard-MOZWHFUM.mjs.map → browser/keyboard-AHSAWKXA.mjs.map} +2 -2
  15. package/dist/lib/browser/meta.json +1 -1
  16. package/dist/lib/browser/{react-surface-N2HPMYCU.mjs → react-surface-FOCB4Y5R.mjs} +10 -10
  17. package/dist/lib/browser/react-surface-FOCB4Y5R.mjs.map +7 -0
  18. package/dist/lib/browser/{state-54DY3DOY.mjs → state-2Q623V7U.mjs} +7 -7
  19. package/dist/lib/browser/state-2Q623V7U.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 +6 -6
  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/CommandsTrigger.d.ts.map +1 -1
  35. package/dist/types/src/components/NavTree/NavTree.d.ts +2 -2
  36. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  37. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +26 -3
  38. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  39. package/dist/types/src/components/NavTreeContainer.d.ts.map +1 -1
  40. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +32 -4
  41. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  42. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +1 -1
  43. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  44. package/dist/types/src/components/Sidebar/L0Menu.d.ts +2 -0
  45. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  46. package/dist/types/src/components/Sidebar/L1Panel.d.ts +5 -6
  47. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  48. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +12 -0
  49. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
  50. package/dist/types/src/components/Sidebar/index.d.ts +1 -0
  51. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -1
  52. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  53. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  54. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/index.d.ts +1 -1
  56. package/dist/types/src/components/index.d.ts.map +1 -1
  57. package/dist/types/src/components/types.d.ts +2 -2
  58. package/dist/types/src/components/types.d.ts.map +1 -1
  59. package/dist/types/src/events.d.ts.map +1 -1
  60. package/dist/types/src/experimental/Tree.d.ts +4 -5
  61. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  62. package/dist/types/src/experimental/Tree.stories.d.ts +10 -4
  63. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  64. package/dist/types/src/meta.d.ts +2 -3
  65. package/dist/types/src/meta.d.ts.map +1 -1
  66. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  67. package/dist/types/src/translations.d.ts +1 -0
  68. package/dist/types/src/translations.d.ts.map +1 -1
  69. package/dist/types/src/util.d.ts +1 -2
  70. package/dist/types/src/util.d.ts.map +1 -1
  71. package/dist/types/tsconfig.tsbuildinfo +1 -1
  72. package/package.json +49 -49
  73. package/src/NavTreePlugin.tsx +75 -76
  74. package/src/capabilities/app-graph-builder.ts +5 -5
  75. package/src/capabilities/capabilities.ts +2 -2
  76. package/src/capabilities/intent-resolver.ts +1 -1
  77. package/src/capabilities/keyboard.ts +2 -2
  78. package/src/capabilities/react-surface.tsx +5 -5
  79. package/src/capabilities/state.ts +4 -3
  80. package/src/components/CommandsDialog/CommandsDialog.tsx +5 -5
  81. package/src/components/CommandsDialog/CommandsTrigger.tsx +5 -7
  82. package/src/components/NavTree/NavTree.stories.tsx +21 -18
  83. package/src/components/NavTree/NavTree.tsx +7 -3
  84. package/src/components/NavTreeContainer.tsx +43 -18
  85. package/src/components/NavTreeDocumentTitle.tsx +2 -2
  86. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +4 -6
  87. package/src/components/NavTreeItem/NavTreeItemAction.tsx +9 -9
  88. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +9 -9
  89. package/src/components/Sidebar/L0Menu.tsx +51 -50
  90. package/src/components/Sidebar/L1Panel.tsx +71 -93
  91. package/src/components/Sidebar/L1Tabs.tsx +58 -0
  92. package/src/components/Sidebar/index.ts +1 -0
  93. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -8
  94. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  95. package/src/components/index.ts +1 -1
  96. package/src/components/types.ts +3 -2
  97. package/src/events.ts +2 -2
  98. package/src/experimental/Tree.stories.tsx +37 -36
  99. package/src/experimental/Tree.tsx +16 -34
  100. package/src/meta.ts +5 -7
  101. package/src/testing/app-graph-builder.ts +1 -1
  102. package/src/translations.ts +1 -0
  103. package/src/util.ts +2 -20
  104. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
  105. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  106. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  107. package/dist/lib/browser/chunk-TZA65AKW.mjs.map +0 -7
  108. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  109. package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
  110. package/dist/lib/browser/react-surface-N2HPMYCU.mjs.map +0 -7
  111. package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
  112. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs +0 -46
  113. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs.map +0 -7
  114. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs +0 -1429
  115. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs.map +0 -7
  116. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs +0 -18
  117. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs.map +0 -7
  118. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs +0 -16
  119. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs.map +0 -7
  120. package/dist/lib/node-esm/index.mjs +0 -150
  121. package/dist/lib/node-esm/index.mjs.map +0 -7
  122. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs +0 -48
  123. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs.map +0 -7
  124. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs +0 -52
  125. package/dist/lib/node-esm/meta.json +0 -1
  126. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs +0 -52
  127. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs.map +0 -7
  128. package/dist/lib/node-esm/state-Z3OTRC2I.mjs +0 -118
  129. package/dist/lib/node-esm/state-Z3OTRC2I.mjs.map +0 -7
@@ -5,68 +5,45 @@
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 { 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
+ // NOTE(burdon): I'm very uneasy about this pattern of declaring hooks as context props.
32
+ const { useItems } = navTreeContext;
33
+ const title = toLocalizedString(item.properties.label, t);
45
34
 
46
35
  // TODO(wittjosiah): Support multiple alternate trees.
47
- const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
36
+ const alternateTree = useItems(item, { disposition: 'alternate-tree' })[0];
48
37
  const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
49
- const handleOpen = useCallback(() => setAlternateTree?.(alternatePath, true), [alternatePath, setAlternateTree]);
50
38
  const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
51
39
  const useAlternateItems = useCallback(
52
40
  (node?: Node, { disposition }: { disposition?: string } = {}) => {
53
41
  // TODO(wittjosiah): Sorting is expensive, limit to necessary items for now.
54
- return navTreeContext.useItems(node, { disposition, sort: node?.id === alternateTree.id });
42
+ return useItems(node, { disposition, sort: node?.id === alternateTree.id });
55
43
  },
56
- [navTreeContext, alternateTree],
44
+ [alternateTree, useItems],
57
45
  );
58
46
 
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
47
  return (
71
48
  <Tabs.Tabpanel
72
49
  key={item.id}
@@ -79,30 +56,11 @@ const L1Panel = ({ open, item, path, currentItemId, onBack }: L1PanelProps) => {
79
56
  ]}
80
57
  tabIndex={-1}
81
58
  aria-label={title}
82
- {...(!open && { inert: 'true' })}
59
+ {...(!open && { inert: true })}
83
60
  >
84
61
  {item.id === currentItemId && (
85
62
  <>
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>
63
+ <L1PanelHeader path={path} item={item} currentItemId={currentItemId} onBack={onBack} />
106
64
  <div role='none' className='overflow-y-auto'>
107
65
  {isAlternate ? (
108
66
  <Tree
@@ -134,41 +92,61 @@ const L1Panel = ({ open, item, path, currentItemId, onBack }: L1PanelProps) => {
134
92
  );
135
93
  };
136
94
 
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
- };
95
+ /**
96
+ * Header row.
97
+ */
98
+ const L1PanelHeader = ({ item, path, onBack }: L1PanelProps) => {
99
+ const { t } = useTranslation(meta.id);
100
+ const { isAlternateTree, setAlternateTree, ...navTreeContext } = useNavTreeContext();
101
+ const title = toLocalizedString(item.properties.label, t);
152
102
 
153
- export type L1PanelsProps = Pick<L1PanelProps, 'open' | 'currentItemId' | 'onBack'> & {
154
- topLevelItems: Node<any>[];
155
- path: string[];
156
- };
103
+ // TODO(wittjosiah): Support multiple alternate trees.
104
+ const alternateTree = navTreeContext.useItems(item, { disposition: 'alternate-tree' })[0];
105
+ const alternatePath = useMemo(() => [...path, item.id], [item.id, path]);
106
+ const isAlternate = isAlternateTree?.(alternatePath, item) ?? false;
107
+ const backCapable = item.id.startsWith('!') || isAlternate;
108
+
109
+ const handleOpen = useCallback(() => {
110
+ setAlternateTree?.(alternatePath, true);
111
+ }, [alternatePath, setAlternateTree]);
157
112
 
158
- export const L1Panels = ({ topLevelItems, onBack, ...props }: L1PanelsProps) => {
113
+ const handleBack = useCallback(() => {
114
+ if (isAlternate) {
115
+ setAlternateTree?.(alternatePath, false);
116
+ } else {
117
+ onBack?.();
118
+ }
119
+ }, [isAlternate, alternatePath, setAlternateTree, onBack]);
120
+
121
+ // TODO(burdon): Reuse same grid as tree?
159
122
  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
- </>
123
+ <div className='flex is-full items-center border-be border-subduedSeparator app-drag density-coarse'>
124
+ <div className='is-6' />
125
+ <h2 className='flex-1 truncate min-is-0'>{title}</h2>
126
+ {(backCapable && (
127
+ <IconButton
128
+ variant='ghost'
129
+ icon='ph--arrow-u-down-left--regular'
130
+ iconOnly
131
+ size={5}
132
+ label={t('button back')}
133
+ classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
134
+ onClick={handleBack}
135
+ />
136
+ )) ||
137
+ (alternateTree && !isAlternate && (
138
+ <IconButton
139
+ data-testid='treeView.alternateTreeButton'
140
+ variant='ghost'
141
+ icon={alternateTree.properties.icon ?? 'ph--placeholder--regular'}
142
+ iconOnly
143
+ size={5}
144
+ label={toLocalizedString(alternateTree.properties.label ?? alternateTree.id, t)}
145
+ classNames={[hoverableControlItem, hoverableOpenControlItem, 'pointer-fine:pli-1']}
146
+ onClick={handleOpen}
147
+ />
148
+ ))}
149
+ <NavTreeItemColumns open path={path} item={item} density='coarse' />
150
+ </div>
173
151
  );
174
152
  };
@@ -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 = {};
@@ -7,6 +7,8 @@ import React from 'react';
7
7
  import { Avatar, type AvatarStatus, type Size } from '@dxos/react-ui';
8
8
  import { hexToFallback } from '@dxos/util';
9
9
 
10
+ import { L0ItemActiveTabIndicator } from '../Sidebar';
11
+
10
12
  export type UserAccountAvatarProps = {
11
13
  size?: Size;
12
14
  userId?: string;
@@ -19,17 +21,23 @@ export const UserAccountAvatar = ({ size, userId, hue, emoji, status }: UserAcco
19
21
  const fallbackValue = hexToFallback(userId ?? '0');
20
22
 
21
23
  return (
22
- <div className='grid place-items-center' data-joyride='welcome/account'>
23
- <Avatar.Root>
24
- <Avatar.Content
25
- variant='circle'
26
- size={size ?? 12}
27
- status={status ?? 'active'}
28
- hue={hue || fallbackValue.hue}
29
- fallback={emoji || fallbackValue.emoji}
30
- data-testid='treeView.userAccount'
31
- />
32
- </Avatar.Root>
33
- </div>
24
+ <>
25
+ <L0ItemActiveTabIndicator classNames='inset-block-6' />
26
+ <div
27
+ className='grid place-items-center dx-focus-ring-group-indicator rounded-full'
28
+ data-joyride='welcome/account'
29
+ >
30
+ <Avatar.Root>
31
+ <Avatar.Content
32
+ variant='circle'
33
+ size={size ?? 12}
34
+ status={status ?? 'active'}
35
+ hue={hue || fallbackValue.hue}
36
+ fallback={emoji || fallbackValue.emoji}
37
+ data-testid='treeView.userAccount'
38
+ />
39
+ </Avatar.Root>
40
+ </div>
41
+ </>
34
42
  );
35
43
  };
@@ -7,4 +7,4 @@ export * from './NavTree';
7
7
  export * from './NavTreeContainer';
8
8
  export * from './NavTreeContext';
9
9
  export * from './NavTreeDocumentTitle';
10
- export * from './types';
10
+ export type * from './types';
@@ -9,9 +9,10 @@ import type { Density } from '@dxos/react-ui';
9
9
  import type { TreeProps } from '@dxos/react-ui-list';
10
10
  import type { MaybePromise } from '@dxos/util';
11
11
 
12
- import type { L1PanelProps } from './Sidebar';
13
12
  import type { FlattenedActions, NavTreeItemGraphNode } from '../types';
14
13
 
14
+ import type { L1PanelProps } from './Sidebar';
15
+
15
16
  export type NavTreeContextValue = Pick<
16
17
  TreeProps<NavTreeItemGraphNode, { disposition?: string; sort?: boolean }>,
17
18
  'useItems' | 'getProps' | 'isCurrent' | 'isOpen' | 'canDrop' | 'onOpenChange' | 'onSelect'
@@ -21,7 +22,7 @@ export type NavTreeContextValue = Pick<
21
22
  topbar?: boolean;
22
23
  popoverAnchorId?: string;
23
24
  renderItemEnd?: FC<{ node: Node; open: boolean }>;
24
- getActions: (node: Node) => FlattenedActions;
25
+ useActions: (node: Node) => FlattenedActions;
25
26
  loadDescendents?: (node: Node) => MaybePromise<void>;
26
27
  isAlternateTree?: (path: string[], item: NavTreeItemGraphNode) => boolean;
27
28
  setAlternateTree?: (path: string[], open: boolean) => void;
package/src/events.ts CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  import { Events } from '@dxos/app-framework';
6
6
 
7
- import { NAVTREE_PLUGIN } from './meta';
7
+ import { meta } from './meta';
8
8
 
9
9
  export namespace NavTreeEvents {
10
- export const StateReady = Events.createStateEvent(NAVTREE_PLUGIN);
10
+ export const StateReady = Events.createStateEvent(meta.id);
11
11
  }
@@ -2,15 +2,13 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
- import { House, List, Planet, PlusCircle, Sailboat } from '@phosphor-icons/react';
8
5
  import { type Meta } from '@storybook/react-vite';
9
6
  import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
10
7
 
11
8
  import { faker } from '@dxos/random';
12
- import { modalSurface, mx } from '@dxos/react-ui-theme';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { Icon, IconButton } from '@dxos/react-ui';
10
+ import { withTheme } from '@dxos/react-ui/testing';
11
+ import { mx } from '@dxos/react-ui-theme';
14
12
 
15
13
  import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visitor } from './Tree';
16
14
 
@@ -48,7 +46,7 @@ const data: TreeNodeData[] = [
48
46
  id: faker.string.uuid(),
49
47
  title: 'plugins/Personal Space',
50
48
  color: 'text-green-400',
51
- Icon: House,
49
+ iconName: 'ph--house--regular',
52
50
  children: [
53
51
  {
54
52
  id: faker.string.uuid(),
@@ -83,7 +81,7 @@ const data: TreeNodeData[] = [
83
81
  {
84
82
  id: faker.string.uuid(),
85
83
  title: faker.commerce.productName(),
86
- Icon: Planet,
84
+ iconName: 'ph--planet--regular',
87
85
  children: [
88
86
  {
89
87
  id: faker.string.uuid(),
@@ -94,38 +92,17 @@ const data: TreeNodeData[] = [
94
92
  {
95
93
  id: faker.string.uuid(),
96
94
  title: faker.commerce.productName(),
97
- Icon: Sailboat,
95
+ iconName: 'ph--sailboat--regular',
98
96
  },
99
97
  {
100
98
  id: faker.string.uuid(),
101
99
  title: faker.commerce.productName(),
102
- Icon: Planet,
100
+ iconName: 'ph--planet--regular',
103
101
  },
104
102
  ],
105
103
  },
106
104
  ];
107
105
 
108
- export const Default = () => {
109
- return <Container sidebar={<Sidebar mutate />} />;
110
- };
111
-
112
- export const Visitor = () => {
113
- const [items] = useState(data);
114
- const root = items[0];
115
- const [openItems] = useState<ItemMap>({ [root.children![0].id]: true });
116
- return (
117
- <div className='flex flex-col'>
118
- {Array.from(visitor(root, openItems)).map(({ node: { id, title }, depth }) => (
119
- <div key={id} className='grid grid-cols-[40px_400px_400px] font-mono'>
120
- <div>{depth}</div>
121
- <div>{id}</div>
122
- <div>{title}</div>
123
- </div>
124
- ))}
125
- </div>
126
- );
127
- };
128
-
129
106
  const Sidebar = ({ mutate }: { mutate?: boolean }) => {
130
107
  const [items, setItems] = useState(data);
131
108
  const root = items[0];
@@ -181,7 +158,7 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
181
158
  setItems((items) => {
182
159
  items[0].children?.push({
183
160
  id: faker.string.uuid(),
184
- Icon: Planet,
161
+ iconName: 'ph--planet--regular',
185
162
  title: faker.commerce.productName(),
186
163
  });
187
164
 
@@ -213,20 +190,44 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
213
190
  </div>
214
191
 
215
192
  <div className='flex items-center my-2 px-2 gap-2'>
216
- <PlusCircle onClick={handleCreateSpace} />
193
+ <IconButton icon='ph--plus-circle--regular' iconOnly label='Create space' onClick={handleCreateSpace} />
217
194
  <span className='grow text-sm' onClick={handleCreateSpace}>
218
195
  New space
219
196
  </span>
220
- <List />
197
+ <Icon icon='ph--list--regular' />
221
198
  </div>
222
199
  </div>
223
200
  );
224
201
  };
225
202
 
226
- const meta: Meta = {
203
+ const meta = {
227
204
  title: 'plugins/plugin-navtree/experimental/Tree',
228
205
  component: Tree,
229
- decorators: [withTheme, withLayout({ fullscreen: true, classNames: modalSurface })],
230
- };
206
+ decorators: [withTheme],
207
+ parameters: {
208
+ layout: 'column',
209
+ },
210
+ } satisfies Meta<typeof Tree>;
231
211
 
232
212
  export default meta;
213
+
214
+ export const Default = () => {
215
+ return <Container sidebar={<Sidebar mutate />} />;
216
+ };
217
+
218
+ export const Visitor = () => {
219
+ const [items] = useState(data);
220
+ const root = items[0];
221
+ const [openItems] = useState<ItemMap>({ [root.children![0].id]: true });
222
+ return (
223
+ <div className='flex flex-col'>
224
+ {Array.from(visitor(root, openItems)).map(({ node: { id, title }, depth }) => (
225
+ <div key={id} className='grid grid-cols-[40px_400px_400px] font-mono'>
226
+ <div>{depth}</div>
227
+ <div>{id}</div>
228
+ <div>{title}</div>
229
+ </div>
230
+ ))}
231
+ </div>
232
+ );
233
+ };
@@ -2,34 +2,25 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { CaretRight, Circle, File, Folder, type Icon, type IconProps, Plus, UserCircle } from '@phosphor-icons/react';
6
5
  import React, { type HTMLAttributes, type PropsWithChildren } from 'react';
7
6
 
8
- import { type ClassNameValue, type Size } from '@dxos/react-ui';
9
- import { getSize, mx } from '@dxos/react-ui-theme';
10
-
11
- // TODO(burdon): Rounded border if first/last.
12
- const styles = {
13
- hover: 'hover:bg-slate-400 dark:hover:bg-slate-800',
14
- selected: '!bg-slate-300 dark:!bg-slate-700',
15
- };
7
+ import { type ClassNameValue, Icon, type Size } from '@dxos/react-ui';
8
+ import { mx } from '@dxos/react-ui-theme';
16
9
 
17
10
  export const IconButton = ({
18
- Icon,
11
+ iconName,
19
12
  classNames,
20
13
  size = 4,
21
14
  onClick,
22
- ...props
23
15
  }: {
24
- Icon: Icon;
16
+ iconName: string;
25
17
  classNames?: ClassNameValue;
26
18
  size?: Size;
27
- } & Pick<IconProps, 'weight'> &
28
- Pick<HTMLAttributes<HTMLDivElement>, 'onClick'>) => {
19
+ } & Pick<HTMLAttributes<HTMLDivElement>, 'onClick'>) => {
29
20
  // TODO(burdon): Density aware.
30
21
  return (
31
22
  <div className={mx('flex w-6 h-6 items-center justify-center select-none', classNames)} onClick={onClick}>
32
- <Icon className={mx('cursor-pointer', getSize(size))} {...props} />
23
+ <Icon icon={iconName} classNames='cursor-pointer' size={size} />
33
24
  </div>
34
25
  );
35
26
  };
@@ -37,7 +28,7 @@ export const IconButton = ({
37
28
  export type TreeNodeData = {
38
29
  id: string;
39
30
  title: string;
40
- Icon?: Icon;
31
+ iconName?: string;
41
32
  color?: string;
42
33
  children?: TreeNodeData[];
43
34
  };
@@ -123,9 +114,8 @@ const StateIcon = ({ node, open, selected, active }: TreeNodeProps) => {
123
114
  // TODO(burdon): No animation if opening/closing folder.
124
115
  return (
125
116
  <IconButton
126
- Icon={isActive ? UserCircle : Circle}
117
+ iconName={isActive ? 'ph--user-circle--regular' : 'ph--circle--regular'}
127
118
  size={4}
128
- weight={selected?.[id] ? 'fill' : 'duotone'}
129
119
  classNames={mx(
130
120
  'text-slate-500',
131
121
  !isChildActive && 'opacity-0 transition duration-500',
@@ -143,7 +133,7 @@ const OpenIcon = ({
143
133
  return (
144
134
  (children?.length && open && (
145
135
  <IconButton
146
- Icon={CaretRight}
136
+ iconName='ph--caret-right--regular'
147
137
  size={3}
148
138
  classNames={mx('transition duration-200', open?.[id] ? 'rotate-90' : 'transform-none')}
149
139
  onClick={(ev) => {
@@ -157,23 +147,15 @@ const OpenIcon = ({
157
147
 
158
148
  // TODO(burdon): Drag handle,
159
149
  const ItemIcon = ({
160
- node: { children, Icon = children?.length ? Folder : File, color },
150
+ node: { children, iconName = children?.length ? 'ph--folder--regular' : 'ph--file--regular', color },
161
151
  }: Pick<TreeNodeProps, 'node'>) => {
162
- return (
163
- (Icon && (
164
- <IconButton
165
- Icon={Icon}
166
- classNames={color ?? 'text-neutral-700 dark:text-neutral-300'}
167
- weight={color ? 'duotone' : 'regular'}
168
- />
169
- )) || <div />
170
- );
152
+ return (iconName && <IconButton iconName={iconName} classNames={color ?? 'text-subdued'} />) || <div />;
171
153
  };
172
154
 
173
155
  const MenuItem = ({ node: { id }, onMenuAction }: Pick<TreeNodeProps, 'node' | 'onMenuAction'>) => {
174
156
  return (
175
157
  <IconButton
176
- Icon={Plus}
158
+ iconName='ph--plus--regular'
177
159
  classNames='invisible group-hover:visible'
178
160
  onClick={(ev) => {
179
161
  ev.stopPropagation();
@@ -211,9 +193,9 @@ export const TreeNodeRow = (props: TreeNodeProps & { className?: string }) => {
211
193
  <Grid
212
194
  style={{ paddingLeft: `${(depth - 1) * 24}px` }}
213
195
  className={mx(
214
- 'group w-full items-center cursor-pointer',
215
- styles.hover,
216
- selected?.[id] && styles.selected,
196
+ 'group w-full items-center cursor-pointer bg-hoverSurface',
197
+ // TODO(burdon): Use data-active.
198
+ selected?.[id] && 'bg-activeSurface',
217
199
  className,
218
200
  )}
219
201
  onClick={() => selected && onChangeSelected?.(id, !selected[id])}
@@ -256,7 +238,7 @@ export const TreeChildNodes = ({
256
238
  } = props;
257
239
  return (
258
240
  <div className={mx('flex flex-col mt-0.5 gap-0.5', className)}>
259
- {children?.map((child, i) => (
241
+ {children?.map((child) => (
260
242
  <TreeNode key={child.id} {...props} depth={depth + 1} node={child} ancestors={[...ancestors, props.node]} />
261
243
  ))}
262
244
  </div>
package/src/meta.ts CHANGED
@@ -4,14 +4,12 @@
4
4
 
5
5
  import { type PluginMeta } from '@dxos/app-framework';
6
6
 
7
- export const NAVTREE_PLUGIN = 'dxos.org/plugin/navtree';
7
+ export const meta: PluginMeta = {
8
+ id: 'dxos.org/plugin/navtree',
9
+ name: 'Navtree',
10
+ };
8
11
 
9
12
  // TODO(wittjosiah): Factor out.
10
13
  export const KEY_BINDING = 'KeyBinding';
11
14
  // TODO(wittjosiah): Factor out.
12
- export const COMMANDS_DIALOG = `${NAVTREE_PLUGIN}/CommandsDialog`;
13
-
14
- export const meta: PluginMeta = {
15
- id: NAVTREE_PLUGIN,
16
- name: 'Navtree',
17
- };
15
+ export const COMMANDS_DIALOG = `${meta.id}/CommandsDialog`;