@chunkflowjs/upload-component-react 0.0.1-alpha.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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @chunkflowjs/upload-component-react
2
+
3
+ Ready-to-use React UI components for ChunkFlow Upload SDK.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @chunkflowjs/upload-component-react
9
+ ```
10
+
11
+ ## Components
12
+
13
+ ### UploadButton
14
+
15
+ A button component that triggers file selection and validates files before passing them to the onSelect callback.
16
+
17
+ #### Props
18
+
19
+ - `accept?: string` - Accepted file types (e.g., "image/\*", ".pdf,.doc")
20
+ - `multiple?: boolean` - Allow multiple file selection (default: false)
21
+ - `maxSize?: number` - Maximum file size in bytes
22
+ - `onSelect?: (files: File[]) => void` - Callback when files are selected
23
+ - `onError?: (error: FileValidationError) => void` - Callback when file validation fails
24
+ - `children?: ReactNode` - Custom button content
25
+ - `className?: string` - CSS class name for the button
26
+ - `disabled?: boolean` - Disable the button (default: false)
27
+
28
+ #### Example
29
+
30
+ ```tsx
31
+ import { UploadButton } from "@chunkflowjs/upload-component-react";
32
+
33
+ function MyComponent() {
34
+ const handleSelect = (files: File[]) => {
35
+ console.log("Selected files:", files);
36
+ };
37
+
38
+ const handleError = (error) => {
39
+ console.error("Validation error:", error.message);
40
+ };
41
+
42
+ return (
43
+ <UploadButton
44
+ accept="image/*"
45
+ maxSize={10 * 1024 * 1024} // 10MB
46
+ multiple
47
+ onSelect={handleSelect}
48
+ onError={handleError}
49
+ >
50
+ Select Images
51
+ </UploadButton>
52
+ );
53
+ }
54
+ ```
55
+
56
+ #### File Validation
57
+
58
+ The UploadButton component validates files based on:
59
+
60
+ 1. **File Type**: Supports exact MIME types (e.g., "image/jpeg"), wildcard MIME types (e.g., "image/\*"), and file extensions (e.g., ".pdf")
61
+ 2. **File Size**: Validates against the `maxSize` prop
62
+
63
+ When validation fails, the `onError` callback is called with a `FileValidationError` that includes:
64
+
65
+ - `message`: Error description
66
+ - `code`: Error code ("FILE_TOO_LARGE" or "INVALID_FILE_TYPE")
67
+ - `file`: The file that failed validation
68
+
69
+ Valid files are passed to the `onSelect` callback.
70
+
71
+ ### Other Components (Coming Soon)
72
+
73
+ - `UploadProgress` - Progress indicator
74
+ - `UploadList` - Upload task list
75
+ - `UploadDropzone` - Drag and drop zone
76
+
77
+ ## License
78
+
79
+ MIT
@@ -0,0 +1,119 @@
1
+ import { CSSProperties, ReactNode } from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+ import { UploadTask } from "@chunkflowjs/core";
4
+
5
+ //#region src/UploadButton.d.ts
6
+ interface UploadButtonProps {
7
+ accept?: string;
8
+ multiple?: boolean;
9
+ maxSize?: number;
10
+ onSelect?: (files: File[]) => void;
11
+ onError?: (error: FileValidationError) => void;
12
+ children?: ReactNode;
13
+ className?: string;
14
+ disabled?: boolean;
15
+ }
16
+ declare class FileValidationError extends Error {
17
+ code: "FILE_TOO_LARGE" | "INVALID_FILE_TYPE";
18
+ file: File;
19
+ constructor(message: string, code: "FILE_TOO_LARGE" | "INVALID_FILE_TYPE", file: File);
20
+ }
21
+ declare function UploadButton({
22
+ accept,
23
+ multiple,
24
+ maxSize,
25
+ onSelect,
26
+ onError,
27
+ children,
28
+ className,
29
+ disabled
30
+ }: UploadButtonProps): react_jsx_runtime0.JSX.Element;
31
+ //#endregion
32
+ //#region src/UploadProgress.d.ts
33
+ interface UploadProgressProps {
34
+ task: UploadTask;
35
+ showSpeed?: boolean;
36
+ showRemainingTime?: boolean;
37
+ className?: string;
38
+ style?: CSSProperties;
39
+ progressBarClassName?: string;
40
+ progressFillClassName?: string;
41
+ progressInfoClassName?: string;
42
+ }
43
+ declare function UploadProgress({
44
+ task,
45
+ showSpeed,
46
+ showRemainingTime,
47
+ className,
48
+ style,
49
+ progressBarClassName,
50
+ progressFillClassName,
51
+ progressInfoClassName
52
+ }: UploadProgressProps): react_jsx_runtime0.JSX.Element;
53
+ //#endregion
54
+ //#region src/UploadList.d.ts
55
+ interface UploadListProps {
56
+ className?: string;
57
+ style?: CSSProperties;
58
+ baseURL?: string;
59
+ renderItem?: (task: UploadTask, actions: UploadItemActions) => ReactNode;
60
+ showCompleted?: boolean;
61
+ showFailed?: boolean;
62
+ showCancelled?: boolean;
63
+ maxItems?: number;
64
+ emptyMessage?: string;
65
+ }
66
+ interface UploadItemActions {
67
+ pause: () => void;
68
+ resume: () => void;
69
+ cancel: () => void;
70
+ remove: () => void;
71
+ }
72
+ declare function UploadList({
73
+ className,
74
+ style,
75
+ baseURL,
76
+ renderItem,
77
+ showCompleted,
78
+ showFailed,
79
+ showCancelled,
80
+ maxItems,
81
+ emptyMessage
82
+ }: UploadListProps): react_jsx_runtime0.JSX.Element;
83
+ //#endregion
84
+ //#region src/UploadDropzone.d.ts
85
+ interface UploadDropzoneProps {
86
+ accept?: string;
87
+ maxSize?: number;
88
+ multiple?: boolean;
89
+ onDrop?: (files: File[]) => void;
90
+ onError?: (error: FileValidationError$1) => void;
91
+ children?: ReactNode;
92
+ className?: string;
93
+ style?: CSSProperties;
94
+ draggingClassName?: string;
95
+ disabled?: boolean;
96
+ }
97
+ declare class FileValidationError$1 extends Error {
98
+ code: "FILE_TOO_LARGE" | "INVALID_FILE_TYPE";
99
+ file: File;
100
+ constructor(message: string, code: "FILE_TOO_LARGE" | "INVALID_FILE_TYPE", file: File);
101
+ }
102
+ declare function UploadDropzone({
103
+ accept,
104
+ maxSize,
105
+ multiple,
106
+ onDrop,
107
+ onError,
108
+ children,
109
+ className,
110
+ style,
111
+ draggingClassName,
112
+ disabled
113
+ }: UploadDropzoneProps): react_jsx_runtime0.JSX.Element;
114
+ //#endregion
115
+ //#region src/index.d.ts
116
+ declare const UPLOAD_COMPONENT_REACT_VERSION = "0.0.0";
117
+ //#endregion
118
+ export { FileValidationError, UPLOAD_COMPONENT_REACT_VERSION, UploadButton, type UploadButtonProps, UploadDropzone, type UploadDropzoneProps, type UploadItemActions, UploadList, type UploadListProps, UploadProgress, type UploadProgressProps };
119
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/UploadButton.tsx","../src/UploadProgress.tsx","../src/UploadList.tsx","../src/UploadDropzone.tsx","../src/index.ts"],"mappings":";;;;;UAKiB,iBAAA;EAIf,MAAA;EAMA,QAAA;EAKA,OAAA;EAKA,QAAA,IAAY,KAAA,EAAO,IAAA;EAKnB,OAAA,IAAW,KAAA,EAAO,mBAAA;EAKlB,QAAA,GAAW,SAAA;EAKX,SAAA;EAMA,QAAA;AAAA;AAAA,cAMW,mBAAA,SAA4B,KAAA;EAG9B,IAAA;EACA,IAAA,EAAM,IAAA;cAFb,OAAA,UACO,IAAA,0CACA,IAAA,EAAM,IAAA;AAAA;AAAA,iBAyBD,YAAA,CAAA;EACd,MAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA;AAAA,GACC,iBAAA,GAAiB,kBAAA,CAAA,GAAA,CAAA,OAAA;;;UCnFH,mBAAA;EAIf,IAAA,EAAM,UAAA;EAMN,SAAA;EAMA,iBAAA;EAKA,SAAA;EAKA,KAAA,GAAQ,aAAA;EAKR,oBAAA;EAKA,qBAAA;EAKA,qBAAA;AAAA;AAAA,iBAgDc,cAAA,CAAA;EACd,IAAA;EACA,SAAA;EACA,iBAAA;EACA,SAAA;EACA,KAAA;EACA,oBAAA;EACA,qBAAA;EACA;AAAA,GACC,mBAAA,GAAmB,kBAAA,CAAA,GAAA,CAAA,OAAA;;;UC/FL,eAAA;EAIf,SAAA;EAKA,KAAA,GAAQ,aAAA;EAMR,OAAA;EAMA,UAAA,IAAc,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,iBAAA,KAAsB,SAAA;EAM/D,aAAA;EAMA,UAAA;EAMA,aAAA;EAMA,QAAA;EAMA,YAAA;AAAA;AAAA,UAMe,iBAAA;EAEf,KAAA;EAEA,MAAA;EAEA,MAAA;EAEA,MAAA;AAAA;AAAA,iBAiQc,UAAA,CAAA;EACd,SAAA;EACA,KAAA;EACA,OAAA;EACA,UAAA;EACA,aAAA;EACA,UAAA;EACA,aAAA;EACA,QAAA;EACA;AAAA,GACC,eAAA,GAAe,kBAAA,CAAA,GAAA,CAAA,OAAA;;;UCjVD,mBAAA;EAIf,MAAA;EAKA,OAAA;EAMA,QAAA;EAKA,MAAA,IAAU,KAAA,EAAO,IAAA;EAKjB,OAAA,IAAW,KAAA,EAAO,qBAAA;EAKlB,QAAA,GAAW,SAAA;EAKX,SAAA;EAKA,KAAA,GAAQ,aAAA;EAKR,iBAAA;EAMA,QAAA;AAAA;AAAA,cAMW,qBAAA,SAA4B,KAAA;EAG9B,IAAA;EACA,IAAA,EAAM,IAAA;cAFb,OAAA,UACO,IAAA,0CACA,IAAA,EAAM,IAAA;AAAA;AAAA,iBAsED,cAAA,CAAA;EACd,MAAA;EACA,OAAA;EACA,QAAA;EACA,MAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA,KAAA;EACA,iBAAA;EACA;AAAA,GACC,mBAAA,GAAmB,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cCrIT,8BAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,683 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { formatFileSize } from "@chunkflowjs/shared";
4
+ import { UploadStatus } from "@chunkflowjs/protocol";
5
+ import { useUploadList } from "@chunkflowjs/upload-client-react";
6
+
7
+ //#region src/UploadButton.tsx
8
+ /**
9
+ * File validation error
10
+ */
11
+ var FileValidationError = class extends Error {
12
+ constructor(message, code, file) {
13
+ super(message);
14
+ this.code = code;
15
+ this.file = file;
16
+ this.name = "FileValidationError";
17
+ }
18
+ };
19
+ /**
20
+ * UploadButton component
21
+ *
22
+ * A button component that triggers file selection and validates files
23
+ * before passing them to the onSelect callback.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * <UploadButton
28
+ * accept="image/*"
29
+ * maxSize={10 * 1024 * 1024} // 10MB
30
+ * multiple
31
+ * onSelect={(files) => console.log('Selected:', files)}
32
+ * >
33
+ * Select Files
34
+ * </UploadButton>
35
+ * ```
36
+ */
37
+ function UploadButton({ accept, multiple = false, maxSize, onSelect, onError, children, className, disabled = false }) {
38
+ const inputRef = useRef(null);
39
+ const handleClick = () => {
40
+ if (!disabled) inputRef.current?.click();
41
+ };
42
+ const validateFile = (file) => {
43
+ if (maxSize && file.size > maxSize) return new FileValidationError(`File "${file.name}" size ${file.size} bytes exceeds maximum ${maxSize} bytes`, "FILE_TOO_LARGE", file);
44
+ if (accept && !matchAccept$1(file, accept)) return new FileValidationError(`File "${file.name}" type "${file.type}" is not accepted`, "INVALID_FILE_TYPE", file);
45
+ return null;
46
+ };
47
+ const handleChange = (e) => {
48
+ const files = Array.from(e.target.files || []);
49
+ if (files.length === 0) return;
50
+ const validFiles = [];
51
+ const errors = [];
52
+ for (const file of files) {
53
+ const error = validateFile(file);
54
+ if (error) errors.push(error);
55
+ else validFiles.push(file);
56
+ }
57
+ if (errors.length > 0 && onError) errors.forEach((error) => onError(error));
58
+ if (validFiles.length > 0 && onSelect) onSelect(validFiles);
59
+ if (inputRef.current) inputRef.current.value = "";
60
+ };
61
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
62
+ onClick: handleClick,
63
+ className,
64
+ disabled,
65
+ type: "button",
66
+ children: children || "Select Files"
67
+ }), /* @__PURE__ */ jsx("input", {
68
+ ref: inputRef,
69
+ type: "file",
70
+ accept,
71
+ multiple,
72
+ onChange: handleChange,
73
+ style: { display: "none" },
74
+ "aria-hidden": "true"
75
+ })] });
76
+ }
77
+ /**
78
+ * Check if a file matches the accept pattern
79
+ *
80
+ * @param file - The file to check
81
+ * @param accept - The accept pattern (e.g., "image/star", ".pdf,.doc", "star/star")
82
+ * @returns true if the file matches the pattern
83
+ */
84
+ function matchAccept$1(file, accept) {
85
+ if (accept === "*/*" || accept === "*") return true;
86
+ const patterns = accept.split(",").map((p) => p.trim());
87
+ for (const pattern of patterns) {
88
+ if (pattern === "*/*" || pattern === "*") return true;
89
+ if (pattern === file.type) return true;
90
+ if (pattern.endsWith("/*")) {
91
+ const prefix = pattern.slice(0, -2);
92
+ if (file.type.startsWith(prefix + "/")) return true;
93
+ }
94
+ if (pattern.startsWith(".")) {
95
+ if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) return true;
96
+ }
97
+ }
98
+ return false;
99
+ }
100
+
101
+ //#endregion
102
+ //#region src/UploadProgress.tsx
103
+ /**
104
+ * Format time in seconds to human-readable format
105
+ *
106
+ * @param seconds - Time in seconds
107
+ * @returns Formatted string (e.g., "1m 30s", "45s")
108
+ */
109
+ function formatTime(seconds) {
110
+ if (seconds <= 0 || !isFinite(seconds)) return "0s";
111
+ const hours = Math.floor(seconds / 3600);
112
+ const minutes = Math.floor(seconds % 3600 / 60);
113
+ const secs = Math.floor(seconds % 60);
114
+ const parts = [];
115
+ if (hours > 0) parts.push(`${hours}h`);
116
+ if (minutes > 0) parts.push(`${minutes}m`);
117
+ if (secs > 0 || parts.length === 0) parts.push(`${secs}s`);
118
+ return parts.join(" ");
119
+ }
120
+ /**
121
+ * UploadProgress component
122
+ *
123
+ * Displays upload progress with a progress bar, percentage, speed, and remaining time.
124
+ * Automatically updates as the upload progresses.
125
+ *
126
+ * @example
127
+ * ```tsx
128
+ * <UploadProgress
129
+ * task={uploadTask}
130
+ * showSpeed={true}
131
+ * showRemainingTime={true}
132
+ * />
133
+ * ```
134
+ */
135
+ function UploadProgress({ task, showSpeed = true, showRemainingTime = true, className, style, progressBarClassName, progressFillClassName, progressInfoClassName }) {
136
+ const [progress, setProgress] = useState(task.getProgress());
137
+ useEffect(() => {
138
+ const handleProgress = () => {
139
+ setProgress(task.getProgress());
140
+ };
141
+ task.on("progress", handleProgress);
142
+ task.on("start", handleProgress);
143
+ task.on("success", handleProgress);
144
+ task.on("error", handleProgress);
145
+ task.on("pause", handleProgress);
146
+ task.on("resume", handleProgress);
147
+ handleProgress();
148
+ return () => {
149
+ task.off("progress", handleProgress);
150
+ task.off("start", handleProgress);
151
+ task.off("success", handleProgress);
152
+ task.off("error", handleProgress);
153
+ task.off("pause", handleProgress);
154
+ task.off("resume", handleProgress);
155
+ };
156
+ }, [task]);
157
+ const defaultStyles = {
158
+ container: {
159
+ width: "100%",
160
+ padding: "8px"
161
+ },
162
+ progressBar: {
163
+ width: "100%",
164
+ height: "8px",
165
+ backgroundColor: "#e0e0e0",
166
+ borderRadius: "4px",
167
+ overflow: "hidden",
168
+ marginBottom: "8px"
169
+ },
170
+ progressFill: {
171
+ height: "100%",
172
+ backgroundColor: "#4caf50",
173
+ transition: "width 0.3s ease",
174
+ borderRadius: "4px"
175
+ },
176
+ progressInfo: {
177
+ display: "flex",
178
+ justifyContent: "space-between",
179
+ fontSize: "14px",
180
+ color: "#666"
181
+ }
182
+ };
183
+ return /* @__PURE__ */ jsxs("div", {
184
+ className,
185
+ style: style || defaultStyles.container,
186
+ "data-testid": "upload-progress",
187
+ children: [/* @__PURE__ */ jsx("div", {
188
+ className: progressBarClassName,
189
+ style: progressBarClassName ? void 0 : defaultStyles.progressBar,
190
+ "data-testid": "progress-bar",
191
+ children: /* @__PURE__ */ jsx("div", {
192
+ className: progressFillClassName,
193
+ style: progressFillClassName ? { width: `${progress.percentage}%` } : {
194
+ ...defaultStyles.progressFill,
195
+ width: `${progress.percentage}%`
196
+ },
197
+ "data-testid": "progress-fill"
198
+ })
199
+ }), /* @__PURE__ */ jsxs("div", {
200
+ className: progressInfoClassName,
201
+ style: progressInfoClassName ? void 0 : defaultStyles.progressInfo,
202
+ "data-testid": "progress-info",
203
+ children: [/* @__PURE__ */ jsxs("span", {
204
+ "data-testid": "progress-percentage",
205
+ children: [progress.percentage.toFixed(1), "%"]
206
+ }), /* @__PURE__ */ jsxs("div", {
207
+ style: {
208
+ display: "flex",
209
+ gap: "16px"
210
+ },
211
+ children: [showSpeed && progress.speed > 0 && /* @__PURE__ */ jsxs("span", {
212
+ "data-testid": "progress-speed",
213
+ children: [formatFileSize(progress.speed), "/s"]
214
+ }), showRemainingTime && progress.remainingTime > 0 && /* @__PURE__ */ jsxs("span", {
215
+ "data-testid": "progress-remaining-time",
216
+ children: [formatTime(progress.remainingTime), " remaining"]
217
+ })]
218
+ })]
219
+ })]
220
+ });
221
+ }
222
+
223
+ //#endregion
224
+ //#region src/UploadList.tsx
225
+ /**
226
+ * Default upload item component
227
+ *
228
+ * Displays file info, progress, and action buttons
229
+ */
230
+ function DefaultUploadItem({ task, onRemove, baseURL }) {
231
+ const status = task.getStatus();
232
+ const [fileUrl, setFileUrl] = useState(null);
233
+ useEffect(() => {
234
+ const handleSuccess = ({ fileUrl }) => {
235
+ setFileUrl(baseURL && fileUrl.startsWith("/") ? `${baseURL}${fileUrl}` : fileUrl);
236
+ };
237
+ task.on("success", handleSuccess);
238
+ return () => {
239
+ task.off("success", handleSuccess);
240
+ };
241
+ }, [task, baseURL]);
242
+ const handleClick = () => {
243
+ if (status === UploadStatus.SUCCESS && fileUrl) window.open(fileUrl, "_blank");
244
+ };
245
+ const defaultStyles = {
246
+ container: {
247
+ padding: "16px",
248
+ border: "1px solid var(--border, #333)",
249
+ borderRadius: "8px",
250
+ marginBottom: "8px",
251
+ backgroundColor: "var(--darker-bg, #0f0f0f)",
252
+ cursor: status === UploadStatus.SUCCESS && fileUrl ? "pointer" : "default",
253
+ transition: "all 0.2s"
254
+ },
255
+ fileInfo: {
256
+ display: "flex",
257
+ justifyContent: "space-between",
258
+ alignItems: "center",
259
+ marginBottom: "8px"
260
+ },
261
+ fileName: {
262
+ fontWeight: 500,
263
+ fontSize: "14px",
264
+ color: "var(--text, #e0e0e0)",
265
+ overflow: "hidden",
266
+ textOverflow: "ellipsis",
267
+ whiteSpace: "nowrap",
268
+ flex: 1,
269
+ marginRight: "8px"
270
+ },
271
+ fileSize: {
272
+ fontSize: "12px",
273
+ color: "var(--text-dim, #999)"
274
+ },
275
+ status: {
276
+ fontSize: "12px",
277
+ padding: "2px 8px",
278
+ borderRadius: "4px",
279
+ marginLeft: "8px"
280
+ },
281
+ actions: {
282
+ display: "flex",
283
+ gap: "8px",
284
+ marginTop: "8px"
285
+ },
286
+ button: {
287
+ padding: "4px 12px",
288
+ fontSize: "12px",
289
+ border: "1px solid var(--border, #333)",
290
+ borderRadius: "4px",
291
+ backgroundColor: "var(--card-bg, #1e1e1e)",
292
+ color: "var(--text, #e0e0e0)",
293
+ cursor: "pointer",
294
+ transition: "all 0.2s"
295
+ }
296
+ };
297
+ const getStatusStyle = (status) => {
298
+ const baseStyle = defaultStyles.status;
299
+ switch (status) {
300
+ case UploadStatus.UPLOADING: return {
301
+ ...baseStyle,
302
+ backgroundColor: "rgba(33, 150, 243, 0.15)",
303
+ color: "#42a5f5"
304
+ };
305
+ case UploadStatus.SUCCESS: return {
306
+ ...baseStyle,
307
+ backgroundColor: "rgba(76, 175, 80, 0.15)",
308
+ color: "#66bb6a"
309
+ };
310
+ case UploadStatus.ERROR: return {
311
+ ...baseStyle,
312
+ backgroundColor: "rgba(244, 67, 54, 0.15)",
313
+ color: "#ef5350"
314
+ };
315
+ case UploadStatus.PAUSED: return {
316
+ ...baseStyle,
317
+ backgroundColor: "rgba(255, 152, 0, 0.15)",
318
+ color: "#ffa726"
319
+ };
320
+ case UploadStatus.CANCELLED: return {
321
+ ...baseStyle,
322
+ backgroundColor: "rgba(158, 158, 158, 0.15)",
323
+ color: "#bdbdbd"
324
+ };
325
+ default: return {
326
+ ...baseStyle,
327
+ backgroundColor: "rgba(158, 158, 158, 0.15)",
328
+ color: "#bdbdbd"
329
+ };
330
+ }
331
+ };
332
+ const getStatusText = (status) => {
333
+ switch (status) {
334
+ case UploadStatus.IDLE: return "Idle";
335
+ case UploadStatus.HASHING: return "Hashing";
336
+ case UploadStatus.UPLOADING: return "Uploading";
337
+ case UploadStatus.PAUSED: return "Paused";
338
+ case UploadStatus.SUCCESS: return "Success";
339
+ case UploadStatus.ERROR: return "Error";
340
+ case UploadStatus.CANCELLED: return "Cancelled";
341
+ default: return "Unknown";
342
+ }
343
+ };
344
+ return /* @__PURE__ */ jsxs("div", {
345
+ style: defaultStyles.container,
346
+ "data-testid": "upload-item",
347
+ onClick: handleClick,
348
+ onMouseEnter: (e) => {
349
+ if (status === UploadStatus.SUCCESS && fileUrl) e.currentTarget.style.backgroundColor = "var(--card-bg, #1e1e1e)";
350
+ },
351
+ onMouseLeave: (e) => {
352
+ e.currentTarget.style.backgroundColor = "var(--darker-bg, #0f0f0f)";
353
+ },
354
+ title: status === UploadStatus.SUCCESS && fileUrl ? "Click to open file" : "",
355
+ children: [
356
+ /* @__PURE__ */ jsxs("div", {
357
+ style: defaultStyles.fileInfo,
358
+ children: [/* @__PURE__ */ jsxs("div", {
359
+ style: {
360
+ display: "flex",
361
+ alignItems: "center",
362
+ flex: 1,
363
+ minWidth: 0
364
+ },
365
+ children: [/* @__PURE__ */ jsx("span", {
366
+ style: defaultStyles.fileName,
367
+ title: task.file.name,
368
+ children: task.file.name
369
+ }), /* @__PURE__ */ jsx("span", {
370
+ style: defaultStyles.fileSize,
371
+ children: formatFileSize(task.file.size)
372
+ })]
373
+ }), /* @__PURE__ */ jsx("span", {
374
+ style: getStatusStyle(status),
375
+ "data-testid": "upload-status",
376
+ children: getStatusText(status)
377
+ })]
378
+ }),
379
+ (status === UploadStatus.UPLOADING || status === UploadStatus.PAUSED || status === UploadStatus.HASHING) && /* @__PURE__ */ jsx(UploadProgress, { task }),
380
+ /* @__PURE__ */ jsxs("div", {
381
+ style: defaultStyles.actions,
382
+ onClick: (e) => e.stopPropagation(),
383
+ children: [
384
+ status === UploadStatus.UPLOADING && /* @__PURE__ */ jsx("button", {
385
+ onClick: () => task.pause(),
386
+ style: defaultStyles.button,
387
+ "data-testid": "pause-button",
388
+ type: "button",
389
+ children: "Pause"
390
+ }),
391
+ status === UploadStatus.PAUSED && /* @__PURE__ */ jsx("button", {
392
+ onClick: () => task.resume(),
393
+ style: defaultStyles.button,
394
+ "data-testid": "resume-button",
395
+ type: "button",
396
+ children: "Resume"
397
+ }),
398
+ (status === UploadStatus.UPLOADING || status === UploadStatus.PAUSED) && /* @__PURE__ */ jsx("button", {
399
+ onClick: () => task.cancel(),
400
+ style: defaultStyles.button,
401
+ "data-testid": "cancel-button",
402
+ type: "button",
403
+ children: "Cancel"
404
+ }),
405
+ status === UploadStatus.SUCCESS && fileUrl && /* @__PURE__ */ jsx("button", {
406
+ onClick: handleClick,
407
+ style: defaultStyles.button,
408
+ "data-testid": "open-button",
409
+ type: "button",
410
+ children: "Open"
411
+ }),
412
+ /* @__PURE__ */ jsx("button", {
413
+ onClick: onRemove,
414
+ style: defaultStyles.button,
415
+ "data-testid": "remove-button",
416
+ type: "button",
417
+ children: "Remove"
418
+ })
419
+ ]
420
+ })
421
+ ]
422
+ });
423
+ }
424
+ /**
425
+ * UploadList component
426
+ *
427
+ * Displays a list of upload tasks with progress bars and action buttons.
428
+ * Integrates with useUploadList hook to automatically sync with the upload manager.
429
+ *
430
+ * @example
431
+ * ```tsx
432
+ * // Basic usage
433
+ * <UploadList />
434
+ *
435
+ * // With custom styling
436
+ * <UploadList
437
+ * className="my-upload-list"
438
+ * showCompleted={false}
439
+ * maxItems={10}
440
+ * />
441
+ *
442
+ * // With custom item renderer
443
+ * <UploadList
444
+ * renderItem={(task, actions) => (
445
+ * <div>
446
+ * <h3>{task.file.name}</h3>
447
+ * <button onClick={actions.pause}>Pause</button>
448
+ * <button onClick={actions.remove}>Remove</button>
449
+ * </div>
450
+ * )}
451
+ * />
452
+ * ```
453
+ */
454
+ function UploadList({ className, style, baseURL, renderItem, showCompleted = true, showFailed = true, showCancelled = true, maxItems, emptyMessage = "No uploads" }) {
455
+ const { tasks, removeTask } = useUploadList();
456
+ const filteredTasks = tasks.filter((task) => {
457
+ const status = task.getStatus();
458
+ if (!showCompleted && status === UploadStatus.SUCCESS) return false;
459
+ if (!showFailed && status === UploadStatus.ERROR) return false;
460
+ if (!showCancelled && status === UploadStatus.CANCELLED) return false;
461
+ return true;
462
+ });
463
+ const displayTasks = maxItems ? filteredTasks.slice(0, maxItems) : filteredTasks;
464
+ const defaultStyles = {
465
+ container: { width: "100%" },
466
+ empty: {
467
+ padding: "32px",
468
+ textAlign: "center",
469
+ color: "var(--text-dim, #999)",
470
+ fontSize: "14px"
471
+ }
472
+ };
473
+ if (displayTasks.length === 0) return /* @__PURE__ */ jsx("div", {
474
+ className,
475
+ style: style || defaultStyles.container,
476
+ "data-testid": "upload-list-empty",
477
+ children: /* @__PURE__ */ jsx("div", {
478
+ style: defaultStyles.empty,
479
+ children: emptyMessage
480
+ })
481
+ });
482
+ return /* @__PURE__ */ jsx("div", {
483
+ className,
484
+ style: style || defaultStyles.container,
485
+ "data-testid": "upload-list",
486
+ children: displayTasks.map((task) => {
487
+ const actions = {
488
+ pause: () => task.pause(),
489
+ resume: () => task.resume(),
490
+ cancel: () => task.cancel(),
491
+ remove: () => removeTask(task.id)
492
+ };
493
+ return /* @__PURE__ */ jsx("div", {
494
+ "data-testid": "upload-list-item",
495
+ children: renderItem ? renderItem(task, actions) : /* @__PURE__ */ jsx(DefaultUploadItem, {
496
+ task,
497
+ onRemove: actions.remove,
498
+ baseURL
499
+ })
500
+ }, task.id);
501
+ })
502
+ });
503
+ }
504
+
505
+ //#endregion
506
+ //#region src/UploadDropzone.tsx
507
+ /**
508
+ * File validation error
509
+ */
510
+ var FileValidationError$1 = class extends Error {
511
+ constructor(message, code, file) {
512
+ super(message);
513
+ this.code = code;
514
+ this.file = file;
515
+ this.name = "FileValidationError";
516
+ }
517
+ };
518
+ /**
519
+ * Check if a file matches the accept pattern
520
+ *
521
+ * @param file - The file to check
522
+ * @param accept - The accept pattern (e.g., "image/star", ".pdf,.doc", "star/star")
523
+ * @returns true if the file matches the pattern
524
+ */
525
+ function matchAccept(file, accept) {
526
+ if (accept === "*/*" || accept === "*") return true;
527
+ const patterns = accept.split(",").map((p) => p.trim());
528
+ for (const pattern of patterns) {
529
+ if (pattern === "*/*" || pattern === "*") return true;
530
+ if (pattern === file.type) return true;
531
+ if (pattern.endsWith("/*")) {
532
+ const prefix = pattern.slice(0, -2);
533
+ if (file.type.startsWith(prefix + "/")) return true;
534
+ }
535
+ if (pattern.startsWith(".")) {
536
+ if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) return true;
537
+ }
538
+ }
539
+ return false;
540
+ }
541
+ /**
542
+ * UploadDropzone component
543
+ *
544
+ * A drag-and-drop zone for file uploads with visual feedback.
545
+ * Validates files based on type and size before passing them to the onDrop callback.
546
+ *
547
+ * @example
548
+ * ```tsx
549
+ * <UploadDropzone
550
+ * accept="image/*"
551
+ * maxSize={10 * 1024 * 1024} // 10MB
552
+ * multiple
553
+ * onDrop={(files) => console.log('Dropped:', files)}
554
+ * >
555
+ * <p>Drag and drop files here, or click to select</p>
556
+ * </UploadDropzone>
557
+ * ```
558
+ */
559
+ function UploadDropzone({ accept, maxSize, multiple = true, onDrop, onError, children, className, style, draggingClassName, disabled = false }) {
560
+ const [isDragging, setIsDragging] = useState(false);
561
+ const inputRef = useRef(null);
562
+ const dragCounterRef = useRef(0);
563
+ const validateFile = (file) => {
564
+ if (maxSize && file.size > maxSize) return new FileValidationError$1(`File "${file.name}" size ${file.size} bytes exceeds maximum ${maxSize} bytes`, "FILE_TOO_LARGE", file);
565
+ if (accept && !matchAccept(file, accept)) return new FileValidationError$1(`File "${file.name}" type "${file.type}" is not accepted`, "INVALID_FILE_TYPE", file);
566
+ return null;
567
+ };
568
+ const processFiles = (files) => {
569
+ if (files.length === 0) return;
570
+ const validFiles = [];
571
+ const errors = [];
572
+ for (const file of files) {
573
+ const error = validateFile(file);
574
+ if (error) errors.push(error);
575
+ else validFiles.push(file);
576
+ }
577
+ if (errors.length > 0 && onError) errors.forEach((error) => onError(error));
578
+ if (validFiles.length > 0 && onDrop) onDrop(validFiles);
579
+ };
580
+ const handleDragEnter = (e) => {
581
+ e.preventDefault();
582
+ e.stopPropagation();
583
+ if (disabled) return;
584
+ dragCounterRef.current++;
585
+ if (e.dataTransfer.items && e.dataTransfer.items.length > 0) setIsDragging(true);
586
+ };
587
+ const handleDragLeave = (e) => {
588
+ e.preventDefault();
589
+ e.stopPropagation();
590
+ if (disabled) return;
591
+ dragCounterRef.current--;
592
+ if (dragCounterRef.current === 0) setIsDragging(false);
593
+ };
594
+ const handleDragOver = (e) => {
595
+ e.preventDefault();
596
+ e.stopPropagation();
597
+ if (disabled) return;
598
+ if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
599
+ };
600
+ const handleDrop = (e) => {
601
+ e.preventDefault();
602
+ e.stopPropagation();
603
+ if (disabled) return;
604
+ setIsDragging(false);
605
+ dragCounterRef.current = 0;
606
+ const files = Array.from(e.dataTransfer?.files || []);
607
+ processFiles(multiple ? files : files.slice(0, 1));
608
+ };
609
+ const handleClick = () => {
610
+ if (!disabled) inputRef.current?.click();
611
+ };
612
+ const handleInputChange = (e) => {
613
+ processFiles(Array.from(e.target.files || []));
614
+ if (inputRef.current) inputRef.current.value = "";
615
+ };
616
+ const defaultStyles = {
617
+ container: {
618
+ border: "2px dashed #ccc",
619
+ borderRadius: "8px",
620
+ padding: "32px",
621
+ textAlign: "center",
622
+ cursor: disabled ? "not-allowed" : "pointer",
623
+ transition: "all 0.3s ease",
624
+ backgroundColor: disabled ? "#f5f5f5" : "#fafafa",
625
+ opacity: disabled ? .6 : 1
626
+ },
627
+ dragging: {
628
+ borderColor: "#4caf50",
629
+ backgroundColor: "#e8f5e9"
630
+ },
631
+ content: {
632
+ color: "#666",
633
+ fontSize: "14px"
634
+ }
635
+ };
636
+ const containerStyle = {
637
+ ...defaultStyles.container,
638
+ ...isDragging && !disabled ? defaultStyles.dragging : {},
639
+ ...style
640
+ };
641
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
642
+ className: [className, isDragging && !disabled ? draggingClassName : ""].filter(Boolean).join(" ") || void 0,
643
+ style: containerStyle,
644
+ onDragEnter: handleDragEnter,
645
+ onDragLeave: handleDragLeave,
646
+ onDragOver: handleDragOver,
647
+ onDrop: handleDrop,
648
+ onClick: handleClick,
649
+ "data-testid": "upload-dropzone",
650
+ "data-dragging": isDragging,
651
+ role: "button",
652
+ tabIndex: disabled ? -1 : 0,
653
+ "aria-disabled": disabled,
654
+ onKeyDown: (e) => {
655
+ if (!disabled && (e.key === "Enter" || e.key === " ")) {
656
+ e.preventDefault();
657
+ handleClick();
658
+ }
659
+ },
660
+ children: children || /* @__PURE__ */ jsxs("div", {
661
+ style: defaultStyles.content,
662
+ "data-testid": "dropzone-default-content",
663
+ children: [/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: "Drag and drop files here" }) }), /* @__PURE__ */ jsx("p", { children: "or click to select files" })]
664
+ })
665
+ }), /* @__PURE__ */ jsx("input", {
666
+ ref: inputRef,
667
+ type: "file",
668
+ accept,
669
+ multiple,
670
+ onChange: handleInputChange,
671
+ style: { display: "none" },
672
+ "aria-hidden": "true",
673
+ disabled
674
+ })] });
675
+ }
676
+
677
+ //#endregion
678
+ //#region src/index.ts
679
+ const UPLOAD_COMPONENT_REACT_VERSION = "0.0.0";
680
+
681
+ //#endregion
682
+ export { FileValidationError, UPLOAD_COMPONENT_REACT_VERSION, UploadButton, UploadDropzone, UploadList, UploadProgress };
683
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":["matchAccept","FileValidationError"],"sources":["../src/UploadButton.tsx","../src/UploadProgress.tsx","../src/UploadList.tsx","../src/UploadDropzone.tsx","../src/index.ts"],"sourcesContent":["import { useRef, ChangeEvent, ReactNode } from \"react\";\n\n/**\n * Props for the UploadButton component\n */\nexport interface UploadButtonProps {\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf,.doc\")\n */\n accept?: string;\n\n /**\n * Allow multiple file selection\n * @default false\n */\n multiple?: boolean;\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Callback when files are selected\n */\n onSelect?: (files: File[]) => void;\n\n /**\n * Callback when file validation fails\n */\n onError?: (error: FileValidationError) => void;\n\n /**\n * Custom button content\n */\n children?: ReactNode;\n\n /**\n * CSS class name for the button\n */\n className?: string;\n\n /**\n * Disable the button\n * @default false\n */\n disabled?: boolean;\n}\n\n/**\n * File validation error\n */\nexport class FileValidationError extends Error {\n constructor(\n message: string,\n public code: \"FILE_TOO_LARGE\" | \"INVALID_FILE_TYPE\",\n public file: File,\n ) {\n super(message);\n this.name = \"FileValidationError\";\n }\n}\n\n/**\n * UploadButton component\n *\n * A button component that triggers file selection and validates files\n * before passing them to the onSelect callback.\n *\n * @example\n * ```tsx\n * <UploadButton\n * accept=\"image/*\"\n * maxSize={10 * 1024 * 1024} // 10MB\n * multiple\n * onSelect={(files) => console.log('Selected:', files)}\n * >\n * Select Files\n * </UploadButton>\n * ```\n */\nexport function UploadButton({\n accept,\n multiple = false,\n maxSize,\n onSelect,\n onError,\n children,\n className,\n disabled = false,\n}: UploadButtonProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n const handleClick = () => {\n if (!disabled) {\n inputRef.current?.click();\n }\n };\n\n const validateFile = (file: File): FileValidationError | null => {\n // Validate file size\n if (maxSize && file.size > maxSize) {\n return new FileValidationError(\n `File \"${file.name}\" size ${file.size} bytes exceeds maximum ${maxSize} bytes`,\n \"FILE_TOO_LARGE\",\n file,\n );\n }\n\n // Validate file type\n if (accept && !matchAccept(file, accept)) {\n return new FileValidationError(\n `File \"${file.name}\" type \"${file.type}\" is not accepted`,\n \"INVALID_FILE_TYPE\",\n file,\n );\n }\n\n return null;\n };\n\n const handleChange = (e: ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files || []);\n\n if (files.length === 0) {\n return;\n }\n\n // Validate all files\n const validFiles: File[] = [];\n const errors: FileValidationError[] = [];\n\n for (const file of files) {\n const error = validateFile(file);\n if (error) {\n errors.push(error);\n } else {\n validFiles.push(file);\n }\n }\n\n // Report errors\n if (errors.length > 0 && onError) {\n errors.forEach((error) => onError(error));\n }\n\n // Call onSelect with valid files\n if (validFiles.length > 0 && onSelect) {\n onSelect(validFiles);\n }\n\n // Reset input value to allow selecting the same file again\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n return (\n <>\n <button onClick={handleClick} className={className} disabled={disabled} type=\"button\">\n {children || \"Select Files\"}\n </button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={handleChange}\n style={{ display: \"none\" }}\n aria-hidden=\"true\"\n />\n </>\n );\n}\n\n/**\n * Check if a file matches the accept pattern\n *\n * @param file - The file to check\n * @param accept - The accept pattern (e.g., \"image/star\", \".pdf,.doc\", \"star/star\")\n * @returns true if the file matches the pattern\n */\nfunction matchAccept(file: File, accept: string): boolean {\n // Accept all files if pattern is \"*/*\" or \"*\"\n if (accept === \"*/*\" || accept === \"*\") {\n return true;\n }\n\n const patterns = accept.split(\",\").map((p) => p.trim());\n\n for (const pattern of patterns) {\n // Accept all files\n if (pattern === \"*/*\" || pattern === \"*\") {\n return true;\n }\n\n // Exact MIME type match (e.g., \"image/jpeg\")\n if (pattern === file.type) {\n return true;\n }\n\n // Wildcard MIME type match (e.g., \"image/*\")\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (file.type.startsWith(prefix + \"/\")) {\n return true;\n }\n }\n\n // File extension match (e.g., \".pdf\")\n if (pattern.startsWith(\".\")) {\n if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) {\n return true;\n }\n }\n }\n\n return false;\n}\n","import { useEffect, useState, CSSProperties } from \"react\";\nimport type { UploadTask } from \"@chunkflowjs/core\";\nimport { formatFileSize } from \"@chunkflowjs/shared\";\n\n/**\n * Props for the UploadProgress component\n */\nexport interface UploadProgressProps {\n /**\n * The upload task to display progress for\n */\n task: UploadTask;\n\n /**\n * Whether to show upload speed\n * @default true\n */\n showSpeed?: boolean;\n\n /**\n * Whether to show remaining time\n * @default true\n */\n showRemainingTime?: boolean;\n\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * CSS class name for the progress bar\n */\n progressBarClassName?: string;\n\n /**\n * CSS class name for the progress fill\n */\n progressFillClassName?: string;\n\n /**\n * CSS class name for the progress info\n */\n progressInfoClassName?: string;\n}\n\n/**\n * Format time in seconds to human-readable format\n *\n * @param seconds - Time in seconds\n * @returns Formatted string (e.g., \"1m 30s\", \"45s\")\n */\nfunction formatTime(seconds: number): string {\n if (seconds <= 0 || !isFinite(seconds)) {\n return \"0s\";\n }\n\n const hours = Math.floor(seconds / 3600);\n const minutes = Math.floor((seconds % 3600) / 60);\n const secs = Math.floor(seconds % 60);\n\n const parts: string[] = [];\n\n if (hours > 0) {\n parts.push(`${hours}h`);\n }\n if (minutes > 0) {\n parts.push(`${minutes}m`);\n }\n if (secs > 0 || parts.length === 0) {\n parts.push(`${secs}s`);\n }\n\n return parts.join(\" \");\n}\n\n/**\n * UploadProgress component\n *\n * Displays upload progress with a progress bar, percentage, speed, and remaining time.\n * Automatically updates as the upload progresses.\n *\n * @example\n * ```tsx\n * <UploadProgress\n * task={uploadTask}\n * showSpeed={true}\n * showRemainingTime={true}\n * />\n * ```\n */\nexport function UploadProgress({\n task,\n showSpeed = true,\n showRemainingTime = true,\n className,\n style,\n progressBarClassName,\n progressFillClassName,\n progressInfoClassName,\n}: UploadProgressProps) {\n const [progress, setProgress] = useState(task.getProgress());\n\n useEffect(() => {\n // Update progress when the task emits progress events\n const handleProgress = () => {\n setProgress(task.getProgress());\n };\n\n // Subscribe to progress events\n task.on(\"progress\", handleProgress);\n\n // Also update on other state changes that might affect progress\n task.on(\"start\", handleProgress);\n task.on(\"success\", handleProgress);\n task.on(\"error\", handleProgress);\n task.on(\"pause\", handleProgress);\n task.on(\"resume\", handleProgress);\n\n // Initial update\n handleProgress();\n\n // Cleanup: unsubscribe from events\n return () => {\n task.off(\"progress\", handleProgress);\n task.off(\"start\", handleProgress);\n task.off(\"success\", handleProgress);\n task.off(\"error\", handleProgress);\n task.off(\"pause\", handleProgress);\n task.off(\"resume\", handleProgress);\n };\n }, [task]);\n\n const defaultStyles = {\n container: {\n width: \"100%\",\n padding: \"8px\",\n } as CSSProperties,\n progressBar: {\n width: \"100%\",\n height: \"8px\",\n backgroundColor: \"#e0e0e0\",\n borderRadius: \"4px\",\n overflow: \"hidden\",\n marginBottom: \"8px\",\n } as CSSProperties,\n progressFill: {\n height: \"100%\",\n backgroundColor: \"#4caf50\",\n transition: \"width 0.3s ease\",\n borderRadius: \"4px\",\n } as CSSProperties,\n progressInfo: {\n display: \"flex\",\n justifyContent: \"space-between\",\n fontSize: \"14px\",\n color: \"#666\",\n } as CSSProperties,\n };\n\n return (\n <div\n className={className}\n style={style || defaultStyles.container}\n data-testid=\"upload-progress\"\n >\n {/* Progress bar */}\n <div\n className={progressBarClassName}\n style={progressBarClassName ? undefined : defaultStyles.progressBar}\n data-testid=\"progress-bar\"\n >\n <div\n className={progressFillClassName}\n style={\n progressFillClassName\n ? { width: `${progress.percentage}%` }\n : { ...defaultStyles.progressFill, width: `${progress.percentage}%` }\n }\n data-testid=\"progress-fill\"\n />\n </div>\n\n {/* Progress info */}\n <div\n className={progressInfoClassName}\n style={progressInfoClassName ? undefined : defaultStyles.progressInfo}\n data-testid=\"progress-info\"\n >\n <span data-testid=\"progress-percentage\">{progress.percentage.toFixed(1)}%</span>\n\n <div style={{ display: \"flex\", gap: \"16px\" }}>\n {showSpeed && progress.speed > 0 && (\n <span data-testid=\"progress-speed\">{formatFileSize(progress.speed)}/s</span>\n )}\n\n {showRemainingTime && progress.remainingTime > 0 && (\n <span data-testid=\"progress-remaining-time\">\n {formatTime(progress.remainingTime)} remaining\n </span>\n )}\n </div>\n </div>\n </div>\n );\n}\n","import { ReactNode, CSSProperties, useState, useEffect } from \"react\";\nimport type { UploadTask } from \"@chunkflowjs/core\";\nimport { UploadStatus } from \"@chunkflowjs/protocol\";\nimport { formatFileSize } from \"@chunkflowjs/shared\";\nimport { useUploadList } from \"@chunkflowjs/upload-client-react\";\nimport { UploadProgress } from \"./UploadProgress\";\n\n/**\n * Props for the UploadList component\n */\nexport interface UploadListProps {\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * Base URL for file downloads (e.g., \"http://localhost:3001\")\n * If provided, relative file URLs will be converted to absolute URLs\n */\n baseURL?: string;\n\n /**\n * Custom render function for each upload item\n * If provided, this will be used instead of the default item renderer\n */\n renderItem?: (task: UploadTask, actions: UploadItemActions) => ReactNode;\n\n /**\n * Whether to show completed uploads\n * @default true\n */\n showCompleted?: boolean;\n\n /**\n * Whether to show failed uploads\n * @default true\n */\n showFailed?: boolean;\n\n /**\n * Whether to show cancelled uploads\n * @default true\n */\n showCancelled?: boolean;\n\n /**\n * Maximum number of items to display\n * If not set, all items will be displayed\n */\n maxItems?: number;\n\n /**\n * Message to display when there are no uploads\n * @default \"No uploads\"\n */\n emptyMessage?: string;\n}\n\n/**\n * Actions available for each upload item\n */\nexport interface UploadItemActions {\n /** Pause the upload */\n pause: () => void;\n /** Resume the upload */\n resume: () => void;\n /** Cancel the upload */\n cancel: () => void;\n /** Remove the upload from the list */\n remove: () => void;\n}\n\n/**\n * Props for the default upload item component\n */\ninterface DefaultUploadItemProps {\n task: UploadTask;\n onRemove: () => void;\n baseURL?: string;\n}\n\n/**\n * Default upload item component\n *\n * Displays file info, progress, and action buttons\n */\nfunction DefaultUploadItem({ task, onRemove, baseURL }: DefaultUploadItemProps) {\n const status = task.getStatus();\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n\n // Listen to success event to get fileUrl\n useEffect(() => {\n const handleSuccess = ({ fileUrl }: { fileUrl: string }) => {\n // Convert relative URL to absolute URL if baseURL is provided\n const absoluteUrl = baseURL && fileUrl.startsWith(\"/\") ? `${baseURL}${fileUrl}` : fileUrl;\n setFileUrl(absoluteUrl);\n };\n\n task.on(\"success\", handleSuccess);\n\n return () => {\n task.off(\"success\", handleSuccess);\n };\n }, [task, baseURL]);\n\n const handleClick = () => {\n // Only open file if upload is successful and we have a URL\n if (status === UploadStatus.SUCCESS && fileUrl) {\n window.open(fileUrl, \"_blank\");\n }\n };\n\n const defaultStyles = {\n container: {\n padding: \"16px\",\n border: \"1px solid var(--border, #333)\",\n borderRadius: \"8px\",\n marginBottom: \"8px\",\n backgroundColor: \"var(--darker-bg, #0f0f0f)\",\n cursor: status === UploadStatus.SUCCESS && fileUrl ? \"pointer\" : \"default\",\n transition: \"all 0.2s\",\n } as CSSProperties,\n fileInfo: {\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"8px\",\n } as CSSProperties,\n fileName: {\n fontWeight: 500,\n fontSize: \"14px\",\n color: \"var(--text, #e0e0e0)\",\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\" as const,\n flex: 1,\n marginRight: \"8px\",\n } as CSSProperties,\n fileSize: {\n fontSize: \"12px\",\n color: \"var(--text-dim, #999)\",\n } as CSSProperties,\n status: {\n fontSize: \"12px\",\n padding: \"2px 8px\",\n borderRadius: \"4px\",\n marginLeft: \"8px\",\n } as CSSProperties,\n actions: {\n display: \"flex\",\n gap: \"8px\",\n marginTop: \"8px\",\n } as CSSProperties,\n button: {\n padding: \"4px 12px\",\n fontSize: \"12px\",\n border: \"1px solid var(--border, #333)\",\n borderRadius: \"4px\",\n backgroundColor: \"var(--card-bg, #1e1e1e)\",\n color: \"var(--text, #e0e0e0)\",\n cursor: \"pointer\",\n transition: \"all 0.2s\",\n } as CSSProperties,\n };\n\n const getStatusStyle = (status: UploadStatus): CSSProperties => {\n const baseStyle = defaultStyles.status;\n switch (status) {\n case UploadStatus.UPLOADING:\n return { ...baseStyle, backgroundColor: \"rgba(33, 150, 243, 0.15)\", color: \"#42a5f5\" };\n case UploadStatus.SUCCESS:\n return { ...baseStyle, backgroundColor: \"rgba(76, 175, 80, 0.15)\", color: \"#66bb6a\" };\n case UploadStatus.ERROR:\n return { ...baseStyle, backgroundColor: \"rgba(244, 67, 54, 0.15)\", color: \"#ef5350\" };\n case UploadStatus.PAUSED:\n return { ...baseStyle, backgroundColor: \"rgba(255, 152, 0, 0.15)\", color: \"#ffa726\" };\n case UploadStatus.CANCELLED:\n return { ...baseStyle, backgroundColor: \"rgba(158, 158, 158, 0.15)\", color: \"#bdbdbd\" };\n default:\n return { ...baseStyle, backgroundColor: \"rgba(158, 158, 158, 0.15)\", color: \"#bdbdbd\" };\n }\n };\n\n const getStatusText = (status: UploadStatus): string => {\n switch (status) {\n case UploadStatus.IDLE:\n return \"Idle\";\n case UploadStatus.HASHING:\n return \"Hashing\";\n case UploadStatus.UPLOADING:\n return \"Uploading\";\n case UploadStatus.PAUSED:\n return \"Paused\";\n case UploadStatus.SUCCESS:\n return \"Success\";\n case UploadStatus.ERROR:\n return \"Error\";\n case UploadStatus.CANCELLED:\n return \"Cancelled\";\n default:\n return \"Unknown\";\n }\n };\n\n return (\n <div\n style={defaultStyles.container}\n data-testid=\"upload-item\"\n onClick={handleClick}\n onMouseEnter={(e) => {\n if (status === UploadStatus.SUCCESS && fileUrl) {\n e.currentTarget.style.backgroundColor = \"var(--card-bg, #1e1e1e)\";\n }\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = \"var(--darker-bg, #0f0f0f)\";\n }}\n title={status === UploadStatus.SUCCESS && fileUrl ? \"Click to open file\" : \"\"}\n >\n {/* File info */}\n <div style={defaultStyles.fileInfo}>\n <div style={{ display: \"flex\", alignItems: \"center\", flex: 1, minWidth: 0 }}>\n <span style={defaultStyles.fileName} title={task.file.name}>\n {task.file.name}\n </span>\n <span style={defaultStyles.fileSize}>{formatFileSize(task.file.size)}</span>\n </div>\n <span style={getStatusStyle(status)} data-testid=\"upload-status\">\n {getStatusText(status)}\n </span>\n </div>\n\n {/* Progress bar (only show for active uploads) */}\n {(status === UploadStatus.UPLOADING ||\n status === UploadStatus.PAUSED ||\n status === UploadStatus.HASHING) && <UploadProgress task={task} />}\n\n {/* Actions */}\n <div style={defaultStyles.actions} onClick={(e) => e.stopPropagation()}>\n {status === UploadStatus.UPLOADING && (\n <button\n onClick={() => task.pause()}\n style={defaultStyles.button}\n data-testid=\"pause-button\"\n type=\"button\"\n >\n Pause\n </button>\n )}\n\n {status === UploadStatus.PAUSED && (\n <button\n onClick={() => task.resume()}\n style={defaultStyles.button}\n data-testid=\"resume-button\"\n type=\"button\"\n >\n Resume\n </button>\n )}\n\n {(status === UploadStatus.UPLOADING || status === UploadStatus.PAUSED) && (\n <button\n onClick={() => task.cancel()}\n style={defaultStyles.button}\n data-testid=\"cancel-button\"\n type=\"button\"\n >\n Cancel\n </button>\n )}\n\n {status === UploadStatus.SUCCESS && fileUrl && (\n <button\n onClick={handleClick}\n style={defaultStyles.button}\n data-testid=\"open-button\"\n type=\"button\"\n >\n Open\n </button>\n )}\n\n <button\n onClick={onRemove}\n style={defaultStyles.button}\n data-testid=\"remove-button\"\n type=\"button\"\n >\n Remove\n </button>\n </div>\n </div>\n );\n}\n\n/**\n * UploadList component\n *\n * Displays a list of upload tasks with progress bars and action buttons.\n * Integrates with useUploadList hook to automatically sync with the upload manager.\n *\n * @example\n * ```tsx\n * // Basic usage\n * <UploadList />\n *\n * // With custom styling\n * <UploadList\n * className=\"my-upload-list\"\n * showCompleted={false}\n * maxItems={10}\n * />\n *\n * // With custom item renderer\n * <UploadList\n * renderItem={(task, actions) => (\n * <div>\n * <h3>{task.file.name}</h3>\n * <button onClick={actions.pause}>Pause</button>\n * <button onClick={actions.remove}>Remove</button>\n * </div>\n * )}\n * />\n * ```\n */\nexport function UploadList({\n className,\n style,\n baseURL,\n renderItem,\n showCompleted = true,\n showFailed = true,\n showCancelled = true,\n maxItems,\n emptyMessage = \"No uploads\",\n}: UploadListProps) {\n const { tasks, removeTask } = useUploadList();\n\n // Filter tasks based on props\n const filteredTasks = tasks.filter((task) => {\n const status = task.getStatus();\n\n if (!showCompleted && status === UploadStatus.SUCCESS) {\n return false;\n }\n\n if (!showFailed && status === UploadStatus.ERROR) {\n return false;\n }\n\n if (!showCancelled && status === UploadStatus.CANCELLED) {\n return false;\n }\n\n return true;\n });\n\n // Limit number of items if maxItems is set\n const displayTasks = maxItems ? filteredTasks.slice(0, maxItems) : filteredTasks;\n\n const defaultStyles = {\n container: {\n width: \"100%\",\n } as CSSProperties,\n empty: {\n padding: \"32px\",\n textAlign: \"center\" as const,\n color: \"var(--text-dim, #999)\",\n fontSize: \"14px\",\n } as CSSProperties,\n };\n\n // Show empty message if no tasks\n if (displayTasks.length === 0) {\n return (\n <div\n className={className}\n style={style || defaultStyles.container}\n data-testid=\"upload-list-empty\"\n >\n <div style={defaultStyles.empty}>{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div className={className} style={style || defaultStyles.container} data-testid=\"upload-list\">\n {displayTasks.map((task) => {\n const actions: UploadItemActions = {\n pause: () => task.pause(),\n resume: () => task.resume(),\n cancel: () => task.cancel(),\n remove: () => removeTask(task.id),\n };\n\n return (\n <div key={task.id} data-testid=\"upload-list-item\">\n {renderItem ? (\n renderItem(task, actions)\n ) : (\n <DefaultUploadItem task={task} onRemove={actions.remove} baseURL={baseURL} />\n )}\n </div>\n );\n })}\n </div>\n );\n}\n","import { useState, useRef, DragEvent, ReactNode, CSSProperties } from \"react\";\n\n/**\n * Props for the UploadDropzone component\n */\nexport interface UploadDropzoneProps {\n /**\n * Accepted file types (e.g., \"image/*\", \".pdf,.doc\")\n */\n accept?: string;\n\n /**\n * Maximum file size in bytes\n */\n maxSize?: number;\n\n /**\n * Allow multiple file selection\n * @default true\n */\n multiple?: boolean;\n\n /**\n * Callback when files are dropped\n */\n onDrop?: (files: File[]) => void;\n\n /**\n * Callback when file validation fails\n */\n onError?: (error: FileValidationError) => void;\n\n /**\n * Custom content to display in the dropzone\n */\n children?: ReactNode;\n\n /**\n * CSS class name for the container\n */\n className?: string;\n\n /**\n * Custom styles for the container\n */\n style?: CSSProperties;\n\n /**\n * CSS class name when dragging over\n */\n draggingClassName?: string;\n\n /**\n * Disable the dropzone\n * @default false\n */\n disabled?: boolean;\n}\n\n/**\n * File validation error\n */\nexport class FileValidationError extends Error {\n constructor(\n message: string,\n public code: \"FILE_TOO_LARGE\" | \"INVALID_FILE_TYPE\",\n public file: File,\n ) {\n super(message);\n this.name = \"FileValidationError\";\n }\n}\n\n/**\n * Check if a file matches the accept pattern\n *\n * @param file - The file to check\n * @param accept - The accept pattern (e.g., \"image/star\", \".pdf,.doc\", \"star/star\")\n * @returns true if the file matches the pattern\n */\nfunction matchAccept(file: File, accept: string): boolean {\n // Accept all files if pattern is \"*/*\" or \"*\"\n if (accept === \"*/*\" || accept === \"*\") {\n return true;\n }\n\n const patterns = accept.split(\",\").map((p) => p.trim());\n\n for (const pattern of patterns) {\n // Accept all files\n if (pattern === \"*/*\" || pattern === \"*\") {\n return true;\n }\n\n // Exact MIME type match (e.g., \"image/jpeg\")\n if (pattern === file.type) {\n return true;\n }\n\n // Wildcard MIME type match (e.g., \"image/*\")\n if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n if (file.type.startsWith(prefix + \"/\")) {\n return true;\n }\n }\n\n // File extension match (e.g., \".pdf\")\n if (pattern.startsWith(\".\")) {\n if (file.name.toLowerCase().endsWith(pattern.toLowerCase())) {\n return true;\n }\n }\n }\n\n return false;\n}\n\n/**\n * UploadDropzone component\n *\n * A drag-and-drop zone for file uploads with visual feedback.\n * Validates files based on type and size before passing them to the onDrop callback.\n *\n * @example\n * ```tsx\n * <UploadDropzone\n * accept=\"image/*\"\n * maxSize={10 * 1024 * 1024} // 10MB\n * multiple\n * onDrop={(files) => console.log('Dropped:', files)}\n * >\n * <p>Drag and drop files here, or click to select</p>\n * </UploadDropzone>\n * ```\n */\nexport function UploadDropzone({\n accept,\n maxSize,\n multiple = true,\n onDrop,\n onError,\n children,\n className,\n style,\n draggingClassName,\n disabled = false,\n}: UploadDropzoneProps) {\n const [isDragging, setIsDragging] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n const dragCounterRef = useRef(0);\n\n const validateFile = (file: File): FileValidationError | null => {\n // Validate file size\n if (maxSize && file.size > maxSize) {\n return new FileValidationError(\n `File \"${file.name}\" size ${file.size} bytes exceeds maximum ${maxSize} bytes`,\n \"FILE_TOO_LARGE\",\n file,\n );\n }\n\n // Validate file type\n if (accept && !matchAccept(file, accept)) {\n return new FileValidationError(\n `File \"${file.name}\" type \"${file.type}\" is not accepted`,\n \"INVALID_FILE_TYPE\",\n file,\n );\n }\n\n return null;\n };\n\n const processFiles = (files: File[]) => {\n if (files.length === 0) {\n return;\n }\n\n // Validate all files\n const validFiles: File[] = [];\n const errors: FileValidationError[] = [];\n\n for (const file of files) {\n const error = validateFile(file);\n if (error) {\n errors.push(error);\n } else {\n validFiles.push(file);\n }\n }\n\n // Report errors\n if (errors.length > 0 && onError) {\n errors.forEach((error) => onError(error));\n }\n\n // Call onDrop with valid files\n if (validFiles.length > 0 && onDrop) {\n onDrop(validFiles);\n }\n };\n\n const handleDragEnter = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n dragCounterRef.current++;\n if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {\n setIsDragging(true);\n }\n };\n\n const handleDragLeave = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n dragCounterRef.current--;\n if (dragCounterRef.current === 0) {\n setIsDragging(false);\n }\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n // Set dropEffect to indicate this is a copy operation\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = \"copy\";\n }\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n e.stopPropagation();\n\n if (disabled) return;\n\n setIsDragging(false);\n dragCounterRef.current = 0;\n\n const files = Array.from(e.dataTransfer?.files || []);\n\n // Limit to single file if multiple is false\n const filesToProcess = multiple ? files : files.slice(0, 1);\n\n processFiles(filesToProcess);\n };\n\n const handleClick = () => {\n if (!disabled) {\n inputRef.current?.click();\n }\n };\n\n const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const files = Array.from(e.target.files || []);\n processFiles(files);\n\n // Reset input value to allow selecting the same file again\n if (inputRef.current) {\n inputRef.current.value = \"\";\n }\n };\n\n const defaultStyles = {\n container: {\n border: \"2px dashed #ccc\",\n borderRadius: \"8px\",\n padding: \"32px\",\n textAlign: \"center\" as const,\n cursor: disabled ? \"not-allowed\" : \"pointer\",\n transition: \"all 0.3s ease\",\n backgroundColor: disabled ? \"#f5f5f5\" : \"#fafafa\",\n opacity: disabled ? 0.6 : 1,\n } as CSSProperties,\n dragging: {\n borderColor: \"#4caf50\",\n backgroundColor: \"#e8f5e9\",\n } as CSSProperties,\n content: {\n color: \"#666\",\n fontSize: \"14px\",\n } as CSSProperties,\n };\n\n const containerStyle = {\n ...defaultStyles.container,\n ...(isDragging && !disabled ? defaultStyles.dragging : {}),\n ...style,\n };\n\n const containerClassName = [className, isDragging && !disabled ? draggingClassName : \"\"]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <>\n <div\n className={containerClassName || undefined}\n style={containerStyle}\n onDragEnter={handleDragEnter}\n onDragLeave={handleDragLeave}\n onDragOver={handleDragOver}\n onDrop={handleDrop}\n onClick={handleClick}\n data-testid=\"upload-dropzone\"\n data-dragging={isDragging}\n role=\"button\"\n tabIndex={disabled ? -1 : 0}\n aria-disabled={disabled}\n onKeyDown={(e) => {\n if (!disabled && (e.key === \"Enter\" || e.key === \" \")) {\n e.preventDefault();\n handleClick();\n }\n }}\n >\n {children || (\n <div style={defaultStyles.content} data-testid=\"dropzone-default-content\">\n <p>\n <strong>Drag and drop files here</strong>\n </p>\n <p>or click to select files</p>\n </div>\n )}\n </div>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n multiple={multiple}\n onChange={handleInputChange}\n style={{ display: \"none\" }}\n aria-hidden=\"true\"\n disabled={disabled}\n />\n </>\n );\n}\n","// React components exports\nexport { UploadButton, FileValidationError } from \"./UploadButton\";\nexport type { UploadButtonProps } from \"./UploadButton\";\n\nexport { UploadProgress } from \"./UploadProgress\";\nexport type { UploadProgressProps } from \"./UploadProgress\";\n\nexport { UploadList } from \"./UploadList\";\nexport type { UploadListProps, UploadItemActions } from \"./UploadList\";\n\nexport { UploadDropzone } from \"./UploadDropzone\";\nexport type { UploadDropzoneProps } from \"./UploadDropzone\";\n\n// Placeholder export to allow build to succeed\nexport const UPLOAD_COMPONENT_REACT_VERSION = \"0.0.0\";\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@chunkflowjs/upload-component-react",
3
+ "version": "0.0.1-alpha.1",
4
+ "description": "React UI components for ChunkFlow Upload SDK",
5
+ "keywords": [
6
+ "components",
7
+ "react",
8
+ "typescript",
9
+ "ui",
10
+ "upload"
11
+ ],
12
+ "license": "MIT",
13
+ "author": "",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "type": "module",
18
+ "main": "./dist/index.mjs",
19
+ "module": "./dist/index.mjs",
20
+ "types": "./dist/index.d.mts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.mts",
24
+ "import": "./dist/index.mjs"
25
+ }
26
+ },
27
+ "scripts": {
28
+ "build": "tsdown src/index.ts --format esm --dts",
29
+ "dev": "tsdown src/index.ts --format esm --dts --watch",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "typecheck": "tsc --noEmit",
33
+ "clean": "rm -rf dist"
34
+ },
35
+ "dependencies": {
36
+ "@chunkflowjs/core": "workspace:*",
37
+ "@chunkflowjs/shared": "workspace:*",
38
+ "@chunkflowjs/upload-client-react": "workspace:*"
39
+ },
40
+ "devDependencies": {
41
+ "@testing-library/jest-dom": "^6.9.1",
42
+ "@testing-library/react": "^16.3.2",
43
+ "@testing-library/user-event": "^14.6.1",
44
+ "@types/react": "^19.2.10",
45
+ "@types/react-dom": "^19.2.3",
46
+ "react": "^19.2.4",
47
+ "react-dom": "^19.2.4",
48
+ "tsdown": "^0.20.1",
49
+ "typescript": "^5.9.3",
50
+ "vitest": "^4.0.18"
51
+ },
52
+ "peerDependencies": {
53
+ "react": "^18.0.0",
54
+ "react-dom": "^18.0.0"
55
+ }
56
+ }