@transferwise/components 46.63.0 → 46.64.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/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/instructionsList/InstructionsList.js.map +1 -1
- package/build/instructionsList/InstructionsList.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/instructionsList/InstructionsList.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 +1 -1
- 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/locale/index.ts +1 -1
- package/src/dateLookup/tableLink/TableLink.tsx +15 -15
- package/src/instructionsList/InstructionsList.tsx +1 -4
- 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
|
@@ -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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PropsWithChildren, MouseEvent } from 'react';
|
|
1
|
+
import { PropsWithChildren, MouseEvent, forwardRef } from 'react';
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
|
|
4
4
|
type UploadItemLinkProps = PropsWithChildren<{
|
|
@@ -7,28 +7,26 @@ type UploadItemLinkProps = PropsWithChildren<{
|
|
|
7
7
|
singleFileUpload: boolean;
|
|
8
8
|
}>;
|
|
9
9
|
|
|
10
|
-
export const UploadItemLink = (
|
|
11
|
-
children,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}: UploadItemLinkProps) => {
|
|
16
|
-
if (!url) {
|
|
17
|
-
return <div>{children}</div>;
|
|
18
|
-
}
|
|
10
|
+
export const UploadItemLink = forwardRef<HTMLAnchorElement | HTMLDivElement, UploadItemLinkProps>(
|
|
11
|
+
({ children, url, onDownload, singleFileUpload }, ref) => {
|
|
12
|
+
if (!url) {
|
|
13
|
+
return <div ref={ref as React.RefObject<HTMLDivElement>}>{children}</div>;
|
|
14
|
+
}
|
|
19
15
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
return (
|
|
17
|
+
<a
|
|
18
|
+
ref={ref as React.RefObject<HTMLAnchorElement>}
|
|
19
|
+
href={url}
|
|
20
|
+
target="_blank"
|
|
21
|
+
rel="noopener noreferrer"
|
|
22
|
+
className={clsx(
|
|
23
|
+
'np-upload-item__link',
|
|
24
|
+
singleFileUpload ? 'np-upload-item--single-file' : '',
|
|
25
|
+
)}
|
|
26
|
+
onClick={onDownload}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</a>
|
|
30
|
+
);
|
|
31
|
+
},
|
|
32
|
+
);
|