@leancodepl/image-uploader 9.6.2

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,508 @@
1
+ # @leancodepl/image-uploader
2
+
3
+ React component for image uploads.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @leancodepl/image-uploader
9
+ ```
10
+
11
+ ```bash
12
+ yarn add @leancodepl/image-uploader
13
+ ```
14
+
15
+ ## API
16
+
17
+ ### `useUploadImages(value, accept, cropper, onError, onChange)`
18
+
19
+ Manages image upload state and provides drag-and-drop functionality. Creates a complete image upload solution with file
20
+ validation, drag-and-drop support, optional cropping, and duplicate detection. Returns upload state and control
21
+ functions.
22
+
23
+ **Parameters:**
24
+
25
+ - `value?: FileWithId[]` - Current array of uploaded files with IDs
26
+ - `accept?: Accept` - File types to accept (defaults to image types)
27
+ - `cropper?: CropperConfig` - Optional cropper configuration for image editing
28
+ - `onError?: (errorCode: ErrorCode) => void` - Callback for handling upload errors
29
+ - `onChange?: (files: FileWithId[]) => void` - Callback when file list changes
30
+
31
+ **Returns:** Upload state and control functions including dropzone props
32
+
33
+ ### `tryUploadWithUploadParams(image, getUploadParams)`
34
+
35
+ Uploads an image file using provided upload parameters. Handles file upload by calling the getUploadParams function to
36
+ retrieve upload configuration, then performs a fetch request to upload the file. Returns early if the image is already
37
+ uploaded.
38
+
39
+ **Parameters:**
40
+
41
+ - `image: FileWithId | UploadedFileWithId` - Image file with ID or already uploaded image with URL
42
+ - `getUploadParams: (image: FileWithId) => Promise<UploadParams | null | undefined>` - Function that returns upload
43
+ parameters (URI, method, headers) for the image
44
+
45
+ **Returns:** Promise resolving to uploaded image with URL
46
+
47
+ **Throws:** `Error` - When upload params are not defined, image is not defined, or upload fails
48
+
49
+ ### `UploadImages.Root`
50
+
51
+ Root wrapper component for image upload functionality. Provides upload context to all child UploadImages components and
52
+ renders a div container. Must wrap all other UploadImages components to provide shared state.
53
+
54
+ **Parameters:**
55
+
56
+ - `uploader: ReturnType<typeof useUploadImages>` - Upload state and functions from `useUploadImages` hook
57
+ - `props: HTMLAttributes<HTMLDivElement>` - Additional HTML div attributes
58
+
59
+ **Returns:** JSX element wrapping children with upload context
60
+
61
+ ### `UploadImages.Zone`
62
+
63
+ Drag-and-drop zone component for file uploads. Creates an interactive drop zone with file input capabilities. Supports
64
+ both click-to-select and drag-and-drop file uploads. Provides drag state to children for visual feedback.
65
+
66
+ **Parameters:**
67
+
68
+ - `children: ((props: UploadZoneChildProps) => ReactNode) | ReactNode` - Content or render function receiving drag state
69
+ props
70
+ - `props: HTMLAttributes<HTMLDivElement>` - Additional HTML div attributes
71
+
72
+ **Returns:** JSX element with drag-and-drop upload functionality
73
+
74
+ ### `UploadImages.Files`
75
+
76
+ Container component for displaying uploaded files. Renders a container for uploaded files and provides current file list
77
+ to children. Used to display and manage the collection of uploaded images.
78
+
79
+ **Parameters:**
80
+
81
+ - `children: ((props: UploadFilesChildProps) => ReactNode) | ReactNode` - Content or render function receiving files
82
+ array
83
+ - `props: HTMLAttributes<HTMLDivElement>` - Additional HTML div attributes
84
+
85
+ **Returns:** JSX element containing uploaded files
86
+
87
+ ### `UploadImages.File`
88
+
89
+ Individual file item component with preview and removal functionality. Displays a single uploaded file with preview
90
+ image generation and removal capability. Automatically generates image preview data and provides remove function to
91
+ children.
92
+
93
+ **Parameters:**
94
+
95
+ - `children: ((props: UploadFileItemChildProps) => ReactNode) | ReactNode` - Content or render function receiving file,
96
+ preview, and remove function
97
+ - `file: FileWithId` - File object with ID to display
98
+ - `props: HTMLAttributes<HTMLDivElement>` - Additional HTML div attributes
99
+
100
+ **Returns:** JSX element for individual file with preview and controls
101
+
102
+ ### `UploadImages.Cropper`
103
+
104
+ Image cropper component with editing controls. Provides image cropping functionality with zoom, rotation, and crop area
105
+ controls. Processes cropped images and integrates with the upload state management.
106
+
107
+ **Parameters:**
108
+
109
+ - `children: ((props: UploadImagesCropperEditorChildProps) => ReactNode) | ReactNode` - Content or render function
110
+ receiving cropper controls
111
+
112
+ **Returns:** JSX element with cropper editing interface
113
+
114
+ **Throws:** `Error` - When cropper config is not defined in context
115
+
116
+ ### `UploadImages.CropperEditor`
117
+
118
+ Visual editor component for image cropping. Renders the interactive crop editor interface using `"react-easy-crop"`.
119
+ Provides visual feedback for crop area, zoom, and rotation adjustments.
120
+
121
+ **Parameters:**
122
+
123
+ - `style?: CSSProperties` - CSS style object, merged with default positioning styles
124
+ - `props: HTMLAttributes<HTMLDivElement>` - Additional HTML div attributes
125
+
126
+ **Returns:** JSX element with interactive crop editor
127
+
128
+ **Throws:** `Error` - When cropper config is not defined in context
129
+
130
+ ## Render Prop Types
131
+
132
+ The following types define the props passed to render functions in various components:
133
+
134
+ ### `UploadZoneChildProps`
135
+
136
+ Props passed to the render function of `UploadImages.Zone` component.
137
+
138
+ **Properties:**
139
+
140
+ - `isDragActive: boolean` - True when files are being dragged over the drop zone
141
+ - `isFileDialogActive: boolean` - True when the native file dialog is open
142
+ - `isFocused: boolean` - True when the drop zone is focused (keyboard navigation)
143
+
144
+ ### `UploadFilesChildProps`
145
+
146
+ Props passed to the render function of `UploadImages.Files` component.
147
+
148
+ **Properties:**
149
+
150
+ - `files?: FileWithId[]` - Array of currently uploaded files with unique IDs
151
+
152
+ ### `UploadFileItemChildProps`
153
+
154
+ Props passed to the render function of `UploadImages.File` component.
155
+
156
+ **Properties:**
157
+
158
+ - `file: FileWithId` - The file object containing the original File and unique ID
159
+ - `preview?: string` - Base64 data URL of the image preview (undefined while loading)
160
+ - `remove: () => void` - Function to remove this file from the upload list
161
+
162
+ ### `UploadImagesCropperEditorChildProps`
163
+
164
+ Props passed to the render function of `UploadImages.Cropper` component.
165
+
166
+ **Properties:**
167
+
168
+ - `zoom: number` - Current zoom level (1.0 = no zoom)
169
+ - `rotation: number` - Current rotation angle in degrees
170
+ - `setZoom: (zoom: number) => void` - Function to update zoom level
171
+ - `setRotation: (rotation: number) => void` - Function to update rotation angle
172
+ - `accept: () => void` - Function to accept the current crop and add to files
173
+ - `close: () => void` - Function to cancel cropping and close the editor
174
+
175
+ ## Usage Examples
176
+
177
+ ### Basic Upload
178
+
179
+ ```typescript
180
+ import { useState } from "react";
181
+ import { UploadImages, useUploadImages, FileWithId } from "@leancodepl/image-uploader";
182
+
183
+ function BasicUpload() {
184
+ const [files, setFiles] = useState<FileWithId[]>([]);
185
+
186
+ const uploader = useUploadImages({
187
+ value: files,
188
+ onChange: setFiles,
189
+ onError: (error) => console.error("Upload error:", error),
190
+ accept: { "image/*": [".jpg", ".png", ".gif"] }
191
+ });
192
+
193
+ return (
194
+ <UploadImages.Root uploader={uploader}>
195
+ <UploadImages.Zone>
196
+ {({ isDragActive }) => (
197
+ <div>
198
+ {isDragActive ? "Drop files here" : "Click or drag files to upload"}
199
+ </div>
200
+ )}
201
+ </UploadImages.Zone>
202
+
203
+ <UploadImages.Files>
204
+ {({ files }) => (
205
+ <div>
206
+ {files?.map(file => (
207
+ <UploadImages.File key={file.id} file={file}>
208
+ {({ preview, remove }) => (
209
+ <div>
210
+ <img src={preview} alt="Preview" />
211
+ <p>{file.originalFile.name}</p>
212
+ <button onClick={remove}>Remove</button>
213
+ </div>
214
+ )}
215
+ </UploadImages.File>
216
+ ))}
217
+ </div>
218
+ )}
219
+ </UploadImages.Files>
220
+ </UploadImages.Root>
221
+ );
222
+ }
223
+ ```
224
+
225
+ ### Server Upload Integration
226
+
227
+ ```typescript
228
+ import { tryUploadWithUploadParams } from "@leancodepl/image-uploader"
229
+
230
+ async function uploadToServer(image: FileWithId) {
231
+ try {
232
+ const uploadedImage = await tryUploadWithUploadParams(image, async img => {
233
+ // Get upload URL from your API
234
+ const response = await fetch("/api/upload-url", {
235
+ method: "POST",
236
+ headers: { "Content-Type": "application/json" },
237
+ body: JSON.stringify({
238
+ fileName: img.originalFile.name,
239
+ fileType: img.originalFile.type,
240
+ }),
241
+ })
242
+
243
+ const { uploadUrl, headers } = await response.json()
244
+
245
+ return {
246
+ uri: uploadUrl,
247
+ method: "PUT",
248
+ requiredHeaders: headers,
249
+ }
250
+ })
251
+
252
+ console.log("Upload successful:", uploadedImage.url)
253
+ return uploadedImage
254
+ } catch (error) {
255
+ console.error("Upload failed:", error)
256
+ throw error
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### Error Handling
262
+
263
+ ```typescript
264
+ import { ErrorCode } from "@leancodepl/image-uploader"
265
+
266
+ function handleUploadError(errorCode: ErrorCode) {
267
+ switch (errorCode) {
268
+ case ErrorCode.FileTooLarge:
269
+ alert("File is too large. Please choose a smaller file.")
270
+ break
271
+ case ErrorCode.FileInvalidType:
272
+ alert("Invalid file type. Please upload an image file.")
273
+ break
274
+ case ErrorCode.TooManyFiles:
275
+ alert("Too many files selected. Please choose fewer files.")
276
+ break
277
+ default:
278
+ alert("An error occurred while uploading the file.")
279
+ }
280
+ }
281
+
282
+ const uploader = useUploadImages({
283
+ value: files,
284
+ onChange: setFiles,
285
+ onError: handleUploadError,
286
+ })
287
+ ```
288
+
289
+ ## Features
290
+
291
+ - **Drag-and-Drop Support**: Built on `"react-dropzone"` for intuitive file selection
292
+ - **Image Cropping**: Optional cropping with zoom and rotation using `"react-easy-crop"`
293
+ - **File Validation**: Automatic validation with customizable error handling
294
+ - **Preview Generation**: Automatic image preview generation for uploaded files
295
+ - **Duplicate Detection**: Prevents uploading the same file multiple times
296
+ - **TypeScript Support**: Full TypeScript definitions included
297
+ - **Flexible UI**: Render prop pattern for complete UI customization
298
+ - **Server Integration**: Helper utilities for uploading to any backend service
299
+
300
+ ## Configuration
301
+
302
+ ### Cropper Configuration
303
+
304
+ ```typescript
305
+ type CropperConfig = {
306
+ aspect: number // Aspect ratio (e.g., 16/9, 1, 4/3)
307
+ maxWidth?: number // Maximum output width in pixels
308
+ maxHeight?: number // Maximum output height in pixels
309
+ withRotation?: boolean // Enable rotation controls
310
+ withZoom?: boolean // Enable zoom controls
311
+ }
312
+ ```
313
+
314
+ ### File Accept Types
315
+
316
+ ```typescript
317
+ // Accept only JPEG and PNG
318
+ const accept = {
319
+ "image/jpeg": [".jpg", ".jpeg"],
320
+ "image/png": [".png"],
321
+ }
322
+
323
+ // Accept all image types (default)
324
+ const accept = { "image/*": [] }
325
+ ```
326
+
327
+ ### Upload with Cropping
328
+
329
+ ```typescript
330
+ import { useState } from "react";
331
+ import { UploadImages, useUploadImages, FileWithId } from "@leancodepl/image-uploader";
332
+
333
+ function CroppableUpload() {
334
+ const [files, setFiles] = useState<FileWithId[]>([]);
335
+
336
+ const uploader = useUploadImages({
337
+ value: files,
338
+ onChange: setFiles,
339
+ cropper: {
340
+ aspect: 16 / 9,
341
+ maxWidth: 800,
342
+ maxHeight: 450,
343
+ withRotation: true,
344
+ withZoom: true
345
+ }
346
+ });
347
+
348
+ return (
349
+ <UploadImages.Root uploader={uploader}>
350
+ <UploadImages.Zone>
351
+ Drop images to crop
352
+ </UploadImages.Zone>
353
+
354
+ <UploadImages.Cropper>
355
+ {({ zoom, rotation, setZoom, setRotation, accept, close }) => (
356
+ <div>
357
+ <UploadImages.CropperEditor />
358
+ <div>
359
+ <label>
360
+ Zoom: <input
361
+ type="range"
362
+ min={1}
363
+ max={3}
364
+ step={0.1}
365
+ value={zoom}
366
+ onChange={(e) => setZoom(Number(e.target.value))}
367
+ />
368
+ </label>
369
+ <label>
370
+ Rotation: <input
371
+ type="range"
372
+ min={-180}
373
+ max={180}
374
+ value={rotation}
375
+ onChange={(e) => setRotation(Number(e.target.value))}
376
+ />
377
+ </label>
378
+ <div>
379
+ <button onClick={accept}>Accept</button>
380
+ <button onClick={close}>Cancel</button>
381
+ </div>
382
+ </div>
383
+ </div>
384
+ )}
385
+ </UploadImages.Cropper>
386
+
387
+ <UploadImages.Files>
388
+ {({ files }) => (
389
+ <div>
390
+ {files?.map(file => (
391
+ <UploadImages.File key={file.id} file={file}>
392
+ {({ preview, remove }) => (
393
+ <div>
394
+ <img src={preview} alt="Cropped" />
395
+ <button onClick={remove}>Remove</button>
396
+ </div>
397
+ )}
398
+ </UploadImages.File>
399
+ ))}
400
+ </div>
401
+ )}
402
+ </UploadImages.Files>
403
+ </UploadImages.Root>
404
+ );
405
+ }
406
+ ```
407
+
408
+ ### Server Upload Integration
409
+
410
+ ```typescript
411
+ import { tryUploadWithUploadParams } from "@leancodepl/image-uploader"
412
+
413
+ async function uploadToServer(image: FileWithId) {
414
+ try {
415
+ const uploadedImage = await tryUploadWithUploadParams(image, async img => {
416
+ // Get upload URL from your API
417
+ const response = await fetch("/api/upload-url", {
418
+ method: "POST",
419
+ headers: { "Content-Type": "application/json" },
420
+ body: JSON.stringify({
421
+ fileName: img.originalFile.name,
422
+ fileType: img.originalFile.type,
423
+ }),
424
+ })
425
+
426
+ const { uploadUrl, headers } = await response.json()
427
+
428
+ return {
429
+ uri: uploadUrl,
430
+ method: "PUT",
431
+ requiredHeaders: headers,
432
+ }
433
+ })
434
+
435
+ console.log("Upload successful:", uploadedImage.url)
436
+ return uploadedImage
437
+ } catch (error) {
438
+ console.error("Upload failed:", error)
439
+ throw error
440
+ }
441
+ }
442
+ ```
443
+
444
+ ### Error Handling
445
+
446
+ ```typescript
447
+ import { ErrorCode } from "@leancodepl/image-uploader"
448
+
449
+ function handleUploadError(errorCode: ErrorCode) {
450
+ switch (errorCode) {
451
+ case ErrorCode.FileTooLarge:
452
+ alert("File is too large. Please choose a smaller file.")
453
+ break
454
+ case ErrorCode.FileInvalidType:
455
+ alert("Invalid file type. Please upload an image file.")
456
+ break
457
+ case ErrorCode.TooManyFiles:
458
+ alert("Too many files selected. Please choose fewer files.")
459
+ break
460
+ default:
461
+ alert("An error occurred while uploading the file.")
462
+ }
463
+ }
464
+
465
+ const uploader = useUploadImages({
466
+ value: files,
467
+ onChange: setFiles,
468
+ onError: handleUploadError,
469
+ })
470
+ ```
471
+
472
+ ## Features
473
+
474
+ - **Drag-and-Drop Support**: Built on `"react-dropzone"` for intuitive file selection
475
+ - **Image Cropping**: Optional cropping with zoom and rotation using `"react-easy-crop"`
476
+ - **File Validation**: Automatic validation with customizable error handling
477
+ - **Preview Generation**: Automatic image preview generation for uploaded files
478
+ - **Duplicate Detection**: Prevents uploading the same file multiple times
479
+ - **TypeScript Support**: Full TypeScript definitions included
480
+ - **Flexible UI**: Render prop pattern for complete UI customization
481
+ - **Server Integration**: Helper utilities for uploading to any backend service
482
+
483
+ ## Configuration
484
+
485
+ ### Cropper Configuration
486
+
487
+ ```typescript
488
+ type CropperConfig = {
489
+ aspect: number // Aspect ratio (e.g., 16/9, 1, 4/3)
490
+ maxWidth?: number // Maximum output width in pixels
491
+ maxHeight?: number // Maximum output height in pixels
492
+ withRotation?: boolean // Enable rotation controls
493
+ withZoom?: boolean // Enable zoom controls
494
+ }
495
+ ```
496
+
497
+ ### File Accept Types
498
+
499
+ ```typescript
500
+ // Accept only JPEG and PNG
501
+ const accept = {
502
+ "image/jpeg": [".jpg", ".jpeg"],
503
+ "image/png": [".png"],
504
+ }
505
+
506
+ // Accept all image types (default)
507
+ const accept = { "image/*": [] }
508
+ ```
package/index.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/index";