@dxos/react-ui-list 0.8.4-main.1da679c → 0.8.4-main.21d9917
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 +641 -702
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +641 -702
- 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 -1
- 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 +5 -3
- package/dist/types/src/components/List/List.stories.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 +7 -4
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +9 -29
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +7 -4
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +17 -4
- 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/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 +30 -28
- package/src/components/Accordion/Accordion.stories.tsx +4 -6
- package/src/components/Accordion/AccordionItem.tsx +2 -2
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/List/List.stories.tsx +34 -21
- package/src/components/List/ListItem.tsx +3 -3
- package/src/components/List/testing.ts +3 -3
- package/src/components/Tree/Tree.stories.tsx +76 -63
- package/src/components/Tree/Tree.tsx +22 -9
- package/src/components/Tree/TreeContext.tsx +7 -4
- package/src/components/Tree/TreeItem.tsx +51 -39
- package/src/components/Tree/TreeItemHeading.tsx +8 -5
- package/src/components/Tree/TreeItemToggle.tsx +29 -18
- package/src/components/Tree/testing.ts +5 -4
|
@@ -10,29 +10,29 @@ import {
|
|
|
10
10
|
attachInstruction,
|
|
11
11
|
extractInstruction,
|
|
12
12
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
13
|
-
import
|
|
13
|
+
import * as Schema from 'effect/Schema';
|
|
14
14
|
import React, { type FC, type KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
15
|
|
|
16
|
-
import { type HasId } from '@dxos/echo-schema';
|
|
17
16
|
import { invariant } from '@dxos/invariant';
|
|
18
17
|
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
19
18
|
import {
|
|
19
|
+
ghostFocusWithin,
|
|
20
20
|
ghostHover,
|
|
21
21
|
hoverableControls,
|
|
22
22
|
hoverableFocusedKeyboardControls,
|
|
23
23
|
hoverableFocusedWithinControls,
|
|
24
|
-
} from '@dxos/
|
|
24
|
+
} from '@dxos/ui-theme';
|
|
25
25
|
|
|
26
26
|
import { DEFAULT_INDENTATION, paddingIndentation } from './helpers';
|
|
27
27
|
import { useTree } from './TreeContext';
|
|
28
28
|
import { TreeItemHeading } from './TreeItemHeading';
|
|
29
29
|
import { TreeItemToggle } from './TreeItemToggle';
|
|
30
30
|
|
|
31
|
-
type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
32
|
-
|
|
33
31
|
const hoverableDescriptionIcons =
|
|
34
32
|
'[--icons-color:inherit] hover-hover:[--icons-color:var(--description-text)] hover-hover:hover:[--icons-color:inherit] focus-within:[--icons-color:inherit]';
|
|
35
33
|
|
|
34
|
+
type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
35
|
+
|
|
36
36
|
export const TreeDataSchema = Schema.Struct({
|
|
37
37
|
id: Schema.String,
|
|
38
38
|
path: Schema.Array(Schema.String),
|
|
@@ -42,7 +42,7 @@ export const TreeDataSchema = Schema.Struct({
|
|
|
42
42
|
export type TreeData = Schema.Schema.Type<typeof TreeDataSchema>;
|
|
43
43
|
export const isTreeData = (data: unknown): data is TreeData => Schema.is(TreeDataSchema)(data);
|
|
44
44
|
|
|
45
|
-
export type ColumnRenderer<T extends
|
|
45
|
+
export type ColumnRenderer<T extends { id: string } = any> = FC<{
|
|
46
46
|
item: T;
|
|
47
47
|
path: string[];
|
|
48
48
|
open: boolean;
|
|
@@ -50,28 +50,32 @@ export type ColumnRenderer<T extends HasId = any> = FC<{
|
|
|
50
50
|
setMenuOpen: (open: boolean) => void;
|
|
51
51
|
}>;
|
|
52
52
|
|
|
53
|
-
export type TreeItemProps<T extends
|
|
53
|
+
export type TreeItemProps<T extends { id: string } = any> = {
|
|
54
54
|
item: T;
|
|
55
55
|
path: string[];
|
|
56
56
|
levelOffset?: number;
|
|
57
57
|
last: boolean;
|
|
58
58
|
draggable?: boolean;
|
|
59
59
|
renderColumns?: ColumnRenderer<T>;
|
|
60
|
+
blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
|
|
60
61
|
canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
|
|
62
|
+
canSelect?: (params: { item: T; path: string[] }) => boolean;
|
|
61
63
|
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
62
64
|
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
63
65
|
};
|
|
64
66
|
|
|
65
|
-
const RawTreeItem = <T extends
|
|
67
|
+
const RawTreeItem = <T extends { id: string } = any>({
|
|
66
68
|
item,
|
|
67
69
|
path: _path,
|
|
70
|
+
levelOffset = 2,
|
|
68
71
|
last,
|
|
69
72
|
draggable: _draggable,
|
|
70
73
|
renderColumns: Columns,
|
|
74
|
+
blockInstruction,
|
|
71
75
|
canDrop,
|
|
76
|
+
canSelect,
|
|
72
77
|
onOpenChange,
|
|
73
78
|
onSelect,
|
|
74
|
-
levelOffset = 2,
|
|
75
79
|
}: TreeItemProps<T>) => {
|
|
76
80
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
77
81
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -81,15 +85,16 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
81
85
|
const [instruction, setInstruction] = useState<Instruction | null>(null);
|
|
82
86
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
83
87
|
|
|
84
|
-
const { useItems, getProps,
|
|
88
|
+
const { useItems, getProps, useIsOpen, useIsCurrent } = useTree();
|
|
85
89
|
const items = useItems(item);
|
|
86
|
-
const { id, label,
|
|
90
|
+
const { id, parentOf, label, className, headingClassName, icon, iconHue, disabled, testId } = getProps(item, _path);
|
|
87
91
|
const path = useMemo(() => [..._path, id], [_path, id]);
|
|
88
|
-
const open =
|
|
89
|
-
const current =
|
|
92
|
+
const open = useIsOpen(path, item);
|
|
93
|
+
const current = useIsCurrent(path, item);
|
|
90
94
|
const level = path.length - levelOffset;
|
|
91
95
|
const isBranch = !!parentOf;
|
|
92
96
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
97
|
+
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
93
98
|
|
|
94
99
|
const cancelExpand = useCallback(() => {
|
|
95
100
|
if (cancelExpandRef.current) {
|
|
@@ -145,7 +150,11 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
145
150
|
},
|
|
146
151
|
getIsSticky: () => true,
|
|
147
152
|
onDrag: ({ self, source }) => {
|
|
148
|
-
const
|
|
153
|
+
const desired = extractInstruction(self.data);
|
|
154
|
+
const block =
|
|
155
|
+
desired && blockInstruction?.({ instruction: desired, source: source.data as TreeData, target: data });
|
|
156
|
+
const instruction: Instruction | null =
|
|
157
|
+
block && desired.type !== 'instruction-blocked' ? { type: 'instruction-blocked', desired } : desired;
|
|
149
158
|
|
|
150
159
|
if (source.data.id !== id) {
|
|
151
160
|
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
@@ -176,43 +185,42 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
176
185
|
},
|
|
177
186
|
}),
|
|
178
187
|
);
|
|
179
|
-
}, [_draggable, item, id, mode, path, open, canDrop]);
|
|
188
|
+
}, [_draggable, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
180
189
|
|
|
181
190
|
// Cancel expand on unmount.
|
|
182
191
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
183
192
|
|
|
184
|
-
const
|
|
193
|
+
const handleOpenToggle = useCallback(
|
|
185
194
|
() => onOpenChange?.({ item, path, open: !open }),
|
|
186
195
|
[onOpenChange, item, path, open],
|
|
187
196
|
);
|
|
188
197
|
|
|
189
198
|
const handleSelect = useCallback(
|
|
190
199
|
(option = false) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
200
|
+
// If the item is a branch, toggle it if:
|
|
201
|
+
// - also holding down the option key
|
|
202
|
+
// - or the item is currently selected
|
|
203
|
+
if (isBranch && (option || current)) {
|
|
204
|
+
handleOpenToggle();
|
|
205
|
+
} else if (canSelectItem) {
|
|
206
|
+
canSelect?.({ item, path });
|
|
194
207
|
rowRef.current?.focus();
|
|
195
208
|
onSelect?.({ item, path, current: !current, option });
|
|
196
209
|
}
|
|
197
210
|
},
|
|
198
|
-
[item, path, current, isBranch,
|
|
211
|
+
[item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
|
|
199
212
|
);
|
|
200
213
|
|
|
201
214
|
const handleKeyDown = useCallback(
|
|
202
215
|
(event: KeyboardEvent) => {
|
|
203
216
|
switch (event.key) {
|
|
204
217
|
case 'ArrowRight':
|
|
205
|
-
isBranch && !open && handleOpenChange();
|
|
206
|
-
break;
|
|
207
218
|
case 'ArrowLeft':
|
|
208
|
-
isBranch &&
|
|
209
|
-
break;
|
|
210
|
-
case ' ':
|
|
211
|
-
handleSelect(event.altKey);
|
|
219
|
+
isBranch && handleOpenToggle();
|
|
212
220
|
break;
|
|
213
221
|
}
|
|
214
222
|
},
|
|
215
|
-
[isBranch, open,
|
|
223
|
+
[isBranch, open, handleOpenToggle, handleSelect],
|
|
216
224
|
);
|
|
217
225
|
|
|
218
226
|
return (
|
|
@@ -230,9 +238,10 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
230
238
|
hoverableFocusedWithinControls,
|
|
231
239
|
hoverableDescriptionIcons,
|
|
232
240
|
ghostHover,
|
|
241
|
+
ghostFocusWithin,
|
|
233
242
|
className,
|
|
234
243
|
]}
|
|
235
|
-
data-
|
|
244
|
+
data-object-id={id}
|
|
236
245
|
data-testid={testId}
|
|
237
246
|
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
238
247
|
// without alerting the user (except for in the correct link element). See also:
|
|
@@ -244,26 +253,27 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
244
253
|
setMenuOpen(true);
|
|
245
254
|
}}
|
|
246
255
|
>
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
256
|
+
<div
|
|
257
|
+
role='none'
|
|
258
|
+
className='indent relative grid grid-cols-subgrid col-[tree-row]'
|
|
250
259
|
style={paddingIndentation(level)}
|
|
251
260
|
>
|
|
252
|
-
<
|
|
253
|
-
<TreeItemToggle isBranch={isBranch} open={open}
|
|
261
|
+
<Treegrid.Cell classNames='flex items-center'>
|
|
262
|
+
<TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
|
|
254
263
|
<TreeItemHeading
|
|
255
|
-
ref={buttonRef}
|
|
256
|
-
label={label}
|
|
257
|
-
icon={icon}
|
|
258
|
-
className={headingClassName}
|
|
259
264
|
disabled={disabled}
|
|
260
265
|
current={current}
|
|
266
|
+
label={label}
|
|
267
|
+
className={headingClassName}
|
|
268
|
+
icon={icon}
|
|
269
|
+
iconHue={iconHue}
|
|
261
270
|
onSelect={handleSelect}
|
|
271
|
+
ref={buttonRef}
|
|
262
272
|
/>
|
|
263
|
-
</
|
|
273
|
+
</Treegrid.Cell>
|
|
264
274
|
{Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
|
|
265
275
|
{instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
|
|
266
|
-
</
|
|
276
|
+
</div>
|
|
267
277
|
</Treegrid.Row>
|
|
268
278
|
{open &&
|
|
269
279
|
items.map((item, index) => (
|
|
@@ -274,7 +284,9 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
274
284
|
last={index === items.length - 1}
|
|
275
285
|
draggable={_draggable}
|
|
276
286
|
renderColumns={Columns}
|
|
287
|
+
blockInstruction={blockInstruction}
|
|
277
288
|
canDrop={canDrop}
|
|
289
|
+
canSelect={canSelect}
|
|
278
290
|
onOpenChange={onOpenChange}
|
|
279
291
|
onSelect={onSelect}
|
|
280
292
|
/>
|
|
@@ -6,22 +6,25 @@ import React, { type KeyboardEvent, type MouseEvent, forwardRef, memo, useCallba
|
|
|
6
6
|
|
|
7
7
|
import { Button, Icon, type Label, toLocalizedString, useTranslation } from '@dxos/react-ui';
|
|
8
8
|
import { TextTooltip } from '@dxos/react-ui-text-tooltip';
|
|
9
|
+
import { getStyles } from '@dxos/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) => {
|
|
@@ -64,7 +67,7 @@ export const TreeItemHeading = memo(
|
|
|
64
67
|
onKeyDown={handleButtonKeydown}
|
|
65
68
|
{...(current && { 'aria-current': 'location' })}
|
|
66
69
|
>
|
|
67
|
-
{icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames='mlb-1' />}
|
|
70
|
+
{icon && <Icon icon={icon ?? 'ph--placeholder--regular'} size={5} classNames={['mlb-1', styles?.icon]} />}
|
|
68
71
|
<span className='flex-1 is-0 truncate text-start text-sm font-normal' data-tooltip>
|
|
69
72
|
{toLocalizedString(label, t)}
|
|
70
73
|
</span>
|
|
@@ -4,29 +4,40 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { forwardRef, memo } from 'react';
|
|
6
6
|
|
|
7
|
-
import {
|
|
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
|
+
'bs-full is-6 pli-0',
|
|
27
|
+
'[&_svg]:transition-[transform] [&_svg]:duration-200',
|
|
28
|
+
open && '[&_svg]:rotate-90',
|
|
29
|
+
hidden ? 'hidden' : !isBranch && 'invisible',
|
|
30
|
+
classNames,
|
|
31
|
+
]}
|
|
32
|
+
size={3}
|
|
33
|
+
icon='ph--caret-right--bold'
|
|
34
|
+
iconOnly
|
|
35
|
+
noTooltip
|
|
36
|
+
label={open ? 'Click to close' : 'Click to open'}
|
|
37
|
+
tabIndex={-1}
|
|
38
|
+
{...props}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
),
|
|
32
43
|
);
|
|
@@ -3,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))),
|