@dxos/react-ui-list 0.7.1 → 0.7.2-main.f1adc9f

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 (42) hide show
  1. package/dist/lib/browser/index.mjs +149 -87
  2. package/dist/lib/browser/index.mjs.map +4 -4
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node/index.cjs +180 -116
  5. package/dist/lib/node/index.cjs.map +4 -4
  6. package/dist/lib/node/meta.json +1 -1
  7. package/dist/lib/node-esm/index.mjs +149 -87
  8. package/dist/lib/node-esm/index.mjs.map +4 -4
  9. package/dist/lib/node-esm/meta.json +1 -1
  10. package/dist/types/src/components/List/DropIndicator.d.ts +5 -4
  11. package/dist/types/src/components/List/DropIndicator.d.ts.map +1 -1
  12. package/dist/types/src/components/Tree/DropIndicator.d.ts +2 -1
  13. package/dist/types/src/components/Tree/DropIndicator.d.ts.map +1 -1
  14. package/dist/types/src/components/Tree/Tree.d.ts +4 -19
  15. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  16. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Tree/TreeContext.d.ts +20 -0
  18. package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -0
  19. package/dist/types/src/components/Tree/TreeItem.d.ts +26 -24
  20. package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
  21. package/dist/types/src/components/Tree/TreeItemHeading.d.ts +1 -1
  22. package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
  23. package/dist/types/src/components/Tree/helpers.d.ts +0 -3
  24. package/dist/types/src/components/Tree/helpers.d.ts.map +1 -1
  25. package/dist/types/src/components/Tree/index.d.ts +1 -1
  26. package/dist/types/src/components/Tree/index.d.ts.map +1 -1
  27. package/dist/types/src/components/Tree/testing.d.ts +3 -6
  28. package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
  29. package/package.json +16 -18
  30. package/src/components/List/DropIndicator.tsx +7 -2
  31. package/src/components/Tree/DropIndicator.tsx +7 -8
  32. package/src/components/Tree/Tree.stories.tsx +71 -70
  33. package/src/components/Tree/Tree.tsx +29 -24
  34. package/src/components/Tree/TreeContext.tsx +32 -0
  35. package/src/components/Tree/TreeItem.tsx +122 -83
  36. package/src/components/Tree/TreeItemHeading.tsx +14 -5
  37. package/src/components/Tree/helpers.ts +0 -16
  38. package/src/components/Tree/index.ts +1 -1
  39. package/src/components/Tree/testing.ts +4 -73
  40. package/dist/types/src/components/Tree/types.d.ts +0 -18
  41. package/dist/types/src/components/Tree/types.d.ts.map +0 -1
  42. package/src/components/Tree/types.ts +0 -34
@@ -1,8 +1,5 @@
1
- import { type ItemMode } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
2
- import { type ItemType } from './types';
3
1
  export declare const DEFAULT_INDENTATION = 8;
4
2
  export declare const paddingIndendation: (level: number, indentation?: number) => {
5
3
  paddingInlineStart: string;
6
4
  };
7
- export declare const getMode: (items: ItemType[], index: number) => ItemMode;
8
5
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/helpers.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,oDAAoD,CAAC;AAEnF,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,eAAO,MAAM,kBAAkB,UAAW,MAAM;;CAE9C,CAAC;AAEH,eAAO,MAAM,OAAO,UAAW,QAAQ,EAAE,SAAS,MAAM,KAAG,QAU1D,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/helpers.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,eAAO,MAAM,kBAAkB,UAAW,MAAM;;CAE9C,CAAC"}
@@ -1,4 +1,4 @@
1
1
  export * from './Tree';
2
+ export * from './TreeContext';
2
3
  export * from './TreeItem';
3
- export * from './types';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/index.ts"],"names":[],"mappings":"AAIA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/index.ts"],"names":[],"mappings":"AAIA,cAAc,QAAQ,CAAC;AACvB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
2
2
  import { S } from '@dxos/echo-schema';
