react-dropzone 11.2.3 → 11.3.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.
@@ -0,0 +1,140 @@
1
+ If you'd like to integrate the dropzone with the [doka](https://pqina.nl/doka/?ref=react-dropzone) image editor, you just need to pass either of the selected images to the `create()` method exported by doka:
2
+
3
+ ```jsx harmony
4
+ import React, {useEffect, useState} from 'react';
5
+ import {useDropzone} from 'react-dropzone';
6
+
7
+ import {create} from 'doka';
8
+
9
+ const thumbsContainer = {
10
+ display: "flex",
11
+ flexDirection: "row",
12
+ flexWrap: "wrap",
13
+ marginTop: 16,
14
+ padding: 20
15
+ };
16
+
17
+ const thumb = {
18
+ position: "relative",
19
+ display: "inline-flex",
20
+ borderRadius: 2,
21
+ border: "1px solid #eaeaea",
22
+ marginBottom: 8,
23
+ marginRight: 8,
24
+ width: 100,
25
+ height: 100,
26
+ padding: 4,
27
+ boxSizing: "border-box"
28
+ };
29
+
30
+ const thumbInner = {
31
+ display: "flex",
32
+ minWidth: 0,
33
+ overflow: "hidden"
34
+ };
35
+
36
+ const img = {
37
+ display: "block",
38
+ width: "auto",
39
+ height: "100%"
40
+ };
41
+
42
+ const thumbButton = {
43
+ position: "absolute",
44
+ right: 10,
45
+ bottom: 10,
46
+ background: "rgba(0,0,0,.8)",
47
+ color: "#fff",
48
+ border: 0,
49
+ borderRadius: ".325em",
50
+ cursor: "pointer"
51
+ };
52
+
53
+ const editImage = (image, done) => {
54
+ const imageFile = image.doka ? image.doka.file : image;
55
+ const imageState = image.doka ? image.doka.data : {};
56
+ create({
57
+ // recreate previous state
58
+ ...imageState,
59
+
60
+ // load original image file
61
+ src: imageFile,
62
+ outputData: true,
63
+
64
+ onconfirm: ({ file, data }) => {
65
+ Object.assign(file, {
66
+ doka: { file: imageFile, data }
67
+ });
68
+ done(file);
69
+ }
70
+ });
71
+ };
72
+
73
+ function Doka(props) {
74
+ const [files, setFiles] = useState([]);
75
+ const { getRootProps, getInputProps } = useDropzone({
76
+ accept: "image/*",
77
+ onDrop: (acceptedFiles) => {
78
+ setFiles(
79
+ acceptedFiles.map((file) =>
80
+ Object.assign(file, {
81
+ preview: URL.createObjectURL(file)
82
+ })
83
+ )
84
+ );
85
+ }
86
+ });
87
+
88
+ const thumbs = files.map((file, index) => (
89
+ <div style={thumb} key={file.name}>
90
+ <div style={thumbInner}>
91
+ <img src={file.preview} style={img} alt="" />
92
+ </div>
93
+ <button
94
+ style={thumbButton}
95
+ onClick={() =>
96
+ editImage(file, (output) => {
97
+ const updatedFiles = [...files];
98
+
99
+ // replace original image with new image
100
+ updatedFiles[index] = output;
101
+
102
+ // revoke preview URL for old image
103
+ if (file.preview) URL.revokeObjectURL(file.preview);
104
+
105
+ // set new preview URL
106
+ Object.assign(output, {
107
+ preview: URL.createObjectURL(output)
108
+ });
109
+
110
+ // update view
111
+ setFiles(updatedFiles);
112
+ })
113
+ }
114
+ >
115
+ edit
116
+ </button>
117
+ </div>
118
+ ));
119
+
120
+ useEffect(
121
+ () => () => {
122
+ // Make sure to revoke the data uris to avoid memory leaks
123
+ files.forEach((file) => URL.revokeObjectURL(file.preview));
124
+ },
125
+ [files]
126
+ );
127
+
128
+ return (
129
+ <section className="container">
130
+ <div {...getRootProps({ className: "dropzone" })}>
131
+ <input {...getInputProps()} />
132
+ <p>Drag 'n' drop some files here, or click to select files</p>
133
+ </div>
134
+ <aside style={thumbsContainer}>{thumbs}</aside>
135
+ </section>
136
+ );
137
+ }
138
+
139
+ <Doka />
140
+ ```
@@ -0,0 +1,68 @@
1
+ By providing `validator` prop you can specify custom validation for files.
2
+
3
+ The value must be a function that accepts File object and returns null if file should be accepted or error object/array of error objects if file should me rejected.
4
+
5
+ ```jsx harmony
6
+ import React from 'react';
7
+ import {useDropzone} from 'react-dropzone';
8
+
9
+ const maxLength = 20;
10
+
11
+ function nameLengthValidator(file) {
12
+ if (file.name.length > maxLength) {
13
+ return {
14
+ code: "name-too-large",
15
+ message: `Name is larger than ${maxLength} characters`
16
+ };
17
+ }
18
+
19
+ return null
20
+ }
21
+
22
+ function CustomValidation(props) {
23
+ const {
24
+ acceptedFiles,
25
+ fileRejections,
26
+ getRootProps,
27
+ getInputProps
28
+ } = useDropzone({
29
+ validator: nameLengthValidator
30
+ });
31
+
32
+ const acceptedFileItems = acceptedFiles.map(file => (
33
+ <li key={file.path}>
34
+ {file.path} - {file.size} bytes
35
+ </li>
36
+ ));
37
+
38
+ const fileRejectionItems = fileRejections.map(({ file, errors }) => (
39
+ <li key={file.path}>
40
+ {file.path} - {file.size} bytes
41
+ <ul>
42
+ {errors.map(e => (
43
+ <li key={e.code}>{e.message}</li>
44
+ ))}
45
+ </ul>
46
+ </li>
47
+ ));
48
+
49
+ return (
50
+ <section className="container">
51
+ <div {...getRootProps({ className: 'dropzone' })}>
52
+ <input {...getInputProps()} />
53
+ <p>Drag 'n' drop some files here, or click to select files</p>
54
+ <em>(Only files with name less than 20 characters will be accepted)</em>
55
+ </div>
56
+ <aside>
57
+ <h4>Accepted files</h4>
58
+ <ul>{acceptedFileItems}</ul>
59
+ <h4>Rejected files</h4>
60
+ <ul>{fileRejectionItems}</ul>
61
+ </aside>
62
+ </section>
63
+ );
64
+ }
65
+
66
+ <CustomValidation />
67
+ ```
68
+
package/package.json CHANGED
@@ -166,8 +166,11 @@
166
166
  "path": "@commitlint/prompt"
