@dxos/react-ui-list 0.8.4-main.406dc2a → 0.8.4-main.422d1c7879
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 +681 -715
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +681 -715
- 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/Accordion/AccordionItem.d.ts.map +1 -1
- package/dist/types/src/components/List/List.d.ts +8 -6
- 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 +8 -6
- 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 +22 -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 +31 -31
- package/src/components/Accordion/Accordion.stories.tsx +5 -8
- package/src/components/Accordion/AccordionItem.tsx +3 -4
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/List/List.stories.tsx +33 -21
- package/src/components/List/List.tsx +4 -9
- package/src/components/List/ListItem.tsx +60 -40
- package/src/components/List/ListRoot.tsx +3 -3
- package/src/components/List/testing.ts +6 -6
- package/src/components/Tree/Tree.stories.tsx +153 -64
- package/src/components/Tree/Tree.tsx +39 -41
- package/src/components/Tree/TreeContext.tsx +19 -8
- package/src/components/Tree/TreeItem.tsx +185 -107
- package/src/components/Tree/TreeItemHeading.tsx +7 -6
- package/src/components/Tree/TreeItemToggle.tsx +4 -4
- package/src/components/Tree/index.ts +2 -0
- package/src/components/Tree/testing.ts +9 -8
|
@@ -2,27 +2,38 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
|
-
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
5
|
import {
|
|
8
6
|
type Instruction,
|
|
9
7
|
type ItemMode,
|
|
10
8
|
attachInstruction,
|
|
11
9
|
extractInstruction,
|
|
12
10
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
11
|
+
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
12
|
+
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
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
|
-
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
28
|
+
import { TreeItem as NaturalTreeItem, Treegrid, TREEGRID_PARENT_OF_SEPARATOR } from '@dxos/react-ui';
|
|
19
29
|
import {
|
|
20
30
|
ghostFocusWithin,
|
|
21
31
|
ghostHover,
|
|
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,74 @@ 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
|
-
|
|
92
|
-
);
|
|
93
|
-
const path = useMemo(() => [...
|
|
94
|
-
|
|
95
|
-
const
|
|
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
|
+
|
|
96
127
|
const level = path.length - levelOffset;
|
|
97
128
|
const isBranch = !!parentOf;
|
|
98
129
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
99
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');
|
|
100
133
|
|
|
101
134
|
const cancelExpand = useCallback(() => {
|
|
102
135
|
if (cancelExpandRef.current) {
|
|
@@ -105,20 +138,27 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
105
138
|
}
|
|
106
139
|
}, []);
|
|
107
140
|
|
|
141
|
+
const isItemDraggable = draggableProp && itemDraggable !== false;
|
|
142
|
+
const isItemDroppable = itemDroppable !== false;
|
|
143
|
+
const nativeDragText = id;
|
|
144
|
+
|
|
108
145
|
useEffect(() => {
|
|
109
|
-
if (!
|
|
146
|
+
if (!draggableProp) {
|
|
110
147
|
return;
|
|
111
148
|
}
|
|
112
149
|
|
|
113
150
|
invariant(buttonRef.current);
|
|
114
151
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
118
|
-
return combine(
|
|
152
|
+
const makeDraggable = () =>
|
|
119
153
|
draggable({
|
|
120
|
-
element: buttonRef.current
|
|
154
|
+
element: buttonRef.current!,
|
|
121
155
|
getInitialData: () => data,
|
|
156
|
+
getInitialDataForExternal: () => {
|
|
157
|
+
if (!shouldSeedNativeDragData) {
|
|
158
|
+
return {};
|
|
159
|
+
}
|
|
160
|
+
return { 'text/plain': nativeDragText };
|
|
161
|
+
},
|
|
122
162
|
onDragStart: () => {
|
|
123
163
|
setState('dragging');
|
|
124
164
|
if (open) {
|
|
@@ -132,58 +172,72 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
132
172
|
onOpenChange?.({ item, path, open: true });
|
|
133
173
|
}
|
|
134
174
|
},
|
|
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
|
-
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
171
|
-
setInstruction(instruction);
|
|
172
|
-
} else {
|
|
173
|
-
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);
|
|
174
210
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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 {
|
|
182
221
|
setInstruction(null);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
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]);
|
|
187
241
|
|
|
188
242
|
// Cancel expand on unmount.
|
|
189
243
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
@@ -221,6 +275,29 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
221
275
|
[isBranch, open, handleOpenToggle, handleSelect],
|
|
222
276
|
);
|
|
223
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
|
+
|
|
224
301
|
return (
|
|
225
302
|
<>
|
|
226
303
|
<Treegrid.Row
|
|
@@ -228,28 +305,26 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
228
305
|
key={id}
|
|
229
306
|
id={id}
|
|
230
307
|
aria-labelledby={`${id}__label`}
|
|
231
|
-
parentOf={parentOf?.join(
|
|
232
|
-
|
|
233
|
-
|
|
308
|
+
parentOf={parentOf?.join(TREEGRID_PARENT_OF_SEPARATOR)}
|
|
309
|
+
data-object-id={id}
|
|
310
|
+
data-testid={testId}
|
|
311
|
+
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
312
|
+
// without alerting the user (except for in the correct link element). See also:
|
|
313
|
+
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
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',
|
|
234
317
|
hoverableControls,
|
|
235
318
|
hoverableFocusedKeyboardControls,
|
|
236
319
|
hoverableFocusedWithinControls,
|
|
237
320
|
hoverableDescriptionIcons,
|
|
238
|
-
ghostHover,
|
|
239
321
|
ghostFocusWithin,
|
|
322
|
+
ghostHover,
|
|
240
323
|
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}
|
|
324
|
+
)}
|
|
248
325
|
onKeyDown={handleKeyDown}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
setMenuOpen(true);
|
|
252
|
-
}}
|
|
326
|
+
onMouseEnter={handleItemHover}
|
|
327
|
+
onContextMenu={handleContextMenu}
|
|
253
328
|
>
|
|
254
329
|
<div
|
|
255
330
|
role='none'
|
|
@@ -264,7 +339,7 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
264
339
|
label={label}
|
|
265
340
|
className={headingClassName}
|
|
266
341
|
icon={icon}
|
|
267
|
-
|
|
342
|
+
iconHue={iconHue}
|
|
268
343
|
onSelect={handleSelect}
|
|
269
344
|
ref={buttonRef}
|
|
270
345
|
/>
|
|
@@ -274,22 +349,25 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
274
349
|
</div>
|
|
275
350
|
</Treegrid.Row>
|
|
276
351
|
{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
|
-
/>
|
|
352
|
+
childIds.map((childId, index) => (
|
|
353
|
+
<TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
|
|
290
354
|
))}
|
|
291
355
|
</>
|
|
292
356
|
);
|
|
293
357
|
};
|
|
294
358
|
|
|
295
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>;
|
|
@@ -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) => {
|
|
@@ -54,9 +56,8 @@ export const TreeItemHeading = memo(
|
|
|
54
56
|
<Button
|
|
55
57
|
data-testid='treeItem.heading'
|
|
56
58
|
variant='ghost'
|
|
57
|
-
density='fine'
|
|
58
59
|
classNames={[
|
|
59
|
-
'grow gap-2
|
|
60
|
+
'grow gap-2 ps-0.5 hover:bg-transparent dark:hover:bg-transparent',
|
|
60
61
|
'disabled:cursor-default disabled:opacity-100',
|
|
61
62
|
className,
|
|
62
63
|
]}
|
|
@@ -65,8 +66,8 @@ export const TreeItemHeading = memo(
|
|
|
65
66
|
onKeyDown={handleButtonKeydown}
|
|
66
67
|
{...(current && { 'aria-current': 'location' })}
|
|
67
68
|
>
|
|
68
|
-
{icon && <Icon icon={icon ?? 'ph--placeholder--regular'}
|
|
69
|
-
<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>
|
|
70
71
|
{toLocalizedString(label, t)}
|
|
71
72
|
</span>
|
|
72
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
|
]}
|
|
@@ -5,38 +5,39 @@
|
|
|
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
|
-
import {
|
|
10
|
+
import { random } 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))),
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
export const createTree = (n = 4, d = 4): TestItem => ({
|
|
28
|
-
id:
|
|
29
|
-
name:
|
|
29
|
+
id: random.string.uuid(),
|
|
30
|
+
name: random.commerce.productName(),
|
|
30
31
|
icon:
|
|
31
32
|
d === 3
|
|
32
33
|
? undefined
|
|
33
|
-
:
|
|
34
|
+
: random.helpers.arrayElement([
|
|
34
35
|
'ph--planet--regular',
|
|
35
36
|
'ph--sailboat--regular',
|
|
36
37
|
'ph--house--regular',
|
|
37
38
|
'ph--gear--regular',
|
|
38
39
|
]),
|
|
39
|
-
items: d > 0 ?
|
|
40
|
+
items: d > 0 ? random.helpers.multiple(() => createTree(n, d - 1), { count: n }) : [],
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
const removeItem = (tree: TestItem, source: TreeData) => {
|