@dxos/react-ui-list 0.8.4-main.f5c0578 → 0.8.4-main.fcfe5033a5
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 +684 -706
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +684 -706
- 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 +8 -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 +8 -6
- package/dist/types/src/components/List/ListItem.d.ts.map +1 -1
- package/dist/types/src/components/List/ListRoot.d.ts +2 -2
- 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 +10 -6
- package/dist/types/src/components/Tree/Tree.d.ts.map +1 -1
- package/dist/types/src/components/Tree/Tree.stories.d.ts +18 -7
- package/dist/types/src/components/Tree/Tree.stories.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeContext.d.ts +24 -10
- package/dist/types/src/components/Tree/TreeContext.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItem.d.ts +25 -4
- package/dist/types/src/components/Tree/TreeItem.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts +4 -3
- package/dist/types/src/components/Tree/TreeItemHeading.d.ts.map +1 -1
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts +3 -3
- package/dist/types/src/components/Tree/TreeItemToggle.d.ts.map +1 -1
- package/dist/types/src/components/Tree/index.d.ts +2 -0
- package/dist/types/src/components/Tree/index.d.ts.map +1 -1
- package/dist/types/src/components/Tree/testing.d.ts +3 -3
- package/dist/types/src/components/Tree/testing.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -28
- package/src/components/Accordion/Accordion.stories.tsx +7 -9
- package/src/components/Accordion/AccordionItem.tsx +6 -5
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/List/List.stories.tsx +44 -30
- package/src/components/List/List.tsx +4 -9
- package/src/components/List/ListItem.tsx +58 -43
- package/src/components/List/ListRoot.tsx +3 -3
- package/src/components/List/testing.ts +7 -7
- package/src/components/Tree/Tree.stories.tsx +179 -88
- package/src/components/Tree/Tree.tsx +43 -40
- package/src/components/Tree/TreeContext.tsx +21 -9
- package/src/components/Tree/TreeItem.tsx +214 -127
- package/src/components/Tree/TreeItemHeading.tsx +10 -8
- package/src/components/Tree/TreeItemToggle.tsx +29 -18
- package/src/components/Tree/index.ts +2 -0
- package/src/components/Tree/testing.ts +10 -9
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-list",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.fcfe5033a5",
|
|
4
4
|
"description": "A list component.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/dxos/dxos"
|
|
10
|
+
},
|
|
7
11
|
"license": "MIT",
|
|
8
12
|
"author": "DXOS.org",
|
|
9
13
|
"type": "module",
|
|
@@ -24,39 +28,38 @@
|
|
|
24
28
|
"src"
|
|
25
29
|
],
|
|
26
30
|
"dependencies": {
|
|
27
|
-
"@atlaskit/pragmatic-drag-and-drop": "
|
|
28
|
-
"@atlaskit/pragmatic-drag-and-drop-hitbox": "
|
|
29
|
-
"@
|
|
30
|
-
"@preact/signals-core": "^1.9.0",
|
|
31
|
+
"@atlaskit/pragmatic-drag-and-drop": "1.7.7",
|
|
32
|
+
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.1.0",
|
|
33
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
31
34
|
"@radix-ui/react-accordion": "1.2.3",
|
|
32
35
|
"@radix-ui/react-context": "1.1.1",
|
|
33
|
-
"@
|
|
34
|
-
"@dxos/
|
|
35
|
-
"@dxos/invariant": "0.8.4-main.
|
|
36
|
-
"@dxos/
|
|
37
|
-
"@dxos/
|
|
38
|
-
"@dxos/
|
|
39
|
-
"@dxos/react-ui-text-tooltip": "0.8.4-main.
|
|
40
|
-
"@dxos/
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
36
|
+
"@radix-ui/react-slot": "1.1.2",
|
|
37
|
+
"@dxos/debug": "0.8.4-main.fcfe5033a5",
|
|
38
|
+
"@dxos/invariant": "0.8.4-main.fcfe5033a5",
|
|
39
|
+
"@dxos/echo": "0.8.4-main.fcfe5033a5",
|
|
40
|
+
"@dxos/log": "0.8.4-main.fcfe5033a5",
|
|
41
|
+
"@dxos/ui-theme": "0.8.4-main.fcfe5033a5",
|
|
42
|
+
"@dxos/react-ui-text-tooltip": "0.8.4-main.fcfe5033a5",
|
|
43
|
+
"@dxos/util": "0.8.4-main.fcfe5033a5",
|
|
44
|
+
"@dxos/react-ui": "0.8.4-main.fcfe5033a5",
|
|
45
|
+
"@dxos/ui-types": "0.8.4-main.fcfe5033a5"
|
|
43
46
|
},
|
|
44
47
|
"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.
|
|
48
|
+
"@types/react": "~19.2.7",
|
|
49
|
+
"@types/react-dom": "~19.2.3",
|
|
50
|
+
"effect": "3.20.0",
|
|
51
|
+
"react": "~19.2.3",
|
|
52
|
+
"react-dom": "~19.2.3",
|
|
53
|
+
"vite": "^7.1.11",
|
|
54
|
+
"@dxos/random": "0.8.4-main.fcfe5033a5",
|
|
55
|
+
"@dxos/storybook-utils": "0.8.4-main.fcfe5033a5"
|
|
53
56
|
},
|
|
54
57
|
"peerDependencies": {
|
|
55
|
-
"effect": "
|
|
56
|
-
"react": "~
|
|
57
|
-
"react-dom": "~
|
|
58
|
-
"@dxos/react-ui": "0.8.4-main.
|
|
59
|
-
"@dxos/
|
|
58
|
+
"effect": "3.20.0",
|
|
59
|
+
"react": "~19.2.3",
|
|
60
|
+
"react-dom": "~19.2.3",
|
|
61
|
+
"@dxos/react-ui": "0.8.4-main.fcfe5033a5",
|
|
62
|
+
"@dxos/ui-theme": "0.8.4-main.fcfe5033a5"
|
|
60
63
|
},
|
|
61
64
|
"publishConfig": {
|
|
62
65
|
"access": "public"
|
|
@@ -2,24 +2,22 @@
|
|
|
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
|
-
import {
|
|
11
|
-
import { withLayout, withTheme } from '@dxos/
|
|
8
|
+
import { random } from '@dxos/random';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
12
10
|
|
|
13
11
|
import { Accordion } from './Accordion';
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
random.seed(1);
|
|
16
14
|
|
|
17
15
|
type TestItem = { id: string; name: string; text: string };
|
|
18
16
|
|
|
19
17
|
const items: TestItem[] = Array.from({ length: 10 }, (_, i) => ({
|
|
20
18
|
id: i.toString(),
|
|
21
19
|
name: `Item ${i}`,
|
|
22
|
-
text:
|
|
20
|
+
text: random.lorem.paragraphs(3),
|
|
23
21
|
}));
|
|
24
22
|
|
|
25
23
|
const DefaultStory = () => {
|
|
@@ -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({ layout: 'column' })],
|
|
46
|
+
} satisfies Meta<typeof Accordion>;
|
|
49
47
|
|
|
50
48
|
export default meta;
|
|
51
49
|
|
|
@@ -7,10 +7,9 @@ import { createContext } from '@radix-ui/react-context';
|
|
|
7
7
|
import React, { type PropsWithChildren } from 'react';
|
|
8
8
|
|
|
9
9
|
import { Icon, type ThemedClassName } from '@dxos/react-ui';
|
|
10
|
-
import { mx } from '@dxos/
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
12
|
import { type ListItemRecord } from '../List';
|
|
13
|
-
|
|
14
13
|
import { useAccordionContext } from './AccordionRoot';
|
|
15
14
|
|
|
16
15
|
const ACCORDION_ITEM_NAME = 'AccordionItem';
|
|
@@ -19,7 +18,9 @@ type AccordionItemContext<T extends ListItemRecord> = {
|
|
|
19
18
|
item: T;
|
|
20
19
|
};
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
// TODO(wittjosiah): This seems to be conflicting with something in the bundle.
|
|
22
|
+
// Perhaps @radix-ui/react-accordion?
|
|
23
|
+
export const [AccordionItemProvider, useDxAccordionItemContext] =
|
|
23
24
|
createContext<AccordionItemContext<any>>(ACCORDION_ITEM_NAME);
|
|
24
25
|
|
|
25
26
|
export type AccordionItemProps<T extends ListItemRecord> = ThemedClassName<PropsWithChildren<{ item: T }>>;
|
|
@@ -41,7 +42,7 @@ export type AccordionItemHeaderProps = ThemedClassName<AccordionPrimitive.Accord
|
|
|
41
42
|
export const AccordionItemHeader = ({ classNames, children, ...props }: AccordionItemHeaderProps) => {
|
|
42
43
|
return (
|
|
43
44
|
<AccordionPrimitive.Header {...props} className={mx(classNames)}>
|
|
44
|
-
<AccordionPrimitive.Trigger className='group flex items-center p-2 dx-focus-ring-inset
|
|
45
|
+
<AccordionPrimitive.Trigger className='group flex items-center p-2 dx-focus-ring-inset w-full text-start'>
|
|
45
46
|
{children}
|
|
46
47
|
<Icon
|
|
47
48
|
icon='ph--caret-right--regular'
|
|
@@ -57,7 +58,7 @@ export type AccordionItemBodyProps = ThemedClassName<PropsWithChildren>;
|
|
|
57
58
|
|
|
58
59
|
export const AccordionItemBody = ({ children, classNames }: AccordionItemBodyProps) => {
|
|
59
60
|
return (
|
|
60
|
-
<AccordionPrimitive.Content className='overflow-hidden data-[state=closed]:animate-
|
|
61
|
+
<AccordionPrimitive.Content className='overflow-hidden data-[state=closed]:animate-slide-up data-[state=open]:animate-slide-down'>
|
|
61
62
|
<div role='none' className={mx('p-2', classNames)}>
|
|
62
63
|
{children}
|
|
63
64
|
</div>
|
|
@@ -7,7 +7,7 @@ import { createContext } from '@radix-ui/react-context';
|
|
|
7
7
|
import React, { type ReactNode } from 'react';
|
|
8
8
|
|
|
9
9
|
import { type ThemedClassName } from '@dxos/react-ui';
|
|
10
|
-
import { mx } from '@dxos/
|
|
10
|
+
import { mx } from '@dxos/ui-theme';
|
|
11
11
|
|
|
12
12
|
import { type ListItemRecord } from '../List';
|
|
13
13
|
|
|
@@ -2,40 +2,43 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
5
|
+
import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
7
6
|
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
8
|
-
import
|
|
9
|
-
import React from 'react';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
import React, { useContext, useMemo } from 'react';
|
|
10
9
|
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
10
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
11
|
+
import { withRegistry } from '@dxos/storybook-utils';
|
|
12
|
+
import { mx } from '@dxos/ui-theme';
|
|
14
13
|
import { arrayMove } from '@dxos/util';
|
|
15
14
|
|
|
16
15
|
import { List, type ListRootProps } from './List';
|
|
17
|
-
import { TestItemSchema, type TestItemType, createList } from './testing';
|
|
16
|
+
import { TestItemSchema, type TestItemType, type TestList, createList } from './testing';
|
|
18
17
|
|
|
19
18
|
// TODO(burdon): var-icon-size.
|
|
20
|
-
const grid = 'grid grid-cols-[32px_1fr_32px] min-
|
|
19
|
+
const grid = 'grid grid-cols-[32px_1fr_32px] min-h-[2rem] rounded-sm';
|
|
21
20
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export default meta;
|
|
21
|
+
const DefaultStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
|
|
22
|
+
const registry = useContext(RegistryContext);
|
|
23
|
+
const listAtom = useMemo(() => Atom.make<TestList>(createList(100)).pipe(Atom.keepAlive), []);
|
|
24
|
+
const list = useAtomValue(listAtom);
|
|
25
|
+
const items = list.items;
|
|
28
26
|
|
|
29
|
-
const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
30
27
|
const handleSelect = (item: TestItemType) => {
|
|
31
28
|
console.log('select', item);
|
|
32
29
|
};
|
|
33
30
|
const handleDelete = (item: TestItemType) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
31
|
+
const prev = registry.get(listAtom);
|
|
32
|
+
registry.set(listAtom, {
|
|
33
|
+
...prev,
|
|
34
|
+
items: prev.items.filter((i) => i.id !== item.id),
|
|
35
|
+
});
|
|
36
36
|
};
|
|
37
37
|
const handleMove = (from: number, to: number) => {
|
|
38
|
-
|
|
38
|
+
const prev = registry.get(listAtom);
|
|
39
|
+
const newItems = [...prev.items];
|
|
40
|
+
arrayMove(newItems, from, to);
|
|
41
|
+
registry.set(listAtom, { ...prev, items: newItems });
|
|
39
42
|
};
|
|
40
43
|
|
|
41
44
|
return (
|
|
@@ -48,9 +51,9 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
48
51
|
<div className='flex items-center text-sm'>Items</div>
|
|
49
52
|
</div>
|
|
50
53
|
|
|
51
|
-
<div role='list' className='
|
|
54
|
+
<div role='list' className='h-full w-full overflow-auto'>
|
|
52
55
|
{items?.map((item) => (
|
|
53
|
-
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid
|
|
56
|
+
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid)}>
|
|
54
57
|
<List.ItemDragHandle />
|
|
55
58
|
<List.ItemTitle onClick={() => handleSelect(item)}>{item.name}</List.ItemTitle>
|
|
56
59
|
<List.ItemDeleteButton onClick={() => handleDelete(item)} />
|
|
@@ -66,7 +69,7 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
66
69
|
|
|
67
70
|
<List.ItemDragPreview<TestItemType>>
|
|
68
71
|
{({ item }) => (
|
|
69
|
-
<List.ItemWrapper classNames={mx(grid, 'bg-
|
|
72
|
+
<List.ItemWrapper classNames={mx(grid, 'bg-modal-surface border border-separator')}>
|
|
70
73
|
<List.ItemDragHandle />
|
|
71
74
|
<div className='flex items-center'>{item.name}</div>
|
|
72
75
|
</List.ItemWrapper>
|
|
@@ -78,13 +81,17 @@ const DefaultStory = ({ items = [], ...props }: ListRootProps<TestItemType>) =>
|
|
|
78
81
|
);
|
|
79
82
|
};
|
|
80
83
|
|
|
81
|
-
const SimpleStory = (
|
|
84
|
+
const SimpleStory = (props: Omit<ListRootProps<TestItemType>, 'items'>) => {
|
|
85
|
+
const listAtom = useMemo(() => Atom.make<TestList>(createList(100)).pipe(Atom.keepAlive), []);
|
|
86
|
+
const list = useAtomValue(listAtom);
|
|
87
|
+
const items = list.items;
|
|
88
|
+
|
|
82
89
|
return (
|
|
83
90
|
<List.Root<TestItemType> dragPreview items={items} {...props}>
|
|
84
91
|
{({ items }) => (
|
|
85
|
-
<div role='list' className='
|
|
92
|
+
<div role='list' className='h-full w-full overflow-auto'>
|
|
86
93
|
{items?.map((item) => (
|
|
87
|
-
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid
|
|
94
|
+
<List.Item<TestItemType> key={item.id} item={item} classNames={mx(grid)}>
|
|
88
95
|
<List.ItemDragHandle />
|
|
89
96
|
<List.ItemTitle>{item.name}</List.ItemTitle>
|
|
90
97
|
<List.ItemDeleteButton />
|
|
@@ -96,20 +103,27 @@ const SimpleStory = ({ items = [], ...props }: ListRootProps<TestItemType>) => {
|
|
|
96
103
|
);
|
|
97
104
|
};
|
|
98
105
|
|
|
99
|
-
const
|
|
106
|
+
const meta = {
|
|
107
|
+
title: 'ui/react-ui-list/List',
|
|
108
|
+
component: List.Root,
|
|
109
|
+
decorators: [withTheme(), withLayout({ layout: 'fullscreen' }), withRegistry],
|
|
110
|
+
parameters: {
|
|
111
|
+
layout: 'fullscreen',
|
|
112
|
+
},
|
|
113
|
+
} satisfies Meta<typeof List.Root>;
|
|
114
|
+
|
|
115
|
+
export default meta;
|
|
100
116
|
|
|
101
|
-
export const Default: StoryObj<
|
|
117
|
+
export const Default: StoryObj<typeof DefaultStory> = {
|
|
102
118
|
render: DefaultStory,
|
|
103
119
|
args: {
|
|
104
|
-
items: list.items,
|
|
105
120
|
isItem: Schema.is(TestItemSchema),
|
|
106
121
|
},
|
|
107
122
|
};
|
|
108
123
|
|
|
109
|
-
export const Simple: StoryObj<
|
|
124
|
+
export const Simple: StoryObj<typeof SimpleStory> = {
|
|
110
125
|
render: SimpleStory,
|
|
111
126
|
args: {
|
|
112
|
-
items: list.items,
|
|
113
127
|
isItem: Schema.is(TestItemSchema),
|
|
114
128
|
},
|
|
115
129
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ListItem,
|
|
7
|
-
|
|
7
|
+
ListItemIconButton,
|
|
8
8
|
ListItemDeleteButton,
|
|
9
9
|
ListItemDragHandle,
|
|
10
10
|
ListItemDragPreview,
|
|
@@ -15,17 +15,12 @@ import {
|
|
|
15
15
|
} from './ListItem';
|
|
16
16
|
import { ListRoot, type ListRootProps } from './ListRoot';
|
|
17
17
|
|
|
18
|
-
// TODO(burdon): Multi-select model.
|
|
19
|
-
// TODO(burdon): Key nav.
|
|
20
|
-
// TODO(burdon): Animation.
|
|
21
|
-
// TODO(burdon): Constrain axis.
|
|
22
|
-
// TODO(burdon): Tree view.
|
|
23
|
-
// TODO(burdon): Fix autoscroll while dragging.
|
|
24
|
-
|
|
25
18
|
/**
|
|
26
19
|
* Draggable list.
|
|
27
20
|
* Ref: https://github.com/atlassian/pragmatic-drag-and-drop
|
|
28
21
|
* Ref: https://github.com/alexreardon/pdnd-react-tailwind/blob/main/src/task.tsx
|
|
22
|
+
*
|
|
23
|
+
* @deprecated Use react-ui-mosaic.
|
|
29
24
|
*/
|
|
30
25
|
export const List = {
|
|
31
26
|
Root: ListRoot,
|
|
@@ -33,8 +28,8 @@ export const List = {
|
|
|
33
28
|
ItemDragPreview: ListItemDragPreview,
|
|
34
29
|
ItemWrapper: ListItemWrapper,
|
|
35
30
|
ItemDragHandle: ListItemDragHandle,
|
|
31
|
+
ItemIconButton: ListItemIconButton,
|
|
36
32
|
ItemDeleteButton: ListItemDeleteButton,
|
|
37
|
-
ItemButton: ListItemButton,
|
|
38
33
|
ItemTitle: ListItemTitle,
|
|
39
34
|
};
|
|
40
35
|
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
6
|
-
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
7
|
-
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
8
5
|
import {
|
|
9
6
|
type Edge,
|
|
10
7
|
attachClosestEdge,
|
|
11
8
|
extractClosestEdge,
|
|
12
9
|
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
10
|
+
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
|
|
11
|
+
import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
12
|
+
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
13
13
|
import { createContext } from '@radix-ui/react-context';
|
|
14
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
14
15
|
import React, {
|
|
15
16
|
type ComponentProps,
|
|
16
17
|
type HTMLAttributes,
|
|
17
|
-
type MutableRefObject,
|
|
18
18
|
type PropsWithChildren,
|
|
19
19
|
type ReactNode,
|
|
20
|
+
RefObject,
|
|
20
21
|
useEffect,
|
|
21
22
|
useRef,
|
|
22
23
|
useState,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
type ThemedClassName,
|
|
32
33
|
useTranslation,
|
|
33
34
|
} from '@dxos/react-ui';
|
|
34
|
-
import { mx } from '@dxos/
|
|
35
|
+
import { mx, osTranslations } from '@dxos/ui-theme';
|
|
35
36
|
|
|
36
37
|
import { useListContext } from './ListRoot';
|
|
37
38
|
|
|
@@ -61,7 +62,7 @@ const stateStyles: { [Key in ItemDragState['type']]?: HTMLAttributes<HTMLDivElem
|
|
|
61
62
|
|
|
62
63
|
type ListItemContext<T extends ListItemRecord> = {
|
|
63
64
|
item: T;
|
|
64
|
-
dragHandleRef:
|
|
65
|
+
dragHandleRef: RefObject<HTMLButtonElement | null>;
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
/**
|
|
@@ -80,6 +81,8 @@ export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
|
|
|
80
81
|
PropsWithChildren<
|
|
81
82
|
{
|
|
82
83
|
item: T;
|
|
84
|
+
asChild?: boolean;
|
|
85
|
+
selected?: boolean;
|
|
83
86
|
} & HTMLAttributes<HTMLDivElement>
|
|
84
87
|
>
|
|
85
88
|
>;
|
|
@@ -87,14 +90,22 @@ export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
|
|
|
87
90
|
/**
|
|
88
91
|
* Draggable list item.
|
|
89
92
|
*/
|
|
90
|
-
export const ListItem = <T extends ListItemRecord>({
|
|
93
|
+
export const ListItem = <T extends ListItemRecord>({
|
|
94
|
+
children,
|
|
95
|
+
classNames,
|
|
96
|
+
item,
|
|
97
|
+
asChild,
|
|
98
|
+
selected,
|
|
99
|
+
...props
|
|
100
|
+
}: ListItemProps<T>) => {
|
|
101
|
+
const Comp = asChild ? Slot : 'div';
|
|
91
102
|
const { isItem, readonly, dragPreview, setState: setRootState } = useListContext(LIST_ITEM_NAME);
|
|
92
|
-
const
|
|
93
|
-
const dragHandleRef = useRef<
|
|
103
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
104
|
+
const dragHandleRef = useRef<HTMLButtonElement | null>(null);
|
|
94
105
|
const [state, setState] = useState<ItemDragState>(idle);
|
|
95
106
|
|
|
96
107
|
useEffect(() => {
|
|
97
|
-
const element =
|
|
108
|
+
const element = rootRef.current;
|
|
98
109
|
invariant(element);
|
|
99
110
|
return combine(
|
|
100
111
|
//
|
|
@@ -170,17 +181,18 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
170
181
|
|
|
171
182
|
return (
|
|
172
183
|
<ListItemProvider item={item} dragHandleRef={dragHandleRef}>
|
|
173
|
-
<
|
|
174
|
-
ref={ref}
|
|
175
|
-
role='listitem'
|
|
176
|
-
className={mx('flex overflow-hidden relative', classNames, stateStyles[state.type])}
|
|
184
|
+
<Comp
|
|
177
185
|
{...props}
|
|
186
|
+
role='listitem'
|
|
187
|
+
aria-selected={selected}
|
|
188
|
+
className={mx('relative p-1 dx-selected dx-hover', classNames, stateStyles[state.type])}
|
|
189
|
+
ref={rootRef}
|
|
178
190
|
>
|
|
179
191
|
{children}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
</Comp>
|
|
193
|
+
{state.type === 'is-dragging-over' && state.closestEdge && (
|
|
194
|
+
<NaturalListItem.DropIndicator edge={state.closestEdge} />
|
|
195
|
+
)}
|
|
184
196
|
</ListItemProvider>
|
|
185
197
|
);
|
|
186
198
|
};
|
|
@@ -189,47 +201,48 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
189
201
|
// List item components
|
|
190
202
|
//
|
|
191
203
|
|
|
192
|
-
export const
|
|
204
|
+
export const ListItemIconButton = ({
|
|
193
205
|
autoHide = true,
|
|
206
|
+
iconOnly = true,
|
|
207
|
+
variant = 'ghost',
|
|
194
208
|
classNames,
|
|
195
209
|
disabled,
|
|
196
|
-
icon = 'ph--x--regular',
|
|
197
|
-
label,
|
|
198
210
|
...props
|
|
199
|
-
}:
|
|
200
|
-
|
|
201
|
-
const { state } = useListContext('DELETE_BUTTON');
|
|
211
|
+
}: IconButtonProps & { autoHide?: boolean }) => {
|
|
212
|
+
const { state } = useListContext('ITEM_BUTTON');
|
|
202
213
|
const isDisabled = state.type !== 'idle' || disabled;
|
|
203
|
-
const { t } = useTranslation('os');
|
|
204
214
|
return (
|
|
205
215
|
<IconButton
|
|
206
|
-
iconOnly
|
|
207
|
-
variant='ghost'
|
|
208
216
|
{...props}
|
|
209
|
-
icon={icon}
|
|
210
217
|
disabled={isDisabled}
|
|
211
|
-
|
|
218
|
+
iconOnly={iconOnly}
|
|
219
|
+
variant={variant}
|
|
212
220
|
classNames={[classNames, autoHide && disabled && 'hidden']}
|
|
213
221
|
/>
|
|
214
222
|
);
|
|
215
223
|
};
|
|
216
224
|
|
|
217
|
-
|
|
225
|
+
// TODO(burdon): Generalize to action button.
|
|
226
|
+
export const ListItemDeleteButton = ({
|
|
218
227
|
autoHide = true,
|
|
219
|
-
iconOnly = true,
|
|
220
|
-
variant = 'ghost',
|
|
221
228
|
classNames,
|
|
222
229
|
disabled,
|
|
230
|
+
icon = 'ph--x--regular',
|
|
231
|
+
label,
|
|
223
232
|
...props
|
|
224
|
-
}: IconButtonProps &
|
|
225
|
-
|
|
233
|
+
}: Partial<Pick<IconButtonProps, 'icon'>> &
|
|
234
|
+
Omit<IconButtonProps, 'icon' | 'label'> & { autoHide?: boolean; label?: string }) => {
|
|
235
|
+
const { state } = useListContext('DELETE_BUTTON');
|
|
226
236
|
const isDisabled = state.type !== 'idle' || disabled;
|
|
237
|
+
const { t } = useTranslation(osTranslations);
|
|
227
238
|
return (
|
|
228
239
|
<IconButton
|
|
229
240
|
{...props}
|
|
241
|
+
variant='ghost'
|
|
230
242
|
disabled={isDisabled}
|
|
231
|
-
|
|
232
|
-
|
|
243
|
+
icon={icon}
|
|
244
|
+
iconOnly
|
|
245
|
+
label={label ?? t('delete.label')}
|
|
233
246
|
classNames={[classNames, autoHide && disabled && 'hidden']}
|
|
234
247
|
/>
|
|
235
248
|
);
|
|
@@ -237,15 +250,15 @@ export const ListItemButton = ({
|
|
|
237
250
|
|
|
238
251
|
export const ListItemDragHandle = ({ disabled }: Pick<IconButtonProps, 'disabled'>) => {
|
|
239
252
|
const { dragHandleRef } = useListItemContext('DRAG_HANDLE');
|
|
240
|
-
const { t } = useTranslation(
|
|
253
|
+
const { t } = useTranslation(osTranslations);
|
|
241
254
|
return (
|
|
242
255
|
<IconButton
|
|
243
|
-
iconOnly
|
|
244
256
|
variant='ghost'
|
|
245
|
-
label={t('drag handle label')}
|
|
246
|
-
ref={dragHandleRef as any}
|
|
247
|
-
icon='ph--dots-six-vertical--regular'
|
|
248
257
|
disabled={disabled}
|
|
258
|
+
icon='ph--dots-six-vertical--regular'
|
|
259
|
+
iconOnly
|
|
260
|
+
label={t('drag-handle.label')}
|
|
261
|
+
ref={dragHandleRef}
|
|
249
262
|
/>
|
|
250
263
|
);
|
|
251
264
|
};
|
|
@@ -260,7 +273,9 @@ export const ListItemDragPreview = <T extends ListItemRecord>({
|
|
|
260
273
|
};
|
|
261
274
|
|
|
262
275
|
export const ListItemWrapper = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
|
|
263
|
-
<div className={mx('flex
|
|
276
|
+
<div role='none' className={mx('flex w-full gap-2', classNames)}>
|
|
277
|
+
{children}
|
|
278
|
+
</div>
|
|
264
279
|
);
|
|
265
280
|
|
|
266
281
|
export const ListItemTitle = ({
|
|
@@ -268,7 +283,7 @@ export const ListItemTitle = ({
|
|
|
268
283
|
children,
|
|
269
284
|
...props
|
|
270
285
|
}: ThemedClassName<PropsWithChildren<ComponentProps<'div'>>>) => (
|
|
271
|
-
<div className={mx('flex grow items-center truncate', classNames)} {...props}>
|
|
286
|
+
<div role='none' className={mx('flex grow items-center truncate', classNames)} {...props}>
|
|
272
287
|
{children}
|
|
273
288
|
</div>
|
|
274
289
|
);
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
6
5
|
import { extractClosestEdge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
7
6
|
import { getReorderDestinationIndex } from '@atlaskit/pragmatic-drag-and-drop-hitbox/util/get-reorder-destination-index';
|
|
7
|
+
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
|
|
8
8
|
import { createContext } from '@radix-ui/react-context';
|
|
9
9
|
import React, { type ReactNode, useCallback, useEffect, useState } from 'react';
|
|
10
10
|
|
|
@@ -26,14 +26,14 @@ export const [ListProvider, useListContext] = createContext<ListContext<any>>(LI
|
|
|
26
26
|
|
|
27
27
|
export type ListRendererProps<T extends ListItemRecord> = {
|
|
28
28
|
state: ListContext<T>['state'];
|
|
29
|
-
items: T[];
|
|
29
|
+
items: readonly T[];
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const defaultGetId = <T extends ListItemRecord>(item: T) => (item as any)?.id;
|
|
33
33
|
|
|
34
34
|
export type ListRootProps<T extends ListItemRecord> = {
|
|
35
35
|
children?: (props: ListRendererProps<T>) => ReactNode;
|
|
36
|
-
items?: T[];
|
|
36
|
+
items?: readonly T[];
|
|
37
37
|
onMove?: (fromIndex: number, toIndex: number) => void;
|
|
38
38
|
} & Pick<ListContext<T>, 'isItem' | 'getId' | 'readonly' | 'dragPreview'>;
|
|
39
39
|
|
|
@@ -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 {
|
|
8
|
-
import {
|
|
7
|
+
import { Obj } from '@dxos/echo';
|
|
8
|
+
import { random } 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
|
|
|
@@ -21,10 +21,10 @@ export const TestList = Schema.Struct({
|
|
|
21
21
|
export type TestList = Schema.Schema.Type<typeof TestList>;
|
|
22
22
|
|
|
23
23
|
export const createList = (n = 10): TestList => ({
|
|
24
|
-
items:
|
|
24
|
+
items: random.helpers.multiple(
|
|
25
25
|
() => ({
|
|
26
|
-
id:
|
|
27
|
-
name:
|
|
26
|
+
id: random.string.uuid(),
|
|
27
|
+
name: random.commerce.productName(),
|
|
28
28
|
}),
|
|
29
29
|
{ count: n },
|
|
30
30
|
),
|