3
- import { type ItemType } from './types';
3
+ import { type TreeData } from './TreeItem';
4
4
  export type TestItem = {
5
5
  id: string;
6
6
  name: string;
@@ -14,13 +14,10 @@ export declare const TestItemSchema: S.Struct<{
14
14
  items: S.mutable<S.Array$<S.suspend<TestItem, TestItem, never>>>;
15
15
  }>;
16
16
  export declare const createTree: (n?: number, d?: number) => TestItem;
17
- export declare const flattenTree: (tree: TestItem, open: string[], getItem: (tree: TestItem) => ItemType) => ItemType[];
18
- export declare const getItem: (testItem: TestItem, parent?: string[]) => ItemType;
19
- export declare const invalidateCache: (id: string) => void;
20
17
  export declare const updateState: ({ state, instruction, source, target, }: {
21
18
  state: TestItem;
22
19
  instruction: Instruction;
23
- source: ItemType;
24
- target: ItemType;
20
+ source: TreeData;
21
+ target: TreeData;
25
22
  }) => void;
26
23
  //# sourceMappingURL=testing.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/testing.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oDAAoD,CAAC;AAEtF,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAKtC,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;EAKzB,CAAC;AAEH,eAAO,MAAM,UAAU,8BAAmB,QAaxC,CAAC;AA4BH,eAAO,MAAM,WAAW,SAAU,QAAQ,QAAQ,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE,QAAQ,KAAK,QAAQ,KAAG,QAAQ,EAQ3G,CAAC;AAKF,eAAO,MAAM,OAAO,aAAc,QAAQ,WAAW,MAAM,EAAE,KAAG,QAiB/D,CAAC;AAEF,eAAO,MAAM,eAAe,OAAQ,MAAM,SAEzC,CAAC;AAmBF,eAAO,MAAM,WAAW,4CAKrB;IACD,KAAK,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,QAAQ,CAAC;CAClB,SAsCA,CAAC"}
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../../../src/components/Tree/testing.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,oDAAoD,CAAC;AAEtF,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAItC,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,cAAc;;;;;EAKzB,CAAC;AAEH,eAAO,MAAM,UAAU,8BAAmB,QAaxC,CAAC;AAmBH,eAAO,MAAM,WAAW,4CAKrB;IACD,KAAK,EAAE,QAAQ,CAAC;IAChB,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,QAAQ,CAAC;IACjB,MAAM,EAAE,QAAQ,CAAC;CAClB,SAgCA,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-list",
3
- "version": "0.7.1",
3
+ "version": "0.7.2-main.f1adc9f",
4
4
  "description": "A list component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -23,21 +23,19 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
26
- "@atlaskit/pragmatic-drag-and-drop-flourish": "^1.1.2",
27
26
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
28
- "@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^1.1.3",
29
27
  "@preact/signals-core": "^1.6.0",
30
28
  "@radix-ui/react-context": "^1.0.0",
31
29
  "effect": "^3.9.2",
32
- "@dxos/debug": "0.7.1",
33
- "@dxos/invariant": "0.7.1",
34
- "@dxos/log": "0.7.1",
35
- "@dxos/react-ui-attention": "0.7.1",
36
- "@dxos/react-ui-text-tooltip": "0.7.1",
37
- "@dxos/react-ui-mosaic": "0.7.1",
38
- "@dxos/echo-schema": "0.7.1",
39
- "@dxos/react-ui-types": "0.7.1",
40
- "@dxos/util": "0.7.1"
30
+ "@dxos/debug": "0.7.2-main.f1adc9f",
31
+ "@dxos/echo-schema": "0.7.2-main.f1adc9f",
32
+ "@dxos/react-ui-attention": "0.7.2-main.f1adc9f",
33
+ "@dxos/log": "0.7.2-main.f1adc9f",
34
+ "@dxos/react-ui-mosaic": "0.7.2-main.f1adc9f",
35
+ "@dxos/invariant": "0.7.2-main.f1adc9f",
36
+ "@dxos/react-ui-types": "0.7.2-main.f1adc9f",
37
+ "@dxos/util": "0.7.2-main.f1adc9f",
38
+ "@dxos/react-ui-text-tooltip": "0.7.2-main.f1adc9f"
41
39
  },
42
40
  "devDependencies": {
43
41
  "@phosphor-icons/react": "^2.1.5",
@@ -46,18 +44,18 @@
46
44
  "react": "~18.2.0",
47
45
  "react-dom": "~18.2.0",
48
46
  "vite": "5.4.7",
49
- "@dxos/random": "0.7.1",
50
- "@dxos/react-ui": "0.7.1",
51
- "@dxos/react-ui-theme": "0.7.1",
52
- "@dxos/storybook-utils": "0.7.1"
47
+ "@dxos/random": "0.7.2-main.f1adc9f",
48
+ "@dxos/react-ui": "0.7.2-main.f1adc9f",
49
+ "@dxos/react-ui-theme": "0.7.2-main.f1adc9f",
50
+ "@dxos/storybook-utils": "0.7.2-main.f1adc9f"
53
51
  },
