@dxos/react-ui-list 0.8.4-main.bc674ce → 0.8.4-main.bcb3aa67d6
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 +230 -191
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +230 -191
- package/dist/lib/node-esm/index.mjs.map +3 -3
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/List/List.d.ts +6 -4
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +8 -6
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.d.ts +6 -5
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +21 -10
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +8 -0
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +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/tsconfig.tsbuildinfo +1 -1
- package/package.json +19 -18
- package/src/components/Accordion/Accordion.stories.tsx +3 -3
- package/src/components/Accordion/AccordionItem.tsx +1 -1
- package/src/components/List/List.stories.tsx +10 -10
- package/src/components/List/List.tsx +4 -9
- package/src/components/List/ListItem.tsx +55 -35
- package/src/components/Tree/Tree.stories.tsx +103 -27
- package/src/components/Tree/Tree.tsx +30 -40
- package/src/components/Tree/TreeContext.tsx +18 -9
- package/src/components/Tree/TreeItem.tsx +176 -101
- package/src/components/Tree/TreeItemHeading.tsx +3 -4
- package/src/components/Tree/TreeItemToggle.tsx +4 -4
- package/src/components/Tree/index.ts +2 -0
|
@@ -1,20 +1,20 @@
|
|
|
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
7
|
import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
|
|
8
8
|
|
|
9
|
-
import { type
|
|
10
|
-
import {
|
|
9
|
+
import { type TreeModel, TreeProvider } from './TreeContext';
|
|
10
|
+
import { TreeItemById, type TreeItemByIdProps, type TreeItemProps } from './TreeItem';
|
|
11
11
|
|
|
12
|
-
export type TreeProps<T extends { id: string } = any
|
|
13
|
-
|
|
12
|
+
export type TreeProps<T extends { id: string } = any> = {
|
|
13
|
+
model: TreeModel<T>;
|
|
14
|
+
rootId?: string;
|
|
14
15
|
path?: string[];
|
|
15
16
|
id: string;
|
|
16
|
-
} &
|
|
17
|
-
Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
17
|
+
} & Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
18
18
|
Pick<
|
|
19
19
|
TreeItemProps<T>,
|
|
20
20
|
| 'draggable'
|
|
@@ -24,17 +24,15 @@ export type TreeProps<T extends { id: string } = any, O = any> = {
|
|
|
24
24
|
| 'canSelect'
|
|
25
25
|
| 'onOpenChange'
|
|
26
26
|
| 'onSelect'
|
|
27
|
+
| 'onItemHover'
|
|
27
28
|
| 'levelOffset'
|
|
28
29
|
>;
|
|
29
30
|
|
|
30
|
-
export const Tree = <T extends { id: string } = any
|
|
31
|
-
|
|
31
|
+
export const Tree = <T extends { id: string } = any>({
|
|
32
|
+
model,
|
|
33
|
+
rootId,
|
|
32
34
|
path,
|
|
33
35
|
id,
|
|
34
|
-
useItems,
|
|
35
|
-
getProps,
|
|
36
|
-
useIsOpen,
|
|
37
|
-
useIsCurrent,
|
|
38
36
|
draggable = false,
|
|
39
37
|
gridTemplateColumns = '[tree-row-start] 1fr min-content [tree-row-end]',
|
|
40
38
|
classNames,
|
|
@@ -45,37 +43,29 @@ export const Tree = <T extends { id: string } = any, O = any>({
|
|
|
45
43
|
canSelect,
|
|
46
44
|
onOpenChange,
|
|
47
45
|
onSelect,
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
useItems,
|
|
52
|
-
getProps,
|
|
53
|
-
useIsOpen,
|
|
54
|
-
useIsCurrent,
|
|
55
|
-
}),
|
|
56
|
-
[useItems, getProps, useIsOpen, useIsCurrent],
|
|
57
|
-
);
|
|
58
|
-
const items = useItems(root);
|
|
46
|
+
onItemHover,
|
|
47
|
+
}: TreeProps<T>) => {
|
|
48
|
+
const childIds = useAtomValue(model.childIds(rootId));
|
|
59
49
|
const treePath = useMemo(() => (path ? [...path, id] : [id]), [id, path]);
|
|
60
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
|
+
|
|
61
64
|
return (
|
|
62
65
|
<Treegrid.Root gridTemplateColumns={gridTemplateColumns} classNames={classNames}>
|
|
63
|
-
<TreeProvider value={
|
|
64
|
-
{
|
|
65
|
-
<
|
|
66
|
-
key={item.id}
|
|
67
|
-
item={item}
|
|
68
|
-
last={index === items.length - 1}
|
|
69
|
-
path={treePath}
|
|
70
|
-
levelOffset={levelOffset}
|
|
71
|
-
draggable={draggable}
|
|
72
|
-
renderColumns={renderColumns}
|
|
73
|
-
blockInstruction={blockInstruction}
|
|
74
|
-
canDrop={canDrop}
|
|
75
|
-
canSelect={canSelect}
|
|
76
|
-
onOpenChange={onOpenChange}
|
|
77
|
-
onSelect={onSelect}
|
|
78
|
-
/>
|
|
66
|
+
<TreeProvider value={model}>
|
|
67
|
+
{childIds.map((childId, index) => (
|
|
68
|
+
<TreeItemById key={childId} id={childId} last={index === childIds.length - 1} {...childProps} />
|
|
79
69
|
))}
|
|
80
70
|
</TreeProvider>
|
|
81
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';
|
|
@@ -11,6 +12,10 @@ export type TreeItemDataProps = {
|
|
|
11
12
|
id: string;
|
|
12
13
|
label: Label;
|
|
13
14
|
parentOf?: string[];
|
|
15
|
+
/** When `false`, the item cannot be dragged (overrides tree-level `draggable`). */
|
|
16
|
+
draggable?: boolean;
|
|
17
|
+
/** When `false`, the item does not participate as a drop target. */
|
|
18
|
+
droppable?: boolean;
|
|
14
19
|
className?: string;
|
|
15
20
|
headingClassName?: string;
|
|
16
21
|
icon?: string;
|
|
@@ -19,16 +24,20 @@ export type TreeItemDataProps = {
|
|
|
19
24
|
testId?: string;
|
|
20
25
|
};
|
|
21
26
|
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
export interface TreeModel<T extends { id: string } = any> {
|
|
28
|
+
/** Atom family: resolve item by ID (content). */
|
|
29
|
+
item: (id: string) => Atom.Atom<T | undefined>;
|
|
30
|
+
/** Atom family: open state keyed by path. */
|
|
31
|
+
itemOpen: (path: string[]) => Atom.Atom<boolean>;
|
|
32
|
+
/** Atom family: current (selected) state keyed by path. */
|
|
33
|
+
itemCurrent: (path: string[]) => Atom.Atom<boolean>;
|
|
34
|
+
/** Atom family: display props for an item at a given path (path includes item's own ID at end). */
|
|
35
|
+
itemProps: (path: string[]) => Atom.Atom<TreeItemDataProps>;
|
|
36
|
+
/** Atom family: outbound child IDs for a parent ID (topology). Undefined = root. */
|
|
37
|
+
childIds: (parentId?: string) => Atom.Atom<string[]>;
|
|
38
|
+
}
|
|
30
39
|
|
|
31
|
-
const TreeContext = createContext<
|
|
40
|
+
const TreeContext = createContext<TreeModel | null>(null);
|
|
32
41
|
|
|
33
42
|
export const TreeProvider = TreeContext.Provider;
|
|
34
43
|
|
|
@@ -10,17 +10,29 @@ 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
27
|
import { invariant } from '@dxos/invariant';
|
|
17
|
-
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
28
|
+
import { TreeItem as NaturalTreeItem, Treegrid, TREEGRID_PARENT_OF_SEPARATOR } from '@dxos/react-ui';
|
|
18
29
|
import {
|
|
19
30
|
ghostFocusWithin,
|
|
20
31
|
ghostHover,
|
|
21
32
|
hoverableControls,
|
|
22
33
|
hoverableFocusedKeyboardControls,
|
|
23
34
|
hoverableFocusedWithinControls,
|
|
35
|
+
mx,
|
|
24
36
|
} from '@dxos/ui-theme';
|
|
25
37
|
|
|
26
38
|
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
@@ -31,7 +43,7 @@ import { TreeItemToggle } from './TreeItemToggle';
|
|
|
31
43
|
const hoverableDescriptionIcons =
|
|
32
44
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
33
45
|
|
|
34
|
-
type
|
|
46
|
+
type TreeItemDragState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
35
47
|
|
|
36
48
|
export const TreeDataSchema = Schema.Struct({
|
|
37
49
|
id: Schema.String,
|
|
@@ -62,39 +74,62 @@ export type TreeItemProps<T extends { id: string } = any> = {
|
|
|
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
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,
|
|
74
87
|
blockInstruction,
|
|
75
88
|
canDrop,
|
|
76
89
|
canSelect,
|
|
77
90
|
onOpenChange,
|
|
78
91
|
onSelect,
|
|
92
|
+
onItemHover,
|
|
79
93
|
}: TreeItemProps<T>) => {
|
|
80
94
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
81
95
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
82
96
|
const openRef = useRef(false);
|
|
83
97
|
const cancelExpandRef = useRef<NodeJS.Timeout | null>(null);
|
|
84
|
-
const [_state, setState] = useState<
|
|
98
|
+
const [_state, setState] = useState<TreeItemDragState>('idle');
|
|
85
99
|
const [instruction, setInstruction] = useState<Instruction | null>(null);
|
|
86
100
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
87
101
|
|
|
88
|
-
const {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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 {
|
|
111
|
+
id,
|
|
112
|
+
parentOf,
|
|
113
|
+
draggable: itemDraggable,
|
|
114
|
+
droppable: itemDroppable,
|
|
115
|
+
label,
|
|
116
|
+
className,
|
|
117
|
+
headingClassName,
|
|
118
|
+
icon,
|
|
119
|
+
iconHue,
|
|
120
|
+
disabled,
|
|
121
|
+
testId,
|
|
122
|
+
} = useAtomValue(itemPropsAtom(path));
|
|
123
|
+
const childIds = useAtomValue(childIdsAtom(item.id));
|
|
124
|
+
const open = useAtomValue(itemOpenAtom(path));
|
|
125
|
+
const current = useAtomValue(itemCurrentAtom(path));
|
|
126
|
+
|
|
94
127
|
const level = path.length - levelOffset;
|
|
95
128
|
const isBranch = !!parentOf;
|
|
96
129
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
97
130
|
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
131
|
+
const data = { id, path, item } satisfies TreeData;
|
|
132
|
+
const shouldSeedNativeDragData = typeof document !== 'undefined' && document.body.hasAttribute('data-platform');
|
|
98
133
|
|
|
99
134
|
const cancelExpand = useCallback(() => {
|
|
100
135
|
if (cancelExpandRef.current) {
|
|
@@ -103,20 +138,27 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
103
138
|
}
|
|
104
139
|
}, []);
|
|
105
140
|
|
|
141
|
+
const isItemDraggable = draggableProp && itemDraggable !== false;
|
|
142
|
+
const isItemDroppable = itemDroppable !== false;
|
|
143
|
+
const nativeDragText = id;
|
|
144
|
+
|
|
106
145
|
useEffect(() => {
|
|
107
|
-
if (!
|
|
146
|
+
if (!draggableProp) {
|
|
108
147
|
return;
|
|
109
148
|
}
|
|
110
149
|
|
|
111
150
|
invariant(buttonRef.current);
|
|
112
151
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
116
|
-
return combine(
|
|
152
|
+
const makeDraggable = () =>
|
|
117
153
|
draggable({
|
|
118
|
-
element: buttonRef.current
|
|
154
|
+
element: buttonRef.current!,
|
|
119
155
|
getInitialData: () => data,
|
|
156
|
+
getInitialDataForExternal: () => {
|
|
157
|
+
if (!shouldSeedNativeDragData) {
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
return { 'text/plain': nativeDragText };
|
|
161
|
+
},
|
|
120
162
|
onDragStart: () => {
|
|
121
163
|
setState('dragging');
|
|
122
164
|
if (open) {
|
|
@@ -130,62 +172,72 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
130
172
|
onOpenChange?.({ item, path, open: true });
|
|
131
173
|
}
|
|
132
174
|
},
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
setInstruction(instruction);
|
|
171
|
-
} else if (instruction?.type === 'reparent') {
|
|
172
|
-
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
173
|
-
setInstruction(instruction);
|
|
174
|
-
} else {
|
|
175
|
-
setInstruction(null);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!isItemDroppable) {
|
|
178
|
+
return isItemDraggable ? makeDraggable() : undefined;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const dropTarget = dropTargetForElements({
|
|
182
|
+
element: buttonRef.current,
|
|
183
|
+
getData: ({ input, element }) => {
|
|
184
|
+
return attachInstruction(data, {
|
|
185
|
+
input,
|
|
186
|
+
element,
|
|
187
|
+
indentPerLevel: DEFAULT_INDENTATION,
|
|
188
|
+
currentLevel: level,
|
|
189
|
+
mode,
|
|
190
|
+
block: isBranch ? [] : ['make-child'],
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
canDrop: ({ source }) => {
|
|
194
|
+
const _canDrop = canDrop ?? (() => true);
|
|
195
|
+
return source.element !== buttonRef.current && _canDrop({ source: source.data as TreeData, target: data });
|
|
196
|
+
},
|
|
197
|
+
getIsSticky: () => true,
|
|
198
|
+
onDrag: ({ self, source }) => {
|
|
199
|
+
const desired = extractInstruction(self.data);
|
|
200
|
+
const block =
|
|
201
|
+
desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
|
|
202
|
+
const instruction: Instruction | null =
|
|
203
|
+
block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
|
|
204
|
+
|
|
205
|
+
if (source.data.id !== id) {
|
|
206
|
+
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
207
|
+
cancelExpandRef.current = setTimeout(() => {
|
|
208
|
+
onOpenChange?.({ item, path, open: true });
|
|
209
|
+
}, 500);
|
|
176
210
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
211
|
+
|
|
212
|
+
if (instruction?.type !== 'make-child') {
|
|
213
|
+
cancelExpand();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
setInstruction(instruction);
|
|
217
|
+
} else if (instruction?.type === 'reparent') {
|
|
218
|
+
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
219
|
+
setInstruction(instruction);
|
|
220
|
+
} else {
|
|
184
221
|
setInstruction(null);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
onDragLeave: () => {
|
|
225
|
+
cancelExpand();
|
|
226
|
+
setInstruction(null);
|
|
227
|
+
},
|
|
228
|
+
onDrop: () => {
|
|
229
|
+
cancelExpand();
|
|
230
|
+
setInstruction(null);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (!isItemDraggable) {
|
|
235
|
+
return dropTarget;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
239
|
+
return combine(makeDraggable(), dropTarget);
|
|
240
|
+
}, [draggableProp, isItemDraggable, isItemDroppable, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
189
241
|
|
|
190
242
|
// Cancel expand on unmount.
|
|
191
243
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
@@ -223,6 +275,29 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
223
275
|
[isBranch, open, handleOpenToggle, handleSelect],
|
|
224
276
|
);
|
|
225
277
|
|
|
278
|
+
const handleItemHover = useCallback(() => {
|
|
279
|
+
onItemHover?.({ item });
|
|
280
|
+
}, [onItemHover, item]);
|
|
281
|
+
|
|
282
|
+
const handleContextMenu = useCallback(
|
|
283
|
+
(event: MouseEvent) => {
|
|
284
|
+
event.preventDefault();
|
|
285
|
+
setMenuOpen(true);
|
|
286
|
+
},
|
|
287
|
+
[setMenuOpen],
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const childProps = {
|
|
291
|
+
draggable: draggableProp,
|
|
292
|
+
renderColumns: Columns,
|
|
293
|
+
blockInstruction,
|
|
294
|
+
canDrop,
|
|
295
|
+
canSelect,
|
|
296
|
+
onItemHover,
|
|
297
|
+
onOpenChange,
|
|
298
|
+
onSelect,
|
|
299
|
+
};
|
|
300
|
+
|
|
226
301
|
return (
|
|
227
302
|
<>
|
|
228
303
|
<Treegrid.Row
|
|
@@ -230,28 +305,26 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
230
305
|
key={id}
|
|
231
306
|
id={id}
|
|
232
307
|
aria-labelledby={`${id}__label`}
|
|
233
|
-
parentOf={parentOf?.join(
|
|
234
|
-
classNames={[
|
|
235
|
-
'grid grid-cols-subgrid col-[tree-row] mbs-0.5 aria-[current]:bg-activeSurface',
|
|
236
|
-
hoverableControls,
|
|
237
|
-
hoverableFocusedKeyboardControls,
|
|
238
|
-
hoverableFocusedWithinControls,
|
|
239
|
-
hoverableDescriptionIcons,
|
|
240
|
-
ghostHover,
|
|
241
|
-
ghostFocusWithin,
|
|
242
|
-
className,
|
|
243
|
-
]}
|
|
308
|
+
parentOf={parentOf?.join(TREEGRID_PARENT_OF_SEPARATOR)}
|
|
244
309
|
data-object-id={id}
|
|
245
310
|
data-testid={testId}
|
|
246
311
|
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
247
312
|
// without alerting the user (except for in the correct link element). See also:
|
|
248
313
|
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
249
314
|
aria-current={current ? ('' as 'page') : undefined}
|
|
315
|
+
classNames={mx(
|
|
316
|
+
'grid grid-cols-subgrid col-[tree-row] mt-0.5 is-current:bg-active-surface',
|
|
317
|
+
hoverableControls,
|
|
318
|
+
hoverableFocusedKeyboardControls,
|
|
319
|
+
hoverableFocusedWithinControls,
|
|
320
|
+
hoverableDescriptionIcons,
|
|
321
|
+
ghostFocusWithin,
|
|
322
|
+
ghostHover,
|
|
323
|
+
className,
|
|
324
|
+
)}
|
|
250
325
|
onKeyDown={handleKeyDown}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
setMenuOpen(true);
|
|
254
|
-
}}
|
|
326
|
+
onMouseEnter={handleItemHover}
|
|
327
|
+
onContextMenu={handleContextMenu}
|
|
255
328
|
>
|
|
256
329
|
<div
|
|
257
330
|
role='none'
|
|
@@ -276,23 +349,25 @@ const RawTreeItem = <T extends { id: string } = any>({
|
|
|
276
349
|
</div>
|
|
277
350
|
</Treegrid.Row>
|
|
278
351
|
{open &&
|
|
279
|
-
|
|
280
|
-
<
|
|
281
|
-
key={item.id}
|
|
282
|
-
item={item}
|
|
283
|
-
path={path}
|
|
284
|
-
last={index === items.length - 1}
|
|
285
|
-
draggable={_draggable}
|
|
286
|
-
renderColumns={Columns}
|
|
287
|
-
blockInstruction={blockInstruction}
|
|
288
|
-
canDrop={canDrop}
|
|
289
|
-
canSelect={canSelect}
|
|
290
|
-
onOpenChange={onOpenChange}
|
|
291
|
-
onSelect={onSelect}
|
|
292
|
-
/>
|
|
352
|
+
childIds.map((childId, index) => (
|
|
353
|
+
<TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
|
|
293
354
|
))}
|
|
294
355
|
</>
|
|
295
356
|
);
|
|
296
357
|
};
|
|
297
358
|
|
|
298
359
|
export const TreeItem = memo(RawTreeItem) as FC<TreeItemProps>;
|
|
360
|
+
|
|
361
|
+
/** Resolves a child ID to an item via the `item` atom and renders a TreeItem. */
|
|
362
|
+
export type TreeItemByIdProps = Omit<TreeItemProps, 'item'> & { id: string };
|
|
363
|
+
|
|
364
|
+
const RawTreeItemById = <T extends { id: string } = any>({ id, ...props }: TreeItemByIdProps) => {
|
|
365
|
+
const { item: itemAtom } = useTree();
|
|
366
|
+
const item = useAtomValue(itemAtom(id)) as T | undefined;
|
|
367
|
+
if (!item) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
return <TreeItem item={item} {...props} />;
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export const TreeItemById = memo(RawTreeItemById) as FC<TreeItemByIdProps>;
|
|
@@ -56,9 +56,8 @@ export const TreeItemHeading = memo(
|
|
|
56
56
|
<Button
|
|
57
57
|
data-testid='treeItem.heading'
|
|
58
58
|
variant='ghost'
|
|
59
|
-
density='fine'
|
|
60
59
|
classNames={[
|
|
61
|
-
'grow gap-2
|
|
60
|
+
'grow gap-2 ps-0.5 hover:bg-transparent dark:hover:bg-transparent',
|
|
62
61
|
'disabled:cursor-default disabled:opacity-100',
|
|
63
62
|
className,
|
|
64
63
|
]}
|
|
@@ -67,8 +66,8 @@ export const TreeItemHeading = memo(
|
|
|
67
66
|
onKeyDown={handleButtonKeydown}
|
|
68
67
|
{...(current && { 'aria-current': 'location' })}
|
|
69
68
|
>
|
|
70
|
-
{icon && <Icon icon={icon ?? 'ph--placeholder--regular'}
|
|
71
|
-
<span className='flex-1
|
|
69
|
+
{icon && <Icon icon={icon ?? 'ph--placeholder--regular'} classNames={['my-1', styles?.surfaceText]} />}
|
|
70
|
+
<span className='flex-1 w-0 truncate text-start font-normal' data-tooltip>
|
|
72
71
|
{toLocalizedString(label, t)}
|
|
73
72
|
</span>
|
|
74
73
|
</Button>
|
|
@@ -14,7 +14,7 @@ export type TreeItemToggleProps = Omit<IconButtonProps, 'icon' | 'size' | 'label
|
|
|
14
14
|
|
|
15
15
|
export const TreeItemToggle = memo(
|
|
16
16
|
forwardRef<HTMLButtonElement, TreeItemToggleProps>(
|
|
17
|
-
({ open, isBranch, hidden,
|
|
17
|
+
({ classNames, open, isBranch, hidden, ...props }, forwardedRef) => {
|
|
18
18
|
return (
|
|
19
19
|
<IconButton
|
|
20
20
|
ref={forwardedRef}
|
|
@@ -23,9 +23,9 @@ export const TreeItemToggle = memo(
|
|
|
23
23
|
variant='ghost'
|
|
24
24
|
density='fine'
|
|
25
25
|
classNames={[
|
|
26
|
-
'
|
|
27
|
-
'[&_svg]:transition-
|
|
28
|
-
open
|
|
26
|
+
'h-full w-6 px-0',
|
|
27
|
+
'[&_svg]:transition-transform [&_svg]:duration-200',
|
|
28
|
+
open ? '[&_svg]:rotate-90' : '[&_svg]:rotate-0',
|
|
29
29
|
hidden ? 'hidden' : !isBranch && 'invisible',
|
|
30
30
|
classNames,
|
|
31
31
|
]}
|