@transferwise/components 46.73.0 → 46.74.1

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 (71) hide show
  1. package/build/common/bottomSheet/BottomSheet.js +3 -1
  2. package/build/common/bottomSheet/BottomSheet.js.map +1 -1
  3. package/build/common/bottomSheet/BottomSheet.mjs +3 -1
  4. package/build/common/bottomSheet/BottomSheet.mjs.map +1 -1
  5. package/build/common/responsivePanel/ResponsivePanel.js +7 -1
  6. package/build/common/responsivePanel/ResponsivePanel.js.map +1 -1
  7. package/build/common/responsivePanel/ResponsivePanel.mjs +7 -1
  8. package/build/common/responsivePanel/ResponsivePanel.mjs.map +1 -1
  9. package/build/dimmer/Dimmer.js +3 -1
  10. package/build/dimmer/Dimmer.js.map +1 -1
  11. package/build/dimmer/Dimmer.mjs +3 -1
  12. package/build/dimmer/Dimmer.mjs.map +1 -1
  13. package/build/drawer/Drawer.js +2 -0
  14. package/build/drawer/Drawer.js.map +1 -1
  15. package/build/drawer/Drawer.mjs +2 -0
  16. package/build/drawer/Drawer.mjs.map +1 -1
  17. package/build/modal/Modal.js +3 -0
  18. package/build/modal/Modal.js.map +1 -1
  19. package/build/modal/Modal.mjs +3 -0
  20. package/build/modal/Modal.mjs.map +1 -1
  21. package/build/popover/Popover.js +6 -1
  22. package/build/popover/Popover.js.map +1 -1
  23. package/build/popover/Popover.mjs +7 -2
  24. package/build/popover/Popover.mjs.map +1 -1
  25. package/build/types/common/bottomSheet/BottomSheet.d.ts +1 -1
  26. package/build/types/common/bottomSheet/BottomSheet.d.ts.map +1 -1
  27. package/build/types/common/responsivePanel/ResponsivePanel.d.ts.map +1 -1
  28. package/build/types/dimmer/Dimmer.d.ts +2 -1
  29. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  30. package/build/types/drawer/Drawer.d.ts +2 -1
  31. package/build/types/drawer/Drawer.d.ts.map +1 -1
  32. package/build/types/modal/Modal.d.ts +2 -1
  33. package/build/types/modal/Modal.d.ts.map +1 -1
  34. package/build/types/popover/Popover.d.ts +6 -4
  35. package/build/types/popover/Popover.d.ts.map +1 -1
  36. package/build/types/uploadInput/UploadInput.d.ts +9 -0
  37. package/build/types/uploadInput/UploadInput.d.ts.map +1 -1
  38. package/build/types/uploadInput/uploadItem/UploadItem.d.ts +16 -1
  39. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  40. package/build/types/uploadInput/uploadItem/UploadItemLink.d.ts.map +1 -1
  41. package/build/uploadInput/UploadInput.js +71 -66
  42. package/build/uploadInput/UploadInput.js.map +1 -1
  43. package/build/uploadInput/UploadInput.mjs +72 -67
  44. package/build/uploadInput/UploadInput.mjs.map +1 -1
  45. package/build/uploadInput/uploadItem/UploadItem.js +13 -4
  46. package/build/uploadInput/uploadItem/UploadItem.js.map +1 -1
  47. package/build/uploadInput/uploadItem/UploadItem.mjs +13 -4
  48. package/build/uploadInput/uploadItem/UploadItem.mjs.map +1 -1
  49. package/build/uploadInput/uploadItem/UploadItemLink.js +1 -0
  50. package/build/uploadInput/uploadItem/UploadItemLink.js.map +1 -1
  51. package/build/uploadInput/uploadItem/UploadItemLink.mjs +1 -0
  52. package/build/uploadInput/uploadItem/UploadItemLink.mjs.map +1 -1
  53. package/package.json +3 -3
  54. package/src/common/bottomSheet/BottomSheet.tsx +4 -2
  55. package/src/common/responsivePanel/ResponsivePanel.tsx +13 -3
  56. package/src/dimmer/Dimmer.spec.js +8 -0
  57. package/src/dimmer/Dimmer.tsx +4 -0
  58. package/src/drawer/Drawer.spec.js +25 -6
  59. package/src/drawer/Drawer.tsx +3 -1
  60. package/src/modal/Modal.spec.js +19 -1
  61. package/src/modal/Modal.tsx +4 -0
  62. package/src/popover/Popover.spec.tsx +64 -21
  63. package/src/popover/Popover.story.tsx +54 -42
  64. package/src/popover/Popover.tsx +12 -5
  65. package/src/popover/__snapshots__/Popover.spec.tsx.snap +2 -0
  66. package/src/uploadInput/UploadInput.spec.tsx +121 -9
  67. package/src/uploadInput/UploadInput.tests.story.tsx +207 -140
  68. package/src/uploadInput/UploadInput.tsx +110 -77
  69. package/src/uploadInput/uploadItem/UploadItem.spec.tsx +1 -0
  70. package/src/uploadInput/uploadItem/UploadItem.tsx +30 -6
  71. package/src/uploadInput/uploadItem/UploadItemLink.tsx +9 -1
