@transferwise/components 0.0.0-experimental-faee41b → 0.0.0-experimental-96d193a
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/alert/Alert.js +3 -1
- package/build/alert/Alert.js.map +1 -1
- package/build/alert/Alert.mjs +3 -1
- package/build/alert/Alert.mjs.map +1 -1
- package/build/dimmer/Dimmer.js +1 -3
- package/build/dimmer/Dimmer.js.map +1 -1
- package/build/dimmer/Dimmer.mjs +1 -3
- package/build/dimmer/Dimmer.mjs.map +1 -1
- package/build/drawer/Drawer.js +0 -2
- package/build/drawer/Drawer.js.map +1 -1
- package/build/drawer/Drawer.mjs +0 -2
- package/build/drawer/Drawer.mjs.map +1 -1
- package/build/field/Field.js +2 -0
- package/build/field/Field.js.map +1 -1
- package/build/field/Field.mjs +2 -0
- package/build/field/Field.mjs.map +1 -1
- package/build/i18n/en.json +5 -0
- package/build/i18n/en.json.js +5 -0
- package/build/i18n/en.json.js.map +1 -1
- package/build/i18n/en.json.mjs +5 -0
- package/build/i18n/en.json.mjs.map +1 -1
- package/build/inlineAlert/InlineAlert.js +3 -1
- package/build/inlineAlert/InlineAlert.js.map +1 -1
- package/build/inlineAlert/InlineAlert.mjs +3 -1
- package/build/inlineAlert/InlineAlert.mjs.map +1 -1
- package/build/modal/Modal.js +0 -3
- package/build/modal/Modal.js.map +1 -1
- package/build/modal/Modal.mjs +0 -3
- package/build/modal/Modal.mjs.map +1 -1
- package/build/statusIcon/StatusIcon.js +50 -16
- package/build/statusIcon/StatusIcon.js.map +1 -1
- package/build/statusIcon/StatusIcon.messages.js +24 -0
- package/build/statusIcon/StatusIcon.messages.js.map +1 -0
- package/build/statusIcon/StatusIcon.messages.mjs +22 -0
- package/build/statusIcon/StatusIcon.messages.mjs.map +1 -0
- package/build/statusIcon/StatusIcon.mjs +48 -14
- package/build/statusIcon/StatusIcon.mjs.map +1 -1
- package/build/types/alert/Alert.d.ts +6 -1
- package/build/types/alert/Alert.d.ts.map +1 -1
- package/build/types/dimmer/Dimmer.d.ts +1 -2
- package/build/types/dimmer/Dimmer.d.ts.map +1 -1
- package/build/types/drawer/Drawer.d.ts +1 -2
- package/build/types/drawer/Drawer.d.ts.map +1 -1
- package/build/types/field/Field.d.ts +6 -1
- package/build/types/field/Field.d.ts.map +1 -1
- package/build/types/inlineAlert/InlineAlert.d.ts +2 -1
- package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
- package/build/types/modal/Modal.d.ts +1 -2
- package/build/types/modal/Modal.d.ts.map +1 -1
- package/build/types/statusIcon/StatusIcon.d.ts +6 -1
- package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
- package/build/types/statusIcon/StatusIcon.messages.d.ts +29 -0
- package/build/types/statusIcon/StatusIcon.messages.d.ts.map +1 -0
- package/build/types/upload/Upload.d.ts +5 -0
- package/build/types/upload/Upload.d.ts.map +1 -1
- package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts +1 -0
- package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts.map +1 -1
- package/build/types/uploadInput/UploadInput.d.ts +0 -9
- package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
- package/build/types/uploadInput/uploadItem/UploadItem.d.ts +1 -16
- package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
- package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
- package/build/upload/Upload.js +4 -2
- package/build/upload/Upload.js.map +1 -1
- package/build/upload/Upload.mjs +4 -2
- package/build/upload/Upload.mjs.map +1 -1
- package/build/upload/steps/uploadImageStep/uploadImageStep.js +4 -2
- package/build/upload/steps/uploadImageStep/uploadImageStep.js.map +1 -1
- package/build/upload/steps/uploadImageStep/uploadImageStep.mjs +4 -2
- package/build/upload/steps/uploadImageStep/uploadImageStep.mjs.map +1 -1
- package/build/uploadInput/UploadInput.js +66 -71
- package/build/uploadInput/UploadInput.js.map +1 -1
- package/build/uploadInput/UploadInput.mjs +67 -72
- package/build/uploadInput/UploadInput.mjs.map +1 -1
- package/build/uploadInput/uploadItem/UploadItem.js +4 -13
- package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
- package/build/uploadInput/uploadItem/UploadItem.mjs +4 -13
- package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
- package/build/uploadInput/uploadItem/UploadItemLink.js +0 -1
- package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
- package/build/uploadInput/uploadItem/UploadItemLink.mjs +0 -1
- package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
- package/package.json +3 -3
- package/src/alert/Alert.spec.tsx +10 -0
- package/src/alert/Alert.tsx +7 -1
- package/src/dimmer/Dimmer.spec.js +0 -8
- package/src/dimmer/Dimmer.tsx +0 -4
- package/src/drawer/Drawer.spec.js +6 -25
- package/src/drawer/Drawer.tsx +1 -3
- package/src/field/Field.spec.tsx +19 -0
- package/src/field/Field.story.tsx +20 -4
- package/src/field/Field.tsx +7 -1
- package/src/i18n/en.json +5 -0
- package/src/inlineAlert/InlineAlert.spec.tsx +12 -1
- package/src/inlineAlert/InlineAlert.tsx +5 -1
- package/src/modal/Modal.spec.js +1 -19
- package/src/modal/Modal.tsx +0 -4
- package/src/statusIcon/StatusIcon.docs.mdx +22 -0
- package/src/statusIcon/StatusIcon.messages.ts +34 -0
- package/src/statusIcon/StatusIcon.spec.tsx +31 -4
- package/src/statusIcon/StatusIcon.story.tsx +2 -5
- package/src/statusIcon/StatusIcon.tsx +59 -14
- package/src/upload/Upload.spec.js +19 -0
- package/src/upload/Upload.tsx +7 -0
- package/src/upload/steps/uploadImageStep/uploadImageStep.spec.js +13 -0
- package/src/upload/steps/uploadImageStep/uploadImageStep.tsx +14 -3
- package/src/uploadInput/UploadInput.spec.tsx +9 -121
- package/src/uploadInput/UploadInput.tests.story.tsx +140 -209
- package/src/uploadInput/UploadInput.tsx +77 -110
- package/src/uploadInput/uploadItem/UploadItem.spec.tsx +0 -1
- package/src/uploadInput/uploadItem/UploadItem.tsx +6 -30
- package/src/uploadInput/uploadItem/UploadItemLink.tsx +1 -9
|
@@ -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';
|
|
@@ -99,45 +99,16 @@ export type UploadInputProps = {
|
|
|
99
99
|
'disabled' | 'multiple' | 'fileTypes' | 'sizeLimit' | 'description' | 'id' | 'uploadButtonTitle'
|
|
100
100
|
> & { onDownload?: UploadItemProps['onDownload'] } & CommonProps;
|
|
101
101
|
|
|
102
|
-
/**
|
|
103
|
-
* Interface representing a reference to an UploadItem component.
|
|
104
|
-
* Provides a method to focus the UploadItem.
|
|
105
|
-
*/
|
|
106
102
|
interface UploadItemRef {
|
|
107
|
-
/**
|
|
108
|
-
* Focuses the UploadItem component.
|
|
109
|
-
*/
|
|
110
103
|
focus: () => void;
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Required id of the UploadItem component.
|
|
114
|
-
*/
|
|
115
|
-
id: string | number;
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Optional status of the UploadItem component.
|
|
119
|
-
*/
|
|
120
|
-
status?: string;
|
|
121
104
|
}
|
|
122
105
|
|
|
123
|
-
/**
|
|
124
|
-
* Generates a unique ID for a file based on its name, size, and the current timestamp
|
|
125
|
-
*/
|
|
126
106
|
function generateFileId(file: File) {
|
|
127
107
|
const { name, size } = file;
|
|
128
108
|
const uploadTimeStamp = new Date().getTime();
|
|
129
109
|
return `${name}_${size}_${uploadTimeStamp}`;
|
|
130
110
|
}
|
|
131
111
|
|
|
132
|
-
/**
|
|
133
|
-
* The component allows users to upload files, manage the list of uploaded files,
|
|
134
|
-
* and handle file validation and deletion.
|
|
135
|
-
*
|
|
136
|
-
* @param {UploadInputProps} props - The properties for the UploadInput component.
|
|
137
|
-
*
|
|
138
|
-
* @see {@link UploadInput} for further information.
|
|
139
|
-
* @see {@link https://storybook.wise.design/?path=/docs/forms-uploadinput--docs|Storybook Wise Design}
|
|
140
|
-
*/
|
|
141
112
|
const UploadInput = ({
|
|
142
113
|
files = [],
|
|
143
114
|
fileInputName = 'file',
|
|
@@ -160,11 +131,13 @@ const UploadInput = ({
|
|
|
160
131
|
uploadButtonTitle,
|
|
161
132
|
}: UploadInputProps) => {
|
|
162
133
|
const inputAttributes = useInputAttributes({ nonLabelable: true });
|
|
134
|
+
|
|
163
135
|
const [markedFileForDelete, setMarkedFileForDelete] = useState<UploadedFile | null>(null);
|
|
136
|
+
const [fileToRemoveIndex, setFileToRemoveIndex] = useState<number | null>(null);
|
|
164
137
|
const [mounted, setMounted] = useState(false);
|
|
165
138
|
const { formatMessage } = useIntl();
|
|
139
|
+
const itemRefs = useRef<(HTMLDivElement | UploadItemRef | null)[]>([]);
|
|
166
140
|
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
|
167
|
-
let fileRefs: (HTMLDivElement | UploadItemRef | null)[] = [];
|
|
168
141
|
|
|
169
142
|
const PROGRESS_STATUSES = new Set([Status.PENDING, Status.PROCESSING]);
|
|
170
143
|
|
|
@@ -174,57 +147,69 @@ const UploadInput = ({
|
|
|
174
147
|
|
|
175
148
|
const uploadedFilesListReference = useRef(multiple || files.length === 0 ? files : [files[0]]);
|
|
176
149
|
|
|
177
|
-
function updateFileList(updateFn: (list: readonly UploadedFile[]) => readonly UploadedFile[]) {
|
|
178
|
-
setUploadedFiles(updateFn);
|
|
179
|
-
uploadedFilesListReference.current = updateFn(uploadedFilesListReference.current);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
150
|
function addFileToList(recentUploadedFile: UploadedFile) {
|
|
183
|
-
|
|
184
|
-
|
|
151
|
+
function addToList(listToAddTo: readonly UploadedFile[]) {
|
|
152
|
+
return [...listToAddTo, recentUploadedFile];
|
|
153
|
+
}
|
|
185
154
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
list.filter((fileInList) => file !== fileInList && file.id !== fileInList.id),
|
|
189
|
-
);
|
|
190
|
-
fileRefs = fileRefs.filter((ref) => ref && ref.id !== file.id);
|
|
155
|
+
setUploadedFiles(addToList);
|
|
156
|
+
uploadedFilesListReference.current = addToList(uploadedFilesListReference.current);
|
|
191
157
|
}
|
|
192
158
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
fileInList
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
|
|
159
|
+
const removeFileFromList = (file: UploadedFile) => {
|
|
160
|
+
function filterOutFrom(listToFilterFrom: readonly UploadedFile[]) {
|
|
161
|
+
return listToFilterFrom.filter(
|
|
162
|
+
(fileInList) => file !== fileInList && file.id !== fileInList.id,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setUploadedFiles(filterOutFrom);
|
|
167
|
+
uploadedFilesListReference.current = filterOutFrom(uploadedFilesListReference.current);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const modifyFileInList = (file: UploadedFile, updates: Partial<UploadedFile>) => {
|
|
171
|
+
const updateListItem = (listToUpdate: readonly UploadedFile[]) =>
|
|
172
|
+
listToUpdate.map((fileInList) => {
|
|
173
|
+
return fileInList === file || fileInList.id === file.id
|
|
174
|
+
? { ...file, ...updates }
|
|
175
|
+
: fileInList;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
setUploadedFiles(updateListItem);
|
|
179
|
+
uploadedFilesListReference.current = updateListItem(uploadedFilesListReference.current);
|
|
180
|
+
};
|
|
200
181
|
|
|
201
|
-
const
|
|
182
|
+
const [fileToRemove, setFileToRemove] = useState<UploadedFile | null>(null);
|
|
183
|
+
|
|
184
|
+
const removeFile = (file: UploadedFile) => {
|
|
202
185
|
const { id, status } = file;
|
|
203
|
-
|
|
186
|
+
const index = uploadedFiles.findIndex((f) => f.id === file.id);
|
|
187
|
+
setFileToRemoveIndex(index);
|
|
204
188
|
|
|
205
189
|
if (status === Status.FAILED) {
|
|
206
190
|
removeFileFromList(file);
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (onDeleteFile && id) {
|
|
191
|
+
setFileToRemove(file);
|
|
192
|
+
} else if (onDeleteFile && id) {
|
|
211
193
|
modifyFileInList(file, { status: Status.PROCESSING, error: undefined });
|
|
212
194
|
|
|
213
|
-
|
|
195
|
+
onDeleteFile(id)
|
|
214
196
|
.then(() => {
|
|
215
197
|
removeFileFromList(file);
|
|
216
198
|
})
|
|
217
199
|
.catch((error) => {
|
|
218
200
|
modifyFileInList(file, { error: error as UploadError });
|
|
201
|
+
})
|
|
202
|
+
.finally(() => {
|
|
203
|
+
setFileToRemove(file);
|
|
219
204
|
});
|
|
220
205
|
}
|
|
221
206
|
};
|
|
222
207
|
|
|
223
208
|
function handleFileUploadFailure(file: File, failureMessage: string) {
|
|
224
209
|
const { name } = file;
|
|
225
|
-
|
|
210
|
+
const id = generateFileId(file);
|
|
226
211
|
const failedUpload = {
|
|
227
|
-
id
|
|
212
|
+
id,
|
|
228
213
|
filename: name,
|
|
229
214
|
status: Status.FAILED,
|
|
230
215
|
error: failureMessage,
|
|
@@ -254,20 +239,28 @@ const UploadInput = ({
|
|
|
254
239
|
return numberOfValidFiles >= maxFiles;
|
|
255
240
|
}
|
|
256
241
|
|
|
242
|
+
// One or more files selected, create entries for them
|
|
257
243
|
const addFiles = (selectedFiles: FileList) => {
|
|
258
244
|
for (let i = 0; i < selectedFiles.length; i += 1) {
|
|
259
245
|
const file = selectedFiles.item(i);
|
|
260
246
|
|
|
247
|
+
// Returning a FormData[] array instead of FileList so we can filter out incorrect files
|
|
261
248
|
const formData = new FormData();
|
|
262
249
|
|
|
263
250
|
if (file) {
|
|
251
|
+
const { name } = file;
|
|
252
|
+
const id = generateFileId(file);
|
|
253
|
+
|
|
264
254
|
const allowedFileTypes = typeof fileTypes === 'string' ? fileTypes : fileTypes.join(',');
|
|
265
255
|
|
|
256
|
+
// Check if file type is valid
|
|
266
257
|
if (!isTypeValid(file, allowedFileTypes)) {
|
|
267
258
|
handleFileUploadFailure(file, formatMessage(MESSAGES.fileTypeNotSupported));
|
|
268
259
|
continue;
|
|
269
260
|
}
|
|
270
261
|
|
|
262
|
+
// Check if the filesize is valid
|
|
263
|
+
// Convert to rough bytes
|
|
271
264
|
if (!isSizeValid(file, sizeLimit * 1000)) {
|
|
272
265
|
const failureMessage = sizeLimitErrorMessage || formatMessage(MESSAGES.fileIsTooLarge);
|
|
273
266
|
handleFileUploadFailure(file, failureMessage);
|
|
@@ -282,11 +275,14 @@ const UploadInput = ({
|
|
|
282
275
|
continue;
|
|
283
276
|
}
|
|
284
277
|
|
|
278
|
+
// Check if the file is already in the list
|
|
285
279
|
const existingFile = uploadedFiles.find((f) => f.filename === file.name);
|
|
286
280
|
if (existingFile) {
|
|
281
|
+
// Remove the file from the list before adding it again
|
|
287
282
|
removeFileFromList(existingFile);
|
|
288
283
|
}
|
|
289
284
|
|
|
285
|
+
// Add the file to the list
|
|
290
286
|
formData.append(fileInputName, file);
|
|
291
287
|
const pendingFile = {
|
|
292
288
|
id: generateFileId(file),
|
|
@@ -296,8 +292,10 @@ const UploadInput = ({
|
|
|
296
292
|
|
|
297
293
|
addFileToList(pendingFile);
|
|
298
294
|
|
|
295
|
+
// Start uploading the file
|
|
299
296
|
onUploadFile(formData)
|
|
300
297
|
.then(({ id, url, error }: UploadResponse) => {
|
|
298
|
+
// Replace the temporary id with the final one received from the API, and also set any errors
|
|
301
299
|
modifyFileInList(pendingFile, { id, url, error, status: Status.SUCCEEDED });
|
|
302
300
|
})
|
|
303
301
|
.catch((error) => {
|
|
@@ -305,12 +303,29 @@ const UploadInput = ({
|
|
|
305
303
|
});
|
|
306
304
|
|
|
307
305
|
if (!multiple) {
|
|
306
|
+
// Only upload a single file
|
|
308
307
|
break;
|
|
309
308
|
}
|
|
310
309
|
}
|
|
311
310
|
}
|
|
312
311
|
};
|
|
313
312
|
|
|
313
|
+
useLayoutEffect(() => {
|
|
314
|
+
if (fileToRemove && fileToRemoveIndex !== null) {
|
|
315
|
+
requestAnimationFrame(() => {
|
|
316
|
+
const nextFocusIndex = Math.min(fileToRemoveIndex, uploadedFiles.length - 1);
|
|
317
|
+
if (itemRefs.current[nextFocusIndex]) {
|
|
318
|
+
itemRefs.current[nextFocusIndex].focus(); // Focus the next UploadItem
|
|
319
|
+
} else {
|
|
320
|
+
// If there's only one item left, focus the UploadButton
|
|
321
|
+
uploadInputRef.current?.focus();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
setFileToRemove(null); // Reset the state
|
|
325
|
+
setFileToRemoveIndex(null); // Reset the index
|
|
326
|
+
}
|
|
327
|
+
}, [uploadedFiles, fileToRemove, fileToRemoveIndex, itemRefs, uploadInputRef]);
|
|
328
|
+
|
|
314
329
|
useEffect(() => {
|
|
315
330
|
setMounted(true);
|
|
316
331
|
}, []);
|
|
@@ -321,41 +336,6 @@ const UploadInput = ({
|
|
|
321
336
|
}
|
|
322
337
|
}, [onFilesChange, uploadedFiles]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
323
338
|
|
|
324
|
-
const [nextFocusable, setNextFocusable] = useState<HTMLDivElement | UploadItemRef | null>(
|
|
325
|
-
uploadInputRef.current,
|
|
326
|
-
);
|
|
327
|
-
|
|
328
|
-
const handleFocus = (fileId: string | number) => {
|
|
329
|
-
fileRefs = fileRefs.filter((ref) => {
|
|
330
|
-
return ref && ref.id !== markedFileForDelete?.id;
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const filesCount = fileRefs.length;
|
|
334
|
-
let next: HTMLDivElement | UploadItemRef | null = uploadInputRef.current;
|
|
335
|
-
|
|
336
|
-
if (filesCount > 1) {
|
|
337
|
-
const currentFileIndex = fileRefs.findIndex((file) => file?.id === fileId);
|
|
338
|
-
const currentFileId = fileRefs?.[currentFileIndex]?.id;
|
|
339
|
-
const lastFileId = fileRefs?.[filesCount - 1]?.id;
|
|
340
|
-
|
|
341
|
-
// if last file, select a previous one
|
|
342
|
-
if (currentFileId === lastFileId) {
|
|
343
|
-
next = fileRefs[filesCount - 2];
|
|
344
|
-
} else {
|
|
345
|
-
next = fileRefs[currentFileIndex + 1];
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
setNextFocusable(next);
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
const handleRefocus = () => {
|
|
352
|
-
if (nextFocusable && 'focus' in nextFocusable && typeof nextFocusable.focus === 'function') {
|
|
353
|
-
setTimeout(() => {
|
|
354
|
-
nextFocusable.focus();
|
|
355
|
-
}, 0);
|
|
356
|
-
}
|
|
357
|
-
};
|
|
358
|
-
|
|
359
339
|
return (
|
|
360
340
|
<>
|
|
361
341
|
<div
|
|
@@ -373,14 +353,7 @@ const UploadInput = ({
|
|
|
373
353
|
<UploadItem
|
|
374
354
|
key={file.id}
|
|
375
355
|
ref={(el: UploadItemRef | null) => {
|
|
376
|
-
|
|
377
|
-
el &&
|
|
378
|
-
el.id !== markedFileForDelete?.id &&
|
|
379
|
-
!fileRefs.some((ref) => ref && ref.id === el.id) &&
|
|
380
|
-
el.status !== 'processing'
|
|
381
|
-
) {
|
|
382
|
-
fileRefs.push(el);
|
|
383
|
-
}
|
|
356
|
+
itemRefs.current[index] = el;
|
|
384
357
|
}}
|
|
385
358
|
file={file}
|
|
386
359
|
singleFileUpload={!multiple}
|
|
@@ -390,14 +363,10 @@ const UploadInput = ({
|
|
|
390
363
|
}
|
|
391
364
|
onDelete={
|
|
392
365
|
file.status === Status.FAILED
|
|
393
|
-
?
|
|
394
|
-
await removeFile(file);
|
|
395
|
-
handleRefocus();
|
|
396
|
-
}
|
|
366
|
+
? () => removeFile(file)
|
|
397
367
|
: () => setMarkedFileForDelete(file)
|
|
398
368
|
}
|
|
399
369
|
onDownload={onDownload}
|
|
400
|
-
onFocus={() => handleFocus(file.id)}
|
|
401
370
|
/>
|
|
402
371
|
))}
|
|
403
372
|
</div>
|
|
@@ -445,10 +414,9 @@ const UploadInput = ({
|
|
|
445
414
|
block
|
|
446
415
|
priority={Priority.SECONDARY}
|
|
447
416
|
type={ControlType.NEGATIVE}
|
|
448
|
-
tabIndex={markedFileForDelete ? 0 : -1}
|
|
449
417
|
onClick={() => {
|
|
450
418
|
if (markedFileForDelete) {
|
|
451
|
-
|
|
419
|
+
removeFile(markedFileForDelete);
|
|
452
420
|
}
|
|
453
421
|
setMarkedFileForDelete(null);
|
|
454
422
|
}}
|
|
@@ -457,7 +425,6 @@ const UploadInput = ({
|
|
|
457
425
|
</Button>
|
|
458
426
|
</>
|
|
459
427
|
}
|
|
460
|
-
onUnmount={handleRefocus}
|
|
461
428
|
onClose={() => {
|
|
462
429
|
setMarkedFileForDelete(null);
|
|
463
430
|
}}
|
|
@@ -4,8 +4,9 @@ import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
|
4
4
|
import { useIntl } from 'react-intl';
|
|
5
5
|
|
|
6
6
|
import Body from '../../body';
|
|
7
|
-
import { Size, Status, Typography } from '../../common';
|
|
7
|
+
import { Size, Status, Typography, Sentiment } from '../../common';
|
|
8
8
|
import ProcessIndicator from '../../processIndicator/ProcessIndicator';
|
|
9
|
+
import StatusIcon from '../../statusIcon/StatusIcon';
|
|
9
10
|
import { UploadedFile, UploadError } from '../types';
|
|
10
11
|
|
|
11
12
|
import MESSAGES from './UploadItem.messages';
|
|
@@ -19,7 +20,6 @@ export type UploadItemProps = React.JSX.IntrinsicAttributes & {
|
|
|
19
20
|
singleFileUpload: boolean;
|
|
20
21
|
canDelete: boolean;
|
|
21
22
|
onDelete: () => void;
|
|
22
|
-
onFocus: () => void;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Callback to be called when the file link is clicked.
|
|
@@ -30,33 +30,18 @@ export type UploadItemProps = React.JSX.IntrinsicAttributes & {
|
|
|
30
30
|
onDownload?: (file: UploadedFile) => void;
|
|
31
31
|
ref?: React.Ref<UploadItemRef>;
|
|
32
32
|
};
|
|
33
|
+
|
|
33
34
|
interface UploadItemRef {
|
|
34
|
-
/**
|
|
35
|
-
* A method to set focus on the upload item.
|
|
36
|
-
* @returns {void}
|
|
37
|
-
*/
|
|
38
35
|
focus: () => void;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* A required unique identifier for the upload item.
|
|
42
|
-
*/
|
|
43
|
-
id: string | number;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* An optional status of the upload item.
|
|
47
|
-
*/
|
|
48
|
-
status?: string;
|
|
49
36
|
}
|
|
50
37
|
|
|
51
38
|
export enum TEST_IDS {
|
|
52
39
|
uploadItem = 'uploadItem',
|
|
53
40
|
mediaBody = 'mediaBody',
|
|
54
|
-
link = 'link',
|
|
55
|
-
action = 'action',
|
|
56
41
|
}
|
|
57
42
|
|
|
58
43
|
const UploadItem = forwardRef<UploadItemRef, UploadItemProps>(
|
|
59
|
-
({ file, canDelete, onDelete, onDownload, singleFileUpload
|
|
44
|
+
({ file, canDelete, onDelete, onDownload, singleFileUpload }, ref) => {
|
|
60
45
|
const { formatMessage } = useIntl();
|
|
61
46
|
const { status, filename, error, errors, url } = file;
|
|
62
47
|
const linkRef = useRef<HTMLAnchorElement>(null);
|
|
@@ -70,8 +55,6 @@ const UploadItem = forwardRef<UploadItemRef, UploadItemProps>(
|
|
|
70
55
|
buttonRef.current?.focus();
|
|
71
56
|
}
|
|
72
57
|
},
|
|
73
|
-
id: file.id,
|
|
74
|
-
status: file.status,
|
|
75
58
|
}));
|
|
76
59
|
|
|
77
60
|
const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;
|
|
@@ -169,20 +152,16 @@ const UploadItem = forwardRef<UploadItemRef, UploadItemProps>(
|
|
|
169
152
|
return (
|
|
170
153
|
<div
|
|
171
154
|
className={clsx('np-upload-input__item', { 'is-interactive': isSucceeded && url })}
|
|
172
|
-
data-testid={
|
|
155
|
+
data-testid={TEST_IDS.uploadItem}
|
|
173
156
|
>
|
|
174
157
|
<UploadItemLink
|
|
175
158
|
ref={linkRef}
|
|
176
159
|
url={isSucceeded ? url : undefined}
|
|
177
160
|
singleFileUpload={singleFileUpload}
|
|
178
|
-
data-testid={`${file.id}-${TEST_IDS.link}`}
|
|
179
161
|
onDownload={onDownloadFile}
|
|
180
162
|
>
|
|
181
163
|
<span className="np-upload-input__icon">{getIcon()}</span>
|
|
182
|
-
<div
|
|
183
|
-
className="np-upload-input__item-content"
|
|
184
|
-
data-testid={`${file.id}-${TEST_IDS.mediaBody}`}
|
|
185
|
-
>
|
|
164
|
+
<div className="np-upload-input__item-content" data-testid={TEST_IDS.mediaBody}>
|
|
186
165
|
<Body type={Typography.BODY_LARGE} className="np-upload-input__title text-word-break">
|
|
187
166
|
{getTitle()}
|
|
188
167
|
</Body>
|
|
@@ -196,10 +175,7 @@ const UploadItem = forwardRef<UploadItemRef, UploadItemProps>(
|
|
|
196
175
|
aria-label={formatMessage(MESSAGES.removeFile, { filename })}
|
|
197
176
|
className="np-upload-input__item-button"
|
|
198
177
|
type="button"
|
|
199
|
-
tabIndex={0}
|
|
200
|
-
data-testid={`${file.id}-${TEST_IDS.action}`}
|
|
201
178
|
onClick={() => onDelete()}
|
|
202
|
-
onFocus={handleFocus}
|
|
203
179
|
>
|
|
204
180
|
<Bin size={16} />
|
|
205
181
|
</button>
|
|
@@ -10,14 +10,7 @@ type UploadItemLinkProps = PropsWithChildren<{
|
|
|
10
10
|
export const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, UploadItemLinkProps>(
|
|
11
11
|
({ children, url, onDownload, singleFileUpload }, ref) => {
|
|
12
12
|
if (!url) {
|
|
13
|
-
return (
|
|
14
|
-
<div
|
|
15
|
-
ref={ref as React.RefObject<HTMLDivElement>}
|
|
16
|
-
className={clsx('np-upload-input__item-container')}
|
|
17
|
-
>
|
|
18
|
-
{children}
|
|
19
|
-
</div>
|
|
20
|
-
);
|
|
13
|
+
return <div ref={ref as React.RefObject<HTMLDivElement>} className={clsx('np-upload-input__item-container')}>{children}</div>;
|
|
21
14
|
}
|
|
22
15
|
|
|
23
16
|
return (
|
|
@@ -30,7 +23,6 @@ export const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, Upl
|
|
|
30
23
|
'np-upload-input__item-link',
|
|
31
24
|
singleFileUpload ? 'np-upload-input__item-link--single-file' : '',
|
|
32
25
|
)}
|
|
33
|
-
tabIndex={0}
|
|
34
26
|
onClick={onDownload}
|
|
35
27
|
>
|
|
36
28
|
{children}
|