@transferwise/components 0.0.0-experimental-91bc693 → 0.0.0-experimental-c023984

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 (43) hide show
  1. package/build/inputs/SelectInput.js +86 -34
  2. package/build/inputs/SelectInput.js.map +1 -1
  3. package/build/inputs/SelectInput.mjs +88 -36
  4. package/build/inputs/SelectInput.mjs.map +1 -1
  5. package/build/main.css +10 -0
  6. package/build/styles/inputs/SelectInput.css +10 -0
  7. package/build/styles/main.css +10 -0
  8. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  9. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  10. package/build/types/uploadInput/uploadButton/UploadButton.d.ts +1 -1
  11. package/build/types/uploadInput/uploadButton/UploadButton.d.ts.map +1 -1
  12. package/build/types/uploadInput/uploadItem/UploadItem.d.ts +14 -9
  13. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  14. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts +5 -5
  15. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
  16. package/build/uploadInput/UploadInput.js +2 -25
  17. package/build/uploadInput/UploadInput.js.map +1 -1
  18. package/build/uploadInput/UploadInput.mjs +3 -26
  19. package/build/uploadInput/UploadInput.mjs.map +1 -1
  20. package/build/uploadInput/uploadButton/UploadButton.js +4 -6
  21. package/build/uploadInput/uploadButton/UploadButton.js.map +1 -1
  22. package/build/uploadInput/uploadButton/UploadButton.mjs +5 -7
  23. package/build/uploadInput/uploadButton/UploadButton.mjs.map +1 -1
  24. package/build/uploadInput/uploadItem/UploadItem.js +30 -22
  25. package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
  26. package/build/uploadInput/uploadItem/UploadItem.mjs +28 -22
  27. package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
  28. package/build/uploadInput/uploadItem/UploadItemLink.js +3 -5
  29. package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
  30. package/build/uploadInput/uploadItem/UploadItemLink.mjs +3 -5
  31. package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
  32. package/package.json +4 -3
  33. package/src/inputs/SelectInput.css +10 -0
  34. package/src/inputs/SelectInput.less +12 -0
  35. package/src/inputs/SelectInput.story.tsx +20 -0
  36. package/src/inputs/SelectInput.tsx +116 -37
  37. package/src/main.css +10 -0
  38. package/src/uploadInput/UploadInput.spec.tsx +3 -4
  39. package/src/uploadInput/UploadInput.story.tsx +2 -2
  40. package/src/uploadInput/UploadInput.tsx +2 -28
  41. package/src/uploadInput/uploadButton/UploadButton.tsx +151 -146
  42. package/src/uploadInput/uploadItem/UploadItem.tsx +141 -122
  43. package/src/uploadInput/uploadItem/UploadItemLink.tsx +25 -23
@@ -1,6 +1,6 @@
1
1
  import { PlusCircle as PlusIcon, Upload as UploadIcon } from '@transferwise/icons';
2
2
  import classNames from 'classnames';
3
- import { ChangeEvent, DragEvent, forwardRef, useRef, useState } from 'react';
3
+ import { ChangeEvent, DragEvent, useRef, useState } from 'react';
4
4
  import { useIntl } from 'react-intl';
5
5
 
6
6
  import Body from '../../body';
@@ -70,168 +70,173 @@ const onDragOver = (event: DragEvent): void => {
70
70
  };
71
71
 
72
72
  const DEFAULT_FILE_INPUT_ID = 'np-upload-button';
