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

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 (221) hide show
  1. package/dist/lib/browser/index.mjs +168 -115
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/types/src/NavTreePlugin.d.ts +2 -1
  5. package/dist/types/src/NavTreePlugin.d.ts.map +1 -1
  6. package/dist/types/src/capabilities/app-graph-builder.d.ts +4 -2
  7. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  8. package/dist/types/src/capabilities/index.d.ts +7 -105
  9. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  10. package/dist/types/src/capabilities/keyboard.d.ts +3 -2
  11. package/dist/types/src/capabilities/keyboard.d.ts.map +1 -1
  12. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  13. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  14. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  15. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  16. package/dist/types/src/capabilities/state.d.ts +4 -18
  17. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  18. package/dist/types/src/components/NavTree/NavTree.d.ts +12 -5
  19. package/dist/types/src/components/NavTree/NavTree.d.ts.map +1 -1
  20. package/dist/types/src/components/NavTree/NavTree.stories.d.ts +26 -5
  21. package/dist/types/src/components/NavTree/NavTree.stories.d.ts.map +1 -1
  22. package/dist/types/src/components/{NavTreeContext.d.ts → NavTreeContext/NavTreeContext.d.ts} +1 -1
  23. package/dist/types/src/components/NavTreeContext/NavTreeContext.d.ts.map +1 -0
  24. package/dist/types/src/components/NavTreeContext/index.d.ts +2 -0
  25. package/dist/types/src/components/NavTreeContext/index.d.ts.map +1 -0
  26. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts +13 -9
  27. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.d.ts.map +1 -1
  28. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts +33 -4
  29. package/dist/types/src/components/NavTreeItem/NavTreeItemAction.stories.d.ts.map +1 -1
  30. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts +2 -2
  31. package/dist/types/src/components/NavTreeItem/NavTreeItemColumns.d.ts.map +1 -1
  32. package/dist/types/src/components/Sidebar/L0Menu.d.ts +10 -5
  33. package/dist/types/src/components/Sidebar/L0Menu.d.ts.map +1 -1
  34. package/dist/types/src/components/Sidebar/L1Panel.d.ts +4 -8
  35. package/dist/types/src/components/Sidebar/L1Panel.d.ts.map +1 -1
  36. package/dist/types/src/components/Sidebar/L1Tabs.d.ts +13 -0
  37. package/dist/types/src/components/Sidebar/L1Tabs.d.ts.map +1 -0
  38. package/dist/types/src/components/Sidebar/index.d.ts +1 -0
  39. package/dist/types/src/components/Sidebar/index.d.ts.map +1 -1
  40. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.d.ts.map +1 -1
  41. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts +8 -5
  42. package/dist/types/src/components/UserAccountAvatar/UserAccountAvatar.stories.d.ts.map +1 -1
  43. package/dist/types/src/components/index.d.ts +1 -4
  44. package/dist/types/src/components/index.d.ts.map +1 -1
  45. package/dist/types/src/components/types.d.ts +8 -16
  46. package/dist/types/src/components/types.d.ts.map +1 -1
  47. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts +6 -0
  48. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.d.ts.map +1 -0
  49. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts +29 -0
  50. package/dist/types/src/containers/CommandsDialogContent/CommandsDialogContent.stories.d.ts.map +1 -0
  51. package/dist/types/src/containers/CommandsDialogContent/index.d.ts +2 -0
  52. package/dist/types/src/containers/CommandsDialogContent/index.d.ts.map +1 -0
  53. package/dist/types/src/containers/CommandsTrigger/CommandsTrigger.d.ts.map +1 -0
  54. package/dist/types/src/containers/CommandsTrigger/index.d.ts +2 -0
  55. package/dist/types/src/containers/CommandsTrigger/index.d.ts.map +1 -0
  56. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts +9 -0
  57. package/dist/types/src/containers/NavTreeContainer/NavTreeContainer.d.ts.map +1 -0
  58. package/dist/types/src/containers/NavTreeContainer/index.d.ts +3 -0
  59. package/dist/types/src/containers/NavTreeContainer/index.d.ts.map +1 -0
  60. package/dist/types/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.d.ts +1 -1
  61. package/dist/types/src/containers/NavTreeDocumentTitle/NavTreeDocumentTitle.d.ts.map +1 -0
  62. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts +2 -0
  63. package/dist/types/src/containers/NavTreeDocumentTitle/index.d.ts.map +1 -0
  64. package/dist/types/src/containers/index.d.ts +7 -0
  65. package/dist/types/src/containers/index.d.ts.map +1 -0
  66. package/dist/types/src/experimental/Tree.d.ts +4 -5
  67. package/dist/types/src/experimental/Tree.d.ts.map +1 -1
  68. package/dist/types/src/experimental/Tree.stories.d.ts +23 -6
  69. package/dist/types/src/experimental/Tree.stories.d.ts.map +1 -1
  70. package/dist/types/src/hooks/index.d.ts +5 -0
  71. package/dist/types/src/hooks/index.d.ts.map +1 -0
  72. package/dist/types/src/hooks/useActions.d.ts +5 -0
  73. package/dist/types/src/hooks/useActions.d.ts.map +1 -0
  74. package/dist/types/src/hooks/useLoadDescendents.d.ts +6 -0
  75. package/dist/types/src/hooks/useLoadDescendents.d.ts.map +1 -0
  76. package/dist/types/src/hooks/useNavTreeModel.d.ts +7 -0
  77. package/dist/types/src/hooks/useNavTreeModel.d.ts.map +1 -0
  78. package/dist/types/src/hooks/useNavTreeState.d.ts +22 -0
  79. package/dist/types/src/hooks/useNavTreeState.d.ts.map +1 -0
  80. package/dist/types/src/index.d.ts +1 -2
  81. package/dist/types/src/index.d.ts.map +1 -1
  82. package/dist/types/src/meta.d.ts +3 -4
  83. package/dist/types/src/meta.d.ts.map +1 -1
  84. package/dist/types/src/operations/expose.d.ts +5 -0
  85. package/dist/types/src/operations/expose.d.ts.map +1 -0
  86. package/dist/types/src/operations/index.d.ts +3 -0
  87. package/dist/types/src/operations/index.d.ts.map +1 -0
  88. package/dist/types/src/testing/app-graph-builder.d.ts +2 -3
  89. package/dist/types/src/testing/app-graph-builder.d.ts.map +1 -1
  90. package/dist/types/src/testing/generator.d.ts +5 -6
  91. package/dist/types/src/testing/generator.d.ts.map +1 -1
  92. package/dist/types/src/testing/update-state.d.ts +2 -2
  93. package/dist/types/src/testing/update-state.d.ts.map +1 -1
  94. package/dist/types/src/translations.d.ts +9 -9
  95. package/dist/types/src/translations.d.ts.map +1 -1
  96. package/dist/types/src/types/capabilities.d.ts +16 -0
  97. package/dist/types/src/types/capabilities.d.ts.map +1 -0
  98. package/dist/types/src/types/events.d.ts +5 -0
  99. package/dist/types/src/types/events.d.ts.map +1 -0
  100. package/dist/types/src/types/index.d.ts +4 -0
  101. package/dist/types/src/types/index.d.ts.map +1 -0
  102. package/dist/types/src/{types.d.ts → types/schema.d.ts} +10 -9
  103. package/dist/types/src/types/schema.d.ts.map +1 -0
  104. package/dist/types/src/util.d.ts +12 -9
  105. package/dist/types/src/util.d.ts.map +1 -1
  106. package/dist/types/tsconfig.tsbuildinfo +1 -1
  107. package/package.json +67 -56
  108. package/src/NavTreePlugin.tsx +66 -85
  109. package/src/capabilities/app-graph-builder.ts +34 -44
  110. package/src/capabilities/index.ts +10 -8
  111. package/src/capabilities/keyboard.ts +58 -51
  112. package/src/capabilities/operation-handler.ts +16 -0
  113. package/src/capabilities/react-surface.tsx +46 -38
  114. package/src/capabilities/state.ts +129 -84
  115. package/src/components/NavTree/NavTree.stories.tsx +110 -102
  116. package/src/components/NavTree/NavTree.tsx +71 -35
  117. package/src/components/{NavTreeContext.tsx → NavTreeContext/NavTreeContext.tsx} +1 -1
  118. package/src/components/NavTreeContext/index.ts +5 -0
  119. package/src/components/NavTreeItem/NavTreeItemAction.stories.tsx +25 -19
  120. package/src/components/NavTreeItem/NavTreeItemAction.tsx +73 -49
  121. package/src/components/NavTreeItem/NavTreeItemColumns.tsx +70 -38
  122. package/src/components/Sidebar/L0Menu.tsx +176 -210
  123. package/src/components/Sidebar/L1Panel.tsx +271 -135
  124. package/src/components/Sidebar/L1Tabs.tsx +42 -0
  125. package/src/components/Sidebar/index.ts +1 -0
  126. package/src/components/UserAccountAvatar/UserAccountAvatar.stories.tsx +9 -9
  127. package/src/components/UserAccountAvatar/UserAccountAvatar.tsx +20 -12
  128. package/src/components/index.ts +2 -4
  129. package/src/components/types.ts +15 -19
  130. package/src/containers/CommandsDialogContent/CommandsDialogContent.stories.tsx +45 -0
  131. package/src/containers/CommandsDialogContent/CommandsDialogContent.tsx +127 -0
  132. package/src/containers/CommandsDialogContent/index.ts +5 -0
  133. package/src/containers/CommandsTrigger/CommandsTrigger.tsx +28 -0
  134. package/src/containers/CommandsTrigger/index.ts +5 -0
  135. package/src/containers/NavTreeContainer/NavTreeContainer.tsx +280 -0
  136. package/src/containers/NavTreeContainer/index.ts +6 -0
  137. package/src/{components → containers/NavTreeDocumentTitle}/NavTreeDocumentTitle.tsx +5 -4
  138. package/src/containers/NavTreeDocumentTitle/index.ts +5 -0
  139. package/src/containers/index.ts +12 -0
  140. package/src/experimental/Tree.stories.tsx +110 -115
  141. package/src/experimental/Tree.tsx +16 -34
  142. package/src/hooks/index.ts +8 -0
  143. package/src/hooks/useActions.ts +34 -0
  144. package/src/hooks/useLoadDescendents.ts +23 -0
  145. package/src/hooks/useNavTreeModel.ts +118 -0
  146. package/src/hooks/useNavTreeState.ts +42 -0
  147. package/src/index.ts +1 -2
  148. package/src/meta.ts +11 -8
  149. package/src/operations/expose.ts +42 -0
  150. package/src/operations/index.ts +7 -0
  151. package/src/testing/app-graph-builder.ts +167 -238
  152. package/src/testing/generator.ts +41 -39
  153. package/src/testing/update-state.ts +6 -6
  154. package/src/translations.ts +9 -9
  155. package/src/types/capabilities.ts +21 -0
  156. package/src/types/events.ts +12 -0
  157. package/src/types/index.ts +7 -0
  158. package/src/{types.ts → types/schema.ts} +9 -8
  159. package/src/util.ts +26 -33
  160. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs +0 -45
  161. package/dist/lib/browser/app-graph-builder-5J4LRWDX.mjs.map +0 -7
  162. package/dist/lib/browser/chunk-ERDGQTH5.mjs +0 -16
  163. package/dist/lib/browser/chunk-ERDGQTH5.mjs.map +0 -7
  164. package/dist/lib/browser/chunk-TZA65AKW.mjs +0 -1428
  165. package/dist/lib/browser/chunk-TZA65AKW.mjs.map +0 -7
  166. package/dist/lib/browser/chunk-WCARAZ7J.mjs +0 -15
  167. package/dist/lib/browser/chunk-WCARAZ7J.mjs.map +0 -7
  168. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs +0 -47
  169. package/dist/lib/browser/intent-resolver-3XYN2CBU.mjs.map +0 -7
  170. package/dist/lib/browser/keyboard-QRS6WOQH.mjs +0 -51
  171. package/dist/lib/browser/keyboard-QRS6WOQH.mjs.map +0 -7
  172. package/dist/lib/browser/react-surface-N2HPMYCU.mjs +0 -51
  173. package/dist/lib/browser/react-surface-N2HPMYCU.mjs.map +0 -7
  174. package/dist/lib/browser/state-54DY3DOY.mjs +0 -117
  175. package/dist/lib/browser/state-54DY3DOY.mjs.map +0 -7
  176. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs +0 -46
  177. package/dist/lib/node-esm/app-graph-builder-GY7GCTGU.mjs.map +0 -7
  178. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs +0 -1429
  179. package/dist/lib/node-esm/chunk-CY7QWR4R.mjs.map +0 -7
  180. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs +0 -18
  181. package/dist/lib/node-esm/chunk-P2JGW3WR.mjs.map +0 -7
  182. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs +0 -16
  183. package/dist/lib/node-esm/chunk-V3QXFDDP.mjs.map +0 -7
  184. package/dist/lib/node-esm/index.mjs +0 -150
  185. package/dist/lib/node-esm/index.mjs.map +0 -7
  186. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs +0 -48
  187. package/dist/lib/node-esm/intent-resolver-R7UNPSZY.mjs.map +0 -7
  188. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs +0 -52
  189. package/dist/lib/node-esm/keyboard-MOZWHFUM.mjs.map +0 -7
  190. package/dist/lib/node-esm/meta.json +0 -1
  191. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs +0 -52
  192. package/dist/lib/node-esm/react-surface-EW5FDMW4.mjs.map +0 -7
  193. package/dist/lib/node-esm/state-Z3OTRC2I.mjs +0 -118
  194. package/dist/lib/node-esm/state-Z3OTRC2I.mjs.map +0 -7
  195. package/dist/types/src/capabilities/capabilities.d.ts +0 -21
  196. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  197. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  198. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  199. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts +0 -5
  200. package/dist/types/src/components/CommandsDialog/CommandsDialog.d.ts.map +0 -1
  201. package/dist/types/src/components/CommandsDialog/CommandsTrigger.d.ts.map +0 -1
  202. package/dist/types/src/components/CommandsDialog/index.d.ts +0 -3
  203. package/dist/types/src/components/CommandsDialog/index.d.ts.map +0 -1
  204. package/dist/types/src/components/NavTreeContainer.d.ts +0 -9
  205. package/dist/types/src/components/NavTreeContainer.d.ts.map +0 -1
  206. package/dist/types/src/components/NavTreeContext.d.ts.map +0 -1
  207. package/dist/types/src/components/NavTreeDocumentTitle.d.ts.map +0 -1
  208. package/dist/types/src/events.d.ts +0 -4
  209. package/dist/types/src/events.d.ts.map +0 -1
  210. package/dist/types/src/hooks.d.ts +0 -3
  211. package/dist/types/src/hooks.d.ts.map +0 -1
  212. package/dist/types/src/types.d.ts.map +0 -1
  213. package/src/capabilities/capabilities.ts +0 -20
  214. package/src/capabilities/intent-resolver.ts +0 -37
  215. package/src/components/CommandsDialog/CommandsDialog.tsx +0 -118
  216. package/src/components/CommandsDialog/CommandsTrigger.tsx +0 -37
  217. package/src/components/CommandsDialog/index.ts +0 -6
  218. package/src/components/NavTreeContainer.tsx +0 -314
  219. package/src/events.ts +0 -11
  220. package/src/hooks.ts +0 -21
  221. /package/dist/types/src/{components/CommandsDialog → containers/CommandsTrigger}/CommandsTrigger.d.ts +0 -0
