@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
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dxos/react-ui-mosaic",
|
|
3
|
+
"version": "0.3.3-main.3ce60d4",
|
|
4
|
+
"description": "An intermediate interface for drag-and-drop interactions.",
|
|
5
|
+
"homepage": "https://dxos.org",
|
|
6
|
+
"bugs": "https://github.com/dxos/dxos/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "DXOS.org",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/lib/browser/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"types": "./dist/types/src/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@dnd-kit/core": "^6.0.5",
|
|
19
|
+
"@dnd-kit/modifiers": "^6.0.0",
|
|
20
|
+
"@dnd-kit/sortable": "^7.0.1",
|
|
21
|
+
"@dnd-kit/utilities": "^3.2.0",
|
|
22
|
+
"@preact/signals-core": "^1.5.0",
|
|
23
|
+
"@radix-ui/react-compose-refs": "^1.0.0",
|
|
24
|
+
"@tldraw/indices": "2.0.0-alpha.14",
|
|
25
|
+
"deepsignal": "^1.3.6",
|
|
26
|
+
"lodash.defaultsdeep": "^4.6.1",
|
|
27
|
+
"lodash.pick": "^4.4.0",
|
|
28
|
+
"react-resize-detector": "^7.1.2",
|
|
29
|
+
"@dxos/debug": "0.3.3-main.3ce60d4",
|
|
30
|
+
"@dxos/invariant": "0.3.3-main.3ce60d4",
|
|
31
|
+
"@dxos/keys": "0.3.3-main.3ce60d4",
|
|
32
|
+
"@dxos/react-ui": "0.3.3-main.3ce60d4",
|
|
33
|
+
"@dxos/react-ui-theme": "0.3.3-main.3ce60d4",
|
|
34
|
+
"@dxos/react-ui-types": "0.3.3-main.3ce60d4",
|
|
35
|
+
"@dxos/util": "0.3.3-main.3ce60d4"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@faker-js/faker": "^8.0.2",
|
|
39
|
+
"@phosphor-icons/react": "^2.0.5",
|
|
40
|
+
"@types/d3": "^7.1.0",
|
|
41
|
+
"@types/lodash.defaultsdeep": "^4.6.6",
|
|
42
|
+
"@types/lodash.pick": "^4.4.7",
|
|
43
|
+
"@types/react": "^18.0.21",
|
|
44
|
+
"@types/react-dom": "^18.0.6",
|
|
45
|
+
"chromatic": "^6.17.0",
|
|
46
|
+
"d3": "^7.4.4",
|
|
47
|
+
"react": "^18.2.0",
|
|
48
|
+
"react-dom": "^18.2.0",
|
|
49
|
+
"vite": "^4.3.9",
|
|
50
|
+
"vite-plugin-turbosnap": "^1.0.2",
|
|
51
|
+
"@braneframe/plugin-graph": "0.3.3-main.3ce60d4",
|
|
52
|
+
"@dxos/echo-schema": "0.3.3-main.3ce60d4",
|
|
53
|
+
"@dxos/gem-core": "0.3.3-main.3ce60d4",
|
|
54
|
+
"@dxos/react-client": "0.3.3-main.3ce60d4"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@phosphor-icons/react": "^2.0.5",
|
|
58
|
+
"react": "^18.2.0",
|
|
59
|
+
"react-dom": "^18.2.0"
|
|
60
|
+
},
|
|
61
|
+
"publishConfig": {
|
|
62
|
+
"access": "public"
|
|
63
|
+
},
|
|
64
|
+
"scripts": {
|
|
65
|
+
"chromatic": "true"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Modifier } from '@dnd-kit/core';
|
|
6
|
+
import React, {
|
|
7
|
+
createContext,
|
|
8
|
+
useEffect,
|
|
9
|
+
type CSSProperties,
|
|
10
|
+
type HTMLAttributes,
|
|
11
|
+
type PropsWithChildren,
|
|
12
|
+
} from 'react';
|
|
13
|
+
|
|
14
|
+
import { type MosaicTileComponent } from './Tile';
|
|
15
|
+
import { useMosaic } from './hooks';
|
|
16
|
+
import { type MosaicDataItem, type MosaicDraggedItem } from './types';
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_TRANSITION = 200;
|
|
19
|
+
|
|
20
|
+
export type MosaicTileOverlayProps = {
|
|
21
|
+
grow?: boolean;
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// TODO(wittjosiah): Add delete.
|
|
26
|
+
export type MosaicOperation = 'adopt' | 'copy' | 'rearrange' | 'reject';
|
|
27
|
+
|
|
28
|
+
export type MosaicMoveEvent<TPosition = unknown> = {
|
|
29
|
+
active: MosaicDraggedItem<TPosition>;
|
|
30
|
+
over: MosaicDraggedItem<TPosition>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type MosaicDropEvent<TPosition = unknown> = MosaicMoveEvent<TPosition> & {
|
|
34
|
+
operation: MosaicOperation;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type MosaicCompareDataItem = Parameters<typeof Array.prototype.sort>[0];
|
|
38
|
+
|
|
39
|
+
export type MosaicContainerProps<TData extends MosaicDataItem = MosaicDataItem, TPosition = unknown> = Pick<
|
|
40
|
+
HTMLAttributes<HTMLDivElement>,
|
|
41
|
+
'className'
|
|
42
|
+
> &
|
|
43
|
+
PropsWithChildren<{
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
// TODO(wittjosiah): Don't expose externally.
|
|
47
|
+
debug?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default component used to render tiles.
|
|
51
|
+
*/
|
|
52
|
+
Component?: MosaicTileComponent<TData, any>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Length of transition when moving tiles in milliseconds.
|
|
56
|
+
*
|
|
57
|
+
* @default 200
|
|
58
|
+
*/
|
|
59
|
+
transitionDuration?: number;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Adapter to transform properties while dragging (e.g., constraint axes).
|
|
63
|
+
* https://github.com/clauderic/dnd-kit/blob/master/packages/core/src/modifiers/types.ts
|
|
64
|
+
*/
|
|
65
|
+
modifier?: (activeItem: MosaicDraggedItem, ...modifierArgs: Parameters<Modifier>) => ReturnType<Modifier>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Property overrides for the default overlay.
|
|
69
|
+
*/
|
|
70
|
+
getOverlayProps?: () => MosaicTileOverlayProps;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Style property overrides for the default overlay.
|
|
74
|
+
*/
|
|
75
|
+
getOverlayStyle?: () => CSSProperties;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Called when a tile is dragged over the container.
|
|
79
|
+
* Returns true if the tile can be dropped.
|
|
80
|
+
*/
|
|
81
|
+
onOver?: (event: MosaicMoveEvent<TPosition>) => MosaicOperation;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Called when a tile is dropped on the container.
|
|
85
|
+
*/
|
|
86
|
+
onDrop?: (event: MosaicDropEvent<TPosition>) => void;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Used to sort items within the container.
|
|
90
|
+
*/
|
|
91
|
+
compare?: MosaicCompareDataItem;
|
|
92
|
+
}>;
|
|
93
|
+
|
|
94
|
+
export type MosaicContainerContextType = Omit<MosaicContainerProps<any>, 'children'>;
|
|
95
|
+
|
|
96
|
+
export const MosaicContainerContext = createContext<MosaicContainerContextType | undefined>(undefined);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Root Container that manages the layout of tiles.
|
|
100
|
+
*/
|
|
101
|
+
export const MosaicContainer = ({
|
|
102
|
+
children,
|
|
103
|
+
id,
|
|
104
|
+
debug,
|
|
105
|
+
Component,
|
|
106
|
+
transitionDuration = DEFAULT_TRANSITION,
|
|
107
|
+
modifier,
|
|
108
|
+
getOverlayProps,
|
|
109
|
+
getOverlayStyle,
|
|
110
|
+
onOver,
|
|
111
|
+
onDrop,
|
|
112
|
+
compare,
|
|
113
|
+
}: MosaicContainerProps) => {
|
|
114
|
+
const mosaic = useMosaic();
|
|
115
|
+
const container = {
|
|
116
|
+
id,
|
|
117
|
+
debug,
|
|
118
|
+
Component,
|
|
119
|
+
transitionDuration,
|
|
120
|
+
modifier,
|
|
121
|
+
getOverlayProps,
|
|
122
|
+
getOverlayStyle,
|
|
123
|
+
onOver,
|
|
124
|
+
onDrop,
|
|
125
|
+
compare,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
mosaic.setContainer(container.id, container);
|
|
130
|
+
return () => {
|
|
131
|
+
// TODO(burdon): The overlay unregisters the container after the tile has re-rendered (removing it).
|
|
132
|
+
// mosaic.setContainer(container.id);
|
|
133
|
+
};
|
|
134
|
+
}, Object.values(container));
|
|
135
|
+
|
|
136
|
+
return <MosaicContainerContext.Provider value={container}>{children}</MosaicContainerContext.Provider>;
|
|
137
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
8
|
+
|
|
9
|
+
type Position = 'bottom-left' | 'bottom-right';
|
|
10
|
+
|
|
11
|
+
export type DebugProps = { data: any; position?: Position };
|
|
12
|
+
|
|
13
|
+
export const Debug: FC<DebugProps> = ({ data, position }) => (
|
|
14
|
+
<pre
|
|
15
|
+
className={mx(
|
|
16
|
+
'font-mono text-xs overflow-hidden m-2 p-1 z-[100]',
|
|
17
|
+
'bg-neutral-50 dark:bg-neutral-700 border border-neutral-300 rounded',
|
|
18
|
+
position === 'bottom-left' && 'fixed left-0 bottom-0',
|
|
19
|
+
position === 'bottom-right' && 'fixed right-0 bottom-0',
|
|
20
|
+
)}
|
|
21
|
+
>
|
|
22
|
+
{JSON.stringify(data, undefined, 1)}
|
|
23
|
+
</pre>
|
|
24
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { forwardRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { Card } from '@dxos/react-ui';
|
|
8
|
+
|
|
9
|
+
import { type MosaicTileComponent } from './Tile';
|
|
10
|
+
import { type MosaicDataItem } from './types';
|
|
11
|
+
import { Path } from './util';
|
|
12
|
+
|
|
13
|
+
export const DefaultComponent: MosaicTileComponent<MosaicDataItem> = forwardRef(
|
|
14
|
+
({ draggableStyle, draggableProps, item: { id }, path }, forwardRef) => {
|
|
15
|
+
return (
|
|
16
|
+
<Card.Root ref={forwardRef} style={draggableStyle}>
|
|
17
|
+
<Card.Header>
|
|
18
|
+
<Card.DragHandle {...draggableProps} />
|
|
19
|
+
<Card.Title title={Path.create(path, id)} classNames='truncate font-mono text-xs' />
|
|
20
|
+
</Card.Header>
|
|
21
|
+
</Card.Root>
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
DefaultComponent.displayName = 'DefaultComponent';
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { DragOverlay } from '@dnd-kit/core';
|
|
6
|
+
import React, { Component, type PropsWithChildren, useEffect, useRef, useState } from 'react';
|
|
7
|
+
|
|
8
|
+
import { DensityProvider } from '@dxos/react-ui';
|
|
9
|
+
|
|
10
|
+
import { type MosaicContainerProps } from './Container';
|
|
11
|
+
import { DefaultComponent } from './DefaultComponent';
|
|
12
|
+
import { type MosaicTileComponent } from './Tile';
|
|
13
|
+
import { useMosaic } from './hooks';
|
|
14
|
+
import { Path } from './util';
|
|
15
|
+
|
|
16
|
+
export type MosaicDragOverlayProps = { delay?: number; debug?: boolean } & Omit<
|
|
17
|
+
React.ComponentProps<typeof DragOverlay>,
|
|
18
|
+
'children'
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Render the currently dragged item of the Mosaic.
|
|
23
|
+
*/
|
|
24
|
+
export const MosaicDragOverlay = ({ delay = 200, debug = false, ...overlayProps }: MosaicDragOverlayProps) => {
|
|
25
|
+
const { containers, operation, activeItem, overItem } = useMosaic();
|
|
26
|
+
|
|
27
|
+
// Get the overlay component from the over container, otherwise default to the original.
|
|
28
|
+
const [state, setState] = useState<{
|
|
29
|
+
container?: MosaicContainerProps<any>;
|
|
30
|
+
OverlayComponent?: MosaicTileComponent<any>;
|
|
31
|
+
}>({});
|
|
32
|
+
|
|
33
|
+
const timer = useRef<ReturnType<typeof setTimeout>>();
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (activeItem) {
|
|
37
|
+
let container: MosaicContainerProps<any> | undefined;
|
|
38
|
+
let OverlayComponent: MosaicTileComponent<any> | undefined;
|
|
39
|
+
if (overItem?.path) {
|
|
40
|
+
container = containers[Path.first(overItem.path)];
|
|
41
|
+
OverlayComponent = container?.Component;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!OverlayComponent) {
|
|
45
|
+
container = containers[Path.first(activeItem.path)];
|
|
46
|
+
OverlayComponent = container?.Component ?? DefaultComponent;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Prevent jitter when transitioning across containers.
|
|
50
|
+
clearTimeout(timer.current);
|
|
51
|
+
timer.current = setTimeout(() => setState({ container, OverlayComponent }), timer.current ? delay : 0);
|
|
52
|
+
} else {
|
|
53
|
+
setState({});
|
|
54
|
+
}
|
|
55
|
+
}, [activeItem, overItem]);
|
|
56
|
+
|
|
57
|
+
// Ensure the overlay is rendered as soon as there's an active item, then use local state.
|
|
58
|
+
const container = state.container ?? (activeItem && containers[Path.first(activeItem.path)]);
|
|
59
|
+
const OverlayComponent = state.OverlayComponent ?? container?.Component;
|
|
60
|
+
// Fallback is always to the active (origin) container.
|
|
61
|
+
const FallbackComponent = (activeItem && containers[Path.first(activeItem.path)])?.Component ?? DefaultComponent;
|
|
62
|
+
|
|
63
|
+
// NOTE: The DragOverlay wrapper element must always be mounted to support animations. Conditionally render the content.
|
|
64
|
+
return (
|
|
65
|
+
// TODO(burdon): Set custom animations (e.g., in/out/around).
|
|
66
|
+
<DragOverlay adjustScale={false} {...overlayProps} style={{ ...container?.getOverlayStyle?.() }}>
|
|
67
|
+
{/* TODO(burdon): Configure density via getOverlayProps. */}
|
|
68
|
+
{activeItem?.path && container && OverlayComponent && (
|
|
69
|
+
<DensityProvider density='fine'>
|
|
70
|
+
<OverlayErrorBoundary
|
|
71
|
+
fallback={
|
|
72
|
+
<FallbackComponent
|
|
73
|
+
{...container.getOverlayProps?.()}
|
|
74
|
+
item={activeItem.item}
|
|
75
|
+
path={activeItem.path}
|
|
76
|
+
operation={operation}
|
|
77
|
+
active='overlay'
|
|
78
|
+
/>
|
|
79
|
+
}
|
|
80
|
+
>
|
|
81
|
+
<OverlayComponent
|
|
82
|
+
{...container.getOverlayProps?.()}
|
|
83
|
+
item={activeItem.item}
|
|
84
|
+
path={activeItem.path}
|
|
85
|
+
operation={operation}
|
|
86
|
+
active='overlay'
|
|
87
|
+
/>
|
|
88
|
+
{debug && (
|
|
89
|
+
<div className='flex flex-wrap mt-1 p-1 bg-neutral-50 dark:bg-neutral-700 text-xs border rounded overflow-hidden gap-1'>
|
|
90
|
+
<span className='truncate'>
|
|
91
|
+
<span className='text-neutral-400'>container </span>
|
|
92
|
+
{container.id}
|
|
93
|
+
</span>
|
|
94
|
+
<span className='truncate'>
|
|
95
|
+
<span className='text-neutral-400'>item </span>
|
|
96
|
+
{activeItem.item.id.slice(0, 8)}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</OverlayErrorBoundary>
|
|
101
|
+
</DensityProvider>
|
|
102
|
+
)}
|
|
103
|
+
</DragOverlay>
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
type OverlayErrorBoundaryProps = PropsWithChildren<{ fallback?: React.ReactNode }>;
|
|
108
|
+
|
|
109
|
+
class OverlayErrorBoundary extends Component<OverlayErrorBoundaryProps> {
|
|
110
|
+
private readonly fallback: React.ReactNode;
|
|
111
|
+
|
|
112
|
+
constructor(props: OverlayErrorBoundaryProps) {
|
|
113
|
+
super(props);
|
|
114
|
+
this.fallback = props.fallback ?? <p>ERROR: ${String(this.state.error)}</p>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
static getDerivedStateFromError(error: Error) {
|
|
118
|
+
return { error };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override state = {
|
|
122
|
+
error: null,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
override componentDidCatch(error: any, info: any) {
|
|
126
|
+
console.warn(error, info);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
override render() {
|
|
130
|
+
if (this.state.error) {
|
|
131
|
+
return this.fallback;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return this.props.children;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type FC } from 'react';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
MosaicContainer,
|
|
9
|
+
type MosaicContainerProps as NaturalMosaicContainerProps,
|
|
10
|
+
type MosaicMoveEvent as NaturalMosaicMoveEvent,
|
|
11
|
+
type MosaicDropEvent as NaturalMosaicDropEvent,
|
|
12
|
+
type MosaicOperation as NaturalMosaicOperation,
|
|
13
|
+
type MosaicCompareDataItem as NaturalMosaicCompareDataItem,
|
|
14
|
+
} from './Container';
|
|
15
|
+
import { Debug, type DebugProps } from './Debug';
|
|
16
|
+
import { DefaultComponent } from './DefaultComponent';
|
|
17
|
+
import { MosaicDragOverlay, type MosaicDragOverlayProps } from './DragOverlay';
|
|
18
|
+
import { MosaicRoot, type MosaicRootProps as NaturalMosaicRootProps } from './Root';
|
|
19
|
+
import { MosaicSortableContext, type MosaicSortableProps as NaturalMosaicSortableProps } from './SortableContext';
|
|
20
|
+
import {
|
|
21
|
+
DraggableTile,
|
|
22
|
+
DroppableTile,
|
|
23
|
+
SortableTile,
|
|
24
|
+
type MosaicTileProps as NaturalMosaicTileProps,
|
|
25
|
+
type MosaicTileComponent as NaturalMosaicTileComponent,
|
|
26
|
+
type MosaicActiveType as NaturalMosaicActiveType,
|
|
27
|
+
} from './Tile';
|
|
28
|
+
import { type MosaicDataItem } from './types';
|
|
29
|
+
|
|
30
|
+
export const Mosaic = {
|
|
31
|
+
Root: MosaicRoot,
|
|
32
|
+
Container: MosaicContainer,
|
|
33
|
+
DragOverlay: MosaicDragOverlay,
|
|
34
|
+
SortableContext: MosaicSortableContext,
|
|
35
|
+
DraggableTile,
|
|
36
|
+
DroppableTile,
|
|
37
|
+
SortableTile,
|
|
38
|
+
|
|
39
|
+
// TODO(wittjosiah): Consider factoring out or using inline styles instead depending on DXOS UI theme/tailwind.
|
|
40
|
+
DefaultComponent,
|
|
41
|
+
// TODO(burdon): Don't export; move to util.
|
|
42
|
+
Debug,
|
|
43
|
+
} as {
|
|
44
|
+
Root: FC<MosaicRootProps>;
|
|
45
|
+
Container: FC<MosaicContainerProps<any, any>>;
|
|
46
|
+
DragOverlay: FC<MosaicOverlayProps>;
|
|
47
|
+
SortableContext: FC<MosaicSortableProps>;
|
|
48
|
+
DraggableTile: FC<MosaicTileProps<any, any>>;
|
|
49
|
+
DroppableTile: FC<MosaicTileProps<any, any>>;
|
|
50
|
+
SortableTile: FC<MosaicTileProps<any, number>>;
|
|
51
|
+
|
|
52
|
+
DefaultComponent: MosaicTileComponent;
|
|
53
|
+
Debug: FC<MosaicDebugProps>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type MosaicRootProps = NaturalMosaicRootProps;
|
|
57
|
+
|
|
58
|
+
export type MosaicContainerProps<
|
|
59
|
+
TData extends MosaicDataItem = MosaicDataItem,
|
|
60
|
+
TPosition = unknown,
|
|
61
|
+
> = NaturalMosaicContainerProps<TData, TPosition>;
|
|
62
|
+
|
|
63
|
+
export type MosaicOverlayProps = MosaicDragOverlayProps;
|
|
64
|
+
|
|
65
|
+
export type MosaicSortableProps = NaturalMosaicSortableProps;
|
|
66
|
+
|
|
67
|
+
export type MosaicTileProps<
|
|
68
|
+
TData extends MosaicDataItem = MosaicDataItem,
|
|
69
|
+
TPosition = unknown,
|
|
70
|
+
> = NaturalMosaicTileProps<TData, TPosition>;
|
|
71
|
+
|
|
72
|
+
export type MosaicTileComponent<
|
|
73
|
+
TData extends MosaicDataItem = MosaicDataItem,
|
|
74
|
+
TElement extends HTMLElement = HTMLDivElement,
|
|
75
|
+
> = NaturalMosaicTileComponent<TData, TElement>;
|
|
76
|
+
|
|
77
|
+
export type MosaicDebugProps = DebugProps;
|
|
78
|
+
|
|
79
|
+
export type MosaicMoveEvent<TPosition = unknown> = NaturalMosaicMoveEvent<TPosition>;
|
|
80
|
+
export type MosaicDropEvent<TPosition = unknown> = NaturalMosaicDropEvent<TPosition>;
|
|
81
|
+
export type MosaicOperation = NaturalMosaicOperation;
|
|
82
|
+
export type MosaicActiveType = NaturalMosaicActiveType;
|
|
83
|
+
|
|
84
|
+
export type MosaicCompareDataItem = NaturalMosaicCompareDataItem;
|