@dxos/react-ui-mosaic 0.3.3-main.3ce60d4
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/LICENSE +8 -0
- package/README.md +45 -0
- package/dist/lib/browser/index.mjs +596 -0
- package/dist/lib/browser/index.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -0
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/mosaic/Container.d.ts +65 -0
- package/dist/types/src/mosaic/Container.d.ts.map +1 -0
- package/dist/types/src/mosaic/Debug.d.ts +9 -0
- package/dist/types/src/mosaic/Debug.d.ts.map +1 -0
- package/dist/types/src/mosaic/DefaultComponent.d.ts +4 -0
- package/dist/types/src/mosaic/DefaultComponent.d.ts.map +1 -0
- package/dist/types/src/mosaic/DragOverlay.d.ts +11 -0
- package/dist/types/src/mosaic/DragOverlay.d.ts.map +1 -0
- package/dist/types/src/mosaic/Mosaic.d.ts +32 -0
- package/dist/types/src/mosaic/Mosaic.d.ts.map +1 -0
- package/dist/types/src/mosaic/Root.d.ts +24 -0
- package/dist/types/src/mosaic/Root.d.ts.map +1 -0
- package/dist/types/src/mosaic/SortableContext.d.ts +14 -0
- package/dist/types/src/mosaic/SortableContext.d.ts.map +1 -0
- package/dist/types/src/mosaic/Tile.d.ts +41 -0
- package/dist/types/src/mosaic/Tile.d.ts.map +1 -0
- package/dist/types/src/mosaic/hooks/index.d.ts +5 -0
- package/dist/types/src/mosaic/hooks/index.d.ts.map +1 -0
- package/dist/types/src/mosaic/hooks/useContainer.d.ts +4 -0
- package/dist/types/src/mosaic/hooks/useContainer.d.ts.map +1 -0
- package/dist/types/src/mosaic/hooks/useItemsWithPreview.d.ts +10 -0
- package/dist/types/src/mosaic/hooks/useItemsWithPreview.d.ts.map +1 -0
- package/dist/types/src/mosaic/hooks/useMosaic.d.ts +2 -0
- package/dist/types/src/mosaic/hooks/useMosaic.d.ts.map +1 -0
- package/dist/types/src/mosaic/hooks/useSortedItems.d.ts +6 -0
- package/dist/types/src/mosaic/hooks/useSortedItems.d.ts.map +1 -0
- package/dist/types/src/mosaic/index.d.ts +5 -0
- package/dist/types/src/mosaic/index.d.ts.map +1 -0
- package/dist/types/src/mosaic/types.d.ts +9 -0
- package/dist/types/src/mosaic/types.d.ts.map +1 -0
- package/dist/types/src/mosaic/util/data.d.ts +3 -0
- package/dist/types/src/mosaic/util/data.d.ts.map +1 -0
- package/dist/types/src/mosaic/util/index.d.ts +4 -0
- package/dist/types/src/mosaic/util/index.d.ts.map +1 -0
- package/dist/types/src/mosaic/util/path.d.ts +14 -0
- package/dist/types/src/mosaic/util/path.d.ts.map +1 -0
- package/dist/types/src/mosaic/util/path.test.d.ts +2 -0
- package/dist/types/src/mosaic/util/path.test.d.ts.map +1 -0
- package/dist/types/src/mosaic/util/styles.d.ts +3 -0
- package/dist/types/src/mosaic/util/styles.d.ts.map +1 -0
- package/dist/types/src/testing/Cards.d.ts +10 -0
- package/dist/types/src/testing/Cards.d.ts.map +1 -0
- package/dist/types/src/testing/decorators.d.ts +4 -0
- package/dist/types/src/testing/decorators.d.ts.map +1 -0
- package/dist/types/src/testing/generator.d.ts +43 -0
- package/dist/types/src/testing/generator.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +5 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/next-fractional-index.d.ts +5 -0
- package/dist/types/src/testing/next-fractional-index.d.ts.map +1 -0
- package/dist/types/src/views/Demo/Demo.stories.d.ts +13 -0
- package/dist/types/src/views/Demo/Demo.stories.d.ts.map +1 -0
- package/dist/types/src/views/Demo/Echo.stories.d.ts +12 -0
- package/dist/types/src/views/Demo/Echo.stories.d.ts.map +1 -0
- package/dist/types/src/views/Grid/Grid.d.ts +30 -0
- package/dist/types/src/views/Grid/Grid.d.ts.map +1 -0
- package/dist/types/src/views/Grid/Grid.stories.d.ts +13 -0
- package/dist/types/src/views/Grid/Grid.stories.d.ts.map +1 -0
- package/dist/types/src/views/Grid/index.d.ts +3 -0
- package/dist/types/src/views/Grid/index.d.ts.map +1 -0
- package/dist/types/src/views/Grid/layout.d.ts +27 -0
- package/dist/types/src/views/Grid/layout.d.ts.map +1 -0
- package/dist/types/src/views/Grid/testing.d.ts +10 -0
- package/dist/types/src/views/Grid/testing.d.ts.map +1 -0
- package/dist/types/src/views/Kanban/Echo.stories.d.ts +18 -0
- package/dist/types/src/views/Kanban/Echo.stories.d.ts.map +1 -0
- package/dist/types/src/views/Kanban/Kanban.d.ts +11 -0
- package/dist/types/src/views/Kanban/Kanban.d.ts.map +1 -0
- package/dist/types/src/views/Kanban/Kanban.stories.d.ts +28 -0
- package/dist/types/src/views/Kanban/Kanban.stories.d.ts.map +1 -0
- package/dist/types/src/views/Kanban/index.d.ts +2 -0
- package/dist/types/src/views/Kanban/index.d.ts.map +1 -0
- package/dist/types/src/views/Kanban/testing.d.ts +20 -0
- package/dist/types/src/views/Kanban/testing.d.ts.map +1 -0
- package/dist/types/src/views/Stack/Columns.stories.d.ts +34 -0
- package/dist/types/src/views/Stack/Columns.stories.d.ts.map +1 -0
- package/dist/types/src/views/Stack/Stack.d.ts +9 -0
- package/dist/types/src/views/Stack/Stack.d.ts.map +1 -0
- package/dist/types/src/views/Stack/Stack.stories.d.ts +39 -0
- package/dist/types/src/views/Stack/Stack.stories.d.ts.map +1 -0
- package/dist/types/src/views/Stack/index.d.ts +2 -0
- package/dist/types/src/views/Stack/index.d.ts.map +1 -0
- package/dist/types/src/views/Stack/testing.d.ts +9 -0
- package/dist/types/src/views/Stack/testing.d.ts.map +1 -0
- package/dist/types/src/views/Tree/Graph.stories.d.ts +21 -0
- package/dist/types/src/views/Tree/Graph.stories.d.ts.map +1 -0
- package/dist/types/src/views/Tree/Tree.d.ts +13 -0
- package/dist/types/src/views/Tree/Tree.d.ts.map +1 -0
- package/dist/types/src/views/Tree/Tree.stories.d.ts +41 -0
- package/dist/types/src/views/Tree/Tree.stories.d.ts.map +1 -0
- package/dist/types/src/views/Tree/index.d.ts +2 -0
- package/dist/types/src/views/Tree/index.d.ts.map +1 -0
- package/dist/types/src/views/Tree/testing.d.ts +15 -0
- package/dist/types/src/views/Tree/testing.d.ts.map +1 -0
- package/dist/types/src/views/index.d.ts +5 -0
- package/dist/types/src/views/index.d.ts.map +1 -0
- package/package.json +67 -0
- package/src/index.ts +5 -0
- package/src/mosaic/Container.tsx +137 -0
- package/src/mosaic/Debug.tsx +24 -0
- package/src/mosaic/DefaultComponent.tsx +26 -0
- package/src/mosaic/DragOverlay.tsx +136 -0
- package/src/mosaic/Mosaic.ts +84 -0
- package/src/mosaic/Root.tsx +255 -0
- package/src/mosaic/SortableContext.tsx +60 -0
- package/src/mosaic/Tile.tsx +189 -0
- package/src/mosaic/hooks/index.ts +8 -0
- package/src/mosaic/hooks/useContainer.ts +16 -0
- package/src/mosaic/hooks/useItemsWithPreview.ts +64 -0
- package/src/mosaic/hooks/useMosaic.ts +11 -0
- package/src/mosaic/hooks/useSortedItems.ts +14 -0
- package/src/mosaic/index.ts +9 -0
- package/src/mosaic/types.ts +13 -0
- package/src/mosaic/util/data.ts +19 -0
- package/src/mosaic/util/index.ts +7 -0
- package/src/mosaic/util/path.test.ts +26 -0
- package/src/mosaic/util/path.ts +21 -0
- package/src/mosaic/util/styles.ts +10 -0
- package/src/testing/Cards.tsx +88 -0
- package/src/testing/decorators.tsx +20 -0
- package/src/testing/generator.ts +135 -0
- package/src/testing/index.ts +9 -0
- package/src/testing/next-fractional-index.ts +36 -0
- package/src/views/Demo/Demo.stories.tsx +79 -0
- package/src/views/Demo/Echo.stories.tsx +74 -0
- package/src/views/Grid/Grid.stories.tsx +48 -0
- package/src/views/Grid/Grid.tsx +248 -0
- package/src/views/Grid/index.ts +7 -0
- package/src/views/Grid/layout.ts +42 -0
- package/src/views/Grid/testing.tsx +91 -0
- package/src/views/Kanban/Echo.stories.tsx +67 -0
- package/src/views/Kanban/Kanban.stories.tsx +48 -0
- package/src/views/Kanban/Kanban.tsx +139 -0
- package/src/views/Kanban/index.ts +5 -0
- package/src/views/Kanban/testing.tsx +231 -0
- package/src/views/Stack/Columns.stories.tsx +61 -0
- package/src/views/Stack/Stack.stories.tsx +62 -0
- package/src/views/Stack/Stack.tsx +58 -0
- package/src/views/Stack/index.ts +5 -0
- package/src/views/Stack/testing.tsx +77 -0
- package/src/views/Tree/Graph.stories.tsx +36 -0
- package/src/views/Tree/Tree.stories.tsx +92 -0
- package/src/views/Tree/Tree.tsx +128 -0
- package/src/views/Tree/index.ts +5 -0
- package/src/views/Tree/testing.tsx +156 -0
- package/src/views/index.ts +8 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Plus } from '@phosphor-icons/react';
|
|
6
|
+
import React, { type FC, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { invariant } from '@dxos/invariant';
|
|
9
|
+
import { type PublicKey } from '@dxos/react-client';
|
|
10
|
+
import { Schema, type TypedObject, useQuery, useSpace } from '@dxos/react-client/echo';
|
|
11
|
+
import { Button, Select, Toolbar } from '@dxos/react-ui';
|
|
12
|
+
import { arrayMove } from '@dxos/util';
|
|
13
|
+
|
|
14
|
+
import { Kanban, type KanbanColumn, type KanbanProps } from './Kanban';
|
|
15
|
+
import { Mosaic, type MosaicDropEvent, Path, swapItems } from '../../mosaic';
|
|
16
|
+
import { TestObjectGenerator, SimpleCard, Priority, Status, type TestItem } from '../../testing';
|
|
17
|
+
|
|
18
|
+
const createKanban = ({ types, columns = 3 }: { types?: string[]; columns?: number }) => {
|
|
19
|
+
const generator = new TestObjectGenerator({ types });
|
|
20
|
+
return Array.from({ length: columns }).map((_, i) => ({
|
|
21
|
+
id: `column-${i}`,
|
|
22
|
+
title: `Column ${i}`,
|
|
23
|
+
items: generator.createObjects({ length: 3 + columns - i }),
|
|
24
|
+
}));
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type DemoKanbanProps = Omit<KanbanProps, 'columns'> & {
|
|
28
|
+
types?: string[];
|
|
29
|
+
count?: number;
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const DemoKanban: FC<DemoKanbanProps> = ({
|
|
34
|
+
id = 'kanban',
|
|
35
|
+
Component = Mosaic.DefaultComponent,
|
|
36
|
+
types = ['document'],
|
|
37
|
+
count = 3,
|
|
38
|
+
debug = false,
|
|
39
|
+
}) => {
|
|
40
|
+
const [columns, setColumns] = useState<KanbanColumn<TestItem>[]>(() => createKanban({ types, columns: count }));
|
|
41
|
+
|
|
42
|
+
// const handleDelete = (id: string) => {
|
|
43
|
+
// setItems1((cards) => cards.filter((card) => card.id !== id));
|
|
44
|
+
// };
|
|
45
|
+
|
|
46
|
+
// TODO(burdon): Reconcile with DemoKanban.
|
|
47
|
+
const handleDrop = ({ active, over }: MosaicDropEvent<number>) => {
|
|
48
|
+
// Reorder columns.
|
|
49
|
+
// TODO(burdon): Buggy dragging empty column.
|
|
50
|
+
if (active.path === Path.create(id, active.item.id)) {
|
|
51
|
+
return setColumns((columns) => [...swapItems(columns, active.item, over.item)]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return setColumns((columns) =>
|
|
55
|
+
columns.map((column) => {
|
|
56
|
+
const items = [...column.items];
|
|
57
|
+
if (Path.last(Path.parent(active.path)) === column.id) {
|
|
58
|
+
// Remove card from current postion.
|
|
59
|
+
invariant(active.position !== undefined);
|
|
60
|
+
items.splice(active.position, 1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (over.path === Path.create(id, column.id)) {
|
|
64
|
+
// Move card to end of column.
|
|
65
|
+
items.push(active.item as TestItem);
|
|
66
|
+
} else if (Path.hasDescendent(id, over.path) && Path.last(Path.parent(over.path)) === column.id) {
|
|
67
|
+
// Move card within or between columns.
|
|
68
|
+
const position = over.position ?? items.length;
|
|
69
|
+
items.splice(position, 0, active.item as TestItem);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { ...column, items };
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return <Kanban id={id} columns={columns} Component={Component} onDrop={handleDrop} debug={debug} />;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// TODO(burdon): Compute this?
|
|
81
|
+
export const columnValues: { [property: string]: any[] } = {
|
|
82
|
+
status: ['unknown', ...Status],
|
|
83
|
+
priority: ['unknown', ...Priority],
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const EchoKanban = ({
|
|
87
|
+
id,
|
|
88
|
+
spaceKey,
|
|
89
|
+
generator,
|
|
90
|
+
debug,
|
|
91
|
+
}: {
|
|
92
|
+
id: string;
|
|
93
|
+
spaceKey: PublicKey;
|
|
94
|
+
generator: TestObjectGenerator;
|
|
95
|
+
debug?: boolean;
|
|
96
|
+
}) => {
|
|
97
|
+
const space = useSpace(spaceKey);
|
|
98
|
+
// TODO(burdon): Decorator is not re-run schema is empty when returning to story after first run. Different kanban id.
|
|
99
|
+
const [kanban] = useQuery(space, { type: 'kanban' });
|
|
100
|
+
|
|
101
|
+
const getProperty = (property: string) => {
|
|
102
|
+
const schemaProperty = kanban.schema.props.find((prop: any) => prop.id === kanban.columnProp);
|
|
103
|
+
if (schemaProperty.type === Schema.PropType.NUMBER) {
|
|
104
|
+
return parseInt(property);
|
|
105
|
+
} else {
|
|
106
|
+
return property;
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const objects = useQuery<TypedObject>(space, (object) => object.__schema === kanban.schema, {}, [kanban.schema]);
|
|
111
|
+
const columns: KanbanColumn<TypedObject>[] = kanban.columnValues.map((value: string) => {
|
|
112
|
+
const objectPosition = kanban.objectPosition[value] ?? [];
|
|
113
|
+
const items =
|
|
114
|
+
objectPosition.length > 0
|
|
115
|
+
? objectPosition.map((id: string) => objects.find((object) => object.id === id))
|
|
116
|
+
: objects;
|
|
117
|
+
|
|
118
|
+
// TODO(burdon): Special case for 'unknown' values?
|
|
119
|
+
return {
|
|
120
|
+
id: getProperty(value),
|
|
121
|
+
title: value,
|
|
122
|
+
items: items.filter((object: TypedObject) => object[kanban.columnProp] === value),
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// TODO(burdon): Why ref?
|
|
127
|
+
const columnsRef = useRef<KanbanColumn<TypedObject>[]>([]);
|
|
128
|
+
columnsRef.current = columns;
|
|
129
|
+
|
|
130
|
+
// TODO(burdon): Called for each object generated (should batch?)
|
|
131
|
+
// console.log(JSON.stringify(objects[0]));
|
|
132
|
+
|
|
133
|
+
// TODO(burdon): Should views maintain an positional index map per property (to enable switching?)
|
|
134
|
+
// TODO(burdon): Is the current index map making use of ECHO object CRDT? Need multi-peer test in this suite.
|
|
135
|
+
const handleSetProperty = (property: string) => {
|
|
136
|
+
kanban.columnProp = property;
|
|
137
|
+
kanban.columnValues = columnValues[kanban.columnProp];
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// TODO(burdon): Factor out util.
|
|
141
|
+
const getOrder = (kanban: TypedObject, property: string) => {
|
|
142
|
+
// TODO(wittjosiah): Columns is stale here.
|
|
143
|
+
return (
|
|
144
|
+
kanban.objectPosition[property] ??
|
|
145
|
+
columnsRef.current.find((column) => column.id === getProperty(property))?.items.map((item) => item.id) ??
|
|
146
|
+
[]
|
|
147
|
+
);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// TODO(burdon): Currently broken: factor out with demo story.
|
|
151
|
+
const handleDrop = ({ active, over }: MosaicDropEvent<number>) => {
|
|
152
|
+
// Reorder columns.
|
|
153
|
+
// TODO(burdon): Factor out util.
|
|
154
|
+
if (active.path === Path.create(id, active.item.id)) {
|
|
155
|
+
// Reorder columns.
|
|
156
|
+
const fromIndex = kanban.columnValues.findIndex((value: string) => value === active.item.id);
|
|
157
|
+
const toIndex = kanban.columnValues.findIndex((value: string) => value === over.item.id);
|
|
158
|
+
fromIndex !== -1 && toIndex !== -1 && arrayMove(kanban.columnValues, fromIndex, toIndex);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (Path.hasDescendent(id, active.path)) {
|
|
163
|
+
const activeProperty = Path.last(Path.parent(active.path));
|
|
164
|
+
const overProperty = Path.length(over.path) > 2 ? Path.last(Path.parent(over.path)) : Path.last(over.path);
|
|
165
|
+
invariant(activeProperty);
|
|
166
|
+
invariant(overProperty);
|
|
167
|
+
|
|
168
|
+
// Update property.
|
|
169
|
+
(active.item as TypedObject)[kanban.columnProp] = getProperty(overProperty);
|
|
170
|
+
|
|
171
|
+
// Update active column order.
|
|
172
|
+
const activeOrder = getOrder(kanban, activeProperty);
|
|
173
|
+
activeOrder.length > 0 && activeOrder.splice(active.position, 1);
|
|
174
|
+
kanban.objectPosition[activeProperty] = activeOrder;
|
|
175
|
+
|
|
176
|
+
// Update over column order.
|
|
177
|
+
const overOrder = getOrder(kanban, overProperty);
|
|
178
|
+
overOrder.length > 0 && Path.length(over.path) === Path.length(active.path)
|
|
179
|
+
? overOrder.splice(over.position, 0, active.item.id)
|
|
180
|
+
: overOrder.push(active.item.id);
|
|
181
|
+
kanban.objectPosition[overProperty] = overOrder;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const handleAddData = () => {
|
|
186
|
+
const object = generator.createObject({ types: ['project'] });
|
|
187
|
+
space?.db.add(object);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className='flex flex-col grow'>
|
|
192
|
+
<Toolbar.Root classNames='p-2'>
|
|
193
|
+
<PropertySelector
|
|
194
|
+
property={kanban.columnProp}
|
|
195
|
+
properties={Object.keys(columnValues)}
|
|
196
|
+
onSetProperty={handleSetProperty}
|
|
197
|
+
/>
|
|
198
|
+
<Button title='Add Data' onClick={handleAddData}>
|
|
199
|
+
<Plus />
|
|
200
|
+
</Button>
|
|
201
|
+
</Toolbar.Root>
|
|
202
|
+
<Kanban id={id} columns={columns} Component={SimpleCard} onDrop={handleDrop} debug={debug} />
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const PropertySelector: FC<{ property: string; properties: string[]; onSetProperty: (property: string) => void }> = ({
|
|
208
|
+
property,
|
|
209
|
+
properties,
|
|
210
|
+
onSetProperty,
|
|
211
|
+
}) => {
|
|
212
|
+
return (
|
|
213
|
+
<Select.Root value={property} onValueChange={onSetProperty}>
|
|
214
|
+
<Select.TriggerButton placeholder='Select value' />
|
|
215
|
+
<Select.Portal>
|
|
216
|
+
<Select.Content>
|
|
217
|
+
<Select.ScrollUpButton />
|
|
218
|
+
<Select.Viewport>
|
|
219
|
+
{properties.map((property) => (
|
|
220
|
+
<Select.Option key={property} value={property}>
|
|
221
|
+
{property}
|
|
222
|
+
</Select.Option>
|
|
223
|
+
))}
|
|
224
|
+
</Select.Viewport>
|
|
225
|
+
<Select.ScrollDownButton />
|
|
226
|
+
<Select.Arrow />
|
|
227
|
+
</Select.Content>
|
|
228
|
+
</Select.Portal>
|
|
229
|
+
</Select.Root>
|
|
230
|
+
);
|
|
231
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
7
|
+
import { faker } from '@faker-js/faker';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { Stack } from './Stack';
|
|
11
|
+
import { DemoStack, type DemoStackProps } from './testing';
|
|
12
|
+
import { Mosaic } from '../../mosaic';
|
|
13
|
+
import { FullscreenDecorator, SimpleCard } from '../../testing';
|
|
14
|
+
|
|
15
|
+
faker.seed(3);
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
title: 'Views/Stack/Columns',
|
|
19
|
+
component: Stack,
|
|
20
|
+
render: (args: DemoStackProps) => {
|
|
21
|
+
return (
|
|
22
|
+
<Mosaic.Root debug={args.debug}>
|
|
23
|
+
<div className='flex grow justify-center p-4'>
|
|
24
|
+
<div className='grid grid-cols-2 gap-4'>
|
|
25
|
+
<DemoStack {...args} id='stack-1' />
|
|
26
|
+
<DemoStack {...args} id='stack-2' />
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<Mosaic.DragOverlay debug={args.debug} />
|
|
30
|
+
</Mosaic.Root>
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
decorators: [FullscreenDecorator()],
|
|
34
|
+
parameters: {
|
|
35
|
+
layout: 'fullscreen',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const Adopt = {
|
|
40
|
+
args: {
|
|
41
|
+
Component: SimpleCard,
|
|
42
|
+
debug: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// TODO(burdon): Should not hide from source while dragging.
|
|
47
|
+
export const Copy = {
|
|
48
|
+
args: {
|
|
49
|
+
Component: SimpleCard,
|
|
50
|
+
debug: true,
|
|
51
|
+
operation: 'copy',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Reject = {
|
|
56
|
+
args: {
|
|
57
|
+
Component: SimpleCard,
|
|
58
|
+
debug: true,
|
|
59
|
+
operation: 'reject',
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
7
|
+
import { faker } from '@faker-js/faker';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { Stack, type StackProps } from './Stack';
|
|
11
|
+
import { DemoStack } from './testing';
|
|
12
|
+
import { Mosaic } from '../../mosaic';
|
|
13
|
+
import { ComplexCard, FullscreenDecorator, SimpleCard } from '../../testing';
|
|
14
|
+
|
|
15
|
+
faker.seed(3);
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
component: Stack,
|
|
19
|
+
render: (
|
|
20
|
+
args: StackProps & {
|
|
21
|
+
types: string[];
|
|
22
|
+
count: number;
|
|
23
|
+
},
|
|
24
|
+
) => {
|
|
25
|
+
return (
|
|
26
|
+
<Mosaic.Root debug={args.debug}>
|
|
27
|
+
<Mosaic.DragOverlay />
|
|
28
|
+
<DemoStack {...args} />
|
|
29
|
+
</Mosaic.Root>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
decorators: [FullscreenDecorator()],
|
|
33
|
+
parameters: {
|
|
34
|
+
layout: 'fullscreen',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const Default = {
|
|
39
|
+
args: {
|
|
40
|
+
Component: SimpleCard,
|
|
41
|
+
count: 8,
|
|
42
|
+
debug: true,
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Horizontal = {
|
|
47
|
+
args: {
|
|
48
|
+
Component: ComplexCard,
|
|
49
|
+
direction: 'horizontal',
|
|
50
|
+
count: 8,
|
|
51
|
+
debug: true,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const Complex = {
|
|
56
|
+
args: {
|
|
57
|
+
Component: ComplexCard,
|
|
58
|
+
types: ['document', 'image'],
|
|
59
|
+
count: 8,
|
|
60
|
+
debug: true,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
8
|
+
|
|
9
|
+
import { Mosaic, type MosaicContainerProps, type MosaicDataItem, useItemsWithPreview } from '../../mosaic';
|
|
10
|
+
|
|
11
|
+
export type Direction = 'horizontal' | 'vertical';
|
|
12
|
+
|
|
13
|
+
export type StackProps<TData extends MosaicDataItem = MosaicDataItem> = MosaicContainerProps<TData, number> & {
|
|
14
|
+
items?: TData[];
|
|
15
|
+
direction?: Direction;
|
|
16
|
+
debug?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// TODO(burdon): Make generic (and forwardRef).
|
|
20
|
+
export const Stack = ({
|
|
21
|
+
id,
|
|
22
|
+
Component = Mosaic.DefaultComponent,
|
|
23
|
+
onOver,
|
|
24
|
+
onDrop,
|
|
25
|
+
items = [],
|
|
26
|
+
direction = 'vertical',
|
|
27
|
+
debug,
|
|
28
|
+
}: StackProps) => {
|
|
29
|
+
const itemsWithPreview = useItemsWithPreview({ path: id, items });
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className={mx('flex overflow-hidden', direction === 'vertical' && 'w-[300px]')}>
|
|
33
|
+
<div
|
|
34
|
+
className={mx('flex flex-col w-full my-1', direction === 'vertical' ? 'overflow-y-auto' : 'overflow-x-auto')}
|
|
35
|
+
>
|
|
36
|
+
<Mosaic.Container {...{ id, Component, onOver, onDrop }}>
|
|
37
|
+
<Mosaic.SortableContext items={itemsWithPreview} direction={direction}>
|
|
38
|
+
{itemsWithPreview.map((item, index) => (
|
|
39
|
+
<Mosaic.SortableTile
|
|
40
|
+
key={item.id}
|
|
41
|
+
item={item}
|
|
42
|
+
path={id}
|
|
43
|
+
position={index}
|
|
44
|
+
className='m-1'
|
|
45
|
+
// debug={debug}
|
|
46
|
+
/>
|
|
47
|
+
))}
|
|
48
|
+
</Mosaic.SortableContext>
|
|
49
|
+
</Mosaic.Container>
|
|
50
|
+
{debug && (
|
|
51
|
+
<div className='grow'>
|
|
52
|
+
<Mosaic.Debug data={{ id, items: itemsWithPreview.length }} />
|
|
53
|
+
</div>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { useState } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Stack, type StackProps } from './Stack';
|
|
8
|
+
import {
|
|
9
|
+
type MosaicDataItem,
|
|
10
|
+
type MosaicDropEvent,
|
|
11
|
+
type MosaicMoveEvent,
|
|
12
|
+
type MosaicOperation,
|
|
13
|
+
Path,
|
|
14
|
+
} from '../../mosaic';
|
|
15
|
+
import { TestObjectGenerator } from '../../testing';
|
|
16
|
+
|
|
17
|
+
export type DemoStackProps = StackProps & {
|
|
18
|
+
types?: string[];
|
|
19
|
+
count?: number;
|
|
20
|
+
operation?: MosaicOperation;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const DemoStack = ({
|
|
24
|
+
id = 'stack',
|
|
25
|
+
Component,
|
|
26
|
+
types,
|
|
27
|
+
count = 8,
|
|
28
|
+
direction = 'vertical',
|
|
29
|
+
operation = 'adopt',
|
|
30
|
+
debug,
|
|
31
|
+
}: DemoStackProps) => {
|
|
32
|
+
const [items, setItems] = useState<MosaicDataItem[]>(() => {
|
|
33
|
+
const generator = new TestObjectGenerator({ types });
|
|
34
|
+
return generator.createObjects({ length: count });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const handleOver = ({ active }: MosaicMoveEvent<number>) => {
|
|
38
|
+
if (operation === 'reject') {
|
|
39
|
+
return 'reject';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const exists = items.findIndex((item) => item.id === active.item.id) >= 0;
|
|
43
|
+
|
|
44
|
+
if (!exists) {
|
|
45
|
+
return operation;
|
|
46
|
+
} else {
|
|
47
|
+
return 'reject';
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleDrop = ({ operation, active, over }: MosaicDropEvent<number>) => {
|
|
52
|
+
setItems((items) => {
|
|
53
|
+
if (
|
|
54
|
+
active.path === Path.create(id, active.item.id) &&
|
|
55
|
+
(operation !== 'copy' || over.path === Path.create(id, over.item.id))
|
|
56
|
+
) {
|
|
57
|
+
items.splice(active.position!, 1);
|
|
58
|
+
}
|
|
59
|
+
if (over.item && over.path === Path.create(id, over.item.id)) {
|
|
60
|
+
items.splice(over.position!, 0, active.item);
|
|
61
|
+
}
|
|
62
|
+
return [...items];
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Stack
|
|
68
|
+
id={id}
|
|
69
|
+
Component={Component}
|
|
70
|
+
onOver={handleOver}
|
|
71
|
+
onDrop={handleDrop}
|
|
72
|
+
items={items}
|
|
73
|
+
direction={direction}
|
|
74
|
+
debug={debug}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
7
|
+
import { faker } from '@faker-js/faker';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { Tree } from './Tree';
|
|
11
|
+
import { GraphTree } from './testing';
|
|
12
|
+
import { Mosaic } from '../../mosaic';
|
|
13
|
+
import { FullscreenDecorator } from '../../testing';
|
|
14
|
+
|
|
15
|
+
faker.seed(3);
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
title: 'Views/Tree',
|
|
19
|
+
component: Tree,
|
|
20
|
+
render: ({ id = 'tree', debug }: { id: string; debug: boolean }) => {
|
|
21
|
+
return (
|
|
22
|
+
<Mosaic.Root debug={debug}>
|
|
23
|
+
<Mosaic.DragOverlay />
|
|
24
|
+
<GraphTree id={id} debug={debug} />
|
|
25
|
+
</Mosaic.Root>
|
|
26
|
+
);
|
|
27
|
+
},
|
|
28
|
+
decorators: [FullscreenDecorator()],
|
|
29
|
+
parameters: {
|
|
30
|
+
layout: 'fullscreen',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const Graph = {
|
|
35
|
+
args: { debug: true },
|
|
36
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxosTheme';
|
|
6
|
+
|
|
7
|
+
import { faker } from '@faker-js/faker';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
import { Tree } from './Tree';
|
|
11
|
+
import { DemoTree, type DemoTreeProps } from './testing';
|
|
12
|
+
import { Mosaic } from '../../mosaic';
|
|
13
|
+
import { FullscreenDecorator, TestObjectGenerator } from '../../testing';
|
|
14
|
+
|
|
15
|
+
faker.seed(3);
|
|
16
|
+
const generator = new TestObjectGenerator({ types: ['document', 'image'] });
|
|
17
|
+
|
|
18
|
+
const count = 5;
|
|
19
|
+
|
|
20
|
+
// TODO(burdon): Move to testing.
|
|
21
|
+
const testItems1 = Array.from({ length: count }).map((_, i) => ({
|
|
22
|
+
id: `branch-${i}`,
|
|
23
|
+
label: `Branch ${i}`,
|
|
24
|
+
children: Array.from({ length: i === count - 1 ? 0 : 5 - i }).map(() => {
|
|
25
|
+
const item = generator.createObject();
|
|
26
|
+
return {
|
|
27
|
+
id: item.id,
|
|
28
|
+
label: item.title, // TODO(burdon): Lens.
|
|
29
|
+
children: [],
|
|
30
|
+
};
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const testItems2 = [
|
|
35
|
+
{
|
|
36
|
+
id: 'Home',
|
|
37
|
+
children: [],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 'Collections',
|
|
41
|
+
children: [
|
|
42
|
+
{ id: 'Spring', children: [] },
|
|
43
|
+
{ id: 'Summer', children: [] },
|
|
44
|
+
{ id: 'Fall', children: [] },
|
|
45
|
+
{ id: 'Winter', children: [] },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'About Us',
|
|
50
|
+
children: [],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'My Account',
|
|
54
|
+
children: [
|
|
55
|
+
{ id: 'Addresses', children: [] },
|
|
56
|
+
{
|
|
57
|
+
id: 'Order History',
|
|
58
|
+
children: [
|
|
59
|
+
{ id: 'Order 1', children: [] },
|
|
60
|
+
{ id: 'Order 2', children: [] },
|
|
61
|
+
{ id: 'Order 3', children: [] },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{ id: 'Payment Methods', children: [] },
|
|
65
|
+
{ id: 'Account Details', children: [] },
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
export default {
|
|
71
|
+
component: Tree,
|
|
72
|
+
render: (args: DemoTreeProps) => {
|
|
73
|
+
return (
|
|
74
|
+
<Mosaic.Root debug={args.debug}>
|
|
75
|
+
<Mosaic.DragOverlay />
|
|
76
|
+
<DemoTree {...args} />
|
|
77
|
+
</Mosaic.Root>
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
decorators: [FullscreenDecorator()],
|
|
81
|
+
parameters: {
|
|
82
|
+
layout: 'fullscreen',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const Default = {
|
|
87
|
+
args: { initialItems: testItems1, debug: true },
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const Deep = {
|
|
91
|
+
args: { initialItems: testItems2 },
|
|
92
|
+
};
|