@dxos/react-ui-list 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d

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 (38) hide show
  1. package/dist/lib/browser/index.mjs +72 -46
  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 +72 -46
  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.stories.d.ts +3 -1
  10. package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
  11. package/dist/types/src/components/List/testing.d.ts +1 -1
  12. package/dist/types/src/components/List/testing.d.ts.map +1 -1
  13. package/dist/types/src/components/Tree/Tree.d.ts +3 -3
  14. package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
  15. package/dist/types/src/components/Tree/Tree.stories.d.ts +2 -3
  16. package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
  17. package/dist/types/src/components/Tree/TreeContext.d.ts +3 -2
  18. package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
  19. package/dist/types/src/components/Tree/TreeItem.d.ts +12 -2
  20. package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
  21. package/dist/types/src/components/Tree/TreeItemHeading.d.ts +4 -3
  22. package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
  23. package/dist/types/src/components/Tree/TreeItemToggle.d.ts +3 -3
  24. package/dist/types/src/components/Tree/TreeItemToggle.d.ts.map +1 -1
  25. package/dist/types/src/components/Tree/testing.d.ts +2 -2
  26. package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
  27. package/dist/types/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +24 -24
  29. package/src/components/Accordion/Accordion.stories.tsx +4 -6
  30. package/src/components/List/List.stories.tsx +9 -8
  31. package/src/components/List/testing.ts +3 -3
  32. package/src/components/Tree/Tree.stories.tsx +3 -4
  33. package/src/components/Tree/Tree.tsx +16 -2
  34. package/src/components/Tree/TreeContext.tsx +3 -2
  35. package/src/components/Tree/TreeItem.tsx +45 -32
  36. package/src/components/Tree/TreeItemHeading.tsx +8 -5
  37. package/src/components/Tree/TreeItemToggle.tsx +29 -18
  38. package/src/components/Tree/testing.ts +4 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/react-ui-list",
3
- "version": "0.8.4-main.5ea62a8",
3
+ "version": "0.8.4-main.66e292d",
4
4
  "description": "A list component.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -27,36 +27,36 @@
27
27
  "@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
28
28
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
29
29
  "@preact-signals/safe-react": "^0.9.0",
30
- "@preact/signals-core": "^1.9.0",
30
+ "@preact/signals-core": "^1.12.1",
31
31
  "@radix-ui/react-accordion": "1.2.3",
32
32
  "@radix-ui/react-context": "1.1.1",
33
- "@dxos/debug": "0.8.4-main.5ea62a8",
34
- "@dxos/echo-schema": "0.8.4-main.5ea62a8",
35
- "@dxos/invariant": "0.8.4-main.5ea62a8",
36
- "@dxos/live-object": "0.8.4-main.5ea62a8",
37
- "@dxos/log": "0.8.4-main.5ea62a8",
38
- "@dxos/react-ui": "0.8.4-main.5ea62a8",
39
- "@dxos/react-ui-text-tooltip": "0.8.4-main.5ea62a8",
40
- "@dxos/react-ui-types": "0.8.4-main.5ea62a8",
41
- "@dxos/react-ui-theme": "0.8.4-main.5ea62a8",
42
- "@dxos/util": "0.8.4-main.5ea62a8"
33
+ "@dxos/invariant": "0.8.4-main.66e292d",
34
+ "@dxos/live-object": "0.8.4-main.66e292d",
35
+ "@dxos/react-ui": "0.8.4-main.66e292d",
36
+ "@dxos/react-ui-text-tooltip": "0.8.4-main.66e292d",
37
+ "@dxos/log": "0.8.4-main.66e292d",
38
+ "@dxos/debug": "0.8.4-main.66e292d",
39
+ "@dxos/react-ui-theme": "0.8.4-main.66e292d",
40
+ "@dxos/react-ui-types": "0.8.4-main.66e292d",
41
+ "@dxos/util": "0.8.4-main.66e292d",
42
+ "@dxos/echo": "0.8.4-main.66e292d"
43
43
  },
