@dxos/react-ui-list 0.8.3 → 0.8.4-main.1c7ec43d41
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 +1374 -739
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +1374 -739
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/components/Accordion/Accordion.d.ts +1 -1
- package/dist/types/src/components/Accordion/Accordion.d.ts.map +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/Accordion/AccordionRoot.d.ts +1 -1
- package/dist/types/src/components/Accordion/AccordionRoot.d.ts.map +1 -1
- package/dist/types/src/components/Combobox/Combobox.d.ts +105 -0
- package/dist/types/src/components/Combobox/Combobox.d.ts.map +1 -0
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts +12 -0
- package/dist/types/src/components/Combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/types/src/components/Combobox/index.d.ts +2 -0
- package/dist/types/src/components/Combobox/index.d.ts.map +1 -0
- package/dist/types/src/components/List/List.d.ts +21 -10
- 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 +12 -13
- 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/Listbox/Listbox.d.ts +27 -0
- package/dist/types/src/components/Listbox/Listbox.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts +12 -0
- package/dist/types/src/components/Listbox/Listbox.stories.d.ts.map +1 -0
- package/dist/types/src/components/Listbox/index.d.ts +2 -0
- package/dist/types/src/components/Listbox/index.d.ts.map +1 -0
- package/dist/types/src/components/Picker/Picker.d.ts +49 -0
- package/dist/types/src/components/Picker/Picker.d.ts.map +1 -0
- package/dist/types/src/components/Picker/Picker.stories.d.ts +28 -0
- package/dist/types/src/components/Picker/Picker.stories.d.ts.map +1 -0
- package/dist/types/src/components/Picker/context.d.ts +29 -0
- package/dist/types/src/components/Picker/context.d.ts.map +1 -0
- package/dist/types/src/components/Picker/index.d.ts +3 -0
- package/dist/types/src/components/Picker/index.d.ts.map +1 -0
- package/dist/types/src/components/RowList/RowList.d.ts +61 -0
- package/dist/types/src/components/RowList/RowList.d.ts.map +1 -0
- package/dist/types/src/components/RowList/RowList.stories.d.ts +35 -0
- package/dist/types/src/components/RowList/RowList.stories.d.ts.map +1 -0
- package/dist/types/src/components/RowList/index.d.ts +3 -0
- package/dist/types/src/components/RowList/index.d.ts.map +1 -0
- 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 +32 -10
- 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/helpers.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/src/components/index.d.ts +4 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/util/path.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +35 -33
- package/src/components/Accordion/Accordion.stories.tsx +8 -10
- package/src/components/Accordion/Accordion.tsx +1 -1
- package/src/components/Accordion/AccordionItem.tsx +7 -5
- package/src/components/Accordion/AccordionRoot.tsx +1 -1
- package/src/components/Combobox/Combobox.stories.tsx +60 -0
- package/src/components/Combobox/Combobox.tsx +387 -0
- package/src/components/Combobox/index.ts +5 -0
- package/src/components/List/List.stories.tsx +46 -32
- package/src/components/List/List.tsx +15 -14
- package/src/components/List/ListItem.tsx +91 -57
- package/src/components/List/ListRoot.tsx +4 -4
- package/src/components/List/testing.ts +7 -7
- package/src/components/Listbox/Listbox.stories.tsx +48 -0
- package/src/components/Listbox/Listbox.tsx +201 -0
- package/src/components/Listbox/index.ts +5 -0
- package/src/components/Picker/Picker.stories.tsx +131 -0
- package/src/components/Picker/Picker.tsx +368 -0
- package/src/components/Picker/context.ts +43 -0
- package/src/components/Picker/index.ts +6 -0
- package/src/components/RowList/RowList.stories.tsx +163 -0
- package/src/components/RowList/RowList.tsx +353 -0
- package/src/components/RowList/index.ts +6 -0
- package/src/components/Tree/Tree.stories.tsx +175 -83
- package/src/components/Tree/Tree.tsx +43 -40
- package/src/components/Tree/TreeContext.tsx +21 -9
- package/src/components/Tree/TreeItem.tsx +222 -135
- package/src/components/Tree/TreeItemHeading.tsx +15 -12
- package/src/components/Tree/TreeItemToggle.tsx +29 -19
- package/src/components/Tree/index.ts +2 -0
- package/src/components/Tree/testing.ts +10 -9
- package/src/components/index.ts +4 -0
- package/dist/lib/node/index.cjs +0 -886
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
|
@@ -2,40 +2,43 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import '@
|
|
6
|
-
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { withLayout, withTheme } from '@dxos/storybook-utils';
|
|
5
|
+
import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
|
+
import * as Schema from 'effect/Schema';
|
|
8
|
+
import React, { useContext, useMemo } from 'react';
|
|
9
|
+
|
|
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 {
|
|
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-
|
|
21
|
-
|
|
22
|
-
const meta: Meta = {
|
|
23
|
-
title: 'ui/react-ui-list/List',
|
|
24
|
-
decorators: [withTheme, withLayout({ fullscreen: true })],
|
|
25
|
-
};
|
|
19
|
+
const grid = 'grid grid-cols-[32px_1fr_32px] min-h-[2rem] rounded-sm';
|
|
26
20
|
|
|
27
|
-
|
|
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
|
};
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
//
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
IconButton,
|
|
7
|
-
type IconButtonProps,
|
|
8
6
|
ListItem,
|
|
7
|
+
ListItemIconButton,
|
|
9
8
|
ListItemDeleteButton,
|
|
10
|
-
ListItemButton,
|
|
11
9
|
ListItemDragHandle,
|
|
12
10
|
ListItemDragPreview,
|
|
13
11
|
type ListItemProps,
|
|
@@ -17,17 +15,21 @@ import {
|
|
|
17
15
|
} from './ListItem';
|
|
18
16
|
import { ListRoot, type ListRootProps } from './ListRoot';
|
|
19
17
|
|
|
20
|
-
// TODO(burdon): Multi-select model.
|
|
21
|
-
// TODO(burdon): Key nav.
|
|
22
|
-
// TODO(burdon): Animation.
|
|
23
|
-
// TODO(burdon): Constrain axis.
|
|
24
|
-
// TODO(burdon): Tree view.
|
|
25
|
-
// TODO(burdon): Fix autoscroll while dragging.
|
|
26
|
-
|
|
27
18
|
/**
|
|
28
|
-
* Draggable list.
|
|
19
|
+
* Draggable list with per-row drag handles and delete buttons.
|
|
29
20
|
* Ref: https://github.com/atlassian/pragmatic-drag-and-drop
|
|
30
21
|
* Ref: https://github.com/alexreardon/pdnd-react-tailwind/blob/main/src/task.tsx
|
|
22
|
+
*
|
|
23
|
+
* @deprecated New code should use one of:
|
|
24
|
+
*
|
|
25
|
+
* - `RowList` / `CardList` from this same package — for selectable
|
|
26
|
+
* pickers (master/detail). Correct ARIA + dx-* by construction.
|
|
27
|
+
* - `Mosaic.Stack` / `Mosaic.VirtualStack` from `@dxos/react-ui-mosaic`
|
|
28
|
+
* — for virtualized or drag-reorderable card stacks.
|
|
29
|
+
*
|
|
30
|
+
* This component is retained for the existing reorder-with-delete-button
|
|
31
|
+
* use cases (plugin-meeting, plugin-automation, plugin-zen, etc.) until
|
|
32
|
+
* each is migrated; see `AUDIT.md` Phase 6 for the migration plan.
|
|
31
33
|
*/
|
|
32
34
|
export const List = {
|
|
33
35
|
Root: ListRoot,
|
|
@@ -35,12 +37,11 @@ export const List = {
|
|
|
35
37
|
ItemDragPreview: ListItemDragPreview,
|
|
36
38
|
ItemWrapper: ListItemWrapper,
|
|
37
39
|
ItemDragHandle: ListItemDragHandle,
|
|
40
|
+
ItemIconButton: ListItemIconButton,
|
|
38
41
|
ItemDeleteButton: ListItemDeleteButton,
|
|
39
|
-
ItemButton: ListItemButton,
|
|
40
42
|
ItemTitle: ListItemTitle,
|
|
41
|
-
IconButton,
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
type ListItem = ListItemRecord;
|
|
45
46
|
|
|
46
|
-
export type { ListRootProps, ListItemProps,
|
|
47
|
+
export type { ListRootProps, ListItemProps, ListItem, ListItemRecord };
|
|
@@ -2,22 +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
|
-
|
|
20
|
+
RefObject,
|
|
21
21
|
useEffect,
|
|
22
22
|
useRef,
|
|
23
23
|
useState,
|
|
@@ -25,8 +25,14 @@ import React, {
|
|
|
25
25
|
import { createPortal } from 'react-dom';
|
|
26
26
|
|
|
27
27
|
import { invariant } from '@dxos/invariant';
|
|
28
|
-
import {
|
|
29
|
-
|
|
28
|
+
import {
|
|
29
|
+
IconButton,
|
|
30
|
+
type IconButtonProps,
|
|
31
|
+
ListItem as NaturalListItem,
|
|
32
|
+
type ThemedClassName,
|
|
33
|
+
useTranslation,
|
|
34
|
+
} from '@dxos/react-ui';
|
|
35
|
+
import { mx, osTranslations } from '@dxos/ui-theme';
|
|
30
36
|
|
|
31
37
|
import { useListContext } from './ListRoot';
|
|
32
38
|
|
|
@@ -56,7 +62,7 @@ const stateStyles: { [Key in ItemDragState['type']]?: HTMLAttributes<HTMLDivElem
|
|
|
56
62
|
|
|
57
63
|
type ListItemContext<T extends ListItemRecord> = {
|
|
58
64
|
item: T;
|
|
59
|
-
dragHandleRef:
|
|
65
|
+
dragHandleRef: RefObject<HTMLButtonElement | null>;
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
/**
|
|
@@ -72,22 +78,34 @@ export const [ListItemProvider, useListItemContext] = createContext<ListItemCont
|
|
|
72
78
|
);
|
|
73
79
|
|
|
74
80
|
export type ListItemProps<T extends ListItemRecord> = ThemedClassName<
|
|
75
|
-
PropsWithChildren<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
PropsWithChildren<
|
|
82
|
+
{
|
|
83
|
+
item: T;
|
|
84
|
+
asChild?: boolean;
|
|
85
|
+
selected?: boolean;
|
|
86
|
+
} & HTMLAttributes<HTMLDivElement>
|
|
87
|
+
>
|
|
79
88
|
>;
|
|
80
89
|
|
|
81
90
|
/**
|
|
82
91
|
* Draggable list item.
|
|
83
92
|
*/
|
|
84
|
-
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';
|
|
85
102
|
const { isItem, readonly, dragPreview, setState: setRootState } = useListContext(LIST_ITEM_NAME);
|
|
86
|
-
const
|
|
87
|
-
const dragHandleRef = useRef<
|
|
103
|
+
const rootRef = useRef<HTMLDivElement | null>(null);
|
|
104
|
+
const dragHandleRef = useRef<HTMLButtonElement | null>(null);
|
|
88
105
|
const [state, setState] = useState<ItemDragState>(idle);
|
|
106
|
+
|
|
89
107
|
useEffect(() => {
|
|
90
|
-
const element =
|
|
108
|
+
const element = rootRef.current;
|
|
91
109
|
invariant(element);
|
|
92
110
|
return combine(
|
|
93
111
|
//
|
|
@@ -142,6 +160,9 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
142
160
|
const closestEdge = extractClosestEdge(self.data);
|
|
143
161
|
setState({ type: 'is-dragging-over', closestEdge });
|
|
144
162
|
},
|
|
163
|
+
onDragLeave: () => {
|
|
164
|
+
setState(idle);
|
|
165
|
+
},
|
|
145
166
|
onDrag: ({ self }) => {
|
|
146
167
|
const closestEdge = extractClosestEdge(self.data);
|
|
147
168
|
setState((current) => {
|
|
@@ -151,9 +172,6 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
151
172
|
return { type: 'is-dragging-over', closestEdge };
|
|
152
173
|
});
|
|
153
174
|
},
|
|
154
|
-
onDragLeave: () => {
|
|
155
|
-
setState(idle);
|
|
156
|
-
},
|
|
157
175
|
onDrop: () => {
|
|
158
176
|
setState(idle);
|
|
159
177
|
},
|
|
@@ -163,19 +181,18 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
163
181
|
|
|
164
182
|
return (
|
|
165
183
|
<ListItemProvider item={item} dragHandleRef={dragHandleRef}>
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
</div>
|
|
184
|
+
<Comp
|
|
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}
|
|
190
|
+
>
|
|
191
|
+
{children}
|
|
192
|
+
</Comp>
|
|
193
|
+
{state.type === 'is-dragging-over' && state.closestEdge && (
|
|
194
|
+
<NaturalListItem.DropIndicator edge={state.closestEdge} />
|
|
195
|
+
)}
|
|
179
196
|
</ListItemProvider>
|
|
180
197
|
);
|
|
181
198
|
};
|
|
@@ -184,51 +201,66 @@ export const ListItem = <T extends ListItemRecord>({ children, classNames, item,
|
|
|
184
201
|
// List item components
|
|
185
202
|
//
|
|
186
203
|
|
|
187
|
-
export
|
|
188
|
-
|
|
189
|
-
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
|
190
|
-
({ classNames, icon, ...props }, forwardedRef) => {
|
|
191
|
-
return (
|
|
192
|
-
<button ref={forwardedRef} className={mx('flex items-center justify-center', classNames)} {...props}>
|
|
193
|
-
<Icon icon={icon} classNames='cursor-pointer' size={4} />
|
|
194
|
-
</button>
|
|
195
|
-
);
|
|
196
|
-
},
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
export const ListItemDeleteButton = ({
|
|
204
|
+
export const ListItemIconButton = ({
|
|
200
205
|
autoHide = true,
|
|
206
|
+
iconOnly = true,
|
|
207
|
+
variant = 'ghost',
|
|
201
208
|
classNames,
|
|
202
209
|
disabled,
|
|
203
|
-
icon = 'ph--x--regular',
|
|
204
210
|
...props
|
|
205
|
-
}:
|
|
206
|
-
const { state } = useListContext('
|
|
211
|
+
}: IconButtonProps & { autoHide?: boolean }) => {
|
|
212
|
+
const { state } = useListContext('ITEM_BUTTON');
|
|
207
213
|
const isDisabled = state.type !== 'idle' || disabled;
|
|
208
214
|
return (
|
|
209
215
|
<IconButton
|
|
210
|
-
|
|
216
|
+
{...props}
|
|
211
217
|
disabled={isDisabled}
|
|
218
|
+
iconOnly={iconOnly}
|
|
219
|
+
variant={variant}
|
|
212
220
|
classNames={[classNames, autoHide && disabled && 'hidden']}
|
|
213
|
-
{...props}
|
|
214
221
|
/>
|
|
215
222
|
);
|
|
216
223
|
};
|
|
217
224
|
|
|
218
|
-
|
|
225
|
+
// TODO(burdon): Generalize to action button.
|
|
226
|
+
export const ListItemDeleteButton = ({
|
|
219
227
|
autoHide = true,
|
|
220
228
|
classNames,
|
|
221
229
|
disabled,
|
|
230
|
+
icon = 'ph--x--regular',
|
|
231
|
+
label,
|
|
222
232
|
...props
|
|
223
|
-
}: IconButtonProps &
|
|
224
|
-
|
|
233
|
+
}: Partial<Pick<IconButtonProps, 'icon'>> &
|
|
234
|
+
Omit<IconButtonProps, 'icon' | 'label'> & { autoHide?: boolean; label?: string }) => {
|
|
235
|
+
const { state } = useListContext('DELETE_BUTTON');
|
|
225
236
|
const isDisabled = state.type !== 'idle' || disabled;
|
|
226
|
-
|
|
237
|
+
const { t } = useTranslation(osTranslations);
|
|
238
|
+
return (
|
|
239
|
+
<IconButton
|
|
240
|
+
{...props}
|
|
241
|
+
variant='ghost'
|
|
242
|
+
disabled={isDisabled}
|
|
243
|
+
icon={icon}
|
|
244
|
+
iconOnly
|
|
245
|
+
label={label ?? t('delete.label')}
|
|
246
|
+
classNames={[classNames, autoHide && disabled && 'hidden']}
|
|
247
|
+
/>
|
|
248
|
+
);
|
|
227
249
|
};
|
|
228
250
|
|
|
229
251
|
export const ListItemDragHandle = ({ disabled }: Pick<IconButtonProps, 'disabled'>) => {
|
|
230
252
|
const { dragHandleRef } = useListItemContext('DRAG_HANDLE');
|
|
231
|
-
|
|
253
|
+
const { t } = useTranslation(osTranslations);
|
|
254
|
+
return (
|
|
255
|
+
<IconButton
|
|
256
|
+
variant='ghost'
|
|
257
|
+
disabled={disabled}
|
|
258
|
+
icon='ph--dots-six-vertical--regular'
|
|
259
|
+
iconOnly
|
|
260
|
+
label={t('drag-handle.label')}
|
|
261
|
+
ref={dragHandleRef}
|
|
262
|
+
/>
|
|
263
|
+
);
|
|
232
264
|
};
|
|
233
265
|
|
|
234
266
|
export const ListItemDragPreview = <T extends ListItemRecord>({
|
|
@@ -241,7 +273,9 @@ export const ListItemDragPreview = <T extends ListItemRecord>({
|
|
|
241
273
|
};
|
|
242
274
|
|
|
243
275
|
export const ListItemWrapper = ({ classNames, children }: ThemedClassName<PropsWithChildren>) => (
|
|
244
|
-
<div className={mx('flex
|
|
276
|
+
<div role='none' className={mx('flex w-full gap-2', classNames)}>
|
|
277
|
+
{children}
|
|
278
|
+
</div>
|
|
245
279
|
);
|
|
246
280
|
|
|
247
281
|
export const ListItemTitle = ({
|
|
@@ -249,7 +283,7 @@ export const ListItemTitle = ({
|
|
|
249
283
|
children,
|
|
250
284
|
...props
|
|
251
285
|
}: ThemedClassName<PropsWithChildren<ComponentProps<'div'>>>) => (
|
|
252
|
-
<div className={mx('flex grow items-center truncate', classNames)} {...props}>
|
|
286
|
+
<div role='none' className={mx('flex grow items-center truncate', classNames)} {...props}>
|
|
253
287
|
{children}
|
|
254
288
|
</div>
|
|
255
289
|
);
|
|
@@ -2,13 +2,13 @@
|
|
|
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
|
|
|
11
|
-
import {
|
|
11
|
+
import { type ItemDragState, type ListItemRecord, idle } from './ListItem';
|
|
12
12
|
|
|
13
13
|
type ListContext<T extends ListItemRecord> = {
|
|
14
14
|
// TODO(burdon): Rename drag state.
|
|
@@ -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
|
),
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React, { useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { random } from '@dxos/random';
|
|
9
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
10
|
+
|
|
11
|
+
import { Listbox } from './Listbox';
|
|
12
|
+
|
|
13
|
+
random.seed(1234);
|
|
14
|
+
|
|
15
|
+
type StoryItem = { value: string; label: string };
|
|
16
|
+
|
|
17
|
+
const options: StoryItem[] = random.helpers.multiple(
|
|
18
|
+
() => ({ value: random.string.uuid(), label: random.commerce.productName() }) satisfies StoryItem,
|
|
19
|
+
{ count: 16 },
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const DefaultStory = () => {
|
|
23
|
+
const [selectedValue, setSelectedValue] = useState<string>();
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Listbox.Root value={selectedValue} onValueChange={setSelectedValue}>
|
|
27
|
+
{options.map((option) => (
|
|
28
|
+
<Listbox.Option key={option.value} value={option.value}>
|
|
29
|
+
<Listbox.OptionLabel>{option.label}</Listbox.OptionLabel>
|
|
30
|
+
<Listbox.OptionIndicator />
|
|
31
|
+
</Listbox.Option>
|
|
32
|
+
))}
|
|
33
|
+
</Listbox.Root>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const meta = {
|
|
38
|
+
title: 'ui/react-ui-list/Listbox',
|
|
39
|
+
component: Listbox.Root,
|
|
40
|
+
render: DefaultStory,
|
|
41
|
+
decorators: [withTheme(), withLayout({ layout: 'column', classNames: 'p-2' })],
|
|
42
|
+
} satisfies Meta<typeof Listbox.Root>;
|
|
43
|
+
|
|
44
|
+
export default meta;
|
|
45
|
+
|
|
46
|
+
type Story = StoryObj<typeof meta>;
|
|
47
|
+
|
|
48
|
+
export const Default: Story = {};
|