@transferwise/components 46.63.0 → 46.65.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/bottomSheet/BottomSheet.js +8 -2
- package/build/common/bottomSheet/BottomSheet.js.map +1 -1
- package/build/common/bottomSheet/BottomSheet.mjs +8 -2
- package/build/common/bottomSheet/BottomSheet.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/drawer/Drawer.js +5 -3
- package/build/drawer/Drawer.js.map +1 -1
- package/build/drawer/Drawer.mjs +5 -3
- package/build/drawer/Drawer.mjs.map +1 -1
- package/build/flowNavigation/FlowNavigation.js +1 -1
- package/build/flowNavigation/FlowNavigation.js.map +1 -1
- package/build/flowNavigation/FlowNavigation.mjs +1 -1
- package/build/flowNavigation/FlowNavigation.mjs.map +1 -1
- package/build/flowNavigation/animatedLabel/AnimatedLabel.js +89 -15
- package/build/flowNavigation/animatedLabel/AnimatedLabel.js.map +1 -1
- package/build/flowNavigation/animatedLabel/AnimatedLabel.mjs +90 -16
- package/build/flowNavigation/animatedLabel/AnimatedLabel.mjs.map +1 -1
- package/build/instructionsList/InstructionsList.js.map +1 -1
- package/build/instructionsList/InstructionsList.mjs.map +1 -1
- package/build/main.css +10 -1
- package/build/styles/flowNavigation/animatedLabel/AnimatedLabel.css +10 -1
- package/build/styles/main.css +10 -1
- package/build/switch/Switch.js +3 -27
- package/build/switch/Switch.js.map +1 -1
- package/build/switch/Switch.mjs +3 -27
- package/build/switch/Switch.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/common/bottomSheet/BottomSheet.d.ts +3 -3
- package/build/types/common/bottomSheet/BottomSheet.d.ts.map +1 -1
- package/build/types/drawer/Drawer.d.ts +4 -3
- package/build/types/drawer/Drawer.d.ts.map +1 -1
- package/build/types/flowNavigation/FlowNavigation.d.ts.map +1 -1
- package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts +3 -3
- package/build/types/flowNavigation/animatedLabel/AnimatedLabel.d.ts.map +1 -1
- package/build/types/instructionsList/InstructionsList.d.ts.map +1 -1
- package/build/types/switch/Switch.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 +3 -3
- 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/bottomSheet/BottomSheet.tsx +13 -4
- package/src/common/locale/index.ts +1 -1
- package/src/dateLookup/tableLink/TableLink.tsx +15 -15
- package/src/drawer/Drawer.tsx +7 -5
- package/src/flowNavigation/FlowNavigation.story.js +69 -17
- package/src/flowNavigation/FlowNavigation.tsx +1 -5
- package/src/flowNavigation/animatedLabel/AnimatedLabel.css +10 -1
- package/src/flowNavigation/animatedLabel/AnimatedLabel.less +10 -1
- package/src/flowNavigation/animatedLabel/AnimatedLabel.spec.js +64 -27
- package/src/flowNavigation/animatedLabel/AnimatedLabel.tsx +102 -20
- package/src/instructionsList/InstructionsList.tsx +1 -4
- package/src/main.css +10 -1
- package/src/switch/Switch.story.tsx +4 -7
- package/src/switch/Switch.tsx +1 -24
- package/src/switch/__snapshots__/Switch.spec.tsx.snap +2 -44
- 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
- package/src/flowNavigation/animatedLabel/__snapshots__/AnimatedLabel.spec.js.snap +0 -25
|
@@ -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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Bin, CheckCircleFill, CrossCircleFill } from '@transferwise/icons';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
|
+
import { forwardRef, useImperativeHandle, useRef } from 'react';
|
|
3
4
|
import { useIntl } from 'react-intl';
|
|
4
5
|
|
|
5
6
|
import Body from '../../body';
|
|
@@ -27,145 +28,166 @@ export type UploadItemProps = React.JSX.IntrinsicAttributes & {
|
|
|
27
28
|
* @param file
|
|
28
29
|
*/
|
|
29
30
|
onDownload?: (file: UploadedFile) => void;
|
|
31
|
+
ref?: React.Ref<UploadItemRef>;
|
|
30
32
|
};
|
|
31
33
|
|
|
34
|
+
interface UploadItemRef {
|
|
35
|
+
focus: () => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
32
38
|
export enum TEST_IDS {
|
|
33
39
|
uploadItem = 'uploadItem',
|
|
34
40
|
mediaBody = 'mediaBody',
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
const UploadItem = (
|
|
38
|
-
file,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
43
|
+
const UploadItem = forwardRef<UploadItemRef, UploadItemProps>(
|
|
44
|
+
({ file, canDelete, onDelete, onDownload, singleFileUpload }, ref) => {
|
|
45
|
+
const { formatMessage } = useIntl();
|
|
46
|
+
const { status, filename, error, errors, url } = file;
|
|
47
|
+
const linkRef = useRef<HTMLAnchorElement>(null);
|
|
48
|
+
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
49
|
+
|
|
50
|
+
useImperativeHandle<UploadItemRef, UploadItemRef>(ref, () => ({
|
|
51
|
+
focus: (): void => {
|
|
52
|
+
if (url) {
|
|
53
|
+
linkRef.current?.focus();
|
|
54
|
+
} else {
|
|
55
|
+
buttonRef.current?.focus();
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* We're temporarily reverting to the regular icon components,
|
|
64
|
+
* until the StatusIcon receives 24px sizing. Some misalignment
|
|
65
|
+
* to be expected.
|
|
66
|
+
*/
|
|
67
|
+
const getIcon = () => {
|
|
68
|
+
if (error || errors?.length || status === Status.FAILED) {
|
|
69
|
+
return <CrossCircleFill size={24} className="emphasis--negative" />;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let processIndicator: React.ReactNode;
|
|
73
|
+
|
|
74
|
+
switch (status) {
|
|
75
|
+
case Status.PROCESSING:
|
|
76
|
+
case Status.PENDING:
|
|
77
|
+
processIndicator = (
|
|
78
|
+
<ProcessIndicator size={Size.EXTRA_SMALL} status={Status.PROCESSING} />
|
|
79
|
+
);
|
|
80
|
+
break;
|
|
81
|
+
case Status.SUCCEEDED:
|
|
82
|
+
case Status.DONE:
|
|
83
|
+
default:
|
|
84
|
+
processIndicator = <CheckCircleFill size={24} className="emphasis--positive" />;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return processIndicator;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const getErrorMessage = (error?: UploadError) =>
|
|
91
|
+
typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);
|
|
92
|
+
|
|
93
|
+
const getMultipleErrors = (errors?: UploadError[]) => {
|
|
94
|
+
if (!errors?.length) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (errors?.length === 1) {
|
|
99
|
+
return getErrorMessage(errors[0]);
|
|
100
|
+
}
|
|
96
101
|
|
|
97
|
-
const getDescription = () => {
|
|
98
|
-
if (error || errors?.length || status === Status.FAILED) {
|
|
99
102
|
return (
|
|
100
|
-
<
|
|
101
|
-
{errors
|
|
102
|
-
|
|
103
|
+
<ul className="np-upload-input-errors m-b-0">
|
|
104
|
+
{errors.map((error, index) => {
|
|
105
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
106
|
+
return <li key={index}>{getErrorMessage(error)}</li>;
|
|
107
|
+
})}
|
|
108
|
+
</ul>
|
|
103
109
|
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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:
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const getDescription = () => {
|
|
113
|
+
if (error || errors?.length || status === Status.FAILED) {
|
|
114
114
|
return (
|
|
115
|
-
<Body type={Typography.BODY_DEFAULT_BOLD} className="text-
|
|
116
|
-
{
|
|
115
|
+
<Body type={Typography.BODY_DEFAULT_BOLD} className="text-negative">
|
|
116
|
+
{errors?.length ? getMultipleErrors(errors) : getErrorMessage(error)}
|
|
117
117
|
</Body>
|
|
118
118
|
);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
switch (status) {
|
|
122
|
+
case Status.PENDING:
|
|
123
|
+
return (
|
|
124
|
+
<Body type={Typography.BODY_DEFAULT_BOLD}>{formatMessage(MESSAGES.uploading)}</Body>
|
|
125
|
+
);
|
|
126
|
+
case Status.PROCESSING:
|
|
127
|
+
return <Body>{formatMessage(MESSAGES.deleting)}</Body>;
|
|
128
|
+
case Status.SUCCEEDED:
|
|
129
|
+
case Status.DONE:
|
|
130
|
+
default:
|
|
131
|
+
return (
|
|
132
|
+
<Body type={Typography.BODY_DEFAULT_BOLD} className="text-positive">
|
|
133
|
+
{formatMessage(MESSAGES.uploaded)}
|
|
134
|
+
</Body>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const getTitle = () => {
|
|
140
|
+
return filename || formatMessage(MESSAGES.uploadedFile);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const onDownloadFile = (event: React.MouseEvent): void => {
|
|
144
|
+
if (onDownload) {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
onDownload(file);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div
|
|
152
|
+
className={clsx('np-upload-item', { 'np-upload-item--link': isSucceeded })}
|
|
153
|
+
data-testid={TEST_IDS.uploadItem}
|
|
154
|
+
>
|
|
155
|
+
<div className="np-upload-item__body">
|
|
156
|
+
<UploadItemLink
|
|
157
|
+
ref={linkRef}
|
|
158
|
+
url={isSucceeded ? url : undefined}
|
|
159
|
+
singleFileUpload={singleFileUpload}
|
|
160
|
+
onDownload={onDownloadFile}
|
|
161
|
+
>
|
|
162
|
+
<div className="np-upload-button" aria-live="polite">
|
|
163
|
+
<div className="media">
|
|
164
|
+
<div className="np-upload-icon media-left">{getIcon()}</div>
|
|
165
|
+
<div className="media-body text-xs-left" data-testid={TEST_IDS.mediaBody}>
|
|
166
|
+
<Body className="text-word-break d-block text-primary">{getTitle()}</Body>
|
|
167
|
+
{getDescription()}
|
|
168
|
+
</div>
|
|
150
169
|
</div>
|
|
151
170
|
</div>
|
|
152
|
-
</
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
171
|
+
</UploadItemLink>
|
|
172
|
+
{canDelete && (
|
|
173
|
+
<button
|
|
174
|
+
ref={buttonRef}
|
|
175
|
+
aria-label={formatMessage(MESSAGES.removeFile, { filename })}
|
|
176
|
+
className={clsx('btn', 'np-upload-item__remove-button', 'media-left', {
|
|
177
|
+
'np-upload-item--single-file': singleFileUpload,
|
|
178
|
+
})}
|
|
179
|
+
type="button"
|
|
180
|
+
onClick={() => onDelete()}
|
|
181
|
+
>
|
|
182
|
+
<Bin size={16} />
|
|
183
|
+
</button>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
166
186
|
</div>
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
187
|
+
);
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
UploadItem.displayName = 'UploadItem';
|
|
170
192
|
|
|
171
193
|
export default UploadItem;
|