@transferwise/components 45.21.1 → 45.21.3

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.
@@ -1 +1 @@
1
- .tw-telephone{display:flex}.tw-telephone .tw-telephone__number-input{margin-left:12px;margin-left:var(--size-12)}[dir=rtl] .tw-telephone .tw-telephone__number-input{margin-left:0;margin-right:12px;margin-right:var(--size-12)}.tw-telephone__country-select{flex-basis:120px;flex-shrink:0}.tw-telephone__number-input{flex:auto 1 1}
1
+ .tw-telephone{display:flex}.tw-telephone .tw-telephone__number-input{margin-left:12px;margin-left:var(--size-12)}[dir=rtl] .tw-telephone .tw-telephone__number-input{margin-left:0;margin-right:12px;margin-right:var(--size-12)}.tw-telephone__country-select{flex-basis:120px;flex-shrink:0}.tw-telephone__country-select .np-input-group{width:100%}.tw-telephone__number-input{flex:auto 1 1}
@@ -4,16 +4,14 @@ import { useState, useEffect } from 'react';
4
4
  import { useIntl } from 'react-intl';
5
5
 
6
6
  import { Size } from '../common';
7
- import Select from '../select';
7
+ import { SelectInput, SelectInputOptionContent } from '../inputs/SelectInput';
8
8
 
9
9
  import countries from './data/countries';
