@transferwise/components 46.33.0 → 46.35.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.
Files changed (74) hide show
  1. package/build/index.js +195 -200
  2. package/build/index.js.map +1 -1
  3. package/build/index.mjs +189 -194
  4. package/build/index.mjs.map +1 -1
  5. package/build/types/accordion/AccordionItem/AccordionItem.d.ts.map +1 -1
  6. package/build/types/alert/Alert.d.ts +3 -2
  7. package/build/types/alert/Alert.d.ts.map +1 -1
  8. package/build/types/common/hooks/useMedia.d.ts.map +1 -1
  9. package/build/types/common/panel/Panel.d.ts.map +1 -1
  10. package/build/types/common/responsivePanel/ResponsivePanel.d.ts.map +1 -1
  11. package/build/types/dimmer/Dimmer.d.ts +1 -11
  12. package/build/types/dimmer/Dimmer.d.ts.map +1 -1
  13. package/build/types/drawer/Drawer.d.ts +4 -4
  14. package/build/types/index.d.ts +3 -2
  15. package/build/types/index.d.ts.map +1 -1
  16. package/build/types/inputs/SelectInput.d.ts.map +1 -1
  17. package/build/types/modal/Modal.d.ts.map +1 -1
  18. package/build/types/processIndicator/ProcessIndicator.d.ts +36 -19
  19. package/build/types/processIndicator/ProcessIndicator.d.ts.map +1 -1
  20. package/build/types/processIndicator/index.d.ts +2 -2
  21. package/build/types/processIndicator/index.d.ts.map +1 -1
  22. package/build/types/promoCard/PromoCard.d.ts.map +1 -1
  23. package/build/types/select/searchBox/SearchBox.d.ts +1 -1
  24. package/build/types/snackbar/Snackbar.d.ts +0 -1
  25. package/build/types/snackbar/Snackbar.d.ts.map +1 -1
  26. package/build/types/test-utils/wait.d.ts +2 -0
  27. package/build/types/test-utils/wait.d.ts.map +1 -0
  28. package/build/types/tooltip/Tooltip.d.ts +1 -1
  29. package/build/types/tooltip/Tooltip.d.ts.map +1 -1
  30. package/build/types/uploadInput/uploadItem/UploadItem.d.ts.map +1 -1
  31. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  32. package/package.json +10 -14
  33. package/src/accordion/AccordionItem/AccordionItem.tsx +2 -4
  34. package/src/alert/Alert.spec.tsx +12 -0
  35. package/src/alert/Alert.story.tsx +11 -1
  36. package/src/alert/Alert.tsx +33 -14
  37. package/src/avatarWrapper/AvatarWrapper.story.tsx +1 -3
  38. package/src/button/Button.tsx +1 -1
  39. package/src/common/hooks/useConditionalListener/useConditionalListener.spec.js +1 -1
  40. package/src/common/hooks/useHasIntersected/useHasIntersected.spec.js +3 -3
  41. package/src/common/hooks/useMedia.spec.ts +1 -1
  42. package/src/common/hooks/useMedia.ts +1 -2
  43. package/src/common/panel/Panel.tsx +90 -92
  44. package/src/common/responsivePanel/ResponsivePanel.tsx +34 -38
  45. package/src/dateLookup/DateLookup.rtl.spec.tsx +181 -5
  46. package/src/dateLookup/DateLookup.testingLibrary.spec.js +171 -124
  47. package/src/drawer/Drawer.js +3 -3
  48. package/src/field/Field.tsx +3 -3
  49. package/src/index.ts +3 -2
  50. package/src/inputs/SelectInput.story.tsx +3 -2
  51. package/src/inputs/SelectInput.tsx +10 -2
  52. package/src/modal/Modal.tsx +1 -2
  53. package/src/processIndicator/ProcessIndicator.rtl.spec.tsx +45 -0
  54. package/src/processIndicator/ProcessIndicator.tsx +110 -0
  55. package/src/promoCard/PromoCard.tsx +1 -2
  56. package/src/radio/__snapshots__/Radio.rtl.spec.tsx.snap +0 -1
  57. package/src/snackbar/Snackbar.spec.js +8 -2
  58. package/src/snackbar/Snackbar.story.tsx +3 -1
  59. package/src/snackbar/Snackbar.tsx +1 -1
  60. package/src/tabs/Tabs.spec.js +46 -27
  61. package/src/test-utils/index.js +5 -7
  62. package/src/test-utils/jest.setup.js +9 -3
  63. package/src/test-utils/wait.ts +7 -0
  64. package/src/tooltip/Tooltip.tsx +44 -46
  65. package/src/tooltip/__snapshots__/Tooltip.spec.tsx.snap +2 -2
  66. package/src/upload/Upload.spec.js +34 -13
  67. package/src/uploadInput/UploadInput.spec.tsx +21 -23
  68. package/src/uploadInput/uploadItem/UploadItem.tsx +1 -3
  69. package/src/withDisplayFormat/WithDisplayFormat.spec.js +63 -32
  70. package/src/withDisplayFormat/WithDisplayFormat.tsx +4 -6
  71. package/src/dateLookup/DateLookup.keyboardEvents.spec.js +0 -180
  72. package/src/processIndicator/ProcessIndicator.js +0 -117
  73. package/src/processIndicator/ProcessIndicator.spec.js +0 -101
  74. /package/src/processIndicator/{index.js → index.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { act, renderHook } from '@testing-library/react-hooks';
1
+ import { act, renderHook } from '@testing-library/react';
2
2
 
3
3
  import { fireEvent } from '../../../test-utils';
4
4
 
@@ -1,4 +1,4 @@
1
- import { renderHook } from '@testing-library/react-hooks';
1
+ import { renderHook } from '@testing-library/react';
2
2
 
3
3
  import useHasIntersected from '.';
4
4
 
@@ -90,8 +90,8 @@ describe('useHasIntersected', () => {
90
90
 
91
91
  describe('calls unobserve with correct ref', () => {
92
92
  it('when component unmounts before unobserve is called', () => {
93
- let current = 'a valid ref';
94
- let reference = { current };
93
+ const current = 'a valid ref';
94
+ const reference = { current };
95
95
 
96
96
  setupIntersectionObserver(true);
97
97
  observe = jest.fn(() => {
@@ -1,4 +1,4 @@
1
- import { renderHook } from '@testing-library/react-hooks';
1
+ import { renderHook } from '@testing-library/react';
2
2
 
3
3
  import { mockMatchMedia } from '../../test-utils/window-mock';
4
4
 
@@ -1,5 +1,4 @@
1
- // eslint-disable-next-line import/extensions
2
- import { useSyncExternalStore } from 'use-sync-external-store/shim/index.js';
1
+ import { useSyncExternalStore } from 'react';
3
2
 
4
3
  export function useMedia(query: string) {
5
4
  return useSyncExternalStore(
@@ -38,109 +38,107 @@ export type PanelProps = PropsWithChildren<{
38
38
  }> &
39
39
  HTMLAttributes<HTMLDivElement>;
40
40
 
41
- const Panel = forwardRef<HTMLDivElement, PanelProps>(
42
- (
43
- {
44
- arrow = false,
45
- flip = true,
46
- altAxis = false,
47
- children,
48
- open = false,
49
- onClose,
50
- position = Position.BOTTOM,
51
- anchorRef,
52
- anchorWidth = false,
53
- ...rest
54
- }: PanelProps,
55
- reference,
56
- ) => {
57
- const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
58
- const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
41
+ const Panel = forwardRef<HTMLDivElement, PanelProps>(function Panel(
42
+ {
43
+ arrow = false,
44
+ flip = true,
45
+ altAxis = false,
46
+ children,
47
+ open = false,
48
+ onClose,
49
+ position = Position.BOTTOM,
50
+ anchorRef,
51
+ anchorWidth = false,
52
+ ...rest
53
+ }: PanelProps,
54
+ reference,
55
+ ) {
56
+ const [arrowElement, setArrowElement] = useState<HTMLDivElement | null>(null);
57
+ const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
59
58
 
60
- const modifiers = [];
59
+ const modifiers = [];
61
60
 
62
- if (altAxis) {
63
- modifiers.push({
64
- // https://popper.js.org/docs/v2/modifiers/prevent-overflow
65
- name: 'preventOverflow',
66
- options: {
67
- altAxis: true,
68
- tether: false,
69
- },
70
- });
71
- }
61
+ if (altAxis) {
62
+ modifiers.push({
63
+ // https://popper.js.org/docs/v2/modifiers/prevent-overflow
64
+ name: 'preventOverflow',
65
+ options: {
66
+ altAxis: true,
67
+ tether: false,
68
+ },
69
+ });
70
+ }
72
71
 
73
- if (arrow) {
74
- modifiers.push({
75
- name: 'arrow',
76
- options: {
77
- element: arrowElement,
78
- options: {
79
- padding: 8, // 8px from the edges of the popper
80
- },
81
- },
82
- });
83
- // This lets you displace a popper element from its reference element.
84
- modifiers.push({ name: 'offset', options: { offset: POPOVER_OFFSET } });
85
- }
86
- if (flip && fallbackPlacements[position]) {
87
- modifiers.push({
88
- name: 'flip',
72
+ if (arrow) {
73
+ modifiers.push({
74
+ name: 'arrow',
75
+ options: {
76
+ element: arrowElement,
89
77
  options: {
90
- fallbackPlacements: fallbackPlacements[position],
78
+ padding: 8, // 8px from the edges of the popper
91
79
  },
92
- });
93
- }
94
-
95
- const { styles, attributes, forceUpdate } = usePopper(anchorRef.current, popperElement, {
96
- placement: position,
97
- modifiers,
80
+ },
98
81
  });
82
+ // This lets you displace a popper element from its reference element.
83
+ modifiers.push({ name: 'offset', options: { offset: POPOVER_OFFSET } });
84
+ }
85
+ if (flip && fallbackPlacements[position]) {
86
+ modifiers.push({
87
+ name: 'flip',
88
+ options: {
89
+ fallbackPlacements: fallbackPlacements[position],
90
+ },
91
+ });
92
+ }
93
+
94
+ const { styles, attributes, forceUpdate } = usePopper(anchorRef.current, popperElement, {
95
+ placement: position,
96
+ modifiers,
97
+ });
99
98
 
100
- // If the trigger is not visible when the position is calculated, it will be incorrect. Because this can happen repeatedly (on resize for example),
101
- // it is most simple just to always position before opening
102
- useEffect(() => {
103
- if (open && forceUpdate) {
104
- forceUpdate();
105
- }
106
- }, [open]);
99
+ // If the trigger is not visible when the position is calculated, it will be incorrect. Because this can happen repeatedly (on resize for example),
100
+ // it is most simple just to always position before opening
101
+ useEffect(() => {
102
+ if (open && forceUpdate) {
103
+ forceUpdate();
104
+ }
105
+ }, [open]);
107
106
 
108
- const contentStyle: CSSProperties = {
109
- ...(anchorWidth ? { width: anchorRef.current?.clientWidth } : undefined),
110
- };
107
+ const contentStyle: CSSProperties = {
108
+ ...(anchorWidth ? { width: anchorRef.current?.clientWidth } : undefined),
109
+ };
111
110
 
112
- return (
113
- <Dimmer open={open} transparent fadeContentOnEnter fadeContentOnExit onClose={onClose}>
111
+ return (
112
+ <Dimmer open={open} transparent fadeContentOnEnter fadeContentOnExit onClose={onClose}>
113
+ <div
114
+ {...rest}
115
+ ref={setPopperElement}
116
+ role="dialog"
117
+ // eslint-disable-next-line react/forbid-dom-props
118
+ style={{ ...styles.popper }}
119
+ {...attributes.popper}
120
+ className={classnames('np-panel', { 'np-panel--open': open }, rest.className)}
121
+ >
114
122
  <div
115
- {...rest}
116
- ref={setPopperElement}
117
- role="dialog"
118
- // eslint-disable-next-line react/forbid-dom-props
119
- style={{ ...styles.popper }}
120
- {...attributes.popper}
121
- className={classnames('np-panel', { 'np-panel--open': open }, rest['className'])}
123
+ ref={reference}
124
+ /* eslint-disable-next-line react/forbid-dom-props */
125
+ style={contentStyle}
126
+ className={classnames('np-panel__content')}
122
127
  >
123
- <div
124
- ref={reference}
125
- /* eslint-disable-next-line react/forbid-dom-props */
126
- style={contentStyle}
127
- className={classnames('np-panel__content')}
128
- >
129
- {children}
130
- {/* Arrow has to stay inside content to get the same animations as the "dialog" and to get hidden when panel is closed. */}
131
- {arrow && (
132
- <div
133
- ref={setArrowElement}
134
- className={classnames('np-panel__arrow')}
135
- // eslint-disable-next-line react/forbid-dom-props
136
- style={styles.arrow}
137
- />
138
- )}
139
- </div>
128
+ {children}
129
+ {/* Arrow has to stay inside content to get the same animations as the "dialog" and to get hidden when panel is closed. */}
130
+ {arrow && (
131
+ <div
132
+ ref={setArrowElement}
133
+ className={classnames('np-panel__arrow')}
134
+ // eslint-disable-next-line react/forbid-dom-props
135
+ style={styles.arrow}
136
+ />
137
+ )}
140
138
  </div>
141
- </Dimmer>
142
- );
143
- },
144
- );
139
+ </div>
140
+ </Dimmer>
141
+ );
142
+ });
145
143
 
146
144
  export default Panel;
@@ -1,5 +1,3 @@
1
- import { useTheme } from '@wise/components-theming';
2
- import classNames from 'classnames';
3
1
  import { forwardRef } from 'react';
4
2
 
5
3
  import { Position } from '..';
@@ -8,44 +6,42 @@ import { useLayout } from '../hooks';
8
6
  import Panel from '../panel';
9
7
  import { PanelProps } from '../panel/Panel';
10
8
 
11
- const ResponsivePanel = forwardRef<HTMLDivElement, PanelProps>(
12
- (
13
- {
14
- anchorRef,
15
- arrow = false,
16
- flip = true,
17
- children,
18
- className = undefined,
19
- onClose,
20
- open = false,
21
- position = Position.BOTTOM,
22
- }: PanelProps,
23
- reference,
24
- ) => {
25
- const { isMobile } = useLayout();
26
- if (isMobile) {
27
- return (
28
- <BottomSheet key="bottomSheet" open={open} className={className} onClose={onClose}>
29
- {children}
30
- </BottomSheet>
31
- );
32
- }
9
+ const ResponsivePanel = forwardRef<HTMLDivElement, PanelProps>(function ResponsivePanel(
10
+ {
11
+ anchorRef,
12
+ arrow = false,
13
+ flip = true,
14
+ children,
15
+ className = undefined,
16
+ onClose,
17
+ open = false,
18
+ position = Position.BOTTOM,
19
+ }: PanelProps,
20
+ reference,
21
+ ) {
22
+ const { isMobile } = useLayout();
23
+ if (isMobile) {
33
24
  return (
34
- <Panel
35
- key="panel"
36
- ref={reference}
37
- flip={flip}
38
- arrow={arrow}
39
- open={open}
40
- position={position}
41
- anchorRef={anchorRef}
42
- className={className}
43
- onClose={onClose}
44
- >
25
+ <BottomSheet key="bottomSheet" open={open} className={className} onClose={onClose}>
45
26
  {children}
46
- </Panel>
27
+ </BottomSheet>
47
28
  );
48
- },
49
- );
29
+ }
30
+ return (
31
+ <Panel
32
+ key="panel"
33
+ ref={reference}
34
+ flip={flip}
35
+ arrow={arrow}
36
+ open={open}
37
+ position={position}
38
+ anchorRef={anchorRef}
39
+ className={className}
40
+ onClose={onClose}
41
+ >
42
+ {children}
43
+ </Panel>
44
+ );
45
+ });
50
46
 
51
47
  export default ResponsivePanel;
@@ -1,20 +1,196 @@
1
1
  import { Field } from '../field/Field';
2
- import { mockMatchMedia, mockResizeObserver, render, screen } from '../test-utils';
3
- import DateLookup from './DateLookup';
2
+ import { mockMatchMedia, mockResizeObserver, render, screen, userEvent } from '../test-utils';
3
+ import DateLookup, { DateLookupProps } from './DateLookup';
4
+
5
+ import { act } from 'react';
4
6
 
5
7
  mockMatchMedia();
6
8
  mockResizeObserver();
7
9
 
8
- const now = new Date();
10
+ const initialValue = new Date(2000, 0, 1);
9
11
 
10
12
  describe('DateLookup', () => {
13
+ beforeEach(() => {
14
+ jest.useFakeTimers();
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await jest.runOnlyPendingTimersAsync();
19
+ jest.useRealTimers();
20
+ });
21
+
11
22
  it('supports `Field` for labeling', () => {
12
23
  render(
13
24
  <Field label="Date of birth">
14
- <DateLookup value={now} onChange={() => {}} />
25
+ <DateLookup value={initialValue} onChange={() => {}} />
15
26
  </Field>,
16
27
  );
17
28
  // TODO: Replace with `.toHaveAttribute('aria-haspopup')`
18
- expect(screen.getByLabelText('Date of birth')).toHaveTextContent(now.getFullYear().toString());
29
+ expect(screen.getByLabelText('Date of birth')).toHaveTextContent(
30
+ initialValue.getFullYear().toString(),
31
+ );
32
+ });
33
+
34
+ it.each([' ', '{Enter}', '{ArrowDown}', '{ArrowUp}', '{ArrowRight}', '{ArrowLeft}'])(
35
+ "opens with '%s' and closes with '{Escape}'",
36
+ async (text) => {
37
+ render(<DateLookup value={initialValue} onChange={() => {}} />);
38
+
39
+ userEvent.tab();
40
+ userEvent.keyboard(text);
41
+
42
+ expect(screen.getByRole('button', { name: /next/iu })).toBeInTheDocument();
43
+
44
+ userEvent.keyboard('{Escape}');
45
+ await act(async () => {
46
+ await jest.runOnlyPendingTimersAsync();
47
+ });
48
+
49
+ expect(screen.queryByRole('button', { name: /next/iu })).not.toBeInTheDocument();
50
+ },
51
+ );
52
+
53
+ const setupAndOpenWithMouse = async (props: Partial<DateLookupProps> = {}) => {
54
+ const view = render(<DateLookup value={initialValue} onChange={() => {}} {...props} />);
55
+
56
+ userEvent.click(screen.getByRole('button'));
57
+ await act(async () => {
58
+ await jest.runOnlyPendingTimersAsync();
59
+ });
60
+
61
+ return view;
62
+ };
63
+
64
+ it('opens and closes with mouse', async () => {
65
+ await setupAndOpenWithMouse();
66
+
67
+ expect(screen.getByRole('button', { name: /next/iu })).toBeInTheDocument();
68
+
69
+ const dimmerElement = screen.getByRole('dialog').parentElement?.parentElement;
70
+ if (dimmerElement != null) {
71
+ userEvent.click(dimmerElement);
72
+ }
73
+ await act(async () => {
74
+ await jest.runOnlyPendingTimersAsync();
75
+ });
76
+
77
+ expect(screen.queryByRole('button', { name: /next/iu })).not.toBeInTheDocument();
78
+ });
79
+
80
+ describe('in day view', () => {
81
+ it.each([
82
+ ['{ArrowLeft}', -1],
83
+ ['{ArrowRight}', +1],
84
+ ['{ArrowUp}', -7],
85
+ ['{ArrowDown}', +7],
86
+ ])("handles '%s' to step %d day(s)", async (text, step) => {
87
+ const handleChange = jest.fn();
88
+ await setupAndOpenWithMouse({ onChange: handleChange });
89
+
90
+ expect(handleChange).not.toHaveBeenCalled();
91
+
92
+ userEvent.keyboard(text);
93
+
94
+ const value = new Date(initialValue);
95
+ value.setDate(initialValue.getDate() + step);
96
+ expect(handleChange).toHaveBeenCalledWith(value);
97
+
98
+ userEvent.keyboard('{Escape}');
99
+ await act(async () => {
100
+ await jest.runOnlyPendingTimersAsync();
101
+ });
102
+
103
+ expect(handleChange).toHaveBeenCalledWith(initialValue);
104
+ });
105
+ });
106
+
107
+ describe('in year view', () => {
108
+ it.each([
109
+ ['{ArrowLeft}', -1],
110
+ ['{ArrowRight}', +1],
111
+ ['{ArrowUp}', -4],
112
+ ['{ArrowDown}', +4],
113
+ ])("handles '%s' to step %d year(s)", async (text, step) => {
114
+ const handleChange = jest.fn();
115
+ await setupAndOpenWithMouse({ onChange: handleChange });
116
+
117
+ userEvent.click(screen.getByRole('button', { name: /year view/iu }));
118
+ await act(async () => {
119
+ await jest.runOnlyPendingTimersAsync();
120
+ });
121
+
122
+ expect(handleChange).not.toHaveBeenCalled();
123
+
124
+ userEvent.keyboard(text);
125
+
126
+ const value = new Date(initialValue);
127
+ value.setFullYear(initialValue.getFullYear() + step);
128
+ expect(handleChange).toHaveBeenCalledWith(value);
129
+
130
+ userEvent.keyboard('{Escape}');
131
+ await act(async () => {
132
+ await jest.runOnlyPendingTimersAsync();
133
+ });
134
+
135
+ expect(handleChange).toHaveBeenCalledWith(initialValue);
136
+ });
137
+ });
138
+
139
+ describe('in month view', () => {
140
+ it.each([
141
+ ['{ArrowLeft}', -1],
142
+ ['{ArrowRight}', +1],
143
+ ['{ArrowUp}', -4],
144
+ ['{ArrowDown}', +4],
145
+ ])("handles '%s' to step %d month(s)", async (text, step) => {
146
+ const handleChange = jest.fn();
147
+ await setupAndOpenWithMouse({ onChange: handleChange });
148
+
149
+ userEvent.click(screen.getByRole('button', { name: /year view/iu }));
150
+ await act(async () => {
151
+ await jest.runOnlyPendingTimersAsync();
152
+ });
153
+ userEvent.keyboard(' ');
154
+ await act(async () => {
155
+ await jest.runOnlyPendingTimersAsync();
156
+ });
157
+
158
+ expect(handleChange).not.toHaveBeenCalled();
159
+
160
+ userEvent.keyboard(text);
161
+
162
+ const value = new Date(initialValue);
163
+ value.setMonth(initialValue.getMonth() + step);
164
+ expect(handleChange).toHaveBeenCalledWith(value);
165
+
166
+ userEvent.keyboard('{Escape}');
167
+ await act(async () => {
168
+ await jest.runOnlyPendingTimersAsync();
169
+ });
170
+
171
+ expect(handleChange).toHaveBeenCalledWith(initialValue);
172
+ });
173
+ });
174
+
175
+ it('limits min value', async () => {
176
+ const min = new Date(initialValue);
177
+ min.setDate(min.getDate() - 1);
178
+ const handleChange = jest.fn();
179
+ await setupAndOpenWithMouse({ min, onChange: handleChange });
180
+
181
+ userEvent.keyboard('{ArrowLeft}{ArrowLeft}');
182
+
183
+ expect(handleChange).toHaveBeenCalledWith(min);
184
+ });
185
+
186
+ it('limits max value', async () => {
187
+ const max = new Date(initialValue);
188
+ max.setDate(max.getDate() + 1);
189
+ const handleChange = jest.fn();
190
+ await setupAndOpenWithMouse({ max, onChange: handleChange });
191
+
192
+ userEvent.keyboard('{ArrowRight}{ArrowRight}');
193
+
194
+ expect(handleChange).toHaveBeenCalledWith(max);
19
195
  });
20
196
  });