@transferwise/components 46.63.0 → 46.64.0
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/build/card/Card.js.map +1 -1
- package/build/card/Card.mjs.map +1 -1
- package/build/circularButton/CircularButton.js.map +1 -1
- package/build/circularButton/CircularButton.mjs.map +1 -1
- package/build/common/locale/index.js.map +1 -1
- package/build/common/locale/index.mjs.map +1 -1
- package/build/dateLookup/tableLink/TableLink.js.map +1 -1
- package/build/dateLookup/tableLink/TableLink.mjs.map +1 -1
- package/build/instructionsList/InstructionsList.js.map +1 -1
- package/build/instructionsList/InstructionsList.mjs.map +1 -1
- package/build/types/card/Card.d.ts.map +1 -1
- package/build/types/circularButton/CircularButton.d.ts.map +1 -1
- package/build/types/instructionsList/InstructionsList.d.ts.map +1 -1
- package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
- package/build/types/uploadInput/uploadButton/UploadButton.d.ts +1 -1
- package/build/types/uploadInput/uploadButton/UploadButton.d.ts.map +1 -1
- package/build/types/uploadInput/uploadItem/UploadItem.d.ts +5 -1
- package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
- package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts +5 -5
- package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
- package/build/uploadInput/UploadInput.js +42 -11
- package/build/uploadInput/UploadInput.js.map +1 -1
- package/build/uploadInput/UploadInput.mjs +43 -12
- package/build/uploadInput/UploadInput.mjs.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.js +14 -7
- package/build/uploadInput/uploadButton/UploadButton.js.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.mjs +15 -8
- package/build/uploadInput/uploadButton/UploadButton.mjs.map +1 -1
- package/build/uploadInput/uploadItem/UploadItem.js +18 -3
- package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
- package/build/uploadInput/uploadItem/UploadItem.mjs +18 -3
- package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
- package/build/uploadInput/uploadItem/UploadItemLink.js +6 -3
- package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
- package/build/uploadInput/uploadItem/UploadItemLink.mjs +6 -3
- package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
- package/package.json +1 -1
- package/src/card/Card.spec.tsx +4 -5
- package/src/card/Card.story.tsx +4 -6
- package/src/card/Card.tsx +3 -2
- package/src/circularButton/CircularButton.tsx +1 -1
- package/src/common/locale/index.ts +1 -1
- package/src/dateLookup/tableLink/TableLink.tsx +15 -15
- package/src/instructionsList/InstructionsList.tsx +1 -4
- package/src/uploadInput/UploadInput.tests.story.tsx +7 -3
- package/src/uploadInput/UploadInput.tsx +50 -8
- package/src/uploadInput/uploadButton/UploadButton.tsx +163 -141
- package/src/uploadInput/uploadItem/UploadItem.tsx +146 -124
- package/src/uploadInput/uploadItem/UploadItemLink.tsx +23 -25
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
|
3
3
|
import { useIntl } from 'react-intl';
|
|
4
4
|
|
|
5
5
|
import Button from '../button';
|
|
@@ -101,6 +101,10 @@ export type UploadInputProps = {
|
|
|
101
101
|
Pick<UploadItemProps, 'onDownload'> &
|
|
102
102
|
CommonProps;
|
|
103
103
|
|
|
104
|
+
interface UploadItemRef {
|
|
105
|
+
focus: () => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
function generateFileId(file: File) {
|
|
105
109
|
const { name, size } = file;
|
|
106
110
|
const uploadTimeStamp = new Date().getTime();
|
|
@@ -131,8 +135,11 @@ const UploadInput = ({
|
|
|
131
135
|
const inputAttributes = useInputAttributes({ nonLabelable: true });
|
|
132
136
|
|
|
133
137
|
const [markedFileForDelete, setMarkedFileForDelete] = useState<UploadedFile | null>(null);
|
|
138
|
+
const [fileToRemoveIndex, setFileToRemoveIndex] = useState<number | null>(null);
|
|
134
139
|
const [mounted, setMounted] = useState(false);
|
|
135
140
|
const { formatMessage } = useIntl();
|
|
141
|
+
const itemRefs = useRef<(HTMLDivElement | UploadItemRef | null)[]>([]);
|
|
142
|
+
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
|
136
143
|
|
|
137
144
|
const PROGRESS_STATUSES = new Set([Status.PENDING, Status.PROCESSING]);
|
|
138
145
|
|
|
@@ -174,21 +181,28 @@ const UploadInput = ({
|
|
|
174
181
|
uploadedFilesListReference.current = updateListItem(uploadedFilesListReference.current);
|
|
175
182
|
};
|
|
176
183
|
|
|
184
|
+
const [fileToRemove, setFileToRemove] = useState<UploadedFile | null>(null);
|
|
185
|
+
|
|
177
186
|
const removeFile = (file: UploadedFile) => {
|
|
178
187
|
const { id, status } = file;
|
|
188
|
+
const index = uploadedFiles.findIndex((f) => f.id === file.id);
|
|
189
|
+
setFileToRemoveIndex(index);
|
|
179
190
|
|
|
180
191
|
if (status === Status.FAILED) {
|
|
181
|
-
// If removing a failed upload, we're just updating the view
|
|
182
192
|
removeFileFromList(file);
|
|
193
|
+
setFileToRemove(file);
|
|
183
194
|
} else if (onDeleteFile && id) {
|
|
184
|
-
// Set status to PROCESSING
|
|
185
195
|
modifyFileInList(file, { status: Status.PROCESSING, error: undefined });
|
|
186
196
|
|
|
187
|
-
// Notify host app about deletion
|
|
188
197
|
onDeleteFile(id)
|
|
189
|
-
.then(() =>
|
|
198
|
+
.then(() => {
|
|
199
|
+
removeFileFromList(file);
|
|
200
|
+
})
|
|
190
201
|
.catch((error) => {
|
|
191
202
|
modifyFileInList(file, { error: error as UploadError });
|
|
203
|
+
})
|
|
204
|
+
.finally(() => {
|
|
205
|
+
setFileToRemove(file);
|
|
192
206
|
});
|
|
193
207
|
}
|
|
194
208
|
};
|
|
@@ -263,10 +277,18 @@ const UploadInput = ({
|
|
|
263
277
|
continue;
|
|
264
278
|
}
|
|
265
279
|
|
|
280
|
+
// Check if the file is already in the list
|
|
281
|
+
const existingFile = uploadedFiles.find((f) => f.filename === file.name);
|
|
282
|
+
if (existingFile) {
|
|
283
|
+
// Remove the file from the list before adding it again
|
|
284
|
+
removeFileFromList(existingFile);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add the file to the list
|
|
266
288
|
formData.append(fileInputName, file);
|
|
267
289
|
const pendingFile = {
|
|
268
|
-
id,
|
|
269
|
-
filename: name,
|
|
290
|
+
id: generateFileId(file),
|
|
291
|
+
filename: file.name,
|
|
270
292
|
status: Status.PENDING,
|
|
271
293
|
};
|
|
272
294
|
|
|
@@ -290,6 +312,22 @@ const UploadInput = ({
|
|
|
290
312
|
}
|
|
291
313
|
};
|
|
292
314
|
|
|
315
|
+
useLayoutEffect(() => {
|
|
316
|
+
if (fileToRemove && fileToRemoveIndex !== null) {
|
|
317
|
+
requestAnimationFrame(() => {
|
|
318
|
+
const nextFocusIndex = Math.min(fileToRemoveIndex, uploadedFiles.length - 1);
|
|
319
|
+
if (itemRefs.current[nextFocusIndex]) {
|
|
320
|
+
itemRefs.current[nextFocusIndex].focus(); // Focus the next UploadItem
|
|
321
|
+
} else {
|
|
322
|
+
// If there's only one item left, focus the UploadButton
|
|
323
|
+
uploadInputRef.current?.focus();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
setFileToRemove(null); // Reset the state
|
|
327
|
+
setFileToRemoveIndex(null); // Reset the index
|
|
328
|
+
}
|
|
329
|
+
}, [uploadedFiles, fileToRemove, fileToRemoveIndex, itemRefs, uploadInputRef]);
|
|
330
|
+
|
|
293
331
|
useEffect(() => {
|
|
294
332
|
setMounted(true);
|
|
295
333
|
}, []);
|
|
@@ -307,9 +345,12 @@ const UploadInput = ({
|
|
|
307
345
|
className={clsx('np-upload-input', className, { disabled })}
|
|
308
346
|
{...inputAttributes}
|
|
309
347
|
>
|
|
310
|
-
{uploadedFiles.map((file) => (
|
|
348
|
+
{uploadedFiles.map((file, index) => (
|
|
311
349
|
<UploadItem
|
|
312
350
|
key={file.id}
|
|
351
|
+
ref={(el: UploadItemRef | null) => {
|
|
352
|
+
itemRefs.current[index] = el;
|
|
353
|
+
}}
|
|
313
354
|
file={file}
|
|
314
355
|
singleFileUpload={!multiple}
|
|
315
356
|
canDelete={
|
|
@@ -326,6 +367,7 @@ const UploadInput = ({
|
|
|
326
367
|
))}
|
|
327
368
|
{(multiple || (!multiple && !uploadedFiles.length)) && (
|
|
328
369
|
<UploadButton
|
|
370
|
+
ref={uploadInputRef}
|
|
329
371
|
id={id}
|
|
330
372
|
uploadButtonTitle={uploadButtonTitle}
|
|
331
373
|
disabled={areMaximumFilesUploadedAlready() || disabled}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import { PlusCircle as PlusIcon, Upload as UploadIcon } from '@transferwise/icons';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ChangeEvent,
|
|
5
|
+
DragEvent,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useImperativeHandle,
|
|
10
|
+
ForwardedRef,
|
|
11
|
+
} from 'react';
|
|
4
12
|
import { useIntl } from 'react-intl';
|
|
5
13
|
|
|
6
14
|
import Body from '../../body';
|
|
@@ -70,169 +78,183 @@ const onDragOver = (event: DragEvent): void => {
|
|
|
70
78
|
};
|
|
71
79
|
|
|
72
80
|
const DEFAULT_FILE_INPUT_ID = 'np-upload-button';
|
|
73
|
-
const UploadButton = (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
81
|
+
const UploadButton = forwardRef<HTMLInputElement | null, UploadButtonProps>(
|
|
82
|
+
(
|
|
83
|
+
{
|
|
84
|
+
disabled,
|
|
85
|
+
multiple,
|
|
86
|
+
description,
|
|
87
|
+
fileTypes = imageFileTypes,
|
|
88
|
+
sizeLimit = DEFAULT_SIZE_LIMIT,
|
|
89
|
+
maxFiles,
|
|
90
|
+
onChange,
|
|
91
|
+
id = DEFAULT_FILE_INPUT_ID,
|
|
92
|
+
uploadButtonTitle,
|
|
93
|
+
},
|
|
94
|
+
ref: ForwardedRef<HTMLInputElement | null>,
|
|
95
|
+
) => {
|
|
96
|
+
const { formatMessage } = useIntl();
|
|
97
|
+
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
98
|
+
|
|
99
|
+
useImperativeHandle(ref, () => {
|
|
100
|
+
if (!inputRef.current) {
|
|
101
|
+
throw new Error('inputRef.current is null');
|
|
102
|
+
}
|
|
103
|
+
return inputRef.current;
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
const [isDropping, setIsDropping] = useState(false);
|
|
107
|
+
|
|
108
|
+
const dragCounter = useRef(0);
|
|
109
|
+
|
|
110
|
+
const reset = (): void => {
|
|
111
|
+
dragCounter.current = 0;
|
|
100
112
|
setIsDropping(false);
|
|
101
|
-
}
|
|
102
|
-
};
|
|
113
|
+
};
|
|
103
114
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
115
|
+
const onDragLeave = (event: DragEvent): void => {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
dragCounter.current -= 1;
|
|
118
|
+
if (dragCounter.current === 0) {
|
|
119
|
+
setIsDropping(false);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
111
122
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
123
|
+
const onDragEnter = (event: DragEvent): void => {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
dragCounter.current += 1;
|
|
126
|
+
if (dragCounter.current === 1) {
|
|
127
|
+
setIsDropping(true);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const onDrop = (event: DragEvent): void => {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
reset();
|
|
134
|
+
if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
|
|
135
|
+
onChange(event.dataTransfer.files);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
119
138
|
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
const filesSelected = (event: ChangeEvent<HTMLInputElement>): void => {
|
|
140
|
+
const { files } = event.target;
|
|
122
141
|
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
if (files) {
|
|
143
|
+
onChange(files);
|
|
125
144
|
|
|
126
|
-
|
|
127
|
-
|
|
145
|
+
if (inputRef.current) {
|
|
146
|
+
inputRef.current.value = '';
|
|
147
|
+
}
|
|
128
148
|
}
|
|
129
|
-
}
|
|
130
|
-
};
|
|
149
|
+
};
|
|
131
150
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
151
|
+
const getFileTypesDescription = (): string => {
|
|
152
|
+
if (fileTypes === '*') {
|
|
153
|
+
return fileTypes;
|
|
154
|
+
}
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
|
|
156
|
+
return getAllowedFileTypes(Array.isArray(fileTypes) ? fileTypes : [fileTypes]).join(', ');
|
|
157
|
+
};
|
|
139
158
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
159
|
+
function getDescription() {
|
|
160
|
+
if (description) {
|
|
161
|
+
return description;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const fileTypesDescription = getFileTypesDescription();
|
|
144
165
|
|
|
145
|
-
|
|
166
|
+
const derivedFileDescription =
|
|
167
|
+
fileTypesDescription === '*' ? formatMessage(MESSAGES.allFileTypes) : fileTypesDescription;
|
|
146
168
|
|
|
147
|
-
|
|
148
|
-
|
|
169
|
+
return formatMessage(MESSAGES.instructions, {
|
|
170
|
+
fileTypes: derivedFileDescription,
|
|
171
|
+
size: Math.round(sizeLimit / 1000),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
149
174
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
size: Math.round(sizeLimit / 1000),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
175
|
+
function getAcceptedTypes(): Pick<React.ComponentPropsWithoutRef<'input'>, 'accept'> {
|
|
176
|
+
const areAllFilesAllowed = getFileTypesDescription() === '*';
|
|
155
177
|
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
if (areAllFilesAllowed) {
|
|
179
|
+
return {}; // file input by default allows all files
|
|
180
|
+
}
|
|
158
181
|
|
|
159
|
-
|
|
160
|
-
|
|
182
|
+
if (Array.isArray(fileTypes)) {
|
|
183
|
+
return { accept: fileTypes.join(',') };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { accept: fileTypes as string };
|
|
161
187
|
}
|
|
162
188
|
|
|
163
|
-
|
|
164
|
-
return
|
|
189
|
+
function renderDescription() {
|
|
190
|
+
return (
|
|
191
|
+
<Body className={clsx({ 'text-primary': !disabled })}>
|
|
192
|
+
{getDescription()}
|
|
193
|
+
{maxFiles && (
|
|
194
|
+
<>
|
|
195
|
+
<br />
|
|
196
|
+
{`Maximum ${maxFiles} files.`}
|
|
197
|
+
</>
|
|
198
|
+
)}
|
|
199
|
+
</Body>
|
|
200
|
+
);
|
|
165
201
|
}
|
|
166
202
|
|
|
167
|
-
|
|
168
|
-
|
|
203
|
+
function renderButtonTitle() {
|
|
204
|
+
if (uploadButtonTitle) {
|
|
205
|
+
return uploadButtonTitle;
|
|
206
|
+
}
|
|
207
|
+
return formatMessage(multiple ? MESSAGES.uploadFiles : MESSAGES.uploadFile);
|
|
208
|
+
}
|
|
169
209
|
|
|
170
|
-
function renderDescription() {
|
|
171
210
|
return (
|
|
172
|
-
<
|
|
173
|
-
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
211
|
+
<div
|
|
212
|
+
className={clsx('np-upload-button-container', 'droppable', {
|
|
213
|
+
'droppable-dropping': isDropping,
|
|
214
|
+
})}
|
|
215
|
+
{...(!disabled && { onDragEnter, onDragLeave, onDrop, onDragOver })}
|
|
216
|
+
>
|
|
217
|
+
<input
|
|
218
|
+
ref={inputRef}
|
|
219
|
+
id={id}
|
|
220
|
+
type="file"
|
|
221
|
+
{...getAcceptedTypes()}
|
|
222
|
+
{...(multiple && { multiple: true })}
|
|
223
|
+
className="tw-droppable-input"
|
|
224
|
+
disabled={disabled}
|
|
225
|
+
name="file-upload"
|
|
226
|
+
data-testid={TEST_IDS.uploadInput}
|
|
227
|
+
onChange={filesSelected}
|
|
228
|
+
/>
|
|
229
|
+
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
230
|
+
<label htmlFor={id} className={clsx('btn', 'np-upload-button')}>
|
|
231
|
+
<div className="media">
|
|
232
|
+
<div className="np-upload-icon media-middle media-left">
|
|
233
|
+
<UploadIcon size={24} className="text-link" />
|
|
234
|
+
</div>
|
|
235
|
+
<div className="media-body text-xs-left" data-testid={TEST_IDS.mediaBody}>
|
|
236
|
+
<Body type={Typography.BODY_LARGE_BOLD} className="d-block">
|
|
237
|
+
{renderButtonTitle()}
|
|
238
|
+
</Body>
|
|
239
|
+
{renderDescription()}
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</label>
|
|
243
|
+
|
|
244
|
+
{/* Drop area overlay */}
|
|
245
|
+
{isDropping && (
|
|
246
|
+
<div
|
|
247
|
+
className={clsx('droppable-card', 'droppable-dropping-card', 'droppable-card-content')}
|
|
248
|
+
>
|
|
249
|
+
<PlusIcon className="m-x-1" size={24} />
|
|
250
|
+
<div>{formatMessage(MESSAGES.dropFile)}</div>
|
|
251
|
+
</div>
|
|
179
252
|
)}
|
|
180
|
-
</
|
|
253
|
+
</div>
|
|
181
254
|
);
|
|
182
|
-
}
|
|
255
|
+
},
|
|
256
|
+
);
|
|
183
257
|
|
|
184
|
-
|
|
185
|
-
if (uploadButtonTitle) {
|
|
186
|
-
return uploadButtonTitle;
|
|
187
|
-
}
|
|
188
|
-
return formatMessage(multiple ? MESSAGES.uploadFiles : MESSAGES.uploadFile);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return (
|
|
192
|
-
<div
|
|
193
|
-
className={clsx('np-upload-button-container', 'droppable', {
|
|
194
|
-
'droppable-dropping': isDropping,
|
|
195
|
-
})}
|
|
196
|
-
{...(!disabled && { onDragEnter, onDragLeave, onDrop, onDragOver })}
|
|
197
|
-
>
|
|
198
|
-
<input
|
|
199
|
-
ref={inputReference}
|
|
200
|
-
id={id}
|
|
201
|
-
type="file"
|
|
202
|
-
{...getAcceptedTypes()}
|
|
203
|
-
{...(multiple && { multiple: true })}
|
|
204
|
-
className="tw-droppable-input"
|
|
205
|
-
disabled={disabled}
|
|
206
|
-
name="file-upload"
|
|
207
|
-
data-testid={TEST_IDS.uploadInput}
|
|
208
|
-
onChange={filesSelected}
|
|
209
|
-
/>
|
|
210
|
-
{/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
|
|
211
|
-
<label htmlFor={id} className={clsx('btn', 'np-upload-button')}>
|
|
212
|
-
<div className="media">
|
|
213
|
-
<div className="np-upload-icon media-middle media-left">
|
|
214
|
-
<UploadIcon size={24} className="text-link" />
|
|
215
|
-
</div>
|
|
216
|
-
<div className="media-body text-xs-left" data-testid={TEST_IDS.mediaBody}>
|
|
217
|
-
<Body type={Typography.BODY_LARGE_BOLD} className="d-block">
|
|
218
|
-
{renderButtonTitle()}
|
|
219
|
-
</Body>
|
|
220
|
-
{renderDescription()}
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
</label>
|
|
224
|
-
|
|
225
|
-
{/* Drop area overlay */}
|
|
226
|
-
{isDropping && (
|
|
227
|
-
<div
|
|
228
|
-
className={clsx('droppable-card', 'droppable-dropping-card', 'droppable-card-content')}
|
|
229
|
-
>
|
|
230
|
-
<PlusIcon className="m-x-1" size={24} />
|
|
231
|
-
<div>{formatMessage(MESSAGES.dropFile)}</div>
|
|
232
|
-
</div>
|
|
233
|
-
)}
|
|
234
|
-
</div>
|
|
235
|
-
);
|
|
236
|
-
};
|
|
258
|
+
UploadButton.displayName = 'UploadButton';
|
|
237
259
|
|
|
238
260
|
export default UploadButton;
|