@transferwise/components 0.0.0-experimental-eb6ccd6 → 0.0.0-experimental-a81440a

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,20 +53,12 @@ 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) => {
61
+ return listSortedByISO3.map((option) => {
72
62
  const { phone, iso3, iso2 } = option;
73
63
  let note = '';
74
64
 
@@ -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],
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={{ value: internalValue.prefix, label: internalValue.prefix }}
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,17 @@ 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({ value: '+44', label: '+44' });
38
55
  });
56
+
39
57
  it('should set number control to empty', () => {
40
58
  expect(input.prop('value')).toBe('');
41
59
  });
60
+
42
61
  it('should not disable the select', () => {
43
62
  expect(select.prop('disabled')).toBe(false);
44
63
  });
64
+
45
65
  it('should not disable the input', () => {
46
66
  expect(input.prop('disabled')).toBe(false);
47
67
  });
@@ -55,7 +75,7 @@ describe('Given a telephone number component', () => {
55
75
  });
56
76
 
57
77
  it('should set control values correctly', () => {
58
- expect(select.props().selected.value).toStrictEqual('+39');
78
+ expect(select.props().value.value).toStrictEqual('+39');
59
79
  expect(input.prop('value')).toBe('123456789');
60
80
  });
61
81
  });
@@ -67,28 +87,29 @@ describe('Given a telephone number component', () => {
67
87
  input = component.find(NUMBER_SELECTOR);
68
88
  });
69
89
 
