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