167
167
  }
168
168
  },
169
- "version": "11.2.3",
169
+ "version": "11.3.2",
170
170
  "engines": {
171
171
  "node": ">= 10"
172
- }
172
+ },
173
+ "browserslist": [
174
+ "defaults"
175
+ ]
173
176
  }
package/src/index.js CHANGED
@@ -60,7 +60,8 @@ const defaultProps = {
60
60
  noClick: false,
61
61
  noKeyboard: false,
62
62
  noDrag: false,
63
- noDragEventsBubbling: false
63
+ noDragEventsBubbling: false,
64
+ validator: null
64
65
  }
65
66
 
66
67
  Dropzone.defaultProps = defaultProps
@@ -226,7 +227,14 @@ Dropzone.propTypes = {
226
227
  * @param {FileRejection[]} fileRejections
227
228
  * @param {(DragEvent|Event)} event
228
229
  */
229
- onDropRejected: PropTypes.func
230
+ onDropRejected: PropTypes.func,
231
+
232
+ /**
233
+ * Custom validation function
234
+ * @param {File} file
235
+ * @returns {FileError|FileError[]}
236
+ */
237
+ validator: PropTypes.func
230
238
  }
231
239
 
232
240
  export default Dropzone
@@ -398,7 +406,8 @@ export function useDropzone(options = {}) {
398
406
  noClick,
399
407
  noKeyboard,
400
408
  noDrag,
401
- noDragEventsBubbling
409
+ noDragEventsBubbling,
410
+ validator
402
411
  } = {
403
412
  ...defaultProps,
404
413
  ...options
@@ -545,13 +554,14 @@ export function useDropzone(options = {}) {
545
554
  event.persist()
546
555
  stopPropagation(event)
547
556
 
548
- if (event.dataTransfer) {
557
+ const hasFiles = isEvtWithFiles(event);
558
+ if (hasFiles && event.dataTransfer) {
549
559
  try {
550
560
  event.dataTransfer.dropEffect = 'copy'
551
561
  } catch {} /* eslint-disable-line no-empty */
552
562
  }
553
563
 
554
- if (isEvtWithFiles(event) && onDragOver) {
564
+ if (hasFiles && onDragOver) {
555
565
  onDragOver(event)
556
566
  }
557
567
 
@@ -615,11 +625,18 @@ export function useDropzone(options = {}) {
615
625
  files.forEach(file => {
616
626
  const [accepted, acceptError] = fileAccepted(file, accept)
617
627
  const [sizeMatch, sizeError] = fileMatchSize(file, minSize, maxSize)
618
- if (accepted && sizeMatch) {
628
+ const customErrors = validator ? validator(file) : null;
629
+
630
+ if (accepted && sizeMatch && !customErrors) {
619
631
  acceptedFiles.push(file)
620
632
  } else {
621
- const errors = [acceptError, sizeError].filter(e => e)
622
- fileRejections.push({ file, errors })
633
+ let errors = [acceptError, sizeError];
634
+
635
+ if (customErrors) {
636
+ errors = errors.concat(customErrors);
637
+ }
638
+
639
+ fileRejections.push({ file, errors: errors.filter(e => e) })
623
640
  }
624
641
  })
625
642
 
package/src/index.spec.js CHANGED
@@ -2719,6 +2719,47 @@ describe('useDropzone() hook', () => {
2719
2719
  expect(fn).not.toThrow()
2720
2720
  })
2721
2721
  })
2722
+
2723
+ describe('validator', () => {
2724
+ it('rejects with custom error', async () => {
2725
+ const validator = file => {
2726
+ if (/dogs/i.test(file.name))
2727
+ return { code: 'dogs-not-allowed', message: 'Dogs not allowed' };
2728
+
2729
+ return null;
2730
+ }
2731
+
2732
+ const onDropSpy = jest.fn()
2733
+
2734
+ const ui = (
2735
+ <Dropzone validator={validator} onDrop={onDropSpy} multiple={true}>
2736
+ {({ getRootProps, getInputProps }) => (
2737
+ <div {...getRootProps()}>
2738
+ <input {...getInputProps()} />
2739
+ </div>
2740
+ )}
2741
+ </Dropzone>
2742
+ )
2743
+
2744
+ const { container, rerender } = render(ui)
2745
+ const dropzone = container.querySelector('div')
2746
+
2747
+ fireDrop(dropzone, createDtWithFiles(images))
2748
+ await flushPromises(rerender, ui)
2749
+
2750
+ expect(onDropSpy).toHaveBeenCalledWith([images[0]], [
2751
+ {
2752
+ file: images[1],
2753
+ errors: [
2754
+ {
2755
+ code: 'dogs-not-allowed',
2756
+ message: 'Dogs not allowed',
2757
+ }
2758
+ ]
2759
+ }
2760
+ ], expect.anything())
2761
+ })
2762
+ })
2722
2763
  })
2723
2764
 
2724
2765
  async function flushPromises(rerender, ui) {
@@ -6,18 +6,25 @@ const { createConfig, babel, css, devServer } = require('webpack-blocks')
6
6
  module.exports = {
7
7
  title: 'react-dropzone',
8
8
  styleguideDir: path.join(__dirname, 'styleguide'),
9
+ template: {
10
+ favicon: 'https://github.com/react-dropzone/react-dropzone/raw/master/logo/logo.png'
11
+ },
9
12
  webpackConfig: createConfig([babel(), css(), devServer({
10
13
  disableHostCheck: true,
11
14
  host: '0.0.0.0',
12
15
  })]),
13
16
  exampleMode: 'expand',
14
17
  usageMode: 'expand',
15
- showSidebar: false,
18
+ showSidebar: true,
16
19
  serverPort: 8080,
17
20
  moduleAliases: {
18
- 'react-dropzone': path.resolve(__dirname, './src')
21
+ 'react-dropzone': path.resolve(__dirname, './src'),
22
+ 'doka': path.resolve(__dirname, './vendor/doka/doka.esm.min.js')
19
23
  },
20
- require: [path.join(__dirname, 'examples/theme.css')],
24
+ require: [
25
+ path.join(__dirname, 'examples/theme.css'),
26
+ path.join(__dirname, 'vendor/doka/doka.min.css'),
27
+ ],
21
28
  sections: [
22
29
  {
23
30
  name: '',
@@ -52,6 +59,10 @@ module.exports = {
52
59
  name: 'Accepting specific number of files',
53
60
  content: 'examples/maxFiles/README.md'
54
61
  },
62
+ {
63
+ name: 'Custom validation',
64
+ content: 'examples/validator/README.md'
65
+ },
55
66
  {
56
67
  name: 'Opening File Dialog Programmatically',
57
68
  content: 'examples/file-dialog/README.md'
@@ -69,6 +80,15 @@ module.exports = {
69
80
  content: 'examples/plugins/README.md'
70
81
  }
71
82
  ]
83
+ },
84
+ {
85
+ name: 'Integrations',
86
+ sections: [
87
+ {
88
+ name: 'Doka',
89
+ content: 'examples/doka/README.md'
90
+ }
91
+ ]
72
92
  }
73
93
  ]
74
94
  }
@@ -10,7 +10,7 @@ export interface DropzoneProps extends DropzoneOptions {
10
10
 
11
11
  export interface FileError {
12
12
  message: string;
13
- code: "file-too-large" | "file-too-small"|"too-many-files"|"file-invalid-type";
13
+ code: "file-too-large" | "file-too-small" | "too-many-files" | "file-invalid-type" | string;
14
14
  }
15
15
 
16
16
  export interface FileRejection {
@@ -34,6 +34,7 @@ export type DropzoneOptions = Pick<React.HTMLProps<HTMLElement>, PropTypes> & {
34
34
  onDropRejected?: (fileRejections: FileRejection[], event: DropEvent) => void;
35
35
  getFilesFromEvent?: (event: DropEvent) => Promise<Array<File | DataTransferItem>>;
36
36
  onFileDialogCancel?: () => void;
37
+ validator?: <T extends File>(file: T) => FileError | FileError[] | null;
37
38
  };
38
39
 
39
40
  export type DropEvent = React.DragEvent<HTMLElement> | React.ChangeEvent<HTMLInputElement> | DragEvent | Event;