@dxos/react-ui-list 0.8.4-main.fd6878d → 0.8.4-main.fffef41
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 +61 -47
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +61 -47
- 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 +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.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.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 +6 -2
- 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 +24 -24
- package/src/components/Accordion/Accordion.stories.tsx +6 -8
- package/src/components/Accordion/AccordionItem.tsx +3 -1
- package/src/components/List/List.stories.tsx +18 -16
- package/src/components/List/ListItem.tsx +1 -6
- package/src/components/List/testing.ts +2 -2
- package/src/components/Tree/Tree.stories.tsx +46 -44
- package/src/components/Tree/Tree.tsx +7 -2
- package/src/components/Tree/TreeContext.tsx +3 -2
- package/src/components/Tree/TreeItem.tsx +36 -30
- package/src/components/Tree/TreeItemHeading.tsx +8 -5
- package/src/components/Tree/TreeItemToggle.tsx +29 -18
- package/src/components/Tree/testing.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-list",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.fffef41",
|
|
4
4
|
"description": "A list component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -27,36 +27,36 @@
|
|
|
27
27
|
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
|
|
28
28
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
|
|
29
29
|
"@preact-signals/safe-react": "^0.9.0",
|
|
30
|
-
"@preact/signals-core": "^1.
|
|
30
|
+
"@preact/signals-core": "^1.12.1",
|
|
31
31
|
"@radix-ui/react-accordion": "1.2.3",
|
|
32
32
|
"@radix-ui/react-context": "1.1.1",
|
|
33
|
-
"@dxos/debug": "0.8.4-main.
|
|
34
|
-
"@dxos/echo
|
|
35
|
-
"@dxos/invariant": "0.8.4-main.
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/react-ui-
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/react-ui-
|
|
42
|
-
"@dxos/
|
|
33
|
+
"@dxos/debug": "0.8.4-main.fffef41",
|
|
34
|
+
"@dxos/echo": "0.8.4-main.fffef41",
|
|
35
|
+
"@dxos/invariant": "0.8.4-main.fffef41",
|
|
36
|
+
"@dxos/log": "0.8.4-main.fffef41",
|
|
37
|
+
"@dxos/react-ui": "0.8.4-main.fffef41",
|
|
38
|
+
"@dxos/react-ui-types": "0.8.4-main.fffef41",
|
|
39
|
+
"@dxos/util": "0.8.4-main.fffef41",
|
|
40
|
+
"@dxos/live-object": "0.8.4-main.fffef41",
|
|
41
|
+
"@dxos/react-ui-text-tooltip": "0.8.4-main.fffef41",
|
|
42
|
+
"@dxos/react-ui-theme": "0.8.4-main.fffef41"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/react": "~
|
|
46
|
-
"@types/react-dom": "~
|
|
47
|
-
"effect": "3.
|
|
48
|
-
"react": "~
|
|
49
|
-
"react-dom": "~
|
|
50
|
-
"vite": "
|
|
51
|
-
"@dxos/random": "0.8.4-main.
|
|
52
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
45
|
+
"@types/react": "~19.2.2",
|
|
46
|
+
"@types/react-dom": "~19.2.2",
|
|
47
|
+
"effect": "3.18.3",
|
|
48
|
+
"react": "~19.2.0",
|
|
49
|
+
"react-dom": "~19.2.0",
|
|
50
|
+
"vite": "7.1.9",
|
|
51
|
+
"@dxos/random": "0.8.4-main.fffef41",
|
|
52
|
+
"@dxos/storybook-utils": "0.8.4-main.fffef41"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"effect": "^3.13.3",
|
|
56
|
-
"react": "
|
|
57
|
-
"react-dom": "
|
|
58
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
59
|
-
"@dxos/react-ui-theme": "0.8.4-main.
|
|
56
|
+
"react": "^19.0.0",
|
|
57
|
+
"react-dom": "^19.0.0",
|
|
58
|
+
"@dxos/react-ui": "0.8.4-main.fffef41",
|
|
59
|
+
"@dxos/react-ui-theme": "0.8.4-main.fffef41"
|
|
60
60
|
},
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
6
|
import React from 'react';
|
|
9
7
|
|
|
10
8
|
import { faker } from '@dxos/random';
|
|
11
|
-
import { withLayout, withTheme } from '@dxos/
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
10
|
|
|
13
11
|
import { Accordion } from './Accordion';
|
|
14
12
|
|
|
@@ -24,9 +22,9 @@ const items: TestItem[] = Array.from({ length: 10 }, (_, i) => ({
|
|
|
24
22
|
|
|
25
23
|
const DefaultStory = () => {
|
|
26
24
|
return (
|
|
27
|
-
<Accordion.Root<TestItem> items={items} classNames='
|
|
25
|
+
<Accordion.Root<TestItem> items={items} classNames='is-[40rem]'>
|
|
28
26
|
{({ items }) => (
|
|
29
|
-
<div className='flex flex-col
|
|
27
|
+
<div className='flex flex-col is-full border-y border-separator divide-y divide-separator'>
|
|
30
28
|
{items.map((item) => (
|
|
31
29
|
<Accordion.Item key={item.id} item={item} classNames='border-x border-separator'>
|
|
32
30
|
<Accordion.ItemHeader>{item.name}</Accordion.ItemHeader>
|
|
@@ -41,11 +39,11 @@ const DefaultStory = () => {
|
|
|
41
39
|
);
|
|
42
40
|
};
|
|
43
41
|
|
|
44
|
-
const meta
|
|
42
|
+
const meta = {
|
|
45
43
|
title: 'ui/react-ui-list/Accordion',
|
|
46
44
|
render: DefaultStory,
|
|
47
|
-
decorators: [withTheme, withLayout({
|
|
48
|
-
}
|
|
45
|
+
decorators: [withTheme, withLayout({ container: 'column' })],
|
|
46
|
+
} satisfies Meta<typeof Accordion>;
|
|
49
47
|
|
|
50
48
|
export default meta;
|
|
51
49
|
|
|
@@ -19,7 +19,9 @@ type AccordionItemContext<T extends ListItemRecord> = {
|
|
|
19
19
|
item: T;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// TODO(wittjosiah): This seems to be conflicting with something in the bundle.
|
|
23
|
+
// Perhaps @radix-ui/react-accordion?
|
|
24
|
+
export const [AccordionItemProvider, useDxAccordionItemContext] =
|
|
23
25
|
createContext<AccordionItemContext<any>>(ACCORDION_ITEM_NAME);
|
|
24
26
|
|
|
25
27
|
export type AccordionItemProps<T extends ListItemRecord> = ThemedClassName<PropsWithChildren<{ item: T }>>;
|
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import
|
|
6
|
+
import * as Schema from 'effect/Schema';
|
|
9
7
|
import React from 'react';
|
|
10
8
|
|
|
11
9
|
import { live } from '@dxos/live-object';
|
|
10
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
12
11
|
import { ghostHover, mx } from '@dxos/react-ui-theme';
|
|
13
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
14
12
|
import { arrayMove } from '@dxos/util';
|
|
15
13
|
|
|
16
14
|
import { List, type ListRootProps } from './List';
|
|
@@ -19,13 +17,6 @@ import { TestItemSchema, type TestItemType, createList } from './testing';
|
|
|
19
17
|
// TODO(burdon): var-icon-size.
|
|
20
18
|
const grid = 'grid grid-cols-[32px_1fr_32px] min-bs-[2rem] rounded';
|
|
21
19
|
|
|
22
|
-
const meta: Meta = {
|
|
23
|
-
title: 'ui/react-ui-list/List',
|
|
24
|
-
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export default meta;
|
|
28
|
-
|
|
29
20
|
const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
30
21
|
const handleSelect = (item: TestItemType) => {
|
|
31
22
|
console.log('select', item);
|
|
@@ -42,13 +33,13 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
42
33
|
<List.Root<TestItemType> dragPreview items={items} getId={(item) => item.id} onMove={handleMove} {...props}>
|
|
43
34
|
{({ items }) => (
|
|
44
35
|
<>
|
|
45
|
-
<div className='flex flex-col
|
|
36
|
+
<div className='flex flex-col is-full'>
|
|
46
37
|
<div role='none' className={grid}>
|
|
47
38
|
<div />
|
|
48
39
|
<div className='flex items-center text-sm'>Items</div>
|
|
49
40
|
</div>
|
|
50
41
|
|
|
51
|
-
<div role='list' className='
|
|
42
|
+
<div role='list' className='is-full bs-full overflow-auto'>
|
|
52
43
|
{items?.map((item) => (
|
|
53
44
|
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
|
|
54
45
|
<List.ItemDragHandle />
|
|
@@ -82,7 +73,7 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
|
82
73
|
return (
|
|
83
74
|
<List.Root<TestItemType> dragPreview items={items} {...props}>
|
|
84
75
|
{({ items }) => (
|
|
85
|
-
<div role='list' className='
|
|
76
|
+
<div role='list' className='is-full bs-full overflow-auto'>
|
|
86
77
|
{items?.map((item) => (
|
|
87
78
|
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
|
|
88
79
|
<List.ItemDragHandle />
|
|
@@ -98,7 +89,18 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
|
98
89
|
|
|
99
90
|
const list = live(createList(100));
|
|
100
91
|
|
|
101
|
-
|
|
92
|
+
const meta = {
|
|
93
|
+
title: 'ui/react-ui-list/List',
|
|
94
|
+
component: List.Root,
|
|
95
|
+
decorators: [withTheme],
|
|
96
|
+
parameters: {
|
|
97
|
+
layout: 'fullscreen',
|
|
98
|
+
},
|
|
99
|
+
} satisfies Meta<typeof List.Root>;
|
|
100
|
+
|
|
101
|
+
export default meta;
|
|
102
|
+
|
|
103
|
+
export const Default: StoryObj<typeof DefaultStory> = {
|
|
102
104
|
render: DefaultStory,
|
|
103
105
|
args: {
|
|
104
106
|
items: list.items,
|
|
@@ -106,7 +108,7 @@ export const Default: StoryObj<ListRootProps<TestItemType>> = {
|
|
|
106
108
|
},
|
|
107
109
|
};
|
|
108
110
|
|
|
109
|
-
export const Simple: StoryObj<
|
|
111
|
+
export const Simple: StoryObj<typeof SimpleStory> = {
|
|
110
112
|
render: SimpleStory,
|
|
111
113
|
args: {
|
|
112
114
|
items: list.items,
|
|
@@ -170,12 +170,7 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
170
170
|
|
|
171
171
|
return (
|
|
172
172
|
<ListItemProvider item={item} dragHandleRef={dragHandleRef}>
|
|
173
|
-
<div
|
|
174
|
-
ref={ref}
|
|
175
|
-
role='listitem'
|
|
176
|
-
className={mx('flex overflow-hidden relative', classNames, stateStyles[state.type])}
|
|
177
|
-
{...props}
|
|
178
|
-
>
|
|
173
|
+
<div ref={ref} role='listitem' className={mx('flex relative', classNames, stateStyles[state.type])} {...props}>
|
|
179
174
|
{children}
|
|
180
175
|
{state.type === 'is-dragging-over' && state.closestEdge && (
|
|
181
176
|
<NaturalListItem.DropIndicator edge={state.closestEdge} />
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
|
-
import { ObjectId } from '@dxos/echo
|
|
7
|
+
import { ObjectId } from '@dxos/echo/internal';
|
|
8
8
|
import { faker } from '@dxos/random';
|
|
9
9
|
|
|
10
10
|
export const TestItemSchema = Schema.Struct({
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@dxos-theme';
|
|
6
|
-
|
|
7
5
|
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
8
6
|
import { type Instruction, extractInstruction } from '@atlaskit/pragmatic-drag-and-drop-hitbox/tree-item';
|
|
9
7
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
@@ -12,61 +10,63 @@ import React, { useEffect } from 'react';
|
|
|
12
10
|
import { type Live, live } from '@dxos/live-object';
|
|
13
11
|
import { faker } from '@dxos/random';
|
|
14
12
|
import { Icon } from '@dxos/react-ui';
|
|
15
|
-
import {
|
|
13
|
+
import { withTheme } from '@dxos/react-ui/testing';
|
|
16
14
|
|
|
17
15
|
import { Path } from '../../util';
|
|
18
16
|
|
|
19
17
|
import { type TestItem, createTree, updateState } from './testing';
|
|
20
|
-
import { Tree } from './Tree';
|
|
18
|
+
import { Tree, type TreeProps } from './Tree';
|
|
21
19
|
import { type TreeData } from './TreeItem';
|
|
22
20
|
|
|
23
21
|
faker.seed(1234);
|
|
24
22
|
|
|
23
|
+
const DefaultStory = (props: TreeProps) => {
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
return monitorForElements({
|
|
26
|
+
canMonitor: ({ source }) => typeof source.data.id === 'string' && Array.isArray(source.data.path),
|
|
27
|
+
onDrop: ({ location, source }) => {
|
|
28
|
+
// Didn't drop on anything.
|
|
29
|
+
if (!location.current.dropTargets.length) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const target = location.current.dropTargets[0];
|
|
34
|
+
const instruction: Instruction | null = extractInstruction(target.data);
|
|
35
|
+
if (instruction !== null) {
|
|
36
|
+
updateState({
|
|
37
|
+
state: tree,
|
|
38
|
+
instruction,
|
|
39
|
+
source: source.data as TreeData,
|
|
40
|
+
target: target.data as TreeData,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}, []);
|
|
46
|
+
|
|
47
|
+
return <Tree {...props} />;
|
|
48
|
+
};
|
|
49
|
+
|
|
25
50
|
const tree = live<TestItem>(createTree());
|
|
26
51
|
const state = new Map<string, Live<{ open: boolean; current: boolean }>>();
|
|
27
52
|
|
|
28
|
-
const meta
|
|
53
|
+
const meta = {
|
|
29
54
|
title: 'ui/react-ui-list/Tree',
|
|
55
|
+
|
|
56
|
+
decorators: [withTheme],
|
|
30
57
|
component: Tree,
|
|
31
|
-
|
|
32
|
-
render: (args) => {
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
return monitorForElements({
|
|
35
|
-
canMonitor: ({ source }) => typeof source.data.id === 'string' && Array.isArray(source.data.path),
|
|
36
|
-
onDrop: ({ location, source }) => {
|
|
37
|
-
// Didn't drop on anything.
|
|
38
|
-
if (!location.current.dropTargets.length) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const target = location.current.dropTargets[0];
|
|
43
|
-
|
|
44
|
-
const instruction: Instruction | null = extractInstruction(target.data);
|
|
45
|
-
if (instruction !== null) {
|
|
46
|
-
updateState({
|
|
47
|
-
state: tree,
|
|
48
|
-
instruction,
|
|
49
|
-
source: source.data as TreeData,
|
|
50
|
-
target: target.data as TreeData,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
return <Tree {...args} />;
|
|
58
|
-
},
|
|
58
|
+
render: DefaultStory,
|
|
59
59
|
args: {
|
|
60
60
|
id: tree.id,
|
|
61
|
-
useItems: (
|
|
62
|
-
return
|
|
61
|
+
useItems: (parent?: TestItem) => {
|
|
62
|
+
return parent?.items ?? tree.items;
|
|
63
63
|
},
|
|
64
|
-
getProps: (
|
|
65
|
-
id:
|
|
66
|
-
label:
|
|
67
|
-
icon:
|
|
68
|
-
...((
|
|
69
|
-
parentOf:
|
|
64
|
+
getProps: (parent: TestItem) => ({
|
|
65
|
+
id: parent.id,
|
|
66
|
+
label: parent.name,
|
|
67
|
+
icon: parent.icon,
|
|
68
|
+
...((parent.items?.length ?? 0) > 0 && {
|
|
69
|
+
parentOf: parent.items!.map(({ id }) => id),
|
|
70
70
|
}),
|
|
71
71
|
}),
|
|
72
72
|
isOpen: (_path: string[]) => {
|
|
@@ -105,13 +105,15 @@ const meta: Meta<typeof Tree<TestItem>> = {
|
|
|
105
105
|
object!.current = current;
|
|
106
106
|
},
|
|
107
107
|
},
|
|
108
|
-
}
|
|
108
|
+
} satisfies Meta<typeof Tree<TestItem>>;
|
|
109
109
|
|
|
110
110
|
export default meta;
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
type Story = StoryObj<typeof meta>;
|
|
113
|
+
|
|
114
|
+
export const Default: Story = {};
|
|
113
115
|
|
|
114
|
-
export const Draggable:
|
|
116
|
+
export const Draggable: Story = {
|
|
115
117
|
args: {
|
|
116
118
|
draggable: true,
|
|
117
119
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import React, { useMemo } from 'react';
|
|
6
6
|
|
|
7
|
-
import { type HasId } from '@dxos/echo
|
|
7
|
+
import { type HasId } from '@dxos/echo/internal';
|
|
8
8
|
import { Treegrid, type TreegridRootProps } from '@dxos/react-ui';
|
|
9
9
|
|
|
10
10
|
import { type TreeContextType, TreeProvider } from './TreeContext';
|
|
@@ -16,7 +16,10 @@ export type TreeProps<T extends HasId = any, O = any> = {
|
|
|
16
16
|
id: string;
|
|
17
17
|
} & TreeContextType<T, O> &
|
|
18
18
|
Partial<Pick<TreegridRootProps, 'gridTemplateColumns' | 'classNames'>> &
|
|
19
|
-
Pick<
|
|
19
|
+
Pick<
|
|
20
|
+
TreeItemProps<T>,
|
|
21
|
+
'draggable' | 'renderColumns' | 'canDrop' | 'canSelect' | 'onOpenChange' | 'onSelect' | 'levelOffset'
|
|
22
|
+
>;
|
|
20
23
|
|
|
21
24
|
export const Tree = <T extends HasId = any, O = any>({
|
|
22
25
|
root,
|
|
@@ -32,6 +35,7 @@ export const Tree = <T extends HasId = any, O = any>({
|
|
|
32
35
|
levelOffset,
|
|
33
36
|
renderColumns,
|
|
34
37
|
canDrop,
|
|
38
|
+
canSelect,
|
|
35
39
|
onOpenChange,
|
|
36
40
|
onSelect,
|
|
37
41
|
}: TreeProps<T, O>) => {
|
|
@@ -60,6 +64,7 @@ export const Tree = <T extends HasId = any, O = any>({
|
|
|
60
64
|
draggable={draggable}
|
|
61
65
|
renderColumns={renderColumns}
|
|
62
66
|
canDrop={canDrop}
|
|
67
|
+
canSelect={canSelect}
|
|
63
68
|
onOpenChange={onOpenChange}
|
|
64
69
|
onSelect={onSelect}
|
|
65
70
|
/>
|
|
@@ -11,10 +11,11 @@ export type TreeItemDataProps = {
|
|
|
11
11
|
id: string;
|
|
12
12
|
label: Label;
|
|
13
13
|
parentOf?: string[];
|
|
14
|
-
icon?: string;
|
|
15
|
-
disabled?: boolean;
|
|
16
14
|
className?: string;
|
|
17
15
|
headingClassName?: string;
|
|
16
|
+
icon?: string;
|
|
17
|
+
iconHue?: string;
|
|
18
|
+
disabled?: boolean;
|
|
18
19
|
testId?: string;
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -10,13 +10,14 @@ 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
|
|
16
|
+
import { type HasId } from '@dxos/echo/internal';
|
|
17
17
|
import { invariant } from '@dxos/invariant';
|
|
18
18
|
import { TreeItem as NaturalTreeItem, Treegrid } from '@dxos/react-ui';
|
|
19
19
|
import {
|
|
20
|
+
ghostFocusWithin,
|
|
20
21
|
ghostHover,
|
|
21
22
|
hoverableControls,
|
|
22
23
|
hoverableFocusedKeyboardControls,
|
|
@@ -28,11 +29,11 @@ import { useTree } from './TreeContext';
|
|
|
28
29
|
import { TreeItemHeading } from './TreeItemHeading';
|
|
29
30
|
import { TreeItemToggle } from './TreeItemToggle';
|
|
30
31
|
|
|
31
|
-
type TreeItemState = 'idle' | 'dragging' | 'preview' | 'parent-of-instruction';
|
|
32
|
-
|
|
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),
|
|
@@ -58,6 +59,7 @@ export type TreeItemProps<T extends HasId = any> = {
|
|
|
58
59
|
draggable?: boolean;
|
|
59
60
|
renderColumns?: ColumnRenderer<T>;
|
|
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
|
};
|
|
@@ -65,13 +67,14 @@ export type TreeItemProps<T extends HasId = any> = {
|
|
|
65
67
|
const RawTreeItem = <T extends HasId = any>({
|
|
66
68
|
item,
|
|
67
69
|
path: _path,
|
|
70
|
+
levelOffset = 2,
|
|
68
71
|
last,
|
|
69
72
|
draggable: _draggable,
|
|
70
73
|
renderColumns: Columns,
|
|
71
74
|
canDrop,
|
|
75
|
+
canSelect,
|
|
72
76
|
onOpenChange,
|
|
73
77
|
onSelect,
|
|
74
|
-
levelOffset = 2,
|
|
75
78
|
}: TreeItemProps<T>) => {
|
|
76
79
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
77
80
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -83,13 +86,14 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
83
86
|
|
|
84
87
|
const { useItems, getProps, isOpen, isCurrent } = useTree();
|
|
85
88
|
const items = useItems(item);
|
|
86
|
-
const { id, label,
|
|
89
|
+
const { id, parentOf, label, className, headingClassName, icon, iconHue, disabled, testId } = getProps(item, _path);
|
|
87
90
|
const path = useMemo(() => [..._path, id], [_path, id]);
|
|
88
91
|
const open = isOpen(path, item);
|
|
89
92
|
const current = isCurrent(path, item);
|
|
90
93
|
const level = path.length - levelOffset;
|
|
91
94
|
const isBranch = !!parentOf;
|
|
92
95
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
96
|
+
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
93
97
|
|
|
94
98
|
const cancelExpand = useCallback(() => {
|
|
95
99
|
if (cancelExpandRef.current) {
|
|
@@ -181,38 +185,37 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
181
185
|
// Cancel expand on unmount.
|
|
182
186
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
183
187
|
|
|
184
|
-
const
|
|
188
|
+
const handleOpenToggle = useCallback(
|
|
185
189
|
() => onOpenChange?.({ item, path, open: !open }),
|
|
186
190
|
[onOpenChange, item, path, open],
|
|
187
191
|
);
|
|
188
192
|
|
|
189
193
|
const handleSelect = useCallback(
|
|
190
194
|
(option = false) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
// If the item is a branch, toggle it if:
|
|
196
|
+
// - also holding down the option key
|
|
197
|
+
// - or the item is currently selected
|
|
198
|
+
if (isBranch && (option || current)) {
|
|
199
|
+
handleOpenToggle();
|
|
200
|
+
} else if (canSelectItem) {
|
|
201
|
+
canSelect?.({ item, path });
|
|
194
202
|
rowRef.current?.focus();
|
|
195
203
|
onSelect?.({ item, path, current: !current, option });
|
|
196
204
|
}
|
|
197
205
|
},
|
|
198
|
-
[item, path, current, isBranch,
|
|
206
|
+
[item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
|
|
199
207
|
);
|
|
200
208
|
|
|
201
209
|
const handleKeyDown = useCallback(
|
|
202
210
|
(event: KeyboardEvent) => {
|
|
203
211
|
switch (event.key) {
|
|
204
212
|
case 'ArrowRight':
|
|
205
|
-
isBranch && !open && handleOpenChange();
|
|
206
|
-
break;
|
|
207
213
|
case 'ArrowLeft':
|
|
208
|
-
isBranch &&
|
|
209
|
-
break;
|
|
210
|
-
case ' ':
|
|
211
|
-
handleSelect(event.altKey);
|
|
214
|
+
isBranch && handleOpenToggle();
|
|
212
215
|
break;
|
|
213
216
|
}
|
|
214
217
|
},
|
|
215
|
-
[isBranch, open,
|
|
218
|
+
[isBranch, open, handleOpenToggle, handleSelect],
|
|
216
219
|
);
|
|
217
220
|
|
|
218
221
|
return (
|
|
@@ -230,9 +233,10 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
230
233
|
hoverableFocusedWithinControls,
|
|
231
234
|
hoverableDescriptionIcons,
|
|
232
235
|
ghostHover,
|
|
236
|
+
ghostFocusWithin,
|
|
233
237
|
className,
|
|
234
238
|
]}
|
|
235
|
-
data-
|
|
239
|
+
data-object-id={id}
|
|
236
240
|
data-testid={testId}
|
|
237
241
|
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
238
242
|
// without alerting the user (except for in the correct link element). See also:
|
|
@@ -244,26 +248,27 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
244
248
|
setMenuOpen(true);
|
|
245
249
|
}}
|
|
246
250
|
>
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
251
|
+
<div
|
|
252
|
+
role='none'
|
|
253
|
+
className='indent relative grid grid-cols-subgrid col-[tree-row]'
|
|
250
254
|
style={paddingIndentation(level)}
|
|
251
255
|
>
|
|
252
|
-
<
|
|
253
|
-
<TreeItemToggle isBranch={isBranch} open={open}
|
|
256
|
+
<Treegrid.Cell classNames='flex items-center'>
|
|
257
|
+
<TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
|
|
254
258
|
<TreeItemHeading
|
|
255
|
-
ref={buttonRef}
|
|
256
|
-
label={label}
|
|
257
|
-
icon={icon}
|
|
258
|
-
className={headingClassName}
|
|
259
259
|
disabled={disabled}
|
|
260
260
|
current={current}
|
|
261
|
+
label={label}
|
|
262
|
+
className={headingClassName}
|
|
263
|
+
icon={icon}
|
|
264
|
+
iconHue={iconHue}
|
|
261
265
|
onSelect={handleSelect}
|
|
266
|
+
ref={buttonRef}
|
|
262
267
|
/>
|
|
263
|
-
</
|
|
268
|
+
</Treegrid.Cell>
|
|
264
269
|
{Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
|
|
265
270
|
{instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
|
|
266
|
-
</
|
|
271
|
+
</div>
|
|
267
272
|
</Treegrid.Row>
|
|
268
273
|
{open &&
|
|
269
274
|
items.map((item, index) => (
|
|
@@ -275,6 +280,7 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
275
280
|
draggable={_draggable}
|
|
276
281
|
renderColumns={Columns}
|
|
277
282
|
canDrop={canDrop}
|
|
283
|
+
canSelect={canSelect}
|
|
278
284
|
onOpenChange={onOpenChange}
|
|
279
285
|
onSelect={onSelect}
|
|
280
286
|
/>
|
|
@@ -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/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>
|