@dxos/react-ui-list 0.6.14-main.2b6a0f3 → 0.6.14-main.f49f251
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 +392 -22
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/types/src/components/List/List.d.ts +1 -1
- package/dist/types/src/components/List/List.d.ts.map +1 -1
- package/dist/types/src/components/List/List.stories.d.ts +1 -1
- package/dist/types/src/components/List/List.stories.d.ts.map +1 -1
- package/dist/types/src/components/List/ListItem.d.ts +1 -3
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/List/ListRoot.d.ts +6 -4
- package/dist/types/src/components/List/ListRoot.d.ts.map +1 -1
- package/dist/types/src/components/List/testing.d.ts.map +1 -0
- package/dist/types/src/components/Tree/DropIndicator.d.ts +7 -0
- package/dist/types/src/components/Tree/DropIndicator.d.ts.map +1 -0
- package/dist/types/src/components/Tree/Tree.d.ts +24 -0
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -0
- package/dist/types/src/components/Tree/Tree.stories.d.ts +8 -0
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -0
- package/dist/types/src/components/Tree/TreeItem.d.ts +34 -0
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -0
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts +12 -0
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -0
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts +8 -0
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts.map +1 -0
- package/dist/types/src/components/Tree/helpers.d.ts +8 -0
- package/dist/types/src/components/Tree/helpers.d.ts.map +1 -0
- package/dist/types/src/components/Tree/index.d.ts +4 -0
- package/dist/types/src/components/Tree/index.d.ts.map +1 -0
- package/dist/types/src/components/Tree/testing.d.ts +26 -0
- package/dist/types/src/components/Tree/testing.d.ts.map +1 -0
- package/dist/types/src/components/Tree/types.d.ts +18 -0
- package/dist/types/src/components/Tree/types.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/package.json +19 -15
- package/src/components/List/List.stories.tsx +4 -4
- package/src/components/List/ListItem.tsx +7 -5
- package/src/components/List/ListRoot.tsx +26 -22
- package/src/components/Tree/DropIndicator.tsx +79 -0
- package/src/components/Tree/Tree.stories.tsx +116 -0
- package/src/components/Tree/Tree.tsx +56 -0
- package/src/components/Tree/TreeItem.tsx +237 -0
- package/src/components/Tree/TreeItemHeading.tsx +62 -0
- package/src/components/Tree/TreeItemToggle.tsx +35 -0
- package/src/components/Tree/helpers.ts +25 -0
- package/src/components/Tree/index.ts +7 -0
- package/src/components/Tree/testing.ts +170 -0
- package/src/components/Tree/types.ts +34 -0
- package/src/components/index.ts +1 -0
- package/dist/types/src/testing.d.ts.map +0 -1
- /package/dist/types/src/{testing.d.ts → components/List/testing.d.ts} +0 -0
- /package/src/{testing.ts → components/List/testing.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-list",
|
|
3
|
-
"version": "0.6.14-main.
|
|
3
|
+
"version": "0.6.14-main.f49f251",
|
|
4
4
|
"description": "A list component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"author": "DXOS.org",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"types": "./dist/types/src/index.d.ts",
|
|
12
|
+
"browser": "./dist/lib/browser/index.mjs"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"types": "dist/types/src/index.d.ts",
|
|
@@ -25,14 +25,18 @@
|
|
|
25
25
|
"@atlaskit/pragmatic-drag-and-drop-flourish": "^1.1.2",
|
|
26
26
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
27
27
|
"@atlaskit/pragmatic-drag-and-drop-react-drop-indicator": "^1.1.3",
|
|
28
|
+
"@preact/signals-core": "^1.6.0",
|
|
28
29
|
"@radix-ui/react-context": "^1.0.0",
|
|
29
30
|
"effect": "^3.9.1",
|
|
30
|
-
"@dxos/debug": "0.6.14-main.
|
|
31
|
-
"@dxos/echo-schema": "0.6.14-main.
|
|
32
|
-
"@dxos/
|
|
33
|
-
"@dxos/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/react-ui-
|
|
31
|
+
"@dxos/debug": "0.6.14-main.f49f251",
|
|
32
|
+
"@dxos/echo-schema": "0.6.14-main.f49f251",
|
|
33
|
+
"@dxos/invariant": "0.6.14-main.f49f251",
|
|
34
|
+
"@dxos/react-ui-mosaic": "0.6.14-main.f49f251",
|
|
35
|
+
"@dxos/log": "0.6.14-main.f49f251",
|
|
36
|
+
"@dxos/react-ui-attention": "0.6.14-main.f49f251",
|
|
37
|
+
"@dxos/react-ui-text-tooltip": "0.6.14-main.f49f251",
|
|
38
|
+
"@dxos/util": "0.6.14-main.f49f251",
|
|
39
|
+
"@dxos/react-ui-types": "0.6.14-main.f49f251"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
42
|
"@phosphor-icons/react": "^2.1.5",
|
|
@@ -41,18 +45,18 @@
|
|
|
41
45
|
"react": "~18.2.0",
|
|
42
46
|
"react-dom": "~18.2.0",
|
|
43
47
|
"vite": "5.4.7",
|
|
44
|
-
"@dxos/random": "0.6.14-main.
|
|
45
|
-
"@dxos/react-ui
|
|
46
|
-
"@dxos/
|
|
47
|
-
"@dxos/
|
|
48
|
+
"@dxos/random": "0.6.14-main.f49f251",
|
|
49
|
+
"@dxos/react-ui": "0.6.14-main.f49f251",
|
|
50
|
+
"@dxos/react-ui-theme": "0.6.14-main.f49f251",
|
|
51
|
+
"@dxos/storybook-utils": "0.6.14-main.f49f251"
|
|
48
52
|
},
|
|
49
53
|
"peerDependencies": {
|
|
50
54
|
"@phosphor-icons/react": "^2.1.5",
|
|
51
55
|
"effect": "^3.9.1",
|
|
52
56
|
"react": "~18.2.0",
|
|
53
57
|
"react-dom": "~18.2.0",
|
|
54
|
-
"@dxos/react-ui": "0.6.14-main.
|
|
55
|
-
"@dxos/react-ui
|
|
58
|
+
"@dxos/react-ui-theme": "0.6.14-main.f49f251",
|
|
59
|
+
"@dxos/react-ui": "0.6.14-main.f49f251"
|
|
56
60
|
},
|
|
57
61
|
"publishConfig": {
|
|
58
62
|
"access": "public"
|
|
@@ -12,7 +12,7 @@ import { ghostHover, mx } from '@dxos/react-ui-theme';
|
|
|
12
12
|
import { withTheme, withLayout } from '@dxos/storybook-utils';
|
|
13
13
|
|
|
14
14
|
import { List, type ListRootProps } from './List';
|
|
15
|
-
import { createList, TestItemSchema, type TestItemType } from '
|
|
15
|
+
import { createList, TestItemSchema, type TestItemType } from './testing';
|
|
16
16
|
|
|
17
17
|
// TODO(burdon): var-icon-size.
|
|
18
18
|
const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2rem] rounded';
|
|
@@ -37,7 +37,7 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
39
|
<div role='list' className='w-full h-full overflow-auto'>
|
|
40
|
-
{items
|
|
40
|
+
{items?.map((item) => (
|
|
41
41
|
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
|
|
42
42
|
<List.ItemDragHandle />
|
|
43
43
|
<List.ItemTitle onClick={() => handleSelect(item)}>{item.name}</List.ItemTitle>
|
|
@@ -48,7 +48,7 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
48
48
|
|
|
49
49
|
<div role='none' className={grid}>
|
|
50
50
|
<div />
|
|
51
|
-
<div className='flex items-center text-sm'>{items
|
|
51
|
+
<div className='flex items-center text-sm'>{items?.length} Items</div>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
@@ -71,7 +71,7 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
|
71
71
|
<List.Root<TestItemType> dragPreview items={items} {...props}>
|
|
72
72
|
{({ items }) => (
|
|
73
73
|
<div role='list' className='w-full h-full overflow-auto'>
|
|
74
|
-
{items
|
|
74
|
+
{items?.map((item) => (
|
|
75
75
|
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
|
|
76
76
|
<List.ItemDragHandle />
|
|
77
77
|
<List.ItemTitle>{item.name}</List.ItemTitle>
|
|
@@ -5,8 +5,11 @@
|
|
|
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 { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import {
|
|
9
|
+
type Edge,
|
|
10
|
+
attachClosestEdge,
|
|
11
|
+
extractClosestEdge,
|
|
12
|
+
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
10
13
|
import { createContext } from '@radix-ui/react-context';
|
|
11
14
|
import React, {
|
|
12
15
|
type ComponentProps,
|
|
@@ -22,14 +25,13 @@ import React, {
|
|
|
22
25
|
import { createPortal } from 'react-dom';
|
|
23
26
|
|
|
24
27
|
import { invariant } from '@dxos/invariant';
|
|
25
|
-
import { type ThemedClassName } from '@dxos/react-ui';
|
|
26
|
-
import { Icon } from '@dxos/react-ui';
|
|
28
|
+
import { Icon, type ThemedClassName } from '@dxos/react-ui';
|
|
27
29
|
import { mx } from '@dxos/react-ui-theme';
|
|
28
30
|
|
|
29
31
|
import { DropIndicator } from './DropIndicator';
|
|
30
32
|
import { useListContext } from './ListRoot';
|
|
31
33
|
|
|
32
|
-
export type ListItemRecord = {
|
|
34
|
+
export type ListItemRecord = {};
|
|
33
35
|
|
|
34
36
|
export type ItemState =
|
|
35
37
|
| {
|
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
6
6
|
import { extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
7
|
-
import {
|
|
7
|
+
import { getReorderDestinationIndex } from '@atlaskit/pragmatic-drag-and-drop-hitbox/util/get-reorder-destination-index';
|
|
8
8
|
import { createContext } from '@radix-ui/react-context';
|
|
9
9
|
import React, { type ReactNode, useEffect, useState } from 'react';
|
|
10
10
|
|
|
11
|
-
import { type ThemedClassName
|
|
11
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { idle, type ItemState, type ListItemRecord } from './ListItem';
|
|
14
14
|
|
|
15
15
|
type ListContext<T extends ListItemRecord> = {
|
|
16
16
|
isItem: (item: any) => boolean;
|
|
17
|
+
isEqual?: (item1: T, item2: T) => boolean;
|
|
18
|
+
getId?: (item: T) => string; // TODO(burdon): Require if T doesn't conform to type.
|
|
17
19
|
dragPreview?: boolean;
|
|
18
20
|
state: ItemState & { item?: T };
|
|
19
21
|
setState: (state: ItemState & { item?: T }) => void;
|
|
@@ -31,21 +33,26 @@ export type ListRendererProps<T extends ListItemRecord> = {
|
|
|
31
33
|
export type ListRootProps<T extends ListItemRecord> = ThemedClassName<{
|
|
32
34
|
children?: (props: ListRendererProps<T>) => ReactNode;
|
|
33
35
|
items?: T[];
|
|
34
|
-
onMove?: (
|
|
36
|
+
onMove?: (fromIndex: number, toIndex: number) => void;
|
|
35
37
|
}> &
|
|
36
|
-
Pick<ListContext<T>, 'isItem' | 'dragPreview'>;
|
|
38
|
+
Pick<ListContext<T>, 'isItem' | 'isEqual' | 'getId' | 'dragPreview'>;
|
|
37
39
|
|
|
38
40
|
export const ListRoot = <T extends ListItemRecord>({
|
|
39
41
|
classNames,
|
|
40
42
|
children,
|
|
41
|
-
items
|
|
43
|
+
items,
|
|
42
44
|
isItem,
|
|
45
|
+
isEqual = (a, b) => (getId ? getId(a) === getId(b) : a === b),
|
|
46
|
+
getId,
|
|
43
47
|
onMove,
|
|
44
48
|
...props
|
|
45
49
|
}: ListRootProps<T>) => {
|
|
46
|
-
const [items, setItems] = useControlledValue<T[]>(_items);
|
|
47
50
|
const [state, setState] = useState<ListContext<T>['state']>(idle);
|
|
48
51
|
useEffect(() => {
|
|
52
|
+
if (!items) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
49
56
|
return monitorForElements({
|
|
50
57
|
canMonitor: ({ source }) => isItem(source.data),
|
|
51
58
|
onDrop: ({ location, source }) => {
|
|
@@ -60,27 +67,24 @@ export const ListRoot = <T extends ListItemRecord>({
|
|
|
60
67
|
return;
|
|
61
68
|
}
|
|
62
69
|
|
|
63
|
-
const sourceIdx = items.findIndex((item) => item
|
|
64
|
-
const targetIdx = items.findIndex((item) => item
|
|
70
|
+
const sourceIdx = items.findIndex((item) => isEqual(item, sourceData as T));
|
|
71
|
+
const targetIdx = items.findIndex((item) => isEqual(item, targetData as T));
|
|
65
72
|
if (targetIdx < 0 || sourceIdx < 0) {
|
|
66
73
|
return;
|
|
67
74
|
}
|
|
68
|
-
|
|
69
75
|
const closestEdgeOfTarget = extractClosestEdge(targetData);
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
onMove?.(sourceData as T, targetIdx);
|
|
76
|
+
const destinationIndex = getReorderDestinationIndex({
|
|
77
|
+
closestEdgeOfTarget,
|
|
78
|
+
startIndex: sourceIdx,
|
|
79
|
+
indexOfTarget: targetIdx,
|
|
80
|
+
axis: 'vertical',
|
|
81
|
+
});
|
|
82
|
+
onMove?.(sourceIdx, destinationIndex);
|
|
81
83
|
},
|
|
82
84
|
});
|
|
83
85
|
}, [items]);
|
|
84
86
|
|
|
85
|
-
return
|
|
87
|
+
return (
|
|
88
|
+
<ListProvider {...{ isItem, state, setState, ...props }}>{children?.({ state, items: items ?? [] })}</ListProvider>
|
|
89
|
+
);
|
|
86
90
|
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
6
|
+
import React, { type HTMLAttributes, type CSSProperties } from 'react';
|
|
7
|
+
|
|
8
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
9
|
+
|
|
10
|
+
// Tree item hitbox
|
|
11
|
+
// https://github.com/atlassian/pragmatic-drag-and-drop/blob/main/packages/hitbox/constellation/index/about.mdx#tree-item
|
|
12
|
+
|
|
13
|
+
export type DropIndicatorProps = {
|
|
14
|
+
instruction: Instruction;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type InstructionType = Exclude<Instruction, { type: 'instruction-blocked' }>['type'];
|
|
18
|
+
type Orientation = 'sibling' | 'child';
|
|
19
|
+
|
|
20
|
+
const edgeToOrientationMap: Record<InstructionType, Orientation> = {
|
|
21
|
+
'reorder-above': 'sibling',
|
|
22
|
+
'reorder-below': 'sibling',
|
|
23
|
+
'make-child': 'child',
|
|
24
|
+
reparent: 'child',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const orientationStyles: Record<Orientation, HTMLAttributes<HTMLElement>['className']> = {
|
|
28
|
+
// TODO(wittjosiah): Stop using left/right here.
|
|
29
|
+
sibling:
|
|
30
|
+
'bs-[--line-thickness] left-[--horizontal-indent] right-0 bg-accentSurface before:left-[--negative-terminal-size]',
|
|
31
|
+
child: 'is-full block-start-0 block-end-0 border-[length:--line-thickness] before:invisible',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const instructionStyles: Record<InstructionType, HTMLAttributes<HTMLElement>['className']> = {
|
|
35
|
+
'reorder-above': 'block-start-[--line-offset] before:block-start-[--offset-terminal]',
|
|
36
|
+
'reorder-below': 'block-end-[--line-offset] before:block-end-[--offset-terminal]',
|
|
37
|
+
'make-child': 'border-accentSurface',
|
|
38
|
+
// TODO(wittjosiah): This is not occurring in the current implementation.
|
|
39
|
+
reparent: '',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const strokeSize = 2;
|
|
43
|
+
const terminalSize = 8;
|
|
44
|
+
const offsetToAlignTerminalWithLine = (strokeSize - terminalSize) / 2;
|
|
45
|
+
const gap = '0px';
|
|
46
|
+
|
|
47
|
+
export const DropIndicator = ({ instruction }: DropIndicatorProps) => {
|
|
48
|
+
const lineOffset = `calc(-0.5 * (${gap} + ${strokeSize}px))`;
|
|
49
|
+
const isBlocked = instruction.type === 'instruction-blocked';
|
|
50
|
+
const desiredInstruction = isBlocked ? instruction.desired : instruction;
|
|
51
|
+
const orientation = edgeToOrientationMap[desiredInstruction.type];
|
|
52
|
+
|
|
53
|
+
if (isBlocked) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
style={
|
|
60
|
+
{
|
|
61
|
+
'--line-thickness': `${strokeSize}px`,
|
|
62
|
+
'--line-offset': `${lineOffset}`,
|
|
63
|
+
'--terminal-size': `${terminalSize}px`,
|
|
64
|
+
'--terminal-radius': `${terminalSize / 2}px`,
|
|
65
|
+
'--negative-terminal-size': `-${terminalSize}px`,
|
|
66
|
+
'--offset-terminal': `${offsetToAlignTerminalWithLine}px`,
|
|
67
|
+
'--horizontal-indent': `${desiredInstruction.currentLevel * desiredInstruction.indentPerLevel + 4}px`,
|
|
68
|
+
} as CSSProperties
|
|
69
|
+
}
|
|
70
|
+
className={mx(
|
|
71
|
+
'absolute z-10 pointer-events-none',
|
|
72
|
+
'before:is-[--terminal-size] before:bs-[--terminal-size] box-border before:absolute',
|
|
73
|
+
'before:border-[length:--line-thickness] before:border-solid before:border-accentSurface before:rounded-full',
|
|
74
|
+
orientationStyles[orientation],
|
|
75
|
+
instructionStyles[desiredInstruction.type],
|
|
76
|
+
)}
|
|
77
|
+
></div>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos-theme';
|
|
6
|
+
|
|
7
|
+
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
8
|
+
import { extractInstruction, type Instruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
9
|
+
import { type StoryObj, type Meta } from '@storybook/react';
|
|
10
|
+
import React, { useEffect } from 'react';
|
|
11
|
+
|
|
12
|
+
import { create } from '@dxos/echo-schema';
|
|
13
|
+
import { faker } from '@dxos/random';
|
|
14
|
+
import { Icon } from '@dxos/react-ui';
|
|
15
|
+
import { Path } from '@dxos/react-ui-mosaic';
|
|
16
|
+
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
17
|
+
|
|
18
|
+
import { Tree, type TreeProps } from './Tree';
|
|
19
|
+
import { createTree, flattenTree, getItem, updateState, type TestItem } from './testing';
|
|
20
|
+
import { isItem, type ItemType } from './types';
|
|
21
|
+
|
|
22
|
+
faker.seed(1234);
|
|
23
|
+
|
|
24
|
+
type State = {
|
|
25
|
+
tree: TestItem;
|
|
26
|
+
open: string[];
|
|
27
|
+
current: string[];
|
|
28
|
+
flatTree: ItemType[];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const state = create<State>({
|
|
32
|
+
tree: createTree(),
|
|
33
|
+
open: [],
|
|
34
|
+
current: [],
|
|
35
|
+
get flatTree() {
|
|
36
|
+
return flattenTree(this.tree, this.open, getItem);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const Story = (args: Partial<TreeProps>) => {
|
|
41
|
+
// NOTE: If passed directly to args, this won't be reactive.
|
|
42
|
+
const items = state.flatTree;
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
return monitorForElements({
|
|
46
|
+
canMonitor: ({ source }) => isItem(source.data),
|
|
47
|
+
onDrop: ({ location, source }) => {
|
|
48
|
+
// Didn't drop on anything.
|
|
49
|
+
if (!location.current.dropTargets.length) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const target = location.current.dropTargets[0];
|
|
54
|
+
|
|
55
|
+
const instruction: Instruction | null = extractInstruction(target.data);
|
|
56
|
+
if (instruction !== null) {
|
|
57
|
+
updateState({
|
|
58
|
+
state: state.tree,
|
|
59
|
+
instruction,
|
|
60
|
+
source: source.data as ItemType,
|
|
61
|
+
target: target.data as ItemType,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
return <Tree items={items} open={state.open} current={state.current} {...args} />;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const meta: Meta<typeof Tree> = {
|
|
72
|
+
title: 'ui/react-ui-list/Tree',
|
|
73
|
+
component: Tree,
|
|
74
|
+
render: Story,
|
|
75
|
+
decorators: [withTheme, withLayout({ tooltips: true })],
|
|
76
|
+
args: {
|
|
77
|
+
renderColumns: () => {
|
|
78
|
+
return (
|
|
79
|
+
<div className='flex items-center'>
|
|
80
|
+
<Icon icon='ph--placeholder--regular' size={5} />
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
onOpenChange: (item: ItemType, open: boolean) => {
|
|
85
|
+
const path = Path.create(...item.path);
|
|
86
|
+
if (open) {
|
|
87
|
+
state.open.push(path);
|
|
88
|
+
} else {
|
|
89
|
+
const index = state.open.indexOf(path);
|
|
90
|
+
if (index > -1) {
|
|
91
|
+
state.open.splice(index, 1);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
onSelect: (item: ItemType, current: boolean) => {
|
|
96
|
+
if (current) {
|
|
97
|
+
state.current.push(item.id);
|
|
98
|
+
} else {
|
|
99
|
+
const index = state.current.indexOf(item.id);
|
|
100
|
+
if (index > -1) {
|
|
101
|
+
state.current.splice(index, 1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default meta;
|
|
109
|
+
|
|
110
|
+
export const Default = {};
|
|
111
|
+
|
|
112
|
+
export const Draggable: StoryObj<typeof Tree> = {
|
|
113
|
+
args: {
|
|
114
|
+
draggable: true,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
|
|
8
|
+
import { Path } from '@dxos/react-ui-mosaic';
|
|
9
|
+
|
|
10
|
+
import { TreeItem, type TreeItemProps } from './TreeItem';
|
|
11
|
+
import { getMode } from './helpers';
|
|
12
|
+
import { type ItemType } from './types';
|
|
13
|
+
|
|
14
|
+
export type TreeProps<T extends ItemType = ItemType> = {
|
|
15
|
+
items: T[];
|
|
16
|
+
open: string[];
|
|
17
|
+
current: string[];
|
|
18
|
+
} & Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
19
|
+
Pick<TreeItemProps<T>, 'draggable' | 'renderColumns' | 'canDrop' | 'onOpenChange' | 'onSelect'>;
|
|
20
|
+
|
|
21
|
+
export const Tree = <T extends ItemType = ItemType>({
|
|
22
|
+
items,
|
|
23
|
+
open,
|
|
24
|
+
current,
|
|
25
|
+
draggable = false,
|
|
26
|
+
gridTemplateColumns = '[tree-row-start] 1fr min-content [tree-row-end]',
|
|
27
|
+
classNames,
|
|
28
|
+
renderColumns,
|
|
29
|
+
canDrop,
|
|
30
|
+
onOpenChange,
|
|
31
|
+
onSelect,
|
|
32
|
+
}: TreeProps<T>) => {
|
|
33
|
+
return (
|
|
34
|
+
<Treegrid.Root gridTemplateColumns={gridTemplateColumns} classNames={classNames}>
|
|
35
|
+
{items.map((item, i) => {
|
|
36
|
+
const path = Path.create(...item.path);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TreeItem<T>
|
|
40
|
+
key={item.id}
|
|
41
|
+
item={item}
|
|
42
|
+
mode={getMode(items, i)}
|
|
43
|
+
open={open.includes(path)}
|
|
44
|
+
// TODO(wittjosiah): This should also be path-based.
|
|
45
|
+
current={current.includes(item.id)}
|
|
46
|
+
draggable={draggable}
|
|
47
|
+
renderColumns={renderColumns}
|
|
48
|
+
canDrop={canDrop}
|
|
49
|
+
onOpenChange={onOpenChange}
|
|
50
|
+
onSelect={onSelect}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
})}
|
|
54
|
+
</Treegrid.Root>
|
|
55
|
+
);
|
|
56
|
+
};
|