44
44
  "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/storybook-utils": "0.8.4-main.5ea62a8",
52
- "@dxos/random": "0.8.4-main.5ea62a8"
45
+ "@types/react": "~19.2.2",
46
+ "@types/react-dom": "~19.2.2",
47
+ "effect": "3.18.3",
48
+ "react": "~19.2.0",
49
+ "react-dom": "~19.2.0",
50
+ "vite": "7.1.9",
51
+ "@dxos/storybook-utils": "0.8.4-main.66e292d",
52
+ "@dxos/random": "0.8.4-main.66e292d"
53
53
  },
54
54
  "peerDependencies": {
55
55
  "effect": "^3.13.3",
56
- "react": "~18.2.0",
57
- "react-dom": "~18.2.0",
58
- "@dxos/react-ui": "0.8.4-main.5ea62a8",
59
- "@dxos/react-ui-theme": "0.8.4-main.5ea62a8"
56
+ "react": "^19.0.0",
57
+ "react-dom": "^19.0.0",
58
+ "@dxos/react-ui-theme": "0.8.4-main.66e292d",
59
+ "@dxos/react-ui": "0.8.4-main.66e292d"
60
60
  },
61
61
  "publishConfig": {
62
62
  "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({ container: 'column' })],
48
46
  } satisfies Meta<typeof Accordion>;
49
47
 
50
48
  export default meta;
@@ -2,15 +2,13 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos-theme';
6
-
7
5
  import { type Meta, type StoryObj } from '@storybook/react-vite';
8
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
9
7
  import React from 'react';
10
8
 
11
9
  import { live } from '@dxos/live-object';
10
+ import { withTheme } from '@dxos/react-ui/testing';
12
11
  import { ghostHover, mx } from '@dxos/react-ui-theme';
13
- import { withLayout, withTheme } from '@dxos/storybook-utils';
14
12
  import { arrayMove } from '@dxos/util';
15
13
 
16
14
  import { List, type ListRootProps } from './List';
@@ -35,13 +33,13 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
35
33
  <List.Root<TestItemType> dragPreview items={items} getId={(item) => item.id} onMove={handleMove} {...props}>
