uikit-react-public 0.45.5 → 0.47.1
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/Dialog/BaseDialog.d.ts +1 -0
- 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 +7308 -6737
- package/lib/components/Dialog/BaseDialog.tsx +25 -15
- 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,418 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DragEvent,
|
|
3
|
+
HTMLAttributes,
|
|
4
|
+
KeyboardEvent,
|
|
5
|
+
Ref,
|
|
6
|
+
RefCallback,
|
|
7
|
+
useCallback,
|
|
8
|
+
useEffect,
|
|
9
|
+
useMemo,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
} from 'react';
|
|
13
|
+
|
|
14
|
+
export const UIKIT_DND_DATA_TYPE = 'application/x-ucl-uikit-dnd';
|
|
15
|
+
|
|
16
|
+
export type DropZoneAcceptedTypes = string | readonly string[];
|
|
17
|
+
|
|
18
|
+
export type DropZoneRejectionReason = 'invalid-type' | 'cannot-drop';
|
|
19
|
+
|
|
20
|
+
export interface DragData<TData = unknown> {
|
|
21
|
+
data: TData;
|
|
22
|
+
rawData?: string;
|
|
23
|
+
type: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface KeyboardDragData<TData = unknown> extends DragData<TData> {
|
|
27
|
+
id: string;
|
|
28
|
+
rawData: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DropZoneDrop<TData = unknown> extends DragData<TData> {
|
|
32
|
+
dataTransfer: DataTransfer | null;
|
|
33
|
+
files: File[];
|
|
34
|
+
rawData: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DropZoneRejection<TData = unknown> {
|
|
38
|
+
drop: DropZoneDrop<TData> | null;
|
|
39
|
+
reason: DropZoneRejectionReason;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface UseDropZoneOptions<TData = unknown> {
|
|
43
|
+
acceptedTypes?: DropZoneAcceptedTypes;
|
|
44
|
+
canDrop?: (drop: DropZoneDrop<TData>) => boolean;
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
dropEffect?: DataTransfer['dropEffect'];
|
|
47
|
+
keyboardDrop?: boolean;
|
|
48
|
+
onDrop?: (drop: DropZoneDrop<TData>, event: DropZoneEvent) => void;
|
|
49
|
+
onReject?: (
|
|
50
|
+
rejection: DropZoneRejection<TData>,
|
|
51
|
+
event: DropZoneEvent
|
|
52
|
+
) => void;
|
|
53
|
+
parseData?: (rawData: string, type: string) => TData;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type DropZoneEvent = DragEvent<HTMLElement> | KeyboardEvent<HTMLElement>;
|
|
57
|
+
|
|
58
|
+
export interface DropZoneRootProps<
|
|
59
|
+
TElement extends HTMLElement,
|
|
60
|
+
> extends HTMLAttributes<TElement> {
|
|
61
|
+
ref: RefCallback<TElement>;
|
|
62
|
+
'aria-disabled'?: boolean;
|
|
63
|
+
'data-drag-active': boolean;
|
|
64
|
+
'data-drag-accept': boolean;
|
|
65
|
+
'data-drag-reject': boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface UseDropZoneReturn<TData = unknown> {
|
|
69
|
+
activeTypes: string[];
|
|
70
|
+
getRootProps: <TElement extends HTMLElement = HTMLDivElement>(
|
|
71
|
+
props?: HTMLAttributes<TElement> & { ref?: Ref<TElement> }
|
|
72
|
+
) => DropZoneRootProps<TElement>;
|
|
73
|
+
isDragAccept: boolean;
|
|
74
|
+
isDragActive: boolean;
|
|
75
|
+
isDragReject: boolean;
|
|
76
|
+
lastDrop: DropZoneDrop<TData> | null;
|
|
77
|
+
lastRejection: DropZoneRejection<TData> | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const normalizeAcceptedTypes = (acceptedTypes?: DropZoneAcceptedTypes) => {
|
|
81
|
+
if (!acceptedTypes) return [];
|
|
82
|
+
|
|
83
|
+
const values =
|
|
84
|
+
typeof acceptedTypes === 'string'
|
|
85
|
+
? acceptedTypes.split(',')
|
|
86
|
+
: Array.from(acceptedTypes);
|
|
87
|
+
|
|
88
|
+
return values.map((value) => value.trim()).filter(Boolean);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const getDataTransferTypes = (dataTransfer: DataTransfer) =>
|
|
92
|
+
Array.from(dataTransfer.types ?? []);
|
|
93
|
+
|
|
94
|
+
const findDropType = (types: string[], acceptedTypes: string[]) => {
|
|
95
|
+
if (acceptedTypes.length === 0) {
|
|
96
|
+
return types[0] ?? null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return acceptedTypes.find((type) => types.includes(type)) ?? null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const parseDefaultData = (rawData: string) => {
|
|
103
|
+
if (!rawData) return rawData;
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(rawData);
|
|
107
|
+
} catch {
|
|
108
|
+
return rawData;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getDrop = <TData>(
|
|
113
|
+
dataTransfer: DataTransfer,
|
|
114
|
+
acceptedTypes: string[],
|
|
115
|
+
parseData?: (rawData: string, type: string) => TData
|
|
116
|
+
): DropZoneDrop<TData> | null => {
|
|
117
|
+
const types = getDataTransferTypes(dataTransfer);
|
|
118
|
+
const type = findDropType(types, acceptedTypes);
|
|
119
|
+
|
|
120
|
+
if (!type) return null;
|
|
121
|
+
|
|
122
|
+
const rawData = type === 'Files' ? '' : dataTransfer.getData(type);
|
|
123
|
+
const data = parseData
|
|
124
|
+
? parseData(rawData, type)
|
|
125
|
+
: (parseDefaultData(rawData) as TData);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
data,
|
|
129
|
+
dataTransfer,
|
|
130
|
+
files: Array.from(dataTransfer.files ?? []),
|
|
131
|
+
rawData,
|
|
132
|
+
type,
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
let activeKeyboardDrag: KeyboardDragData<unknown> | null = null;
|
|
137
|
+
const keyboardDragListeners = new Set<() => void>();
|
|
138
|
+
|
|
139
|
+
export const getActiveKeyboardDrag = () => activeKeyboardDrag;
|
|
140
|
+
|
|
141
|
+
export const setActiveKeyboardDrag = (dragData: KeyboardDragData<unknown>) => {
|
|
142
|
+
activeKeyboardDrag = dragData;
|
|
143
|
+
keyboardDragListeners.forEach((listener) => listener());
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const clearActiveKeyboardDrag = () => {
|
|
147
|
+
activeKeyboardDrag = null;
|
|
148
|
+
keyboardDragListeners.forEach((listener) => listener());
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export const subscribeToKeyboardDrag = (listener: () => void) => {
|
|
152
|
+
keyboardDragListeners.add(listener);
|
|
153
|
+
|
|
154
|
+
return () => {
|
|
155
|
+
keyboardDragListeners.delete(listener);
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const setRef = <TValue>(ref: Ref<TValue> | undefined, value: TValue) => {
|
|
160
|
+
if (!ref) return;
|
|
161
|
+
|
|
162
|
+
if (typeof ref === 'function') {
|
|
163
|
+
ref(value);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
ref.current = value;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const useDropZone = <TData = unknown>({
|
|
171
|
+
acceptedTypes,
|
|
172
|
+
canDrop,
|
|
173
|
+
disabled = false,
|
|
174
|
+
dropEffect = 'move',
|
|
175
|
+
keyboardDrop = true,
|
|
176
|
+
onDrop: onDropCallback,
|
|
177
|
+
onReject,
|
|
178
|
+
parseData,
|
|
179
|
+
}: UseDropZoneOptions<TData> = {}): UseDropZoneReturn<TData> => {
|
|
180
|
+
const rootRef = useRef<HTMLElement | null>(null);
|
|
181
|
+
const dragTargetsRef = useRef<EventTarget[]>([]);
|
|
182
|
+
const [activeTypes, setActiveTypes] = useState<string[]>([]);
|
|
183
|
+
const [isDragActive, setIsDragActive] = useState(false);
|
|
184
|
+
const [isDragAccept, setIsDragAccept] = useState(false);
|
|
185
|
+
const [isDragReject, setIsDragReject] = useState(false);
|
|
186
|
+
const [lastDrop, setLastDrop] = useState<DropZoneDrop<TData> | null>(null);
|
|
187
|
+
const [lastRejection, setLastRejection] =
|
|
188
|
+
useState<DropZoneRejection<TData> | null>(null);
|
|
189
|
+
const normalizedAcceptedTypes = useMemo(
|
|
190
|
+
() => normalizeAcceptedTypes(acceptedTypes),
|
|
191
|
+
[acceptedTypes]
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const resetDragState = useCallback(() => {
|
|
195
|
+
dragTargetsRef.current = [];
|
|
196
|
+
setActiveTypes([]);
|
|
197
|
+
setIsDragActive(false);
|
|
198
|
+
setIsDragAccept(false);
|
|
199
|
+
setIsDragReject(false);
|
|
200
|
+
}, []);
|
|
201
|
+
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
const syncKeyboardDragState = () => {
|
|
204
|
+
const keyboardDrag = getActiveKeyboardDrag();
|
|
205
|
+
|
|
206
|
+
if (!keyboardDrag || disabled || !keyboardDrop) {
|
|
207
|
+
resetDragState();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const nextActiveTypes = [keyboardDrag.type];
|
|
212
|
+
const hasAcceptedType =
|
|
213
|
+
findDropType(nextActiveTypes, normalizedAcceptedTypes) !== null;
|
|
214
|
+
|
|
215
|
+
setActiveTypes(nextActiveTypes);
|
|
216
|
+
setIsDragActive(true);
|
|
217
|
+
setIsDragAccept(hasAcceptedType);
|
|
218
|
+
setIsDragReject(!hasAcceptedType);
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
syncKeyboardDragState();
|
|
222
|
+
|
|
223
|
+
return subscribeToKeyboardDrag(syncKeyboardDragState);
|
|
224
|
+
}, [disabled, keyboardDrop, normalizedAcceptedTypes, resetDragState]);
|
|
225
|
+
|
|
226
|
+
const processDrop = useCallback(
|
|
227
|
+
(drop: DropZoneDrop<TData> | null, event: DropZoneEvent) => {
|
|
228
|
+
if (!drop) {
|
|
229
|
+
const rejection = {
|
|
230
|
+
drop,
|
|
231
|
+
reason: 'invalid-type' as const,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
setLastDrop(null);
|
|
235
|
+
setLastRejection(rejection);
|
|
236
|
+
onReject?.(rejection, event);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (canDrop && !canDrop(drop)) {
|
|
241
|
+
const rejection = {
|
|
242
|
+
drop,
|
|
243
|
+
reason: 'cannot-drop' as const,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
setLastDrop(null);
|
|
247
|
+
setLastRejection(rejection);
|
|
248
|
+
onReject?.(rejection, event);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
setLastDrop(drop);
|
|
253
|
+
setLastRejection(null);
|
|
254
|
+
onDropCallback?.(drop, event);
|
|
255
|
+
},
|
|
256
|
+
[canDrop, onDropCallback, onReject]
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const getRootProps = useCallback(
|
|
260
|
+
<TElement extends HTMLElement = HTMLDivElement>({
|
|
261
|
+
onDragEnter: userOnDragEnter,
|
|
262
|
+
onDragLeave: userOnDragLeave,
|
|
263
|
+
onDragOver: userOnDragOver,
|
|
264
|
+
onDrop: userOnDrop,
|
|
265
|
+
onKeyDown: userOnKeyDown,
|
|
266
|
+
ref: userRef,
|
|
267
|
+
tabIndex,
|
|
268
|
+
...props
|
|
269
|
+
}: HTMLAttributes<TElement> & { ref?: Ref<TElement> } = {}) => {
|
|
270
|
+
const ref: RefCallback<TElement> = (element) => {
|
|
271
|
+
rootRef.current = element;
|
|
272
|
+
setRef(userRef, element);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
const onDragEnter = (event: DragEvent<TElement>) => {
|
|
276
|
+
userOnDragEnter?.(event);
|
|
277
|
+
if (disabled || event.defaultPrevented) return;
|
|
278
|
+
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
|
|
281
|
+
const nextActiveTypes = getDataTransferTypes(event.dataTransfer);
|
|
282
|
+
const hasAcceptedType =
|
|
283
|
+
findDropType(nextActiveTypes, normalizedAcceptedTypes) !== null;
|
|
284
|
+
|
|
285
|
+
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
286
|
+
setActiveTypes(nextActiveTypes);
|
|
287
|
+
setIsDragActive(true);
|
|
288
|
+
setIsDragAccept(hasAcceptedType);
|
|
289
|
+
setIsDragReject(!hasAcceptedType);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const onDragOver = (event: DragEvent<TElement>) => {
|
|
293
|
+
userOnDragOver?.(event);
|
|
294
|
+
if (disabled || event.defaultPrevented) return;
|
|
295
|
+
|
|
296
|
+
event.preventDefault();
|
|
297
|
+
event.dataTransfer.dropEffect = dropEffect;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const onDragLeave = (event: DragEvent<TElement>) => {
|
|
301
|
+
userOnDragLeave?.(event);
|
|
302
|
+
if (disabled || event.defaultPrevented) return;
|
|
303
|
+
|
|
304
|
+
event.preventDefault();
|
|
305
|
+
|
|
306
|
+
dragTargetsRef.current = dragTargetsRef.current.filter(
|
|
307
|
+
(target) => target !== event.target
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
if (dragTargetsRef.current.length === 0) {
|
|
311
|
+
resetDragState();
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const onDrop = (event: DragEvent<TElement>) => {
|
|
316
|
+
userOnDrop?.(event);
|
|
317
|
+
if (disabled || event.defaultPrevented) return;
|
|
318
|
+
|
|
319
|
+
event.preventDefault();
|
|
320
|
+
resetDragState();
|
|
321
|
+
|
|
322
|
+
processDrop(
|
|
323
|
+
getDrop(event.dataTransfer, normalizedAcceptedTypes, parseData),
|
|
324
|
+
event
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const onKeyDown = (event: KeyboardEvent<TElement>) => {
|
|
329
|
+
const keyboardDrag = getActiveKeyboardDrag();
|
|
330
|
+
|
|
331
|
+
userOnKeyDown?.(event);
|
|
332
|
+
if (
|
|
333
|
+
disabled ||
|
|
334
|
+
!keyboardDrop ||
|
|
335
|
+
event.defaultPrevented ||
|
|
336
|
+
(event.key !== 'Enter' && event.key !== ' ')
|
|
337
|
+
) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (event.target !== event.currentTarget) return;
|
|
342
|
+
|
|
343
|
+
if (!keyboardDrag || getActiveKeyboardDrag()?.id !== keyboardDrag.id) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
event.preventDefault();
|
|
348
|
+
|
|
349
|
+
const matchingType = findDropType(
|
|
350
|
+
[keyboardDrag.type],
|
|
351
|
+
normalizedAcceptedTypes
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
if (!matchingType) {
|
|
355
|
+
processDrop(null, event);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const rawData = keyboardDrag.rawData ?? '';
|
|
360
|
+
const data = parseData
|
|
361
|
+
? parseData(rawData, matchingType)
|
|
362
|
+
: (keyboardDrag.data as TData);
|
|
363
|
+
|
|
364
|
+
processDrop(
|
|
365
|
+
{
|
|
366
|
+
data,
|
|
367
|
+
dataTransfer: null,
|
|
368
|
+
files: [],
|
|
369
|
+
rawData,
|
|
370
|
+
type: matchingType,
|
|
371
|
+
},
|
|
372
|
+
event
|
|
373
|
+
);
|
|
374
|
+
clearActiveKeyboardDrag();
|
|
375
|
+
resetDragState();
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
...props,
|
|
380
|
+
ref,
|
|
381
|
+
tabIndex: disabled ? -1 : keyboardDrop ? (tabIndex ?? 0) : tabIndex,
|
|
382
|
+
'aria-disabled': disabled || undefined,
|
|
383
|
+
'data-drag-active': isDragActive,
|
|
384
|
+
'data-drag-accept': isDragAccept,
|
|
385
|
+
'data-drag-reject': isDragReject,
|
|
386
|
+
onDragEnter,
|
|
387
|
+
onDragLeave,
|
|
388
|
+
onDragOver,
|
|
389
|
+
onDrop,
|
|
390
|
+
onKeyDown,
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
[
|
|
394
|
+
disabled,
|
|
395
|
+
dropEffect,
|
|
396
|
+
isDragAccept,
|
|
397
|
+
isDragActive,
|
|
398
|
+
isDragReject,
|
|
399
|
+
keyboardDrop,
|
|
400
|
+
normalizedAcceptedTypes,
|
|
401
|
+
parseData,
|
|
402
|
+
processDrop,
|
|
403
|
+
resetDragState,
|
|
404
|
+
]
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
activeTypes,
|
|
409
|
+
getRootProps,
|
|
410
|
+
isDragAccept,
|
|
411
|
+
isDragActive,
|
|
412
|
+
isDragReject,
|
|
413
|
+
lastDrop,
|
|
414
|
+
lastRejection,
|
|
415
|
+
};
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
export default useDropZone;
|