@react-magma/dropzone 0.1.4 → 1.0.0-next.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.
@@ -33,6 +33,7 @@ import {
33
33
  import { CloudUploadIcon } from 'react-magma-icons';
34
34
  import { Preview } from './Preview';
35
35
  import { FilePreview, FileError } from './FilePreview';
36
+ import { transparentize } from 'polished';
36
37
 
37
38
  export interface OnSendFileProps {
38
39
  file: FilePreview;
@@ -125,11 +126,11 @@ export interface DropzoneProps
125
126
 
126
127
  const Container = styled(Flex)<
127
128
  DropzoneRootProps &
128
- FlexProps & {
129
- dragState?: DragState;
130
- noDrag?: boolean;
131
- isInverse?: boolean;
132
- }
129
+ FlexProps & {
130
+ dragState?: DragState;
131
+ noDrag?: boolean;
132
+ isInverse?: boolean;
133
+ }
133
134
  >`
134
135
  flex-direction: column;
135
136
  align-items: ${({ noDrag }) => (noDrag ? 'left' : 'center')};
@@ -142,13 +143,13 @@ const Container = styled(Flex)<
142
143
  ? `0px`
143
144
  : dragState === 'dragReject' || dragState === 'error'
144
145
  ? isInverse
145
- ? `2px dashed ${theme.colors.dangerInverse}`
146
- : `2px dashed ${theme.colors.danger}`
146
+ ? `1px dashed ${theme.colors.danger200}`
147
+ : `1px dashed ${theme.colors.danger}`
147
148
  : dragState === 'dragActive'
148
- ? `2px dashed ${theme.colors.primary}`
149
+ ? `1px dashed ${theme.colors.primary}`
149
150
  : dragState === 'dragAccept'
150
- ? `2px dashed ${theme.colors.success}`
151
- : `2px dashed ${theme.colors.neutral05}`};
151
+ ? `1px dashed ${theme.colors.success}`
152
+ : `1px dashed ${theme.colors.neutral400}`};
152
153
 
153
154
  border-style: ${({ dragState = 'default' }) =>
154
155
  dragState === 'error' ? 'solid' : 'dashed'};
@@ -156,15 +157,15 @@ const Container = styled(Flex)<
156
157
  noDrag
157
158
  ? 'transparent'
158
159
  : isInverse
159
- ? theme.colors.foundation
160
- : theme.colors.neutral07};
160
+ ? transparentize(0.75, theme.colors.neutral900)
161
+ : theme.colors.neutral200};
161
162
  outline: none;
162
163
  transition: ${({ noDrag }) => `border ${noDrag ? 0 : '.24s'} ease-in-out`};
163
164
  `;
164
165
 
165
166
  const HelperMessage = styled.span<{ isInverse?: boolean }>`
166
167
  color: ${({ theme, isInverse }) =>
167
- isInverse ? theme.colors.neutral08 : theme.colors.neutral03};
168
+ isInverse ? theme.colors.neutral100 : theme.colors.neutral700};
168
169
  display: block;
169
170
  font-size: 14px;
170
171
  margin: -8px 0 16px 0;
@@ -172,287 +173,301 @@ const HelperMessage = styled.span<{ isInverse?: boolean }>`
172
173
 
173
174
  const Wrapper = styled.div<{ isInverse?: boolean }>`
174
175
  color: ${({ theme, isInverse }) =>
175
- isInverse ? theme.colors.neutral07 : theme.colors.neutral02};
176
+ isInverse ? theme.colors.neutral100 : theme.colors.neutral700};
176
177
  margin: 0 0 24px 0;
177
178
  font-size: ${({ theme }) => theme.typeScale.size02.fontSize};
178
179
  line-height: ${({ theme }) => theme.typeScale.size02.lineHeight};
179
- font-weight: 600;
180
+ font-weight: 500;
180
181
  padding: ${({ theme }) => theme.spaceScale.spacing01};
181
182
  `;
182
- export const Dropzone = React.forwardRef<
183
- HTMLInputElement,
184
- DropzoneProps
185
- >((props, ref) => {
186
- const {
187
- accept,
188
- containerStyle,
189
- disabled,
190
- dropzoneOptions = {
191
- multiple: true,
192
- },
193
- helperMessage,
194
- id: defaultId,
195
- inputSize,
196
- isInverse: isInverseProp,
197
- isLabelVisuallyHidden,
198
- labelStyle,
199
- labelText,
200
- maxFiles,
201
- minFiles,
202
- maxSize,
203
- minSize,
204
- multiple = true,
205
- noDrag = false,
206
- onSendFile,
207
- onDeleteFile,
208
- onRemoveFile,
209
- sendFiles = false,
210
- testId,
211
- thumbnails = true,
212
- ...rest
213
- } = props;
183
+ export const Dropzone = React.forwardRef<HTMLInputElement, DropzoneProps>(
184
+ (props, ref) => {
185
+ const {
186
+ accept,
187
+ containerStyle,
188
+ disabled,
189
+ dropzoneOptions = {
190
+ multiple: true,
191
+ },
192
+ helperMessage,
193
+ id: defaultId,
194
+ inputSize,
195
+ isInverse: isInverseProp,
196
+ isLabelVisuallyHidden,
197
+ labelStyle,
198
+ labelText,
199
+ maxFiles,
200
+ minFiles,
201
+ maxSize,
202
+ minSize,
203
+ multiple = true,
204
+ noDrag = false,
205
+ onSendFile,
206
+ onDeleteFile,
207
+ onRemoveFile,
208
+ sendFiles = false,
209
+ testId,
210
+ thumbnails = true,
211
+ ...rest
212
+ } = props;
214
213
 
215
- const [files, setFiles] = React.useState<FilePreview[]>([]);
216
- const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
214
+ const [files, setFiles] = React.useState<FilePreview[]>([]);
215
+ const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
217
216
 
218
- const isInverse = useIsInverse(isInverseProp);
219
- const theme: ThemeInterface = React.useContext(ThemeContext);
220
- const i18n: I18nInterface = React.useContext(I18nContext);
221
- const id = useGenerateId(defaultId);
217
+ const isInverse = useIsInverse(isInverseProp);
218
+ const theme: ThemeInterface = React.useContext(ThemeContext);
219
+ const i18n: I18nInterface = React.useContext(I18nContext);
220
+ const id = useGenerateId(defaultId);
222
221
 
223
- const onDrop = React.useCallback(
224
- (acceptedFiles: FilePreview[], rejectedFiles: FileRejection[]) => {
225
- setFiles((files: FilePreview[]) => [
226
- ...files,
227
- ...acceptedFiles.map((file: FilePreview) =>
228
- Object.assign(file, {
229
- preview: URL.createObjectURL(file),
230
- })
231
- ),
232
- ...rejectedFiles.map(
233
- ({ file, errors }: { file: FilePreview; errors: FileError[] }) =>
222
+ const onDrop = React.useCallback(
223
+ (acceptedFiles: FilePreview[], rejectedFiles: FileRejection[]) => {
224
+ setFiles((files: FilePreview[]) => [
225
+ ...files,
226
+ ...acceptedFiles.map((file: FilePreview) =>
234
227
  Object.assign(file, {
235
- errors,
228
+ preview: URL.createObjectURL(file),
236
229
  })
237
- ),
238
- ]);
239
- },
240
- []
241
- );
230
+ ),
231
+ ...rejectedFiles.map(
232
+ ({ file, errors }: { file: FilePreview; errors: FileError[] }) =>
233
+ Object.assign(file, {
234
+ errors,
235
+ })
236
+ ),
237
+ ]);
238
+ },
239
+ []
240
+ );
242
241
 
243
- const {
244
- getInputProps,
245
- getRootProps,
246
- isDragAccept,
247
- isDragActive,
248
- isDragReject,
249
- open,
250
- } = useDropzone({
251
- noClick: true,
252
- disabled,
253
- multiple,
254
- maxSize,
255
- minSize,
256
- accept,
257
- onDrop,
258
- noDrag,
259
- });
242
+ const {
243
+ getInputProps,
244
+ getRootProps,
245
+ isDragAccept,
246
+ isDragActive,
247
+ isDragReject,
248
+ open,
249
+ } = useDropzone({
250
+ noClick: true,
251
+ disabled,
252
+ multiple,
253
+ maxSize,
254
+ minSize,
255
+ accept,
256
+ onDrop,
257
+ noDrag,
258
+ });
260
259
 
261
- const dragState: DragState = errorMessage
262
- ? 'error'
263
- : isDragAccept
264
- ? 'dragAccept'
265
- : isDragReject
266
- ? 'dragReject'
267
- : isDragActive
268
- ? 'dragActive'
269
- : 'default';
260
+ const dragState: DragState = errorMessage
261
+ ? 'error'
262
+ : isDragAccept
263
+ ? 'dragAccept'
264
+ : isDragReject
265
+ ? 'dragReject'
266
+ : isDragActive
267
+ ? 'dragActive'
268
+ : 'default';
270
269
 
271
- const handleRemoveFile = (removedFile: FilePreview) => {
272
- setFiles(files => files.filter(file => file !== removedFile));
273
- onRemoveFile && typeof onRemoveFile === 'function' && onRemoveFile(removedFile);
274
- };
270
+ const handleRemoveFile = (removedFile: FilePreview) => {
271
+ setFiles(files => files.filter(file => file !== removedFile));
272
+ onRemoveFile &&
273
+ typeof onRemoveFile === 'function' &&
274
+ onRemoveFile(removedFile);
275
+ };
275
276
 
276
- const handleDeleteFile = (removedFile: FilePreview) => {
277
- setFiles(files => files.filter(file => file !== removedFile));
278
- onDeleteFile && typeof onDeleteFile === 'function' && onDeleteFile(removedFile);
279
- };
277
+ const handleDeleteFile = (removedFile: FilePreview) => {
278
+ setFiles(files => files.filter(file => file !== removedFile));
279
+ onDeleteFile &&
280
+ typeof onDeleteFile === 'function' &&
281
+ onDeleteFile(removedFile);
282
+ };
280
283
 
281
- const setProgress = (props: { percent: number; file: FilePreview }) => {
282
- setFiles(files =>
283
- files.map(file =>
284
- file === props.file
285
- ? Object.assign(file, {
286
- processor: {
287
- ...file.processor,
288
- percent: `${props.percent}%`,
289
- status: 'pending'
290
- },
291
- })
292
- : file
293
- )
294
- );
295
- };
284
+ const setProgress = (props: { percent: number; file: FilePreview }) => {
285
+ setFiles(files =>
286
+ files.map(file =>
287
+ file === props.file
288
+ ? Object.assign(file, {
289
+ processor: {
290
+ ...file.processor,
291
+ percent: `${props.percent}%`,
292
+ status: 'pending',
293
+ },
294
+ })
295
+ : file
296
+ )
297
+ );
298
+ };
296
299
 
297
- const setFinished = (props: { file: FilePreview }) => {
298
- setFiles(files =>
299
- files.map(file =>
300
- file === props.file
301
- ? Object.assign(file, {
302
- processor: { ...file.processor, percent: '', status: 'finished' },
303
- })
304
- : file
305
- )
306
- );
307
- };
300
+ const setFinished = (props: { file: FilePreview }) => {
301
+ setFiles(files =>
302
+ files.map(file =>
303
+ file === props.file
304
+ ? Object.assign(file, {
305
+ processor: {
306
+ ...file.processor,
307
+ percent: '',
308
+ status: 'finished',
309
+ },
310
+ })
311
+ : file
312
+ )
313
+ );
314
+ };
308
315
 
309
- const setError = (props: { errors: FileError[]; file: FilePreview }) => {
310
- setFiles(files =>
311
- files.map(file =>
312
- file === props.file
313
- ? Object.assign(file, {
314
- errors: props.errors,
315
- processor: { ...file.processor, status: 'error' },
316
- })
317
- : file
318
- )
319
- );
320
- };
316
+ const setError = (props: { errors: FileError[]; file: FilePreview }) => {
317
+ setFiles(files =>
318
+ files.map(file =>
319
+ file === props.file
320
+ ? Object.assign(file, {
321
+ errors: props.errors,
322
+ processor: { ...file.processor, status: 'error' },
323
+ })
324
+ : file
325
+ )
326
+ );
327
+ };
321
328
 
322
- const formatError = (
323
- code: string | null,
324
- constraints: { maxFiles?: number; minFiles?: number }
325
- ) => {
326
- if (code === null) return null;
327
- const error = i18n.dropzone.errors[code];
328
- switch (code) {
329
- case 'too-many-files':
330
- return `${error.message} ${constraints.maxFiles} ${i18n.dropzone.files}.`;
331
- case 'too-few-files':
332
- return `${error.message} ${constraints.minFiles} ${i18n.dropzone.files}.`;
333
- default:
334
- return error.message;
335
- }
336
- };
329
+ const formatError = (
330
+ code: string | null,
331
+ constraints: { maxFiles?: number; minFiles?: number }
332
+ ) => {
333
+ if (code === null) return null;
334
+ const error = i18n.dropzone.errors[code];
335
+ switch (code) {
336
+ case 'too-many-files':
337
+ return `${error.message} ${constraints.maxFiles} ${i18n.dropzone.files}.`;
338
+ case 'too-few-files':
339
+ return `${error.message} ${constraints.minFiles} ${i18n.dropzone.files}.`;
340
+ default:
341
+ return error.message;
342
+ }
343
+ };
337
344
 
338
- React.useEffect(
339
- () => () => {
340
- files.forEach(file => file.preview && URL.revokeObjectURL(file.preview));
341
- },
342
- [files]
343
- );
345
+ React.useEffect(
346
+ () => () => {
347
+ files.forEach(
348
+ file => file.preview && URL.revokeObjectURL(file.preview)
349
+ );
350
+ },
351
+ [files]
352
+ );
344
353
 
345
- React.useEffect(() => {
346
- const minFileError = minFiles && files.length < minFiles;
347
- const maxFileError = maxFiles && files.length > maxFiles;
354
+ React.useEffect(() => {
355
+ const minFileError = minFiles && files.length < minFiles;
356
+ const maxFileError = maxFiles && files.length > maxFiles;
348
357
 
349
- setErrorMessage(
350
- formatError(
351
- maxFileError
352
- ? 'too-many-files'
353
- : minFileError
354
- ? 'too-few-files'
355
- : null,
356
- { minFiles, maxFiles }
357
- )
358
- );
358
+ setErrorMessage(
359
+ formatError(
360
+ maxFileError
361
+ ? 'too-many-files'
362
+ : minFileError
363
+ ? 'too-few-files'
364
+ : null,
365
+ { minFiles, maxFiles }
366
+ )
367
+ );
359
368
 
360
- if (sendFiles && files.length > 0 && !maxFileError && !minFileError) {
361
- setFiles((files: FilePreview[]) => {
362
- return files.map((file: FilePreview) => {
363
- !file.errors && ! file.processor && onSendFile && onSendFile({
364
- file,
365
- onError: setError,
366
- onFinish: setFinished,
367
- onProgress: setProgress,
368
- })
369
- return file;
369
+ if (sendFiles && files.length > 0 && !maxFileError && !minFileError) {
370
+ setFiles((files: FilePreview[]) => {
371
+ return files.map((file: FilePreview) => {
372
+ !file.errors &&
373
+ !file.processor &&
374
+ onSendFile &&
375
+ onSendFile({
376
+ file,
377
+ onError: setError,
378
+ onFinish: setFinished,
379
+ onProgress: setProgress,
380
+ });
381
+ return file;
382
+ });
370
383
  });
371
- });
372
- }
373
- }, [sendFiles, files.length, onSendFile]);
384
+ }
385
+ }, [sendFiles, files.length, onSendFile]);
374
386
 
375
- return (
376
- <InverseContext.Provider value={{ isInverse }}>
377
- <FormFieldContainer
378
- actionable={false}
379
- containerStyle={containerStyle}
380
- errorMessage={errorMessage}
381
- fieldId={id}
382
- inputSize={inputSize}
383
- isInverse={isInverse}
384
- isLabelVisuallyHidden={isLabelVisuallyHidden}
385
- labelStyle={labelStyle}
386
- labelText={labelText}
387
- messageStyle={{ minHeight: 0 }}
388
- data-testid={testId}
389
- >
390
- <HelperMessage theme={theme} isInverse={isInverse}>
391
- {helperMessage}
392
- </HelperMessage>
393
- <Container
394
- behavior={FlexBehavior.container}
395
- dragState={dragState}
387
+ return (
388
+ <InverseContext.Provider value={{ isInverse }}>
389
+ <FormFieldContainer
390
+ actionable={false}
391
+ containerStyle={containerStyle}
392
+ errorMessage={errorMessage}
393
+ fieldId={id}
394
+ inputSize={inputSize}
396
395
  isInverse={isInverse}
397
- noDrag={noDrag}
398
- theme={theme}
399
- {...getRootProps()}
400
- {...rest}
401
- testId={testId}
402
- tabIndex={-1}
396
+ isLabelVisuallyHidden={isLabelVisuallyHidden}
397
+ labelStyle={labelStyle}
398
+ labelText={labelText}
399
+ messageStyle={{ minHeight: 0 }}
400
+ data-testid={testId}
403
401
  >
404
- <input ref={ref} {...getInputProps({id})} />
405
- {noDrag ? (
406
- <Flex xs behavior={FlexBehavior.item}>
407
- <Button
408
- color={ButtonColor.primary}
409
- disabled={disabled}
410
- isInverse={isInverse}
411
- onClick={open}
412
- style={{ margin: 0 }}
413
- >
414
- {i18n.dropzone.browseFiles}
415
- </Button>
416
- </Flex>
417
- ) : (
418
- <Flex behavior={FlexBehavior.item}>
419
- <CloudUploadIcon
420
- aria-hidden="true"
421
- color={
422
- isInverse ? theme.colors.neutral07 : theme.colors.neutral02
423
- }
424
- size={48}
425
- />
426
- <Wrapper isInverse={isInverse} theme={theme}>
427
- {i18n.dropzone.dragMessage}
428
- </Wrapper>
429
- <Button
430
- color={ButtonColor.secondary}
431
- disabled={disabled}
432
- isInverse={isInverse}
433
- onClick={open}
434
- style={{ margin: 0 }}
435
- variant={ButtonVariant.solid}
436
- >
437
- {i18n.dropzone.browseFiles}
438
- </Button>
439
- </Flex>
440
- )}
441
- </Container>
442
- </FormFieldContainer>
443
- {files.map((file: FilePreview) => (
444
- <Preview
445
- accept={accept}
446
- file={file}
447
- isInverse={isInverse}
448
- key={file.name}
449
- maxSize={maxSize}
450
- minSize={minSize}
451
- onDeleteFile={handleDeleteFile}
452
- onRemoveFile={handleRemoveFile}
453
- thumbnails={thumbnails}
454
- />
455
- ))}
456
- </InverseContext.Provider>
457
- );
458
- });
402
+ <HelperMessage theme={theme} isInverse={isInverse}>
403
+ {helperMessage}
404
+ </HelperMessage>
405
+ <Container
406
+ behavior={FlexBehavior.container}
407
+ dragState={dragState}
408
+ isInverse={isInverse}
409
+ noDrag={noDrag}
410
+ theme={theme}
411
+ {...getRootProps()}
412
+ {...rest}
413
+ testId={testId}
414
+ tabIndex={-1}
415
+ >
416
+ <input ref={ref} {...getInputProps({ id })} />
417
+ {noDrag ? (
418
+ <Flex xs behavior={FlexBehavior.item}>
419
+ <Button
420
+ color={ButtonColor.primary}
421
+ disabled={disabled}
422
+ isInverse={isInverse}
423
+ onClick={open}
424
+ style={{ margin: 0 }}
425
+ >
426
+ {i18n.dropzone.browseFiles}
427
+ </Button>
428
+ </Flex>
429
+ ) : (
430
+ <Flex behavior={FlexBehavior.item}>
431
+ <CloudUploadIcon
432
+ aria-hidden="true"
433
+ color={
434
+ isInverse
435
+ ? theme.colors.neutral100
436
+ : theme.colors.neutral500
437
+ }
438
+ size={48}
439
+ />
440
+ <Wrapper isInverse={isInverse} theme={theme}>
441
+ {i18n.dropzone.dragMessage}
442
+ </Wrapper>
443
+ <Button
444
+ color={ButtonColor.primary}
445
+ disabled={disabled}
446
+ isInverse={isInverse}
447
+ onClick={open}
448
+ style={{ margin: 0 }}
449
+ variant={ButtonVariant.solid}
450
+ >
451
+ {i18n.dropzone.browseFiles}
452
+ </Button>
453
+ </Flex>
454
+ )}
455
+ </Container>
456
+ </FormFieldContainer>
457
+ {files.map((file: FilePreview) => (
458
+ <Preview
459
+ accept={accept}
460
+ file={file}
461
+ isInverse={isInverse}
462
+ key={file.name}
463
+ maxSize={maxSize}
464
+ minSize={minSize}
465
+ onDeleteFile={handleDeleteFile}
466
+ onRemoveFile={handleRemoveFile}
467
+ thumbnails={thumbnails}
468
+ />
469
+ ))}
470
+ </InverseContext.Provider>
471
+ );
472
+ }
473
+ );