@dxos/react-ui-list 0.8.4-main.c4373fc → 0.8.4-main.c85a9c8dae
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.
- package/dist/lib/browser/index.mjs +657 -712
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +657 -712
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Accordion/Accordion.stories.d.ts +0 -3
- package/dist/types/src/components/Accordion/Accordion.stories.d.ts.map +1 -1
- package/dist/types/src/components/List/List.d.ts +2 -2
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/List.stories.d.ts +2 -2
- package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +2 -2
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/List/ListRoot.d.ts +2 -2
- package/dist/types/src/components/List/ListRoot.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.d.ts +10 -6
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +9 -28
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +18 -9
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +20 -3
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/Tree/index.d.ts +2 -0
- package/dist/types/src/components/Tree/index.d.ts.map +1 -1
- package/dist/types/src/components/Tree/testing.d.ts +2 -2
- package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +30 -28
- package/src/components/Accordion/Accordion.stories.tsx +2 -5
- package/src/components/Accordion/AccordionItem.tsx +3 -3
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/List/List.stories.tsx +29 -17
- package/src/components/List/ListItem.tsx +13 -13
- package/src/components/List/ListRoot.tsx +2 -2
- package/src/components/List/testing.ts +2 -2
- package/src/components/Tree/Tree.stories.tsx +150 -60
- package/src/components/Tree/Tree.tsx +39 -41
- package/src/components/Tree/TreeContext.tsx +15 -8
- package/src/components/Tree/TreeItem.tsx +99 -51
- package/src/components/Tree/TreeItemHeading.tsx +9 -5
- package/src/components/Tree/TreeItemToggle.tsx +1 -1
- package/src/components/Tree/index.ts +2 -0
- package/src/components/Tree/testing.ts +4 -3
|
@@ -1,73 +1,71 @@
|
|
|
1
1
|
//
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
|
-
//
|
|
4
3
|
|
|
4
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type HasId } from '@dxos/echo/internal';
|
|
8
7
|
import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
|
|
9
8
|
|
|
10
|
-
import { type
|
|
11
|
-
import {
|
|
9
|
+
import { type TreeModel, TreeProvider } from './TreeContext';
|
|
10
|
+
import { TreeItemById, type TreeItemByIdProps, type TreeItemProps } from './TreeItem';
|
|
12
11
|
|
|
13
|
-
export type TreeProps<T extends
|
|
14
|
-
|
|
12
|
+
export type TreeProps<T extends { id: string } = any> = {
|
|
13
|
+
model: TreeModel<T>;
|
|
14
|
+
rootId?: string;
|
|
15
15
|
path?: string[];
|
|
16
16
|
id: string;
|
|
17
|
-
} &
|
|
18
|
-
Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
17
|
+
} & Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
19
18
|
Pick<
|
|
20
19
|
TreeItemProps<T>,
|
|
21
|
-
|
|
20
|
+
| 'draggable'
|
|
21
|
+
| 'renderColumns'
|
|
22
|
+
| 'blockInstruction'
|
|
23
|
+
| 'canDrop'
|
|
24
|
+
| 'canSelect'
|
|
25
|
+
| 'onOpenChange'
|
|
26
|
+
| 'onSelect'
|
|
27
|
+
| 'onItemHover'
|
|
28
|
+
| 'levelOffset'
|
|
22
29
|
>;
|
|
23
30
|
|
|
24
|
-
export const Tree = <T extends
|
|
25
|
-
|
|
31
|
+
export const Tree = <T extends { id: string } = any>({
|
|
32
|
+
model,
|
|
33
|
+
rootId,
|
|
26
34
|
path,
|
|
27
35
|
id,
|
|
28
|
-
useItems,
|
|
29
|
-
getProps,
|
|
30
|
-
isOpen,
|
|
31
|
-
isCurrent,
|
|
32
36
|
draggable = false,
|
|
33
37
|
gridTemplateColumns = '[tree-row-start] 1fr min-content [tree-row-end]',
|
|
34
38
|
classNames,
|
|
35
39
|
levelOffset,
|
|
36
40
|
renderColumns,
|
|
41
|
+
blockInstruction,
|
|
37
42
|
canDrop,
|
|
38
43
|
canSelect,
|
|
39
44
|
onOpenChange,
|
|
40
45
|
onSelect,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
useItems,
|
|
45
|
-
getProps,
|
|
46
|
-
isOpen,
|
|
47
|
-
isCurrent,
|
|
48
|
-
}),
|
|
49
|
-
[useItems, getProps, isOpen, isCurrent],
|
|
50
|
-
);
|
|
51
|
-
const items = useItems(root);
|
|
46
|
+
onItemHover,
|
|
47
|
+
}: TreeProps<T>) => {
|
|
48
|
+
const childIds = useAtomValue(model.childIds(rootId));
|
|
52
49
|
const treePath = useMemo(() => (path ? [...path, id] : [id]), [id, path]);
|
|
53
50
|
|
|
51
|
+
const childProps: Omit<TreeItemByIdProps, 'id' | 'last'> = {
|
|
52
|
+
path: treePath,
|
|
53
|
+
levelOffset,
|
|
54
|
+
draggable,
|
|
55
|
+
renderColumns,
|
|
56
|
+
blockInstruction,
|
|
57
|
+
canDrop,
|
|
58
|
+
canSelect,
|
|
59
|
+
onOpenChange,
|
|
60
|
+
onSelect,
|
|
61
|
+
onItemHover,
|
|
62
|
+
};
|
|
63
|
+
|
|
54
64
|
return (
|
|
55
65
|
<Treegrid.Root gridTemplateColumns={gridTemplateColumns} classNames={classNames}>
|
|
56
|
-
<TreeProvider value={
|
|
57
|
-
{
|
|
58
|
-
<
|
|
59
|
-
key={item.id}
|
|
60
|
-
item={item}
|
|
61
|
-
last={index === items.length - 1}
|
|
62
|
-
path={treePath}
|
|
63
|
-
levelOffset={levelOffset}
|
|
64
|
-
draggable={draggable}
|
|
65
|
-
renderColumns={renderColumns}
|
|
66
|
-
canDrop={canDrop}
|
|
67
|
-
canSelect={canSelect}
|
|
68
|
-
onOpenChange={onOpenChange}
|
|
69
|
-
onSelect={onSelect}
|
|
70
|
-
/>
|
|
66
|
+
<TreeProvider value={model}>
|
|
67
|
+
{childIds.map((childId, index) => (
|
|
68
|
+
<TreeItemById key={childId} id={childId} last={index === childIds.length - 1} {...childProps} />
|
|
71
69
|
))}
|
|
72
70
|
</TreeProvider>
|
|
73
71
|
</Treegrid.Root>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { type Atom } from '@effect-atom/atom-react';
|
|
5
6
|
import { createContext, useContext } from 'react';
|
|
6
7
|
|
|
7
8
|
import { raise } from '@dxos/debug';
|
|
@@ -14,19 +15,25 @@ export type TreeItemDataProps = {
|
|
|
14
15
|
className?: string;
|
|
15
16
|
headingClassName?: string;
|
|
16
17
|
icon?: string;
|
|
17
|
-
|
|
18
|
+
iconHue?: string;
|
|
18
19
|
disabled?: boolean;
|
|
19
20
|
testId?: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
export interface TreeModel<T extends { id: string } = any> {
|
|
24
|
+
/** Atom family: resolve item by ID (content). */
|
|
25
|
+
item: (id: string) => Atom.Atom<T | undefined>;
|
|
26
|
+
/** Atom family: open state keyed by path. */
|
|
27
|
+
itemOpen: (path: string[]) => Atom.Atom<boolean>;
|
|
28
|
+
/** Atom family: current (selected) state keyed by path. */
|
|
29
|
+
itemCurrent: (path: string[]) => Atom.Atom<boolean>;
|
|
30
|
+
/** Atom family: display props for an item at a given path (path includes item's own ID at end). */
|
|
31
|
+
itemProps: (path: string[]) => Atom.Atom<TreeItemDataProps>;
|
|
32
|
+
/** Atom family: outbound child IDs for a parent ID (topology). Undefined = root. */
|
|
33
|
+
childIds: (parentId?: string) => Atom.Atom<string[]>;
|
|
34
|
+
}
|
|
28
35
|
|
|
29
|
-
const TreeContext = createContext<
|
|
36
|
+
const TreeContext = createContext<TreeModel | null>(null);
|
|
30
37
|
|
|
31
38
|
export const TreeProvider = TreeContext.Provider;
|
|
32
39
|
|
|
@@ -10,10 +10,20 @@ import {
|
|
|
10
10
|
attachInstruction,
|
|
11
11
|
extractInstruction,
|
|
12
12
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
13
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
13
14
|
import * as Schema from 'effect/Schema';
|
|
14
|
-
import React, {
|
|
15
|
+
import React, {
|
|
16
|
+
type FC,
|
|
17
|
+
type KeyboardEvent,
|
|
18
|
+
type MouseEvent,
|
|
19
|
+
memo,
|
|
20
|
+
useCallback,
|
|
21
|
+
useEffect,
|
|
22
|
+
useMemo,
|
|
23
|
+
useRef,
|
|
24
|
+
useState,
|
|
25
|
+
} from 'react';
|
|
15
26
|
|
|
16
|
-
import { type HasId } from '@dxos/echo/internal';
|
|
17
27
|
import { invariant } from '@dxos/invariant';
|
|
18
28
|
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
19
29
|
import {
|
|
@@ -22,7 +32,8 @@ import {
|
|
|
22
32
|
hoverableControls,
|
|
23
33
|
hoverableFocusedKeyboardControls,
|
|
24
34
|
hoverableFocusedWithinControls,
|
|
25
|
-
|
|
35
|
+
mx,
|
|
36
|
+
} from '@dxos/ui-theme';
|
|
26
37
|
|
|
27
38
|
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
28
39
|
import { useTree } from './TreeContext';
|
|
@@ -32,7 +43,7 @@ import { TreeItemToggle } from './TreeItemToggle';
|
|
|
32
43
|
const hoverableDescriptionIcons =
|
|
33
44
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
34
45
|
|
|
35
|
-
type
|
|
46
|
+
type TreeItemDragState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
36
47
|
|
|
37
48
|
export const TreeDataSchema = Schema.Struct({
|
|
38
49
|
id: Schema.String,
|
|
@@ -43,7 +54,7 @@ export const TreeDataSchema = Schema.Struct({
|
|
|
43
54
|
export type TreeData = Schema.Schema.Type<typeof TreeDataSchema>;
|
|
44
55
|
export const isTreeData = (data: unknown): data is TreeData => Schema.is(TreeDataSchema)(data);
|
|
45
56
|
|
|
46
|
-
export type ColumnRenderer<T extends
|
|
57
|
+
export type ColumnRenderer<T extends { id: string } = any> = FC<{
|
|
47
58
|
item: T;
|
|
48
59
|
path: string[];
|
|
49
60
|
open: boolean;
|
|
@@ -51,52 +62,63 @@ export type ColumnRenderer<T extends HasId = any> = FC<{
|
|
|
51
62
|
setMenuOpen: (open: boolean) => void;
|
|
52
63
|
}>;
|
|
53
64
|
|
|
54
|
-
export type TreeItemProps<T extends
|
|
65
|
+
export type TreeItemProps<T extends { id: string } = any> = {
|
|
55
66
|
item: T;
|
|
56
67
|
path: string[];
|
|
57
68
|
levelOffset?: number;
|
|
58
69
|
last: boolean;
|
|
59
70
|
draggable?: boolean;
|
|
60
71
|
renderColumns?: ColumnRenderer<T>;
|
|
72
|
+
blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
|
|
61
73
|
canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
|
|
62
74
|
canSelect?: (params: { item: T; path: string[] }) => boolean;
|
|
63
75
|
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
64
76
|
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
77
|
+
onItemHover?: (params: { item: T }) => void;
|
|
65
78
|
};
|
|
66
79
|
|
|
67
|
-
const RawTreeItem = <T extends
|
|
80
|
+
const RawTreeItem = <T extends { id: string } = any>({
|
|
68
81
|
item,
|
|
69
|
-
path:
|
|
82
|
+
path: pathProp,
|
|
70
83
|
levelOffset = 2,
|
|
71
84
|
last,
|
|
72
|
-
draggable:
|
|
85
|
+
draggable: draggableProp,
|
|
73
86
|
renderColumns: Columns,
|
|
87
|
+
blockInstruction,
|
|
74
88
|
canDrop,
|
|
75
89
|
canSelect,
|
|
76
90
|
onOpenChange,
|
|
77
91
|
onSelect,
|
|
92
|
+
onItemHover,
|
|
78
93
|
}: TreeItemProps<T>) => {
|
|
79
94
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
80
95
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
81
96
|
const openRef = useRef(false);
|
|
82
97
|
const cancelExpandRef = useRef<NodeJS.Timeout | null>(null);
|
|
83
|
-
const [_state, setState] = useState<
|
|
98
|
+
const [_state, setState] = useState<TreeItemDragState>('idle');
|
|
84
99
|
const [instruction, setInstruction] = useState<Instruction | null>(null);
|
|
85
100
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
86
101
|
|
|
87
|
-
const {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
102
|
+
const {
|
|
103
|
+
itemProps: itemPropsAtom,
|
|
104
|
+
childIds: childIdsAtom,
|
|
105
|
+
itemOpen: itemOpenAtom,
|
|
106
|
+
itemCurrent: itemCurrentAtom,
|
|
107
|
+
} = useTree();
|
|
108
|
+
const path = useMemo(() => [...pathProp, item.id], [pathProp, item.id]);
|
|
109
|
+
|
|
110
|
+
const { id, parentOf, label, className, headingClassName, icon, iconHue, disabled, testId } = useAtomValue(
|
|
111
|
+
itemPropsAtom(path),
|
|
92
112
|
);
|
|
93
|
-
const
|
|
94
|
-
const open =
|
|
95
|
-
const current =
|
|
113
|
+
const childIds = useAtomValue(childIdsAtom(item.id));
|
|
114
|
+
const open = useAtomValue(itemOpenAtom(path));
|
|
115
|
+
const current = useAtomValue(itemCurrentAtom(path));
|
|
116
|
+
|
|
96
117
|
const level = path.length - levelOffset;
|
|
97
118
|
const isBranch = !!parentOf;
|
|
98
119
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
99
120
|
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
121
|
+
const data = { id, path, item } satisfies TreeData;
|
|
100
122
|
|
|
101
123
|
const cancelExpand = useCallback(() => {
|
|
102
124
|
if (cancelExpandRef.current) {
|
|
@@ -106,14 +128,12 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
106
128
|
}, []);
|
|
107
129
|
|
|
108
130
|
useEffect(() => {
|
|
109
|
-
if (!
|
|
131
|
+
if (!draggableProp) {
|
|
110
132
|
return;
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
invariant(buttonRef.current);
|
|
114
136
|
|
|
115
|
-
const data = { id, path, item } satisfies TreeData;
|
|
116
|
-
|
|
117
137
|
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
118
138
|
return combine(
|
|
119
139
|
draggable({
|
|
@@ -152,7 +172,11 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
152
172
|
},
|
|
153
173
|
getIsSticky: () => true,
|
|
154
174
|
onDrag: ({ self, source }) => {
|
|
155
|
-
const
|
|
175
|
+
const desired = extractInstruction(self.data);
|
|
176
|
+
const block =
|
|
177
|
+
desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
|
|
178
|
+
const instruction: Instruction | null =
|
|
179
|
+
block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
|
|
156
180
|
|
|
157
181
|
if (source.data.id !== id) {
|
|
158
182
|
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
@@ -183,7 +207,7 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
183
207
|
},
|
|
184
208
|
}),
|
|
185
209
|
);
|
|
186
|
-
}, [
|
|
210
|
+
}, [draggableProp, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
187
211
|
|
|
188
212
|
// Cancel expand on unmount.
|
|
189
213
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
@@ -221,6 +245,29 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
221
245
|
[isBranch, open, handleOpenToggle, handleSelect],
|
|
222
246
|
);
|
|
223
247
|
|
|
248
|
+
const handleItemHover = useCallback(() => {
|
|
249
|
+
onItemHover?.({ item });
|
|
250
|
+
}, [onItemHover, item]);
|
|
251
|
+
|
|
252
|
+
const handleContextMenu = useCallback(
|
|
253
|
+
(event: MouseEvent) => {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
setMenuOpen(true);
|
|
256
|
+
},
|
|
257
|
+
[setMenuOpen],
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const childProps = {
|
|
261
|
+
draggable: draggableProp,
|
|
262
|
+
renderColumns: Columns,
|
|
263
|
+
blockInstruction,
|
|
264
|
+
canDrop,
|
|
265
|
+
canSelect,
|
|
266
|
+
onItemHover,
|
|
267
|
+
onOpenChange,
|
|
268
|
+
onSelect,
|
|
269
|
+
};
|
|
270
|
+
|
|
224
271
|
return (
|
|
225
272
|
<>
|
|
226
273
|
<Treegrid.Row
|
|
@@ -229,27 +276,25 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
229
276
|
id={id}
|
|
230
277
|
aria-labelledby={`${id}__label`}
|
|
231
278
|
parentOf={parentOf?.join(Treegrid.PARENT_OF_SEPARATOR)}
|
|
232
|
-
|
|
233
|
-
|
|
279
|
+
data-object-id={id}
|
|
280
|
+
data-testid={testId}
|
|
281
|
+
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
282
|
+
// without alerting the user (except for in the correct link element). See also:
|
|
283
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
284
|
+
aria-current={current ? ('' as 'page') : undefined}
|
|
285
|
+
classNames={mx(
|
|
286
|
+
'grid grid-cols-subgrid col-[tree-row] mt-0.5 is-current:bg-active-surface',
|
|
234
287
|
hoverableControls,
|
|
235
288
|
hoverableFocusedKeyboardControls,
|
|
236
289
|
hoverableFocusedWithinControls,
|
|
237
290
|
hoverableDescriptionIcons,
|
|
238
|
-
ghostHover,
|
|
239
291
|
ghostFocusWithin,
|
|
292
|
+
ghostHover,
|
|
240
293
|
className,
|
|
241
|
-
|
|
242
|
-
data-itemid={id}
|
|
243
|
-
data-testid={testId}
|
|
244
|
-
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
245
|
-
// without alerting the user (except for in the correct link element). See also:
|
|
246
|
-
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
247
|
-
aria-current={current ? ('' as 'page') : undefined}
|
|
294
|
+
)}
|
|
248
295
|
onKeyDown={handleKeyDown}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
setMenuOpen(true);
|
|
252
|
-
}}
|
|
296
|
+
onMouseEnter={handleItemHover}
|
|
297
|
+
onContextMenu={handleContextMenu}
|
|
253
298
|
>
|
|
254
299
|
<div
|
|
255
300
|
role='none'
|
|
@@ -264,7 +309,7 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
264
309
|
label={label}
|
|
265
310
|
className={headingClassName}
|
|
266
311
|
icon={icon}
|
|
267
|
-
|
|
312
|
+
iconHue={iconHue}
|
|
268
313
|
onSelect={handleSelect}
|
|
269
314
|
ref={buttonRef}
|
|
270
315
|
/>
|
|
@@ -274,22 +319,25 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
274
319
|
</div>
|
|
275
320
|
</Treegrid.Row>
|
|
276
321
|
{open &&
|
|
277
|
-
|
|
278
|
-
<
|
|
279
|
-
key={item.id}
|
|
280
|
-
item={item}
|
|
281
|
-
path={path}
|
|
282
|
-
last={index === items.length - 1}
|
|
283
|
-
draggable={_draggable}
|
|
284
|
-
renderColumns={Columns}
|
|
285
|
-
canDrop={canDrop}
|
|
286
|
-
canSelect={canSelect}
|
|
287
|
-
onOpenChange={onOpenChange}
|
|
288
|
-
onSelect={onSelect}
|
|
289
|
-
/>
|
|
322
|
+
childIds.map((childId, index) => (
|
|
323
|
+
<TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
|
|
290
324
|
))}
|
|
291
325
|
</>
|
|
292
326
|
);
|
|
293
327
|
};
|
|
294
328
|
|
|
295
329
|
export const TreeItem = memo(RawTreeItem) as FC<TreeItemProps>;
|
|
330
|
+
|
|
331
|
+
/** Resolves a child ID to an item via the `item` atom and renders a TreeItem. */
|
|
332
|
+
export type TreeItemByIdProps = Omit<TreeItemProps, 'item'> & { id: string };
|
|
333
|
+
|
|
334
|
+
const RawTreeItemById = <T extends { id: string } = any>({ id, ...props }: TreeItemByIdProps) => {
|
|
335
|
+
const { item: itemAtom } = useTree();
|
|
336
|
+
const item = useAtomValue(itemAtom(id)) as T | undefined;
|
|
337
|
+
if (!item) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
return <TreeItem item={item} {...props} />;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
export const TreeItemById = memo(RawTreeItemById) as FC<TreeItemByIdProps>;
|
|
@@ -6,6 +6,7 @@ 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/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.
|
|
@@ -13,7 +14,7 @@ export type TreeItemHeadingProps = {
|
|
|
13
14
|
label: Label;
|
|
14
15
|
className?: string;
|
|
15
16
|
icon?: string;
|
|
16
|
-
|
|
17
|
+
iconHue?: string;
|
|
17
18
|
disabled?: boolean;
|
|
18
19
|
current?: boolean;
|
|
19
20
|
onSelect?: (option: boolean) => void;
|
|
@@ -21,8 +22,9 @@ export type TreeItemHeadingProps = {
|
|
|
21
22
|
|
|
22
23
|
export const TreeItemHeading = memo(
|
|
23
24
|
forwardRef<HTMLButtonElement, TreeItemHeadingProps>(
|
|
24
|
-
({ label, className, icon,
|
|
25
|
+
({ label, className, icon, iconHue, disabled, current, onSelect }, forwardedRef) => {
|
|
25
26
|
const { t } = useTranslation();
|
|
27
|
+
const styles = iconHue ? getStyles(iconHue) : undefined;
|
|
26
28
|
|
|
27
29
|
const handleSelect = useCallback(
|
|
28
30
|
(event: MouseEvent) => {
|
|
@@ -56,7 +58,7 @@ export const TreeItemHeading = memo(
|
|
|
56
58
|
variant='ghost'
|
|
57
59
|
density='fine'
|
|
58
60
|
classNames={[
|
|
59
|
-
'grow gap-2
|
|
61
|
+
'grow gap-2 ps-0.5 hover:bg-transparent dark:hover:bg-transparent',
|
|
60
62
|
'disabled:cursor-default disabled:opacity-100',
|
|
61
63
|
className,
|
|
62
64
|
]}
|
|
@@ -65,8 +67,10 @@ export const TreeItemHeading = memo(
|
|
|
65
67
|
onKeyDown={handleButtonKeydown}
|
|
66
68
|
{...(current && { 'aria-current': 'location' })}
|
|
67
69
|
>
|
|
68
|
-
{icon &&
|
|
69
|
-
|
|
70
|
+
{icon && (
|
|
71
|
+
<Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['my-1', styles?.surfaceText]} />
|
|
72
|
+
)}
|
|
73
|
+
<span className='flex-1 w-0 truncate text-start font-normal' data-tooltip>
|
|
70
74
|
{toLocalizedString(label, t)}
|
|
71
75
|
</span>
|
|
72
76
|
</Button>
|
|
@@ -23,7 +23,7 @@ export const TreeItemToggle = memo(
|
|
|
23
23
|
variant='ghost'
|
|
24
24
|
density='fine'
|
|
25
25
|
classNames={[
|
|
26
|
-
'
|
|
26
|
+
'h-full w-6 px-0',
|
|
27
27
|
'[&_svg]:transition-[transform] [&_svg]:duration-200',
|
|
28
28
|
open && '[&_svg]:rotate-90',
|
|
29
29
|
hidden ? 'hidden' : !isBranch && 'invisible',
|
|
@@ -5,20 +5,21 @@
|
|
|
5
5
|
import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
6
6
|
import * as Schema from 'effect/Schema';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { Obj } from '@dxos/echo';
|
|
9
9
|
import { log } from '@dxos/log';
|
|
10
10
|
import { faker } from '@dxos/random';
|
|
11
11
|
|
|
12
12
|
import { type TreeData } from './TreeItem';
|
|
13
13
|
|
|
14
|
-
export type TestItem =
|
|
14
|
+
export type TestItem = {
|
|
15
|
+
id: string;
|
|
15
16
|
name: string;
|
|
16
17
|
icon?: string;
|
|
17
18
|
items: TestItem[];
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
export const TestItemSchema = Schema.Struct({
|
|
21
|
-
id:
|
|
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))),
|