uikit-react-public 0.45.4 → 0.47.0
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/components/DropZone/DropZone.d.ts +21 -0
- package/dist/components/DropZone/DropZone.stories.d.ts +52 -0
- package/dist/components/DropZone/__tests__/DropZone.test.d.ts +1 -0
- package/dist/components/DropZone/index.d.ts +2 -0
- package/dist/components/Label/Label.d.ts +1 -0
- package/dist/components/Label/Label.stories.d.ts +1 -0
- package/dist/components/Skeleton/Skeleton.d.ts +35 -0
- package/dist/components/Skeleton/Skeleton.stories.d.ts +27 -0
- package/dist/components/Skeleton/SkeletonAvatar.d.ts +7 -0
- package/dist/components/Skeleton/SkeletonInput.d.ts +4 -0
- package/dist/components/Skeleton/SkeletonTableRow.d.ts +9 -0
- package/dist/components/Skeleton/SkeletonText.d.ts +4 -0
- package/dist/components/Skeleton/__tests__/Skeleton.test.d.ts +1 -0
- package/dist/components/Skeleton/index.d.ts +6 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/hooks/__tests__/useDraggable.test.d.ts +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/useDraggable.d.ts +31 -0
- package/dist/hooks/useDropZone.d.ts +57 -0
- package/dist/index.js +7247 -6685
- package/lib/components/DropZone/DropZone.mdx +72 -0
- package/lib/components/DropZone/DropZone.stories.tsx +139 -0
- package/lib/components/DropZone/DropZone.tsx +213 -0
- package/lib/components/DropZone/__tests__/DropZone.test.tsx +378 -0
- package/lib/components/DropZone/__tests__/__snapshots__/DropZone.test.tsx.snap +46 -0
- package/lib/components/DropZone/index.ts +2 -0
- package/lib/components/Label/Label.stories.tsx +17 -0
- package/lib/components/Label/Label.tsx +4 -7
- package/lib/components/Label/__tests__/Label.test.tsx +15 -0
- package/lib/components/Label/__tests__/__snapshots__/Label.test.tsx.snap +1 -3
- package/lib/components/Skeleton/Skeleton.mdx +34 -0
- package/lib/components/Skeleton/Skeleton.stories.tsx +77 -0
- package/lib/components/Skeleton/Skeleton.tsx +158 -0
- package/lib/components/Skeleton/SkeletonAvatar.tsx +20 -0
- package/lib/components/Skeleton/SkeletonInput.tsx +13 -0
- package/lib/components/Skeleton/SkeletonTableRow.tsx +44 -0
- package/lib/components/Skeleton/SkeletonText.tsx +13 -0
- package/lib/components/Skeleton/__tests__/Skeleton.test.tsx +86 -0
- package/lib/components/Skeleton/index.ts +6 -0
- package/lib/components/index.ts +13 -0
- package/lib/hooks/__tests__/useDraggable.test.tsx +184 -0
- package/lib/hooks/index.ts +19 -0
- package/lib/hooks/useDraggable.ts +243 -0
- package/lib/hooks/useDropZone.ts +418 -0
- package/package.json +1 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { HTMLAttributes, Ref } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { useTheme, type ThemeType } from '../../theme';
|
|
4
|
+
import marginsStyle, { MarginProps } from '../common/marginsStyle';
|
|
5
|
+
import SkeletonAvatar from './SkeletonAvatar';
|
|
6
|
+
import SkeletonInput from './SkeletonInput';
|
|
7
|
+
import SkeletonTableRow from './SkeletonTableRow';
|
|
8
|
+
import SkeletonText from './SkeletonText';
|
|
9
|
+
|
|
10
|
+
export const NAME = 'ucl-uikit-skeleton';
|
|
11
|
+
|
|
12
|
+
export type SkeletonVariant = 'text' | 'circle' | 'rect';
|
|
13
|
+
|
|
14
|
+
export interface SkeletonBaseProps extends HTMLAttributes<HTMLDivElement> {
|
|
15
|
+
/** Placeholder shape. @default 'text' */
|
|
16
|
+
variant?: SkeletonVariant;
|
|
17
|
+
/** Overrides the bar width. Numbers are treated as pixels. */
|
|
18
|
+
width?: number | string;
|
|
19
|
+
/** Overrides the bar height. Numbers are treated as pixels. */
|
|
20
|
+
height?: number | string;
|
|
21
|
+
/** Number of bars to render. @default 1 */
|
|
22
|
+
count?: number;
|
|
23
|
+
/** Optional border-radius token. Ignored for the `circle` variant. */
|
|
24
|
+
radius?: keyof ThemeType['radius'];
|
|
25
|
+
testId?: string;
|
|
26
|
+
ref?: Ref<HTMLDivElement>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SkeletonProps = SkeletonBaseProps & MarginProps;
|
|
30
|
+
|
|
31
|
+
const toCss = (value?: number | string) =>
|
|
32
|
+
typeof value === 'number' ? `${value}px` : value;
|
|
33
|
+
|
|
34
|
+
export const getSkeletonShimmerBackground = (theme: ThemeType) => {
|
|
35
|
+
return `
|
|
36
|
+
linear-gradient(
|
|
37
|
+
270deg,
|
|
38
|
+
color-mix(in srgb, ${theme.colour.bg.default} 0%, transparent) 0%,
|
|
39
|
+
color-mix(in srgb, ${theme.colour.bg.default} 55%, transparent) 50%,
|
|
40
|
+
color-mix(in srgb, ${theme.colour.bg.default} 0%, transparent) 100%
|
|
41
|
+
),
|
|
42
|
+
linear-gradient(
|
|
43
|
+
270deg,
|
|
44
|
+
color-mix(in srgb, ${theme.colour.surface.secondary} 0%, transparent) 0%,
|
|
45
|
+
color-mix(in srgb, ${theme.colour.fill.subtle} 30%, transparent) 25%,
|
|
46
|
+
color-mix(in srgb, ${theme.colour.fill.subtle} 75%, transparent) 50%,
|
|
47
|
+
color-mix(in srgb, ${theme.colour.fill.subtle} 40%, transparent) 75%,
|
|
48
|
+
color-mix(in srgb, ${theme.colour.surface.secondary} 0%, transparent) 100%
|
|
49
|
+
)
|
|
50
|
+
`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const Skeleton = ({
|
|
54
|
+
variant = 'text',
|
|
55
|
+
width,
|
|
56
|
+
height,
|
|
57
|
+
count = 1,
|
|
58
|
+
radius,
|
|
59
|
+
testId = NAME,
|
|
60
|
+
className,
|
|
61
|
+
ref,
|
|
62
|
+
...props
|
|
63
|
+
}: SkeletonProps) => {
|
|
64
|
+
const [theme] = useTheme();
|
|
65
|
+
|
|
66
|
+
const variantDefaults = {
|
|
67
|
+
text: { width: '100%', height: '8px' },
|
|
68
|
+
circle: { width: '72px', height: '72px' },
|
|
69
|
+
rect: { width: '100%', height: '96px' },
|
|
70
|
+
}[variant];
|
|
71
|
+
|
|
72
|
+
const wrapperStyle = css`
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: ${variant === 'text' ? theme.padding.p16 : 0};
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
const barStyle = css`
|
|
79
|
+
position: relative;
|
|
80
|
+
display: block;
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
width: ${toCss(width) ?? variantDefaults.width};
|
|
83
|
+
height: ${toCss(height) ?? variantDefaults.height};
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
border-radius: ${
|
|
86
|
+
variant === 'circle' ? '9999px' : radius ? theme.radius[radius] : 0
|
|
87
|
+
};
|
|
88
|
+
background-color: ${theme.colour.surface.secondary};
|
|
89
|
+
|
|
90
|
+
&::after {
|
|
91
|
+
position: absolute;
|
|
92
|
+
top: 0;
|
|
93
|
+
left: -175px;
|
|
94
|
+
width: 175px;
|
|
95
|
+
height: 100%;
|
|
96
|
+
content: '';
|
|
97
|
+
background: ${getSkeletonShimmerBackground(theme)};
|
|
98
|
+
animation: ${NAME}-sweep 1.4s ease-in-out infinite;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@keyframes ${NAME}-sweep {
|
|
102
|
+
from {
|
|
103
|
+
left: -175px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
to {
|
|
107
|
+
left: 100%;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@media (prefers-reduced-motion: reduce) {
|
|
112
|
+
&::after {
|
|
113
|
+
display: none;
|
|
114
|
+
animation: none;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
const style = cx(NAME, wrapperStyle, marginsStyle(props, theme), className);
|
|
120
|
+
const isRaggedLastLine = (index: number) =>
|
|
121
|
+
variant === 'text' && count > 1 && index === count - 1;
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
{...props}
|
|
126
|
+
ref={ref}
|
|
127
|
+
aria-hidden='true'
|
|
128
|
+
data-testid={testId}
|
|
129
|
+
className={style}
|
|
130
|
+
>
|
|
131
|
+
{Array.from({ length: count }, (_, index) => (
|
|
132
|
+
<span
|
|
133
|
+
key={index}
|
|
134
|
+
className={barStyle}
|
|
135
|
+
style={isRaggedLastLine(index) ? { width: '61.8%' } : undefined}
|
|
136
|
+
/>
|
|
137
|
+
))}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export interface SkeletonSubcomponents {
|
|
143
|
+
Text: typeof SkeletonText;
|
|
144
|
+
Avatar: typeof SkeletonAvatar;
|
|
145
|
+
Input: typeof SkeletonInput;
|
|
146
|
+
TableRow: typeof SkeletonTableRow;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const SkeletonWithSubcomponents = Skeleton as typeof Skeleton &
|
|
150
|
+
SkeletonSubcomponents;
|
|
151
|
+
|
|
152
|
+
SkeletonWithSubcomponents.Text = SkeletonText;
|
|
153
|
+
SkeletonWithSubcomponents.Avatar = SkeletonAvatar;
|
|
154
|
+
SkeletonWithSubcomponents.Input = SkeletonInput;
|
|
155
|
+
SkeletonWithSubcomponents.TableRow = SkeletonTableRow;
|
|
156
|
+
|
|
157
|
+
export { Skeleton };
|
|
158
|
+
export default SkeletonWithSubcomponents;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Skeleton, SkeletonProps } from './Skeleton';
|
|
2
|
+
|
|
3
|
+
export interface SkeletonAvatarProps extends Omit<
|
|
4
|
+
SkeletonProps,
|
|
5
|
+
'variant' | 'width' | 'height' | 'count'
|
|
6
|
+
> {
|
|
7
|
+
/** Matches the `Avatar` size options. @default 72 */
|
|
8
|
+
size?: 32 | 48 | 56 | 72 | 80;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const SkeletonAvatar = ({ size = 72, ...props }: SkeletonAvatarProps) => (
|
|
12
|
+
<Skeleton
|
|
13
|
+
variant='circle'
|
|
14
|
+
width={size}
|
|
15
|
+
height={size}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export default SkeletonAvatar;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Skeleton, SkeletonProps } from './Skeleton';
|
|
2
|
+
|
|
3
|
+
export type SkeletonInputProps = Omit<SkeletonProps, 'variant' | 'count'>;
|
|
4
|
+
|
|
5
|
+
const SkeletonInput = ({ height = 48, ...props }: SkeletonInputProps) => (
|
|
6
|
+
<Skeleton
|
|
7
|
+
variant='rect'
|
|
8
|
+
height={height}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default SkeletonInput;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { HTMLAttributes } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import { useTheme } from '../../theme';
|
|
4
|
+
import { Skeleton } from './Skeleton';
|
|
5
|
+
|
|
6
|
+
export const NAME = 'ucl-uikit-skeleton__table-row';
|
|
7
|
+
|
|
8
|
+
export interface SkeletonTableRowProps extends HTMLAttributes<HTMLTableRowElement> {
|
|
9
|
+
/** Number of cells to render. @default 4 */
|
|
10
|
+
columns?: number;
|
|
11
|
+
testId?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const SkeletonTableRow = ({
|
|
15
|
+
columns = 4,
|
|
16
|
+
testId = NAME,
|
|
17
|
+
className,
|
|
18
|
+
...props
|
|
19
|
+
}: SkeletonTableRowProps) => {
|
|
20
|
+
const [theme] = useTheme();
|
|
21
|
+
|
|
22
|
+
const cellStyle = css`
|
|
23
|
+
padding: ${theme.padding.p12} ${theme.padding.p16};
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<tr
|
|
28
|
+
className={cx(NAME, className)}
|
|
29
|
+
data-testid={testId}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{Array.from({ length: columns }, (_, index) => (
|
|
33
|
+
<td
|
|
34
|
+
key={index}
|
|
35
|
+
className={cellStyle}
|
|
36
|
+
>
|
|
37
|
+
<Skeleton variant='text' />
|
|
38
|
+
</td>
|
|
39
|
+
))}
|
|
40
|
+
</tr>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default SkeletonTableRow;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Skeleton, SkeletonProps } from './Skeleton';
|
|
2
|
+
|
|
3
|
+
export type SkeletonTextProps = Omit<SkeletonProps, 'variant'>;
|
|
4
|
+
|
|
5
|
+
const SkeletonText = ({ count = 4, ...props }: SkeletonTextProps) => (
|
|
6
|
+
<Skeleton
|
|
7
|
+
variant='text'
|
|
8
|
+
count={count}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export default SkeletonText;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { render } from '@testing-library/react';
|
|
3
|
+
import { ThemeContextProvider } from '../../../theme/useTheme';
|
|
4
|
+
import darkTheme from '../../../theme/dark/darkTheme';
|
|
5
|
+
import lightTheme from '../../../theme/light/lightTheme';
|
|
6
|
+
import Skeleton, { getSkeletonShimmerBackground } from '../Skeleton';
|
|
7
|
+
|
|
8
|
+
const wrap = (ui: React.ReactElement) =>
|
|
9
|
+
render(<ThemeContextProvider>{ui}</ThemeContextProvider>);
|
|
10
|
+
|
|
11
|
+
describe('Skeleton', () => {
|
|
12
|
+
test('renders one bar per count', () => {
|
|
13
|
+
const { getByTestId } = wrap(
|
|
14
|
+
<Skeleton
|
|
15
|
+
count={4}
|
|
16
|
+
testId='skeleton'
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
expect(getByTestId('skeleton').childElementCount).toBe(4);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('is hidden from assistive technology', () => {
|
|
24
|
+
const { getByTestId } = wrap(<Skeleton testId='skeleton' />);
|
|
25
|
+
|
|
26
|
+
expect(getByTestId('skeleton')).toHaveAttribute('aria-hidden', 'true');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('applies a custom class name', () => {
|
|
30
|
+
const { getByTestId } = wrap(
|
|
31
|
+
<Skeleton
|
|
32
|
+
className='custom-class'
|
|
33
|
+
testId='skeleton'
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
expect(getByTestId('skeleton')).toHaveClass('custom-class');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('renders a 61.8% final line for multiline text', () => {
|
|
41
|
+
const { getByTestId } = wrap(
|
|
42
|
+
<Skeleton
|
|
43
|
+
count={4}
|
|
44
|
+
testId='skeleton'
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
expect(getByTestId('skeleton').lastElementChild).toHaveStyle({
|
|
49
|
+
width: '61.8%',
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('does not shorten a single text line', () => {
|
|
54
|
+
const { getByTestId } = wrap(<Skeleton testId='skeleton' />);
|
|
55
|
+
|
|
56
|
+
expect(getByTestId('skeleton').firstElementChild).not.toHaveAttribute(
|
|
57
|
+
'style'
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('Skeleton.TableRow renders one cell per column', () => {
|
|
62
|
+
const { getByTestId } = wrap(
|
|
63
|
+
<table>
|
|
64
|
+
<tbody>
|
|
65
|
+
<Skeleton.TableRow
|
|
66
|
+
columns={4}
|
|
67
|
+
testId='skeleton-row'
|
|
68
|
+
/>
|
|
69
|
+
</tbody>
|
|
70
|
+
</table>
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
expect(getByTestId('skeleton-row').querySelectorAll('td')).toHaveLength(4);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test.each([
|
|
77
|
+
['light', lightTheme],
|
|
78
|
+
['dark', darkTheme],
|
|
79
|
+
])('uses semantic theme colours for the %s shimmer', (_, theme) => {
|
|
80
|
+
const shimmer = getSkeletonShimmerBackground(theme);
|
|
81
|
+
|
|
82
|
+
expect(shimmer).toContain(theme.colour.bg.default);
|
|
83
|
+
expect(shimmer).toContain(theme.colour.surface.secondary);
|
|
84
|
+
expect(shimmer).toContain(theme.colour.fill.subtle);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default } from './Skeleton';
|
|
2
|
+
export type { SkeletonProps, SkeletonVariant } from './Skeleton';
|
|
3
|
+
export type { SkeletonAvatarProps } from './SkeletonAvatar';
|
|
4
|
+
export type { SkeletonInputProps } from './SkeletonInput';
|
|
5
|
+
export type { SkeletonTableRowProps } from './SkeletonTableRow';
|
|
6
|
+
export type { SkeletonTextProps } from './SkeletonText';
|
package/lib/components/index.ts
CHANGED
|
@@ -56,6 +56,16 @@ export type {
|
|
|
56
56
|
ProgressBarVariant,
|
|
57
57
|
} from './ProgressBar';
|
|
58
58
|
|
|
59
|
+
export { default as Skeleton } from './Skeleton';
|
|
60
|
+
export type {
|
|
61
|
+
SkeletonProps,
|
|
62
|
+
SkeletonVariant,
|
|
63
|
+
SkeletonTextProps,
|
|
64
|
+
SkeletonAvatarProps,
|
|
65
|
+
SkeletonInputProps,
|
|
66
|
+
SkeletonTableRowProps,
|
|
67
|
+
} from './Skeleton';
|
|
68
|
+
|
|
59
69
|
export { default as Textarea } from './Textarea';
|
|
60
70
|
export type { TextareaProps } from './Textarea';
|
|
61
71
|
|
|
@@ -215,6 +225,9 @@ export type { CalendarProps, CalendarStatuses } from './Calendar';
|
|
|
215
225
|
|
|
216
226
|
export { default as WeekPicker } from './WeekPicker';
|
|
217
227
|
|
|
228
|
+
export { default as DropZone } from './DropZone';
|
|
229
|
+
export type { DropZoneProps, DropZoneRenderProps } from './DropZone';
|
|
230
|
+
|
|
218
231
|
export { default as FileInput } from './FileInput';
|
|
219
232
|
export type { FileInputProps } from './FileInput';
|
|
220
233
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test, vitest } from 'vitest';
|
|
2
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
3
|
+
import { UIKIT_DND_DATA_TYPE, useDraggable } from '..';
|
|
4
|
+
import { clearActiveKeyboardDrag, getActiveKeyboardDrag } from '../useDropZone';
|
|
5
|
+
|
|
6
|
+
const TABLE_CELL_TYPE = 'application/x-ucl-uikit-table-cell';
|
|
7
|
+
|
|
8
|
+
const createDataTransfer = () =>
|
|
9
|
+
({
|
|
10
|
+
effectAllowed: 'all',
|
|
11
|
+
setData: vitest.fn(),
|
|
12
|
+
setDragImage: vitest.fn(),
|
|
13
|
+
}) as unknown as DataTransfer;
|
|
14
|
+
|
|
15
|
+
const DraggableDemo = () => {
|
|
16
|
+
const { getDragProps, isDragging } = useDraggable({
|
|
17
|
+
data: {
|
|
18
|
+
column: 'Status',
|
|
19
|
+
row: 'Research proposal',
|
|
20
|
+
value: 'Approved',
|
|
21
|
+
},
|
|
22
|
+
type: TABLE_CELL_TYPE,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<div
|
|
27
|
+
data-testid='drag-source'
|
|
28
|
+
{...getDragProps()}
|
|
29
|
+
>
|
|
30
|
+
{isDragging ? 'Dragging' : 'Ready'}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const StringDraggableDemo = () => {
|
|
36
|
+
const { getDragProps } = useDraggable({
|
|
37
|
+
data: '123',
|
|
38
|
+
type: TABLE_CELL_TYPE,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
data-testid='string-drag-source'
|
|
44
|
+
{...getDragProps()}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const DisabledDraggableDemo = ({ disabled }: { disabled: boolean }) => {
|
|
50
|
+
const { getDragProps } = useDraggable({
|
|
51
|
+
data: 'Approved',
|
|
52
|
+
disabled,
|
|
53
|
+
type: TABLE_CELL_TYPE,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
data-testid='disabled-drag-source'
|
|
59
|
+
{...getDragProps()}
|
|
60
|
+
/>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
describe('useDraggable', () => {
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
clearActiveKeyboardDrag();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('sets draggable data on drag start', () => {
|
|
70
|
+
const dataTransfer = createDataTransfer();
|
|
71
|
+
|
|
72
|
+
render(<DraggableDemo />);
|
|
73
|
+
|
|
74
|
+
fireEvent.dragStart(screen.getByTestId('drag-source'), {
|
|
75
|
+
dataTransfer,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(dataTransfer.effectAllowed).toBe('move');
|
|
79
|
+
expect(dataTransfer.setData).toHaveBeenCalledWith(
|
|
80
|
+
TABLE_CELL_TYPE,
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
column: 'Status',
|
|
83
|
+
row: 'Research proposal',
|
|
84
|
+
value: 'Approved',
|
|
85
|
+
})
|
|
86
|
+
);
|
|
87
|
+
expect(dataTransfer.setData).toHaveBeenCalledWith(
|
|
88
|
+
UIKIT_DND_DATA_TYPE,
|
|
89
|
+
JSON.stringify({
|
|
90
|
+
data: JSON.stringify({
|
|
91
|
+
column: 'Status',
|
|
92
|
+
row: 'Research proposal',
|
|
93
|
+
value: 'Approved',
|
|
94
|
+
}),
|
|
95
|
+
type: TABLE_CELL_TYPE,
|
|
96
|
+
})
|
|
97
|
+
);
|
|
98
|
+
expect(screen.getByText('Dragging')).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('clears dragging state on drag end', () => {
|
|
102
|
+
render(<DraggableDemo />);
|
|
103
|
+
|
|
104
|
+
fireEvent.dragStart(screen.getByTestId('drag-source'), {
|
|
105
|
+
dataTransfer: createDataTransfer(),
|
|
106
|
+
});
|
|
107
|
+
fireEvent.dragEnd(screen.getByTestId('drag-source'));
|
|
108
|
+
|
|
109
|
+
expect(screen.getByText('Ready')).toBeInTheDocument();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('serializes strings as JSON strings', () => {
|
|
113
|
+
const dataTransfer = createDataTransfer();
|
|
114
|
+
|
|
115
|
+
render(<StringDraggableDemo />);
|
|
116
|
+
|
|
117
|
+
fireEvent.dragStart(screen.getByTestId('string-drag-source'), {
|
|
118
|
+
dataTransfer,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(dataTransfer.setData).toHaveBeenCalledWith(
|
|
122
|
+
TABLE_CELL_TYPE,
|
|
123
|
+
JSON.stringify('123')
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('supports keyboard drag start and cancel', () => {
|
|
128
|
+
render(<DraggableDemo />);
|
|
129
|
+
|
|
130
|
+
fireEvent.keyDown(screen.getByTestId('drag-source'), { key: 'Enter' });
|
|
131
|
+
|
|
132
|
+
expect(screen.getByText('Dragging')).toBeInTheDocument();
|
|
133
|
+
|
|
134
|
+
fireEvent.keyDown(screen.getByTestId('drag-source'), { key: 'Escape' });
|
|
135
|
+
|
|
136
|
+
expect(screen.getByText('Ready')).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('does not replace an active keyboard drag', () => {
|
|
140
|
+
render(
|
|
141
|
+
<>
|
|
142
|
+
<DraggableDemo />
|
|
143
|
+
<StringDraggableDemo />
|
|
144
|
+
</>
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
fireEvent.keyDown(screen.getByTestId('drag-source'), { key: 'Enter' });
|
|
148
|
+
fireEvent.keyDown(screen.getByTestId('string-drag-source'), {
|
|
149
|
+
key: 'Enter',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(getActiveKeyboardDrag()?.data).toEqual({
|
|
153
|
+
column: 'Status',
|
|
154
|
+
row: 'Research proposal',
|
|
155
|
+
value: 'Approved',
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('clears an owned keyboard drag on unmount', () => {
|
|
160
|
+
const { unmount } = render(<DraggableDemo />);
|
|
161
|
+
|
|
162
|
+
fireEvent.keyDown(screen.getByTestId('drag-source'), { key: 'Enter' });
|
|
163
|
+
|
|
164
|
+
expect(getActiveKeyboardDrag()).not.toBeNull();
|
|
165
|
+
|
|
166
|
+
unmount();
|
|
167
|
+
|
|
168
|
+
expect(getActiveKeyboardDrag()).toBeNull();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('clears an owned keyboard drag when disabled', () => {
|
|
172
|
+
const { rerender } = render(<DisabledDraggableDemo disabled={false} />);
|
|
173
|
+
|
|
174
|
+
fireEvent.keyDown(screen.getByTestId('disabled-drag-source'), {
|
|
175
|
+
key: 'Enter',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
expect(getActiveKeyboardDrag()).not.toBeNull();
|
|
179
|
+
|
|
180
|
+
rerender(<DisabledDraggableDemo disabled />);
|
|
181
|
+
|
|
182
|
+
expect(getActiveKeyboardDrag()).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
});
|
package/lib/hooks/index.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
export { useFocusTrap } from './useFocusTrap';
|
|
2
|
+
export { default as useDraggable } from './useDraggable';
|
|
3
|
+
export type {
|
|
4
|
+
DraggableProps,
|
|
5
|
+
UseDraggableOptions,
|
|
6
|
+
UseDraggableReturn,
|
|
7
|
+
} from './useDraggable';
|
|
8
|
+
export { default as useDropZone, UIKIT_DND_DATA_TYPE } from './useDropZone';
|
|
9
|
+
export type {
|
|
10
|
+
DragData,
|
|
11
|
+
DropZoneAcceptedTypes,
|
|
12
|
+
DropZoneDrop,
|
|
13
|
+
DropZoneEvent,
|
|
14
|
+
DropZoneRejection,
|
|
15
|
+
DropZoneRejectionReason,
|
|
16
|
+
DropZoneRootProps,
|
|
17
|
+
KeyboardDragData,
|
|
18
|
+
UseDropZoneOptions,
|
|
19
|
+
UseDropZoneReturn,
|
|
20
|
+
} from './useDropZone';
|
|
2
21
|
export { default as useMediaQuery } from './useMediaQuery';
|