@@ -1,6 +1,6 @@
1
1
  import { useTheme } from '@wise/components-theming';
2
2
  import { clsx } from 'clsx';
3
- import { useRef, useState, cloneElement, useEffect, isValidElement } from 'react';
3
+ import { useRef, useState, cloneElement, useEffect, isValidElement, useId } from 'react';
4
4
 
5
5
  import { Position, Typography } from '../common';
6
6
  import ResponsivePanel from '../common/responsivePanel';
@@ -23,11 +23,13 @@ export type PopoverPreferredPlacement =
23
23
 
24
24
  export interface PopoverProps {
25
25
  children?: React.ReactNode;
26
- className?: string;
27
- content: React.ReactNode;
26
+ title?: React.ReactNode;
27
+ /** Screen-reader-friendly title. Must be provided if `title` prop is not set. */
28
+ 'aria-label'?: string;
28
29
  preferredPlacement?: PopoverPreferredPlacement;
30
+ content: React.ReactNode;
29
31
  onClose?: () => void;
30
- title?: React.ReactNode;
32
+ className?: string;
31
33
  }
32
34
 
33
35
  function resolvePlacement(preferredPlacement: PopoverPreferredPlacement) {
@@ -50,7 +52,10 @@ export default function Popover({
50
52
  preferredPlacement = Position.RIGHT,
51
53
  title,
52
54
  onClose,
55
+ 'aria-label': ariaLabel,
53
56
  }: PopoverProps) {
57
+ const titleId = useId();
58
+
54
59
  const resolvedPlacement = resolvePlacement(preferredPlacement);
55
60
  useEffect(() => {
56
61
  if (resolvedPlacement !== preferredPlacement) {
@@ -81,6 +86,8 @@ export default function Popover({
81
86
  : children}
82
87
  </span>
83
88
  <ResponsivePanel
89
+ aria-label={ariaLabel}
90
+ aria-labelledby={title && !ariaLabel ? titleId : undefined}
84
91
  open={open}
85
92
  anchorRef={anchorReference}
86
93
  position={resolvedPlacement}
@@ -90,7 +97,7 @@ export default function Popover({
90
97
  >
91
98
  <div className="np-popover__content np-text-default-body">
92
99
  {title && (
93
- <Title type={Typography.TITLE_BODY} className="m-b-1">
100
+ <Title type={Typography.TITLE_BODY} id={titleId} className="m-b-1">
94
101
  {title}
95
102
  </Title>
96
103
  )}
@@ -2,6 +2,7 @@
2
2
 
3
3
  exports[`Popover on desktop renders when is open 1`] = `
4
4
  <div
5
+ aria-labelledby=":r0:"
5
6
  class="np-panel np-panel--open np-popover__container"
6
7
  data-popper-escaped="true"
7
8
  data-popper-placement="right"
@@ -17,6 +18,7 @@ exports[`Popover on desktop renders when is open 1`] = `
17
18
  >
18
19
  <h4
19
20
  class="np-text-title-body m-b-1"
21
+ id=":r0:"
20
22
  >
21
23
  title
22
24
  </h4>
@@ -1,17 +1,31 @@
1
- import { within } from '@testing-library/react';
1
+ import { Matcher, within } from '@testing-library/react';
2
2
  import { userEvent } from '@testing-library/user-event';
3
3
  import { act } from 'react';
4
4
 
5
5
  import { Status } from '../common';
6
6
  import { Field } from '../field/Field';
7
- import { mockMatchMedia, render, screen, waitFor, waitForElementToBeRemoved } from '../test-utils';
7
+ import { mockMatchMedia, render, screen, waitFor } from '../test-utils';
8
8
 
9
9
  import UploadInput, { UploadInputProps } from './UploadInput';
10
10
  import { TEST_IDS as UPLOAD_BUTTON_TEST_IDS } from './uploadButton/UploadButton';
11
- import { TEST_IDS as UPLOAD_ITEM_TEST_IDS } from './uploadItem/UploadItem';
12
11
 
13
12
  const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTimeAsync });
14
13
 
14
+ const deleteFileAndWaitForFocus = async (fileToDeleteTestId: Matcher, nextFocusTestId: Matcher) => {
15
+ const fileToDelete = screen.getByTestId(fileToDeleteTestId);
16
+
17
+ await user.click(within(fileToDelete).getByLabelText('Remove file', { exact: false }));
18
+
19
+ const removeButton = screen.queryByText('Remove');
20
+ if (removeButton) {
21
+ await user.click(removeButton);
22
+ }
23
+
24
+ await waitFor(() => {
25
+ expect(screen.getByTestId(nextFocusTestId)).toHaveFocus();
26
+ });
27
+ };
28
+
15
29
  mockMatchMedia();
16
30
 
17
31
  describe('UploadInput', () => {
@@ -139,15 +153,21 @@ describe('UploadInput', () => {
139
153
  onFilesChange,
140
154
  });
141
155
 
142
- const fileToDelete = screen.getAllByTestId(UPLOAD_ITEM_TEST_IDS.uploadItem)[0];
143
- within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
156
+ const fileToDelete = screen.getByTestId('1-uploadItem');
144
157
  await act(async () => {
158
+ within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
145
159
  await jest.runOnlyPendingTimersAsync();
146
160
  });
147
161
 
148
- screen.getByText('Remove').click();
162
+ await act(async () => {
163
+ screen.getByText('Remove').click();
164
+ await jest.runOnlyPendingTimersAsync();
165
+ });
166
+
167
+ await waitFor(() => {
168
+ expect(screen.queryByTestId('1-uploadItem')).not.toBeInTheDocument();
169
+ });
149
170
 
150
- await waitForElementToBeRemoved(fileToDelete);
151
171
  expect(props.onDeleteFile).toHaveBeenCalledWith(files[0].id);
152
172
 
153
173
  expect(onFilesChange).toHaveBeenCalledTimes(2);
@@ -190,9 +210,9 @@ describe('UploadInput', () => {
190
210
  onFilesChange,
191
211
  });
192
212
 
193
- const fileToDelete = screen.getAllByTestId(UPLOAD_ITEM_TEST_IDS.uploadItem)[0];
194
- within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
213
+ const fileToDelete = screen.getByTestId('1-uploadItem');
195
214
  await act(async () => {
215
+ within(fileToDelete).getByLabelText('Remove file', { exact: false }).click();
196
216
  await jest.runOnlyPendingTimersAsync();
197
217
  });
198
218
 
@@ -212,6 +232,98 @@ describe('UploadInput', () => {
212
232
 
213
233
  expect(screen.queryByLabelText('Remove file ', { exact: false })).not.toBeInTheDocument();
214
234
  });
235
+
236
+ it('should focus the next item after a file is deleted', async () => {
237
+ const files = [
238
+ { id: 1, filename: 'Sales-2024-invoice.pdf', status: Status.DONE },
239
+ { id: 2, filename: 'CoWork-0317-invoice.pdf', status: Status.DONE },
240
+ { id: 3, filename: 'purchase-receipt.pdf', status: Status.DONE },
241
+ ];
242
+
243
+ renderComponent({ ...props, files, multiple: true, onFilesChange });
244
+
245
+ // Delete the first file and expect focus to move to the next one
246
+ await deleteFileAndWaitForFocus('1-uploadItem', '2-action');
247
+ });
248
+
249
+ it('should focus the previous item after the last file is deleted', async () => {
250
+ const files = [
251
+ {
252
+ id: 1,
253
+ filename: 'Sales-2024-invoice.pdf',
254
+ status: Status.DONE,
255
+ },
256
+ {
257
+ id: 2,
258
+ filename: 'CoWork-0317-invoice.pdf',
259
+ status: Status.DONE,
260
+ },
261
+ {
262
+ id: 3,
263
+ filename: 'purchase-receipt.pdf',
264
+ status: Status.DONE,
265
+ },
266
+ ];
267
+
268
+ renderComponent({
269
+ ...props,
270
+ files,
271
+ multiple: true,
272
+ onFilesChange,
273
+ });
274
+
275
+ await deleteFileAndWaitForFocus('3-uploadItem', '2-action');
276
+ });
277
+
278
+ it('should focus the upload input after the only file is deleted', async () => {
279
+ const singleFile = [
280
+ {
281
+ id: 3,
282
+ filename: 'purchase-receipt.pdf',
283
+ status: Status.DONE,
284
+ },
285
+ ];
286
+
287
+ renderComponent({
288
+ ...props,
289
+ files: singleFile,
290
+ multiple: true,
291
+ onFilesChange,
292
+ });
293
+
294
+ await deleteFileAndWaitForFocus('3-uploadItem', 'uploadInput');
295
+ });
296
+
297
+ it('should focus on the next item or upload input after each file is deleted in sequence', async () => {
298
+ const filesWithFailed = [
299
+ {
300
+ id: 1,
301
+ filename: 'Sales-2024-invoice.pdf',
302
+ status: Status.DONE,
303
+ },
304
+ {
305
+ id: 2,
306
+ filename: 'CoWork-0317-invoice.pdf',
307
+ status: Status.FAILED,
308
+ },
309
+ {
310
+ id: 3,
311
+ filename: 'purchase-receipt.pdf',
312
+ status: Status.DONE,
313
+ },
314
+ ];
315
+
316
+ renderComponent({
317
+ ...props,
318
+ files: filesWithFailed,
319
+ multiple: true,
320
+ onFilesChange,
321
+ });
322
+
323
+ await deleteFileAndWaitForFocus('3-uploadItem', '2-action');
324
+ await deleteFileAndWaitForFocus('1-uploadItem', '2-action');
325
+ await deleteFileAndWaitForFocus('2-uploadItem', 'uploadInput');
326
+ });
215
327
  });
216
328
 
217
329
  describe('Max File Upload limit', () => {
@@ -1,52 +1,52 @@
1
1
  import { action } from '@storybook/addon-actions';
2
- import { StoryFn, Meta } from '@storybook/react';
2
+ import { Meta, StoryObj } from '@storybook/react';
3
3
 
4
4
  import { Status } from '../common';
5
-
6
5
  import UploadInput, { UploadInputProps } from './UploadInput';
7
6
  import { UploadedFile, UploadResponse } from './types';
7
+ import { userEvent, within } from '@storybook/test';
8
8
 
9
9
  const meta: Meta<typeof UploadInput> = {
10
10
  title: 'Forms/UploadInput/Tests',
11
11
  component: UploadInput,
12
12
  };
13
-
14
13
  export default meta;
15
- type Story = StoryFn<UploadInputProps>;
16
14
 
17
- const files = [
15
+ type Story = StoryObj<UploadInputProps>;
16
+
17
+ const files: UploadedFile[] = [
18
18
  {
19
- id: 1,
19
+ id: '0hd8hf8',
20
20
  filename: 'purchase-receipt-0.pdf',
21
21
  url: 'https://wise.com/public-resources/assets/logos/wise/brand_logo_inverse.svg',
22
22
  },
23
23
  {
24
- id: 2,
24
+ id: '1r7hgc83',
25
25
  filename: 'purchase-receipt-1.pdf',
26
26
  },
27
27
  {
28
- id: 3,
28
+ id: '2nhc7387hc8h',
29
29
  filename: 'purchase-receipt-2.pdf',
30
30
  url: 'https://wise.com/public-resources/assets/logos/wise/brand_logo_inverse.svg',
31
31
  },
32
32
  {
33
- id: 4,
33
+ id: '39wd8uc',
34
34
  filename: 'receipt failed.png',
35
35
  status: Status.FAILED,
36
36
  },
37
37
  {
38
- id: 5,
38
+ id: '437yyf8hf',
39
39
  filename: 'receipt failed With error string.png',
40
40
  status: Status.FAILED,
41
41
  error: 'Something went wrong',
42
42
  },
43
43
  {
44
- id: 6,
44
+ id: '5biehveifh',
45
45
  filename: 'receipt failed With error object.png',
46
46
  status: Status.FAILED,
47
47
  error: { message: 'Something went wrong' },
48
48
  },
49
- ] satisfies UploadedFile[];
49
+ ];
50
50
 
51
51
  const createDelayedPromise = async ({
52
52
  successful = true,
@@ -65,163 +65,230 @@ const createDelayedPromise = async ({
65
65
  });
66
66
 
67
67
  const props = {
68
- onUploadFile: async (formData: FormData) => {
69
- return createDelayedPromise();
68
+ onUploadFile: async (formData: FormData) => createDelayedPromise(),
69
+ onDeleteFile: async (id: string | number) => createDelayedPromise(),
70
+ };
71
+
72
+ export const UploadInputWithDescriptionFromProps: Story = {
73
+ args: {
74
+ ...props,
75
+ multiple: true,
76
+ description: 'Custom file description from prop',
70
77
  },
71
- onDeleteFile: async (id: string | number) => {
72
- return createDelayedPromise();
78
+ };
79
+
80
+ export const Disabled: Story = {
81
+ args: {
82
+ ...props,
83
+ disabled: true,
73
84
  },
74
85
  };
75
86
 
76
- const Template: Story = (args: UploadInputProps) => <UploadInput {...args} />;
87
+ export const WithAnyFileType: Story = {
88
+ args: {
89
+ ...props,
90
+ fileTypes: '*',
91
+ },
92
+ };
77
93
 
78
- export const UploadInputWithDescriptionFromProps: Story = Template.bind({});
79
- UploadInputWithDescriptionFromProps.args = {
80
- ...props,
81
- multiple: true,
82
- description: 'Custom file description from prop',
94
+ export const WithSingleFileType: Story = {
95
+ args: {
96
+ ...props,
97
+ fileTypes: '.zip,application/zip',
98
+ },
83
99
  };
84
100
 
85
- export const Disabled: Story = Template.bind({});
86
- Disabled.args = { ...props, disabled: true };
101
+ export const WithMultipleExistingFiles: Story = {
102
+ args: {
103
+ ...props,
104
+ files,
105
+ multiple: true,
106
+ },
107
+ };
87
108
 
88
- export const WithAnyFileType: Story = Template.bind({});
89
- WithAnyFileType.args = { ...props, fileTypes: '*' };
109
+ export const WithFileErrors: Story = {
110
+ args: {
111
+ ...props,
112
+ files: [
113
+ { id: 1, filename: 'Error with default message.png', status: Status.FAILED },
114
+ {
115
+ id: 2,
116
+ filename: 'Error with `string` error.png',
117
+ status: Status.FAILED,
118
+ error: 'Single string error',
119
+ },
120
+ {
121
+ id: 3,
122
+ filename: 'Error with `obj` error ({ message : `string` }).png',
123
+ status: Status.FAILED,
124
+ error: { message: 'Single obj error' },
125
+ },
126
+ {
127
+ id: 4,
128
+ filename: 'Error with single error passed in `array`.png',
129
+ status: Status.FAILED,
130
+ errors: ['Single error in array'],
131
+ },
132
+ {
133
+ id: 5,
134
+ filename: 'Error with multiple `string` errors passed in `array`.png',
135
+ status: Status.FAILED,
136
+ errors: [
137
+ 'Error 1',
138
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
139
+ 'Error 3',
140
+ ],
141
+ },
142
+ {
143
+ id: 6,
144
+ filename: 'Error with multiple `obj` errors passed in `array`.png',
145
+ status: Status.FAILED,
146
+ errors: [
147
+ { message: 'Error 1' },
148
+ {
149
+ message:
150
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
151
+ },
152
+ { message: 'Error 3' },
153
+ ],
154
+ },
155
+ ],
156
+ multiple: true,
157
+ },
158
+ };
90
159
 
91
- export const WithSingleFileType: Story = Template.bind({});
92
- WithSingleFileType.args = { ...props, fileTypes: '.zip,application/zip' };
160
+ export const WithoutDelete: Story = {
161
+ args: {
162
+ ...props,
163
+ files,
164
+ onDeleteFile: undefined,
165
+ multiple: true,
166
+ },
167
+ };
93
168
 
94
- export const WithMultipleExistingFiles: Story = Template.bind({});
95
- WithMultipleExistingFiles.args = {
96
- ...props,
97
- files,
98
- multiple: true,
169
+ export const WithUploadFailed: Story = {
170
+ args: {
171
+ ...props,
172
+ files: files.slice(0),
173
+ onUploadFile: async () => createDelayedPromise({ successful: false }),
174
+ multiple: true,
175
+ },
99
176
  };
100
177
 
101
- export const WithFileErrors: Story = Template.bind({});
102
- WithFileErrors.args = {
103
- ...props,
104
- files: [
105
- {
106
- id: 1,
107
- filename: 'Error with default message.png',
108
- status: Status.FAILED,
109
- },
110
- {
111
- id: 2,
112
- filename: 'Error with `string` error.png',
113
- status: Status.FAILED,
114
- error: 'Single string error',
115
- },
116
- {
117
- id: 3,
118
- filename: 'Error with `obj` error ({ message : `string` }).png',
119
- status: Status.FAILED,
120
- error: { message: 'Single obj error' },
121
- },
122
- {
123
- id: 4,
124
- filename: 'Error with single error passed in `array`.png',
125
- status: Status.FAILED,
126
- errors: ['Single error in array'],
127
- },
128
- {
129
- id: 5,
130
- filename: 'Error with multiple `string` errors passed in `array`.png',
131
- status: Status.FAILED,
132
- errors: [
133
- 'Error 1',
134
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
135
- 'Error 3',
136
- ],
137
- },
138
- {
139
- id: 6,
140
- filename: 'Error with multiple `obj` errors passed in `array`.png',
141
- status: Status.FAILED,
142
- errors: [
143
- { message: 'Error 1' },
144
- {
145
- message:
146
- 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
147
- },
148
- { message: 'Error 3' },
149
- ],
150
- },
151
- ],
152
- multiple: true,
178
+ export const WithDeleteFailed: Story = {
179
+ args: {
180
+ ...props,
181
+ files: files.slice(0),
182
+ onDeleteFile: async () => createDelayedPromise({ successful: false }),
183
+ multiple: true,
184
+ },
153
185
  };
154
186
 
155
- export const WithoutDelete: Story = Template.bind({});
156
- WithoutDelete.args = {
157
- ...props,
158
- files,
159
- onDeleteFile: undefined,
160
- multiple: true,
187
+ export const CustomConfirmMessage: Story = {
188
+ args: {
189
+ ...props,
190
+ files: files.slice(0),
191
+ deleteConfirm: {
192
+ title: 'Sure you want to remove this invoice?',
193
+ body: (
194
+ <img
195
+ alt="brand logo"
196
+ src="https://wise.com/public-resources/assets/logos/wise/brand_logo.svg"
197
+ />
198
+ ),
199
+ },
200
+ },
161
201
  };
162
202
 
163
- export const WithUploadFailed: Story = Template.bind({});
164
- WithUploadFailed.args = {
165
- ...props,
166
- files: files.slice(0),
167
- onUploadFile: async () => createDelayedPromise({ successful: false }),
168
- multiple: true,
203
+ export const WithManualDownloadHandler: Story = {
204
+ args: {
205
+ ...props,
206
+ files,
207
+ onDownload: action('Manual download handler'),
208
+ },
169
209
  };
170
210
 
171
- export const WithDeleteFailed: Story = Template.bind({});
172
- WithDeleteFailed.args = {
173
- ...props,
174
- files: files.slice(0),
175
- onDeleteFile: async () => createDelayedPromise({ successful: false }),
176
- multiple: true,
211
+ export const WithFilesChangeHandler: Story = {
212
+ args: {
213
+ ...props,
214
+ files,
215
+ onFilesChange: action('Files change handler'),
216
+ },
177
217
  };
178
218
 
179
- export const CustomConfirmMessage: Story = Template.bind({});
180
- CustomConfirmMessage.args = {
181
- ...props,
182
- files: files.slice(0),
183
- deleteConfirm: {
184
- title: 'Sure you want to remove this invoice?',
185
- body: (
186
- <img
187
- alt="brand logo"
188
- src="https://wise.com/public-resources/assets/logos/wise/brand_logo.svg"
189
- />
190
- ),
219
+ export const WithMaxFilesToUploadLimit: Story = {
220
+ args: {
221
+ ...props,
222
+ multiple: true,
223
+ maxFiles: 5,
224
+ maxFilesErrorMessage: "Can't upload as maximum number of files allowed are already uploaded",
191
225
  },
192
226
  };
193
227
 
194
- export const WithManualDownloadHandler: Story = Template.bind({});
195
- WithManualDownloadHandler.args = {
196
- ...props,
197
- files,
198
- onDownload: action('Manual download handler'),
228
+ export const WithFileSizeErrorMessage: Story = {
229
+ args: {
230
+ ...props,
231
+ sizeLimit: 1,
232
+ sizeLimitErrorMessage: 'The file is oversized',
233
+ },
199
234
  };
200
235
 
201
- export const WithFilesChangeHandler: Story = Template.bind({});
202
- WithFilesChangeHandler.args = {
203
- ...props,
204
- files,
205
- onFilesChange: action('Files change handler'),
236
+ export const WithCustomUploadButtonTitle: Story = {
237
+ args: {
238
+ ...props,
239
+ uploadButtonTitle: 'Upload the VAT receipts for FY 2022-23',
240
+ },
206
241
  };
207
242
 
208
- export const WithMaxFilesToUploadLimit: Story = Template.bind({});
209
- WithMaxFilesToUploadLimit.args = {
210
- ...props,
211
- multiple: true,
212
- maxFiles: 5,
213
- maxFilesErrorMessage: "Can't upload as maximum number of files allowed are already uploaded",
243
+ const triggerModalAndConfirm = async ({ isLink = true } = {}) => {
244
+ if (isLink) {
245
+ await wait();
246
+ await userEvent.tab();
247
+ }
248
+ await wait();
249
+ await userEvent.keyboard('{Enter}');
250
+ await wait();
251
+ await userEvent.tab();
252
+ await wait();
253
+ await userEvent.tab();
254
+ await wait();
255
+ await userEvent.tab();
256
+ await wait();
257
+ await userEvent.keyboard('{Enter}');
258
+ await wait();
214
259
  };
260
+ const wait = async (time = 250) =>
261
+ new Promise((resolve) => {
262
+ setTimeout(resolve, time);
263
+ });
215
264
 
216
- export const WithFileSizeErrorMessage: Story = Template.bind({});
217
- WithFileSizeErrorMessage.args = {
218
- ...props,
219
- sizeLimit: 1,
220
- sizeLimitErrorMessage: 'The file is oversized',
265
+ export const DeletingTop: Story = {
266
+ args: {
267
+ ...props,
268
+ files: [files[0], files[1], files[2]],
269
+ multiple: true,
270
+ },
271
+ play: async ({ canvasElement }) => {
272
+ await userEvent.tab();
273
+ await triggerModalAndConfirm();
274
+ await triggerModalAndConfirm({ isLink: false });
275
+ await triggerModalAndConfirm();
276
+ },
221
277
  };
222
278
 
223
- export const WithCustomUploadButtonTitle: Story = Template.bind({});
224
- WithCustomUploadButtonTitle.args = {
225
- ...props,
226
- uploadButtonTitle: 'Upload the VAT receipts for FY 2022-23',
279
+ export const DeletingBottom: Story = {
280
+ args: {
281
+ ...props,
282
+ files: [files[0], files[1], files[2]],
283
+ multiple: true,
284
+ },
285
+ play: async ({ canvasElement }) => {
286
+ await userEvent.tab();
287
+ await userEvent.tab();
288
+ await userEvent.tab();
289
+ await userEvent.tab();
290
+ await triggerModalAndConfirm();
291
+ await triggerModalAndConfirm({ isLink: false });
292
+ await triggerModalAndConfirm();
293
+ },
227
294
  };