@dxos/react-ui-list 0.8.4-main.1da679c → 0.8.4-main.21d9917

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 (43) hide show
  1. package/dist/lib/browser/index.mjs +641 -702
  2. package/dist/lib/browser/index.mjs.map +3 -3
  3. package/dist/lib/browser/meta.json +1 -1
  4. package/dist/lib/node-esm/index.mjs +641 -702
  5. package/dist/lib/node-esm/index.mjs.map +3 -3
  6. package/dist/lib/node-esm/meta.json +1 -1
  7. package/dist/types/src/components/Accordion/Accordion.stories.d.ts +0 -1
  8. package/dist/types/src/components/Accordion/Accordion.stories.d.ts.map +1 -1
  9. package/dist/types/src/components/List/List.d.ts +2 -2
  10. package/dist/types/src/components/List/List.d.ts.map +1 -1
  11. package/dist/types/src/components/List/List.stories.d.ts +5 -3
  12. package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
  13. package/dist/types/src/components/List/testing.d.ts +1 -1
  14. package/dist/types/src/components/List/testing.d.ts.map +1 -1
  15. package/dist/types/src/components/Tree/Tree.d.ts +7 -4
  16. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  17. package/dist/types/src/components/Tree/Tree.stories.d.ts +9 -29
  18. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  19. package/dist/types/src/components/Tree/TreeContext.d.ts +7 -4
  20. package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
  21. package/dist/types/src/components/Tree/TreeItem.d.ts +17 -4
  22. package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
  23. package/dist/types/src/components/Tree/TreeItemHeading.d.ts +4 -3
  24. package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
  25. package/dist/types/src/components/Tree/TreeItemToggle.d.ts +3 -3
  26. package/dist/types/src/components/Tree/TreeItemToggle.d.ts.map +1 -1
  27. package/dist/types/src/components/Tree/testing.d.ts +3 -3
  28. package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
  29. package/dist/types/tsconfig.tsbuildinfo +1 -1
  30. package/package.json +30 -28
  31. package/src/components/Accordion/Accordion.stories.tsx +4 -6
  32. package/src/components/Accordion/AccordionItem.tsx +2 -2
  33. package/src/components/Accordion/AccordionRoot.tsx +1 -1
  34. package/src/components/List/List.stories.tsx +34 -21
  35. package/src/components/List/ListItem.tsx +3 -3
  36. package/src/components/List/testing.ts +3 -3
  37. package/src/components/Tree/Tree.stories.tsx +76 -63
  38. package/src/components/Tree/Tree.tsx +22 -9
  39. package/src/components/Tree/TreeContext.tsx +7 -4
  40. package/src/components/Tree/TreeItem.tsx +51 -39
  41. package/src/components/Tree/TreeItemHeading.tsx +8 -5
  42. package/src/components/Tree/TreeItemToggle.tsx +29 -18
  43. package/src/components/Tree/testing.ts +5 -4
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-list",
3
- "version": "0.8.4-main.1da679c",
3
+ "version": "0.8.4-main.21d9917",
4
4
  "description": "A list component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
13
  "type": "module",
@@ -24,39 +28,37 @@
24
28
  "src"
25
29
  ],
