@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.
Files changed (112) hide show
  1. package/build/alert/Alert.js +3 -1
  2. package/build/alert/Alert.js.map +1 -1
  3. package/build/alert/Alert.mjs +3 -1
  4. package/build/alert/Alert.mjs.map +1 -1
  5. package/build/dimmer/Dimmer.js +1 -3
  6. package/build/dimmer/Dimmer.js.map +1 -1
  7. package/build/dimmer/Dimmer.mjs +1 -3
  8. package/build/dimmer/Dimmer.mjs.map +1 -1
  9. package/build/drawer/Drawer.js +0 -2
  10. package/build/drawer/Drawer.js.map +1 -1
  11. package/build/drawer/Drawer.mjs +0 -2
  12. package/build/drawer/Drawer.mjs.map +1 -1
  13. package/build/field/Field.js +2 -0
  14. package/build/field/Field.js.map +1 -1
  15. package/build/field/Field.mjs +2 -0
  16. package/build/field/Field.mjs.map +1 -1
  17. package/build/i18n/en.json +5 -0
  18. package/build/i18n/en.json.js +5 -0
  19. package/build/i18n/en.json.js.map +1 -1
  20. package/build/i18n/en.json.mjs +5 -0
  21. package/build/i18n/en.json.mjs.map +1 -1
  22. package/build/inlineAlert/InlineAlert.js +3 -1
  23. package/build/inlineAlert/InlineAlert.js.map +1 -1
  24. package/build/inlineAlert/InlineAlert.mjs +3 -1
  25. package/build/inlineAlert/InlineAlert.mjs.map +1 -1
  26. package/build/modal/Modal.js +0 -3
  27. package/build/modal/Modal.js.map +1 -1
  28. package/build/modal/Modal.mjs +0 -3
  29. package/build/modal/Modal.mjs.map +1 -1
  30. package/build/statusIcon/StatusIcon.js +50 -16
  31. package/build/statusIcon/StatusIcon.js.map +1 -1
  32. package/build/statusIcon/StatusIcon.messages.js +24 -0
  33. package/build/statusIcon/StatusIcon.messages.js.map +1 -0
  34. package/build/statusIcon/StatusIcon.messages.mjs +22 -0
  35. package/build/statusIcon/StatusIcon.messages.mjs.map +1 -0
  36. package/build/statusIcon/StatusIcon.mjs +48 -14
  37. package/build/statusIcon/StatusIcon.mjs.map +1 -1
  38. package/build/types/alert/Alert.d.ts +6 -1
  39. package/build/types/alert/Alert.d.ts.map +1 -1
  40. package/build/types/dimmer/Dimmer.d.ts +1 -2
  41. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  42. package/build/types/drawer/Drawer.d.ts +1 -2
  43. package/build/types/drawer/Drawer.d.ts.map +1 -1
  44. package/build/types/field/Field.d.ts +6 -1
  45. package/build/types/field/Field.d.ts.map +1 -1
  46. package/build/types/inlineAlert/InlineAlert.d.ts +2 -1
  47. package/build/types/inlineAlert/InlineAlert.d.ts.map +1 -1
  48. package/build/types/modal/Modal.d.ts +1 -2
  49. package/build/types/modal/Modal.d.ts.map +1 -1
  50. package/build/types/statusIcon/StatusIcon.d.ts +6 -1
  51. package/build/types/statusIcon/StatusIcon.d.ts.map +1 -1
  52. package/build/types/statusIcon/StatusIcon.messages.d.ts +29 -0
  53. package/build/types/statusIcon/StatusIcon.messages.d.ts.map +1 -0
  54. package/build/types/upload/Upload.d.ts +5 -0
  55. package/build/types/upload/Upload.d.ts.map +1 -1
  56. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts +1 -0
  57. package/build/types/upload/steps/uploadImageStep/uploadImageStep.d.ts.map +1 -1
  58. package/build/types/uploadInput/UploadInput.d.ts +0 -9
  59. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  60. package/build/types/uploadInput/uploadItem/UploadItem.d.ts +1 -16
  61. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  62. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
  63. package/build/upload/Upload.js +4 -2
  64. package/build/upload/Upload.js.map +1 -1
  65. package/build/upload/Upload.mjs +4 -2
  66. package/build/upload/Upload.mjs.map +1 -1
  67. package/build/upload/steps/uploadImageStep/uploadImageStep.js +4 -2
  68. package/build/upload/steps/uploadImageStep/uploadImageStep.js.map +1 -1
  69. package/build/upload/steps/uploadImageStep/uploadImageStep.mjs +4 -2
  70. package/build/upload/steps/uploadImageStep/uploadImageStep.mjs.map +1 -1
  71. package/build/uploadInput/UploadInput.js +66 -71
  72. package/build/uploadInput/UploadInput.js.map +1 -1
  73. package/build/uploadInput/UploadInput.mjs +67 -72
  74. package/build/uploadInput/UploadInput.mjs.map +1 -1
  75. package/build/uploadInput/uploadItem/UploadItem.js +4 -13
  76. package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
  77. package/build/uploadInput/uploadItem/UploadItem.mjs +4 -13
  78. package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
  79. package/build/uploadInput/uploadItem/UploadItemLink.js +0 -1
  80. package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
  81. package/build/uploadInput/uploadItem/UploadItemLink.mjs +0 -1
  82. package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
  83. package/package.json +3 -3
  84. package/src/alert/Alert.spec.tsx +10 -0
  85. package/src/alert/Alert.tsx +7 -1
  86. package/src/dimmer/Dimmer.spec.js +0 -8
  87. package/src/dimmer/Dimmer.tsx +0 -4
  88. package/src/drawer/Drawer.spec.js +6 -25
  89. package/src/drawer/Drawer.tsx +1 -3
  90. package/src/field/Field.spec.tsx +19 -0
  91. package/src/field/Field.story.tsx +20 -4
  92. package/src/field/Field.tsx +7 -1
  93. package/src/i18n/en.json +5 -0
  94. package/src/inlineAlert/InlineAlert.spec.tsx +12 -1
  95. package/src/inlineAlert/InlineAlert.tsx +5 -1
  96. package/src/modal/Modal.spec.js +1 -19
  97. package/src/modal/Modal.tsx +0 -4
  98. package/src/statusIcon/StatusIcon.docs.mdx +22 -0
  99. package/src/statusIcon/StatusIcon.messages.ts +34 -0
  100. package/src/statusIcon/StatusIcon.spec.tsx +31 -4
  101. package/src/statusIcon/StatusIcon.story.tsx +2 -5
  102. package/src/statusIcon/StatusIcon.tsx +59 -14
  103. package/src/upload/Upload.spec.js +19 -0
  104. package/src/upload/Upload.tsx +7 -0
  105. package/src/upload/steps/uploadImageStep/uploadImageStep.spec.js +13 -0
  106. package/src/upload/steps/uploadImageStep/uploadImageStep.tsx +14 -3
  107. package/src/uploadInput/UploadInput.spec.tsx +9 -121
  108. package/src/uploadInput/UploadInput.tests.story.tsx +140 -209
  109. package/src/uploadInput/UploadInput.tsx +77 -110
  110. package/src/uploadInput/uploadItem/UploadItem.spec.tsx +0 -1
  111. package/src/uploadInput/uploadItem/UploadItem.tsx +6 -30
  112. 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
