@transferwise/components 46.139.0 → 46.140.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.
@@ -142,6 +142,61 @@ describe('Popover', () => {
142
142
  expect(handleOnClose).toHaveBeenCalledTimes(1);
143
143
  });
144
144
 
145
+ describe('className', () => {
146
+ describe('on desktop', () => {
147
+ beforeEach(() => {
148
+ global.innerWidth = Breakpoint.SMALL;
149
+ });
150
+
151
+ it('applies className to the panel container', async () => {
152
+ render(
153
+ <Popover {...props} className="custom-popover-class">
154
+ <button type="button">Open</button>
155
+ </Popover>,
156
+ );
157
+
158
+ await userEvent.click(screen.getByText('Open'));
159
+ await waitForPanel();
160
+
161
+ const panel = getPanel();
162
+ expect(panel).toHaveClass('np-popover__container');
163
+ expect(panel).toHaveClass('custom-popover-class');
164
+ });
165
+ });
166
+
167
+ describe('on mobile', () => {
168
+ beforeEach(() => {
169
+ global.innerWidth = Breakpoint.SMALL - 1;
170
+ });
171
+
172
+ it('applies className to the bottom sheet container', async () => {
173
+ render(
174
+ <Popover {...props} className="custom-popover-class">
175
+ <button type="button">Open</button>
176
+ </Popover>,
177
+ );
178
+
179
+ await userEvent.click(screen.getByText('Open'));
180
+
181
+ const bottomSheet = getBottomSheet();
182
+ expect(bottomSheet).toHaveClass('np-popover__container');
183
+ expect(bottomSheet).toHaveClass('custom-popover-class');
184
+ });
185
+ });
186
+
187
+ it('does not apply className to the trigger wrapper', () => {
188
+ const { container } = render(
189
+ <Popover {...props} className="custom-popover-class">
190
+ <button type="button">Open</button>
191
+ </Popover>,
192
+ );
193
+
194
+ const wrapper = container.firstChild;
195
+ expect(wrapper).toHaveClass('np-popover');
196
+ expect(wrapper).not.toHaveClass('custom-popover-class');
197
+ });
198
+ });
199
+
145
200
  const waitForPanel = async () => screen.findByText('content');
146
201
  const getDimmer = () => document.querySelector('.dimmer');
147
202
  const getPanel = () => document.querySelector('.np-panel');