26
30
  "dependencies": {
27
- "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
28
- "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
29
- "@preact-signals/safe-react": "^0.9.0",
30
- "@preact/signals-core": "^1.9.0",
31
+ "@atlaskit/pragmatic-drag-and-drop": "1.7.7",
32
+ "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
33
+ "@effect-atom/atom-react": "^0.4.6",
31
34
  "@radix-ui/react-accordion": "1.2.3",
32
35
  "@radix-ui/react-context": "1.1.1",
33
- "@dxos/debug": "0.8.4-main.1da679c",
34
- "@dxos/echo-schema": "0.8.4-main.1da679c",
35
- "@dxos/invariant": "0.8.4-main.1da679c",
36
- "@dxos/live-object": "0.8.4-main.1da679c",
37
- "@dxos/log": "0.8.4-main.1da679c",
38
- "@dxos/react-ui": "0.8.4-main.1da679c",
39
- "@dxos/react-ui-theme": "0.8.4-main.1da679c",
40
- "@dxos/react-ui-text-tooltip": "0.8.4-main.1da679c",
41
- "@dxos/react-ui-types": "0.8.4-main.1da679c",
42
- "@dxos/util": "0.8.4-main.1da679c"
36
+ "@dxos/echo": "0.8.4-main.21d9917",
37
+ "@dxos/log": "0.8.4-main.21d9917",
38
+ "@dxos/invariant": "0.8.4-main.21d9917",
39
+ "@dxos/react-ui": "0.8.4-main.21d9917",
40
+ "@dxos/ui-theme": "0.8.4-main.21d9917",
41
+ "@dxos/ui-types": "0.8.4-main.21d9917",
42
+ "@dxos/react-ui-text-tooltip": "0.8.4-main.21d9917",
43
+ "@dxos/util": "0.8.4-main.21d9917",
44
+ "@dxos/debug": "0.8.4-main.21d9917"
43
45
  },
44
46
  "devDependencies": {
45
- "@types/react": "~18.2.0",
46
- "@types/react-dom": "~18.2.0",
47
- "effect": "3.17.7",
48
- "react": "~18.2.0",
49
- "react-dom": "~18.2.0",
50
- "vite": "7.1.1",
51
- "@dxos/random": "0.8.4-main.1da679c",
52
- "@dxos/storybook-utils": "0.8.4-main.1da679c"
47
+ "@types/react": "~19.2.7",
48
+ "@types/react-dom": "~19.2.3",
49
+ "effect": "3.19.11",
50
+ "react": "~19.2.3",
51
+ "react-dom": "~19.2.3",
52
+ "vite": "7.1.9",
53
+ "@dxos/storybook-utils": "0.8.4-main.21d9917",
54
+ "@dxos/random": "0.8.4-main.21d9917"
53
55
  },
54
56
  "peerDependencies": {
55
- "effect": "^3.13.3",
56
- "react": "~18.2.0",
57
- "react-dom": "~18.2.0",
58
- "@dxos/react-ui": "0.8.4-main.1da679c",
59
- "@dxos/react-ui-theme": "0.8.4-main.1da679c"
57
+ "effect": "3.19.11",
58
+ "react": "~19.2.3",
59
+ "react-dom": "~19.2.3",
60
+ "@dxos/react-ui": "0.8.4-main.21d9917",
61
+ "@dxos/ui-theme": "0.8.4-main.21d9917"
60
62
  },
