1
+ import accepts from 'attr-accept' ;
2
+ import React , { Component , PropTypes } from 'react' ;
3
+
4
+ export default class DropZone extends Component {
5
+ static propTypes = {
6
+ // Overriding drop behavior
7
+ onDrop : PropTypes . func ,
8
+ onDropAccepted : PropTypes . func ,
9
+ onDropRejected : PropTypes . func ,
10
+
11
+ // Overriding drag behavior
12
+ onDragStart : PropTypes . func ,
13
+ onDragEnter : PropTypes . func ,
14
+ onDragLeave : PropTypes . func ,
15
+
16
+ children : PropTypes . node , // Contents of the dropzone
17
+ style : PropTypes . object , // CSS styles to apply
18
+ activeStyle : PropTypes . object , // CSS styles to apply when drop will be accepted
19
+ rejectStyle : PropTypes . object , // CSS styles to apply when drop will be rejected
20
+ className : PropTypes . string , // Optional className
21
+ activeClassName : PropTypes . string , // className for accepted state
22
+ rejectClassName : PropTypes . string , // className for rejected state
23
+
24
+ disablePreview : PropTypes . bool , // Enable/disable preview generation
25
+ disableClick : PropTypes . bool , // Disallow clicking on the dropzone container to open file dialog
26
+
27
+ inputProps : PropTypes . object , // Pass additional attributes to the <input type="file"/> tag
28
+ multiple : PropTypes . bool , // Allow dropping multiple files
29
+ accept : PropTypes . string , // Allow specific types of files. See https://github.com/okonet/attr-accept for more information
30
+ name : PropTypes . string // name attribute for the input tag
31
+ } ;
32
+
33
+
34
+ static defaultProps = {
35
+ disablePreview : false ,
36
+ disableClick : false ,
37
+ multiple : true
38
+ } ;
39
+
40
+ state = {
41
+ isDragActive : false
42
+ } ;
43
+
44
+ componentDidMount ( ) {
45
+ this . enterCounter = 0 ;
46
+ }
47
+
48
+ onDragStart = ( e ) => {
49
+ if ( this . props . onDragStart ) {
50
+ this . props . onDragStart . call ( this , e ) ;
51
+ }
52
+ } ;
53
+
54
+ onDragEnter = ( e ) => {
55
+ const { onDragEnter} = this . props ;
56
+
57
+ e . preventDefault ( ) ;
58
+
59
+ // Count the dropzone and any children that are entered.
60
+ ++ this . enterCounter ;
61
+
62
+ // This is tricky. During the drag even the dataTransfer.files is null
63
+ // But Chrome implements some drag store, which is accesible via dataTransfer.items
64
+ const dataTransferItems = e . dataTransfer && e . dataTransfer . items ? e . dataTransfer . items : [ ] ;
65
+
66
+ // Now we need to convert the DataTransferList to Array
67
+ const allFilesAccepted = this . allFilesAccepted ( Array . prototype . slice . call ( dataTransferItems ) ) ;
68
+
69
+ this . setState ( {
70
+ isDragActive : allFilesAccepted ,
71
+ isDragReject : ! allFilesAccepted
72
+ } ) ;
73
+
74
+ if ( onDragEnter ) {
75
+ onDragEnter . call ( this , e ) ;
76
+ }
77
+ } ;
78
+
79
+ onDragOver = ( e ) => {
80
+ e . preventDefault ( ) ;
81
+ e . stopPropagation ( ) ;
82
+ return false ;
83
+ } ;
84
+
85
+ onDragLeave = ( e ) => {
86
+ const { onDragLeave} = this . props ;
87
+
88
+ e . preventDefault ( ) ;
89
+
90
+ // Only deactivate once the dropzone and all children was left.
91
+ if ( -- this . enterCounter > 0 ) {
92
+ return ;
93
+ }
94
+
95
+ this . setState ( {
96
+ isDragActive : false ,
97
+ isDragReject : false
98
+ } ) ;
99
+
100
+ if ( onDragLeave ) {
101
+ onDragLeave . call ( this , e ) ;
102
+ }
103
+ } ;
104
+
105
+ onDrop = ( e ) => {
106
+ const { onDrop, onDropAccepted, onDropRejected} = this . props ;
107
+
108
+ e . preventDefault ( ) ;
109
+
110
+ // Reset the counter along with the drag on a drop.
111
+ this . enterCounter = 0 ;
112
+
113
+ this . setState ( {
114
+ isDragActive : false ,
115
+ isDragReject : false
116
+ } ) ;
117
+
118
+ const droppedFiles = e . dataTransfer ? e . dataTransfer . files : e . target . files ;
119
+ const max = this . props . multiple ? droppedFiles . length : Math . min ( droppedFiles . length , 1 ) ;
120
+ const files = [ ] ;
121
+
122
+ for ( let i = 0 ; i < max ; i ++ ) {
123
+ const file = droppedFiles [ i ] ;
124
+ // We might want to disable the preview creation to support big files
125
+ if ( ! this . props . disablePreview ) {
126
+ file . preview = window . URL . createObjectURL ( file ) ;
127
+ }
128
+
129
+ files . push ( file ) ;
130
+ }
131
+
132
+ if ( this . allFilesAccepted ( files ) ) {
133
+ if ( onDrop ) {
134
+ onDrop . call ( this , files , e ) ;
135
+ }
136
+
137
+ if ( onDropAccepted ) {
138
+ onDropAccepted . call ( this , files , e ) ;
139
+ }
140
+ } else {
141
+ if ( onDropRejected ) {
142
+ onDropRejected . call ( this , files , e ) ;
143
+ }
144
+ }
145
+ } ;
146
+
147
+ onClick = ( ) => {
148
+ if ( ! this . props . disableClick ) {
149
+ this . open ( ) ;
150
+ }
151
+ } ;
152
+
153
+ allFilesAccepted ( files ) {
154
+ return files . every ( file => accepts ( file , this . props . accept ) ) ;
155
+ }
156
+
157
+ open ( ) {
158
+ this . fileInputEl . value = null ;
159
+ this . fileInputEl . click ( ) ;
160
+ }
161
+
162
+ render ( ) {
163
+ const {
164
+ accept,
165
+ activeClassName,
166
+ inputProps,
167
+ multiple,
168
+ name,
169
+ rejectClassName,
170
+ ...rest
171
+ } = this . props ;
172
+
173
+ let {
174
+ activeStyle,
175
+ className,
176
+ rejectStyle,
177
+ style,
178
+ ...props // eslint-disable-line prefer-const
179
+ } = rest ;
180
+
181
+ const { isDragActive, isDragReject} = this . state ;
182
+
183
+ className = className || '' ;
184
+
185
+ if ( isDragActive && activeClassName ) {
186
+ className += ' ' + activeClassName ;
187
+ }
188
+ if ( isDragReject && rejectClassName ) {
189
+ className += ' ' + rejectClassName ;
190
+ }
191
+
192
+ if ( ! className && ! style && ! activeStyle && ! rejectStyle ) {
193
+ style = {
194
+ backgroundColor : "#fafafa" ,
195
+ width : 'auto' ,
196
+ borderWidth : 1 ,
197
+ borderColor : '#757575' ,
198
+ borderStyle : 'dashed' ,
199
+ borderRadius : 2
200
+ } ;
201
+ activeStyle = {
202
+ borderStyle : 'solid' ,
203
+ backgroundColor : '#fafafa'
204
+ } ;
205
+ rejectStyle = {
206
+ borderStyle : 'solid' ,
207
+ backgroundColor : '#fafafa'
208
+ } ;
209
+ }
210
+
211
+ let appliedStyle ;
212
+ if ( activeStyle && isDragActive ) {
213
+ appliedStyle = {
214
+ ...style ,
215
+ ...activeStyle
216
+ } ;
217
+ } else if ( rejectStyle && isDragReject ) {
218
+ appliedStyle = {
219
+ ...style ,
220
+ ...rejectStyle
221
+ } ;
222
+ } else {
223
+ appliedStyle = {
224
+ ...style
225
+ } ;
226
+ }
227
+
228
+ const inputAttributes = {
229
+ accept,
230
+ type : 'file' ,
231
+ style : { display : 'none' } ,
232
+ multiple : multiple ,
233
+ ref : el => this . fileInputEl = el , // eslint-disable-line
234
+ onChange : this . onDrop
235
+ } ;
236
+
237
+ if ( name && name . length ) {
238
+ inputAttributes . name = name ;
239
+ }
240
+
241
+ // Remove custom properties before passing them to the wrapper div element
242
+ const customProps = [ 'disablePreview' , 'disableClick' , 'onDropAccepted' , 'onDropRejected' ] ;
243
+ const divProps = { ...props } ;
244
+ customProps . forEach ( prop => delete divProps [ prop ] ) ;
245
+
246
+ return (
247
+ < div
248
+ className = { className }
249
+ style = { appliedStyle }
250
+ { ...divProps /* expand user provided props first so event handlers are never overridden */ }
251
+ onClick = { this . onClick }
252
+ onDragStart = { this . onDragStart }
253
+ onDragEnter = { this . onDragEnter }
254
+ onDragOver = { this . onDragOver }
255
+ onDragLeave = { this . onDragLeave }
256
+ onDrop = { this . onDrop }
257
+ >
258
+ { this . props . children }
259
+ < input
260
+ { ...inputProps /* expand user provided inputProps first so inputAttributes override them */ }
261
+ { ...inputAttributes }
262
+ multiple />
263
+ </ div >
264
+ ) ;
265
+ }
266
+ }
0 commit comments