@transferwise/components 46.62.1 → 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/checkbox/Checkbox.js +1 -1
- package/build/checkbox/Checkbox.js.map +1 -1
- package/build/checkbox/Checkbox.mjs +1 -1
- package/build/checkbox/Checkbox.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/radio/Radio.js +2 -9
- package/build/radio/Radio.js.map +1 -1
- package/build/radio/Radio.mjs +2 -9
- package/build/radio/Radio.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/radio/Radio.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 +2 -2
- 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/checkbox/Checkbox.tsx +1 -1
- package/src/checkboxButton/CheckboxButton.story.tsx +18 -21
- 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/radio/Radio.tsx +3 -9
- package/src/radio/__snapshots__/Radio.rtl.spec.tsx.snap +1 -1
- package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +8 -3
- 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
|
@@ -2,13 +2,14 @@ import { action } from '@storybook/addon-actions';
|
|
|
2
2
|
import { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
|
|
5
|
-
import CheckboxButton
|
|
5
|
+
import CheckboxButton from './CheckboxButton';
|
|
6
6
|
|
|
7
7
|
export default {
|
|
8
8
|
component: CheckboxButton,
|
|
9
9
|
title: 'Actions/CheckboxButton',
|
|
10
10
|
tags: ['autodocs'],
|
|
11
11
|
args: {
|
|
12
|
+
disabled: false,
|
|
12
13
|
onBlur: action('blur'),
|
|
13
14
|
onClick: action('click'),
|
|
14
15
|
onFocus: action('focus'),
|
|
@@ -20,7 +21,6 @@ type Story = StoryObj<typeof CheckboxButton>;
|
|
|
20
21
|
export const Basic: Story = {
|
|
21
22
|
args: {
|
|
22
23
|
'aria-label': 'Toggle email updates',
|
|
23
|
-
disabled: false,
|
|
24
24
|
},
|
|
25
25
|
render: (args) => {
|
|
26
26
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -30,25 +30,22 @@ export const Basic: Story = {
|
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
const [indeterminate, setIndeterminate] = useState(true);
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<CheckboxButton
|
|
39
|
-
{...props}
|
|
40
|
-
checked={checked}
|
|
41
|
-
indeterminate={indeterminate}
|
|
42
|
-
onChange={() => setIndeterminate(!indeterminate)}
|
|
43
|
-
/>
|
|
44
|
-
);
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const Indeterminate = () => {
|
|
48
|
-
const args = {
|
|
33
|
+
export const Indeterminate: Story = {
|
|
34
|
+
args: {
|
|
49
35
|
'aria-label': 'Group checkbox with indeterminate state',
|
|
50
|
-
disabled: false,
|
|
51
36
|
indeterminate: true,
|
|
52
|
-
}
|
|
53
|
-
|
|
37
|
+
},
|
|
38
|
+
render: (args) => {
|
|
39
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
40
|
+
const [indeterminate, setIndeterminate] = useState(true);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<CheckboxButton
|
|
44
|
+
{...args}
|
|
45
|
+
checked
|
|
46
|
+
indeterminate={indeterminate}
|
|
47
|
+
onChange={() => setIndeterminate(!indeterminate)}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
},
|
|
54
51
|
};
|
|
@@ -3,7 +3,7 @@ import { cloneElement } from 'react';
|
|
|
3
3
|
|
|
4
4
|
import Body from '../body/Body';
|
|
5
5
|
import { typeClassMap, priorityClassMap } from '../button/classMap';
|
|
6
|
-
import { ControlType, Priority
|
|
6
|
+
import { ControlType, Priority, Typography } from '../common';
|
|
7
7
|
|
|
8
8
|
export interface CircularButtonProps {
|
|
9
9
|
className?: string;
|
|
@@ -79,7 +79,7 @@ export function getLangFromLocale(locale: string) {
|
|
|
79
79
|
*/
|
|
80
80
|
export function getCountryFromLocale(locale: string) {
|
|
81
81
|
const adjustedLocale = adjustLocale(locale);
|
|
82
|
-
return adjustedLocale != null ? new Intl.Locale(adjustedLocale).region ?? null : null;
|
|
82
|
+
return adjustedLocale != null ? (new Intl.Locale(adjustedLocale).region ?? null) : null;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
/**
|
|
@@ -56,21 +56,21 @@ const TableLink = ({
|
|
|
56
56
|
|
|
57
57
|
return (
|
|
58
58
|
<button
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
ref={buttonRef}
|
|
60
|
+
type="button"
|
|
61
|
+
className={clsx(
|
|
62
|
+
`tw-date-lookup-${type}-option np-text-body-default-bold`,
|
|
63
|
+
{ active: !!active },
|
|
64
|
+
{ today: !!today },
|
|
65
|
+
)}
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
tabIndex={autofocus ? 0 : -1}
|
|
68
|
+
aria-label={calculateAriaLabel()}
|
|
69
|
+
aria-pressed={active}
|
|
70
|
+
onClick={onCalendarClick}
|
|
71
|
+
>
|
|
72
|
+
{title || item}
|
|
73
|
+
</button>
|
|
74
74
|
);
|
|
75
75
|
};
|
|
76
76
|
|
|
@@ -57,10 +57,7 @@ function Instruction({ item, type }: { item: ReactNode | InstructionNode; type:
|
|
|
57
57
|
const isInstructionNode =
|
|
58
58
|
typeof item === 'object' && item !== null && 'content' in item && 'aria-label' in item;
|
|
59
59
|
return (
|
|
60
|
-
<li
|
|
61
|
-
className="instruction"
|
|
62
|
-
aria-label={isInstructionNode ? (item['aria-label']) : undefined}
|
|
63
|
-
>
|
|
60
|
+
<li className="instruction" aria-label={isInstructionNode ? item['aria-label'] : undefined}>
|
|
64
61
|
{type === 'do' ? (
|
|
65
62
|
<DoIcon size={24} className={type} />
|
|
66
63
|
) : (
|
package/src/radio/Radio.tsx
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { useTheme } from '@wise/components-theming';
|
|
2
1
|
import { clsx } from 'clsx';
|
|
3
2
|
|
|
4
3
|
import Body from '../body/Body';
|
|
@@ -22,20 +21,19 @@ export default function Radio<T extends string | number = ''>({
|
|
|
22
21
|
secondary,
|
|
23
22
|
...otherProps
|
|
24
23
|
}: RadioProps<T>) {
|
|
25
|
-
const { isModern } = useTheme();
|
|
26
24
|
return (
|
|
27
25
|
<div
|
|
28
26
|
className={clsx(
|
|
29
27
|
'radio np-radio',
|
|
30
28
|
{
|
|
31
29
|
'radio-lg': secondary,
|
|
32
|
-
disabled:
|
|
30
|
+
'radio-disabled': disabled,
|
|
33
31
|
},
|
|
34
32
|
className,
|
|
35
33
|
)}
|
|
36
34
|
>
|
|
37
35
|
<label className={clsx({ disabled })} htmlFor={id}>
|
|
38
|
-
<span className=
|
|
36
|
+
<span className="m-r-2 np-radio-button">
|
|
39
37
|
<RadioButton id={id} disabled={disabled} {...otherProps} />
|
|
40
38
|
</span>
|
|
41
39
|
<Body
|
|
@@ -44,11 +42,7 @@ export default function Radio<T extends string | number = ''>({
|
|
|
44
42
|
className="np-radio__text"
|
|
45
43
|
>
|
|
46
44
|
{label}
|
|
47
|
-
{secondary &&
|
|
48
|
-
<Body as="span" className={clsx({ secondary: !isModern })}>
|
|
49
|
-
{secondary}
|
|
50
|
-
</Body>
|
|
51
|
-
)}
|
|
45
|
+
{secondary && <Body as="span">{secondary}</Body>}
|
|
52
46
|
</Body>
|
|
53
47
|
{avatar && <span className="np-radio__avatar m-l-auto">{avatar}</span>}
|
|
54
48
|
</label>
|
|
@@ -2,6 +2,7 @@ import { StoryObj } from '@storybook/react';
|
|
|
2
2
|
import { userEvent, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import TextareaWithDisplayFormat from './TextareaWithDisplayFormat';
|
|
5
|
+
import { Field } from '../field/Field';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
8
|
component: TextareaWithDisplayFormat,
|
|
@@ -11,15 +12,19 @@ export default {
|
|
|
11
12
|
type Story = StoryObj<typeof TextareaWithDisplayFormat>;
|
|
12
13
|
|
|
13
14
|
export const Basic: Story = {
|
|
14
|
-
render: (
|
|
15
|
+
render: () => {
|
|
16
|
+
const label = 'Textarea with display format';
|
|
17
|
+
const id = label.replaceAll(' ', '-').toLowerCase();
|
|
18
|
+
|
|
15
19
|
return (
|
|
16
|
-
|
|
20
|
+
<Field id={id} label={label}>
|
|
17
21
|
<TextareaWithDisplayFormat
|
|
22
|
+
id={id}
|
|
18
23
|
value="0000"
|
|
19
24
|
displayPattern="**** - **** - ****"
|
|
20
25
|
onChange={console.log}
|
|
21
26
|
/>
|
|
22
|
-
|
|
27
|
+
</Field>
|
|
23
28
|
);
|
|
24
29
|
},
|
|
25
30
|
// intentionally use interactive typing (over init value via `value` prop)
|
|
@@ -23,21 +23,25 @@ const files = [
|
|
|
23
23
|
{
|
|
24
24
|
id: 2,
|
|
25
25
|
filename: 'purchase-receipt-1.pdf',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: 6,
|
|
29
|
+
filename: 'purchase-receipt-1.pdf',
|
|
26
30
|
url: 'https://wise.com/public-resources/assets/logos/wise/brand_logo_inverse.svg',
|
|
27
31
|
},
|
|
28
32
|
{
|
|
29
|
-
id:
|
|
33
|
+
id: 3,
|
|
30
34
|
filename: 'receipt failed.png',
|
|
31
35
|
status: Status.FAILED,
|
|
32
36
|
},
|
|
33
37
|
{
|
|
34
|
-
id:
|
|
38
|
+
id: 4,
|
|
35
39
|
filename: 'receipt failed With error string.png',
|
|
36
40
|
status: Status.FAILED,
|
|
37
41
|
error: 'Something went wrong',
|
|
38
42
|
},
|
|
39
43
|
{
|
|
40
|
-
id:
|
|
44
|
+
id: 5,
|
|
41
45
|
filename: 'receipt failed With error object.png',
|
|
42
46
|
status: Status.FAILED,
|
|
43
47
|
error: { message: 'Something went wrong' },
|
|
@@ -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';
|
|
@@ -101,6 +101,10 @@ export type UploadInputProps = {
|
|
|
101
101
|
Pick<UploadItemProps, 'onDownload'> &
|
|
102
102
|
CommonProps;
|
|
103
103
|
|
|
104
|
+
interface UploadItemRef {
|
|
105
|
+
focus: () => void;
|
|
106
|
+
}
|
|
107
|
+
|
|
104
108
|
function generateFileId(file: File) {
|
|
105
109
|
const { name, size } = file;
|
|
106
110
|
const uploadTimeStamp = new Date().getTime();
|
|
@@ -131,8 +135,11 @@ const UploadInput = ({
|
|
|
131
135
|
const inputAttributes = useInputAttributes({ nonLabelable: true });
|
|
132
136
|
|
|
133
137
|
const [markedFileForDelete, setMarkedFileForDelete] = useState<UploadedFile | null>(null);
|
|
138
|
+
const [fileToRemoveIndex, setFileToRemoveIndex] = useState<number | null>(null);
|
|
134
139
|
const [mounted, setMounted] = useState(false);
|
|
135
140
|
const { formatMessage } = useIntl();
|
|
141
|
+
const itemRefs = useRef<(HTMLDivElement | UploadItemRef | null)[]>([]);
|
|
142
|
+
const uploadInputRef = useRef<HTMLInputElement | null>(null);
|
|
136
143
|
|
|
137
144
|
const PROGRESS_STATUSES = new Set([Status.PENDING, Status.PROCESSING]);
|
|
138
145
|
|
|
@@ -174,21 +181,28 @@ const UploadInput = ({
|
|
|
174
181
|
uploadedFilesListReference.current = updateListItem(uploadedFilesListReference.current);
|
|
175
182
|
};
|
|
176
183
|
|
|
184
|
+
const [fileToRemove, setFileToRemove] = useState<UploadedFile | null>(null);
|
|
185
|
+
|
|
177
186
|
const removeFile = (file: UploadedFile) => {
|
|
178
187
|
const { id, status } = file;
|
|
188
|
+
const index = uploadedFiles.findIndex((f) => f.id === file.id);
|
|
189
|
+
setFileToRemoveIndex(index);
|
|
179
190
|
|
|
180
191
|
if (status === Status.FAILED) {
|
|
181
|
-
// If removing a failed upload, we're just updating the view
|
|
182
192
|
removeFileFromList(file);
|
|
193
|
+
setFileToRemove(file);
|
|
183
194
|
} else if (onDeleteFile && id) {
|
|
184
|
-
// Set status to PROCESSING
|
|
185
195
|
modifyFileInList(file, { status: Status.PROCESSING, error: undefined });
|
|
186
196
|
|
|
187
|
-
// Notify host app about deletion
|
|
188
197
|
onDeleteFile(id)
|
|
189
|
-
.then(() =>
|
|
198
|
+
.then(() => {
|
|
199
|
+
removeFileFromList(file);
|
|
200
|
+
})
|
|
190
201
|
.catch((error) => {
|
|
191
202
|
modifyFileInList(file, { error: error as UploadError });
|
|
203
|
+
})
|
|
204
|
+
.finally(() => {
|
|
205
|
+
setFileToRemove(file);
|
|
192
206
|
});
|
|
193
207
|
}
|
|
194
208
|
};
|
|
@@ -263,10 +277,18 @@ const UploadInput = ({
|
|
|
263
277
|
continue;
|
|
264
278
|
}
|
|
265
279
|
|
|
280
|
+
// Check if the file is already in the list
|
|
281
|
+
const existingFile = uploadedFiles.find((f) => f.filename === file.name);
|
|
282
|
+
if (existingFile) {
|
|
283
|
+
// Remove the file from the list before adding it again
|
|
284
|
+
removeFileFromList(existingFile);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Add the file to the list
|
|
266
288
|
formData.append(fileInputName, file);
|
|
267
289
|
const pendingFile = {
|
|
268
|
-
id,
|
|
269
|
-
filename: name,
|
|
290
|
+
id: generateFileId(file),
|
|
291
|
+
filename: file.name,
|
|
270
292
|
status: Status.PENDING,
|
|
271
293
|
};
|
|
272
294
|
|
|
@@ -290,6 +312,22 @@ const UploadInput = ({
|
|
|
290
312
|
}
|
|
291
313
|
};
|
|
292
314
|
|
|
315
|
+
useLayoutEffect(() => {
|
|
316
|
+
if (fileToRemove && fileToRemoveIndex !== null) {
|
|
317
|
+
requestAnimationFrame(() => {
|
|
318
|
+
const nextFocusIndex = Math.min(fileToRemoveIndex, uploadedFiles.length - 1);
|
|
319
|
+
if (itemRefs.current[nextFocusIndex]) {
|
|
320
|
+
itemRefs.current[nextFocusIndex].focus(); // Focus the next UploadItem
|
|
321
|
+
} else {
|
|
322
|
+
// If there's only one item left, focus the UploadButton
|
|
323
|
+
uploadInputRef.current?.focus();
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
setFileToRemove(null); // Reset the state
|
|
327
|
+
setFileToRemoveIndex(null); // Reset the index
|
|
328
|
+
}
|
|
329
|
+
}, [uploadedFiles, fileToRemove, fileToRemoveIndex, itemRefs, uploadInputRef]);
|
|
330
|
+
|
|
293
331
|
useEffect(() => {
|
|
294
332
|
setMounted(true);
|
|
295
333
|
}, []);
|
|
@@ -307,9 +345,12 @@ const UploadInput = ({
|
|
|
307
345
|
className={clsx('np-upload-input', className, { disabled })}
|
|
308
346
|
{...inputAttributes}
|
|
309
347
|
>
|
|
310
|
-
{uploadedFiles.map((file) => (
|
|
348
|
+
{uploadedFiles.map((file, index) => (
|
|
311
349
|
<UploadItem
|
|
312
350
|
key={file.id}
|
|
351
|
+
ref={(el: UploadItemRef | null) => {
|
|
352
|
+
itemRefs.current[index] = el;
|
|
353
|
+
}}
|
|
313
354
|
file={file}
|
|
314
355
|
singleFileUpload={!multiple}
|
|
315
356
|
canDelete={
|
|
@@ -326,6 +367,7 @@ const UploadInput = ({
|
|
|
326
367
|
))}
|
|
327
368
|
{(multiple || (!multiple && !uploadedFiles.length)) && (
|
|
328
369
|
<UploadButton
|
|
370
|
+
ref={uploadInputRef}
|
|
329
371
|
id={id}
|
|
330
372
|
uploadButtonTitle={uploadButtonTitle}
|
|
331
373
|
disabled={areMaximumFilesUploadedAlready() || disabled}
|