36
34
  {({ items }) => (
37
35
  <>
38
- <div className='flex flex-col w-full'>
36
+ <div className='flex flex-col is-full'>
39
37
  <div role='none' className={grid}>
40
38
  <div />
41
39
  <div className='flex items-center text-sm'>Items</div>
42
40
  </div>
43
41
 
44
- <div role='list' className='w-full h-full overflow-auto'>
42
+ <div role='list' className='is-full bs-full overflow-auto'>
45
43
  {items?.map((item) => (
46
44
  <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
47
45
  <List.ItemDragHandle />
@@ -75,7 +73,7 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
75
73
  return (
76
74
  <List.Root<TestItemType> dragPreview items={items} {...props}>
77
75
  {({ items }) => (
78
- <div role='list' className='w-full h-full overflow-auto'>
76
+ <div role='list' className='is-full bs-full overflow-auto'>
79
77
  {items?.map((item) => (
80
78
  <List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
81
79
  <List.ItemDragHandle />
@@ -94,7 +92,10 @@ const list = live(createList(100));
94
92
  const meta = {
95
93
  title: 'ui/react-ui-list/List',
96
94
  component: List.Root,
97
- decorators: [withTheme, withLayout({ fullscreen: true })],
95
+ decorators: [withTheme],
96
+ parameters: {
97
+ layout: 'fullscreen',
98
+ },
98
99
  } satisfies Meta<typeof List.Root>;
99
100
 
100
101
  export default meta;
@@ -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,8 +2,6 @@
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';
9
7
  import { type Meta, type StoryObj } from '@storybook/react-vite';
@@ -12,7 +10,7 @@ import React, { useEffect } from 'react';
12
10
  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';
16
14
 
17
15
  import { Path } from '../../util';
18
16
 
@@ -54,9 +52,10 @@ const state = new Map<string, Live<{ open: boolean; current: boolean }>>();
54
52
 
55
53
  const meta = {
56
54
  title: 'ui/react-ui-list/Tree',
55
+
56
+ decorators: [withTheme],
57
57
  component: Tree,
58
58
  render: DefaultStory,
59
- decorators: [withTheme, withLayout()],
60
59
  args: {
61
60
  id: tree.id,
62
61
  useItems: (parent?: TestItem) => {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import React, { useMemo } from 'react';
6
6
 
7
- import { type HasId } from '@dxos/echo-schema';
7
+ import { type HasId } from '@dxos/echo/internal';
8
8
  import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
9
9
 
10
10
  import { type TreeContextType, TreeProvider } from './TreeContext';
@@ -16,7 +16,17 @@ export type TreeProps<T extends HasId = any, O = any> = {
16
16
  id: string;
17
17
  } & TreeContextType<T, O> &
18
18
  Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
19
- Pick<TreeItemProps<T>, 'draggable' | 'renderColumns' | 'canDrop' | 'onOpenChange' | 'onSelect' | 'levelOffset'>;
19
+ Pick<
20
+ TreeItemProps<T>,
21
+ | 'draggable'
22
+ | 'renderColumns'
23
+ | 'blockInstruction'
24
+ | 'canDrop'
25
+ | 'canSelect'
26
+ | 'onOpenChange'
27
+ | 'onSelect'
28
+ | 'levelOffset'
29
+ >;
20
30
 
21
31
  export const Tree = <T extends HasId = any, O = any>({
22
32
  root,
@@ -31,7 +41,9 @@ export const Tree = <T extends HasId = any, O = any>({
31
41
  classNames,
32
42
  levelOffset,
33
43
  renderColumns,
44
+ blockInstruction,
34
45
  canDrop,
46
+ canSelect,
35
47
  onOpenChange,
36
48
  onSelect,
37
49
  }: TreeProps<T, O>) => {
@@ -59,7 +71,9 @@ export const Tree = <T extends HasId = any, O = any>({
59
71
  levelOffset={levelOffset}
60
72
  draggable={draggable}
61
73
  renderColumns={renderColumns}
74
+ blockInstruction={blockInstruction}
62
75
  canDrop={canDrop}
76
+ canSelect={canSelect}
63
77
  onOpenChange={onOpenChange}
64
78
  onSelect={onSelect}
65
79
  />
@@ -11,10 +11,11 @@ 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
 
@@ -10,13 +10,14 @@ import {
10
10
  attachInstruction,
11
11
  extractInstruction,
12
12
  } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
13
- import { Schema } from 'effect';
13
+ import * as Schema from 'effect/Schema';
14
14
  import React, { type FC, type KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
15
 
16
- import { type HasId } from '@dxos/echo-schema';
16
+ import { type HasId } from '@dxos/echo/internal';
17
17
  import { invariant } from '@dxos/invariant';
18
18
  import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
19
19
  import {
20
+ ghostFocusWithin,
20
21
  ghostHover,
21
22
  hoverableControls,
22
23
  hoverableFocusedKeyboardControls,
@@ -28,11 +29,11 @@ import { useTree } from './TreeContext';
28
29
  import { TreeItemHeading } from './TreeItemHeading';
29
30
  import { TreeItemToggle } from './TreeItemToggle';
30
31
 
31
- type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
32
-
33
32
  const hoverableDescriptionIcons =
34
33
  '[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
35
34
 
35
+ type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
36
+
36
37
  export const TreeDataSchema = Schema.Struct({
37
38
  id: Schema.String,
38
39
  path: Schema.Array(Schema.String),
@@ -57,7 +58,9 @@ export type TreeItemProps<T extends HasId = any> = {
57
58
  last: boolean;
58
59
  draggable?: boolean;
59
60
  renderColumns?: ColumnRenderer<T>;
61
+ blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
60
62
  canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
63
+ canSelect?: (params: { item: T; path: string[] }) => boolean;
61
64
  onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
62
65
  onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
63
66
  };
@@ -65,13 +68,15 @@ export type TreeItemProps<T extends HasId = any> = {
65
68
  const RawTreeItem = <T extends HasId = any>({
66
69
  item,
67
70
  path: _path,
71
+ levelOffset = 2,
68
72
  last,
69
73
  draggable: _draggable,
70
74
  renderColumns: Columns,
75
+ blockInstruction,
71
76
  canDrop,
77
+ canSelect,
72
78
  onOpenChange,
73
79
  onSelect,
74
- levelOffset = 2,
75
80
  }: TreeItemProps<T>) => {
76
81
  const rowRef = useRef<HTMLDivElement | null>(null);
77
82
  const buttonRef = useRef<HTMLButtonElement | null>(null);
@@ -83,13 +88,14 @@ const RawTreeItem = <T extends HasId = any>({
83
88
 
84
89
  const { useItems, getProps, isOpen, isCurrent } = useTree();
85
90
  const items = useItems(item);
86
- const { id, label, parentOf, icon, disabled, className, headingClassName, testId } = getProps(item, _path);
91
+ const { id, parentOf, label, className, headingClassName, icon, iconHue, disabled, testId } = getProps(item, _path);
87
92
  const path = useMemo(() => [..._path, id], [_path, id]);
88
93
  const open = isOpen(path, item);
89
94
  const current = isCurrent(path, item);
90
95
  const level = path.length - levelOffset;
91
96
  const isBranch = !!parentOf;
92
97
  const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
98
+ const canSelectItem = canSelect?.({ item, path }) ?? true;
93
99
 
94
100
  const cancelExpand = useCallback(() => {
95
101
  if (cancelExpandRef.current) {
@@ -145,7 +151,11 @@ const RawTreeItem = <T extends HasId = any>({
145
151
  },
146
152
  getIsSticky: () => true,
147
153
  onDrag: ({ self, source }) => {
148
- const instruction = extractInstruction(self.data);
154
+ const desired = extractInstruction(self.data);
155
+ const block =
156
+ desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
157
+ const instruction: Instruction | null =
158
+ block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
149
159
 
150
160
  if (source.data.id !== id) {
151
161
  if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
@@ -176,43 +186,42 @@ const RawTreeItem = <T extends HasId = any>({
176
186
  },
177
187
  }),
178
188
  );
179
- }, [_draggable, item, id, mode, path, open, canDrop]);
189
+ }, [_draggable, item, id, mode, path, open, blockInstruction, canDrop]);
180
190
 
181
191
  // Cancel expand on unmount.
182
192
  useEffect(() => () => cancelExpand(), [cancelExpand]);
183
193
 
184
- const handleOpenChange = useCallback(
194
+ const handleOpenToggle = useCallback(
185
195
  () => onOpenChange?.({ item, path, open: !open }),
186
196
  [onOpenChange, item, path, open],
187
197
  );
188
198
 
189
199
  const handleSelect = useCallback(
190
200
  (option = false) => {
191
- if (isBranch) {
192
- handleOpenChange();
193
- } else {
201
+ // If the item is a branch, toggle it if:
202
+ // - also holding down the option key
203
+ // - or the item is currently selected
204
+ if (isBranch && (option || current)) {
205
+ handleOpenToggle();
206
+ } else if (canSelectItem) {
207
+ canSelect?.({ item, path });
194
208
  rowRef.current?.focus();
195
209
  onSelect?.({ item, path, current: !current, option });
196
210
  }
197
211
  },
198
- [item, path, current, isBranch, handleOpenChange, onSelect],
212
+ [item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
199
213
  );
200
214
 
201
215
  const handleKeyDown = useCallback(
202
216
  (event: KeyboardEvent) => {
203
217
  switch (event.key) {
204
218
  case 'ArrowRight':
205
- isBranch && !open && handleOpenChange();
206
- break;
207
219
  case 'ArrowLeft':
208
- isBranch && open && handleOpenChange();
209
- break;
210
- case ' ':
211
- handleSelect(event.altKey);
220
+ isBranch && handleOpenToggle();
212
221
  break;
213
222
  }
214
223
  },
215
- [isBranch, open, handleOpenChange, handleSelect],
224
+ [isBranch, open, handleOpenToggle, handleSelect],
216
225
  );
217
226
 
218
227
  return (
@@ -230,9 +239,10 @@ const RawTreeItem = <T extends HasId = any>({
230
239
  hoverableFocusedWithinControls,
231
240
  hoverableDescriptionIcons,
232
241
  ghostHover,
242
+ ghostFocusWithin,
233
243
  className,
234
244
  ]}
235
- data-itemid={id}
245
+ data-object-id={id}
236
246
  data-testid={testId}
237
247
  // NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
238
248
  // without alerting the user (except for in the correct link element). See also:
@@ -244,26 +254,27 @@ const RawTreeItem = <T extends HasId = any>({
244
254
  setMenuOpen(true);
245
255
  }}
246
256
  >
247
- <Treegrid.Cell
248
- indent
249
- classNames='relative grid grid-cols-subgrid col-[tree-row]'
257
+ <div
258
+ role='none'
259
+ className='indent relative grid grid-cols-subgrid col-[tree-row]'
250
260
  style={paddingIndentation(level)}
251
261
  >
252
- <div role='none' className='flex items-center'>
253
- <TreeItemToggle isBranch={isBranch} open={open} onToggle={handleOpenChange} />
262
+ <Treegrid.Cell classNames='flex items-center'>
263
+ <TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
254
264
  <TreeItemHeading
255
- ref={buttonRef}
256
- label={label}
257
- icon={icon}
258
- className={headingClassName}
259
265
  disabled={disabled}
260
266
  current={current}
267
+ label={label}
268
+ className={headingClassName}
269
+ icon={icon}
270
+ iconHue={iconHue}
261
271
  onSelect={handleSelect}
272
+ ref={buttonRef}
262
273
  />
263
- </div>
274
+ </Treegrid.Cell>
264
275
  {Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
265
276
  {instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
266
- </Treegrid.Cell>
277
+ </div>
267
278
  </Treegrid.Row>
268
279
  {open &&
269
280
  items.map((item, index) => (
@@ -274,7 +285,9 @@ const RawTreeItem = <T extends HasId = any>({
274
285
  last={index === items.length - 1}
275
286
  draggable={_draggable}
276
287
  renderColumns={Columns}
288
+ blockInstruction={blockInstruction}
277
289
  canDrop={canDrop}
290
+ canSelect={canSelect}
278
291
  onOpenChange={onOpenChange}
279
292
  onSelect={onSelect}
280
293
  />
@@ -6,22 +6,25 @@ import React, { type KeyboardEvent, type MouseEvent, forwardRef, memo, useCallba
6
6
 
7
7
  import { Button, Icon, type Label, toLocalizedString, useTranslation } from '@dxos/react-ui';
8
8
  import { TextTooltip } from '@dxos/react-ui-text-tooltip';
9
+ import { getStyles } from '@dxos/react-ui-theme';
9
10
 
10
11
  // TODO(wittjosiah): Consider whether there should be a separate disabled prop which was visually distinct
11
12
  // rather than just making the item unselectable.
12
- export type NavTreeItemHeadingProps = {
13
+ export type TreeItemHeadingProps = {
13
14
  label: Label;
14
- icon?: string;
15
15
  className?: string;
16
+ icon?: string;
17
+ iconHue?: string;
16
18
  disabled?: boolean;
17
19
  current?: boolean;
18
20
  onSelect?: (option: boolean) => void;
19
21
  };
20
22
 
21
23
  export const TreeItemHeading = memo(
22
- forwardRef<HTMLButtonElement, NavTreeItemHeadingProps>(
23
- ({ label, icon, className, disabled, current, onSelect }, forwardedRef) => {
24
+ forwardRef<HTMLButtonElement, TreeItemHeadingProps>(
25
+ ({ label, className, icon, iconHue, disabled, current, onSelect }, forwardedRef) => {
24
26
  const { t } = useTranslation();
27
+ const styles = iconHue ? getStyles(iconHue) : undefined;
25
28
 
26
29
  const handleSelect = useCallback(
27
30
  (event: MouseEvent) => {
@@ -64,7 +67,7 @@ export const TreeItemHeading = memo(
64
67
  onKeyDown={handleButtonKeydown}
65
68
  {...(current && { 'aria-current': 'location' })}
66
69
  >
67
- {icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames='mlb-1' />}
70
+ {icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['mlb-1', styles?.icon]} />}
68
71
  <span className='flex-1 is-0 truncate text-start text-sm font-normal' data-tooltip>
69
72
  {toLocalizedString(label, t)}
70
73
  </span>
@@ -4,29 +4,40 @@
4
4
 
5
5
  import React, { forwardRef, memo } from 'react';
6
6
 
7
- import { Button, Icon } from '@dxos/react-ui';
7
+ import { IconButton, type IconButtonProps } from '@dxos/react-ui';
8
8
 
9
- export type TreeItemToggleProps = {
9
+ export type TreeItemToggleProps = Omit<IconButtonProps, 'icon' | 'size' | 'label'> & {
10
10
  open?: boolean;
11
11
  isBranch?: boolean;
12
- onToggle?: () => void;
13
12
  hidden?: boolean;
14
13
  };
15
14
 
16
15
  export const TreeItemToggle = memo(
17
- forwardRef<HTMLButtonElement, TreeItemToggleProps>(({ open, isBranch, hidden, onToggle }, forwardedRef) => {
18
- return (
19
- <Button
20
- ref={forwardedRef}
21
- data-testid='treeItem.toggle'
22
- aria-expanded={open}
23
- variant='ghost'
24
- density='fine'
25
- classNames={['is-6 pli-0 dx-focus-ring-inset', hidden ? 'hidden' : !isBranch && 'invisible']}
26
- onClick={onToggle}
27
- >
28
- <Icon icon='ph--caret-right--bold' size={3} classNames={['transition duration-200', open && 'rotate-90']} />
29
- </Button>
30
- );
31
- }),
16
+ forwardRef<HTMLButtonElement, TreeItemToggleProps>(
17
+ ({ open, isBranch, hidden, classNames, ...props }, forwardedRef) => {
18
+ return (
19
+ <IconButton
20
+ ref={forwardedRef}
21
+ data-testid='treeItem.toggle'
22
+ aria-expanded={open}
23
+ variant='ghost'
24
+ density='fine'
25
+ classNames={[
26
+ 'bs-full is-6 pli-0',
27
+ '[&_svg]:transition-[transform] [&_svg]:duration-200',
28
+ open && '[&_svg]:rotate-90',
29
+ hidden ? 'hidden' : !isBranch && 'invisible',
30
+ classNames,
31
+ ]}
32
+ size={3}
33
+ icon='ph--caret-right--bold'
34
+ iconOnly
35
+ noTooltip
36
+ label={open ? 'Click to close' : 'Click to open'}
37
+ tabIndex={-1}
38
+ {...props}
39
+ />
40
+ );
41
+ },
42
+ ),
32
43
  );
@@ -3,9 +3,10 @@
3
3
  //
4
4
 
5
5
  import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
6
- import { Schema } from 'effect';
6
+ import * as Schema from 'effect/Schema';
7
7
 
8
- import { type HasId, ObjectId } from '@dxos/echo-schema';
8
+ import { Obj } from '@dxos/echo';
9
+ import { type HasId } from '@dxos/echo/internal';
9
10
  import { log } from '@dxos/log';
10
11
  import { faker } from '@dxos/random';
11
12
 
@@ -18,7 +19,7 @@ export type TestItem = HasId & {
18
19
  };
19
20
 
20
21
  export const TestItemSchema = Schema.Struct({
21
- id: ObjectId,
22
+ id: Obj.ID,
22
23
  name: Schema.String,
23
24
  icon: Schema.optional(Schema.String),
24
25
  items: Schema.mutable(Schema.Array(Schema.suspend((): Schema.Schema<TestItem> => TestItemSchema))),