uikit-react-public 0.45.5 → 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/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 +7229 -6665
- 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/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,72 @@
|
|
|
1
|
+
import * as DropZoneStories from './DropZone.stories';
|
|
2
|
+
import {
|
|
3
|
+
Meta,
|
|
4
|
+
Title,
|
|
5
|
+
Subtitle,
|
|
6
|
+
Description,
|
|
7
|
+
Canvas,
|
|
8
|
+
Controls,
|
|
9
|
+
ArgTypes,
|
|
10
|
+
} from '@storybook/addon-docs/blocks';
|
|
11
|
+
|
|
12
|
+
export const usage = {
|
|
13
|
+
default: `<DropZone
|
|
14
|
+
acceptedTypes='application/x-ucl-uikit-table-cell'
|
|
15
|
+
onDrop={(drop) => console.log(drop.data)}
|
|
16
|
+
/>`,
|
|
17
|
+
sourceAndTarget: `const TABLE_CELL_TYPE = 'application/x-ucl-uikit-table-cell';
|
|
18
|
+
|
|
19
|
+
const { getDragProps } = useDraggable({
|
|
20
|
+
type: TABLE_CELL_TYPE,
|
|
21
|
+
data: { row: 'Research proposal', column: 'Status', value: 'Approved' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
<Table.Cell {...getDragProps()}>Approved</Table.Cell>
|
|
25
|
+
|
|
26
|
+
<DropZone
|
|
27
|
+
acceptedTypes={TABLE_CELL_TYPE}
|
|
28
|
+
onDrop={(drop) => setDroppedCell(drop.data)}
|
|
29
|
+
/>`,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
<Meta of={DropZoneStories} />
|
|
33
|
+
<Title />
|
|
34
|
+
<Subtitle>A themed drop target built on the public useDropZone hook</Subtitle>
|
|
35
|
+
|
|
36
|
+
<Description>
|
|
37
|
+
`DropZone` accepts generic drag data. Use it with `useDraggable` for
|
|
38
|
+
UIKit-managed drag data, or pass native drag handlers when integrating with
|
|
39
|
+
another drag source. UIKit-managed draggables can be picked up with Space or
|
|
40
|
+
Enter, cancelled with Escape, and dropped on a focused drop target with Space
|
|
41
|
+
or Enter.
|
|
42
|
+
</Description>
|
|
43
|
+
|
|
44
|
+
<Canvas
|
|
45
|
+
of={DropZoneStories.Default}
|
|
46
|
+
source={{ code: usage.default }}
|
|
47
|
+
/>
|
|
48
|
+
|
|
49
|
+
## Source And Target
|
|
50
|
+
|
|
51
|
+
Use `useDraggable` on a source element and `DropZone` on the target.
|
|
52
|
+
|
|
53
|
+
For workflows such as list reordering, provide an equivalent button or menu
|
|
54
|
+
action when the drag operation is not the only reasonable way to complete the
|
|
55
|
+
task.
|
|
56
|
+
|
|
57
|
+
<Canvas
|
|
58
|
+
of={DropZoneStories.WithTableCell}
|
|
59
|
+
source={{ code: usage.sourceAndTarget }}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
## States
|
|
63
|
+
|
|
64
|
+
<Canvas of={DropZoneStories.Disabled} />
|
|
65
|
+
|
|
66
|
+
## Props
|
|
67
|
+
|
|
68
|
+
<ArgTypes of={DropZoneStories} />
|
|
69
|
+
|
|
70
|
+
## Interactive Controls
|
|
71
|
+
|
|
72
|
+
<Controls of={DropZoneStories.Default} />
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { css } from '@emotion/css';
|
|
4
|
+
import DropZone from './DropZone';
|
|
5
|
+
import Text from '../Text';
|
|
6
|
+
import { useDraggable } from '../../hooks';
|
|
7
|
+
|
|
8
|
+
const TABLE_CELL_TYPE = 'application/x-ucl-uikit-table-cell';
|
|
9
|
+
|
|
10
|
+
interface TableCellDragData {
|
|
11
|
+
column: string;
|
|
12
|
+
row: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const meta = {
|
|
17
|
+
title: 'Components/DropZone',
|
|
18
|
+
component: DropZone,
|
|
19
|
+
parameters: {
|
|
20
|
+
layout: 'centered',
|
|
21
|
+
},
|
|
22
|
+
argTypes: {
|
|
23
|
+
acceptedTypes: {
|
|
24
|
+
control: { type: 'object' },
|
|
25
|
+
},
|
|
26
|
+
disabled: {
|
|
27
|
+
control: { type: 'boolean' },
|
|
28
|
+
},
|
|
29
|
+
dropEffect: {
|
|
30
|
+
options: ['copy', 'move', 'link'],
|
|
31
|
+
control: { type: 'select' },
|
|
32
|
+
},
|
|
33
|
+
testId: {
|
|
34
|
+
control: { type: 'text' },
|
|
35
|
+
},
|
|
36
|
+
className: {
|
|
37
|
+
control: false,
|
|
38
|
+
},
|
|
39
|
+
children: {
|
|
40
|
+
control: false,
|
|
41
|
+
},
|
|
42
|
+
onDrop: {
|
|
43
|
+
action: 'drop',
|
|
44
|
+
},
|
|
45
|
+
onReject: {
|
|
46
|
+
action: 'reject',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
args: {
|
|
50
|
+
acceptedTypes: TABLE_CELL_TYPE,
|
|
51
|
+
},
|
|
52
|
+
tags: ['autodocs'],
|
|
53
|
+
} satisfies Meta<typeof DropZone>;
|
|
54
|
+
|
|
55
|
+
export default meta;
|
|
56
|
+
type Story = StoryObj<typeof meta>;
|
|
57
|
+
|
|
58
|
+
const storyContainerStyle = css`
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: minmax(180px, 1fr) minmax(260px, 1.5fr);
|
|
61
|
+
gap: 24px;
|
|
62
|
+
width: min(720px, 90vw);
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
const draggableCellStyle = css`
|
|
66
|
+
display: inline-flex;
|
|
67
|
+
align-items: center;
|
|
68
|
+
min-height: 40px;
|
|
69
|
+
padding: 0 12px;
|
|
70
|
+
border: 1px solid #d8dde8;
|
|
71
|
+
border-radius: 4px;
|
|
72
|
+
cursor: grab;
|
|
73
|
+
user-select: none;
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
const DraggableCell = ({ data }: { data: TableCellDragData }) => {
|
|
77
|
+
const { getDragProps, isDragging } = useDraggable({
|
|
78
|
+
data,
|
|
79
|
+
type: TABLE_CELL_TYPE,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<span
|
|
84
|
+
{...getDragProps({
|
|
85
|
+
className: draggableCellStyle,
|
|
86
|
+
})}
|
|
87
|
+
aria-label={`Drag ${data.value}`}
|
|
88
|
+
>
|
|
89
|
+
{isDragging ? 'Dragging...' : data.value}
|
|
90
|
+
</span>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const TableCellDropExample = () => {
|
|
95
|
+
const [droppedCell, setDroppedCell] = useState<TableCellDragData | null>(
|
|
96
|
+
null
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className={storyContainerStyle}>
|
|
101
|
+
<DraggableCell
|
|
102
|
+
data={{
|
|
103
|
+
column: 'Status',
|
|
104
|
+
row: 'Research proposal',
|
|
105
|
+
value: 'Approved',
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
<DropZone<TableCellDragData>
|
|
109
|
+
acceptedTypes={TABLE_CELL_TYPE}
|
|
110
|
+
onDrop={(drop) => setDroppedCell(drop.data)}
|
|
111
|
+
>
|
|
112
|
+
{({ isDragActive }) => (
|
|
113
|
+
<Text level='md-semibold'>
|
|
114
|
+
{droppedCell
|
|
115
|
+
? `${droppedCell.row}: ${droppedCell.value}`
|
|
116
|
+
: isDragActive
|
|
117
|
+
? 'Release to drop the table cell'
|
|
118
|
+
: 'Drop a table cell here'}
|
|
119
|
+
</Text>
|
|
120
|
+
)}
|
|
121
|
+
</DropZone>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export const Default: Story = {
|
|
127
|
+
render: (args) => <DropZone {...args} />,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const WithTableCell: Story = {
|
|
131
|
+
render: () => <TableCellDropExample />,
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const Disabled: Story = {
|
|
135
|
+
args: {
|
|
136
|
+
disabled: true,
|
|
137
|
+
},
|
|
138
|
+
render: (args) => <DropZone {...args} />,
|
|
139
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode, Ref, memo } from 'react';
|
|
2
|
+
import { css, cx } from '@emotion/css';
|
|
3
|
+
import useTheme from '../../theme/useTheme';
|
|
4
|
+
import useDropZone, {
|
|
5
|
+
DropZoneAcceptedTypes,
|
|
6
|
+
DropZoneDrop,
|
|
7
|
+
DropZoneRejection,
|
|
8
|
+
UseDropZoneOptions,
|
|
9
|
+
} from '../../hooks/useDropZone';
|
|
10
|
+
import marginsStyle, { MarginProps } from '../common/marginsStyle';
|
|
11
|
+
import Icon from '../Icon';
|
|
12
|
+
import Paragraph from '../Paragraph';
|
|
13
|
+
import Text from '../Text';
|
|
14
|
+
|
|
15
|
+
export const NAME = 'ucl-uikit-drop-zone';
|
|
16
|
+
|
|
17
|
+
export interface DropZoneRenderProps<TData = unknown> {
|
|
18
|
+
activeTypes: string[];
|
|
19
|
+
isDragAccept: boolean;
|
|
20
|
+
isDragActive: boolean;
|
|
21
|
+
isDragReject: boolean;
|
|
22
|
+
lastDrop: DropZoneDrop<TData> | null;
|
|
23
|
+
lastRejection: DropZoneRejection<TData> | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DropZoneBaseProps<TData = unknown>
|
|
27
|
+
extends
|
|
28
|
+
Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onDrop'>,
|
|
29
|
+
UseDropZoneOptions<TData> {
|
|
30
|
+
children?: ReactNode | ((props: DropZoneRenderProps<TData>) => ReactNode);
|
|
31
|
+
ref?: Ref<HTMLDivElement>;
|
|
32
|
+
testId?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type DropZoneProps<TData = unknown> = DropZoneBaseProps<TData> &
|
|
36
|
+
MarginProps;
|
|
37
|
+
|
|
38
|
+
const getDefaultDescription = (acceptedTypes?: DropZoneAcceptedTypes) => {
|
|
39
|
+
if (!acceptedTypes) return 'Drop an item here';
|
|
40
|
+
|
|
41
|
+
const values =
|
|
42
|
+
typeof acceptedTypes === 'string'
|
|
43
|
+
? acceptedTypes.split(',')
|
|
44
|
+
: Array.from(acceptedTypes);
|
|
45
|
+
const normalizedValues = values.map((value) => value.trim()).filter(Boolean);
|
|
46
|
+
|
|
47
|
+
return normalizedValues.length > 0
|
|
48
|
+
? `Drop ${normalizedValues.join(', ')} here`
|
|
49
|
+
: 'Drop an item here';
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const DropZone = <TData = unknown,>({
|
|
53
|
+
'aria-label': ariaLabel,
|
|
54
|
+
'aria-labelledby': ariaLabelledBy,
|
|
55
|
+
acceptedTypes,
|
|
56
|
+
canDrop,
|
|
57
|
+
children,
|
|
58
|
+
className,
|
|
59
|
+
disabled = false,
|
|
60
|
+
dropEffect = 'move',
|
|
61
|
+
keyboardDrop,
|
|
62
|
+
onDrop,
|
|
63
|
+
onReject,
|
|
64
|
+
parseData,
|
|
65
|
+
ref,
|
|
66
|
+
role,
|
|
67
|
+
testId = NAME,
|
|
68
|
+
...props
|
|
69
|
+
}: DropZoneProps<TData>) => {
|
|
70
|
+
const [theme] = useTheme();
|
|
71
|
+
const {
|
|
72
|
+
activeTypes,
|
|
73
|
+
getRootProps,
|
|
74
|
+
isDragAccept,
|
|
75
|
+
isDragActive,
|
|
76
|
+
isDragReject,
|
|
77
|
+
lastDrop,
|
|
78
|
+
lastRejection,
|
|
79
|
+
} = useDropZone<TData>({
|
|
80
|
+
acceptedTypes,
|
|
81
|
+
canDrop,
|
|
82
|
+
disabled,
|
|
83
|
+
dropEffect,
|
|
84
|
+
keyboardDrop,
|
|
85
|
+
onDrop,
|
|
86
|
+
onReject,
|
|
87
|
+
parseData,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const baseStyle = css`
|
|
91
|
+
box-sizing: border-box;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
align-items: center;
|
|
95
|
+
justify-content: center;
|
|
96
|
+
gap: ${theme.padding.p8};
|
|
97
|
+
width: 100%;
|
|
98
|
+
min-height: 160px;
|
|
99
|
+
padding: ${theme.padding.p24};
|
|
100
|
+
color: ${theme.colour.text.default};
|
|
101
|
+
background-color: ${theme.colour.surface.default};
|
|
102
|
+
border: ${theme.border.b2} dashed ${theme.colour.border.default};
|
|
103
|
+
border-radius: ${theme.radius.r4};
|
|
104
|
+
outline: none;
|
|
105
|
+
text-align: center;
|
|
106
|
+
|
|
107
|
+
&:focus-visible {
|
|
108
|
+
box-shadow: ${theme.boxShadow.focus};
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
const activeStyle = css`
|
|
113
|
+
background-color: ${theme.colour.surface.brand};
|
|
114
|
+
border-color: ${theme.colour.border.brand};
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
const acceptStyle = css`
|
|
118
|
+
border-color: ${theme.colour.border.success};
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
const rejectStyle = css`
|
|
122
|
+
background-color: ${theme.colour.surface.critical};
|
|
123
|
+
border-color: ${theme.colour.border.critical};
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const disabledStyle = css`
|
|
127
|
+
color: ${theme.colour.text.disabled};
|
|
128
|
+
background-color: ${theme.colour.fill.disabled};
|
|
129
|
+
border-color: ${theme.colour.border.disabled};
|
|
130
|
+
cursor: not-allowed;
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const iconStyle = css`
|
|
134
|
+
color: ${
|
|
135
|
+
disabled
|
|
136
|
+
? theme.colour.icon.disabled
|
|
137
|
+
: isDragReject
|
|
138
|
+
? theme.colour.icon.critical
|
|
139
|
+
: isDragAccept
|
|
140
|
+
? theme.colour.icon.success
|
|
141
|
+
: theme.colour.icon.brand
|
|
142
|
+
};
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const style = cx(
|
|
146
|
+
NAME,
|
|
147
|
+
baseStyle,
|
|
148
|
+
marginsStyle(props, theme),
|
|
149
|
+
isDragActive && activeStyle,
|
|
150
|
+
isDragAccept && acceptStyle,
|
|
151
|
+
isDragReject && rejectStyle,
|
|
152
|
+
disabled && disabledStyle,
|
|
153
|
+
className
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const renderProps: DropZoneRenderProps<TData> = {
|
|
157
|
+
activeTypes,
|
|
158
|
+
isDragAccept,
|
|
159
|
+
isDragActive,
|
|
160
|
+
isDragReject,
|
|
161
|
+
lastDrop,
|
|
162
|
+
lastRejection,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
data-testid={testId}
|
|
168
|
+
{...getRootProps({
|
|
169
|
+
...props,
|
|
170
|
+
'aria-label': ariaLabel ?? (ariaLabelledBy ? undefined : 'Drop zone'),
|
|
171
|
+
'aria-labelledby': ariaLabelledBy,
|
|
172
|
+
ref,
|
|
173
|
+
role: role ?? 'group',
|
|
174
|
+
className: style,
|
|
175
|
+
})}
|
|
176
|
+
>
|
|
177
|
+
{typeof children === 'function' ? (
|
|
178
|
+
children(renderProps)
|
|
179
|
+
) : (
|
|
180
|
+
<>
|
|
181
|
+
<Icon.Move
|
|
182
|
+
aria-hidden='true'
|
|
183
|
+
className={iconStyle}
|
|
184
|
+
/>
|
|
185
|
+
{children ?? (
|
|
186
|
+
<>
|
|
187
|
+
<Paragraph
|
|
188
|
+
as='div'
|
|
189
|
+
level='md-semibold'
|
|
190
|
+
noMargins
|
|
191
|
+
>
|
|
192
|
+
{isDragReject
|
|
193
|
+
? 'This item cannot be dropped here'
|
|
194
|
+
: isDragActive
|
|
195
|
+
? 'Release to drop'
|
|
196
|
+
: getDefaultDescription(acceptedTypes)}
|
|
197
|
+
</Paragraph>
|
|
198
|
+
<Text level='sm'>
|
|
199
|
+
{isDragReject
|
|
200
|
+
? 'This drop target does not accept that item'
|
|
201
|
+
: 'Drag a compatible item into this area'}
|
|
202
|
+
</Text>
|
|
203
|
+
</>
|
|
204
|
+
)}
|
|
205
|
+
</>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const MemoizedDropZone = memo(DropZone) as typeof DropZone;
|
|
212
|
+
|
|
213
|
+
export default MemoizedDropZone;
|