10
10
  import {
11
11
  explodeNumberModel,
12
- filterOptionsForQuery,
13
12
  isValidPhoneNumber,
14
13
  cleanNumber,
15
14
  setDefaultPrefix,
16
- isStringNumeric,
17
15
  sortArrayByProperty,
18
16
  groupCountriesByPrefix,
19
17
  excludeCountries,
@@ -55,21 +53,13 @@ const PhoneNumberInput = (props) => {
55
53
 
56
54
  const [internalValue, setInternalValue] = useState(getInitialValue());
57
55
  const [broadcastedValue, setBroadcastedValue] = useState(null);
58
- const [searchQuery, setSearchQuery] = useState('');
59
-
60
- const countriesList = excludeCountries(countries, disabledCountries);
61
-
62
- const listSortedByISO3 = groupCountriesByPrefix(sortArrayByProperty(countriesList, 'iso3'));
63
- const listSortedByPhone = groupCountriesByPrefix(sortArrayByProperty(countriesList, 'phone'));
64
56
 
65
57
  const getSelectOptions = () => {
66
- const filteredOptions = filterOptionsForQuery(
67
- isStringNumeric(searchQuery) ? listSortedByPhone : listSortedByISO3,
68
- searchQuery,
69
- );
58
+ const countriesList = excludeCountries(countries, disabledCountries);
59
+ const listSortedByISO3 = groupCountriesByPrefix(sortArrayByProperty(countriesList, 'iso3'));
70
60
 
71
- return filteredOptions.map((option) => {
72
- const { phone, iso3, iso2 } = option;
61
+ return listSortedByISO3.map((option) => {
62
+ const { phone, iso3, iso2, name } = option;
73
63
  let note = '';
74
64
 
75
65
  if (iso3) {
@@ -78,14 +68,21 @@ const PhoneNumberInput = (props) => {
78
68
  note = isArray(iso2) ? iso2.join(', ') : iso2;
79
69
  }
80
70
 
81
- return { value: phone, label: phone, note };
71
+ return {
72
+ type: 'option',
73
+ value: {
74
+ value: phone,
75
+ label: phone,
76
+ note: note,
77
+ },
78
+ filterMatchers: [phone, note, name],
79
+ };
82
80
  });
83
81
  };
84
82
 
85
83
  const options = getSelectOptions();
86
84
 
87
85
  const onPrefixChange = ({ value }) => {
88
- setSearchQuery('');
89
86
  setInternalValue({ prefix: value, suffix: internalValue.suffix });
90
87
  };
91
88
 
@@ -101,12 +98,13 @@ const PhoneNumberInput = (props) => {
101
98
  if (!event.nativeEvent.clipboardData) {
102
99
  return;
103
100
  }
101
+
104
102
  const pastedValue = (event.nativeEvent.clipboardData.getData('text/plain') || '').replace(
105
103
  /(\s|-)+/g,
106
104
  '',
107
105
  );
108
106
  const { prefix: pastedPrefix, suffix: pastedSuffix } = explodeNumberModel(pastedValue);
109
- const selectedPrefix = options.find(({ value }) => value === pastedPrefix);
107
+ const selectedPrefix = options.find(({ value }) => value.value === pastedPrefix);
110
108
 
111
109
  if (selectedPrefix && ALLOWED_PHONE_CHARS.test(pastedSuffix)) {
112
110
  setInternalValue({ prefix: pastedPrefix, suffix: pastedSuffix });
@@ -136,23 +134,24 @@ const PhoneNumberInput = (props) => {
136
134
  return (
137
135
  <div className="tw-telephone">
138
136
  <div className="tw-telephone__country-select">
139
- <Select
140
- id={id ? `${id}-select` : undefined}
141
- options={options}
142
- selected={{ value: internalValue.prefix, label: internalValue.prefix }}
137
+ <SelectInput
143
138
  placeholder="Select an option..."
144
- searchPlaceholder={searchPlaceholder}
145
- dropdownWidth={Size.SMALL}
146
- searchValue={searchQuery}
147
- required={required}
139
+ items={options}
140
+ value={options.find((item) => item.value.value === internalValue.prefix)?.value}
141
+ renderValue={(option, withinTrigger) => (
142
+ <SelectInputOptionContent
143
+ title={option.label}
144
+ note={withinTrigger ? undefined : option.note}
145
+ />
146
+ )}
147
+ filterable
148
+ filterPlaceholder={searchPlaceholder}
148
149
  disabled={disabled}
149
150
  size={size}
150
151
  onChange={onPrefixChange}
151
- onSearchChange={(newSearch) => setSearchQuery(newSearch)}
152
152
  {...selectProps}
153
153
  />
154
154
  </div>
155
-
156
155
  <div className="tw-telephone__number-input">
157
156
  <div className={`input-group input-group-${size}`}>
158
157
  <input
@@ -13,6 +13,10 @@
13
13
  .tw-telephone__country-select {
14
14
  flex-basis: @four-digit-country-code-width;
15
15
  flex-shrink: 0;
16
+
17
+ .np-input-group {
18
+ width: 100%;
19
+ }
16
20
  }
17
21
 
18
22
  .tw-telephone__number-input {
@@ -1,12 +1,29 @@
1
1
  import { shallow, mount } from 'enzyme';
2
2
  import { useIntl } from 'react-intl';
3
3
 
4
- import { fakeEvent } from '../common/fakeEvents';
5
-
6
4
  import PhoneNumberInput from '.';
7
5
 
8
6
  jest.mock('react-intl');
9
7
 
8
+ Object.defineProperty(window, 'matchMedia', {
9
+ writable: true,
10
+ value: jest.fn().mockImplementation((query) => ({
11
+ matches: false,
12
+ media: query,
13
+ onchange: null,
14
+ addListener: jest.fn(), // Deprecated
15
+ removeListener: jest.fn(), // Deprecated
16
+ addEventListener: jest.fn(),
17
+ removeEventListener: jest.fn(),
18
+ dispatchEvent: jest.fn(),
19
+ })),
20
+ });
21
+
22
+ class ResizeObserver {
23
+ observe() {}
24
+ unobserve() {}
25
+ }
26
+
10
27
  const simulatePaste = (element, value) =>
11
28
  element.simulate('paste', { nativeEvent: { clipboardData: { getData: () => value } } });
12
29
 
@@ -15,7 +32,7 @@ describe('Given a telephone number component', () => {
15
32
  let input;
16
33
  let component;
17
34
  const props = { onChange: jest.fn() };
18
- const PREFIX_SELECT_SELECTOR = 'Select';
35
+ const PREFIX_SELECT_SELECTOR = 'SelectInput';
19
36
  const NUMBER_SELECTOR = 'input[name="phoneNumber"]';
20
37
 
21
38
  beforeEach(() => {
@@ -34,14 +51,21 @@ describe('Given a telephone number component', () => {
34
51
  });
35
52
 
36
53
  it('should set prefix control to default UK value', () => {
37
- expect(select.props().selected).toStrictEqual({ value: '+44', label: '+44' });
54
+ expect(select.props().value).toStrictEqual({
55
+ value: '+44',
56
+ note: 'GBR, GGY, IMN, JEY',
57
+ label: '+44',
58
+ });
38
59
  });
60
+
39
61
  it('should set number control to empty', () => {
40
62
  expect(input.prop('value')).toBe('');
41
63
  });
64
+
42
65
  it('should not disable the select', () => {
43
66
  expect(select.prop('disabled')).toBe(false);
44
67
  });
68
+
45
69
  it('should not disable the input', () => {
46
70
  expect(input.prop('disabled')).toBe(false);
47
71
  });
@@ -55,7 +79,7 @@ describe('Given a telephone number component', () => {
55
79
  });
56
80
 
57
81
  it('should set control values correctly', () => {
58
- expect(select.props().selected.value).toStrictEqual('+39');
82
+ expect(select.props().value.value).toStrictEqual('+39');
59
83
  expect(input.prop('value')).toBe('123456789');
60
84
  });
61
85
  });
@@ -67,28 +91,29 @@ describe('Given a telephone number component', () => {
67
91
  input = component.find(NUMBER_SELECTOR);
68
92
  });
69
93
 
70
- it('should render input with expected id', () => {
71
- expect(input.prop('id')).toBe('mock-id');
94
+ it('should render select with undefined id', () => {
95
+ expect(select.prop('id')).toBeUndefined();
72
96
  });
73
97
 
74
- it('should render select with expected id', () => {
75
- expect(select.prop('id')).toBe('mock-id-select');
98
+ it('should render input with expected id', () => {
99
+ expect(input.prop('id')).toBe('mock-id');
76
100
  });
77
101
  });
78
102
 
79
103
  describe('when an id is not supplied', () => {
80
104
  beforeEach(() => {
81
105
  component = shallow(<PhoneNumberInput {...props} />);
82
- select = component.find(PREFIX_SELECT_SELECTOR);
83
106
  input = component.find(NUMBER_SELECTOR);
84
107
  });
85
108
 
86
- it('should render select with undefined id', () => {
87
- expect(select.prop('id')).toBeUndefined();
109
+ it('should render input with null id', () => {
110
+ expect(input.prop('id')).toBeNull();
88
111
  });
89
112
  });
90
113
 
91
114
  describe('when pasting', () => {
115
+ window.ResizeObserver = ResizeObserver;
116
+
92
117
  beforeEach(() => {
93
118
  component = mount(<PhoneNumberInput {...props} initialValue="+39123456789" />);
94
119
  select = () => component.find(PREFIX_SELECT_SELECTOR);
@@ -108,7 +133,8 @@ describe('Given a telephone number component', () => {
108
133
  ].forEach(({ number, countryCode, localNumber }) => {
109
134
  it(`${number} code should update the value properly`, () => {
110
135
  simulatePaste(component.find('input'), number);
111
- expect(select().props().selected.value).toStrictEqual(countryCode);
136
+
137
+ expect(select().props().value.value).toStrictEqual(countryCode);
112
138
  expect(input().prop('value')).toBe(localNumber);
113
139
  expect(props.onChange).toHaveBeenCalledWith(number.replace(/(\s|-)+/g, ''), countryCode);
114
140
  });
@@ -116,28 +142,28 @@ describe('Given a telephone number component', () => {
116
142
 
117
143
  it('should not paste invalid characters', () => {
118
144
  simulatePaste(component.find('input'), '+36asdasdasd');
119
- expect(select().props().selected.value).toStrictEqual('+39');
145
+ expect(select().props().value.value).toStrictEqual('+39');
120
146
  expect(input().prop('value')).toBe('123456789');
121
147
  expect(props.onChange).not.toHaveBeenCalled();
122
148
  });
123
149
 
124
150
  it('should not paste countries which are not in the select', () => {
125
151
  simulatePaste(component.find('input'), '+9992342343423');
126
- expect(select().props().selected.value).toStrictEqual('+39');
152
+ expect(select().props().value.value).toStrictEqual('+39');
127
153
  expect(input().prop('value')).toBe('123456789');
128
154
  expect(props.onChange).not.toHaveBeenCalled();
129
155
  });
130
156
 
131
157
  it("should not paste numbers which doesn't start with the country code", () => {
132
158
  simulatePaste(component.find('input'), '0+36303932551');
133
- expect(select().props().selected.value).toStrictEqual('+39');
159
+ expect(select().props().value.value).toStrictEqual('+39');
134
160
  expect(input().prop('value')).toBe('123456789');
135
161
  expect(props.onChange).not.toHaveBeenCalled();
136
162
  });
137
163
 
138
164
  it("should not paste numbers which doesn't contain a country code", () => {
139
165
  simulatePaste(component.find('input'), '06303932551');
140
- expect(select().props().selected.value).toStrictEqual('+39');
166
+ expect(select().props().value.value).toStrictEqual('+39');
141
167
  expect(input().prop('value')).toBe('123456789');
142
168
  expect(props.onChange).not.toHaveBeenCalled();
143
169
  });
@@ -151,8 +177,9 @@ describe('Given a telephone number component', () => {
151
177
  });
152
178
 
153
179
  it('should set the select to the longest matching prefix', () => {
154
- expect(select.props().selected.value).toStrictEqual('+1868');
180
+ expect(select.props().value.value).toStrictEqual('+1868');
155
181
  });
182
+
156
183
  it('should set the number input to the rest of the number', () => {
157
184
  expect(input.prop('value')).toBe('123456789');
158
185
  });
@@ -166,8 +193,9 @@ describe('Given a telephone number component', () => {
166
193
  });
167
194
 
168
195
  it('should empty the select', () => {
169
- expect(select.props().selected.value).toStrictEqual('');
196
+ expect(select.props().value).toBeUndefined();
170
197
  });
198
+
171
199
  it('should put the whole value in the input without the plus', () => {
172
200
  expect(input.prop('value')).toBe('999123456789');
173
201
  });
@@ -176,10 +204,10 @@ describe('Given a telephone number component', () => {
176
204
  describe('when an invalid model is supplied', () => {
177
205
  it('should not re-explode model', () => {
178
206
  component = shallow(<PhoneNumberInput {...props} initialValue="+123" />);
179
-
180
207
  select = component.find(PREFIX_SELECT_SELECTOR);
181
208
  input = component.find(NUMBER_SELECTOR);
182
- expect(select.props().selected.value).toStrictEqual('+44');
209
+
210
+ expect(select.props().value.value).toStrictEqual('+44');
183
211
  expect(input.prop('value')).toBe('');
184
212
  });
185
213
  });
@@ -194,6 +222,7 @@ describe('Given a telephone number component', () => {
194
222
  it('should disable the select', () => {
195
223
  expect(select.prop('disabled')).toBe(true);
196
224
  });
225
+
197
226
  it('should disable the input', () => {
198
227
  expect(input.prop('disabled')).toBe(true);
199
228
  });
@@ -227,7 +256,7 @@ describe('Given a telephone number component', () => {
227
256
  });
228
257
 
229
258
  it('should use the provided searchPlaceholder', () => {
230
- expect(select.prop('searchPlaceholder')).toBe('searchPlaceholder');
259
+ expect(select.prop('filterPlaceholder')).toBe('searchPlaceholder');
231
260
  });
232
261
  });
233
262
 
@@ -241,7 +270,7 @@ describe('Given a telephone number component', () => {
241
270
  });
242
271
 
243
272
  it('should use the prefix of the supplied value', () => {
244
- expect(select.props().selected.value).toBe('+1');
273
+ expect(select.props().value.value).toBe('+1');
245
274
  });
246
275
  });
247
276
 
@@ -255,7 +284,7 @@ describe('Given a telephone number component', () => {
255
284
  });
256
285
 
257
286
  it('should default the prefix to the local country', () => {
258
- expect(select.props().selected.value).toBe('+34');
287
+ expect(select.props().value.value).toBe('+34');
259
288
  });
260
289
  });
261
290
 
@@ -268,7 +297,7 @@ describe('Given a telephone number component', () => {
268
297
  });
269
298
 
270
299
  it('should override locale prefix with country specific prefix', () => {
271
- expect(select.props().selected.value).toBe('+1');
300
+ expect(select.props().value.value).toBe('+1');
272
301
  });
273
302
  });
274
303
  });
@@ -277,37 +306,31 @@ describe('Given a telephone number component', () => {
277
306
  describe('when user insert valid value', () => {
278
307
  beforeEach(() => {
279
308
  component = mount(<PhoneNumberInput {...props} />);
280
- input = component.find(NUMBER_SELECTOR);
281
309
  select = component.find(PREFIX_SELECT_SELECTOR);
310
+ input = component.find(NUMBER_SELECTOR);
282
311
  });
283
312
 
284
313
  it('should trigger onChange handler', () => {
285
314
  input.simulate('change', { target: { value: '123' } });
286
315
  expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
287
316
  });
288
-
289
- it('should trigger onChange handler and set previousReturned value', () => {
290
- changeSelectValue('+39');
291
- input.simulate('change', { target: { value: '123' } });
292
- expect(props.onChange).toHaveBeenCalledWith('+39123', '+39');
293
- });
294
317
  });
295
318
 
296
319
  describe('when user insert an invalid number', () => {
297
320
  it('should trigger onChange with null value', () => {
298
321
  component = mount(<PhoneNumberInput {...props} initialValue="+12345678" />);
299
322
  input = component.find(NUMBER_SELECTOR);
300
- select = component.find(PREFIX_SELECT_SELECTOR);
323
+
301
324
  input.simulate('change', { target: { value: '1' } });
302
- select.simulate('change', { value: '+39', label: '+39' });
303
325
  expect(props.onChange).toHaveBeenCalledWith(null, '+1');
304
326
  });
305
327
 
306
328
  it("shouldn't re-trigger onChange and set previousReturned state", () => {
307
329
  component = shallow(<PhoneNumberInput {...props} />);
308
- input = component.find(NUMBER_SELECTOR);
309
330
  select = component.find(PREFIX_SELECT_SELECTOR);
310
- select.simulate('change', { value: '+1', label: '+1' });
331
+ input = component.find(NUMBER_SELECTOR);
332
+
333
+ select.simulate('change', { value: { value: '+1', label: '+1' } });
311
334
  input.simulate('change', { target: { value: '12' } });
312
335
  expect(props.onChange).not.toHaveBeenCalled();
313
336
  });
@@ -317,30 +340,15 @@ describe('Given a telephone number component', () => {
317
340
  it('should strip them', () => {
318
341
  component = mount(<PhoneNumberInput {...props} value="+12345678" />);
319
342
  input = component.find(NUMBER_SELECTOR);
320
- select = component.find(PREFIX_SELECT_SELECTOR);
321
- input.simulate('change', { target: { value: '123--' } });
322
- changeSelectValue('+39');
323
- expect(props.onChange).toHaveBeenCalledWith('+39123', '+39');
324
- });
325
- });
326
343
 
327
- describe('when user search by number options are sorted by phone values', () => {
328
- it('should return sorted by value options', () => {
329
- component = mount(<PhoneNumberInput {...props} value="+12345678" />);
330
-
331
- component.find('button.np-dropdown-toggle').simulate('click', fakeEvent());
332
- component
333
- .find({ className: 'np-select-filter' })
334
- .simulate('change', { target: { value: '+124' } });
335
- select = component.find(PREFIX_SELECT_SELECTOR);
336
- expect(+select.prop('options')[0].value).toBeLessThan(+select.prop('options')[1].value);
344
+ input.simulate('change', { target: { value: '123--' } });
345
+ expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
337
346
  });
338
347
  });
339
348
 
340
349
  describe('overlapping prefix and suffix numbers', () => {
341
350
  it("shouldn't change the prefix number on matching suffix input", () => {
342
351
  component = mount(<PhoneNumberInput {...props} countryCode="eg" />);
343
-
344
352
  component.find(NUMBER_SELECTOR).simulate('change', { target: { value: '1111111' } });
345
353
 
346
354
  expect(component.find(NUMBER_SELECTOR).props().value).toBe('1111111');
@@ -351,29 +359,13 @@ describe('Given a telephone number component', () => {
351
359
  describe('when selectProps is supplied', () => {
352
360
  beforeEach(() => {
353
361
  component = shallow(
354
- <PhoneNumberInput
355
- selectProps={{
356
- buttonProps: {
357
- 'aria-label': 'mock-button-label',
358
- },
359
- }}
360
- {...props}
361
- />,
362
+ <PhoneNumberInput selectProps={{ className: 'custom-class' }} {...props} />,
362
363
  );
363
364
  });
364
365
 
365
366
  it('renders Select component with expected props', () => {
366
- const select = component.find('Select');
367
-
368
- expect(select.prop('buttonProps')).toStrictEqual({
369
- 'aria-label': 'mock-button-label',
370
- });
367
+ const select = component.find(PREFIX_SELECT_SELECTOR);
368
+ expect(select.prop('className')).toStrictEqual('custom-class');
371
369
  });
372
370
  });
373
-
374
- const changeSelectValue = (value) => {
375
- component.find('button.np-dropdown-toggle').simulate('click', fakeEvent());
376
- component.find({ className: 'np-select-filter' }).simulate('change', { target: { value } });
377
- component.find('.np-dropdown-item.clickable').simulate('click', fakeEvent());
378
- };
379
371
  });
@@ -12,9 +12,7 @@ export const Basic = () => {
12
12
  const required = boolean('required', false);
13
13
  const size = select('size', ['sm', 'md', 'lg'], 'md');
14
14
  const selectProps = object('selectProps', {
15
- buttonProps: {
16
- 'aria-label': 'Select country code',
17
- },
15
+ className: 'custom-class',
18
16
  });
19
17
 
20
18
  return (