70
- it('should render input with expected id', () => {
71
- expect(input.prop('id')).toBe('mock-id');
90
+ it('should render select with undefined id', () => {
91
+ expect(select.prop('id')).toBeUndefined();
72
92
  });
73
93
 
74
- it('should render select with expected id', () => {
75
- expect(select.prop('id')).toBe('mock-id-select');
94
+ it('should render input with expected id', () => {
95
+ expect(input.prop('id')).toBe('mock-id');
76
96
  });
77
97
  });
78
98
 
79
99
  describe('when an id is not supplied', () => {
80
100
  beforeEach(() => {
81
101
  component = shallow(<PhoneNumberInput {...props} />);
82
- select = component.find(PREFIX_SELECT_SELECTOR);
83
102
  input = component.find(NUMBER_SELECTOR);
84
103
  });
85
104
 
86
- it('should render select with undefined id', () => {
87
- expect(select.prop('id')).toBeUndefined();
105
+ it('should render input with null id', () => {
106
+ expect(input.prop('id')).toBeNull();
88
107
  });
89
108
  });
90
109
 
91
110
  describe('when pasting', () => {
111
+ window.ResizeObserver = ResizeObserver;
112
+
92
113
  beforeEach(() => {
93
114
  component = mount(<PhoneNumberInput {...props} initialValue="+39123456789" />);
94
115
  select = () => component.find(PREFIX_SELECT_SELECTOR);
@@ -108,7 +129,8 @@ describe('Given a telephone number component', () => {
108
129
  ].forEach(({ number, countryCode, localNumber }) => {
109
130
  it(`${number} code should update the value properly`, () => {
110
131
  simulatePaste(component.find('input'), number);
111
- expect(select().props().selected.value).toStrictEqual(countryCode);
132
+
133
+ expect(select().props().value.value).toStrictEqual(countryCode);
112
134
  expect(input().prop('value')).toBe(localNumber);
113
135
  expect(props.onChange).toHaveBeenCalledWith(number.replace(/(\s|-)+/g, ''), countryCode);
114
136
  });
@@ -116,28 +138,28 @@ describe('Given a telephone number component', () => {
116
138
 
117
139
  it('should not paste invalid characters', () => {
118
140
  simulatePaste(component.find('input'), '+36asdasdasd');
119
- expect(select().props().selected.value).toStrictEqual('+39');
141
+ expect(select().props().value.value).toStrictEqual('+39');
120
142
  expect(input().prop('value')).toBe('123456789');
121
143
  expect(props.onChange).not.toHaveBeenCalled();
122
144
  });
123
145
 
124
146
  it('should not paste countries which are not in the select', () => {
125
147
  simulatePaste(component.find('input'), '+9992342343423');
126
- expect(select().props().selected.value).toStrictEqual('+39');
148
+ expect(select().props().value.value).toStrictEqual('+39');
127
149
  expect(input().prop('value')).toBe('123456789');
128
150
  expect(props.onChange).not.toHaveBeenCalled();
129
151
  });
130
152
 
131
153
  it("should not paste numbers which doesn't start with the country code", () => {
132
154
  simulatePaste(component.find('input'), '0+36303932551');
133
- expect(select().props().selected.value).toStrictEqual('+39');
155
+ expect(select().props().value.value).toStrictEqual('+39');
134
156
  expect(input().prop('value')).toBe('123456789');
135
157
  expect(props.onChange).not.toHaveBeenCalled();
136
158
  });
137
159
 
138
160
  it("should not paste numbers which doesn't contain a country code", () => {
139
161
  simulatePaste(component.find('input'), '06303932551');
140
- expect(select().props().selected.value).toStrictEqual('+39');
162
+ expect(select().props().value.value).toStrictEqual('+39');
141
163
  expect(input().prop('value')).toBe('123456789');
142
164
  expect(props.onChange).not.toHaveBeenCalled();
143
165
  });
@@ -151,8 +173,9 @@ describe('Given a telephone number component', () => {
151
173
  });
152
174
 
153
175
  it('should set the select to the longest matching prefix', () => {
154
- expect(select.props().selected.value).toStrictEqual('+1868');
176
+ expect(select.props().value.value).toStrictEqual('+1868');
155
177
  });
178
+
156
179
  it('should set the number input to the rest of the number', () => {
157
180
  expect(input.prop('value')).toBe('123456789');
158
181
  });
@@ -166,8 +189,9 @@ describe('Given a telephone number component', () => {
166
189
  });
167
190
 
168
191
  it('should empty the select', () => {
169
- expect(select.props().selected.value).toStrictEqual('');
192
+ expect(select.props().value.value).toStrictEqual('');
170
193
  });
194
+
171
195
  it('should put the whole value in the input without the plus', () => {
172
196
  expect(input.prop('value')).toBe('999123456789');
173
197
  });
@@ -176,10 +200,10 @@ describe('Given a telephone number component', () => {
176
200
  describe('when an invalid model is supplied', () => {
177
201
  it('should not re-explode model', () => {
178
202
  component = shallow(<PhoneNumberInput {...props} initialValue="+123" />);
179
-
180
203
  select = component.find(PREFIX_SELECT_SELECTOR);
181
204
  input = component.find(NUMBER_SELECTOR);
182
- expect(select.props().selected.value).toStrictEqual('+44');
205
+
206
+ expect(select.props().value.value).toStrictEqual('+44');
183
207
  expect(input.prop('value')).toBe('');
184
208
  });
185
209
  });
@@ -194,6 +218,7 @@ describe('Given a telephone number component', () => {
194
218
  it('should disable the select', () => {
195
219
  expect(select.prop('disabled')).toBe(true);
196
220
  });
221
+
197
222
  it('should disable the input', () => {
198
223
  expect(input.prop('disabled')).toBe(true);
199
224
  });
@@ -227,7 +252,7 @@ describe('Given a telephone number component', () => {
227
252
  });
228
253
 
229
254
  it('should use the provided searchPlaceholder', () => {
230
- expect(select.prop('searchPlaceholder')).toBe('searchPlaceholder');
255
+ expect(select.prop('filterPlaceholder')).toBe('searchPlaceholder');
231
256
  });
232
257
  });
233
258
 
@@ -241,7 +266,7 @@ describe('Given a telephone number component', () => {
241
266
  });
242
267
 
243
268
  it('should use the prefix of the supplied value', () => {
244
- expect(select.props().selected.value).toBe('+1');
269
+ expect(select.props().value.value).toBe('+1');
245
270
  });
246
271
  });
247
272
 
@@ -255,7 +280,7 @@ describe('Given a telephone number component', () => {
255
280
  });
256
281
 
257
282
  it('should default the prefix to the local country', () => {
258
- expect(select.props().selected.value).toBe('+34');
283
+ expect(select.props().value.value).toBe('+34');
259
284
  });
260
285
  });
261
286
 
@@ -268,7 +293,7 @@ describe('Given a telephone number component', () => {
268
293
  });
269
294
 
270
295
  it('should override locale prefix with country specific prefix', () => {
271
- expect(select.props().selected.value).toBe('+1');
296
+ expect(select.props().value.value).toBe('+1');
272
297
  });
273
298
  });
274
299
  });
@@ -277,37 +302,31 @@ describe('Given a telephone number component', () => {
277
302
  describe('when user insert valid value', () => {
278
303
  beforeEach(() => {
279
304
  component = mount(<PhoneNumberInput {...props} />);
280
- input = component.find(NUMBER_SELECTOR);
281
305
  select = component.find(PREFIX_SELECT_SELECTOR);
306
+ input = component.find(NUMBER_SELECTOR);
282
307
  });
283
308
 
284
309
  it('should trigger onChange handler', () => {
285
310
  input.simulate('change', { target: { value: '123' } });
286
311
  expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
287
312
  });
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
313
  });
295
314
 
296
315
  describe('when user insert an invalid number', () => {
297
316
  it('should trigger onChange with null value', () => {
298
317
  component = mount(<PhoneNumberInput {...props} initialValue="+12345678" />);
299
318
  input = component.find(NUMBER_SELECTOR);
300
- select = component.find(PREFIX_SELECT_SELECTOR);
319
+
301
320
  input.simulate('change', { target: { value: '1' } });
302
- select.simulate('change', { value: '+39', label: '+39' });
303
321
  expect(props.onChange).toHaveBeenCalledWith(null, '+1');
304
322
  });
305
323
 
306
324
  it("shouldn't re-trigger onChange and set previousReturned state", () => {
307
325
  component = shallow(<PhoneNumberInput {...props} />);
308
- input = component.find(NUMBER_SELECTOR);
309
326
  select = component.find(PREFIX_SELECT_SELECTOR);
310
- select.simulate('change', { value: '+1', label: '+1' });
327
+ input = component.find(NUMBER_SELECTOR);
328
+
329
+ select.simulate('change', { value: { value: '+1', label: '+1' } });
311
330
  input.simulate('change', { target: { value: '12' } });
312
331
  expect(props.onChange).not.toHaveBeenCalled();
313
332
  });
@@ -317,30 +336,15 @@ describe('Given a telephone number component', () => {
317
336
  it('should strip them', () => {
318
337
  component = mount(<PhoneNumberInput {...props} value="+12345678" />);
319
338
  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
339
 
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);
340
+ input.simulate('change', { target: { value: '123--' } });
341
+ expect(props.onChange).toHaveBeenCalledWith('+44123', '+44');
337
342
  });
338
343
  });
339
344
 
340
345
  describe('overlapping prefix and suffix numbers', () => {
341
346
  it("shouldn't change the prefix number on matching suffix input", () => {
342
347
  component = mount(<PhoneNumberInput {...props} countryCode="eg" />);
343
-
344
348
  component.find(NUMBER_SELECTOR).simulate('change', { target: { value: '1111111' } });
345
349
 
346
350
  expect(component.find(NUMBER_SELECTOR).props().value).toBe('1111111');
@@ -351,29 +355,13 @@ describe('Given a telephone number component', () => {
351
355
  describe('when selectProps is supplied', () => {
352
356
  beforeEach(() => {
353
357
  component = shallow(
354
- <PhoneNumberInput
355
- selectProps={{
356
- buttonProps: {
357
- 'aria-label': 'mock-button-label',
358
- },
359
- }}
360
- {...props}
361
- />,
358
+ <PhoneNumberInput selectProps={{ className: 'custom-class' }} {...props} />,
362
359
  );
363
360
  });
364
361
 
365
362
  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
- });
363
+ const select = component.find(PREFIX_SELECT_SELECTOR);
364
+ expect(select.prop('className')).toStrictEqual('custom-class');
371
365
  });
372
366
  });
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
367
  });
@@ -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 (