@@ -2,41 +2,17 @@
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
- import React, { type JSX, type PropsWithChildren, useEffect, useState } from 'react';
6
+ import React, { useEffect, useState } from 'react';
10
7
 
11
- import { faker } from '@dxos/random';
12
- import { modalSurface, mx } from '@dxos/react-ui-theme';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
8
+ import { random } from '@dxos/random';
9
+ import { Icon, IconButton, Panel, ScrollArea } from '@dxos/react-ui';
10
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
11
+ import { mx } from '@dxos/ui-theme';
14
12
 
15
13
  import { type ItemMap, Tree, type TreeNodeData, type TreeProps, visitNodes, visitor } from './Tree';
16
14
 
17
- faker.seed(1234);
18
-
19
- /**
20
- * Space:
21
- * - Space panels are separated from each other to give them more weight than folders.
22
- * - Private space isn't different (just first, different icon).
23
- * - Edit titles in place.
24
- *
25
- * Navigation:
26
- * - up/down across spaces/folder peers
27
- * - right to enter space/folder children
28
- * - left to parent
29
- */
30
- const Container = ({ children, sidebar }: PropsWithChildren<{ sidebar: JSX.Element }>) => {
31
- return (
32
- <div className='flex'>
33
- {/* TODO(burdon): Custom thin scrollbar. */}
34
- {/* TODO(burdon): Horizontal scrolling within navtree? */}
35
- <div className='flex flex-col overflow-y-auto w-[300px] bg-neutral-100 dark:bg-neutral-950'>{sidebar}</div>
36
- <div className='flex flex-col grow overflow-hidden'>{children}</div>
37
- </div>
38
- );
39
- };
15
+ random.seed(1234);
40
16
 
