@dxos/react-ui-list 0.8.4-main.5ea62a8 → 0.8.4-main.66e292d
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 +72 -46
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +72 -46
- 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.stories.d.ts +3 -1
- 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 +3 -3
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +2 -3
- 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 +12 -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 +4 -6
- package/src/components/List/List.stories.tsx +9 -8
- package/src/components/List/testing.ts +3 -3
- package/src/components/Tree/Tree.stories.tsx +3 -4
- package/src/components/Tree/Tree.tsx +16 -2
- package/src/components/Tree/TreeContext.tsx +3 -2
- package/src/components/Tree/TreeItem.tsx +45 -32
- package/src/components/Tree/TreeItemHeading.tsx +8 -5
- package/src/components/Tree/TreeItemToggle.tsx +29 -18
- package/src/components/Tree/testing.ts +4 -3
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.66e292d",
|
|
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/
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/log": "0.8.4-main.
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/react-ui-
|
|
40
|
-
"@dxos/react-ui-types": "0.8.4-main.
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
33
|
+
"@dxos/invariant": "0.8.4-main.66e292d",
|
|
34
|
+
"@dxos/live-object": "0.8.4-main.66e292d",
|
|
35
|
+
"@dxos/react-ui": "0.8.4-main.66e292d",
|
|
36
|
+
"@dxos/react-ui-text-tooltip": "0.8.4-main.66e292d",
|
|
37
|
+
"@dxos/log": "0.8.4-main.66e292d",
|
|
38
|
+
"@dxos/debug": "0.8.4-main.66e292d",
|
|
39
|
+
"@dxos/react-ui-theme": "0.8.4-main.66e292d",
|
|
40
|
+
"@dxos/react-ui-types": "0.8.4-main.66e292d",
|
|
41
|
+
"@dxos/util": "0.8.4-main.66e292d",
|
|
42
|
+
"@dxos/echo": "0.8.4-main.66e292d"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/react": "~
|
|
46
|
-
"@types/react-dom": "~
|
|
47
|
-
"effect": "3.
|
|
48
|
-
"react": "~
|
|
49
|
-
"react-dom": "~
|
|
50
|
-
"vite": "7.1.
|
|
51
|
-
"@dxos/storybook-utils": "0.8.4-main.
|
|
52
|
-
"@dxos/random": "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/storybook-utils": "0.8.4-main.66e292d",
|
|
52
|
+
"@dxos/random": "0.8.4-main.66e292d"
|
|
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
|
|
56
|
+
"react": "^19.0.0",
|
|
57
|
+
"react-dom": "^19.0.0",
|
|
58
|
+
"@dxos/react-ui-theme": "0.8.4-main.66e292d",
|
|
59
|
+
"@dxos/react-ui": "0.8.4-main.66e292d"
|
|
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>
|
|
@@ -44,7 +42,7 @@ const DefaultStory = () => {
|
|
|
44
42
|
const meta = {
|
|
45
43
|
title: 'ui/react-ui-list/Accordion',
|
|
46
44
|
render: DefaultStory,
|
|
47
|
-
decorators: [withTheme, withLayout({
|
|
45
|
+
decorators: [withTheme, withLayout({ container: 'column' })],
|
|
48
46
|
} satisfies Meta<typeof Accordion>;
|
|
49
47
|
|
|
50
48
|
export default meta;
|
|
@@ -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';
|
|
@@ -35,13 +33,13 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
35
33
|
<List.Root<TestItemType> dragPreview items={items} getId={(item) => item.id} onMove={handleMove} {...props}>
|
|
36
34
|
{({ items }) => (
|
|
37
35
|
<>
|
|
38
|
-
<div className='flex flex-col
|
|
36
|
+
<div className='flex flex-col is-full'>
|
|
39
37
|
<div role='none' className={grid}>
|
|
40
38
|
<div />
|
|
41
39
|
<div className='flex items-center text-sm'>Items</div>
|
|
42
40
|
</div>
|
|
43
41
|
|
|
44
|
-
<div role='list' className='
|
|
42
|
+
<div role='list' className='is-full bs-full overflow-auto'>
|
|
45
43
|
{items?.map((item) => (
|
|
46
44
|
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
|
|
47
45
|
<List.ItemDragHandle />
|
|
@@ -75,7 +73,7 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
|
75
73
|
return (
|
|
76
74
|
<List.Root<TestItemType> dragPreview items={items} {...props}>
|
|
77
75
|
{({ items }) => (
|
|
78
|
-
<div role='list' className='
|
|
76
|
+
<div role='list' className='is-full bs-full overflow-auto'>
|
|
79
77
|
{items?.map((item) => (
|
|
80
78
|
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid, ghostHover)}>
|
|
81
79
|
<List.ItemDragHandle />
|
|
@@ -94,7 +92,10 @@ const list = live(createList(100));
|
|
|
94
92
|
const meta = {
|
|
95
93
|
title: 'ui/react-ui-list/List',
|
|
96
94
|
component: List.Root,
|
|
97
|
-
decorators: [withTheme
|
|
95
|
+
decorators: [withTheme],
|
|
96
|
+
parameters: {
|
|
97
|
+
layout: 'fullscreen',
|
|
98
|
+
},
|
|
98
99
|
} satisfies Meta<typeof List.Root>;
|
|
99
100
|
|
|
100
101
|
export default meta;
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { Obj } from '@dxos/echo';
|
|
8
8
|
import { faker } from '@dxos/random';
|
|
9
9
|
|
|
10
10
|
export const TestItemSchema = Schema.Struct({
|
|
11
|
-
id:
|
|
11
|
+
id: Obj.ID,
|
|
12
12
|
name: Schema.String,
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -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,7 +10,7 @@ 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
|
|
|
@@ -54,9 +52,10 @@ const state = new Map<string, Live<{ open: boolean; current: boolean }>>();
|
|
|
54
52
|
|
|
55
53
|
const meta = {
|
|
56
54
|
title: 'ui/react-ui-list/Tree',
|
|
55
|
+
|
|
56
|
+
decorators: [withTheme],
|
|
57
57
|
component: Tree,
|
|
58
58
|
render: DefaultStory,
|
|
59
|
-
decorators: [withTheme, withLayout()],
|
|
60
59
|
args: {
|
|
61
60
|
id: tree.id,
|
|
62
61
|
useItems: (parent?: TestItem) => {
|
|
@@ -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,17 @@ 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'
|
|
22
|
+
| 'renderColumns'
|
|
23
|
+
| 'blockInstruction'
|
|
24
|
+
| 'canDrop'
|
|
25
|
+
| 'canSelect'
|
|
26
|
+
| 'onOpenChange'
|
|
27
|
+
| 'onSelect'
|
|
28
|
+
| 'levelOffset'
|
|
29
|
+
>;
|
|
20
30
|
|
|
21
31
|
export const Tree = <T extends HasId = any, O = any>({
|
|
22
32
|
root,
|
|
@@ -31,7 +41,9 @@ export const Tree = <T extends HasId = any, O = any>({
|
|
|
31
41
|
classNames,
|
|
32
42
|
levelOffset,
|
|
33
43
|
renderColumns,
|
|
44
|
+
blockInstruction,
|
|
34
45
|
canDrop,
|
|
46
|
+
canSelect,
|
|
35
47
|
onOpenChange,
|
|
36
48
|
onSelect,
|
|
37
49
|
}: TreeProps<T, O>) => {
|
|
@@ -59,7 +71,9 @@ export const Tree = <T extends HasId = any, O = any>({
|
|
|
59
71
|
levelOffset={levelOffset}
|
|
60
72
|
draggable={draggable}
|
|
61
73
|
renderColumns={renderColumns}
|
|
74
|
+
blockInstruction={blockInstruction}
|
|
62
75
|
canDrop={canDrop}
|
|
76
|
+
canSelect={canSelect}
|
|
63
77
|
onOpenChange={onOpenChange}
|
|
64
78
|
onSelect={onSelect}
|
|
65
79
|
/>
|
|
@@ -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),
|
|
@@ -57,7 +58,9 @@ export type TreeItemProps<T extends HasId = any> = {
|
|
|
57
58
|
last: boolean;
|
|
58
59
|
draggable?: boolean;
|
|
59
60
|
renderColumns?: ColumnRenderer<T>;
|
|
61
|
+
blockInstruction?: (params: { instruction: Instruction; source: TreeData; target: TreeData }) => boolean;
|
|
60
62
|
canDrop?: (params: { source: TreeData; target: TreeData }) => boolean;
|
|
63
|
+
canSelect?: (params: { item: T; path: string[] }) => boolean;
|
|
61
64
|
onOpenChange?: (params: { item: T; path: string[]; open: boolean }) => void;
|
|
62
65
|
onSelect?: (params: { item: T; path: string[]; current: boolean; option: boolean }) => void;
|
|
63
66
|
};
|
|
@@ -65,13 +68,15 @@ export type TreeItemProps<T extends HasId = any> = {
|
|
|
65
68
|
const RawTreeItem = <T extends HasId = any>({
|
|
66
69
|
item,
|
|
67
70
|
path: _path,
|
|
71
|
+
levelOffset = 2,
|
|
68
72
|
last,
|
|
69
73
|
draggable: _draggable,
|
|
70
74
|
renderColumns: Columns,
|
|
75
|
+
blockInstruction,
|
|
71
76
|
canDrop,
|
|
77
|
+
canSelect,
|
|
72
78
|
onOpenChange,
|
|
73
79
|
onSelect,
|
|
74
|
-
levelOffset = 2,
|
|
75
80
|
}: TreeItemProps<T>) => {
|
|
76
81
|
const rowRef = useRef<HTMLDivElement | null>(null);
|
|
77
82
|
const buttonRef = useRef<HTMLButtonElement | null>(null);
|
|
@@ -83,13 +88,14 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
83
88
|
|
|
84
89
|
const { useItems, getProps, isOpen, isCurrent } = useTree();
|
|
85
90
|
const items = useItems(item);
|
|
86
|
-
const { id, label,
|
|
91
|
+
const { id, parentOf, label, className, headingClassName, icon, iconHue, disabled, testId } = getProps(item, _path);
|
|
87
92
|
const path = useMemo(() => [..._path, id], [_path, id]);
|
|
88
93
|
const open = isOpen(path, item);
|
|
89
94
|
const current = isCurrent(path, item);
|
|
90
95
|
const level = path.length - levelOffset;
|
|
91
96
|
const isBranch = !!parentOf;
|
|
92
97
|
const mode: ItemMode = last ? 'last-in-group' : open ? 'expanded' : 'standard';
|
|
98
|
+
const canSelectItem = canSelect?.({ item, path }) ?? true;
|
|
93
99
|
|
|
94
100
|
const cancelExpand = useCallback(() => {
|
|
95
101
|
if (cancelExpandRef.current) {
|
|
@@ -145,7 +151,11 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
145
151
|
},
|
|
146
152
|
getIsSticky: () => true,
|
|
147
153
|
onDrag: ({ self, source }) => {
|
|
148
|
-
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;
|
|
149
159
|
|
|
150
160
|
if (source.data.id !== id) {
|
|
151
161
|
if (instruction?.type === 'make-child' && isBranch && !open && !cancelExpandRef.current) {
|
|
@@ -176,43 +186,42 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
176
186
|
},
|
|
177
187
|
}),
|
|
178
188
|
);
|
|
179
|
-
}, [_draggable, item, id, mode, path, open, canDrop]);
|
|
189
|
+
}, [_draggable, item, id, mode, path, open, blockInstruction, canDrop]);
|
|
180
190
|
|
|
181
191
|
// Cancel expand on unmount.
|
|
182
192
|
useEffect(() => () => cancelExpand(), [cancelExpand]);
|
|
183
193
|
|
|
184
|
-
const
|
|
194
|
+
const handleOpenToggle = useCallback(
|
|
185
195
|
() => onOpenChange?.({ item, path, open: !open }),
|
|
186
196
|
[onOpenChange, item, path, open],
|
|
187
197
|
);
|
|
188
198
|
|
|
189
199
|
const handleSelect = useCallback(
|
|
190
200
|
(option = false) => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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 });
|
|
194
208
|
rowRef.current?.focus();
|
|
195
209
|
onSelect?.({ item, path, current: !current, option });
|
|
196
210
|
}
|
|
197
211
|
},
|
|
198
|
-
[item, path, current, isBranch,
|
|
212
|
+
[item, path, current, isBranch, canSelectItem, handleOpenToggle, onSelect],
|
|
199
213
|
);
|
|
200
214
|
|
|
201
215
|
const handleKeyDown = useCallback(
|
|
202
216
|
(event: KeyboardEvent) => {
|
|
203
217
|
switch (event.key) {
|
|
204
218
|
case 'ArrowRight':
|
|
205
|
-
isBranch && !open && handleOpenChange();
|
|
206
|
-
break;
|
|
207
219
|
case 'ArrowLeft':
|
|
208
|
-
isBranch &&
|
|
209
|
-
break;
|
|
210
|
-
case ' ':
|
|
211
|
-
handleSelect(event.altKey);
|
|
220
|
+
isBranch && handleOpenToggle();
|
|
212
221
|
break;
|
|
213
222
|
}
|
|
214
223
|
},
|
|
215
|
-
[isBranch, open,
|
|
224
|
+
[isBranch, open, handleOpenToggle, handleSelect],
|
|
216
225
|
);
|
|
217
226
|
|
|
218
227
|
return (
|
|
@@ -230,9 +239,10 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
230
239
|
hoverableFocusedWithinControls,
|
|
231
240
|
hoverableDescriptionIcons,
|
|
232
241
|
ghostHover,
|
|
242
|
+
ghostFocusWithin,
|
|
233
243
|
className,
|
|
234
244
|
]}
|
|
235
|
-
data-
|
|
245
|
+
data-object-id={id}
|
|
236
246
|
data-testid={testId}
|
|
237
247
|
// NOTE(thure): This is intentionally an empty string to for descendents to select by in the CSS
|
|
238
248
|
// without alerting the user (except for in the correct link element). See also:
|
|
@@ -244,26 +254,27 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
244
254
|
setMenuOpen(true);
|
|
245
255
|
}}
|
|
246
256
|
>
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
257
|
+
<div
|
|
258
|
+
role='none'
|
|
259
|
+
className='indent relative grid grid-cols-subgrid col-[tree-row]'
|
|
250
260
|
style={paddingIndentation(level)}
|
|
251
261
|
>
|
|
252
|
-
<
|
|
253
|
-
<TreeItemToggle isBranch={isBranch} open={open}
|
|
262
|
+
<Treegrid.Cell classNames='flex items-center'>
|
|
263
|
+
<TreeItemToggle isBranch={isBranch} open={open} onClick={handleOpenToggle} />
|
|
254
264
|
<TreeItemHeading
|
|
255
|
-
ref={buttonRef}
|
|
256
|
-
label={label}
|
|
257
|
-
icon={icon}
|
|
258
|
-
className={headingClassName}
|
|
259
265
|
disabled={disabled}
|
|
260
266
|
current={current}
|
|
267
|
+
label={label}
|
|
268
|
+
className={headingClassName}
|
|
269
|
+
icon={icon}
|
|
270
|
+
iconHue={iconHue}
|
|
261
271
|
onSelect={handleSelect}
|
|
272
|
+
ref={buttonRef}
|
|
262
273
|
/>
|
|
263
|
-
</
|
|
274
|
+
</Treegrid.Cell>
|
|
264
275
|
{Columns && <Columns item={item} path={path} open={open} menuOpen={menuOpen} setMenuOpen={setMenuOpen} />}
|
|
265
276
|
{instruction && <NaturalTreeItem.DropIndicator instruction={instruction} gap={2} />}
|
|
266
|
-
</
|
|
277
|
+
</div>
|
|
267
278
|
</Treegrid.Row>
|
|
268
279
|
{open &&
|
|
269
280
|
items.map((item, index) => (
|
|
@@ -274,7 +285,9 @@ const RawTreeItem = <T extends HasId = any>({
|
|
|
274
285
|
last={index === items.length - 1}
|
|
275
286
|
draggable={_draggable}
|
|
276
287
|
renderColumns={Columns}
|
|
288
|
+
blockInstruction={blockInstruction}
|
|
277
289
|
canDrop={canDrop}
|
|
290
|
+
canSelect={canSelect}
|
|
278
291
|
onOpenChange={onOpenChange}
|
|
279
292
|
onSelect={onSelect}
|
|
280
293
|
/>
|
|
@@ -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>
|
|
@@ -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))),
|