@dxos/react-ui-stack 0.6.13 → 0.6.14-main.7bd9c89
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 +9 -8
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/testing/index.mjs +1 -1
- package/dist/lib/browser/testing/index.mjs.map +3 -3
- package/dist/lib/node/index.cjs +7 -6
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/testing/index.cjs.map +2 -2
- package/dist/lib/node-esm/index.mjs +378 -0
- package/dist/lib/node-esm/index.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -0
- package/dist/lib/node-esm/testing/index.mjs +161 -0
- package/dist/lib/node-esm/testing/index.mjs.map +7 -0
- package/dist/types/src/components/CaretDownUp.d.ts.map +1 -1
- package/dist/types/src/components/Deck.stories.d.ts.map +1 -1
- package/dist/types/src/components/Section.d.ts +2 -3
- package/dist/types/src/components/Section.d.ts.map +1 -1
- package/dist/types/src/components/Stack.d.ts +2 -2
- package/dist/types/src/components/Stack.d.ts.map +1 -1
- package/dist/types/src/next/Stack.d.ts +9 -0
- package/dist/types/src/next/Stack.d.ts.map +1 -0
- package/dist/types/src/next/Stack.stories.d.ts +8 -0
- package/dist/types/src/next/Stack.stories.d.ts.map +1 -0
- package/dist/types/src/next/StackItem.d.ts +14 -0
- package/dist/types/src/next/StackItem.d.ts.map +1 -0
- package/dist/types/src/next/index.d.ts +2 -0
- package/dist/types/src/next/index.d.ts.map +1 -0
- package/dist/types/src/playwright/playwright.config.d.ts +2 -2
- package/dist/types/src/playwright/playwright.config.d.ts.map +1 -1
- package/dist/types/src/testing/TableContent.d.ts.map +1 -1
- package/dist/types/src/testing/generator.d.ts +2 -2
- package/dist/types/src/testing/generator.d.ts.map +1 -1
- package/package.json +30 -21
- package/src/components/CaretDownUp.tsx +1 -0
- package/src/components/ContentTypes.stories.tsx +1 -1
- package/src/components/Deck.stories.tsx +17 -27
- package/src/components/Section.stories.tsx +1 -1
- package/src/components/Section.tsx +9 -18
- package/src/components/Stack.stories.tsx +1 -1
- package/src/components/Stack.tsx +2 -2
- package/src/next/Stack.stories.tsx +148 -0
- package/src/next/Stack.tsx +30 -0
- package/src/next/StackItem.tsx +78 -0
- package/src/next/index.ts +5 -0
- package/src/playwright/playwright.config.ts +13 -2
- package/src/playwright/smoke.spec.ts +11 -19
- package/src/testing/TableContent.tsx +1 -0
- package/src/testing/generator.ts +4 -4
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
6
|
+
import { type Meta, type StoryObj } from '@storybook/react';
|
|
7
|
+
import React, { useState, useCallback } from 'react';
|
|
8
|
+
|
|
9
|
+
import { withTheme } from '@dxos/storybook-utils';
|
|
10
|
+
|
|
11
|
+
import { Stack } from './Stack';
|
|
12
|
+
import { StackItem } from './StackItem';
|
|
13
|
+
|
|
14
|
+
type CardItem = {
|
|
15
|
+
id: string;
|
|
16
|
+
type: 'card';
|
|
17
|
+
content: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ColumnItem = {
|
|
21
|
+
id: string;
|
|
22
|
+
type: 'column';
|
|
23
|
+
title: string;
|
|
24
|
+
cards: CardItem[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const KanbanBlock = ({ item }: { item: CardItem }) => {
|
|
28
|
+
return (
|
|
29
|
+
<div className='is-64 bs-24 bg-input rounded-lg border border-separator shadow-sm grid place-content-center'>
|
|
30
|
+
<span className='text-sm font-medium'>{item.content}</span>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const StorybookStack = () => {
|
|
36
|
+
const [columns, setColumns] = useState<ColumnItem[]>([
|
|
37
|
+
{
|
|
38
|
+
id: 'col-0',
|
|
39
|
+
type: 'column',
|
|
40
|
+
title: 'To Do',
|
|
41
|
+
cards: [
|
|
42
|
+
{ id: 'banana', type: 'card', content: 'Banana' },
|
|
43
|
+
{ id: 'pickle', type: 'card', content: 'Pickle' },
|
|
44
|
+
{ id: 'wombat', type: 'card', content: 'Wombat' },
|
|
45
|
+
{ id: 'kazoo', type: 'card', content: 'Kazoo' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'col-1',
|
|
50
|
+
type: 'column',
|
|
51
|
+
title: 'In Progress',
|
|
52
|
+
cards: [
|
|
53
|
+
{ id: 'noodle', type: 'card', content: 'Noodle' },
|
|
54
|
+
{ id: 'squish', type: 'card', content: 'Squish' },
|
|
55
|
+
{ id: 'wobble', type: 'card', content: 'Wobble' },
|
|
56
|
+
{ id: 'floof', type: 'card', content: 'Floof' },
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'col-2',
|
|
61
|
+
type: 'column',
|
|
62
|
+
title: 'Done',
|
|
63
|
+
cards: [
|
|
64
|
+
{ id: 'snorkel', type: 'card', content: 'Snorkel' },
|
|
65
|
+
{ id: 'bloop', type: 'card', content: 'Bloop' },
|
|
66
|
+
{ id: 'wiggle', type: 'card', content: 'Wiggle' },
|
|
67
|
+
{ id: 'zoop', type: 'card', content: 'Zoop' },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const reorderItem = useCallback((sourceId: string, targetId: string, closestEdge: Edge | null) => {
|
|
73
|
+
setColumns((prevColumns) => {
|
|
74
|
+
const newColumns = [...prevColumns];
|
|
75
|
+
const sourceColumn = newColumns.find(
|
|
76
|
+
(col) => col.id === sourceId || col.cards.some((card) => card.id === sourceId),
|
|
77
|
+
);
|
|
78
|
+
const targetColumn = newColumns.find(
|
|
79
|
+
(col) => col.id === targetId || col.cards.some((card) => card.id === targetId),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
if (sourceColumn && targetColumn) {
|
|
83
|
+
if (sourceId.startsWith('col-') && targetId.startsWith('col-')) {
|
|
84
|
+
// Reordering columns
|
|
85
|
+
const sourceIndex = newColumns.findIndex((col) => col.id === sourceId);
|
|
86
|
+
const targetIndex = newColumns.findIndex((col) => col.id === targetId);
|
|
87
|
+
const [movedColumn] = newColumns.splice(sourceIndex, 1);
|
|
88
|
+
const insertIndex = closestEdge === 'right' ? targetIndex + 1 : targetIndex;
|
|
89
|
+
newColumns.splice(insertIndex, 0, movedColumn);
|
|
90
|
+
} else {
|
|
91
|
+
// Reordering cards within a column
|
|
92
|
+
const sourceCardIndex = sourceColumn.cards.findIndex((card) => card.id === sourceId);
|
|
93
|
+
const targetCardIndex = targetColumn.cards.findIndex((card) => card.id === targetId);
|
|
94
|
+
const [movedCard] = sourceColumn.cards.splice(sourceCardIndex, 1);
|
|
95
|
+
|
|
96
|
+
let insertIndex;
|
|
97
|
+
if (sourceColumn === targetColumn && sourceCardIndex < targetCardIndex) {
|
|
98
|
+
insertIndex = closestEdge === 'bottom' ? targetCardIndex : targetCardIndex - 1;
|
|
99
|
+
} else {
|
|
100
|
+
insertIndex = closestEdge === 'bottom' ? targetCardIndex + 1 : targetCardIndex;
|
|
101
|
+
}
|
|
102
|
+
targetColumn.cards.splice(insertIndex, 0, movedCard);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return newColumns;
|
|
107
|
+
});
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Stack orientation={'horizontal'} classNames='gap-1'>
|
|
112
|
+
{columns.map((column) => (
|
|
113
|
+
<StackItem
|
|
114
|
+
key={column.id}
|
|
115
|
+
item={column}
|
|
116
|
+
orientation={'horizontal'}
|
|
117
|
+
classNames='p-4 bg-deck rounded-md'
|
|
118
|
+
onReorder={reorderItem}
|
|
119
|
+
>
|
|
120
|
+
<Stack orientation={'vertical'} classNames='gap-1'>
|
|
121
|
+
{column.cards.map((card) => (
|
|
122
|
+
<StackItem key={card.id} item={card} orientation={'vertical'} onReorder={reorderItem}>
|
|
123
|
+
<KanbanBlock item={card} />
|
|
124
|
+
</StackItem>
|
|
125
|
+
))}
|
|
126
|
+
</Stack>
|
|
127
|
+
</StackItem>
|
|
128
|
+
))}
|
|
129
|
+
</Stack>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
type Story = StoryObj<typeof StorybookStack>;
|
|
134
|
+
|
|
135
|
+
export const Default: Story = {
|
|
136
|
+
args: {
|
|
137
|
+
orientation: 'horizontal',
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const meta: Meta<typeof StorybookStack> = {
|
|
142
|
+
title: 'ui/react-ui-stack-next/Stack',
|
|
143
|
+
component: StorybookStack,
|
|
144
|
+
decorators: [withTheme],
|
|
145
|
+
argTypes: { orientation: { control: 'radio', options: ['horizontal', 'vertical'] } },
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default meta;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import React, { Children, type CSSProperties, type ComponentPropsWithoutRef } from 'react';
|
|
6
|
+
|
|
7
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
8
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
9
|
+
|
|
10
|
+
type Orientation = 'horizontal' | 'vertical';
|
|
11
|
+
|
|
12
|
+
export type StackProps = Omit<ThemedClassName<ComponentPropsWithoutRef<'div'>>, 'aria-orientation'> & {
|
|
13
|
+
orientation?: Orientation;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const Stack = ({ children, classNames, style, orientation, ...props }: StackProps) => {
|
|
17
|
+
const childrenCount = Children.count(children);
|
|
18
|
+
|
|
19
|
+
const styles: CSSProperties = {
|
|
20
|
+
[orientation === 'horizontal' ? 'gridTemplateColumns' : 'gridTemplateRows']:
|
|
21
|
+
`repeat(${childrenCount}, min-content)`,
|
|
22
|
+
...style,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div className={mx('grid relative', classNames)} aria-orientation={orientation} style={styles} {...props}>
|
|
27
|
+
{children}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
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 {
|
|
8
|
+
attachClosestEdge,
|
|
9
|
+
type Edge,
|
|
10
|
+
extractClosestEdge,
|
|
11
|
+
} from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
|
12
|
+
import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box';
|
|
13
|
+
import React, { useEffect, useRef, useState, type ComponentPropsWithoutRef } from 'react';
|
|
14
|
+
|
|
15
|
+
import { type ThemedClassName } from '@dxos/react-ui';
|
|
16
|
+
import { mx } from '@dxos/react-ui-theme';
|
|
17
|
+
|
|
18
|
+
import { type StackProps } from './Stack';
|
|
19
|
+
|
|
20
|
+
export type StackItemProps = Omit<ThemedClassName<ComponentPropsWithoutRef<'div'>>, 'aria-orientation'> & {
|
|
21
|
+
item: { id: string; type: 'column' | 'card' };
|
|
22
|
+
orientation?: StackProps['orientation'];
|
|
23
|
+
onReorder: (sourceId: string, targetId: string, closestEdge: Edge | null) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const StackItem = ({ item, children, classNames, orientation, onReorder, ...props }: StackItemProps) => {
|
|
27
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
28
|
+
const [closestEdge, setEdge] = useState<Edge | null>(null);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (!ref.current) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const element = ref.current;
|
|
36
|
+
|
|
37
|
+
return combine(
|
|
38
|
+
draggable({ element, getInitialData: () => ({ id: item.id, type: item.type }) }),
|
|
39
|
+
dropTargetForElements({
|
|
40
|
+
element,
|
|
41
|
+
getData: ({ input, element }) => {
|
|
42
|
+
return attachClosestEdge(
|
|
43
|
+
{ id: item.id, type: item.type },
|
|
44
|
+
{ input, element, allowedEdges: orientation === 'vertical' ? ['top', 'bottom'] : ['left', 'right'] },
|
|
45
|
+
);
|
|
46
|
+
},
|
|
47
|
+
onDragEnter: ({ self, source }) => {
|
|
48
|
+
if (source.data.type === self.data.type) {
|
|
49
|
+
setEdge(extractClosestEdge(self.data));
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
onDrag: ({ self, source }) => {
|
|
53
|
+
if (source.data.type === self.data.type) {
|
|
54
|
+
setEdge(extractClosestEdge(self.data));
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
onDragLeave: () => setEdge(null),
|
|
58
|
+
onDrop: ({ self, source }) => {
|
|
59
|
+
setEdge(null);
|
|
60
|
+
if (source.data.type === self.data.type) {
|
|
61
|
+
onReorder(source.data.id as string, self.data.id as string, extractClosestEdge(self.data));
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
}, [orientation, item, onReorder]);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div
|
|
70
|
+
ref={ref}
|
|
71
|
+
className={mx('relative', orientation === 'horizontal' ? 'grid-cols-subgrid' : 'grid-rows-subgrid', classNames)}
|
|
72
|
+
{...props}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
{closestEdge && <DropIndicator edge={closestEdge} />}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { nxE2EPreset } from '@nx/playwright/preset';
|
|
6
|
+
import { defineConfig } from '@playwright/test';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
import { e2ePreset } from '@dxos/test-utils/playwright';
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
...nxE2EPreset(__filename, { testDir: __dirname }),
|
|
12
|
+
...e2ePreset(__dirname),
|
|
13
|
+
webServer: {
|
|
14
|
+
command: 'pnpm -w nx storybook-e2e stories',
|
|
15
|
+
port: 9009,
|
|
16
|
+
reuseExistingServer: !process.env.CI,
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { expect, test } from '@playwright/test';
|
|
6
6
|
|
|
7
|
-
import { setupPage } from '@dxos/test/playwright';
|
|
7
|
+
import { setupPage } from '@dxos/test-utils/playwright';
|
|
8
8
|
|
|
9
9
|
import { StackManager } from '../testing';
|
|
10
10
|
|
|
@@ -13,10 +13,8 @@ const storybookUrl = (storyId: string) => `http://localhost:9009/iframe.html?id=
|
|
|
13
13
|
|
|
14
14
|
test.describe('Stack', () => {
|
|
15
15
|
test('remove', async ({ browser }) => {
|
|
16
|
-
const { page } = await setupPage(browser, {
|
|
17
|
-
|
|
18
|
-
waitFor: (page) => page.getByTestId('stack-transfer').isVisible(),
|
|
19
|
-
});
|
|
16
|
+
const { page } = await setupPage(browser, { url: storybookUrl('react-ui-stack-stack--transfer') });
|
|
17
|
+
await page.getByTestId('stack-transfer').waitFor({ state: 'visible' });
|
|
20
18
|
|
|
21
19
|
const stack = new StackManager(page.getByTestId('stack-1'));
|
|
22
20
|
await expect(stack.sections()).toHaveCount(8);
|
|
@@ -28,10 +26,8 @@ test.describe('Stack', () => {
|
|
|
28
26
|
});
|
|
29
27
|
|
|
30
28
|
test('rearrange', async ({ browser }) => {
|
|
31
|
-
const { page } = await setupPage(browser, {
|
|
32
|
-
|
|
33
|
-
waitFor: (page) => page.getByTestId('stack-transfer').isVisible(),
|
|
34
|
-
});
|
|
29
|
+
const { page } = await setupPage(browser, { url: storybookUrl('react-ui-stack-stack--transfer') });
|
|
30
|
+
await page.getByTestId('stack-transfer').waitFor({ state: 'visible' });
|
|
35
31
|
|
|
36
32
|
const stack = new StackManager(page.getByTestId('stack-1'));
|
|
37
33
|
const sectionText = await stack.section(0).locator.innerText();
|
|
@@ -42,15 +38,13 @@ test.describe('Stack', () => {
|
|
|
42
38
|
});
|
|
43
39
|
|
|
44
40
|
test('transfer', async ({ browser, browserName }) => {
|
|
45
|
-
if (browserName
|
|
46
|
-
// TODO(wittjosiah): This test is
|
|
41
|
+
if (browserName !== 'chromium') {
|
|
42
|
+
// TODO(wittjosiah): This test is flaky in Webkit & Firefox.
|
|
47
43
|
test.skip();
|
|
48
44
|
}
|
|
49
45
|
|
|
50
|
-
const { page } = await setupPage(browser, {
|
|
51
|
-
|
|
52
|
-
waitFor: (page) => page.getByTestId('stack-transfer').isVisible(),
|
|
53
|
-
});
|
|
46
|
+
const { page } = await setupPage(browser, { url: storybookUrl('react-ui-stack-stack--transfer') });
|
|
47
|
+
await page.getByTestId('stack-transfer').waitFor({ state: 'visible' });
|
|
54
48
|
|
|
55
49
|
const stack1 = new StackManager(page.getByTestId('stack-1'));
|
|
56
50
|
const stack2 = new StackManager(page.getByTestId('stack-2'));
|
|
@@ -69,10 +63,8 @@ test.describe('Stack', () => {
|
|
|
69
63
|
});
|
|
70
64
|
|
|
71
65
|
test('copy', async ({ browser }) => {
|
|
72
|
-
const { page } = await setupPage(browser, {
|
|
73
|
-
|
|
74
|
-
waitFor: (page) => page.getByTestId('stack-copy').isVisible(),
|
|
75
|
-
});
|
|
66
|
+
const { page } = await setupPage(browser, { url: storybookUrl('react-ui-stack-stack--copy') });
|
|
67
|
+
await page.getByTestId('stack-copy').waitFor({ state: 'visible' });
|
|
76
68
|
|
|
77
69
|
const stack1 = new StackManager(page.getByTestId('stack-1'));
|
|
78
70
|
const stack2 = new StackManager(page.getByTestId('stack-2'));
|
package/src/testing/generator.ts
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// Copyright 2023 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { create, type ReactiveObject, S } from '@dxos/echo-schema';
|
|
6
|
+
import { faker } from '@dxos/random';
|
|
7
|
+
|
|
5
8
|
// TODO(burdon): Reconcile with @dxos/plugin-debug, @dxos/react-ui/testing.
|
|
6
9
|
|
|
7
10
|
// TODO(burdon): Bug when adding stale objects to space (e.g., static objects already added in previous story invocation).
|
|
8
11
|
|
|
9
|
-
import { S, create, type EchoReactiveObject } from '@dxos/echo-schema';
|
|
10
|
-
import { faker } from '@dxos/random';
|
|
11
|
-
|
|
12
12
|
// TODO(burdon): Util.
|
|
13
13
|
export const range = <T>(fn: (i: number) => T | undefined, length: number): T[] =>
|
|
14
14
|
Array.from({ length })
|
|
@@ -23,7 +23,7 @@ type ObjectDataGenerator = {
|
|
|
23
23
|
createData: () => any;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
type ObjectFactory<T extends
|
|
26
|
+
type ObjectFactory<T extends ReactiveObject<any>> = {
|
|
27
27
|
schema?: S.Schema<any>; // TODO(burdon): Support both typed and expando schema.
|
|
28
28
|
createObject: () => T;
|
|
29
29
|
};
|