41
17
  // TODO(burdon): Generate.
42
18
  const data: TreeNodeData[] = [
@@ -45,34 +21,34 @@ const data: TreeNodeData[] = [
45
21
  title: 'plugins/Root',
46
22
  children: [
47
23
  {
48
- id: faker.string.uuid(),
24
+ id: random.string.uuid(),
49
25
  title: 'plugins/Personal Space',
50
26
  color: 'text-green-400',
51
- Icon: House,
27
+ iconName: 'ph--house--regular',
52
28
  children: [
53
29
  {
54
- id: faker.string.uuid(),
55
- title: faker.commerce.productName(),
30
+ id: random.string.uuid(),
31
+ title: random.commerce.productName(),
56
32
  },
57
33
  {
58
- id: faker.string.uuid(),
59
- title: faker.commerce.productName(),
34
+ id: random.string.uuid(),
35
+ title: random.commerce.productName(),
60
36
  },
61
37
  {
62
- id: faker.string.uuid(),
63
- title: faker.commerce.productName(),
38
+ id: random.string.uuid(),
39
+ title: random.commerce.productName(),
64
40
  children: [
65
41
  {
66
- id: faker.string.uuid(),
67
- title: faker.commerce.productName(),
42
+ id: random.string.uuid(),
43
+ title: random.commerce.productName(),
68
44
  children: [
69
45
  {
70
- id: faker.string.uuid(),
71
- title: faker.commerce.productName(),
46
+ id: random.string.uuid(),
47
+ title: random.commerce.productName(),
72
48
  },
73
49
  {
74
- id: faker.string.uuid(),
75
- title: faker.commerce.productName(),
50
+ id: random.string.uuid(),
51
+ title: random.commerce.productName(),
76
52
  },
77
53
  ],
78
54
  },
@@ -81,51 +57,30 @@ const data: TreeNodeData[] = [
81
57
  ],
82
58
  },
83
59
  {
84
- id: faker.string.uuid(),
85
- title: faker.commerce.productName(),
86
- Icon: Planet,
60
+ id: random.string.uuid(),
61
+ title: random.commerce.productName(),
62
+ iconName: 'ph--planet--regular',
87
63
  children: [
88
64
  {
89
- id: faker.string.uuid(),
90
- title: faker.commerce.productName(),
65
+ id: random.string.uuid(),
66
+ title: random.commerce.productName(),
91
67
  },
92
68
  ],
93
69
  },
94
70
  {
95
- id: faker.string.uuid(),
96
- title: faker.commerce.productName(),
97
- Icon: Sailboat,
71
+ id: random.string.uuid(),
72
+ title: random.commerce.productName(),
73
+ iconName: 'ph--sailboat--regular',
98
74
  },
99
75
  {
100
- id: faker.string.uuid(),
101
- title: faker.commerce.productName(),
102
- Icon: Planet,
76
+ id: random.string.uuid(),
77
+ title: random.commerce.productName(),
78
+ iconName: 'ph--planet--regular',
103
79
  },
104
80
  ],
105
81
  },
106
82
  ];
107
83
 
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
84
  const Sidebar = ({ mutate }: { mutate?: boolean }) => {
130
85
  const [items, setItems] = useState(data);
131
86
  const root = items[0];
@@ -157,7 +112,7 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
157
112
  }
158
113
  }, []);
159
114
 
160
- const handleCreateItem: TreeProps['onMenuAction'] = (id, action) => {
115
+ const handleCreateItem: TreeProps['onMenuAction'] = (id) => {
161
116
  setItems((items) => {
162
117
  let parent: TreeNodeData | undefined;
163
118
  visitNodes(root, (node) => {
@@ -167,8 +122,8 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
167
122
  });
168
123
 
169
124
  (parent!.children ??= []).push({
170
- id: faker.string.uuid(),
171
- title: faker.commerce.productName(),
125
+ id: random.string.uuid(),
126
+ title: random.commerce.productName(),
172
127
  });
173
128
 
174
129
  setOpenItems((open) => ({ ...open, [parent!.id]: true }));
@@ -180,9 +135,9 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
180
135
  const handleCreateSpace = () => {
181
136
  setItems((items) => {
182
137
  items[0].children?.push({
183
- id: faker.string.uuid(),
184
- Icon: Planet,
185
- title: faker.commerce.productName(),
138
+ id: random.string.uuid(),
139
+ iconName: 'ph--planet--regular',
140
+ title: random.commerce.productName(),
186
141
  });
187
142
 
188
143
  return { ...items };
@@ -190,43 +145,83 @@ const Sidebar = ({ mutate }: { mutate?: boolean }) => {
190
145
  };
191
146
 
192
147
  return (
193
- <div className='flex flex-col overflow-hidden'>
194
- <div className='flex flex-col overflow-y-auto'>
195
- <Tree
196
- className='p-0.5 gap-1'
197
- node={root}
198
- open={openItems}
199
- selected={selectedItems}
200
- active={activeItems}
201
- onChangeOpen={(id, open) => setOpenItems((items) => ({ ...items, [id]: open }))}
202
- onChangeSelected={(id, open) => setSelectedItems((items) => ({ ...items, [id]: open }))}
203
- onMenuAction={handleCreateItem}
204
- getSlots={(node, open, depth, ancestors) => {
205
- if (depth === 1) {
206
- return {
207
- root: 'rounded bg-white dark:bg-neutral-850',
208
- header: mx('rounded-t bg-neutral-50 dark:bg-neutral-900', !open && 'rounded-b'),
209
- };
210
- }
211
- }}
212
- />
213
- </div>
214
-
215
- <div className='flex items-center my-2 px-2 gap-2'>
216
- <PlusCircle onClick={handleCreateSpace} />
217
- <span className='grow text-sm' onClick={handleCreateSpace}>
218
- New space
219
- </span>
220
- <List />
221
- </div>
222
- </div>
148
+ <Panel.Root>
149
+ <Panel.Content asChild>
150
+ <ScrollArea.Root orientation='vertical' thin>
151
+ <ScrollArea.Viewport>
152
+ <Tree
153
+ className='p-0.5 gap-1'
154
+ node={root}
155
+ open={openItems}
156
+ selected={selectedItems}
157
+ active={activeItems}
158
+ onChangeOpen={(id, open) => setOpenItems((items) => ({ ...items, [id]: open }))}
159
+ onChangeSelected={(id, open) => setSelectedItems((items) => ({ ...items, [id]: open }))}
160
+ onMenuAction={handleCreateItem}
161
+ getSlots={(node, open, depth) => {
162
+ if (depth === 1) {
163
+ return {
164
+ root: 'rounded-sm bg-white dark:bg-neutral-850',
165
+ header: mx('rounded-t bg-neutral-50 dark:bg-neutral-900', !open && 'rounded-b'),
166
+ };
167
+ }
168
+ }}
169
+ />
170
+ </ScrollArea.Viewport>
171
+ </ScrollArea.Root>
172
+ </Panel.Content>
173
+ <Panel.Statusbar>
174
+ <div className='flex items-center my-2 px-2 gap-2'>
175
+ <IconButton icon='ph--plus-circle--regular' iconOnly label='Create space' onClick={handleCreateSpace} />
176
+ <span className='grow text-sm' onClick={handleCreateSpace}>
177
+ New space
178
+ </span>
179
+ <Icon icon='ph--list--regular' />
180
+ </div>
181
+ </Panel.Statusbar>
182
+ </Panel.Root>
223
183
  );
224
184
  };
225
185
 
226
- const meta: Meta = {
186
+ const meta = {
227
187
  title: 'plugins/plugin-navtree/experimental/Tree',
228
188
  component: Tree,
229
- decorators: [withTheme, withLayout({ fullscreen: true, classNames: modalSurface })],
230
- };
189
+ decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'w-[20rem]' })],
190
+ parameters: {
191
+ layout: 'fullscreen',
192
+ },
193
+ } satisfies Meta<typeof Tree>;
231
194
 
232
195
  export default meta;
196
+
197
+ /**
198
+ * Space:
199
+ * - Space panels are separated from each other to give them more weight than folders.
200
+ * - Private space isn't different (just first, different icon).
201
+ * - Edit titles in place.
202
+ *
203
+ * Navigation:
204
+ * - up/down across spaces/folder peers
205
+ * - right to enter space/folder children
206
+ * - left to parent
207
+ */
208
+ export const Default = () => {
209
+ return <Sidebar mutate />;
210
+ };
211
+
212
+ export const Visitor = () => {
213
+ const [items] = useState(data);
214
+ const root = items[0];
215
+ const [openItems] = useState<ItemMap>({ [root.children![0].id]: true });
216
+ return (
217
+ <div className='flex flex-col'>
218
+ {Array.from(visitor(root, openItems)).map(({ node: { id, title }, depth }) => (
219
+ <div key={id} className='grid grid-cols-[40px_400px_400px] font-mono'>
220
+ <div>{depth}</div>
221
+ <div>{id}</div>
222
+ <div>{title}</div>
223
+ </div>
224
+ ))}
225
+ </div>
226
+ );
227
+ };
@@ -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/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-hover-surface',
197
+ // TODO(burdon): Use data-active.
198
+ selected?.[id] && 'bg-active-surface',
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>
@@ -0,0 +1,8 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ export * from './useActions';
6
+ export * from './useLoadDescendents';
7
+ export * from './useNavTreeModel';
8
+ export * from './useNavTreeState';
@@ -0,0 +1,34 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useMemo } from 'react';
6
+
7
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
8
+ import { Graph, Node, useActions as useGraphActions } from '@dxos/plugin-graph';
9
+
10
+ import { type FlattenedActions } from '#types';
11
+
12
+ /** Returns flattened actions and grouped sub-actions for a given graph node. */
13
+ export const useActions = (node: Node.Node): FlattenedActions => {
14
+ const { graph } = useAppGraph();
15
+ const actions = useGraphActions(graph, node.id);
16
+
17
+ return useMemo(() => {
18
+ return actions.reduce(
19
+ (acc: FlattenedActions, arg) => {
20
+ if (arg.properties.disposition === 'item') {
21
+ return acc;
22
+ }
23
+
24
+ acc.actions.push(arg);
25
+ if (!Node.isAction(arg)) {
26
+ const actionGroup = Graph.getActions(graph, arg.id);
27
+ acc.groupedActions[arg.id] = actionGroup;
28
+ }
29
+ return acc;
30
+ },
31
+ { actions: [], groupedActions: {} },
32
+ );
33
+ }, [actions]);
34
+ };
@@ -0,0 +1,23 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { useEffect } from 'react';
6
+
7
+ import { type Node } from '@dxos/app-graph';
8
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
9
+ import { Graph } from '@dxos/plugin-graph';
10
+
11
+ /**
12
+ * Expands a root node in the app graph.
13
+ */
14
+ export const useLoadDescendents = (root?: Node.Node) => {
15
+ const { graph } = useAppGraph();
16
+ useEffect(() => {
17
+ if (!root) {
18
+ return;
19
+ }
20
+
21
+ Graph.expand(graph, root.id, 'child');
22
+ }, [graph, root]);
23
+ };
@@ -0,0 +1,118 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Atom } from '@effect-atom/atom-react';
6
+ import * as Option from 'effect/Option';
7
+ import { useMemo } from 'react';
8
+
9
+ import { useAppGraph } from '@dxos/app-toolkit/ui';
10
+ import { PLANK_COMPANION_TYPE } from '@dxos/plugin-deck/types';
11
+ import { type Node } from '@dxos/plugin-graph';
12
+ import { Path, type TreeModel } from '@dxos/react-ui-list';
13
+ import { mx } from '@dxos/ui-theme';
14
+
15
+ import { type NavTreeItemGraphNode } from '#types';
16
+
17
+ import { filterItems } from '../util';
18
+ import { useNavTreeState } from './useNavTreeState';
19
+
20
+ // TODO(wittjosiah): Move companion/hidden nodes to their own edge categories so this filter is unnecessary.
21
+ const isVisibleChild = (node: Node.Node): boolean =>
22
+ node.type !== PLANK_COMPANION_TYPE && node.properties.disposition !== 'hidden';
23
+
24
+ /** Create an atom family for item display props keyed by path. */
25
+ const createItemPropsFamily = (graph: ReturnType<typeof useAppGraph>['graph']) =>
26
+ Atom.family((pathKey: string) => {
27
+ const path = Path.parts(pathKey);
28
+ const id = Path.last(pathKey);
29
+ return Atom.make((get) => {
30
+ const nodeOpt = get(graph.node(id));
31
+ const node = Option.getOrElse(nodeOpt, () => undefined);
32
+ if (!node) {
33
+ return { id, label: id };
34
+ }
35
+ const safeChildren = get(graph.connections(node.id, 'child')).filter((child) => !path.includes(child.id));
36
+ const visibleChildren = safeChildren.filter(isVisibleChild);
37
+ const parentOf =
38
+ visibleChildren.length > 0
39
+ ? visibleChildren.map((child) => child.id)
40
+ : node.properties.role === 'branch'
41
+ ? []
42
+ : undefined;
43
+ const parentId = path.length >= 2 ? path[path.length - 2] : undefined;
44
+ const parentNode = parentId ? Option.getOrElse(get(graph.node(parentId)), () => undefined) : undefined;
45
+ const droppable =
46
+ node.properties.droppable === false || parentNode?.properties.childrenDroppable === false ? false : undefined;
47
+
48
+ return {
49
+ id: node.id,
50
+ parentOf,
51
+ disabled: node.properties.disabled,
52
+ draggable: node.properties.draggable,
53
+ droppable,
54
+ label: node.properties.label ?? node.id,
55
+ className: mx(node.properties.className, node.properties.modified && 'italic'),
56
+ headingClassName: node.properties.headingClassName,
57
+ icon: node.properties.icon,
58
+ iconHue: node.properties.iconHue,
59
+ testId: node.properties.testId,
60
+ };
61
+ }).pipe(Atom.keepAlive);
62
+ });
63
+
64
+ /** Create an atom family for outbound child IDs keyed by parent ID (pre-sorted by position on write). */
65
+ const createChildIdsFamily = (graph: ReturnType<typeof useAppGraph>['graph']) =>
66
+ Atom.family((id: string) =>
67
+ Atom.make((get) => get(graph.connections(id, 'child')).map((child) => child.id)).pipe(Atom.keepAlive),
68
+ );
69
+
70
+ /** Create an atom family for item resolution keyed by ID. */
71
+ const createItemFamily = (graph: ReturnType<typeof useAppGraph>['graph']) =>
72
+ Atom.family((id: string) =>
73
+ Atom.make((get) => {
74
+ const node = Option.getOrElse(get(graph.node(id)), () => undefined);
75
+ return node && filterItems(node) ? node : undefined;
76
+ }).pipe(Atom.keepAlive),
77
+ );
78
+
79
+ /** Create an atom family for open state keyed by path. */
80
+ const createItemOpenFamily = (getItemAtom: ReturnType<typeof useNavTreeState>['getItemAtom']) =>
81
+ Atom.family((pathKey: string) => {
82
+ const path = Path.parts(pathKey);
83
+ const stateAtom = getItemAtom(path);
84
+ return Atom.make((get) => get(stateAtom).open).pipe(Atom.keepAlive);
85
+ });
86
+
87
+ /** Create an atom family for current (selected) state keyed by path. */
88
+ const createItemCurrentFamily = (getItemAtom: ReturnType<typeof useNavTreeState>['getItemAtom']) =>
89
+ Atom.family((pathKey: string) => {
90
+ const path = Path.parts(pathKey);
91
+ const stateAtom = getItemAtom(path);
92
+ return Atom.make((get) => get(stateAtom).current).pipe(Atom.keepAlive);
93
+ });
94
+
95
+ /**
96
+ * Creates a TreeModel backed by the app graph and navtree state.
97
+ */
98
+ export const useNavTreeModel = (rootId: string): TreeModel<NavTreeItemGraphNode> => {
99
+ const { graph } = useAppGraph();
100
+ const { getItemAtom } = useNavTreeState();
101
+
102
+ const itemPropsFamily = useMemo(() => createItemPropsFamily(graph), [graph]);
103
+ const childIdsFamily = useMemo(() => createChildIdsFamily(graph), [graph]);
104
+ const itemFamily = useMemo(() => createItemFamily(graph), [graph]);
105
+ const itemOpenFamily = useMemo(() => createItemOpenFamily(getItemAtom), [getItemAtom]);
106
+ const itemCurrentFamily = useMemo(() => createItemCurrentFamily(getItemAtom), [getItemAtom]);
107
+
108
+ return useMemo(
109
+ () => ({
110
+ item: (id: string) => itemFamily(id),
111
+ itemProps: (path: string[]) => itemPropsFamily(Path.create(...path)),
112
+ itemOpen: (path: string[]) => itemOpenFamily(Path.create(...path)),
113
+ itemCurrent: (path: string[]) => itemCurrentFamily(Path.create(...path)),
114
+ childIds: (parentId?: string) => childIdsFamily(parentId ?? rootId),
115
+ }),
116
+ [itemFamily, itemPropsFamily, itemOpenFamily, itemCurrentFamily, childIdsFamily, rootId],
117
+ );
118
+ };
@@ -0,0 +1,42 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Atom, useAtomValue } from '@effect-atom/atom-react';
6
+ import { useMemo } from 'react';
7
+
8
+ import { useCapability } from '@dxos/app-framework/ui';
9
+
10
+ import { NavTreeCapabilities } from '#types';
11
+
12
+ export type UseNavTreeStateResult = {
13
+ getItem: (path: string[]) => NavTreeCapabilities.NavTreeItemState;
14
+ getItemAtom: (path: string[]) => Atom.Atom<NavTreeCapabilities.NavTreeItemState>;
15
+ setItem: (path: string[], key: 'open' | 'current' | 'alternateTree', next: boolean) => void;
16
+ };
17
+
18
+ /**
19
+ * Hook that provides access to navtree state functions.
20
+ * For reactive subscriptions, use `useNavTreeItemState` or `useAtomValue(getItemAtom(path))`.
21
+ */
22
+ export const useNavTreeState = (): UseNavTreeStateResult => {
23
+ return useCapability(NavTreeCapabilities.State);
24
+ };
25
+
26
+ /**
27
+ * Hook that subscribes to a single navtree item's state.
28
+ * Use this for fine-grained reactivity.
29
+ */
30
+ export const useNavTreeItemState = (path: string[]): NavTreeCapabilities.NavTreeItemState => {
31
+ const { getItemAtom } = useCapability(NavTreeCapabilities.State);
32
+ const pathKey = useMemo(() => path.join('~'), [path]);
33
+ const atom = useMemo(() => getItemAtom(path), [getItemAtom, pathKey]);
34
+ return useAtomValue(atom);
35
+ };
36
+
37
+ /**
38
+ * Hook that subscribes to the alternateTree state for a tree item.
39
+ */
40
+ export const useIsAlternateTree = (path: string[], _item?: any): boolean => {
41
+ return useNavTreeItemState(path).alternateTree ?? false;
42
+ };
package/src/index.ts CHANGED
@@ -2,8 +2,7 @@
2
2
  // Copyright 2023 DXOS.org
3
3
  //
4
4
 
5
- export { NavTreeCapabilities } from './capabilities';
6
- export { NavTreeEvents } from './events';
5
+ export { NavTreeCapabilities, NavTreeEvents } from './types';
7
6
  export * from './NavTreePlugin';
8
7
  export * from './meta';
9
8
  export * from './util';