54
52
  "peerDependencies": {
55
53
  "@phosphor-icons/react": "^2.1.5",
56
54
  "effect": "^3.9.2",
57
55
  "react": "~18.2.0",
58
56
  "react-dom": "~18.2.0",
59
- "@dxos/react-ui-theme": "0.7.1",
60
- "@dxos/react-ui": "0.7.1"
57
+ "@dxos/react-ui": "0.7.2-main.f1adc9f",
58
+ "@dxos/react-ui-theme": "0.7.2-main.f1adc9f"
61
59
  },
62
60
  "publishConfig": {
63
61
  "access": "public"
@@ -32,11 +32,16 @@ const strokeSize = 2;
32
32
  const terminalSize = 8;
33
33
  const offsetToAlignTerminalWithLine = (strokeSize - terminalSize) / 2;
34
34
 
35
+ export type DropIndicatorProps = {
36
+ edge: Edge;
37
+ gap?: number;
38
+ };
39
+
35
40
  /**
36
41
  * This is a tailwind port of `@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box`
37
42
  */
38
- export const DropIndicator = ({ edge, gap = '0px' }: { edge: Edge; gap?: string }) => {
39
- const lineOffset = `calc(-0.5 * (${gap} + ${strokeSize}px))`;
43
+ export const DropIndicator = ({ edge, gap = 0 }: DropIndicatorProps) => {
44
+ const lineOffset = `calc(-0.5 * (${gap}px + ${strokeSize}px))`;
40
45
 
41
46
  const orientation = edgeToOrientationMap[edge];
42
47
 
@@ -10,10 +10,6 @@ import { mx } from '@dxos/react-ui-theme';
10
10
  // Tree item hitbox
11
11
  // https://github.com/atlassian/pragmatic-drag-and-drop/blob/main/packages/hitbox/constellation/index/about.mdx#tree-item
12
12
 
13
- export type DropIndicatorProps = {
14
- instruction: Instruction;
15
- };
16
-
17
13
  type InstructionType = Exclude<Instruction, { type: 'instruction-blocked' }>['type'];
18
14
  type Orientation = 'sibling' | 'child';
19
15
 
@@ -42,14 +38,17 @@ const instructionStyles: Record<InstructionType, HTMLAttributes<HTMLElement>['cl
42
38
  const strokeSize = 2;
43
39
  const terminalSize = 8;
44
40
  const offsetToAlignTerminalWithLine = (strokeSize - terminalSize) / 2;
45
- const gap = '0px';
46
41
 
47
- export const DropIndicator = ({ instruction }: DropIndicatorProps) => {
48
- const lineOffset = `calc(-0.5 * (${gap} + ${strokeSize}px))`;
42
+ export type DropIndicatorProps = {
43
+ instruction: Instruction;
44
+ gap?: number;
45
+ };
46
+
47
+ export const DropIndicator = ({ instruction, gap = 0 }: DropIndicatorProps) => {
48
+ const lineOffset = `calc(-0.5 * (${gap}px + ${strokeSize}px))`;
49
49
  const isBlocked = instruction.type === 'instruction-blocked';
50
50
  const desiredInstruction = isBlocked ? instruction.desired : instruction;
51
51
  const orientation = edgeToOrientationMap[desiredInstruction.type];
52
-
53
52
  if (isBlocked) {
54
53
  return null;
55
54
  }
@@ -9,71 +9,83 @@ import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-a
9
9
  import { type StoryObj, type Meta } from '@storybook/react';
10
10
  import React, { useEffect } from 'react';
11
11
 
12
- import { create } from '@dxos/echo-schema';
12
+ import { create, type ReactiveObject } from '@dxos/echo-schema';
13
13
  import { faker } from '@dxos/random';
14
14
  import { Icon } from '@dxos/react-ui';
15
15
  import { Path } from '@dxos/react-ui-mosaic';
16
16
  import { withLayout, withTheme } from '@dxos/storybook-utils';
17
17
 
18
- import { Tree, type TreeProps } from './Tree';
19
- import { createTree, flattenTree, getItem, updateState, type TestItem } from './testing';
20
- import { isItem, type ItemType } from './types';
18
+ import { Tree } from './Tree';
19
+ import { type TreeData } from './TreeItem';
20
+ import { createTree, updateState, type TestItem } from './testing';
21
21
 
22
22
  faker.seed(1234);
23
23
 
24
- type State = {
25
- tree: TestItem;
26
- open: string[];
27
- current: string[];
28
- flatTree: ItemType[];
29
- };
30
-
31
- const state = create<State>({
32
- tree: createTree(),
33
- open: [],
34
- current: [],
35
- get flatTree() {
36
- return flattenTree(this.tree, this.open, getItem);
37
- },
38
- });
39
-
40
- const Story = (args: Partial<TreeProps>) => {
41
- // NOTE: If passed directly to args, this won't be reactive.
42
- const items = state.flatTree;
43
-
44
- useEffect(() => {
45
- return monitorForElements({
46
- canMonitor: ({ source }) => isItem(source.data),
47
- onDrop: ({ location, source }) => {
48
- // Didn't drop on anything.
49
- if (!location.current.dropTargets.length) {
50
- return;
51
- }
52
-
53
- const target = location.current.dropTargets[0];
54
-
55
- const instruction: Instruction | null = extractInstruction(target.data);
56
- if (instruction !== null) {
57
- updateState({
58
- state: state.tree,
59
- instruction,
60
- source: source.data as ItemType,
61
- target: target.data as ItemType,
62
- });
63
- }
64
- },
65
- });
66
- }, []);
67
-
68
- return <Tree items={items} open={state.open} current={state.current} {...args} />;
69
- };
24
+ const tree = create<TestItem>(createTree());
25
+ const state = new Map<string, ReactiveObject<{ open: boolean; current: boolean }>>();
70
26
 
71
27
  const meta: Meta<typeof Tree> = {
72
28
  title: 'ui/react-ui-list/Tree',
73
29
  component: Tree,
74
- render: Story,
75
30
  decorators: [withTheme, withLayout({ tooltips: true })],
31
+ render: (args) => {
32
+ useEffect(() => {
33
+ return monitorForElements({
34
+ canMonitor: ({ source }) => typeof source.data.id === 'string' && Array.isArray(source.data.path),
35
+ onDrop: ({ location, source }) => {
36
+ // Didn't drop on anything.
37
+ if (!location.current.dropTargets.length) {
38
+ return;
39
+ }
40
+
41
+ const target = location.current.dropTargets[0];
42
+
43
+ const instruction: Instruction | null = extractInstruction(target.data);
44
+ if (instruction !== null) {
45
+ updateState({
46
+ state: tree,
47
+ instruction,
48
+ source: source.data as TreeData,
49
+ target: target.data as TreeData,
50
+ });
51
+ }
52
+ },
53
+ });
54
+ }, []);
55
+
56
+ return <Tree {...args} />;
57
+ },
76
58
  args: {
59
+ id: tree.id,
60
+ getItems: (testItem?: TestItem) => {
61
+ return testItem?.items ?? tree.items;
62
+ },
63
+ getProps: (testItem: TestItem) => ({
64
+ id: testItem.id,
65
+ label: testItem.name,
66
+ icon: testItem.icon,
67
+ ...((testItem.items?.length ?? 0) > 0 && {
68
+ parentOf: testItem.items!.map(({ id }) => id),
69
+ }),
70
+ }),
71
+ isOpen: (_path: string[]) => {
72
+ const path = Path.create(..._path);
73
+ const object = state.get(path) ?? create({ open: false, current: false });
74
+ if (!state.has(path)) {
75
+ state.set(path, object);
76
+ }
77
+
78
+ return object.open;
79
+ },
80
+ isCurrent: (_path: string[]) => {
81
+ const path = Path.create(..._path);
82
+ const object = state.get(path) ?? create({ open: false, current: false });
83
+ if (!state.has(path)) {
84
+ state.set(path, object);
85
+ }
86
+
87
+ return object.current;
88
+ },
77
89
  renderColumns: () => {
78
90
  return (
79
91
  <div className='flex items-center'>
@@ -81,26 +93,15 @@ const meta: Meta<typeof Tree> = {
81
93
  </div>
82
94
  );
83
95
  },
84
- onOpenChange: (item: ItemType, open: boolean) => {
85
- const path = Path.create(...item.path);
86
- if (open) {
87
- state.open.push(path);
88
- } else {
89
- const index = state.open.indexOf(path);
90
- if (index > -1) {
91
- state.open.splice(index, 1);
92
- }
93
- }
96
+ onOpenChange: ({ path: _path, open }) => {
97
+ const path = Path.create(..._path);
98
+ const object = state.get(path);
99
+ object!.open = open;
94
100
  },
95
- onSelect: (item: ItemType, current: boolean) => {
96
- if (current) {
97
- state.current.push(item.id);
98
- } else {
99
- const index = state.current.indexOf(item.id);
100
- if (index > -1) {
101
- state.current.splice(index, 1);
102
- }
103
- }
101
+ onSelect: ({ path: _path, current }) => {
102
+ const path = Path.create(..._path);
103
+ const object = state.get(path);
104
+ object!.current = current;
104
105
  },
105
106
  },
106
107
  };
@@ -2,26 +2,23 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import React from 'react';
5
+ import React, { useMemo } from 'react';
6
6
 
7
7
  import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
8
- import { Path } from '@dxos/react-ui-mosaic';
9
8
 
9
+ import { type TreeContextType, TreeProvider } from './TreeContext';
10
10
  import { TreeItem, type TreeItemProps } from './TreeItem';
11
- import { getMode } from './helpers';
12
- import { type ItemType } from './types';
13
11
 
14
- export type TreeProps<T extends ItemType = ItemType> = {
15
- items: T[];
16
- open: string[];
17
- current: string[];
18
- } & Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
12
+ export type TreeProps<T = any> = { id: string } & TreeContextType &
13
+ Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
19
14
  Pick<TreeItemProps<T>, 'draggable' | 'renderColumns' | 'canDrop' | 'onOpenChange' | 'onSelect'>;
20
15
 
21
- export const Tree = <T extends ItemType = ItemType>({
22
- items,
23
- open,
24
- current,
16
+ export const Tree = <T = any,>({
17
+ id,
18
+ getItems,
19
+ getProps,
20
+ isOpen,
21
+ isCurrent,
25
22
  draggable = false,
26
23
  gridTemplateColumns = '[tree-row-start] 1fr min-content [tree-row-end]',
27
24
  classNames,
@@ -30,27 +27,35 @@ export const Tree = <T extends ItemType = ItemType>({
30
27
  onOpenChange,
31
28
  onSelect,
32
29
  }: TreeProps<T>) => {
30
+ const context = useMemo(
31
+ () => ({
32
+ getItems,
33
+ getProps,
34
+ isOpen,
35
+ isCurrent,
36
+ }),
37
+ [getItems, getProps, isOpen, isCurrent],
38
+ );
39
+ const items = getItems();
40
+ const path = useMemo(() => [id], [id]);
41
+
33
42
  return (
34
43
  <Treegrid.Root gridTemplateColumns={gridTemplateColumns} classNames={classNames}>
35
- {items.map((item, i) => {
36
- const path = Path.create(...item.path);
37
-
38
- return (
39
- <TreeItem<T>
44
+ <TreeProvider value={context}>
45
+ {items.map((item, index) => (
46
+ <TreeItem
40
47
  key={item.id}
41
48
  item={item}
42
- mode={getMode(items, i)}
43
- open={open.includes(path)}
44
- // TODO(wittjosiah): This should also be path-based.
45
- current={current.includes(item.id)}
49
+ last={index === items.length - 1}
50
+ path={path}
46
51
  draggable={draggable}
47
52
  renderColumns={renderColumns}
48
53
  canDrop={canDrop}
49
54
  onOpenChange={onOpenChange}
50
55
  onSelect={onSelect}
51
56
  />
52
- );
53
- })}
57
+ ))}
58
+ </TreeProvider>
54
59
  </Treegrid.Root>
55
60
  );
56
61
  };
@@ -0,0 +1,32 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { createContext, useContext } from 'react';
6
+
7
+ import { raise } from '@dxos/debug';
8
+ import { type Label } from '@dxos/react-ui';
9
+
10
+ export type PropsFromTreeItem = {
11
+ id: string;
12
+ label: Label;
13
+ parentOf?: string[];
14
+ icon?: string;
15
+ disabled?: boolean;
16
+ className?: string;
17
+ headingClassName?: string;
18
+ testId?: string;
19
+ };
20
+
21
+ export type TreeContextType<T = any> = {
22
+ getItems: (parent?: T) => T[];
23
+ getProps: (item: T, parent: string[]) => PropsFromTreeItem;
24
+ isOpen: (path: string[], item: T) => boolean;
25
+ isCurrent: (path: string[], item: T) => boolean;
26
+ };
27
+
28
+ const TreeContext = createContext<null | TreeContextType>(null);
29
+
30
+ export const useTree = () => useContext(TreeContext) ?? raise(new Error('TreeContext not found'));
31
+
32
+ export const TreeProvider = TreeContext.Provider;