react-upload-pro 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +736 -0
- package/dist/UploadProgress-8cEls1YP.d.cts +145 -0
- package/dist/UploadProgress-uRdhENqW.d.ts +145 -0
- package/dist/chunk-JI2V5ITY.js +2985 -0
- package/dist/chunk-JI2V5ITY.js.map +1 -0
- package/dist/chunk-VVMPVTIX.cjs +3030 -0
- package/dist/chunk-VVMPVTIX.cjs.map +1 -0
- package/dist/cloud/index.cjs +287 -0
- package/dist/cloud/index.cjs.map +1 -0
- package/dist/cloud/index.d.cts +125 -0
- package/dist/cloud/index.d.ts +125 -0
- package/dist/cloud/index.js +279 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/index.cjs +534 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +377 -0
- package/dist/index.d.ts +377 -0
- package/dist/index.js +351 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.css +81 -0
- package/dist/types-BDyFUrlv.d.cts +218 -0
- package/dist/types-BDyFUrlv.d.ts +218 -0
- package/dist/variants/index.cjs +1028 -0
- package/dist/variants/index.cjs.map +1 -0
- package/dist/variants/index.d.cts +106 -0
- package/dist/variants/index.d.ts +106 -0
- package/dist/variants/index.js +1005 -0
- package/dist/variants/index.js.map +1 -0
- package/package.json +122 -0
- package/tailwind.preset.cjs +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# react-upload-pro
|
|
4
|
+
|
|
5
|
+
**The most feature-rich React file upload & dropzone library.**
|
|
6
|
+
|
|
7
|
+
Drag & drop · chunk uploads · cloud adapters · 21+ UI variants · i18n in 23 locales · a11y · SSR-safe · TypeScript-first.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/react-upload-pro)
|
|
10
|
+
[](#typescript)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
[](#performance)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Table of contents
|
|
19
|
+
|
|
20
|
+
- [Why react-upload-pro?](#why-react-upload-pro)
|
|
21
|
+
- [Install](#install)
|
|
22
|
+
- [30-second example](#30-second-example)
|
|
23
|
+
- [Step-by-step setup](#step-by-step-setup)
|
|
24
|
+
- [1. Vite + React](#1-vite--react)
|
|
25
|
+
- [2. Next.js (App Router)](#2-nextjs-app-router)
|
|
26
|
+
- [3. Create React App](#3-create-react-app)
|
|
27
|
+
- [Tailwind setup](#tailwind-setup)
|
|
28
|
+
- [Core usage](#core-usage)
|
|
29
|
+
- [Pre-built variants](#pre-built-variants)
|
|
30
|
+
- [Hooks-first usage](#hooks-first-usage)
|
|
31
|
+
- [Cloud adapters](#cloud-adapters)
|
|
32
|
+
- [Validation](#validation)
|
|
33
|
+
- [Internationalization (i18n)](#internationalization-i18n)
|
|
34
|
+
- [Theming](#theming)
|
|
35
|
+
- [Upload modes & strategies](#upload-modes--strategies)
|
|
36
|
+
- [Props reference](#props-reference)
|
|
37
|
+
- [TypeScript](#typescript)
|
|
38
|
+
- [SSR / Next.js notes](#ssr--nextjs-notes)
|
|
39
|
+
- [Performance](#performance)
|
|
40
|
+
- [Contributing / development](#contributing--development)
|
|
41
|
+
- [License](#license)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Why react-upload-pro?
|
|
46
|
+
|
|
47
|
+
`react-dropzone` stops at "drop files, get a callback". `react-upload-pro` ships everything you need to ship a real upload feature:
|
|
48
|
+
|
|
49
|
+
- ✅ **Inputs** — drag/drop, click, paste from clipboard, folder upload (recursive)
|
|
50
|
+
- ✅ **Upload engine** — `instant` / `manual` / `auto` / `queue` modes, parallel + sequential, chunked, pause / resume / retry / cancel
|
|
51
|
+
- ✅ **Cloud adapters** — AWS S3 (presigned), Cloudinary, Firebase Storage, Supabase, DigitalOcean Spaces, Azure Blob, GCS
|
|
52
|
+
- ✅ **Validation** — MIME, extension, size (min/max), max files, duplicates, magic-number signatures, custom async validators
|
|
53
|
+
- ✅ **Preview** — image / video / audio / PDF / text / Office (icon) with zoom & rotate
|
|
54
|
+
- ✅ **Progress** — bar / circle / striped, per-file + aggregate, EWMA speed + ETA
|
|
55
|
+
- ✅ **21+ UI variants** across Minimal / Business / Creative / Enterprise / Layouts
|
|
56
|
+
- ✅ **a11y** — ARIA roles, keyboard nav, focus rings, RTL
|
|
57
|
+
- ✅ **i18n** — 23 built-in locales (en, hi, gu, fr, de, ar, zh, es, pt, ru, ja, ko, it, tr, id, bn, ur, nl, pl, vi, th, he, fa) + custom messages
|
|
58
|
+
- ✅ **Theming** — light / dark / auto with CSS variables and a Tailwind preset
|
|
59
|
+
- ✅ **Framework agnostic** — works with Axios / Fetch / GraphQL / REST via custom `getUploadToken`
|
|
60
|
+
- ✅ **Tree-shakeable** — core ships without `framer-motion` or any cloud SDK
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# npm
|
|
68
|
+
npm install react-upload-pro
|
|
69
|
+
|
|
70
|
+
# yarn
|
|
71
|
+
yarn add react-upload-pro
|
|
72
|
+
|
|
73
|
+
# pnpm
|
|
74
|
+
pnpm add react-upload-pro
|
|
75
|
+
|
|
76
|
+
# bun
|
|
77
|
+
bun add react-upload-pro
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Peer deps: `react >= 17`, `react-dom >= 17`. `framer-motion` is optional (only some animated variants need it).
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 30-second example
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { Dropzone, ThemeProvider } from "react-upload-pro";
|
|
88
|
+
import "react-upload-pro/styles.css";
|
|
89
|
+
|
|
90
|
+
export default function App() {
|
|
91
|
+
return (
|
|
92
|
+
<ThemeProvider defaultTheme="auto">
|
|
93
|
+
<Dropzone
|
|
94
|
+
endpoint="/api/upload"
|
|
95
|
+
accept="image/*,application/pdf"
|
|
96
|
+
maxSize={10 * 1024 * 1024} // 10 MB
|
|
97
|
+
maxFiles={20}
|
|
98
|
+
mode="auto"
|
|
99
|
+
retries={3}
|
|
100
|
+
onUploadSuccess={(file) => console.log("done", file)}
|
|
101
|
+
onUploadError={(file, err) => console.error(err)}
|
|
102
|
+
/>
|
|
103
|
+
</ThemeProvider>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
That's it. The component handles drag/drop, validation, progress bars, retries, previews, and gallery rendering.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Step-by-step setup
|
|
113
|
+
|
|
114
|
+
### 1. Vite + React
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# 1. Create a Vite app
|
|
118
|
+
npm create vite@latest my-app -- --template react-ts
|
|
119
|
+
cd my-app
|
|
120
|
+
npm install
|
|
121
|
+
|
|
122
|
+
# 2. Install the package
|
|
123
|
+
npm install react-upload-pro
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// src/App.tsx
|
|
128
|
+
import { Dropzone } from "react-upload-pro";
|
|
129
|
+
import "react-upload-pro/styles.css";
|
|
130
|
+
|
|
131
|
+
export default function App() {
|
|
132
|
+
return (
|
|
133
|
+
<div style={{ padding: 24 }}>
|
|
134
|
+
<Dropzone endpoint="/api/upload" maxSize={5 * 1024 * 1024} />
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm run dev # → http://localhost:5173
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 2. Next.js (App Router)
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npx create-next-app@latest my-app --typescript --app
|
|
148
|
+
cd my-app
|
|
149
|
+
npm install react-upload-pro
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
// app/upload/page.tsx
|
|
154
|
+
"use client";
|
|
155
|
+
|
|
156
|
+
import { Dropzone, ThemeProvider } from "react-upload-pro";
|
|
157
|
+
import "react-upload-pro/styles.css";
|
|
158
|
+
|
|
159
|
+
export default function UploadPage() {
|
|
160
|
+
return (
|
|
161
|
+
<ThemeProvider defaultTheme="auto">
|
|
162
|
+
<Dropzone
|
|
163
|
+
endpoint="/api/upload"
|
|
164
|
+
accept="image/*"
|
|
165
|
+
maxSize={10 * 1024 * 1024}
|
|
166
|
+
/>
|
|
167
|
+
</ThemeProvider>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
// app/api/upload/route.ts
|
|
174
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
175
|
+
|
|
176
|
+
export async function POST(req: NextRequest) {
|
|
177
|
+
const formData = await req.formData();
|
|
178
|
+
const file = formData.get("file") as File;
|
|
179
|
+
// ...save to disk / S3 / Cloudinary / etc.
|
|
180
|
+
return NextResponse.json({ url: "/uploads/" + file.name });
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> **Why `'use client'`?** The component uses browser APIs (drag events, `File`, `FileReader`). The package emits a `"use client"` banner, so importing from a server component works — but pages that render it directly need the directive.
|
|
185
|
+
|
|
186
|
+
### 3. Create React App
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
npx create-react-app my-app --template typescript
|
|
190
|
+
cd my-app
|
|
191
|
+
npm install react-upload-pro
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Same `src/App.tsx` as the Vite example — CRA just works.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Tailwind setup
|
|
199
|
+
|
|
200
|
+
If you use Tailwind, plug in the preset to pick up `react-upload-pro`'s CSS variables and utilities:
|
|
201
|
+
|
|
202
|
+
```js
|
|
203
|
+
// tailwind.config.js
|
|
204
|
+
module.exports = {
|
|
205
|
+
presets: [require("react-upload-pro/tailwind")],
|
|
206
|
+
content: [
|
|
207
|
+
"./src/**/*.{ts,tsx,js,jsx}",
|
|
208
|
+
"./node_modules/react-upload-pro/dist/**/*.{js,cjs}",
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Then import the base styles **once** (e.g. in your root layout / `main.tsx`):
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import "react-upload-pro/styles.css";
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Not using Tailwind? You can skip the preset and just import the CSS — everything still works, the preset only matters if you want to extend the design tokens.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Core usage
|
|
224
|
+
|
|
225
|
+
### Minimum
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
<Dropzone endpoint="/api/upload" />
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Real-world example
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { Dropzone, type UploadFile } from "react-upload-pro";
|
|
235
|
+
import "react-upload-pro/styles.css";
|
|
236
|
+
|
|
237
|
+
export function ProfilePictureUploader() {
|
|
238
|
+
return (
|
|
239
|
+
<Dropzone
|
|
240
|
+
endpoint="/api/avatar"
|
|
241
|
+
accept="image/*"
|
|
242
|
+
maxSize={2 * 1024 * 1024} // 2 MB
|
|
243
|
+
maxFiles={1}
|
|
244
|
+
multiple={false}
|
|
245
|
+
mode="instant" // upload immediately on drop
|
|
246
|
+
previewable // eye icon → fullscreen preview
|
|
247
|
+
editable // pencil icon → rename + tag
|
|
248
|
+
retries={2}
|
|
249
|
+
onUploadStart={(f: UploadFile) => console.log("uploading", f.name)}
|
|
250
|
+
onUploadSuccess={(f) => console.log("done", f.url)}
|
|
251
|
+
onUploadError={(f, e) => alert(e.message)}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Custom label / hint
|
|
258
|
+
|
|
259
|
+
```tsx
|
|
260
|
+
<Dropzone
|
|
261
|
+
endpoint="/api/upload"
|
|
262
|
+
label="Drop your resume here"
|
|
263
|
+
hint="PDF or DOCX, up to 5 MB"
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Manual control with the render prop
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
<Dropzone endpoint="/api/upload" maxSize={5e6}>
|
|
271
|
+
{({ getRootProps, getInputProps, files, start, isUploading }) => (
|
|
272
|
+
<div>
|
|
273
|
+
<div
|
|
274
|
+
{...getRootProps()}
|
|
275
|
+
className="border-2 border-dashed p-8 rounded-lg"
|
|
276
|
+
>
|
|
277
|
+
<input {...getInputProps()} />
|
|
278
|
+
Drop files or click
|
|
279
|
+
</div>
|
|
280
|
+
<p>{files.length} queued</p>
|
|
281
|
+
<button onClick={() => start()} disabled={isUploading}>
|
|
282
|
+
Upload
|
|
283
|
+
</button>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
</Dropzone>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## Pre-built variants
|
|
292
|
+
|
|
293
|
+
21+ designs grouped into 5 categories. Every variant accepts the same options as `Dropzone`, so any feature works on any look.
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
import {
|
|
297
|
+
MinimalGlass,
|
|
298
|
+
BusinessCRM,
|
|
299
|
+
EnterpriseDocs,
|
|
300
|
+
LayoutModal,
|
|
301
|
+
} from "react-upload-pro/variants";
|
|
302
|
+
|
|
303
|
+
<MinimalGlass endpoint="/api/upload" accent="#6366f1" />;
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
| Category | Variants |
|
|
307
|
+
| -------------- | ---------------------------------------------------------------------------------------- |
|
|
308
|
+
| **Minimal** | `MinimalModern`, `MinimalGlass`, `MinimalNeumorphic`, `MinimalMaterial`, `MinimalInline` |
|
|
309
|
+
| **Business** | `BusinessCRM`, `BusinessDashboard`, `BusinessSaaS` |
|
|
310
|
+
| **Creative** | `CreativeGradient`, `CreativeAnimated`, `CreativePremium`, `CreativeAvatar` |
|
|
311
|
+
| **Enterprise** | `EnterpriseDocs`, `EnterpriseTeam`, `EnterpriseMediaLibrary`, `EnterpriseFullscreen` |
|
|
312
|
+
| **Layouts** | `LayoutBox`, `LayoutCard`, `LayoutSidebar`, `LayoutModal`, `LayoutFloating` |
|
|
313
|
+
|
|
314
|
+
Try them all live in the playground: `npm run dev` after cloning.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Hooks-first usage
|
|
319
|
+
|
|
320
|
+
For full control — no built-in UI, no gallery, just upload state and helpers — use `useDropzone`:
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
import { useDropzone, UploadGallery } from "react-upload-pro";
|
|
324
|
+
import "react-upload-pro/styles.css";
|
|
325
|
+
|
|
326
|
+
function MyUploader() {
|
|
327
|
+
const {
|
|
328
|
+
getRootProps,
|
|
329
|
+
getInputProps,
|
|
330
|
+
files,
|
|
331
|
+
isUploading,
|
|
332
|
+
isDragActive,
|
|
333
|
+
start,
|
|
334
|
+
pause,
|
|
335
|
+
resume,
|
|
336
|
+
remove,
|
|
337
|
+
retry,
|
|
338
|
+
clear,
|
|
339
|
+
} = useDropzone({
|
|
340
|
+
endpoint: "/api/upload",
|
|
341
|
+
accept: { "image/*": [".png", ".jpg", ".webp"] },
|
|
342
|
+
maxSize: 5 * 1024 * 1024,
|
|
343
|
+
mode: "manual", // wait for explicit start()
|
|
344
|
+
chunkSize: 5 * 1024 * 1024, // 5 MB chunks
|
|
345
|
+
strategy: "parallel",
|
|
346
|
+
concurrency: 3,
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
return (
|
|
350
|
+
<div>
|
|
351
|
+
<div
|
|
352
|
+
{...getRootProps()}
|
|
353
|
+
className={`border-2 border-dashed p-8 rounded-lg ${
|
|
354
|
+
isDragActive ? "border-blue-500 bg-blue-50" : "border-gray-300"
|
|
355
|
+
}`}
|
|
356
|
+
>
|
|
357
|
+
<input {...getInputProps()} />
|
|
358
|
+
{isDragActive ? "Drop here…" : "Drop files or click"}
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<UploadGallery
|
|
362
|
+
files={files}
|
|
363
|
+
onRemove={(f) => remove(f.id)}
|
|
364
|
+
onRetry={(f) => retry(f.id)}
|
|
365
|
+
/>
|
|
366
|
+
|
|
367
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
368
|
+
<button onClick={() => start()} disabled={isUploading}>
|
|
369
|
+
Upload
|
|
370
|
+
</button>
|
|
371
|
+
<button onClick={pause}>Pause</button>
|
|
372
|
+
<button onClick={resume}>Resume</button>
|
|
373
|
+
<button onClick={clear}>Clear</button>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## Cloud adapters
|
|
383
|
+
|
|
384
|
+
Upload directly to your cloud bucket without proxying through your server. Credentials never leave your backend — the adapter just consumes presigned URLs / signed tokens / SAS.
|
|
385
|
+
|
|
386
|
+
### AWS S3
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
import { useDropzone } from "react-upload-pro";
|
|
390
|
+
import { createS3Adapter } from "react-upload-pro/cloud";
|
|
391
|
+
|
|
392
|
+
const s3 = createS3Adapter({
|
|
393
|
+
getPresignedUrl: async (file) => {
|
|
394
|
+
const res = await fetch("/api/s3/presign", {
|
|
395
|
+
method: "POST",
|
|
396
|
+
body: JSON.stringify({ name: file.name, type: file.type }),
|
|
397
|
+
});
|
|
398
|
+
return res.json(); // { url, method: 'PUT', headers? }
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
useDropzone({ cloud: s3, mode: "auto" });
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
// /api/s3/presign (Next.js Route Handler example)
|
|
407
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
408
|
+
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
409
|
+
import { PutObjectCommand } from "@aws-sdk/client-s3";
|
|
410
|
+
|
|
411
|
+
const s3 = new S3Client({ region: process.env.AWS_REGION });
|
|
412
|
+
|
|
413
|
+
export async function POST(req: Request) {
|
|
414
|
+
const { name, type } = await req.json();
|
|
415
|
+
const cmd = new PutObjectCommand({
|
|
416
|
+
Bucket: process.env.S3_BUCKET,
|
|
417
|
+
Key: name,
|
|
418
|
+
ContentType: type,
|
|
419
|
+
});
|
|
420
|
+
const url = await getSignedUrl(s3, cmd, { expiresIn: 60 });
|
|
421
|
+
return Response.json({ url, method: "PUT" });
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Other adapters
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import {
|
|
429
|
+
createCloudinaryAdapter,
|
|
430
|
+
createFirebaseStorageAdapter,
|
|
431
|
+
createSupabaseAdapter,
|
|
432
|
+
createDigitalOceanAdapter,
|
|
433
|
+
createAzureBlobAdapter,
|
|
434
|
+
createGcsAdapter,
|
|
435
|
+
} from "react-upload-pro/cloud";
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Every adapter has the same shape — pass it to `cloud:` on `Dropzone` or `useDropzone`.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Validation
|
|
443
|
+
|
|
444
|
+
```ts
|
|
445
|
+
useDropzone({
|
|
446
|
+
accept: { "image/*": [".png", ".jpg"] },
|
|
447
|
+
minSize: 1024, // 1 KB
|
|
448
|
+
maxSize: 5 * 1024 * 1024, // 5 MB
|
|
449
|
+
maxFiles: 10,
|
|
450
|
+
rejectDuplicates: true, // same name + size + lastModified
|
|
451
|
+
validators: [
|
|
452
|
+
async (file) =>
|
|
453
|
+
file.name.includes(" ")
|
|
454
|
+
? { code: "custom", message: "No spaces in filename" }
|
|
455
|
+
: null,
|
|
456
|
+
],
|
|
457
|
+
});
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
For security-sensitive flows, validate the real magic number instead of trusting the MIME the browser reports:
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
import { detectSignature } from "react-upload-pro";
|
|
464
|
+
|
|
465
|
+
const actual = await detectSignature(file); // → 'image/png' | 'application/pdf' | ...
|
|
466
|
+
if (!actual) throw new Error("unknown file type");
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Showing rejection errors as a modal
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
import {
|
|
473
|
+
Dropzone,
|
|
474
|
+
ValidationErrorsModal,
|
|
475
|
+
type ValidationError,
|
|
476
|
+
} from "react-upload-pro";
|
|
477
|
+
import { useState } from "react";
|
|
478
|
+
|
|
479
|
+
function App() {
|
|
480
|
+
const [errors, setErrors] = useState<ValidationError[]>([]);
|
|
481
|
+
return (
|
|
482
|
+
<>
|
|
483
|
+
<Dropzone endpoint="/api/upload" onDropRejected={setErrors} />
|
|
484
|
+
<ValidationErrorsModal
|
|
485
|
+
open={errors.length > 0}
|
|
486
|
+
errors={errors}
|
|
487
|
+
onClose={() => setErrors([])}
|
|
488
|
+
/>
|
|
489
|
+
</>
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## Internationalization (i18n)
|
|
497
|
+
|
|
498
|
+
23 built-in locales — wrap with `I18nProvider` and the dropzone UI translates automatically:
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
import { I18nProvider, Dropzone } from "react-upload-pro";
|
|
502
|
+
|
|
503
|
+
<I18nProvider locale="ja">
|
|
504
|
+
<Dropzone endpoint="/api/upload" />
|
|
505
|
+
</I18nProvider>;
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### Supported locales
|
|
509
|
+
|
|
510
|
+
| Code | Language | RTL |
|
|
511
|
+
| ---- | ---------------- | --- |
|
|
512
|
+
| `en` | English | |
|
|
513
|
+
| `es` | Español | |
|
|
514
|
+
| `fr` | Français | |
|
|
515
|
+
| `de` | Deutsch | |
|
|
516
|
+
| `it` | Italiano | |
|
|
517
|
+
| `pt` | Português | |
|
|
518
|
+
| `nl` | Nederlands | |
|
|
519
|
+
| `pl` | Polski | |
|
|
520
|
+
| `ru` | Русский | |
|
|
521
|
+
| `tr` | Türkçe | |
|
|
522
|
+
| `zh` | 中文 | |
|
|
523
|
+
| `ja` | 日本語 | |
|
|
524
|
+
| `ko` | 한국어 | |
|
|
525
|
+
| `vi` | Tiếng Việt | |
|
|
526
|
+
| `th` | ไทย | |
|
|
527
|
+
| `id` | Bahasa Indonesia | |
|
|
528
|
+
| `hi` | हिन्दी | |
|
|
529
|
+
| `gu` | ગુજરાતી | |
|
|
530
|
+
| `bn` | বাংলা | |
|
|
531
|
+
| `ar` | العربية | ✓ |
|
|
532
|
+
| `ur` | اردو | ✓ |
|
|
533
|
+
| `he` | עברית | ✓ |
|
|
534
|
+
| `fa` | فارسی | ✓ |
|
|
535
|
+
|
|
536
|
+
### Custom messages
|
|
537
|
+
|
|
538
|
+
Override any string per-locale:
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
<I18nProvider
|
|
542
|
+
locale="en"
|
|
543
|
+
messages={{ dropHere: "Drop your resume here", browse: "Pick a PDF" }}
|
|
544
|
+
>
|
|
545
|
+
<Dropzone endpoint="/api/upload" />
|
|
546
|
+
</I18nProvider>
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Check the RTL set programmatically
|
|
550
|
+
|
|
551
|
+
```ts
|
|
552
|
+
import { rtlLocales } from "react-upload-pro";
|
|
553
|
+
|
|
554
|
+
const isRtl = rtlLocales.has(currentLocale);
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## Theming
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
import { ThemeProvider, useTheme } from "react-upload-pro";
|
|
563
|
+
|
|
564
|
+
<ThemeProvider defaultTheme="auto">
|
|
565
|
+
{" "}
|
|
566
|
+
{/* 'light' | 'dark' | 'auto' */}
|
|
567
|
+
<Dropzone endpoint="/api/upload" />
|
|
568
|
+
</ThemeProvider>;
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Custom accent color
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
<Dropzone endpoint="/api/upload" accent="#10b981" />
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
Or via CSS variable on any ancestor:
|
|
578
|
+
|
|
579
|
+
```css
|
|
580
|
+
:root {
|
|
581
|
+
--rup-accent: 16 185 129; /* RGB triplet, no rgb() wrapper */
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
The accent drives buttons, progress, focus rings, and scrollbars.
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Upload modes & strategies
|
|
590
|
+
|
|
591
|
+
| Option | Values | Default | Description |
|
|
592
|
+
| ---------------- | -------------------------------------------------------------- | ------------ | ------------------------- |
|
|
593
|
+
| `mode` | `'manual'` | `'instant'` | `'auto'` | `'queue'` | `'manual'` | When to start uploading |
|
|
594
|
+
| `strategy` | `'parallel'` | `'sequential'` | `'parallel'` | How to dispatch the queue |
|
|
595
|
+
| `concurrency` | number | `3` | Parallel upload slots |
|
|
596
|
+
| `retries` | number | `2` | Retry attempts per file |
|
|
597
|
+
| `retryBackoffMs` | number | `500` | Doubles each retry |
|
|
598
|
+
| `chunkSize` | number (bytes) | unset | Single-shot if unset |
|
|
599
|
+
|
|
600
|
+
### Mode cheat sheet
|
|
601
|
+
|
|
602
|
+
- **`manual`** — files queue up; user clicks an "Upload" button to start
|
|
603
|
+
- **`instant`** — each file starts uploading the moment it's dropped
|
|
604
|
+
- **`auto`** — same as `instant`, but adds a small debounce so multi-drop bursts batch nicely
|
|
605
|
+
- **`queue`** — strictly one at a time, in drop order, ignoring `concurrency`
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
## Props reference
|
|
610
|
+
|
|
611
|
+
### `<Dropzone>` (most common)
|
|
612
|
+
|
|
613
|
+
| Prop | Type | Notes |
|
|
614
|
+
| ------------------ | -------------------------------------------- | ---------------------------------------------------------------- |
|
|
615
|
+
| `endpoint` | `string` | URL for the multipart `POST`. Use `cloud:` for direct-to-S3 etc. |
|
|
616
|
+
| `cloud` | `CloudAdapter` | Direct cloud upload (mutually exclusive with `endpoint`) |
|
|
617
|
+
| `accept` | `string` | `Accept` | `"image/*"`, `".pdf,.docx"`, or `{ 'image/*': ['.png'] }` |
|
|
618
|
+
| `maxSize` | `number` | Bytes |
|
|
619
|
+
| `minSize` | `number` | Bytes |
|
|
620
|
+
| `maxFiles` | `number` | |
|
|
621
|
+
| `multiple` | `boolean` | Default `true` |
|
|
622
|
+
| `directory` | `boolean` | Folder upload (recursive) |
|
|
623
|
+
| `clipboard` | `boolean` | Paste from clipboard. Default `true` |
|
|
624
|
+
| `rejectDuplicates` | `boolean` | Default `false` |
|
|
625
|
+
| `disabled` | `boolean` | |
|
|
626
|
+
| `mode` | `'manual' \| 'instant' \| 'auto' \| 'queue'` | Default `'manual'` |
|
|
627
|
+
| `strategy` | `'parallel' \| 'sequential'` | Default `'parallel'` |
|
|
628
|
+
| `concurrency` | `number` | Default `3` |
|
|
629
|
+
| `retries` | `number` | Default `2` |
|
|
630
|
+
| `chunkSize` | `number` | Bytes. Unset = single-shot upload |
|
|
631
|
+
| `label` | `ReactNode` | Replaces the default heading |
|
|
632
|
+
| `hint` | `ReactNode` | Small descriptive line under the label |
|
|
633
|
+
| `previewable` | `boolean` | Eye icon → fullscreen preview |
|
|
634
|
+
| `editable` | `boolean` | Pencil icon → rename + tag + describe |
|
|
635
|
+
| `scrollAfter` | `number` | List becomes scrollable above this count |
|
|
636
|
+
| `maxHeight` | `string` | CSS height of the scrollable region |
|
|
637
|
+
| `width` / `height` | `string` | Outer container CSS sizing |
|
|
638
|
+
| `onDrop` | `(accepted, rejected) => void` | |
|
|
639
|
+
| `onDropRejected` | `(errors) => void` | |
|
|
640
|
+
| `onUploadStart` | `(file) => void` | |
|
|
641
|
+
| `onUploadProgress` | `(file, progress) => void` | |
|
|
642
|
+
| `onUploadSuccess` | `(file) => void` | |
|
|
643
|
+
| `onUploadError` | `(file, error) => void` | |
|
|
644
|
+
|
|
645
|
+
### Full API surface
|
|
646
|
+
|
|
647
|
+
- **Components** — `Dropzone`, `UploadArea`, `UploadButton`, `UploadProgress`, `UploadPreview`, `UploadGallery`, `UploadModal`, `FilePreviewModal`, `FileEditModal`, `ValidationErrorsModal`
|
|
648
|
+
- **Hooks** — `useDropzone`, `useUploader`, `useUploadQueue`, `useUploadProgress`, `useFilePreview`
|
|
649
|
+
- **Providers** — `ThemeProvider`, `I18nProvider`
|
|
650
|
+
- **Core** — `UploadQueue`, `validateFile`, `validateBatch`, `matchesAccept`
|
|
651
|
+
- **Cloud (subpath)** — `createS3Adapter`, `createCloudinaryAdapter`, `createFirebaseStorageAdapter`, `createSupabaseAdapter`, `createDigitalOceanAdapter`, `createAzureBlobAdapter`, `createGcsAdapter`
|
|
652
|
+
- **Variants (subpath)** — 21 named exports (see [Pre-built variants](#pre-built-variants))
|
|
653
|
+
- **Utilities** — `formatBytes`, `formatSpeed`, `formatEta`, `formatPercent`, `getFileCategory`, `detectSignature`, `wrapFile`, `generatePreview`, `revokePreview`, `cn`
|
|
654
|
+
- **i18n exports** — `translations`, `rtlLocales`, type `Locale`, type `Translations`
|
|
655
|
+
|
|
656
|
+
See [`docs/API.md`](./docs/API.md) for the full reference (when published).
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## TypeScript
|
|
661
|
+
|
|
662
|
+
Fully typed — every public API has `.d.ts`. The package exports both ESM and CJS with separate type declarations so it works in any modern bundler.
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
import type {
|
|
666
|
+
DropzoneOptions,
|
|
667
|
+
DropzoneState,
|
|
668
|
+
UploadFile,
|
|
669
|
+
UploadStatus,
|
|
670
|
+
ValidationError,
|
|
671
|
+
CloudAdapter,
|
|
672
|
+
Locale,
|
|
673
|
+
Theme,
|
|
674
|
+
} from "react-upload-pro";
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## SSR / Next.js notes
|
|
680
|
+
|
|
681
|
+
- Every public surface is **SSR-safe** — DOM APIs are only touched inside `useEffect`.
|
|
682
|
+
- The package emits a `"use client"` directive, so importing from a server component works transparently. Pages that render `Dropzone` directly still need `'use client'` at the top.
|
|
683
|
+
- The base CSS (`react-upload-pro/styles.css`) is plain CSS — import it once in your root layout.
|
|
684
|
+
|
|
685
|
+
```tsx
|
|
686
|
+
// app/layout.tsx
|
|
687
|
+
import "react-upload-pro/styles.css";
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
## Performance
|
|
693
|
+
|
|
694
|
+
- Default render path is **O(n)** on file count.
|
|
695
|
+
- For galleries beyond a few hundred files, use `scrollAfter={N}` to cap the rendered region. For thousands of files, virtualize with your own list library — the gallery layout is intentionally pluggable.
|
|
696
|
+
- `previewUrl` is lazily generated and **automatically revoked** on remove / unmount, so no object-URL leaks.
|
|
697
|
+
- Core is tree-shakeable. `framer-motion` and cloud SDKs are not imported until you opt in.
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Contributing / development
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
git clone https://github.com/yogeshgabani/react-upload-pro.git
|
|
705
|
+
cd react-upload-pro
|
|
706
|
+
npm install
|
|
707
|
+
|
|
708
|
+
# 🎮 Interactive playground — every variant, every option, live code preview
|
|
709
|
+
npm run dev # → http://localhost:5173
|
|
710
|
+
|
|
711
|
+
# Other scripts
|
|
712
|
+
npm run dev:lib # tsup --watch — rebuild library on every save
|
|
713
|
+
npm run storybook # individual component stories on :6006
|
|
714
|
+
npm test # vitest unit + component
|
|
715
|
+
npm run test:e2e # playwright smoke
|
|
716
|
+
npm run build # tsup → dist/ (ESM + CJS + .d.ts)
|
|
717
|
+
npm run typecheck # tsc --noEmit
|
|
718
|
+
npm run lint # eslint
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
The playground at `npm run dev` is the fastest way to explore the API — every prop is wired to a UI control and the generated code updates live as you tweak.
|
|
722
|
+
|
|
723
|
+
PRs welcome — please:
|
|
724
|
+
|
|
725
|
+
1. Open an issue first for non-trivial changes
|
|
726
|
+
2. Add a test for new behavior
|
|
727
|
+
3. Run `npm run typecheck && npm run lint && npm test` before pushing
|
|
728
|
+
|
|
729
|
+
---
|
|
730
|
+
|
|
731
|
+
## License
|
|
732
|
+
|
|
733
|
+
[MIT](./LICENSE) © Yogesh Gabani
|
|
734
|
+
MIT License - Copyright (c) 2026 **Yogesh Gabani**
|
|
735
|
+
|
|
736
|
+
Built by **Yogesh Gabani**.
|