- updateFileList((list) => [...list, recentUploadedFile]);
184
- }
151
+ function addToList(listToAddTo: readonly UploadedFile[]) {
152
+ return [...listToAddTo, recentUploadedFile];
153
+ }
185
154
 
186
- function removeFileFromList(file: UploadedFile) {
187
- updateFileList((list) =>
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
- function modifyFileInList(file: UploadedFile, updates: Partial<UploadedFile>) {
194
- updateFileList((list) =>
195
- list.map((fileInList) =>
196
- fileInList === file || fileInList.id === file.id ? { ...file, ...updates } : 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 removeFile = async (file: UploadedFile) => {
182
+ const [fileToRemove, setFileToRemove] = useState<UploadedFile | null>(null);
183
+
184
+ const removeFile = (file: UploadedFile) => {
202
185
  const { id, status } = file;
203
- fileRefs = fileRefs.filter((item) => item && item.id !== file.id);
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
- return Promise.resolve();
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
- return onDeleteFile(id)
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: generateFileId(file),
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
- if (
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
- ? async () => {
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
- void removeFile(markedFileForDelete);
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
  }}
@@ -16,7 +16,6 @@ describe('UploadItem', () => {
16
16
  canDelete: true,
17
17
  onDelete: jest.fn(),
18
18
  singleFileUpload: true,
19
- onFocus: jest.fn(),
20
19
  };
21
20
 
22
21
  const renderComponent = (customProps = props) => render(<UploadItem {...customProps} />);
@@ -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, onFocus: handleFocus }, ref) => {
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={`${file.id}-${TEST_IDS.uploadItem}`}
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}