@@ -75,7 +75,7 @@ export default function Popover({
75
75
  };
76
76
 
77
77
  return (
78
- <span className={clsx('np-popover', className)}>
78
+ <span className="np-popover">
79
79
  <span ref={anchorReference} className="d-inline-block">
80
80
  {isValidElement<{ onClick?: () => void }>(children)
81
81
  ? cloneElement(children, {
@@ -93,7 +93,7 @@ export default function Popover({
93
93
  anchorRef={anchorReference}
94
94
  position={resolvedPlacement}
95
95
  arrow
96
- className="np-popover__container"
96
+ className={clsx('np-popover__container', className)}
97
97
  onClose={handleOnClose}
98
98
  >
99
99
  <div className="np-popover__content np-text-default-body">
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react-webpack5';
2
- import { userEvent, within } from 'storybook/test';
2
+ import { expect, screen, userEvent, waitFor, within } from 'storybook/test';
3
3
  import { useState } from 'react';
4
4
 
5
5
  import { withVariantConfig } from '../../.storybook/helpers';
@@ -19,6 +19,11 @@ const options: SelectOptionItem[] = Array.from({ length: 10 }, (_, i) => ({
19
19
  label: `Option ${i + 1}`,
20
20
  }));
21
21
 
22
+ const wait = async (duration = 500) =>
23
+ new Promise<void>((resolve) => {
24
+ setTimeout(resolve, duration);
25
+ });
26
+
22
27
  /**
23
28
  * Visual regression story: Select placed at the bottom of the viewport so the dropdown
24
29
  * flips to the top when opened. The play function opens the dropdown automatically.
@@ -57,3 +62,71 @@ export const DropdownFlipsToTop: Story = {
57
62
  },
58
63
  ...withVariantConfig(['default']),
59
64
  };
65
+
66
+ /**
67
+ * This test insures that there's no unintended page scroll after the dropdown
68
+ * is closed without selecting an option (e.g. clicking outside).
69
+ * It tests 2 scenarios:
70
+ * 1. when the dropdown fits naturally on the screen below the trigger
71
+ * 2. when there's not enough space below the trigger and it renders above it
72
+ */
73
+ const generateNoScrollTest = (inverted?: boolean): Story => ({
74
+ render: function Render() {
75
+ const [selected, setSelected] = useState<SelectOptionItem>(options[0]);
76
+ return (
77
+ <div>
78
+ <style>
79
+ {`
80
+ .storybook-container{
81
+ min-height: unset;
82
+ height: unset;
83
+ }
84
+ `}
85
+ </style>
86
+ {inverted ? <p style={{ height: '70vh', background: 'blanchedalmond' }} /> : null}
87
+
88
+ <Select
89
+ id="flip-test-select"
90
+ size="md"
91
+ block
92
+ options={options}
93
+ selected={selected}
94
+ onChange={(option) => {
95
+ if (option && 'value' in option) {
96
+ setSelected(option as SelectOptionItem);
97
+ }
98
+ }}
99
+ />
100
+
101
+ <p style={{ margin: '1rem 0', height: '150vh', background: 'blanchedalmond' }} />
102
+ </div>
103
+ );
104
+ },
105
+ play: async ({ canvasElement, step }) => {
106
+ const canvas = within(canvasElement);
107
+ const trigger = canvas.getByRole('button', { name: /Option 1/i });
108
+
109
+ await step('Open the combobox', async () => {
110
+ await userEvent.click(trigger);
111
+ });
112
+
113
+ await step('Check if options are displayed', async () => {
114
+ await waitFor(async () =>
115
+ expect((await screen.findAllByRole('option')).length).toBeGreaterThan(0),
116
+ );
117
+ });
118
+
119
+ await step('Close the combobox', async () => {
120
+ await userEvent.keyboard('{Escape}');
121
+ await wait();
122
+ });
123
+
124
+ await step('Verify the page has not scrolled', async () => {
125
+ const scrollParent = canvasElement.ownerDocument.documentElement;
126
+ await expect(scrollParent.scrollTop).toBe(0);
127
+ });
128
+ },
129
+ });
130
+
131
+ export const NoScrollAfterCloseTop: Story = generateNoScrollTest();
132
+ export const NoScrollAfterCloseBottom: Story = generateNoScrollTest(true);
@@ -360,9 +360,6 @@ export default function Select({
360
360
  const handleCloseOptions = () => {
361
361
  setOpen(false);
362
362
  setKeyboardFocusedOptionIndex(-1);
363
- if (dropdownButtonReference.current) {
364
- dropdownButtonReference.current.focus();
365
- }
366
363
  };
367
364
 
368
365
  function createSelectHandlerForOption(option: SelectItemWithPlaceholder) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Do not edit directly, this file was auto-generated.
3
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
3
+ * Generated on Wed, 13 May 2026 12:45:11 GMT
4
4
  */
5
5
 
6
6
  :root {
@@ -144,7 +144,7 @@
144
144
 
145
145
  /**
146
146
  * Do not edit directly, this file was auto-generated.
147
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
147
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
148
148
  */
149
149
 
150
150
  .np-theme-personal {
@@ -326,75 +326,9 @@
326
326
  --font-family-display: 'Wise Sans', 'Inter', sans-serif;
327
327
  }
328
328
 
329
- /**
330
- * We added new shape theme into tokens to prevent breaking changes. This is a temporary measure.
331
- *
332
- * We delete this hack once all consumers no longer import tokens in their projects (e.g Next.js app.tsx or Storybook's preivew.tsx)
333
- */
334
-
335
- @media (max-width: 320px) {
336
- .np-theme-personal {
337
- --delta: 2;
338
- --size-4: calc(4px / var(--delta));
339
- --size-5: calc(5px / var(--delta));
340
- --size-8: calc(8px / var(--delta));
341
- --size-10: calc(10px / var(--delta));
342
- --size-12: calc(12px / var(--delta));
343
- --size-14: calc(14px / var(--delta));
344
- --size-16: calc(16px / var(--delta));
345
- --size-24: calc(24px / var(--delta));
346
- --size-32: calc(32px / var(--delta));
347
- --size-40: calc(40px / var(--delta));
348
- --size-48: calc(48px / var(--delta));
349
- --size-52: calc(52px / var(--delta));
350
- --size-56: calc(56px / var(--delta));
351
- --size-60: calc(60px / var(--delta));
352
- --size-64: calc(64px / var(--delta));
353
- --size-72: calc(72px / var(--delta));
354
- --size-80: calc(80px / var(--delta));
355
- --size-88: calc(88px / var(--delta));
356
- --size-96: calc(96px / var(--delta));
357
- --size-104: calc(104px / var(--delta));
358
- --size-112: calc(112px / var(--delta));
359
- --size-120: calc(120px / var(--delta));
360
- --size-126: calc(126px / var(--delta));
361
- --size-128: calc(128px / var(--delta));
362
- --size-146: calc(146px / var(--delta));
363
- --size-154: calc(154px / var(--delta));
364
- --size-x-small: calc(24px / var(--delta));
365
- --size-small: calc(32px / var(--delta));
366
- --size-medium: calc(40px / var(--delta));
367
- --size-large: calc(48px / var(--delta));
368
- --size-x-large: calc(56px / var(--delta));
369
- --size-2x-large: calc(72px / var(--delta));
370
- --space-content-horizontal: calc(16px / var(--delta));
371
- --space-small: calc(16px / var(--delta));
372
- --space-medium: calc(32px / var(--delta));
373
- --space-large: calc(40px / var(--delta));
374
- --space-x-large: calc(56px / var(--delta));
375
- --padding-x-small: var(--size-8);
376
- --padding-small: var(--size-16);
377
- --padding-medium: var(--size-24);
378
- --padding-large: var(--size-32);
379
- --input-height-base: var(--size-32);
380
- --input-height-large: var(--input-height-small);
381
- --input-padding: var(--input-padding-small);
382
- --input-padding-large: var(--input-padding-small);
383
- --input-group-addon-padding: var(--input-group-addon-sm-padding);
384
- --input-group-addon-lg-padding: var(--input-group-addon-sm-padding);
385
- --btn-height: var(--input-height-base);
386
- --btn-lg-height: var(--btn-height);
387
- --btn-sm-height: var(--btn-height);
388
- --btn-padding: var(--input-padding);
389
- --btn-sm-padding: var(--btn-padding);
390
- --btn-lg-padding: var(--btn-padding);
391
- --dropdown-link-padding: var(--size-12) var(--size-16);
392
- }
393
- }
394
-
395
329
  /**
396
330
  * Do not edit directly, this file was auto-generated.
397
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
331
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
398
332
  */
399
333
 
400
334
  .np-theme-personal--forest-green {
@@ -576,75 +510,9 @@
576
510
  --font-family-display: 'Wise Sans', 'Inter', sans-serif;
577
511
  }
578
512
 
579
- /**
580
- * We added new shape theme into tokens to prevent breaking changes. This is a temporary measure.
581
- *
582
- * We delete this hack once all consumers no longer import tokens in their projects (e.g Next.js app.tsx or Storybook's preivew.tsx)
583
- */
584
-
585
- @media (max-width: 320px) {
586
- .np-theme-personal {
587
- --delta: 2;
588
- --size-4: calc(4px / var(--delta));
589
- --size-5: calc(5px / var(--delta));
590
- --size-8: calc(8px / var(--delta));
591
- --size-10: calc(10px / var(--delta));
592
- --size-12: calc(12px / var(--delta));
593
- --size-14: calc(14px / var(--delta));
594
- --size-16: calc(16px / var(--delta));
595
- --size-24: calc(24px / var(--delta));
596
- --size-32: calc(32px / var(--delta));
597
- --size-40: calc(40px / var(--delta));
598
- --size-48: calc(48px / var(--delta));
599
- --size-52: calc(52px / var(--delta));
600
- --size-56: calc(56px / var(--delta));
601
- --size-60: calc(60px / var(--delta));
602
- --size-64: calc(64px / var(--delta));
603
- --size-72: calc(72px / var(--delta));
604
- --size-80: calc(80px / var(--delta));
605
- --size-88: calc(88px / var(--delta));
606
- --size-96: calc(96px / var(--delta));
607
- --size-104: calc(104px / var(--delta));
608
- --size-112: calc(112px / var(--delta));
609
- --size-120: calc(120px / var(--delta));
610
- --size-126: calc(126px / var(--delta));
611
- --size-128: calc(128px / var(--delta));
612
- --size-146: calc(146px / var(--delta));
613
- --size-154: calc(154px / var(--delta));
614
- --size-x-small: calc(24px / var(--delta));
615
- --size-small: calc(32px / var(--delta));
616
- --size-medium: calc(40px / var(--delta));
617
- --size-large: calc(48px / var(--delta));
618
- --size-x-large: calc(56px / var(--delta));
619
- --size-2x-large: calc(72px / var(--delta));
620
- --space-content-horizontal: calc(16px / var(--delta));
621
- --space-small: calc(16px / var(--delta));
622
- --space-medium: calc(32px / var(--delta));
623
- --space-large: calc(40px / var(--delta));
624
- --space-x-large: calc(56px / var(--delta));
625
- --padding-x-small: var(--size-8);
626
- --padding-small: var(--size-16);
627
- --padding-medium: var(--size-24);
628
- --padding-large: var(--size-32);
629
- --input-height-base: var(--size-32);
630
- --input-height-large: var(--input-height-small);
631
- --input-padding: var(--input-padding-small);
632
- --input-padding-large: var(--input-padding-small);
633
- --input-group-addon-padding: var(--input-group-addon-sm-padding);
634
- --input-group-addon-lg-padding: var(--input-group-addon-sm-padding);
635
- --btn-height: var(--input-height-base);
636
- --btn-lg-height: var(--btn-height);
637
- --btn-sm-height: var(--btn-height);
638
- --btn-padding: var(--input-padding);
639
- --btn-sm-padding: var(--btn-padding);
640
- --btn-lg-padding: var(--btn-padding);
641
- --dropdown-link-padding: var(--size-12) var(--size-16);
642
- }
643
- }
644
-
645
513
  /**
646
514
  * Do not edit directly, this file was auto-generated.
647
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
515
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
648
516
  */
649
517
 
650
518
  .np-theme-personal--bright-green {
@@ -826,75 +694,9 @@
826
694
  --font-family-display: 'Wise Sans', 'Inter', sans-serif;
827
695
  }
828
696
 
829
- /**
830
- * We added new shape theme into tokens to prevent breaking changes. This is a temporary measure.
831
- *
832
- * We delete this hack once all consumers no longer import tokens in their projects (e.g Next.js app.tsx or Storybook's preivew.tsx)
833
- */
834
-
835
- @media (max-width: 320px) {
836
- .np-theme-personal {
837
- --delta: 2;
838
- --size-4: calc(4px / var(--delta));
839
- --size-5: calc(5px / var(--delta));
840
- --size-8: calc(8px / var(--delta));
841
- --size-10: calc(10px / var(--delta));
842
- --size-12: calc(12px / var(--delta));
843
- --size-14: calc(14px / var(--delta));
844
- --size-16: calc(16px / var(--delta));
845
- --size-24: calc(24px / var(--delta));
846
- --size-32: calc(32px / var(--delta));
847
- --size-40: calc(40px / var(--delta));
848
- --size-48: calc(48px / var(--delta));
849
- --size-52: calc(52px / var(--delta));
850
- --size-56: calc(56px / var(--delta));
851
- --size-60: calc(60px / var(--delta));
852
- --size-64: calc(64px / var(--delta));
853
- --size-72: calc(72px / var(--delta));
854
- --size-80: calc(80px / var(--delta));
855
- --size-88: calc(88px / var(--delta));
856
- --size-96: calc(96px / var(--delta));
857
- --size-104: calc(104px / var(--delta));
858
- --size-112: calc(112px / var(--delta));
859
- --size-120: calc(120px / var(--delta));
860
- --size-126: calc(126px / var(--delta));
861
- --size-128: calc(128px / var(--delta));
862
- --size-146: calc(146px / var(--delta));
863
- --size-154: calc(154px / var(--delta));
864
- --size-x-small: calc(24px / var(--delta));
865
- --size-small: calc(32px / var(--delta));
866
- --size-medium: calc(40px / var(--delta));
867
- --size-large: calc(48px / var(--delta));
868
- --size-x-large: calc(56px / var(--delta));
869
- --size-2x-large: calc(72px / var(--delta));
870
- --space-content-horizontal: calc(16px / var(--delta));
871
- --space-small: calc(16px / var(--delta));
872
- --space-medium: calc(32px / var(--delta));
873
- --space-large: calc(40px / var(--delta));
874
- --space-x-large: calc(56px / var(--delta));
875
- --padding-x-small: var(--size-8);
876
- --padding-small: var(--size-16);
877
- --padding-medium: var(--size-24);
878
- --padding-large: var(--size-32);
879
- --input-height-base: var(--size-32);
880
- --input-height-large: var(--input-height-small);
881
- --input-padding: var(--input-padding-small);
882
- --input-padding-large: var(--input-padding-small);
883
- --input-group-addon-padding: var(--input-group-addon-sm-padding);
884
- --input-group-addon-lg-padding: var(--input-group-addon-sm-padding);
885
- --btn-height: var(--input-height-base);
886
- --btn-lg-height: var(--btn-height);
887
- --btn-sm-height: var(--btn-height);
888
- --btn-padding: var(--input-padding);
889
- --btn-sm-padding: var(--btn-padding);
890
- --btn-lg-padding: var(--btn-padding);
891
- --dropdown-link-padding: var(--size-12) var(--size-16);
892
- }
893
- }
894
-
895
697
  /**
896
698
  * Do not edit directly, this file was auto-generated.
897
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
699
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
898
700
  */
899
701
 
900
702
  .np-theme-personal--dark {
@@ -1076,75 +878,9 @@
1076
878
  --font-family-display: 'Wise Sans', 'Inter', sans-serif;
1077
879
  }
1078
880
 
1079
- /**
1080
- * We added new shape theme into tokens to prevent breaking changes. This is a temporary measure.
1081
- *
1082
- * We delete this hack once all consumers no longer import tokens in their projects (e.g Next.js app.tsx or Storybook's preivew.tsx)
1083
- */
1084
-
1085
- @media (max-width: 320px) {
1086
- .np-theme-personal {
1087
- --delta: 2;
1088
- --size-4: calc(4px / var(--delta));
1089
- --size-5: calc(5px / var(--delta));
1090
- --size-8: calc(8px / var(--delta));
1091
- --size-10: calc(10px / var(--delta));
1092
- --size-12: calc(12px / var(--delta));
1093
- --size-14: calc(14px / var(--delta));
1094
- --size-16: calc(16px / var(--delta));
1095
- --size-24: calc(24px / var(--delta));
1096
- --size-32: calc(32px / var(--delta));
1097
- --size-40: calc(40px / var(--delta));
1098
- --size-48: calc(48px / var(--delta));
1099
- --size-52: calc(52px / var(--delta));
1100
- --size-56: calc(56px / var(--delta));
1101
- --size-60: calc(60px / var(--delta));
1102
- --size-64: calc(64px / var(--delta));
1103
- --size-72: calc(72px / var(--delta));
1104
- --size-80: calc(80px / var(--delta));
1105
- --size-88: calc(88px / var(--delta));
1106
- --size-96: calc(96px / var(--delta));
1107
- --size-104: calc(104px / var(--delta));
1108
- --size-112: calc(112px / var(--delta));
1109
- --size-120: calc(120px / var(--delta));
1110
- --size-126: calc(126px / var(--delta));
1111
- --size-128: calc(128px / var(--delta));
1112
- --size-146: calc(146px / var(--delta));
1113
- --size-154: calc(154px / var(--delta));
1114
- --size-x-small: calc(24px / var(--delta));
1115
- --size-small: calc(32px / var(--delta));
1116
- --size-medium: calc(40px / var(--delta));
1117
- --size-large: calc(48px / var(--delta));
1118
- --size-x-large: calc(56px / var(--delta));
1119
- --size-2x-large: calc(72px / var(--delta));
1120
- --space-content-horizontal: calc(16px / var(--delta));
1121
- --space-small: calc(16px / var(--delta));
1122
- --space-medium: calc(32px / var(--delta));
1123
- --space-large: calc(40px / var(--delta));
1124
- --space-x-large: calc(56px / var(--delta));
1125
- --padding-x-small: var(--size-8);
1126
- --padding-small: var(--size-16);
1127
- --padding-medium: var(--size-24);
1128
- --padding-large: var(--size-32);
1129
- --input-height-base: var(--size-32);
1130
- --input-height-large: var(--input-height-small);
1131
- --input-padding: var(--input-padding-small);
1132
- --input-padding-large: var(--input-padding-small);
1133
- --input-group-addon-padding: var(--input-group-addon-sm-padding);
1134
- --input-group-addon-lg-padding: var(--input-group-addon-sm-padding);
1135
- --btn-height: var(--input-height-base);
1136
- --btn-lg-height: var(--btn-height);
1137
- --btn-sm-height: var(--btn-height);
1138
- --btn-padding: var(--input-padding);
1139
- --btn-sm-padding: var(--btn-padding);
1140
- --btn-lg-padding: var(--btn-padding);
1141
- --dropdown-link-padding: var(--size-12) var(--size-16);
1142
- }
1143
- }
1144
-
1145
881
  /**
1146
882
  * Do not edit directly, this file was auto-generated.
1147
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
883
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
1148
884
  */
1149
885
 
1150
886
  .np-theme-platform {
@@ -1328,7 +1064,7 @@
1328
1064
 
1329
1065
  /**
1330
1066
  * Do not edit directly, this file was auto-generated.
1331
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
1067
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
1332
1068
  */
1333
1069
 
1334
1070
  .np-theme-platform--forest-green {
@@ -1512,7 +1248,7 @@
1512
1248
 
1513
1249
  /**
1514
1250
  * Do not edit directly, this file was auto-generated.
1515
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
1251
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
1516
1252
  */
1517
1253
 
1518
1254
  .np-theme-business {
@@ -1697,7 +1433,7 @@
1697
1433
 
1698
1434
  /**
1699
1435
  * Do not edit directly, this file was auto-generated.
1700
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
1436
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
1701
1437
  */
1702
1438
 
1703
1439
  .np-theme-business--dark {
@@ -1882,7 +1618,7 @@
1882
1618
 
1883
1619
  /**
1884
1620
  * Do not edit directly, this file was auto-generated.
1885
- * Generated on Fri, 10 Apr 2026 14:46:00 GMT
1621
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
1886
1622
  */
1887
1623
 
1888
1624
  .np-theme-business--forest-green {
@@ -2067,7 +1803,7 @@
2067
1803
 
2068
1804
  /**
2069
1805
  * Do not edit directly, this file was auto-generated.
2070
- * Generated on Fri, 10 Apr 2026 14:46:01 GMT
1806
+ * Generated on Wed, 13 May 2026 12:45:12 GMT
2071
1807
  */
2072
1808
 
2073
1809
  .np-theme-business--bright-green {