@dxos/react-ui-list 0.8.4-main.c1de068 → 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 +676 -729
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +676 -729
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Accordion/Accordion.stories.d.ts +7 -4
- package/dist/types/src/components/Accordion/Accordion.stories.d.ts.map +1 -1
- package/dist/types/src/components/Accordion/AccordionItem.d.ts +1 -1
- package/dist/types/src/components/Accordion/AccordionItem.d.ts.map +1 -1
- package/dist/types/src/components/List/List.d.ts +8 -8
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/List.stories.d.ts +14 -5
- package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +6 -9
- 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/List/testing.d.ts +1 -1
- package/dist/types/src/components/List/testing.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 +18 -7
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +20 -10
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +32 -10
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts +4 -3
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts +3 -3
- package/dist/types/src/components/Tree/TreeItemToggle.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 +3 -3
- package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -28
- package/src/components/Accordion/Accordion.stories.tsx +5 -7
- package/src/components/Accordion/Accordion.tsx +1 -1
- package/src/components/Accordion/AccordionItem.tsx +8 -5
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/List/List.stories.tsx +40 -26
- package/src/components/List/List.tsx +2 -5
- package/src/components/List/ListItem.tsx +50 -38
- package/src/components/List/ListRoot.tsx +3 -3
- package/src/components/List/testing.ts +3 -3
- package/src/components/Tree/Tree.stories.tsx +172 -79
- package/src/components/Tree/Tree.tsx +43 -40
- package/src/components/Tree/TreeContext.tsx +17 -9
- package/src/components/Tree/TreeItem.tsx +141 -83
- package/src/components/Tree/TreeItemHeading.tsx +13 -8
- package/src/components/Tree/TreeItemToggle.tsx +29 -18
- package/src/components/Tree/index.ts +2 -0
- package/src/components/Tree/testing.ts +5 -4
|
@@ -5,34 +5,46 @@
|
|
|
5
5
|
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
6
|
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
7
|
import {
|
|
8
|
-
attachInstruction,
|
|
9
|
-
extractInstruction,
|
|
10
8
|
type Instruction,
|
|
11
9
|
type ItemMode,
|
|
10
|
+
attachInstruction,
|
|
11
|
+
extractInstruction,
|
|
12
12
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
13
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
14
|
+
import * as Schema from 'effect/Schema';
|
|
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-schema';
|
|
17
27
|
import { invariant } from '@dxos/invariant';
|
|
18
|
-
import {
|
|
28
|
+
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
19
29
|
import {
|
|
30
|
+
ghostFocusWithin,
|
|
20
31
|
ghostHover,
|
|
21
32
|
hoverableControls,
|
|
22
33
|
hoverableFocusedKeyboardControls,
|
|
23
34
|
hoverableFocusedWithinControls,
|
|
24
|
-
|
|
35
|
+
mx,
|
|
36
|
+
} from '@dxos/ui-theme';
|
|
25
37
|
|
|
38
|
+
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
26
39
|
import { useTree } from './TreeContext';
|
|
27
40
|
import { TreeItemHeading } from './TreeItemHeading';
|
|
28
41
|
import { TreeItemToggle } from './TreeItemToggle';
|
|
29
|
-
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
30
|
-
|
|
31
|
-
type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
32
42
|
|
|
33
43
|
const hoverableDescriptionIcons =
|
|
34
44
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
35
45
|
|
|
46
|
+
type TreeItemDragState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
47
|
+
|
|
36
48
|
export const TreeDataSchema = Schema.Struct({
|
|
37
49
|
id: Schema.String,
|
|
38
50
|
path: Schema.Array(Schema.String),
|
|
@@ -40,55 +52,73 @@ export const TreeDataSchema = Schema.Struct({
|
|
|
40
52
|
});
|
|
41
53
|
|
|
42
54
|
export type TreeData = Schema.Schema.Type<typeof TreeDataSchema>;
|
|
43
|
-
|
|
44
55
|
export const isTreeData = (data: unknown): data is TreeData => Schema.is(TreeDataSchema)(data);
|
|
45
56
|
|
|
46
|
-
export type
|
|
57
|
+
export type ColumnRenderer<T extends { id: string } = any> = FC<{
|
|
58
|
+
item: T;
|
|
59
|
+
path: string[];
|
|
60
|
+
open: boolean;
|
|
61
|
+
menuOpen: boolean;
|
|
62
|
+
setMenuOpen: (open: boolean) => void;
|
|
63
|
+
}>;
|
|
64
|
+
|
|
65
|
+
export type TreeItemProps<T extends { id: string } = any> = {
|
|
47
66
|
item: T;
|
|
48
67
|
path: string[];
|
|
49
68
|
levelOffset?: number;
|
|
50
69
|
last: boolean;
|
|
51
70
|
draggable?: boolean;
|
|
52
|
-
renderColumns?:
|
|
53
|
-
|
|
54
|
-
path: string[];
|
|
55
|
-
open: boolean;
|
|
56
|
-
menuOpen: boolean;
|
|
57
|
-
setMenuOpen: (open: boolean) => void;
|
|
58
|
-
}>;
|
|
71
|
+
renderColumns?: ColumnRenderer<T>;
|
|
72
|
+
blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
|
|
59
73
|
canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
|
|
74
|
+
canSelect?: (params: { item: T; path: string[] }) => boolean;
|
|
60
75
|
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
61
76
|
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
77
|
+
onItemHover?: (params: { item: T }) => void;
|
|
62
78
|
};
|
|
63
79
|
|
|
64
|
-
const RawTreeItem = <T extends
|
|
80
|
+
const RawTreeItem = <T extends { id: string } = any>({
|
|
65
81
|
item,
|
|
66
|
-
path:
|
|
82
|
+
path: pathProp,
|
|
83
|
+
levelOffset = 2,
|
|
67
84
|
last,
|
|
68
|
-
draggable:
|
|
85
|
+
draggable: draggableProp,
|
|
69
86
|
renderColumns: Columns,
|
|
87
|
+
blockInstruction,
|
|
70
88
|
canDrop,
|
|
89
|
+
canSelect,
|
|
71
90
|
onOpenChange,
|
|
72
91
|
onSelect,
|
|
73
|
-
|
|
92
|
+
onItemHover,
|
|
74
93
|
}: TreeItemProps<T>) => {
|
|
75
94
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
76
95
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
77
96
|
const openRef = useRef(false);
|
|
78
97
|
const cancelExpandRef = useRef<NodeJS.Timeout | null>(null);
|
|
79
|
-
const [_state, setState] = useState<
|
|
98
|
+
const [_state, setState] = useState<TreeItemDragState>('idle');
|
|
80
99
|
const [instruction, setInstruction] = useState<Instruction | null>(null);
|
|
81
100
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
82
101
|
|
|
83
|
-
const {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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),
|
|
112
|
+
);
|
|
113
|
+
const childIds = useAtomValue(childIdsAtom(item.id));
|
|
114
|
+
const open = useAtomValue(itemOpenAtom(path));
|
|
115
|
+
const current = useAtomValue(itemCurrentAtom(path));
|
|
116
|
+
|
|
89
117
|
const level = path.length - levelOffset;
|
|
90
118
|
const isBranch = !!parentOf;
|
|
91
119
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
120
|
+
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
121
|
+
const data = { id, path, item } satisfies TreeData;
|
|
92
122
|
|
|
93
123
|
const cancelExpand = useCallback(() => {
|
|
94
124
|
if (cancelExpandRef.current) {
|
|
@@ -98,14 +128,12 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
98
128
|
}, []);
|
|
99
129
|
|
|
100
130
|
useEffect(() => {
|
|
101
|
-
if (!
|
|
131
|
+
if (!draggableProp) {
|
|
102
132
|
return;
|
|
103
133
|
}
|
|
104
134
|
|
|
105
135
|
invariant(buttonRef.current);
|
|
106
136
|
|
|
107
|
-
const data = { id, path, item } satisfies TreeData;
|
|
108
|
-
|
|
109
137
|
// https://atlassian.design/components/pragmatic-drag-and-drop/core-package/adapters/element/about
|
|
110
138
|
return combine(
|
|
111
139
|
draggable({
|
|
@@ -144,7 +172,11 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
144
172
|
},
|
|
145
173
|
getIsSticky: () => true,
|
|
146
174
|
onDrag: ({ self, source }) => {
|
|
147
|
-
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;
|
|
148
180
|
|
|
149
181
|
if (source.data.id !== id) {
|
|
150
182
|
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
@@ -175,45 +207,67 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
175
207
|
},
|
|
176
208
|
}),
|
|
177
209
|
);
|
|
178
|
-
}, [
|
|
210
|
+
}, [draggableProp, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
179
211
|
|
|
180
212
|
// Cancel expand on unmount.
|
|
181
213
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
182
214
|
|
|
183
|
-
const
|
|
215
|
+
const handleOpenToggle = useCallback(
|
|
184
216
|
() => onOpenChange?.({ item, path, open: !open }),
|
|
185
217
|
[onOpenChange, item, path, open],
|
|
186
218
|
);
|
|
187
219
|
|
|
188
220
|
const handleSelect = useCallback(
|
|
189
221
|
(option = false) => {
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
222
|
+
// If the item is a branch, toggle it if:
|
|
223
|
+
// - also holding down the option key
|
|
224
|
+
// - or the item is currently selected
|
|
225
|
+
if (isBranch && (option || current)) {
|
|
226
|
+
handleOpenToggle();
|
|
227
|
+
} else if (canSelectItem) {
|
|
228
|
+
canSelect?.({ item, path });
|
|
193
229
|
rowRef.current?.focus();
|
|
194
230
|
onSelect?.({ item, path, current: !current, option });
|
|
195
231
|
}
|
|
196
232
|
},
|
|
197
|
-
[item, path, current, isBranch,
|
|
233
|
+
[item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
|
|
198
234
|
);
|
|
199
235
|
|
|
200
236
|
const handleKeyDown = useCallback(
|
|
201
237
|
(event: KeyboardEvent) => {
|
|
202
238
|
switch (event.key) {
|
|
203
239
|
case 'ArrowRight':
|
|
204
|
-
isBranch && !open && handleOpenChange();
|
|
205
|
-
break;
|
|
206
240
|
case 'ArrowLeft':
|
|
207
|
-
isBranch &&
|
|
208
|
-
break;
|
|
209
|
-
case ' ':
|
|
210
|
-
handleSelect(event.altKey);
|
|
241
|
+
isBranch && handleOpenToggle();
|
|
211
242
|
break;
|
|
212
243
|
}
|
|
213
244
|
},
|
|
214
|
-
[isBranch, open,
|
|
245
|
+
[isBranch, open, handleOpenToggle, handleSelect],
|
|
246
|
+
);
|
|
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],
|
|
215
258
|
);
|
|
216
259
|
|
|
260
|
+
const childProps = {
|
|
261
|
+
draggable: draggableProp,
|
|
262
|
+
renderColumns: Columns,
|
|
263
|
+
blockInstruction,
|
|
264
|
+
canDrop,
|
|
265
|
+
canSelect,
|
|
266
|
+
onItemHover,
|
|
267
|
+
onOpenChange,
|
|
268
|
+
onSelect,
|
|
269
|
+
};
|
|
270
|
+
|
|
217
271
|
return (
|
|
218
272
|
<>
|
|
219
273
|
<Treegrid.Row
|
|
@@ -222,64 +276,68 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
222
276
|
id={id}
|
|
223
277
|
aria-labelledby={`${id}__label`}
|
|
224
278
|
parentOf={parentOf?.join(Treegrid.PARENT_OF_SEPARATOR)}
|
|
225
|
-
|
|
226
|
-
|
|
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',
|
|
227
287
|
hoverableControls,
|
|
228
288
|
hoverableFocusedKeyboardControls,
|
|
229
289
|
hoverableFocusedWithinControls,
|
|
230
290
|
hoverableDescriptionIcons,
|
|
291
|
+
ghostFocusWithin,
|
|
231
292
|
ghostHover,
|
|
232
293
|
className,
|
|
233
|
-
|
|
234
|
-
data-itemid={id}
|
|
235
|
-
data-testid={testId}
|
|
236
|
-
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
237
|
-
// without alerting the user (except for in the correct link element). See also:
|
|
238
|
-
// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current#description
|
|
239
|
-
aria-current={current ? ('' as 'page') : undefined}
|
|
294
|
+
)}
|
|
240
295
|
onKeyDown={handleKeyDown}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
setMenuOpen(true);
|
|
244
|
-
}}
|
|
296
|
+
onMouseEnter={handleItemHover}
|
|
297
|
+
onContextMenu={handleContextMenu}
|
|
245
298
|
>
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
299
|
+
<div
|
|
300
|
+
role='none'
|
|
301
|
+
className='indent relative grid grid-cols-subgrid col-[tree-row]'
|
|
249
302
|
style={paddingIndentation(level)}
|
|
250
303
|
>
|
|
251
|
-
<
|
|
252
|
-
<TreeItemToggle isBranch={isBranch} open={open}
|
|
304
|
+
<Treegrid.Cell classNames='flex items-center'>
|
|
305
|
+
<TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
|
|
253
306
|
<TreeItemHeading
|
|
254
|
-
ref={buttonRef}
|
|
255
|
-
label={label}
|
|
256
|
-
icon={icon}
|
|
257
|
-
className={headingClassName}
|
|
258
307
|
disabled={disabled}
|
|
259
308
|
current={current}
|
|
309
|
+
label={label}
|
|
310
|
+
className={headingClassName}
|
|
311
|
+
icon={icon}
|
|
312
|
+
iconHue={iconHue}
|
|
260
313
|
onSelect={handleSelect}
|
|
314
|
+
ref={buttonRef}
|
|
261
315
|
/>
|
|
262
|
-
</
|
|
316
|
+
</Treegrid.Cell>
|
|
263
317
|
{Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
|
|
264
318
|
{instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
|
|
265
|
-
</
|
|
319
|
+
</div>
|
|
266
320
|
</Treegrid.Row>
|
|
267
321
|
{open &&
|
|
268
|
-
|
|
269
|
-
<
|
|
270
|
-
key={item.id}
|
|
271
|
-
item={item}
|
|
272
|
-
path={path}
|
|
273
|
-
last={index === items.length - 1}
|
|
274
|
-
draggable={_draggable}
|
|
275
|
-
renderColumns={Columns}
|
|
276
|
-
canDrop={canDrop}
|
|
277
|
-
onOpenChange={onOpenChange}
|
|
278
|
-
onSelect={onSelect}
|
|
279
|
-
/>
|
|
322
|
+
childIds.map((childId, index) => (
|
|
323
|
+
<TreeItemById key={childId} id={childId} path={path} last={index === childIds.length - 1} {...childProps} />
|
|
280
324
|
))}
|
|
281
325
|
</>
|
|
282
326
|
);
|
|
283
327
|
};
|
|
284
328
|
|
|
285
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>;
|
|
@@ -4,24 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { type KeyboardEvent, type MouseEvent, forwardRef, memo, useCallback } from 'react';
|
|
6
6
|
|
|
7
|
-
import { Button, Icon, toLocalizedString, useTranslation
|
|
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.
|
|
12
|
-
export type
|
|
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,
|
|
23
|
-
({ label, icon,
|
|
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) => {
|
|
@@ -55,7 +58,7 @@ export const TreeItemHeading = memo(
|
|
|
55
58
|
variant='ghost'
|
|
56
59
|
density='fine'
|
|
57
60
|
classNames={[
|
|
58
|
-
'grow gap-2
|
|
61
|
+
'grow gap-2 ps-0.5 hover:bg-transparent dark:hover:bg-transparent',
|
|
59
62
|
'disabled:cursor-default disabled:opacity-100',
|
|
60
63
|
className,
|
|
61
64
|
]}
|
|
@@ -64,8 +67,10 @@ export const TreeItemHeading = memo(
|
|
|
64
67
|
onKeyDown={handleButtonKeydown}
|
|
65
68
|
{...(current && { 'aria-current': 'location' })}
|
|
66
69
|
>
|
|
67
|
-
{icon &&
|
|
68
|
-
|
|
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>
|
|
69
74
|
{toLocalizedString(label, t)}
|
|
70
75
|
</span>
|
|
71
76
|
</Button>
|
|
@@ -4,29 +4,40 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { forwardRef, memo } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
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>(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
'h-full w-6 px-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,22 +3,23 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
6
|
-
import
|
|
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))),
|