61
63
  "publishConfig": {
62
64
  "access": "public"
@@ -2,13 +2,11 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
6
  import React from 'react';
9
7
 
10
8
  import { faker } from '@dxos/random';
11
- import { withLayout, withTheme } from '@dxos/storybook-utils';
9
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
12
10
 
13
11
  import { Accordion } from './Accordion';
14
12
 
@@ -24,9 +22,9 @@ const items: TestItem[] = Array.from({ length: 10 }, (_, i) => ({
24
22
 
25
23
  const DefaultStory = () => {
26
24
  return (
27
- <Accordion.Root<TestItem> items={items} classNames='w-[40rem]'>
25
+ <Accordion.Root<TestItem> items={items} classNames='is-[40rem]'>
28
26
  {({ items }) => (
29
- <div className='flex flex-col w-full border-y border-separator divide-y divide-separator'>
27
+ <div className='flex flex-col is-full border-y border-separator divide-y divide-separator'>
30
28
  {items.map((item) => (
31
29
  <Accordion.Item key={item.id} item={item} classNames='border-x border-separator'>
32
30
  <Accordion.ItemHeader>{item.name}</Accordion.ItemHeader>
@@ -44,7 +42,7 @@ const DefaultStory = () => {
44
42
  const meta = {
45
43
  title: 'ui/react-ui-list/Accordion',
46
44
  render: DefaultStory,
47
- decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'flex justify-center' })],
45
+ decorators: [withTheme, withLayout({ layout: 'column' })],
48
46
  } satisfies Meta<typeof Accordion>;
49
47
 
50
48
  export default meta;
@@ -7,7 +7,7 @@ import { createContext } from '@radix-ui/react-context';
7
7
  import React, { type PropsWithChildren } from 'react';
8
8
 
9
9
  import { Icon, type ThemedClassName } from '@dxos/react-ui';
10
- import { mx } from '@dxos/react-ui-theme';
10
+ import { mx } from '@dxos/ui-theme';
11
11
 
12
12
  import { type ListItemRecord } from '../List';
13
13
 
@@ -59,7 +59,7 @@ export type AccordionItemBodyProps = ThemedClassName<PropsWithChildren>;
59
59
 
60
60
  export const AccordionItemBody = ({ children, classNames }: AccordionItemBodyProps) => {
61
61
  return (
62
- <AccordionPrimitive.Content className='overflow-hidden data-[state=closed]:animate-slideUp data-[state=open]:animate-slideDown'>
62
+ <AccordionPrimitive.Content className='overflow-hidden data-[state=closed]:animate-slide-up data-[state=open]:animate-slide-down'>
63
63
  <div role='none' className={mx('p-2', classNames)}>
64
64
  {children}
65
65
  </div>
@@ -7,7 +7,7 @@ import { createContext } from '@radix-ui/react-context';
7
7
  import React, { type ReactNode } from 'react';
8
8
 
9
9
  import { type ThemedClassName } from '@dxos/react-ui';
10
- import { mx } from '@dxos/react-ui-theme';
10
+ import { mx } from '@dxos/ui-theme';
11
11
 
12
12
  import { type ListItemRecord } from '../List';
13
13
 
@@ -2,46 +2,56 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
5
+ import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
7
6
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import { Schema } from 'effect';
9
- import React from 'react';
7
+ import * as Schema from 'effect/Schema';
8
+ import React, { useContext, useMemo } from 'react';
10
9
 
11
- import { live } from '@dxos/live-object';
12
- import { ghostHover, mx } from '@dxos/react-ui-theme';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
10
+ import { withTheme } from '@dxos/react-ui/testing';
11
+ import { withRegistry } from '@dxos/storybook-utils';
12
+ import { ghostHover, mx } from '@dxos/ui-theme';
14
13
  import { arrayMove } from '@dxos/util';
15
14
 
16
15
  import { List, type ListRootProps } from './List';
17
- import { TestItemSchema, type TestItemType, createList } from './testing';
16
+ import { TestItemSchema, type TestItemType, type TestList, createList } from './testing';
18
17
 
19
18
  // TODO(burdon): var-icon-size.
20
19
  const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2rem] rounded';
21
20
 
22
- const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
21
+ const DefaultStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
22
+ const registry = useContext(RegistryContext);
23
+ const listAtom = useMemo(() => Atom.make<TestList>(createList(100)).pipe(Atom.keepAlive), []);
24
+ const list = useAtomValue(listAtom);
25
+ const items = list.items;
26
+
23
27
  const handleSelect = (item: TestItemType) => {
24
28
  console.log('select', item);
25
29
  };
26
30
  const handleDelete = (item: TestItemType) => {
27
- const idx = items.findIndex((i) => i.id === item.id);
28
- items.splice(idx, 1);
31
+ const prev = registry.get(listAtom);
32
+ registry.set(listAtom, {
33
+ ...prev,
34
+ items: prev.items.filter((i) => i.id !== item.id),
35
+ });
29
36
  };
30
37
  const handleMove = (from: number, to: number) => {
31
- arrayMove(items, from, to);
38
+ const prev = registry.get(listAtom);
39
+ const newItems = [...prev.items];
40
+ arrayMove(newItems, from, to);
41
+ registry.set(listAtom, { ...prev, items: newItems });
32
42
  };
33
43
 
34
44
  return (
35
45
  <List.Root<TestItemType> dragPreview items={items} getId={(item) => item.id} onMove={handleMove} {...props}>
36
46
  {({ items }) => (
37
47
  <>
38
- <div className='flex flex-col w-full'>
48
+ <div className='flex flex-col is-full'>
39
49
  <div role='none' className={grid}>
40
50
  <div />
41
51
  <div className='flex items-center text-sm'>Items</div>
42
52
  </div>
43
53
 
44
- <div role='list' className='w-full h-full overflow-auto'>
54
+ <div role='list' className='is-full bs-full overflow-auto'>
45
55
  {items?.map((item) => (
46
56
  <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
47
57
  <List.ItemDragHandle />
@@ -71,11 +81,15 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
71
81
  );
72
82
  };
73
83
 
74
- const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
84
+ const SimpleStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
85
+ const listAtom = useMemo(() => Atom.make<TestList>(createList(100)).pipe(Atom.keepAlive), []);
86
+ const list = useAtomValue(listAtom);
87
+ const items = list.items;
88
+
75
89
  return (
76
90
  <List.Root<TestItemType> dragPreview items={items} {...props}>
77
91
  {({ items }) => (
78
- <div role='list' className='w-full h-full overflow-auto'>
92
+ <div role='list' className='is-full bs-full overflow-auto'>
79
93
  {items?.map((item) => (
80
94
  <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
81
95
  <List.ItemDragHandle />
@@ -89,12 +103,13 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
89
103
  );
90
104
  };
91
105
 
92
- const list = live(createList(100));
93
-
94
106
  const meta = {
95
107
  title: 'ui/react-ui-list/List',
96
108
  component: List.Root,
97
- decorators: [withTheme, withLayout({ fullscreen: true })],
109
+ decorators: [withTheme, withRegistry],
110
+ parameters: {
111
+ layout: 'fullscreen',
112
+ },
98
113
  } satisfies Meta<typeof List.Root>;
99
114
 
100
115
  export default meta;
@@ -102,7 +117,6 @@ export default meta;
102
117
  export const Default: StoryObj<typeof DefaultStory> = {
103
118
  render: DefaultStory,
104
119
  args: {
105
- items: list.items,
106
120
  isItem: Schema.is(TestItemSchema),
107
121
  },
108
122
  };
@@ -110,7 +124,6 @@ export const Default: StoryObj<typeof DefaultStory> = {
110
124
  export const Simple: StoryObj<typeof SimpleStory> = {
111
125
  render: SimpleStory,
112
126
  args: {
113
- items: list.items,
114
127
  isItem: Schema.is(TestItemSchema),
115
128
  },
116
129
  };
@@ -31,7 +31,7 @@ import {
31
31
  type ThemedClassName,
32
32
  useTranslation,
33
33
  } from '@dxos/react-ui';
34
- import { mx } from '@dxos/react-ui-theme';
34
+ import { mx, osTranslations } from '@dxos/ui-theme';
35
35
 
36
36
  import { useListContext } from './ListRoot';
37
37
 
@@ -195,7 +195,7 @@ export const ListItemDeleteButton = ({
195
195
  Omit<IconButtonProps, 'icon' | 'label'> & { autoHide?: boolean; label?: string }) => {
196
196
  const { state } = useListContext('DELETE_BUTTON');
197
197
  const isDisabled = state.type !== 'idle' || disabled;
198
- const { t } = useTranslation('os');
198
+ const { t } = useTranslation(osTranslations);
199
199
  return (
200
200
  <IconButton
201
201
  iconOnly
@@ -232,7 +232,7 @@ export const ListItemButton = ({
232
232
 
233
233
  export const ListItemDragHandle = ({ disabled }: Pick<IconButtonProps, 'disabled'>) => {
234
234
  const { dragHandleRef } = useListItemContext('DRAG_HANDLE');
235
- const { t } = useTranslation('os');
235
+ const { t } = useTranslation(osTranslations);
236
236
  return (
237
237
  <IconButton
238
238
  iconOnly
@@ -2,13 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import { Schema } from 'effect';
5
+ import * as Schema from 'effect/Schema';
6
6
 
7
- import { ObjectId } from '@dxos/echo-schema';
7
+ import { Obj } from '@dxos/echo';
8
8
  import { faker } from '@dxos/random';
9
9
 
10
10
  export const TestItemSchema = Schema.Struct({
11
- id: ObjectId,
11
+ id: Obj.ID,
12
12
  name: Schema.String,
13
13
  });
14
14
 
@@ -2,32 +2,73 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
8
6
  import { type Instruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
7
+ import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
9
8
  import { type Meta, type StoryObj } from '@storybook/react-vite';
10
- import React, { useEffect } from 'react';
9
+ import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
11
10
 
12
- import { type Live, live } from '@dxos/live-object';
13
11
  import { faker } from '@dxos/random';
14
12
  import { Icon } from '@dxos/react-ui';
15
- import { withLayout, withTheme } from '@dxos/storybook-utils';
13
+ import { withTheme } from '@dxos/react-ui/testing';
14
+ import { withRegistry } from '@dxos/storybook-utils';
16
15
 
17
16
  import { Path } from '../../util';
18
17
 
19
18
  import { type TestItem, createTree, updateState } from './testing';
20
- import { Tree, type TreeProps } from './Tree';
19
+ import { Tree } from './Tree';
21
20
  import { type TreeData } from './TreeItem';
22
21
 
23
22
  faker.seed(1234);
24
23
 
25
- const DefaultStory = (props: TreeProps) => {
24
+ const tree = createTree() as TestItem;
25
+
26
+ const DefaultStory = ({ draggable }: { draggable?: boolean }) => {
27
+ const registry = useContext(RegistryContext);
28
+ const stateAtomsRef = useRef(new Map<string, Atom.Writable<{ open: boolean; current: boolean }>>());
29
+
30
+ const getOrCreateStateAtom = useCallback((path: string) => {
31
+ let atom = stateAtomsRef.current.get(path);
32
+ if (!atom) {
33
+ atom = Atom.make({ open: false, current: false }).pipe(Atom.keepAlive);
34
+ stateAtomsRef.current.set(path, atom);
35
+ }
36
+ return atom;
37
+ }, []);
38
+
39
+ const useItemState = useCallback(
40
+ (_path: string[]) => {
41
+ const path = useMemo(() => Path.create(..._path), [_path.join('~')]);
42
+ const atom = getOrCreateStateAtom(path);
43
+ return useAtomValue(atom);
44
+ },
45
+ [getOrCreateStateAtom],
46
+ );
47
+
48
+ const handleOpenChange = useCallback(
49
+ ({ path: _path, open }: { path: string[]; open: boolean }) => {
50
+ const path = Path.create(..._path);
51
+ const atom = getOrCreateStateAtom(path);
52
+ const prev = registry.get(atom);
53
+ registry.set(atom, { ...prev, open });
54
+ },
55
+ [getOrCreateStateAtom, registry],
56
+ );
57
+
58
+ const handleSelect = useCallback(
59
+ ({ path: _path, current }: { path: string[]; current: boolean }) => {
60
+ const path = Path.create(..._path);
61
+ const atom = getOrCreateStateAtom(path);
62
+ const prev = registry.get(atom);
63
+ registry.set(atom, { ...prev, current });
64
+ },
65
+ [getOrCreateStateAtom, registry],
66
+ );
67
+
26
68
  useEffect(() => {
27
69
  return monitorForElements({
28
70
  canMonitor: ({ source }) => typeof source.data.id === 'string' && Array.isArray(source.data.path),
29
71
  onDrop: ({ location, source }) => {
30
- // Didn't drop on anything.
31
72
  if (!location.current.dropTargets.length) {
32
73
  return;
33
74
  }
@@ -46,71 +87,43 @@ const DefaultStory = (props: TreeProps) => {
46
87
  });
47
88
  }, []);
48
89
 
49
- return <Tree {...props} />;
90
+ return (
91
+ <Tree
92
+ id={tree.id}
93
+ draggable={draggable}
94
+ useItems={(parent?: TestItem) => parent?.items ?? tree.items}
95
+ getProps={(parent: TestItem) => ({
96
+ id: parent.id,
97
+ label: parent.name,
98
+ icon: parent.icon,
99
+ ...((parent.items?.length ?? 0) > 0 && {
100
+ parentOf: parent.items!.map(({ id }) => id),
101
+ }),
102
+ })}
103
+ useIsOpen={(_path: string[]) => useItemState(_path).open}
104
+ useIsCurrent={(_path: string[]) => useItemState(_path).current}
105
+ renderColumns={() => (
106
+ <div className='flex items-center'>
107
+ <Icon icon='ph--placeholder--regular' size={5} />
108
+ </div>
109
+ )}
110
+ onOpenChange={handleOpenChange}
111
+ onSelect={handleSelect}
112
+ />
113
+ );
50
114
  };
51
115
 
52
- const tree = live<TestItem>(createTree());
53
- const state = new Map<string, Live<{ open: boolean; current: boolean }>>();
54
-
55
116
  const meta = {
56
117
  title: 'ui/react-ui-list/Tree',
118
+
119
+ decorators: [withTheme, withRegistry],
57
120
  component: Tree,
58
121
  render: DefaultStory,
59
- decorators: [withTheme, withLayout()],
60
- args: {
61
- id: tree.id,
62
- useItems: (parent?: TestItem) => {
63
- return parent?.items ?? tree.items;
64
- },
65
- getProps: (parent: TestItem) => ({
66
- id: parent.id,
67
- label: parent.name,
68
- icon: parent.icon,
69
- ...((parent.items?.length ?? 0) > 0 && {
70
- parentOf: parent.items!.map(({ id }) => id),
71
- }),
72
- }),
73
- isOpen: (_path: string[]) => {
74
- const path = Path.create(..._path);
75
- const object = state.get(path) ?? live({ open: false, current: false });
76
- if (!state.has(path)) {
77
- state.set(path, object);
78
- }
79
-
80
- return object.open;
81
- },
82
- isCurrent: (_path: string[]) => {
83
- const path = Path.create(..._path);
84
- const object = state.get(path) ?? live({ open: false, current: false });
85
- if (!state.has(path)) {
86
- state.set(path, object);
87
- }
88
-
89
- return object.current;
90
- },
91
- renderColumns: () => {
92
- return (
93
- <div className='flex items-center'>
94
- <Icon icon='ph--placeholder--regular' size={5} />
95
- </div>
96
- );
97
- },
98
- onOpenChange: ({ path: _path, open }) => {
99
- const path = Path.create(..._path);
100
- const object = state.get(path);
101
- object!.open = open;
102
- },
103
- onSelect: ({ path: _path, current }) => {
104
- const path = Path.create(..._path);
105
- const object = state.get(path);
106
- object!.current = current;
107
- },
108
- },
109
122
  } satisfies Meta<typeof Tree<TestItem>>;
110
123
 
111
124
  export default meta;
112
125
 
113
- type Story = StoryObj<typeof meta>;
126
+ type Story = StoryObj<typeof DefaultStory>;
114
127
 
115
128
  export const Default: Story = {};
116
129
 
@@ -4,34 +4,45 @@
4
4
 
5
5
  import React, { useMemo } from 'react';
6
6
 
7
- import { type HasId } from '@dxos/echo-schema';
8
7
  import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
9
8
 
10
9
  import { type TreeContextType, TreeProvider } from './TreeContext';
11
10
  import { TreeItem, type TreeItemProps } from './TreeItem';
12
11
 
13
- export type TreeProps<T extends HasId = any, O = any> = {
12
+ export type TreeProps<T extends { id: string } = any, O = any> = {
14
13
  root?: T;
15
14
  path?: string[];
16
15
  id: string;
17
16
  } & TreeContextType<T, O> &
18
17
  Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
19
- Pick<TreeItemProps<T>, 'draggable' | 'renderColumns' | 'canDrop' | 'onOpenChange' | 'onSelect' | 'levelOffset'>;
18
+ Pick<
19
+ TreeItemProps<T>,
20
+ | 'draggable'
21
+ | 'renderColumns'
22
+ | 'blockInstruction'
23
+ | 'canDrop'
24
+ | 'canSelect'
25
+ | 'onOpenChange'
26
+ | 'onSelect'
27
+ | 'levelOffset'
28
+ >;
20
29
 
21
- export const Tree = <T extends HasId = any, O = any>({
30
+ export const Tree = <T extends { id: string } = any, O = any>({
22
31
  root,
23
32
  path,
24
33
  id,
25
34
  useItems,
26
35
  getProps,
27
- isOpen,
28
- isCurrent,
36
+ useIsOpen,
37
+ useIsCurrent,
29
38
  draggable = false,
30
39
  gridTemplateColumns = '[tree-row-start] 1fr min-content [tree-row-end]',
31
40
  classNames,
32
41
  levelOffset,
33
42
  renderColumns,
43
+ blockInstruction,
34
44
  canDrop,
45
+ canSelect,
35
46
  onOpenChange,
36
47
  onSelect,
37
48
  }: TreeProps<T, O>) => {
@@ -39,10 +50,10 @@ export const Tree = <T extends HasId = any, O = any>({
39
50
  () => ({
40
51
  useItems,
41
52
  getProps,
42
- isOpen,
43
- isCurrent,
53
+ useIsOpen,
54
+ useIsCurrent,
44
55
  }),
45
- [useItems, getProps, isOpen, isCurrent],
56
+ [useItems, getProps, useIsOpen, useIsCurrent],
46
57
  );
47
58
  const items = useItems(root);
48
59
  const treePath = useMemo(() => (path ? [...path, id] : [id]), [id, path]);
@@ -59,7 +70,9 @@ export const Tree = <T extends HasId = any, O = any>({
59
70
  levelOffset={levelOffset}
60
71
  draggable={draggable}
61
72
  renderColumns={renderColumns}
73
+ blockInstruction={blockInstruction}
62
74
  canDrop={canDrop}
75
+ canSelect={canSelect}
63
76
  onOpenChange={onOpenChange}
64
77
  onSelect={onSelect}
65
78
  />
@@ -11,18 +11,21 @@ export type TreeItemDataProps = {
11
11
  id: string;
12
12
  label: Label;
13
13
  parentOf?: string[];
14
- icon?: string;
15
- disabled?: boolean;
16
14
  className?: string;
17
15
  headingClassName?: string;
16
+ icon?: string;
17
+ iconHue?: string;
18
+ disabled?: boolean;
18
19
  testId?: string;
19
20
  };
20
21
 
21
22
  export type TreeContextType<T = any, O = any> = {
22
23
  useItems: (parent?: T, options?: O) => T[];
23
24
  getProps: (item: T, parent: string[]) => TreeItemDataProps;
24
- isOpen: (path: string[], item: T) => boolean;
25
- isCurrent: (path: string[], item: T) => boolean;
25
+ /** Hook that subscribes to and returns the open state for a tree item. */
26
+ useIsOpen: (path: string[], item: T) => boolean;
27
+ /** Hook that subscribes to and returns the current state for a tree item. */
28
+ useIsCurrent: (path: string[], item: T) => boolean;
26
29
  };
27
30
 
28
31
  const TreeContext = createContext<null | TreeContextType>(null);