@uploadista/react 0.0.3

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.
@@ -0,0 +1,614 @@
1
+ import type {
2
+ BrowserUploadInput,
3
+ FlowUploadConfig,
4
+ FlowUploadItem,
5
+ MultiFlowUploadOptions,
6
+ } from "@uploadista/client-browser";
7
+ import type { ReactNode } from "react";
8
+ import { useMultiFlowUpload } from "../hooks/use-multi-flow-upload";
9
+
10
+ /**
11
+ * Render props passed to the FlowUploadList children function.
12
+ * Provides access to upload items, aggregate statistics, and control methods.
13
+ *
14
+ * @property items - All flow upload items in the queue
15
+ * @property totalProgress - Average progress across all uploads (0-100)
16
+ * @property activeUploads - Count of currently uploading items
17
+ * @property completedUploads - Count of successfully completed uploads
18
+ * @property failedUploads - Count of failed uploads
19
+ * @property isUploading - True when any uploads are in progress
20
+ * @property addFiles - Add new files to the upload queue
21
+ * @property removeFile - Remove a specific file from the queue
22
+ * @property startUpload - Begin uploading all pending files
23
+ * @property abortUpload - Cancel a specific active upload
24
+ * @property abortAll - Cancel all active uploads
25
+ * @property clear - Remove all items from the queue
26
+ * @property retryUpload - Retry a specific failed upload
27
+ */
28
+ export interface FlowUploadListRenderProps {
29
+ /**
30
+ * List of upload items
31
+ */
32
+ items: FlowUploadItem<BrowserUploadInput>[];
33
+
34
+ /**
35
+ * Total progress across all uploads
36
+ */
37
+ totalProgress: number;
38
+
39
+ /**
40
+ * Number of active uploads
41
+ */
42
+ activeUploads: number;
43
+
44
+ /**
45
+ * Number of completed uploads
46
+ */
47
+ completedUploads: number;
48
+
49
+ /**
50
+ * Number of failed uploads
51
+ */
52
+ failedUploads: number;
53
+
54
+ /**
55
+ * Whether any uploads are in progress
56
+ */
57
+ isUploading: boolean;
58
+
59
+ /**
60
+ * Add files to the upload queue
61
+ */
62
+ addFiles: (files: File[] | FileList) => void;
63
+
64
+ /**
65
+ * Remove a file from the queue
66
+ */
67
+ removeFile: (id: string) => void;
68
+
69
+ /**
70
+ * Start uploading all pending files
71
+ */
72
+ startUpload: () => void;
73
+
74
+ /**
75
+ * Abort a specific upload
76
+ */
77
+ abortUpload: (id: string) => void;
78
+
79
+ /**
80
+ * Abort all uploads
81
+ */
82
+ abortAll: () => void;
83
+
84
+ /**
85
+ * Clear all items
86
+ */
87
+ clear: () => void;
88
+
89
+ /**
90
+ * Retry a failed upload
91
+ */
92
+ retryUpload: (id: string) => void;
93
+ }
94
+
95
+ /**
96
+ * Props for the FlowUploadList component.
97
+ *
98
+ * @property flowConfig - Flow execution configuration (flowId, storageId, etc.)
99
+ * @property options - Multi-flow upload options (callbacks, concurrency, etc.)
100
+ * @property children - Render function receiving flow upload list state
101
+ */
102
+ export interface FlowUploadListProps {
103
+ /**
104
+ * Flow configuration
105
+ */
106
+ flowConfig: FlowUploadConfig;
107
+
108
+ /**
109
+ * Multi-upload options
110
+ */
111
+ options?: Omit<MultiFlowUploadOptions<BrowserUploadInput>, "flowConfig">;
112
+
113
+ /**
114
+ * Render function for the upload list
115
+ */
116
+ children: (props: FlowUploadListRenderProps) => ReactNode;
117
+ }
118
+
119
+ /**
120
+ * Headless flow upload list component for managing batch file uploads through a flow.
121
+ * Uses render props pattern to provide complete control over the UI while handling
122
+ * concurrent uploads and flow processing.
123
+ *
124
+ * Each file is uploaded and processed independently through the specified flow,
125
+ * with automatic queue management and concurrency control.
126
+ *
127
+ * Must be used within an UploadistaProvider.
128
+ *
129
+ * @param props - Flow upload list configuration and render prop
130
+ * @returns Rendered flow upload list using the provided render prop
131
+ *
132
+ * @example
133
+ * ```tsx
134
+ * // Batch image processing with custom UI
135
+ * <FlowUploadList
136
+ * flowConfig={{
137
+ * flowId: "image-batch-processing",
138
+ * storageId: "s3-images",
139
+ * outputNodeId: "optimized",
140
+ * }}
141
+ * options={{
142
+ * maxConcurrent: 3,
143
+ * onItemSuccess: (item) => {
144
+ * console.log(`${item.file.name} processed successfully`);
145
+ * },
146
+ * onComplete: (items) => {
147
+ * const successful = items.filter(i => i.status === 'success');
148
+ * console.log(`Batch complete: ${successful.length}/${items.length} successful`);
149
+ * },
150
+ * }}
151
+ * >
152
+ * {({
153
+ * items,
154
+ * totalProgress,
155
+ * activeUploads,
156
+ * completedUploads,
157
+ * failedUploads,
158
+ * addFiles,
159
+ * startUpload,
160
+ * abortUpload,
161
+ * retryUpload,
162
+ * clear,
163
+ * }) => (
164
+ * <div>
165
+ * <input
166
+ * type="file"
167
+ * multiple
168
+ * accept="image/*"
169
+ * onChange={(e) => {
170
+ * if (e.target.files) {
171
+ * addFiles(e.target.files);
172
+ * startUpload();
173
+ * }
174
+ * }}
175
+ * />
176
+ *
177
+ * <div style={{ marginTop: '1rem' }}>
178
+ * <h3>Upload Progress</h3>
179
+ * <div>Overall: {totalProgress}%</div>
180
+ * <div>
181
+ * Active: {activeUploads}, Completed: {completedUploads}, Failed: {failedUploads}
182
+ * </div>
183
+ * <button onClick={clear}>Clear All</button>
184
+ * </div>
185
+ *
186
+ * <ul style={{ listStyle: 'none', padding: 0 }}>
187
+ * {items.map((item) => (
188
+ * <li key={item.id} style={{
189
+ * padding: '1rem',
190
+ * border: '1px solid #ccc',
191
+ * marginBottom: '0.5rem'
192
+ * }}>
193
+ * <div>{item.file instanceof File ? item.file.name : 'File'}</div>
194
+ * <div>Status: {item.status}</div>
195
+ *
196
+ * {item.status === "uploading" && (
197
+ * <div>
198
+ * <progress value={item.progress} max={100} style={{ width: '100%' }} />
199
+ * <div>{item.progress}%</div>
200
+ * <button onClick={() => abortUpload(item.id)}>Cancel</button>
201
+ * </div>
202
+ * )}
203
+ *
204
+ * {item.status === "error" && (
205
+ * <div>
206
+ * <div style={{ color: 'red' }}>{item.error?.message}</div>
207
+ * <button onClick={() => retryUpload(item.id)}>Retry</button>
208
+ * </div>
209
+ * )}
210
+ *
211
+ * {item.status === "success" && (
212
+ * <div style={{ color: 'green' }}>✓ Complete</div>
213
+ * )}
214
+ * </li>
215
+ * ))}
216
+ * </ul>
217
+ * </div>
218
+ * )}
219
+ * </FlowUploadList>
220
+ * ```
221
+ *
222
+ * @see {@link SimpleFlowUploadList} for a pre-styled version
223
+ * @see {@link useMultiFlowUpload} for the underlying hook
224
+ */
225
+ export function FlowUploadList({
226
+ flowConfig,
227
+ options,
228
+ children,
229
+ }: FlowUploadListProps) {
230
+ const multiUpload = useMultiFlowUpload({
231
+ ...options,
232
+ flowConfig,
233
+ });
234
+
235
+ return (
236
+ <>
237
+ {children({
238
+ items: multiUpload.state.items,
239
+ totalProgress: multiUpload.state.totalProgress,
240
+ activeUploads: multiUpload.state.activeUploads,
241
+ completedUploads: multiUpload.state.completedUploads,
242
+ failedUploads: multiUpload.state.failedUploads,
243
+ isUploading: multiUpload.isUploading,
244
+ addFiles: multiUpload.addFiles,
245
+ removeFile: multiUpload.removeFile,
246
+ startUpload: multiUpload.startUpload,
247
+ abortUpload: multiUpload.abortUpload,
248
+ abortAll: multiUpload.abortAll,
249
+ clear: multiUpload.clear,
250
+ retryUpload: multiUpload.retryUpload,
251
+ })}
252
+ </>
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Props for the SimpleFlowUploadListItem component.
258
+ *
259
+ * @property item - The flow upload item to display
260
+ * @property onAbort - Called when the abort button is clicked
261
+ * @property onRetry - Called when the retry button is clicked
262
+ * @property onRemove - Called when the remove button is clicked
263
+ */
264
+ export interface SimpleFlowUploadListItemProps {
265
+ /**
266
+ * Upload item
267
+ */
268
+ item: FlowUploadItem<BrowserUploadInput>;
269
+
270
+ /**
271
+ * Abort the upload
272
+ */
273
+ onAbort: () => void;
274
+
275
+ /**
276
+ * Retry the upload
277
+ */
278
+ onRetry: () => void;
279
+
280
+ /**
281
+ * Remove the item
282
+ */
283
+ onRemove: () => void;
284
+ }
285
+
286
+ /**
287
+ * Pre-styled flow upload list item component with status indicators.
288
+ * Displays file name, upload progress, status, and contextual action buttons.
289
+ *
290
+ * Features:
291
+ * - Status-specific icons and colors
292
+ * - Progress bar with percentage and byte count
293
+ * - Error message display
294
+ * - Contextual action buttons (cancel, retry, remove)
295
+ *
296
+ * @param props - Upload item and callback functions
297
+ * @returns Styled flow upload list item component
298
+ *
299
+ * @example
300
+ * ```tsx
301
+ * <SimpleFlowUploadListItem
302
+ * item={uploadItem}
303
+ * onAbort={() => console.log('Abort')}
304
+ * onRetry={() => console.log('Retry')}
305
+ * onRemove={() => console.log('Remove')}
306
+ * />
307
+ * ```
308
+ */
309
+ export function SimpleFlowUploadListItem({
310
+ item,
311
+ onAbort,
312
+ onRetry,
313
+ onRemove,
314
+ }: SimpleFlowUploadListItemProps) {
315
+ const getStatusIcon = () => {
316
+ switch (item.status) {
317
+ case "success":
318
+ return "✓";
319
+ case "error":
320
+ return "✗";
321
+ case "uploading":
322
+ return "⟳";
323
+ case "aborted":
324
+ return "⊘";
325
+ default:
326
+ return "○";
327
+ }
328
+ };
329
+
330
+ const getStatusColor = () => {
331
+ switch (item.status) {
332
+ case "success":
333
+ return "green";
334
+ case "error":
335
+ return "red";
336
+ case "uploading":
337
+ return "blue";
338
+ case "aborted":
339
+ return "gray";
340
+ default:
341
+ return "black";
342
+ }
343
+ };
344
+
345
+ return (
346
+ <div
347
+ style={{
348
+ display: "flex",
349
+ alignItems: "center",
350
+ gap: "12px",
351
+ padding: "8px",
352
+ borderBottom: "1px solid #eee",
353
+ }}
354
+ >
355
+ <span style={{ color: getStatusColor(), fontSize: "18px" }}>
356
+ {getStatusIcon()}
357
+ </span>
358
+
359
+ <div style={{ flex: 1, minWidth: 0 }}>
360
+ <div
361
+ style={{
362
+ fontSize: "14px",
363
+ fontWeight: 500,
364
+ overflow: "hidden",
365
+ textOverflow: "ellipsis",
366
+ whiteSpace: "nowrap",
367
+ }}
368
+ >
369
+ {item.file instanceof File ? item.file.name : "Upload"}
370
+ </div>
371
+
372
+ {item.status === "uploading" && (
373
+ <div style={{ marginTop: "4px" }}>
374
+ <progress
375
+ value={item.progress}
376
+ max={100}
377
+ style={{ width: "100%", height: "4px" }}
378
+ />
379
+ <div style={{ fontSize: "12px", color: "#666", marginTop: "2px" }}>
380
+ {item.progress}% • {Math.round(item.bytesUploaded / 1024)} KB /{" "}
381
+ {Math.round(item.totalBytes / 1024)} KB
382
+ </div>
383
+ </div>
384
+ )}
385
+
386
+ {item.status === "error" && (
387
+ <div style={{ fontSize: "12px", color: "red", marginTop: "2px" }}>
388
+ {item.error?.message || "Upload failed"}
389
+ </div>
390
+ )}
391
+
392
+ {item.status === "success" && (
393
+ <div style={{ fontSize: "12px", color: "green", marginTop: "2px" }}>
394
+ Upload complete
395
+ </div>
396
+ )}
397
+ </div>
398
+
399
+ <div style={{ display: "flex", gap: "8px" }}>
400
+ {item.status === "uploading" && (
401
+ <button
402
+ type="button"
403
+ onClick={onAbort}
404
+ style={{
405
+ padding: "4px 8px",
406
+ fontSize: "12px",
407
+ borderRadius: "4px",
408
+ border: "1px solid #ccc",
409
+ backgroundColor: "#fff",
410
+ cursor: "pointer",
411
+ }}
412
+ >
413
+ Cancel
414
+ </button>
415
+ )}
416
+
417
+ {item.status === "error" && (
418
+ <button
419
+ type="button"
420
+ onClick={onRetry}
421
+ style={{
422
+ padding: "4px 8px",
423
+ fontSize: "12px",
424
+ borderRadius: "4px",
425
+ border: "1px solid #ccc",
426
+ backgroundColor: "#fff",
427
+ cursor: "pointer",
428
+ }}
429
+ >
430
+ Retry
431
+ </button>
432
+ )}
433
+
434
+ {(item.status === "pending" ||
435
+ item.status === "error" ||
436
+ item.status === "aborted") && (
437
+ <button
438
+ type="button"
439
+ onClick={onRemove}
440
+ style={{
441
+ padding: "4px 8px",
442
+ fontSize: "12px",
443
+ borderRadius: "4px",
444
+ border: "1px solid #ccc",
445
+ backgroundColor: "#fff",
446
+ cursor: "pointer",
447
+ }}
448
+ >
449
+ Remove
450
+ </button>
451
+ )}
452
+ </div>
453
+ </div>
454
+ );
455
+ }
456
+
457
+ /**
458
+ * Props for the SimpleFlowUploadList component.
459
+ *
460
+ * @property flowConfig - Flow execution configuration
461
+ * @property options - Multi-flow upload options (callbacks, concurrency)
462
+ * @property className - CSS class name for the container
463
+ * @property showFileInput - Whether to display the file input (default: true)
464
+ * @property accept - Accepted file types for the file input
465
+ */
466
+ export interface SimpleFlowUploadListProps {
467
+ /**
468
+ * Flow configuration
469
+ */
470
+ flowConfig: FlowUploadConfig;
471
+
472
+ /**
473
+ * Multi-upload options
474
+ */
475
+ options?: Omit<MultiFlowUploadOptions<BrowserUploadInput>, "flowConfig">;
476
+
477
+ /**
478
+ * CSS class for the container
479
+ */
480
+ className?: string;
481
+
482
+ /**
483
+ * Show file input
484
+ */
485
+ showFileInput?: boolean;
486
+
487
+ /**
488
+ * File input accept attribute
489
+ */
490
+ accept?: string;
491
+ }
492
+
493
+ /**
494
+ * Simple pre-styled flow upload list component with built-in UI.
495
+ * Provides a ready-to-use interface for batch file uploads with flow processing.
496
+ *
497
+ * Features:
498
+ * - Built-in file input
499
+ * - Overall progress display
500
+ * - Individual item progress tracking
501
+ * - Status indicators and action buttons
502
+ * - Automatic upload start on file selection
503
+ *
504
+ * @param props - Flow upload list configuration with styling options
505
+ * @returns Styled flow upload list component
506
+ *
507
+ * @example
508
+ * ```tsx
509
+ * // Basic batch image upload
510
+ * <SimpleFlowUploadList
511
+ * flowConfig={{
512
+ * flowId: "image-batch-processing",
513
+ * storageId: "s3-images",
514
+ * }}
515
+ * options={{
516
+ * maxConcurrent: 3,
517
+ * onItemSuccess: (item) => {
518
+ * console.log(`${item.file.name} processed`);
519
+ * },
520
+ * onComplete: (items) => {
521
+ * console.log("Batch complete:", items.length);
522
+ * },
523
+ * }}
524
+ * accept="image/*"
525
+ * className="my-upload-list"
526
+ * />
527
+ *
528
+ * // Without file input (add files programmatically)
529
+ * <SimpleFlowUploadList
530
+ * flowConfig={{
531
+ * flowId: "document-processing",
532
+ * storageId: "docs",
533
+ * }}
534
+ * showFileInput={false}
535
+ * options={{
536
+ * maxConcurrent: 2,
537
+ * }}
538
+ * />
539
+ * ```
540
+ *
541
+ * @see {@link FlowUploadList} for the headless version with full control
542
+ */
543
+ export function SimpleFlowUploadList({
544
+ flowConfig,
545
+ options,
546
+ className = "",
547
+ showFileInput = true,
548
+ accept,
549
+ }: SimpleFlowUploadListProps) {
550
+ return (
551
+ <FlowUploadList flowConfig={flowConfig} options={options}>
552
+ {({
553
+ items,
554
+ addFiles,
555
+ startUpload,
556
+ abortUpload,
557
+ retryUpload,
558
+ removeFile,
559
+ totalProgress,
560
+ }) => (
561
+ <div className={className}>
562
+ {showFileInput && (
563
+ <div style={{ marginBottom: "16px" }}>
564
+ <input
565
+ type="file"
566
+ multiple
567
+ accept={accept}
568
+ onChange={(e) => {
569
+ if (e.target.files) {
570
+ addFiles(e.target.files);
571
+ startUpload();
572
+ }
573
+ }}
574
+ style={{
575
+ padding: "8px",
576
+ border: "1px solid #ccc",
577
+ borderRadius: "4px",
578
+ }}
579
+ />
580
+ </div>
581
+ )}
582
+
583
+ {items.length > 0 && (
584
+ <div>
585
+ <div
586
+ style={{ marginBottom: "8px", fontSize: "14px", color: "#666" }}
587
+ >
588
+ Total Progress: {totalProgress}%
589
+ </div>
590
+
591
+ <div
592
+ style={{
593
+ border: "1px solid #eee",
594
+ borderRadius: "8px",
595
+ overflow: "hidden",
596
+ }}
597
+ >
598
+ {items.map((item) => (
599
+ <SimpleFlowUploadListItem
600
+ key={item.id}
601
+ item={item}
602
+ onAbort={() => abortUpload(item.id)}
603
+ onRetry={() => retryUpload(item.id)}
604
+ onRemove={() => removeFile(item.id)}
605
+ />
606
+ ))}
607
+ </div>
608
+ </div>
609
+ )}
610
+ </div>
611
+ )}
612
+ </FlowUploadList>
613
+ );
614
+ }