@dxos/react-ui-mosaic 0.8.4-main.bc674ce → 0.8.4-main.c85a9c8dae
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/chunk-7S4PIFKU.mjs +953 -0
- package/dist/lib/browser/chunk-7S4PIFKU.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +596 -70
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/playwright/index.mjs +138 -0
- package/dist/lib/browser/playwright/index.mjs.map +7 -0
- package/dist/lib/browser/testing/index.mjs +61 -232
- package/dist/lib/browser/testing/index.mjs.map +4 -4
- package/dist/lib/node-esm/chunk-MK6KU66S.mjs +955 -0
- package/dist/lib/node-esm/chunk-MK6KU66S.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +596 -70
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/playwright/index.mjs +140 -0
- package/dist/lib/node-esm/playwright/index.mjs.map +7 -0
- package/dist/lib/node-esm/testing/index.mjs +61 -232
- package/dist/lib/node-esm/testing/index.mjs.map +4 -4
- package/dist/types/src/components/Board/Board.d.ts +81 -0
- package/dist/types/src/components/Board/Board.d.ts.map +1 -0
- package/dist/types/src/components/{Mosaic/Mosaic.stories.d.ts → Board/Board.stories.d.ts} +15 -1
- package/dist/types/src/components/Board/Board.stories.d.ts.map +1 -0
- package/dist/types/src/components/Board/Column.d.ts +40 -0
- package/dist/types/src/components/Board/Column.d.ts.map +1 -0
- package/dist/types/src/components/Board/Item.d.ts +9 -0
- package/dist/types/src/components/Board/Item.d.ts.map +1 -0
- package/dist/types/src/components/Board/index.d.ts +2 -0
- package/dist/types/src/components/Board/index.d.ts.map +1 -0
- package/dist/types/src/components/Focus/Focus.d.ts +5 -4
- package/dist/types/src/components/Focus/Focus.d.ts.map +1 -1
- package/dist/types/src/components/Focus/index.d.ts +0 -1
- package/dist/types/src/components/Focus/index.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Container.d.ts +42 -0
- package/dist/types/src/components/Mosaic/Container.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/Mosaic.d.ts +35 -118
- package/dist/types/src/components/Mosaic/Mosaic.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/Placeholder.d.ts +21 -0
- package/dist/types/src/components/Mosaic/Placeholder.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/Root.d.ts +36 -0
- package/dist/types/src/components/Mosaic/Root.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/Stack.d.ts +23 -0
- package/dist/types/src/components/Mosaic/Stack.d.ts.map +1 -0
- package/dist/types/src/components/{Stack → Mosaic}/Stack.stories.d.ts +2 -2
- package/dist/types/src/components/Mosaic/Stack.stories.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/Tile.d.ts +48 -0
- package/dist/types/src/components/Mosaic/Tile.d.ts.map +1 -0
- package/dist/types/src/components/Mosaic/styles.d.ts.map +1 -1
- package/dist/types/src/components/Mosaic/types.d.ts +9 -2
- package/dist/types/src/components/Mosaic/types.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -4
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +1 -0
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts +15 -0
- package/dist/types/src/hooks/useDefaultColumnEventHandler.d.ts.map +1 -0
- package/dist/types/src/hooks/useEventHandlerAdapter.d.ts +1 -1
- package/dist/types/src/hooks/useEventHandlerAdapter.d.ts.map +1 -1
- package/dist/types/src/hooks/useVisibleItems.d.ts +2 -2
- package/dist/types/src/hooks/useVisibleItems.d.ts.map +1 -1
- package/dist/types/src/playwright/board-manager.d.ts +45 -0
- package/dist/types/src/playwright/board-manager.d.ts.map +1 -0
- package/dist/types/src/playwright/index.d.ts +9 -0
- package/dist/types/src/playwright/index.d.ts.map +1 -0
- package/dist/types/src/playwright/playwright.config.d.ts +3 -0
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -0
- package/dist/types/src/playwright/smoke.spec.d.ts +2 -0
- package/dist/types/src/playwright/smoke.spec.d.ts.map +1 -0
- package/dist/types/src/testing/CardContainer.d.ts +5 -4
- package/dist/types/src/testing/CardContainer.d.ts.map +1 -1
- package/dist/types/src/testing/DefaultStackTile.d.ts +2 -2
- package/dist/types/src/testing/DefaultStackTile.d.ts.map +1 -1
- package/dist/types/src/testing/index.d.ts +1 -1
- package/dist/types/src/testing/index.d.ts.map +1 -1
- package/dist/types/src/testing/types.d.ts +21 -0
- package/dist/types/src/testing/types.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +5 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +43 -28
- package/src/components/Board/Board.stories.tsx +273 -0
- package/src/components/Board/Board.tsx +191 -0
- package/src/components/Board/Column.tsx +300 -0
- package/src/components/Board/Item.tsx +116 -0
- package/src/components/{Layout → Board}/index.ts +1 -1
- package/src/components/Focus/Focus.tsx +26 -17
- package/src/components/Focus/index.ts +0 -2
- package/src/components/Mosaic/Container.tsx +289 -0
- package/src/components/Mosaic/Mosaic.tsx +35 -913
- package/src/components/Mosaic/Placeholder.tsx +133 -0
- package/src/components/Mosaic/Root.tsx +292 -0
- package/src/components/{Stack → Mosaic}/Stack.stories.tsx +30 -26
- package/src/components/Mosaic/Stack.tsx +246 -0
- package/src/components/Mosaic/Tile.tsx +242 -0
- package/src/components/Mosaic/extension.ts +1 -1
- package/src/components/Mosaic/styles.ts +9 -8
- package/src/components/Mosaic/types.ts +15 -3
- package/src/components/index.ts +1 -4
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useContainerDebug.tsx +4 -2
- package/src/hooks/useDefaultColumnEventHandler.ts +35 -0
- package/src/hooks/useEventHandlerAdapter.ts +15 -6
- package/src/hooks/useVisibleItems.ts +7 -2
- package/src/playwright/board-manager.ts +154 -0
- package/src/playwright/index.ts +13 -0
- package/src/playwright/playwright.config.ts +17 -0
- package/src/playwright/smoke.spec.ts +138 -0
- package/src/testing/CardContainer.tsx +30 -18
- package/src/testing/DefaultStackTile.tsx +27 -16
- package/src/testing/index.ts +1 -1
- package/src/testing/types.ts +38 -0
- package/src/translations.ts +5 -1
- package/dist/lib/browser/chunk-UMBPEG4F.mjs +0 -1637
- package/dist/lib/browser/chunk-UMBPEG4F.mjs.map +0 -7
- package/dist/lib/browser/index.css +0 -13
- package/dist/lib/browser/index.css.map +0 -7
- package/dist/lib/browser/testing/index.css +0 -13
- package/dist/lib/browser/testing/index.css.map +0 -7
- package/dist/lib/node-esm/chunk-X6GV7SMK.mjs +0 -1639
- package/dist/lib/node-esm/chunk-X6GV7SMK.mjs.map +0 -7
- package/dist/lib/node-esm/index.css +0 -13
- package/dist/lib/node-esm/index.css.map +0 -7
- package/dist/lib/node-esm/testing/index.css +0 -13
- package/dist/lib/node-esm/testing/index.css.map +0 -7
- package/dist/types/src/components/Card/Card.d.ts +0 -121
- package/dist/types/src/components/Card/Card.d.ts.map +0 -1
- package/dist/types/src/components/Card/Card.stories.d.ts +0 -21
- package/dist/types/src/components/Card/Card.stories.d.ts.map +0 -1
- package/dist/types/src/components/Card/CardDragPreview.d.ts +0 -6
- package/dist/types/src/components/Card/CardDragPreview.d.ts.map +0 -1
- package/dist/types/src/components/Card/index.d.ts +0 -4
- package/dist/types/src/components/Card/index.d.ts.map +0 -1
- package/dist/types/src/components/Card/styles.d.ts +0 -13
- package/dist/types/src/components/Card/styles.d.ts.map +0 -1
- package/dist/types/src/components/Focus/styles.d.ts +0 -6
- package/dist/types/src/components/Focus/styles.d.ts.map +0 -1
- package/dist/types/src/components/Image/Image.d.ts +0 -14
- package/dist/types/src/components/Image/Image.d.ts.map +0 -1
- package/dist/types/src/components/Image/Image.stories.d.ts +0 -33
- package/dist/types/src/components/Image/Image.stories.d.ts.map +0 -1
- package/dist/types/src/components/Image/index.d.ts +0 -2
- package/dist/types/src/components/Image/index.d.ts.map +0 -1
- package/dist/types/src/components/Layout/Layout.d.ts +0 -33
- package/dist/types/src/components/Layout/Layout.d.ts.map +0 -1
- package/dist/types/src/components/Layout/Layout.stories.d.ts +0 -10
- package/dist/types/src/components/Layout/Layout.stories.d.ts.map +0 -1
- package/dist/types/src/components/Layout/index.d.ts +0 -2
- package/dist/types/src/components/Layout/index.d.ts.map +0 -1
- package/dist/types/src/components/Mosaic/Mosaic.stories.d.ts.map +0 -1
- package/dist/types/src/components/Scrollable/Scrollable.d.ts +0 -24
- package/dist/types/src/components/Scrollable/Scrollable.d.ts.map +0 -1
- package/dist/types/src/components/Scrollable/Scrollable.stories.d.ts +0 -10
- package/dist/types/src/components/Scrollable/Scrollable.stories.d.ts.map +0 -1
- package/dist/types/src/components/Scrollable/index.d.ts +0 -2
- package/dist/types/src/components/Scrollable/index.d.ts.map +0 -1
- package/dist/types/src/components/Stack/Stack.d.ts +0 -22
- package/dist/types/src/components/Stack/Stack.d.ts.map +0 -1
- package/dist/types/src/components/Stack/Stack.stories.d.ts.map +0 -1
- package/dist/types/src/components/Stack/index.d.ts +0 -2
- package/dist/types/src/components/Stack/index.d.ts.map +0 -1
- package/dist/types/src/testing/Board.d.ts +0 -45
- package/dist/types/src/testing/Board.d.ts.map +0 -1
- package/dist/types/src/testing/Slot.stories.d.ts +0 -14
- package/dist/types/src/testing/Slot.stories.d.ts.map +0 -1
- package/dist/types/src/testing/Virtualizer.stories.d.ts +0 -12
- package/dist/types/src/testing/Virtualizer.stories.d.ts.map +0 -1
- package/src/components/Card/Card.stories.tsx +0 -148
- package/src/components/Card/Card.tsx +0 -458
- package/src/components/Card/CardDragPreview.tsx +0 -23
- package/src/components/Card/index.ts +0 -8
- package/src/components/Card/styles.ts +0 -23
- package/src/components/Focus/styles.ts +0 -24
- package/src/components/Image/Image.stories.tsx +0 -86
- package/src/components/Image/Image.tsx +0 -223
- package/src/components/Image/index.ts +0 -5
- package/src/components/Layout/Layout.stories.tsx +0 -59
- package/src/components/Layout/Layout.tsx +0 -108
- package/src/components/Mosaic/Mosaic.stories.tsx +0 -113
- package/src/components/Scrollable/Scrollable.stories.tsx +0 -59
- package/src/components/Scrollable/Scrollable.tsx +0 -88
- package/src/components/Scrollable/index.ts +0 -5
- package/src/components/Scrollable/scrollable.css +0 -20
- package/src/components/Stack/Stack.tsx +0 -210
- package/src/components/Stack/index.ts +0 -5
- package/src/testing/Board.tsx +0 -278
- package/src/testing/Slot.stories.tsx +0 -101
- package/src/testing/Virtualizer.stories.tsx +0 -133
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/react-ui-mosaic",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.c85a9c8dae",
|
|
4
4
|
"description": "Modular UI components.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
"browser": "./dist/lib/browser/index.mjs",
|
|
20
20
|
"node": "./dist/lib/node-esm/index.mjs"
|
|
21
21
|
},
|
|
22
|
+
"./playwright": {
|
|
23
|
+
"source": "./src/playwright/index.ts",
|
|
24
|
+
"types": "./dist/types/src/playwright/index.d.ts",
|
|
25
|
+
"browser": "./dist/lib/browser/playwright/index.mjs",
|
|
26
|
+
"node": "./dist/lib/node-esm/playwright/index.mjs"
|
|
27
|
+
},
|
|
22
28
|
"./testing": {
|
|
23
29
|
"source": "./src/testing/index.ts",
|
|
24
30
|
"types": "./dist/types/src/testing/index.d.ts",
|
|
@@ -28,7 +34,14 @@
|
|
|
28
34
|
},
|
|
29
35
|
"types": "dist/types/src/index.d.ts",
|
|
30
36
|
"typesVersions": {
|
|
31
|
-
"*": {
|
|
37
|
+
"*": {
|
|
38
|
+
"playwright": [
|
|
39
|
+
"dist/types/src/playwright/index.d.ts"
|
|
40
|
+
],
|
|
41
|
+
"testing": [
|
|
42
|
+
"dist/types/src/testing/index.d.ts"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
32
45
|
},
|
|
33
46
|
"files": [
|
|
34
47
|
"dist",
|
|
@@ -49,42 +62,44 @@
|
|
|
49
62
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
|
50
63
|
"@tanstack/react-virtual": "^3.13.18",
|
|
51
64
|
"bind-event-listener": "^3.0.0",
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"@dxos/
|
|
55
|
-
"@dxos/
|
|
56
|
-
"@dxos/
|
|
57
|
-
"@dxos/
|
|
58
|
-
"@dxos/
|
|
59
|
-
"@dxos/react-
|
|
60
|
-
"@dxos/react-ui-
|
|
61
|
-
"@dxos/
|
|
62
|
-
"@dxos/
|
|
63
|
-
"@dxos/util": "0.8.4-main.bc674ce"
|
|
65
|
+
"@dxos/async": "0.8.4-main.c85a9c8dae",
|
|
66
|
+
"@dxos/echo": "0.8.4-main.c85a9c8dae",
|
|
67
|
+
"@dxos/invariant": "0.8.4-main.c85a9c8dae",
|
|
68
|
+
"@dxos/keys": "0.8.4-main.c85a9c8dae",
|
|
69
|
+
"@dxos/log": "0.8.4-main.c85a9c8dae",
|
|
70
|
+
"@dxos/react-client": "0.8.4-main.c85a9c8dae",
|
|
71
|
+
"@dxos/react-ui-menu": "0.8.4-main.c85a9c8dae",
|
|
72
|
+
"@dxos/react-ui-dnd": "0.8.4-main.c85a9c8dae",
|
|
73
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.c85a9c8dae",
|
|
74
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
|
|
75
|
+
"@dxos/util": "0.8.4-main.c85a9c8dae"
|
|
64
76
|
},
|
|
65
77
|
"devDependencies": {
|
|
78
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
66
79
|
"@types/react": "~19.2.7",
|
|
67
80
|
"@types/react-dom": "~19.2.3",
|
|
68
|
-
"effect": "3.19.
|
|
81
|
+
"effect": "3.19.16",
|
|
69
82
|
"react": "~19.2.3",
|
|
70
83
|
"react-dom": "~19.2.3",
|
|
71
|
-
"vite": "7.1.
|
|
72
|
-
"@dxos/
|
|
73
|
-
"@dxos/
|
|
74
|
-
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.
|
|
75
|
-
"@dxos/
|
|
76
|
-
"@dxos/
|
|
77
|
-
"@dxos/test-utils": "0.8.4-main.
|
|
78
|
-
"@dxos/
|
|
79
|
-
"@dxos/ui-theme": "0.8.4-main.
|
|
84
|
+
"vite": "^7.1.11",
|
|
85
|
+
"@dxos/echo-atom": "0.8.4-main.c85a9c8dae",
|
|
86
|
+
"@dxos/random": "0.8.4-main.c85a9c8dae",
|
|
87
|
+
"@dxos/react-ui-syntax-highlighter": "0.8.4-main.c85a9c8dae",
|
|
88
|
+
"@dxos/react-ui": "0.8.4-main.c85a9c8dae",
|
|
89
|
+
"@dxos/schema": "0.8.4-main.c85a9c8dae",
|
|
90
|
+
"@dxos/test-utils": "0.8.4-main.c85a9c8dae",
|
|
91
|
+
"@dxos/storybook-utils": "0.8.4-main.c85a9c8dae",
|
|
92
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae",
|
|
93
|
+
"@dxos/types": "0.8.4-main.c85a9c8dae"
|
|
80
94
|
},
|
|
81
95
|
"peerDependencies": {
|
|
82
|
-
"effect": "
|
|
96
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
97
|
+
"effect": "3.19.16",
|
|
83
98
|
"react": "~19.2.3",
|
|
84
99
|
"react-dom": "~19.2.3",
|
|
85
|
-
"@dxos/
|
|
86
|
-
"@dxos/
|
|
87
|
-
"@dxos/
|
|
100
|
+
"@dxos/react-ui": "0.8.4-main.c85a9c8dae",
|
|
101
|
+
"@dxos/random": "0.8.4-main.c85a9c8dae",
|
|
102
|
+
"@dxos/ui-theme": "0.8.4-main.c85a9c8dae"
|
|
88
103
|
},
|
|
89
104
|
"publishConfig": {
|
|
90
105
|
"access": "public"
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom, RegistryContext, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
7
|
+
import React, { useContext, useMemo } from 'react';
|
|
8
|
+
import { expect, within } from 'storybook/test';
|
|
9
|
+
|
|
10
|
+
import { type Database, Filter, Obj, Ref } from '@dxos/echo';
|
|
11
|
+
import { AtomObj, AtomQuery } from '@dxos/echo-atom';
|
|
12
|
+
import { invariant } from '@dxos/invariant';
|
|
13
|
+
import { faker } from '@dxos/random';
|
|
14
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
15
|
+
import { withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
16
|
+
import { withRegistry } from '@dxos/storybook-utils';
|
|
17
|
+
import { mx } from '@dxos/ui-theme';
|
|
18
|
+
|
|
19
|
+
import { useEventHandlerAdapter } from '../../hooks';
|
|
20
|
+
import { TestColumn, TestItem } from '../../testing';
|
|
21
|
+
import { translations } from '../../translations';
|
|
22
|
+
import { Focus } from '../Focus';
|
|
23
|
+
import { Mosaic, type MosaicEventHandler } from '../Mosaic';
|
|
24
|
+
|
|
25
|
+
import { Board, type BoardModel } from './Board';
|
|
26
|
+
import { DefaultBoardColumn } from './Column';
|
|
27
|
+
|
|
28
|
+
faker.seed(999);
|
|
29
|
+
|
|
30
|
+
const randomItems = () => faker.number.int({ min: 0, max: 20 });
|
|
31
|
+
|
|
32
|
+
const createTestData = (db: Database.Database, columns: number, items?: (column: number) => number) => {
|
|
33
|
+
Array.from({ length: columns }).forEach((_, i) => {
|
|
34
|
+
db.add(
|
|
35
|
+
Obj.make(TestColumn, {
|
|
36
|
+
name: `Column ${i}`,
|
|
37
|
+
items: Array.from({ length: items?.(i) ?? 0 }).map((_, j) => {
|
|
38
|
+
const item = db.add(
|
|
39
|
+
Obj.make(TestItem, {
|
|
40
|
+
name: faker.lorem.sentence(3),
|
|
41
|
+
description: faker.lorem.paragraph(1),
|
|
42
|
+
label: `${String.fromCharCode(65 + i)}-${j}`,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return Ref.make(item);
|
|
47
|
+
}),
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type StoryProps = {
|
|
54
|
+
columns?: number;
|
|
55
|
+
items?: (column: number) => number;
|
|
56
|
+
debug?: boolean;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
type TestBoardModelResult = {
|
|
60
|
+
model: BoardModel<TestColumn, TestItem>;
|
|
61
|
+
eventHandler: MosaicEventHandler<TestColumn>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const useTestBoardModel = (): TestBoardModelResult => {
|
|
65
|
+
const { space } = useClientStory();
|
|
66
|
+
const registry = useContext(RegistryContext);
|
|
67
|
+
|
|
68
|
+
const { model, orderedColumnsAtom, orderAtom } = useMemo(() => {
|
|
69
|
+
const getColumnId = (data: TestColumn) => data.id;
|
|
70
|
+
const getItemId = (data: TestItem) => data.id;
|
|
71
|
+
|
|
72
|
+
// In-memory column order (ids only). Empty until the user first reorders; then persist drag/drop order.
|
|
73
|
+
const orderAtom = Atom.make<string[]>([]);
|
|
74
|
+
|
|
75
|
+
// Source of truth for which columns exist; subscribed to ECHO query.
|
|
76
|
+
const columnsAtom =
|
|
77
|
+
space?.db != null ? AtomQuery.make(space.db, Filter.type(TestColumn)) : Atom.make<TestColumn[]>([]);
|
|
78
|
+
|
|
79
|
+
// If orderAtom is empty, use Echo order as-is; else sort by orderAtom and append new columns.
|
|
80
|
+
const orderedColumnsAtom = Atom.make((get) => {
|
|
81
|
+
const cols = get(columnsAtom);
|
|
82
|
+
const order = get(orderAtom);
|
|
83
|
+
if (order.length === 0) return cols;
|
|
84
|
+
const byId = new Map(cols.map((column) => [getColumnId(column), column]));
|
|
85
|
+
const ordered = order.map((id) => byId.get(id)).filter((column): column is TestColumn => column != null);
|
|
86
|
+
const appended = cols.filter((column) => !order.includes(getColumnId(column)));
|
|
87
|
+
return [...ordered, ...appended];
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const itemsAtomFamily =
|
|
91
|
+
space?.db != null
|
|
92
|
+
? Atom.family((column: TestColumn) =>
|
|
93
|
+
Atom.make((get) => {
|
|
94
|
+
const refs = get(AtomObj.makeProperty(column, 'items'));
|
|
95
|
+
return (
|
|
96
|
+
refs
|
|
97
|
+
?.map((ref: Ref.Ref<TestItem>) => get(AtomObj.makeWithReactive(ref)))
|
|
98
|
+
.filter((item): item is TestItem => item != null) ?? []
|
|
99
|
+
);
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
: Atom.family((_column: TestColumn) => Atom.make<TestItem[]>([]));
|
|
103
|
+
|
|
104
|
+
const model = {
|
|
105
|
+
getColumnId,
|
|
106
|
+
getItemId,
|
|
107
|
+
isColumn: (obj: unknown): obj is TestColumn => Obj.isObject(obj) && Obj.instanceOf(TestColumn, obj),
|
|
108
|
+
isItem: (obj: unknown): obj is TestItem => Obj.isObject(obj) && Obj.instanceOf(TestItem, obj),
|
|
109
|
+
columns: orderedColumnsAtom,
|
|
110
|
+
items: (column: TestColumn) => itemsAtomFamily(column),
|
|
111
|
+
getColumns: () => registry.get(orderedColumnsAtom),
|
|
112
|
+
getItems: (column: TestColumn) => registry.get(itemsAtomFamily(column)),
|
|
113
|
+
onItemCreate: async (column: TestColumn) => {
|
|
114
|
+
invariant(space);
|
|
115
|
+
const item = space.db.add(
|
|
116
|
+
Obj.make(TestItem, {
|
|
117
|
+
name: faker.lorem.sentence(3),
|
|
118
|
+
description: faker.lorem.paragraph(1),
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
Obj.change(column, (column) => {
|
|
123
|
+
column.items.push(Ref.make(item));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return item;
|
|
127
|
+
},
|
|
128
|
+
onItemDelete: (column: TestColumn, current: TestItem) => {
|
|
129
|
+
Obj.change(column, (mutableColumn) => {
|
|
130
|
+
if (!mutableColumn.items) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const idx = mutableColumn.items.findIndex((ref: Ref.Ref<TestItem>) => ref.target?.id === current?.id);
|
|
134
|
+
if (idx !== -1) {
|
|
135
|
+
mutableColumn.items.splice(idx, 1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
} satisfies BoardModel<TestColumn, TestItem>;
|
|
140
|
+
|
|
141
|
+
return { model, orderedColumnsAtom, orderAtom };
|
|
142
|
+
}, [space?.db, registry]);
|
|
143
|
+
|
|
144
|
+
const orderedColumns = useAtomValue(model.columns);
|
|
145
|
+
const eventHandler = useEventHandlerAdapter<TestColumn, TestColumn>({
|
|
146
|
+
id: 'board',
|
|
147
|
+
items: orderedColumns,
|
|
148
|
+
getId: model.getColumnId,
|
|
149
|
+
get: (data) => data,
|
|
150
|
+
make: (object) => object,
|
|
151
|
+
canDrop: ({ source }) => model.isColumn(source.data),
|
|
152
|
+
// On drag/drop, adapter mutates a copy of the list; we write the new order into orderAtom so it persists in memory.
|
|
153
|
+
onChange: (mutate) => {
|
|
154
|
+
const current = registry.get(orderedColumnsAtom);
|
|
155
|
+
const next = [...current];
|
|
156
|
+
mutate(next);
|
|
157
|
+
registry.set(
|
|
158
|
+
orderAtom,
|
|
159
|
+
next.map((column) => model.getColumnId(column)),
|
|
160
|
+
);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return { model, eventHandler };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const DefaultStory = ({ debug = false }: StoryProps) => {
|
|
168
|
+
const { model, eventHandler } = useTestBoardModel();
|
|
169
|
+
const columns = useAtomValue(model.columns);
|
|
170
|
+
|
|
171
|
+
if (columns.length === 0) {
|
|
172
|
+
return <></>;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Mosaic.Root asChild debug={debug}>
|
|
177
|
+
<div role='none' className={mx('grid md:p-2 overflow-hidden', debug && 'grid-cols-[1fr_20rem] gap-2')}>
|
|
178
|
+
<Board.Root model={model}>
|
|
179
|
+
<Board.Content id='board' debug={debug} eventHandler={eventHandler} Tile={DefaultBoardColumn} />
|
|
180
|
+
</Board.Root>
|
|
181
|
+
{debug && (
|
|
182
|
+
<Focus.Group classNames='flex flex-col gap-2 overflow-hidden'>
|
|
183
|
+
<Board.Debug classNames='p-2' />
|
|
184
|
+
</Focus.Group>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
</Mosaic.Root>
|
|
188
|
+
);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const meta = {
|
|
192
|
+
title: 'ui/react-ui-mosaic/Board',
|
|
193
|
+
render: DefaultStory,
|
|
194
|
+
decorators: [
|
|
195
|
+
withRegistry,
|
|
196
|
+
withTheme({ platform: 'desktop' }),
|
|
197
|
+
withLayout({ layout: 'fullscreen' }),
|
|
198
|
+
withClientProvider({
|
|
199
|
+
types: [TestColumn, TestItem],
|
|
200
|
+
createIdentity: true,
|
|
201
|
+
createSpace: true,
|
|
202
|
+
onCreateSpace: ({ space }, context) => {
|
|
203
|
+
const args = context.args as StoryProps;
|
|
204
|
+
createTestData(space.db, args.columns ?? 0, args.items);
|
|
205
|
+
},
|
|
206
|
+
}),
|
|
207
|
+
],
|
|
208
|
+
parameters: {
|
|
209
|
+
layout: 'fullscreen',
|
|
210
|
+
translations,
|
|
211
|
+
},
|
|
212
|
+
argTypes: {
|
|
213
|
+
columns: {
|
|
214
|
+
control: 'number',
|
|
215
|
+
},
|
|
216
|
+
debug: {
|
|
217
|
+
control: 'boolean',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
221
|
+
|
|
222
|
+
export default meta;
|
|
223
|
+
|
|
224
|
+
type Story = StoryObj<typeof meta>;
|
|
225
|
+
|
|
226
|
+
export const Default: Story = {
|
|
227
|
+
args: {
|
|
228
|
+
columns: 8,
|
|
229
|
+
items: randomItems,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const SingleColumn: Story = {
|
|
234
|
+
args: {
|
|
235
|
+
columns: 1,
|
|
236
|
+
items: () => 1,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const Empty: Story = {};
|
|
241
|
+
|
|
242
|
+
export const EmptyColumn: Story = {
|
|
243
|
+
args: {
|
|
244
|
+
columns: 1,
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export const Debug: Story = {
|
|
249
|
+
args: {
|
|
250
|
+
debug: true,
|
|
251
|
+
columns: 2,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export const Spec: Story = {
|
|
256
|
+
args: {
|
|
257
|
+
columns: 3,
|
|
258
|
+
items: (column) => (column < 2 ? 5 : 0),
|
|
259
|
+
},
|
|
260
|
+
play: async () => {
|
|
261
|
+
const body = within(document.body);
|
|
262
|
+
const columns = await body.findAllByTestId('board-column', undefined, { timeout: 30_000 });
|
|
263
|
+
await expect(columns).toHaveLength(3);
|
|
264
|
+
|
|
265
|
+
// First two columns have items, third is empty.
|
|
266
|
+
for (const column of columns.slice(0, 2)) {
|
|
267
|
+
const items = within(column).queryAllByTestId('board-item');
|
|
268
|
+
await expect(items.length).toBeGreaterThan(0);
|
|
269
|
+
}
|
|
270
|
+
const items = within(columns[2]).queryAllByTestId('board-item');
|
|
271
|
+
await expect(items).toHaveLength(0);
|
|
272
|
+
},
|
|
273
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2023 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Atom, useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import { createContext } from '@radix-ui/react-context';
|
|
7
|
+
import React, {
|
|
8
|
+
type PropsWithChildren,
|
|
9
|
+
type ReactElement,
|
|
10
|
+
type Ref as ReactRef,
|
|
11
|
+
forwardRef,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
} from 'react';
|
|
15
|
+
|
|
16
|
+
import { ScrollArea, type ThemedClassName } from '@dxos/react-ui';
|
|
17
|
+
import { Json } from '@dxos/react-ui-syntax-highlighter';
|
|
18
|
+
import { mx } from '@dxos/ui-theme';
|
|
19
|
+
|
|
20
|
+
import { useContainerDebug } from '../../hooks';
|
|
21
|
+
import { Focus } from '../Focus';
|
|
22
|
+
import {
|
|
23
|
+
type GetId,
|
|
24
|
+
Mosaic,
|
|
25
|
+
type MosaicEventHandler,
|
|
26
|
+
type MosaicPlaceholderProps,
|
|
27
|
+
type MosaicStackProps,
|
|
28
|
+
mosaicStyles,
|
|
29
|
+
useMosaic,
|
|
30
|
+
} from '../Mosaic';
|
|
31
|
+
|
|
32
|
+
import { BoardColumn, type BoardColumnProps, DefaultBoardColumn, useBoardColumn } from './Column';
|
|
33
|
+
import { BoardItem, type BoardItemProps } from './Item';
|
|
34
|
+
|
|
35
|
+
//
|
|
36
|
+
// Model
|
|
37
|
+
//
|
|
38
|
+
|
|
39
|
+
export interface BoardModel<TColumn = any, TItem = any> {
|
|
40
|
+
getColumnId: GetId<TColumn>;
|
|
41
|
+
getItemId: GetId<TItem>;
|
|
42
|
+
isColumn: (obj: unknown) => obj is TColumn;
|
|
43
|
+
isItem: (obj: unknown) => obj is TItem;
|
|
44
|
+
columns: Atom.Atom<readonly TColumn[] | TColumn[]>;
|
|
45
|
+
items: (column: TColumn) => Atom.Atom<TItem[]>;
|
|
46
|
+
getColumns: () => TColumn[];
|
|
47
|
+
getItems: (column: TColumn) => TItem[];
|
|
48
|
+
onColumnCreate?: () => Promise<TColumn>;
|
|
49
|
+
onColumnDelete?: (column: TColumn) => void;
|
|
50
|
+
onItemCreate?: (column: TColumn) => Promise<TItem>;
|
|
51
|
+
onItemDelete?: (column: TColumn, item: TItem) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//
|
|
55
|
+
// Context
|
|
56
|
+
//
|
|
57
|
+
|
|
58
|
+
const BOARD_NAME = 'Board';
|
|
59
|
+
|
|
60
|
+
type BoardContextValue<TColumn = any, TItem = any> = {
|
|
61
|
+
model: BoardModel<TColumn, TItem>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const [BoardContextProvider, useBoardContext] = createContext<BoardContextValue>(BOARD_NAME);
|
|
65
|
+
|
|
66
|
+
/** Hook to read the board model from context (e.g. in custom column tiles). Pass TColumn and TItem for typed model. */
|
|
67
|
+
function useBoard<TColumn = any, TItem = any>(displayName?: string): BoardContextValue<TColumn, TItem> {
|
|
68
|
+
return useBoardContext(displayName ?? BOARD_NAME) as BoardContextValue<TColumn, TItem>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
//
|
|
72
|
+
// Root
|
|
73
|
+
//
|
|
74
|
+
|
|
75
|
+
const BOARD_ROOT_NAME = 'Board.Root';
|
|
76
|
+
|
|
77
|
+
type BoardRootProps<TColumn = any, TItem = any> = PropsWithChildren<BoardContextValue<TColumn, TItem>>;
|
|
78
|
+
|
|
79
|
+
const BoardRootInner = ({ model, children }: BoardRootProps) => {
|
|
80
|
+
return <BoardContextProvider model={model}>{children}</BoardContextProvider>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
BoardRootInner.displayName = BOARD_ROOT_NAME;
|
|
84
|
+
|
|
85
|
+
const BoardRoot = BoardRootInner as <TColumn = any, TItem = any>(props: BoardRootProps<TColumn, TItem>) => ReactElement;
|
|
86
|
+
|
|
87
|
+
//
|
|
88
|
+
// Content
|
|
89
|
+
//
|
|
90
|
+
|
|
91
|
+
const BOARD_CONTENT_NAME = 'Board.Content';
|
|
92
|
+
|
|
93
|
+
type BoardContentProps<TColumn = any> = ThemedClassName<{
|
|
94
|
+
id: string;
|
|
95
|
+
debug?: boolean;
|
|
96
|
+
eventHandler?: MosaicEventHandler<TColumn>;
|
|
97
|
+
Tile?: MosaicStackProps<TColumn>['Tile'];
|
|
98
|
+
}>;
|
|
99
|
+
|
|
100
|
+
const BoardContentInner = forwardRef<HTMLDivElement, BoardContentProps>(
|
|
101
|
+
({ classNames, id: _id, debug, eventHandler, Tile = DefaultBoardColumn }, forwardedRef) => {
|
|
102
|
+
const { model } = useBoardContext(BOARD_CONTENT_NAME);
|
|
103
|
+
const [DebugInfo, debugHandler] = useContainerDebug(debug);
|
|
104
|
+
const [viewport, setViewport] = useState<HTMLElement | null>(null);
|
|
105
|
+
|
|
106
|
+
const items = useAtomValue(model.columns);
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div ref={forwardedRef} className={mx('flex h-full w-full overflow-hidden', classNames)}>
|
|
110
|
+
<Focus.Group asChild orientation='horizontal'>
|
|
111
|
+
<Mosaic.Container
|
|
112
|
+
asChild
|
|
113
|
+
withFocus
|
|
114
|
+
orientation='horizontal'
|
|
115
|
+
autoScroll={viewport}
|
|
116
|
+
eventHandler={eventHandler}
|
|
117
|
+
debug={debugHandler}
|
|
118
|
+
>
|
|
119
|
+
<ScrollArea.Root orientation='horizontal' classNames='md:pt-3' margin padding>
|
|
120
|
+
<ScrollArea.Viewport classNames='snap-mandatory snap-x md:snap-none' ref={setViewport}>
|
|
121
|
+
<Mosaic.Stack items={items} getId={model.getColumnId} Tile={Tile} debug={debug} />
|
|
122
|
+
</ScrollArea.Viewport>
|
|
123
|
+
</ScrollArea.Root>
|
|
124
|
+
</Mosaic.Container>
|
|
125
|
+
</Focus.Group>
|
|
126
|
+
<DebugInfo />
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
BoardContentInner.displayName = BOARD_CONTENT_NAME;
|
|
133
|
+
|
|
134
|
+
const BoardContent = BoardContentInner as <TColumn = any>(
|
|
135
|
+
props: BoardContentProps<TColumn> & { ref?: ReactRef<HTMLDivElement> },
|
|
136
|
+
) => ReactElement;
|
|
137
|
+
|
|
138
|
+
//
|
|
139
|
+
// Placeholder
|
|
140
|
+
//
|
|
141
|
+
|
|
142
|
+
const BOARD_PLACEHOLDER_NAME = 'Board.Placeholder';
|
|
143
|
+
|
|
144
|
+
const BoardPlaceholder = (props: MosaicPlaceholderProps<number>) => {
|
|
145
|
+
return (
|
|
146
|
+
<Mosaic.Placeholder {...props} classNames={mosaicStyles.placeholder.root}>
|
|
147
|
+
<div
|
|
148
|
+
className={mx('flex h-full border border-dashed border-separator rounded-xs', mosaicStyles.placeholder.content)}
|
|
149
|
+
/>
|
|
150
|
+
</Mosaic.Placeholder>
|
|
151
|
+
);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
BoardPlaceholder.displayName = BOARD_PLACEHOLDER_NAME;
|
|
155
|
+
|
|
156
|
+
//
|
|
157
|
+
// Debug
|
|
158
|
+
//
|
|
159
|
+
|
|
160
|
+
const BOARD_DEBUG_NAME = 'Board.Debug';
|
|
161
|
+
|
|
162
|
+
export const BoardDebug = forwardRef<HTMLDivElement, ThemedClassName>(({ classNames }, forwardedRef) => {
|
|
163
|
+
const { containers, dragging } = useMosaic(BOARD_DEBUG_NAME);
|
|
164
|
+
const counter = useRef(0);
|
|
165
|
+
return (
|
|
166
|
+
<Json
|
|
167
|
+
data={{ containers, dragging, count: counter.current++ }}
|
|
168
|
+
classNames={mx('text-xs', classNames)}
|
|
169
|
+
ref={forwardedRef}
|
|
170
|
+
/>
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
BoardDebug.displayName = BOARD_DEBUG_NAME;
|
|
175
|
+
|
|
176
|
+
//
|
|
177
|
+
// Board
|
|
178
|
+
//
|
|
179
|
+
|
|
180
|
+
export const Board = {
|
|
181
|
+
Root: BoardRoot,
|
|
182
|
+
Content: BoardContent,
|
|
183
|
+
Column: BoardColumn,
|
|
184
|
+
Item: BoardItem,
|
|
185
|
+
Placeholder: BoardPlaceholder,
|
|
186
|
+
Debug: BoardDebug,
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
export { useBoard, useBoardColumn };
|
|
190
|
+
|
|
191
|
+
export type { BoardRootProps, BoardContentProps, BoardColumnProps, BoardItemProps };
|