73
- const UploadButton = forwardRef<HTMLInputElement, UploadButtonProps>(
74
- (
75
- {
76
- disabled,
77
- multiple,
78
- description,
79
- fileTypes = imageFileTypes,
80
- sizeLimit = DEFAULT_SIZE_LIMIT,
81
- maxFiles,
82
- onChange,
83
- id = DEFAULT_FILE_INPUT_ID,
84
- uploadButtonTitle,
85
- },
86
- ref,
87
- ) => {
88
- const { formatMessage } = useIntl();
89
- const buttonRef = useRef<HTMLLabelElement>(null);
90
-
91
- const inputReference = useRef<HTMLInputElement>(null);
92
- const [isDropping, setIsDropping] = useState(false);
93
- const dragCounter = useRef(0);
94
-
95
- const reset = (): void => {
96
- dragCounter.current = 0;
73
+ const UploadButton = ({
74
+ disabled,
75
+ multiple,
76
+ description,
77
+ fileTypes = imageFileTypes,
78
+ sizeLimit = DEFAULT_SIZE_LIMIT,
79
+ maxFiles,
80
+ onChange,
81
+ id = DEFAULT_FILE_INPUT_ID,
82
+ uploadButtonTitle,
83
+ }: UploadButtonProps) => {
84
+ const { formatMessage } = useIntl();
85
+ const inputReference = useRef<HTMLInputElement>(null);
86
+
87
+ const [isDropping, setIsDropping] = useState(false);
88
+
89
+ const dragCounter = useRef(0);
90
+
91
+ const reset = (): void => {
92
+ dragCounter.current = 0;
93
+ setIsDropping(false);
94
+ };
95
+
96
+ const onDragLeave = (event: DragEvent): void => {
97
+ event.preventDefault();
98
+ dragCounter.current -= 1;
99
+ if (dragCounter.current === 0) {
97
100
  setIsDropping(false);
98
- };
101
+ }
102
+ };
99
103
 
100
- const onDragLeave = (event: DragEvent): void => {
101
- event.preventDefault();
102
- dragCounter.current -= 1;
103
- if (dragCounter.current === 0) {
104
- setIsDropping(false);
105
- }
106
- };
104
+ const onDragEnter = (event: DragEvent): void => {
105
+ event.preventDefault();
106
+ dragCounter.current += 1;
107
+ if (dragCounter.current === 1) {
108
+ setIsDropping(true);
109
+ }
110
+ };
107
111
 
108
- const onDragEnter = (event: DragEvent): void => {
109
- event.preventDefault();
110
- dragCounter.current += 1;
111
- if (dragCounter.current === 1) {
112
- setIsDropping(true);
113
- }
114
- };
112
+ const onDrop = (event: DragEvent): void => {
113
+ event.preventDefault();
114
+ reset();
115
+ if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
116
+ onChange(event.dataTransfer.files);
117
+ }
118
+ };
115
119
 
116
- const onDrop = (event: DragEvent): void => {
117
- event.preventDefault();
118
- reset();
119
- if (event.dataTransfer && event.dataTransfer.files && event.dataTransfer.files[0]) {
120
- onChange(event.dataTransfer.files);
121
- }
122
- };
123
-
124
- const filesSelected = (event: ChangeEvent<HTMLInputElement>): void => {
125
- const { files } = event.target;
126
- if (files) {
127
- onChange(files);
128
- if (inputReference.current) {
129
- inputReference.current.value = '';
130
- }
131
- }
132
- };
120
+ const filesSelected = (event: ChangeEvent<HTMLInputElement>): void => {
121
+ const { files } = event.target;
133
122
 
134
- const getFileTypesDescription = (): string => {
135
- if (fileTypes === '*') {
136
- return fileTypes;
137
- }
138
- return getAllowedFileTypes(Array.isArray(fileTypes) ? fileTypes : [fileTypes]).join(', ');
139
- };
123
+ if (files) {
124
+ onChange(files);
140
125
 
141
- function getDescription() {
142
- if (description) {
143
- return description;
126
+ if (inputReference.current) {
127
+ inputReference.current.value = '';
144
128
  }
145
- const fileTypesDescription = getFileTypesDescription();
146
- const derivedFileDescription =
147
- fileTypesDescription === '*' ? formatMessage(MESSAGES.allFileTypes) : fileTypesDescription;
148
-
149
- return formatMessage(MESSAGES.instructions, {
150
- fileTypes: derivedFileDescription,
151
- size: Math.round(sizeLimit / 1000),
152
- });
153
129
  }
130
+ };
154
131
 
155
- function getAcceptedTypes(): Pick<React.ComponentPropsWithoutRef<'input'>, 'accept'> {
156
- const areAllFilesAllowed = getFileTypesDescription() === '*';
157
- if (areAllFilesAllowed) {
158
- return {};
159
- }
160
- if (Array.isArray(fileTypes)) {
161
- return { accept: fileTypes.join(',') };
162
- }
163
- return { accept: fileTypes as string };
132
+ const getFileTypesDescription = (): string => {
133
+ if (fileTypes === '*') {
134
+ return fileTypes;
164
135
  }
165
136
 
166
- function renderDescription() {
167
- return (
168
- <Body className={classNames({ 'text-primary': !disabled })}>
169
- {getDescription()}
170
- {maxFiles && (
171
- <>
172
- <br />
173
- {`Maximum ${maxFiles} files.`}
174
- </>
175
- )}
176
- </Body>
177
- );
137
+ return getAllowedFileTypes(Array.isArray(fileTypes) ? fileTypes : [fileTypes]).join(', ');
138
+ };
139
+
140
+ function getDescription() {
141
+ if (description) {
142
+ return description;
178
143
  }
179
144
 
180
- function renderButtonTitle() {
181
- if (uploadButtonTitle) {
182
- return uploadButtonTitle;
183
- }
184
- return formatMessage(multiple ? MESSAGES.uploadFiles : MESSAGES.uploadFile);
145
+ const fileTypesDescription = getFileTypesDescription();
146
+
147
+ const derivedFileDescription =
148
+ fileTypesDescription === '*' ? formatMessage(MESSAGES.allFileTypes) : fileTypesDescription;
149
+
150
+ return formatMessage(MESSAGES.instructions, {
151
+ fileTypes: derivedFileDescription,
152
+ size: Math.round(sizeLimit / 1000),
153
+ });
154
+ }
155
+
156
+ function getAcceptedTypes(): Pick<React.ComponentPropsWithoutRef<'input'>, 'accept'> {
157
+ const areAllFilesAllowed = getFileTypesDescription() === '*';
158
+
159
+ if (areAllFilesAllowed) {
160
+ return {}; // file input by default allows all files
185
161
  }
186
162
 
163
+ if (Array.isArray(fileTypes)) {
164
+ return { accept: fileTypes.join(',') };
165
+ }
166
+
167
+ return { accept: fileTypes as string };
168
+ }
169
+
170
+ function renderDescription() {
187
171
  return (
188
- <div
189
- className={classNames('np-upload-button-container', 'droppable', {
190
- 'droppable-dropping': isDropping,
191
- })}
192
- {...(!disabled && { onDragEnter, onDragLeave, onDrop, onDragOver })}
193
- >
194
- <input
195
- ref={inputReference}
196
- id={id}
197
- type="file"
198
- {...getAcceptedTypes()}
199
- {...(multiple && { multiple: true })}
200
- className="tw-droppable-input"
201
- disabled={disabled}
202
- name="file-upload"
203
- data-testid={TEST_IDS.uploadInput}
204
- onChange={filesSelected}
205
- />
206
- {/* eslint-disable-next-line jsx-a11y/label-has-associated-control */}
207
- <label ref={buttonRef} htmlFor={id} className={classNames('btn', 'np-upload-button')}>
208
- <div className="media">
209
- <div className="np-upload-icon media-middle media-left">
210
- <UploadIcon size={24} className="text-link" />
211
- </div>
212
- <div className="media-body text-xs-left" data-testid={TEST_IDS.mediaBody}>
213
- <Body type={Typography.BODY_LARGE_BOLD} className="d-block">
214
- {renderButtonTitle()}
215
- </Body>
216
- {renderDescription()}
217
- </div>
218
- </div>
219
- </label>
220
- {isDropping && (
221
- <div
222
- className={classNames(
223
- 'droppable-card',
224
- 'droppable-dropping-card',
225
- 'droppable-card-content',
226
- )}
227
- >
228
- <PlusIcon className="m-x-1" size={24} />
229
- <div>{formatMessage(MESSAGES.dropFile)}</div>
230
- </div>
172
+ <Body className={classNames({ 'text-primary': !disabled })}>
173
+ {getDescription()}
174
+ {maxFiles && (
175
+ <>
176
+ <br />
177
+ {`Maximum ${maxFiles} files.`}
178
+ </>
231
179
  )}
232
- </div>
180
+ </Body>
233
181
  );
234
- },
235
- );
182
+ }
183
+
184
+ function renderButtonTitle() {
185
+ if (uploadButtonTitle) {
186
+ return uploadButtonTitle;
187
+ }
188
+ return formatMessage(multiple ? MESSAGES.uploadFiles : MESSAGES.uploadFile);
189
+ }
190
+
191
+ return (
192
+ <div
193
+ className={classNames('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={classNames('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={classNames(
229
+ 'droppable-card',
230
+ 'droppable-dropping-card',
231
+ 'droppable-card-content',
232
+ )}
233
+ >
234
+ <PlusIcon className="m-x-1" size={24} />
235
+ <div>{formatMessage(MESSAGES.dropFile)}</div>
236
+ </div>
237
+ )}
238
+ </div>
239
+ );
240
+ };
236
241
 
237
242
  export default UploadButton;
@@ -1,152 +1,171 @@
1
1
  import { Bin, CheckCircleFill, CrossCircleFill } from '@transferwise/icons';
2
2
  import classNames from 'classnames';
3
3
  import { useIntl } from 'react-intl';
4
+
4
5
  import Body from '../../body';
5
- import { Size, Status, Typography } from '../../common';
6
+ import { Size, Status, Typography, Sentiment } from '../../common';
6
7
  import ProcessIndicator from '../../processIndicator/ProcessIndicator';
8
+ import StatusIcon from '../../statusIcon/StatusIcon';
7
9
  import { UploadedFile, UploadError } from '../types';
10
+
8
11
  import MESSAGES from './UploadItem.messages';
9
12
  import { UploadItemLink } from './UploadItemLink';
10
- import { forwardRef } from 'react';
11
13
 
12
14
  export type UploadItemProps = JSX.IntrinsicAttributes & {
13
15
  file: UploadedFile;
16
+ /**
17
+ * Is this Item part of a multiple- or single-file UploadInput
18
+ */
14
19
  singleFileUpload: boolean;
15
20
  canDelete: boolean;
16
21
  onDelete: () => void;
22
+
23
+ /**
24
+ * Callback to be called when the file link is clicked.
25
+ * When provided, you need to manually trigger actions to load/download the file.
26
+ *
27
+ * @param file
28
+ */
17
29
  onDownload?: (file: UploadedFile) => void;
18
- testid?: string;
19
30
  };
20
31
 
21
- const UploadItem = forwardRef<HTMLAnchorElement | HTMLButtonElement, UploadItemProps>(
22
- ({ file, canDelete, onDelete, onDownload, singleFileUpload, testid }, ref) => {
23
- const { formatMessage } = useIntl();
24
- const { status, filename, error, errors, url } = file;
25
- const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;
26
-
27
- const getIcon = () => {
28
- if (error || errors?.length || status === Status.FAILED) {
29
- return <CrossCircleFill size={24} className="emphasis--negative" />;
30
- }
31
-
32
- let processIndicator: React.ReactNode;
33
-
34
- switch (status) {
35
- case Status.PROCESSING:
36
- case Status.PENDING:
37
- processIndicator = (
38
- <ProcessIndicator size={Size.EXTRA_SMALL} status={Status.PROCESSING} />
39
- );
40
- break;
41
- case Status.SUCCEEDED:
42
- case Status.DONE:
43
- default:
44
- processIndicator = <CheckCircleFill size={24} className="emphasis--positive" />;
45
- return processIndicator;
46
- }
47
- };
48
-
49
- const getErrorMessage = (error?: UploadError) =>
50
- typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);
51
-
52
- const getMultipleErrors = (errors?: UploadError[]) => {
53
- if (!errors?.length) {
54
- return null;
55
- }
56
-
57
- if (errors?.length === 1) {
58
- return getErrorMessage(errors[0]);
59
- }
32
+ export enum TEST_IDS {
33
+ uploadItem = 'uploadItem',
34
+ mediaBody = 'mediaBody',
35
+ }
36
+
37
+ const UploadItem = ({
38
+ file,
39
+ canDelete,
40
+ onDelete,
41
+ onDownload,
42
+ singleFileUpload,
43
+ }: UploadItemProps) => {
44
+ const { formatMessage } = useIntl();
45
+ const { status, filename, error, errors, url } = file;
46
+
47
+ const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;
48
+
49
+ /**
50
+ * We're temporarily reverting to the regular icon components,
51
+ * until the StatusIcon receives 24px sizing. Some misalignment
52
+ * to be expected.
53
+ */
54
+ const getIcon = () => {
55
+ if (error || errors?.length || status === Status.FAILED) {
56
+ return <CrossCircleFill size={24} className="emphasis--negative" />;
57
+ }
58
+
59
+ let processIndicator: React.ReactNode;
60
+
61
+ switch (status) {
62
+ case Status.PROCESSING:
63
+ case Status.PENDING:
64
+ processIndicator = <ProcessIndicator size={Size.EXTRA_SMALL} status={Status.PROCESSING} />;
65
+ break;
66
+ case Status.SUCCEEDED:
67
+ case Status.DONE:
68
+ default:
69
+ processIndicator = <CheckCircleFill size={24} className="emphasis--positive" />;
70
+ }
71
+
72
+ return processIndicator;
73
+ };
74
+
75
+ const getErrorMessage = (error?: UploadError) =>
76
+ typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);
77
+
78
+ const getMultipleErrors = (errors?: UploadError[]) => {
79
+ if (!errors?.length) {
80
+ return null;
81
+ }
60
82
 
83
+ if (errors?.length === 1) {
84
+ return getErrorMessage(errors[0]);
85
+ }
86
+
87
+ return (
88
+ <ul className="np-upload-input-errors m-b-0">
89
+ {errors.map((error, index) => {
90
+ // eslint-disable-next-line react/no-array-index-key
91
+ return <li key={index}>{getErrorMessage(error)}</li>;
92
+ })}
93
+ </ul>
94
+ );
95
+ };
96
+
97
+ const getDescription = () => {
98
+ if (error || errors?.length || status === Status.FAILED) {
61
99
  return (
62
- <ul className="np-upload-input-errors m-b-0">
63
- {errors.map((error, index) => (
64
- <li key={index}>{getErrorMessage(error)}</li>
65
- ))}
66
- </ul>
100
+ <Body type={Typography.BODY_DEFAULT_BOLD} className="text-negative">
101
+ {errors?.length ? getMultipleErrors(errors) : getErrorMessage(error)}
102
+ </Body>
67
103
  );
68
- };
104
+ }
69
105
 
70
- const getDescription = () => {
71
- if (error || errors?.length || status === Status.FAILED) {
106
+ switch (status) {
107
+ case Status.PENDING:
108
+ return <Body type={Typography.BODY_DEFAULT_BOLD}>{formatMessage(MESSAGES.uploading)}</Body>;
109
+ case Status.PROCESSING:
110
+ return <Body>{formatMessage(MESSAGES.deleting)}</Body>;
111
+ case Status.SUCCEEDED:
112
+ case Status.DONE:
113
+ default:
72
114
  return (
73
- <Body type={Typography.BODY_DEFAULT_BOLD} className="text-negative">
74
- {errors?.length ? getMultipleErrors(errors) : getErrorMessage(error)}
115
+ <Body type={Typography.BODY_DEFAULT_BOLD} className="text-positive">
116
+ {formatMessage(MESSAGES.uploaded)}
75
117
  </Body>
76
118
  );
77
- }
78
- switch (status) {
79
- case Status.PENDING:
80
- return (
81
- <Body type={Typography.BODY_DEFAULT_BOLD}>{formatMessage(MESSAGES.uploading)}</Body>
82
- );
83
- case Status.PROCESSING:
84
- return <Body>{formatMessage(MESSAGES.deleting)}</Body>;
85
- case Status.SUCCEEDED:
86
- case Status.DONE:
87
- default:
88
- return (
89
- <Body type={Typography.BODY_DEFAULT_BOLD} className="text-positive">
90
- {formatMessage(MESSAGES.uploaded)}
91
- </Body>
92
- );
93
- }
94
- };
95
-
96
- const getTitle = () => filename || formatMessage(MESSAGES.uploadedFile);
97
-
98
- const onDownloadFile = (event: React.MouseEvent): void => {
99
- if (onDownload) {
100
- event.preventDefault();
101
- onDownload(file);
102
- }
103
- };
104
-
105
- const handleDelete = () => {
106
- onDelete();
107
- const uploadButtonElement = document.querySelector<HTMLLabelElement>('.np-upload-button');
108
- uploadButtonElement?.focus();
109
- };
119
+ }
120
+ };
110
121
 
111
- return (
112
- <div
113
- className={classNames('np-upload-item', { 'np-upload-item--link': isSucceeded })}
114
- data-testid={`upload-item-${testid}`}
115
- >
116
- <div className="np-upload-item__body">
117
- <UploadItemLink
118
- ref={isSucceeded ? (ref as React.Ref<HTMLAnchorElement>) : undefined}
119
- url={isSucceeded ? url : undefined}
120
- singleFileUpload={singleFileUpload}
121
- onDownload={onDownloadFile}
122
- >
123
- <div className="np-upload-button" aria-live="polite">
124
- <div className="media">
125
- <div className="np-upload-icon media-left">{getIcon()}</div>
126
- <div className="media-body text-xs-left" data-testid="mediaBody">
127
- <Body className="text-word-break d-block text-primary">{getTitle()}</Body>
128
- {getDescription()}
129
- </div>
122
+ const getTitle = () => {
123
+ return filename || formatMessage(MESSAGES.uploadedFile);
124
+ };
125
+
126
+ const onDownloadFile = (event: React.MouseEvent): void => {
127
+ if (onDownload) {
128
+ event.preventDefault();
129
+ onDownload(file);
130
+ }
131
+ };
132
+
133
+ return (
134
+ <div
135
+ className={classNames('np-upload-item', { 'np-upload-item--link': isSucceeded })}
136
+ data-testid={TEST_IDS.uploadItem}
137
+ >
138
+ <div className="np-upload-item__body">
139
+ <UploadItemLink
140
+ url={isSucceeded ? url : undefined}
141
+ singleFileUpload={singleFileUpload}
142
+ onDownload={onDownloadFile}
143
+ >
144
+ <div className="np-upload-button" aria-live="polite">
145
+ <div className="media">
146
+ <div className="np-upload-icon media-left">{getIcon()}</div>
147
+ <div className="media-body text-xs-left" data-testid={TEST_IDS.mediaBody}>
148
+ <Body className="text-word-break d-block text-primary">{getTitle()}</Body>
149
+ {getDescription()}
130
150
  </div>
131
151
  </div>
132
- </UploadItemLink>
133
- {canDelete && (
134
- <button
135
- ref={isSucceeded ? undefined : (ref as React.Ref<HTMLButtonElement>)}
136
- aria-label={formatMessage(MESSAGES.removeFile, { filename })}
137
- className={classNames('btn', 'np-upload-item__remove-button', 'media-left', {
138
- 'np-upload-item--single-file': singleFileUpload,
139
- })}
140
- type="button"
141
- onClick={handleDelete}
142
- >
143
- <Bin size={16} />
144
- </button>
145
- )}
146
- </div>
152
+ </div>
153
+ </UploadItemLink>
154
+ {canDelete && (
155
+ <button
156
+ aria-label={formatMessage(MESSAGES.removeFile, { filename })}
157
+ className={classNames('btn', 'np-upload-item__remove-button', 'media-left', {
158
+ 'np-upload-item--single-file': singleFileUpload,
159
+ })}
160
+ type="button"
161
+ onClick={() => onDelete()}
162
+ >
163
+ <Bin size={16} />
164
+ </button>
165
+ )}
147
166
  </div>
148
- );
149
- },
150
- );
167
+ </div>
168
+ );
169
+ };
151
170
 
152
171
  export default UploadItem;
@@ -1,5 +1,5 @@
1
+ import { PropsWithChildren, MouseEvent } from 'react';
1
2
  import classnames from 'classnames';
2
- import { PropsWithChildren, MouseEvent, forwardRef } from 'react';
3
3
 
4
4
  type UploadItemLinkProps = PropsWithChildren<{
5
5
  url?: string;
@@ -7,26 +7,28 @@ type UploadItemLinkProps = PropsWithChildren<{
7
7
  singleFileUpload: boolean;
8
8
  }>;
9
9
 
10
- export const UploadItemLink = forwardRef<HTMLDivElement | HTMLAnchorElement, UploadItemLinkProps>(
11
- ({ children, url, onDownload, singleFileUpload }, ref) => {
12
- if (!url) {
13
- return <div>{children}</div>;
14
- }
10
+ export const UploadItemLink = ({
11
+ children,
12
+ url,
13
+ onDownload,
14
+ singleFileUpload,
15
+ }: UploadItemLinkProps) => {
16
+ if (!url) {
17
+ return <div>{children}</div>;
18
+ }
15
19
 
16
- return (
17
- <a
18
- ref={ref as React.Ref<HTMLAnchorElement>}
19
- href={url}
20
- target="_blank"
21
- rel="noopener noreferrer"
22
- className={classnames(
23
- 'np-upload-item__link',
24
- singleFileUpload ? 'np-upload-item--single-file' : '',
25
- )}
26
- onClick={onDownload}
27
- >
28
- {children}
29
- </a>
30
- );
31
- },
32
- );
20
+ return (
21
+ <a
22
+ href={url}
23
+ target="_blank"
24
+ rel="noopener noreferrer"
25
+ className={classnames(
26
+ 'np-upload-item__link',
27
+ singleFileUpload ? 'np-upload-item--single-file' : '',
28
+ )}
29
+ onClick={onDownload}
30
+ >
31
+ {children}
32
+ </a>
33
+ );
34
+ };