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.
Files changed (41) hide show
  1. package/dist/components/Dialog/BaseDialog.d.ts +1 -0
  2. package/dist/components/DropZone/DropZone.d.ts +21 -0
  3. package/dist/components/DropZone/DropZone.stories.d.ts +52 -0
  4. package/dist/components/DropZone/__tests__/DropZone.test.d.ts +1 -0
  5. package/dist/components/DropZone/index.d.ts +2 -0
  6. package/dist/components/Skeleton/Skeleton.d.ts +35 -0
  7. package/dist/components/Skeleton/Skeleton.stories.d.ts +27 -0
  8. package/dist/components/Skeleton/SkeletonAvatar.d.ts +7 -0
  9. package/dist/components/Skeleton/SkeletonInput.d.ts +4 -0
  10. package/dist/components/Skeleton/SkeletonTableRow.d.ts +9 -0
  11. package/dist/components/Skeleton/SkeletonText.d.ts +4 -0
  12. package/dist/components/Skeleton/__tests__/Skeleton.test.d.ts +1 -0
  13. package/dist/components/Skeleton/index.d.ts +6 -0
  14. package/dist/components/index.d.ts +4 -0
  15. package/dist/hooks/__tests__/useDraggable.test.d.ts +1 -0
  16. package/dist/hooks/index.d.ts +4 -0
  17. package/dist/hooks/useDraggable.d.ts +31 -0
  18. package/dist/hooks/useDropZone.d.ts +57 -0
  19. package/dist/index.js +7308 -6737
  20. package/lib/components/Dialog/BaseDialog.tsx +25 -15
  21. package/lib/components/DropZone/DropZone.mdx +72 -0
  22. package/lib/components/DropZone/DropZone.stories.tsx +139 -0
  23. package/lib/components/DropZone/DropZone.tsx +213 -0
  24. package/lib/components/DropZone/__tests__/DropZone.test.tsx +378 -0
  25. package/lib/components/DropZone/__tests__/__snapshots__/DropZone.test.tsx.snap +46 -0
  26. package/lib/components/DropZone/index.ts +2 -0
  27. package/lib/components/Skeleton/Skeleton.mdx +34 -0
  28. package/lib/components/Skeleton/Skeleton.stories.tsx +77 -0
  29. package/lib/components/Skeleton/Skeleton.tsx +158 -0
  30. package/lib/components/Skeleton/SkeletonAvatar.tsx +20 -0
  31. package/lib/components/Skeleton/SkeletonInput.tsx +13 -0
  32. package/lib/components/Skeleton/SkeletonTableRow.tsx +44 -0
  33. package/lib/components/Skeleton/SkeletonText.tsx +13 -0
  34. package/lib/components/Skeleton/__tests__/Skeleton.test.tsx +86 -0
  35. package/lib/components/Skeleton/index.ts +6 -0
  36. package/lib/components/index.ts +13 -0
  37. package/lib/hooks/__tests__/useDraggable.test.tsx +184 -0
  38. package/lib/hooks/index.ts +19 -0
  39. package/lib/hooks/useDraggable.ts +243 -0
  40. package/lib/hooks/useDropZone.ts +418 -0
  41. 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;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "uikit-react-public",
3
3
  "private": false,
4
4
  "license": "UNLICENSED",
5
- "version": "0.45.5",
5
+ "version": "0.47.1",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",