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