@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.
- package/build/inputs/SelectInput.js +86 -34
- package/build/inputs/SelectInput.js.map +1 -1
- package/build/inputs/SelectInput.mjs +88 -36
- package/build/inputs/SelectInput.mjs.map +1 -1
- package/build/main.css +10 -0
- package/build/styles/inputs/SelectInput.css +10 -0
- package/build/styles/main.css +10 -0
- package/build/types/inputs/SelectInput.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 +14 -9
- 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 +2 -25
- package/build/uploadInput/UploadInput.js.map +1 -1
- package/build/uploadInput/UploadInput.mjs +3 -26
- package/build/uploadInput/UploadInput.mjs.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.js +4 -6
- package/build/uploadInput/uploadButton/UploadButton.js.map +1 -1
- package/build/uploadInput/uploadButton/UploadButton.mjs +5 -7
- package/build/uploadInput/uploadButton/UploadButton.mjs.map +1 -1
- package/build/uploadInput/uploadItem/UploadItem.js +30 -22
- package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
- package/build/uploadInput/uploadItem/UploadItem.mjs +28 -22
- package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
- package/build/uploadInput/uploadItem/UploadItemLink.js +3 -5
- package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
- package/build/uploadInput/uploadItem/UploadItemLink.mjs +3 -5
- package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
- package/package.json +4 -3
- package/src/inputs/SelectInput.css +10 -0
- package/src/inputs/SelectInput.less +12 -0
- package/src/inputs/SelectInput.story.tsx +20 -0
- package/src/inputs/SelectInput.tsx +116 -37
- package/src/main.css +10 -0
- package/src/uploadInput/UploadInput.spec.tsx +3 -4
- package/src/uploadInput/UploadInput.story.tsx +2 -2
- package/src/uploadInput/UploadInput.tsx +2 -28
- package/src/uploadInput/uploadButton/UploadButton.tsx +151 -146
- package/src/uploadInput/uploadItem/UploadItem.tsx +141 -122
- 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,
|
|
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 =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
return fileTypes;
|
|
137
|
-
}
|
|
138
|
-
return getAllowedFileTypes(Array.isArray(fileTypes) ? fileTypes : [fileTypes]).join(', ');
|
|
139
|
-
};
|
|
123
|
+
if (files) {
|
|
124
|
+
onChange(files);
|
|
140
125
|
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
</
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
<
|
|
63
|
-
{errors
|
|
64
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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-
|
|
74
|
-
{
|
|
115
|
+
<Body type={Typography.BODY_DEFAULT_BOLD} className="text-positive">
|
|
116
|
+
{formatMessage(MESSAGES.uploaded)}
|
|
75
117
|
</Body>
|
|
76
118
|
);
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
</
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
+
};
|