@transferwise/components 0.0.0-experimental-85b9dd1 → 0.0.0-experimental-0db2ae7

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 (151) hide show
  1. package/build/index.esm.js +335 -234
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +336 -235
  4. package/build/index.js.map +1 -1
  5. package/build/types/common/locale/index.d.ts +43 -26
  6. package/build/types/common/locale/index.d.ts.map +1 -1
  7. package/build/types/common/textFormat/formatWithPattern/formatWithPattern.d.ts +1 -1
  8. package/build/types/common/textFormat/formatWithPattern/formatWithPattern.d.ts.map +1 -1
  9. package/build/types/common/textFormat/getCursorPositionAfterKeystroke/getCursorPositionAfterKeystroke.d.ts +2 -2
  10. package/build/types/common/textFormat/getCursorPositionAfterKeystroke/getCursorPositionAfterKeystroke.d.ts.map +1 -1
  11. package/build/types/common/textFormat/getSymbolsInPatternWithPosition/getSymbolsInPatternWithPosition.d.ts +5 -1
  12. package/build/types/common/textFormat/getSymbolsInPatternWithPosition/getSymbolsInPatternWithPosition.d.ts.map +1 -1
  13. package/build/types/common/textFormat/unformatWithPattern/unformatWithPattern.d.ts +1 -1
  14. package/build/types/common/textFormat/unformatWithPattern/unformatWithPattern.d.ts.map +1 -1
  15. package/build/types/index.d.ts +2 -0
  16. package/build/types/index.d.ts.map +1 -1
  17. package/build/types/inputWithDisplayFormat/InputWithDisplayFormat.d.ts +7 -11
  18. package/build/types/inputWithDisplayFormat/InputWithDisplayFormat.d.ts.map +1 -1
  19. package/build/types/inputWithDisplayFormat/index.d.ts +2 -1
  20. package/build/types/inputWithDisplayFormat/index.d.ts.map +1 -1
  21. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +27 -22
  22. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  23. package/build/types/phoneNumberInput/data/countries.d.ts +10 -5
  24. package/build/types/phoneNumberInput/data/countries.d.ts.map +1 -1
  25. package/build/types/phoneNumberInput/index.d.ts +1 -1
  26. package/build/types/phoneNumberInput/index.d.ts.map +1 -1
  27. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts +1 -1
  28. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts.map +1 -1
  29. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts +1 -1
  30. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts.map +1 -1
  31. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +1 -8
  32. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
  33. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts +1 -1
  34. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts.map +1 -1
  35. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts +4 -8
  36. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts.map +1 -1
  37. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts +2 -0
  38. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts.map +1 -0
  39. package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts +1 -1
  40. package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts.map +1 -1
  41. package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts +1 -1
  42. package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts.map +1 -1
  43. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts +1 -2
  44. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts.map +1 -1
  45. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts +1 -1
  46. package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts.map +1 -1
  47. package/build/types/phoneNumberInput/utils/index.d.ts +13 -11
  48. package/build/types/phoneNumberInput/utils/index.d.ts.map +1 -1
  49. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts +2 -0
  50. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts.map +1 -0
  51. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts +3 -0
  52. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts.map +1 -0
  53. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts +1 -1
  54. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts.map +1 -1
  55. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts +1 -1
  56. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts.map +1 -1
  57. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts +1 -1
  58. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts.map +1 -1
  59. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts +1 -6
  60. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts.map +1 -1
  61. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +1 -2
  62. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
  63. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts +1 -7
  64. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts.map +1 -1
  65. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts +1 -1
  66. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts.map +1 -1
  67. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
  68. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
  69. package/build/types/textareaWithDisplayFormat/TextareaWithDisplayFormat.d.ts +7 -11
  70. package/build/types/textareaWithDisplayFormat/TextareaWithDisplayFormat.d.ts.map +1 -1
  71. package/build/types/textareaWithDisplayFormat/index.d.ts +2 -1
  72. package/build/types/textareaWithDisplayFormat/index.d.ts.map +1 -1
  73. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts +54 -82
  74. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  75. package/build/types/withDisplayFormat/index.d.ts +2 -1
  76. package/build/types/withDisplayFormat/index.d.ts.map +1 -1
  77. package/package.json +1 -1
  78. package/src/common/locale/index.js +139 -0
  79. package/src/common/locale/{index.spec.ts → index.spec.js} +4 -4
  80. package/src/common/textFormat/formatWithPattern/{formatWithPattern.js → formatWithPattern.ts} +8 -4
  81. package/src/common/textFormat/getCursorPositionAfterKeystroke/{getCursorPositionAfterKeystroke.js → getCursorPositionAfterKeystroke.ts} +8 -8
  82. package/src/common/textFormat/getSymbolsInPatternWithPosition/{getSymbolsInPatternWithPosition.js → getSymbolsInPatternWithPosition.ts} +7 -2
  83. package/src/common/textFormat/unformatWithPattern/{unformatWithPattern.js → unformatWithPattern.ts} +3 -2
  84. package/src/index.ts +2 -0
  85. package/src/inputWithDisplayFormat/InputWithDisplayFormat.tsx +10 -0
  86. package/src/inputWithDisplayFormat/index.ts +2 -0
  87. package/src/phoneNumberInput/PhoneNumberInput.js +210 -0
  88. package/src/phoneNumberInput/PhoneNumberInput.spec.js +22 -18
  89. package/src/phoneNumberInput/data/{countries.ts → countries.js} +1 -9
  90. package/src/phoneNumberInput/data/countries.spec.js +12 -0
  91. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.js +4 -0
  92. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.ts → excludeCountries.js} +5 -6
  93. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.spec.ts → excludeCountries.spec.js} +1 -1
  94. package/src/phoneNumberInput/utils/explodeNumberModel/{explodeNumberModel.spec.ts → explodeNumberModel.spec.js} +1 -1
  95. package/src/phoneNumberInput/utils/explodeNumberModel/index.js +27 -0
  96. package/src/phoneNumberInput/utils/filterOptionsForQuery/filterOptionsForQuery.spec.js +36 -0
  97. package/src/phoneNumberInput/utils/filterOptionsForQuery/index.js +11 -0
  98. package/src/phoneNumberInput/utils/findCountryByCode/{findCountryByCode.spec.ts → findCountryByCode.spec.js} +1 -0
  99. package/src/phoneNumberInput/utils/findCountryByCode/index.js +10 -0
  100. package/src/phoneNumberInput/utils/findCountryByPrefix/index.js +11 -0
  101. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.js +26 -0
  102. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.js +67 -0
  103. package/src/phoneNumberInput/utils/{index.ts → index.js} +2 -0
  104. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/index.js +1 -0
  105. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.js +25 -0
  106. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.spec.js +66 -0
  107. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.js +1 -0
  108. package/src/phoneNumberInput/utils/isStringNumeric/{isStringNumeric.spec.ts → isStringNumeric.spec.js} +1 -0
  109. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.js +10 -0
  110. package/src/phoneNumberInput/utils/isValidPhoneNumber/{isValidPhoneNumber.spec.ts → isValidPhoneNumber.spec.js} +1 -1
  111. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.js +2 -0
  112. package/src/phoneNumberInput/utils/setDefaultPrefix/index.js +25 -0
  113. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.js +3 -0
  114. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.spec.js +3 -1
  115. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +32 -0
  116. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.tsx +13 -0
  117. package/src/textareaWithDisplayFormat/index.ts +2 -0
  118. package/src/withDisplayFormat/WithDisplayFormat.spec.js +1 -1
  119. package/src/withDisplayFormat/{WithDisplayFormat.js → WithDisplayFormat.tsx} +127 -107
  120. package/src/withDisplayFormat/index.ts +2 -0
  121. package/src/common/locale/index.ts +0 -96
  122. package/src/inputWithDisplayFormat/InputWithDisplayFormat.js +0 -14
  123. package/src/inputWithDisplayFormat/index.js +0 -1
  124. package/src/phoneNumberInput/PhoneNumberInput.tsx +0 -193
  125. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +0 -3
  126. package/src/phoneNumberInput/utils/explodeNumberModel/index.ts +0 -24
  127. package/src/phoneNumberInput/utils/findCountryByCode/index.ts +0 -12
  128. package/src/phoneNumberInput/utils/findCountryByPrefix/index.ts +0 -12
  129. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.ts +0 -102
  130. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +0 -12
  131. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.ts +0 -1
  132. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.ts +0 -7
  133. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +0 -4
  134. package/src/phoneNumberInput/utils/setDefaultPrefix/index.ts +0 -20
  135. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +0 -6
  136. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.js +0 -14
  137. package/src/textareaWithDisplayFormat/index.js +0 -1
  138. package/src/withDisplayFormat/index.js +0 -1
  139. /package/src/phoneNumberInput/{PhoneNumberInput.story.tsx → PhoneNumberInput.story.js} +0 -0
  140. /package/src/phoneNumberInput/{index.ts → index.js} +0 -0
  141. /package/src/phoneNumberInput/utils/cleanNumber/{cleanNumber.spec.ts → cleanNumber.spec.js} +0 -0
  142. /package/src/phoneNumberInput/utils/cleanNumber/{index.ts → index.js} +0 -0
  143. /package/src/phoneNumberInput/utils/excludeCountries/{index.ts → index.js} +0 -0
  144. /package/src/phoneNumberInput/utils/findCountryByPrefix/{findCountryByPrefix.spec.ts → findCountryByPrefix.spec.js} +0 -0
  145. /package/src/phoneNumberInput/utils/groupCountriesByPrefix/{index.ts → index.js} +0 -0
  146. /package/src/phoneNumberInput/utils/isStringNumeric/{index.ts → index.js} +0 -0
  147. /package/src/phoneNumberInput/utils/isValidPhoneNumber/{index.ts → index.js} +0 -0
  148. /package/src/phoneNumberInput/utils/longestMatchingPrefix/{longestMatchingPrefix.spec.ts → longestMatchingPrefix.spec.js} +0 -0
  149. /package/src/phoneNumberInput/utils/setDefaultPrefix/{setDefaultPrefix.spec.ts → setDefaultPrefix.spec.js} +0 -0
  150. /package/src/phoneNumberInput/utils/sortArrayByProperty/{index.ts → index.js} +0 -0
  151. /package/src/phoneNumberInput/utils/sortArrayByProperty/{sortArrayByProperty.spec.ts → sortArrayByProperty.spec.js} +0 -0
@@ -0,0 +1,210 @@
1
+ import { isArray } from '@transferwise/neptune-validation';
2
+ import PropTypes from 'prop-types';
3
+ import { useState, useEffect } from 'react';
4
+ import { useIntl } from 'react-intl';
5
+
6
+ import { Size } from '../common';
7
+ import { SelectInput, SelectInputOptionContent } from '../inputs/SelectInput';
8
+
9
+ import countries from './data/countries';
10
+ import {
11
+ explodeNumberModel,
12
+ isValidPhoneNumber,
13
+ cleanNumber,
14
+ setDefaultPrefix,
15
+ sortArrayByProperty,
16
+ groupCountriesByPrefix,
17
+ excludeCountries,
18
+ } from './utils';
19
+
20
+ const ALLOWED_PHONE_CHARS = /^$|^[\d-\s]+$/;
21
+
22
+ const PhoneNumberInput = (props) => {
23
+ const {
24
+ id,
25
+ onChange,
26
+ searchPlaceholder,
27
+ disabled,
28
+ required,
29
+ size,
30
+ placeholder,
31
+ onFocus,
32
+ onBlur,
33
+ countryCode,
34
+ selectProps,
35
+ disabledCountries,
36
+ } = props;
37
+ const { locale } = useIntl();
38
+
39
+ const getInitialValue = () => {
40
+ const { initialValue } = props;
41
+
42
+ const cleanValue = initialValue ? cleanNumber(initialValue) : null;
43
+
44
+ if (!cleanValue || !isValidPhoneNumber(cleanValue)) {
45
+ return {
46
+ prefix: setDefaultPrefix(locale, countryCode),
47
+ suffix: '',
48
+ };
49
+ }
50
+
51
+ return explodeNumberModel(cleanValue);
52
+ };
53
+
54
+ const [internalValue, setInternalValue] = useState(getInitialValue());
55
+ const [broadcastedValue, setBroadcastedValue] = useState(null);
56
+
57
+ const getSelectOptions = () => {
58
+ const countriesList = excludeCountries(countries, disabledCountries);
59
+ const listSortedByISO3 = groupCountriesByPrefix(sortArrayByProperty(countriesList, 'iso3'));
60
+
61
+ return listSortedByISO3.map((option) => {
62
+ const { phone, iso3, iso2, name } = option;
63
+ let note = '';
64
+
65
+ if (iso3) {
66
+ note = isArray(iso3) ? iso3.join(', ') : iso3;
67
+ } else if (iso2) {
68
+ note = isArray(iso2) ? iso2.join(', ') : iso2;
69
+ }
70
+
71
+ return {
72
+ type: 'option',
73
+ value: {
74
+ value: phone,
75
+ label: phone,
76
+ note: note,
77
+ },
78
+ filterMatchers: [phone, note, name],
79
+ };
80
+ });
81
+ };
82
+
83
+ const options = getSelectOptions();
84
+
85
+ const onPrefixChange = ({ value }) => {
86
+ setInternalValue({ prefix: value, suffix: internalValue.suffix });
87
+ };
88
+
89
+ const onSuffixChange = (event) => {
90
+ const { value = '' } = event.target;
91
+
92
+ if (ALLOWED_PHONE_CHARS.test(value)) {
93
+ setInternalValue({ prefix: internalValue.prefix, suffix: value });
94
+ }
95
+ };
96
+
97
+ const onPaste = (event) => {
98
+ if (!event.nativeEvent.clipboardData) {
99
+ return;
100
+ }
101
+
102
+ const pastedValue = (event.nativeEvent.clipboardData.getData('text/plain') || '').replace(
103
+ /(\s|-)+/g,
104
+ '',
105
+ );
106
+ const { prefix: pastedPrefix, suffix: pastedSuffix } = explodeNumberModel(pastedValue);
107
+ const selectedPrefix = options.find(({ value }) => value.value === pastedPrefix);
108
+
109
+ if (selectedPrefix && ALLOWED_PHONE_CHARS.test(pastedSuffix)) {
110
+ setInternalValue({ prefix: pastedPrefix, suffix: pastedSuffix });
111
+ }
112
+ };
113
+
114
+ useEffect(() => {
115
+ if (broadcastedValue === null) {
116
+ return setBroadcastedValue(internalValue);
117
+ }
118
+
119
+ const internalPhoneNumber = internalValue.prefix + internalValue.suffix;
120
+ const broadcastedPhoneNumber = broadcastedValue.prefix + broadcastedValue.suffix;
121
+
122
+ if (internalPhoneNumber === broadcastedPhoneNumber) {
123
+ return;
124
+ }
125
+
126
+ const newValue = isValidPhoneNumber(internalPhoneNumber)
127
+ ? cleanNumber(internalPhoneNumber)
128
+ : null;
129
+
130
+ onChange(newValue, internalValue.prefix);
131
+ setBroadcastedValue(internalValue);
132
+ }, [onChange, broadcastedValue, internalValue]);
133
+
134
+ return (
135
+ <div className="tw-telephone">
136
+ <div className="tw-telephone__country-select">
137
+ <SelectInput
138
+ placeholder="Select an option..."
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}
149
+ disabled={disabled}
150
+ size={size}
151
+ onChange={onPrefixChange}
152
+ {...selectProps}
153
+ />
154
+ </div>
155
+ <div className="tw-telephone__number-input">
156
+ <div className={`input-group input-group-${size}`}>
157
+ <input
158
+ id={id}
159
+ autoComplete="tel-national"
160
+ name="phoneNumber"
161
+ inputMode="numeric"
162
+ value={internalValue.suffix}
163
+ className="form-control"
164
+ disabled={disabled}
165
+ required={required}
166
+ placeholder={placeholder}
167
+ onChange={onSuffixChange}
168
+ onPaste={onPaste}
169
+ onFocus={onFocus}
170
+ onBlur={onBlur}
171
+ />
172
+ </div>
173
+ </div>
174
+ </div>
175
+ );
176
+ };
177
+
178
+ PhoneNumberInput.propTypes = {
179
+ id: PropTypes.string,
180
+ required: PropTypes.bool,
181
+ disabled: PropTypes.bool,
182
+ initialValue: PropTypes.string,
183
+ onChange: PropTypes.func.isRequired,
184
+ onFocus: PropTypes.func,
185
+ onBlur: PropTypes.func,
186
+ countryCode: PropTypes.string,
187
+ searchPlaceholder: PropTypes.string,
188
+ size: PropTypes.oneOf(['sm', 'md', 'lg']),
189
+ placeholder: PropTypes.string,
190
+ selectProps: PropTypes.object,
191
+ /** List of iso3 codes of countries to remove from the list */
192
+ disabledCountries: PropTypes.arrayOf(PropTypes.string),
193
+ };
194
+
195
+ PhoneNumberInput.defaultProps = {
196
+ id: null,
197
+ required: false,
198
+ disabled: false,
199
+ initialValue: null,
200
+ onFocus() {},
201
+ onBlur() {},
202
+ countryCode: null,
203
+ searchPlaceholder: 'Prefix',
204
+ size: Size.MEDIUM,
205
+ placeholder: '',
206
+ selectProps: {},
207
+ disabledCountries: [],
208
+ };
209
+
210
+ export default PhoneNumberInput;
@@ -37,7 +37,11 @@ describe('Given a telephone number component', () => {
37
37
  });
38
38
 
39
39
  it('should set prefix control to default UK value', () => {
40
- expect(select.props().value).toBe('+44');
40
+ expect(select.props().value).toStrictEqual({
41
+ value: '+44',
42
+ note: 'GBR, GGY, IMN, JEY',
43
+ label: '+44',
44
+ });
41
45
  });
42
46
 
43
47
  it('should set number control to empty', () => {
@@ -45,11 +49,11 @@ describe('Given a telephone number component', () => {
45
49
  });
46
50
 
47
51
  it('should not disable the select', () => {
48
- expect(select.prop('disabled')).toBeFalsy();
52
+ expect(select.prop('disabled')).toBe(false);
49
53
  });
50
54
 
51
55
  it('should not disable the input', () => {
52
- expect(input.prop('disabled')).toBeFalsy();
56
+ expect(input.prop('disabled')).toBe(false);
53
57
  });
54
58
  });
55
59
 
@@ -61,7 +65,7 @@ describe('Given a telephone number component', () => {
61
65
  });
62
66
 
63
67
  it('should set control values correctly', () => {
64
- expect(select.props().value).toBe('+39');
68
+ expect(select.props().value.value).toStrictEqual('+39');
65
69
  expect(input.prop('value')).toBe('123456789');
66
70
  });
67
71
  });
@@ -88,8 +92,8 @@ describe('Given a telephone number component', () => {
88
92
  input = component.find(NUMBER_SELECTOR);
89
93
  });
90
94
 
91
- it('should render input with unspecified id', () => {
92
- expect(input.prop('id')).toBeUndefined();
95
+ it('should render input with null id', () => {
96
+ expect(input.prop('id')).toBeNull();
93
97
  });
94
98
  });
95
99
 
@@ -114,7 +118,7 @@ describe('Given a telephone number component', () => {
114
118
  it(`${number} code should update the value properly`, () => {
115
119
  simulatePaste(component.find('input'), number);
116
120
 
117
- expect(select().props().value).toBe(countryCode);
121
+ expect(select().props().value.value).toStrictEqual(countryCode);
118
122
  expect(input().prop('value')).toBe(localNumber);
119
123
  expect(props.onChange).toHaveBeenCalledWith(number.replace(/(\s|-)+/g, ''), countryCode);
120
124
  });
@@ -122,28 +126,28 @@ describe('Given a telephone number component', () => {
122
126
 
123
127
  it('should not paste invalid characters', () => {
124
128
  simulatePaste(component.find('input'), '+36asdasdasd');
125
- expect(select().props().value).toBe('+39');
129
+ expect(select().props().value.value).toStrictEqual('+39');
126
130
  expect(input().prop('value')).toBe('123456789');
127
131
  expect(props.onChange).not.toHaveBeenCalled();
128
132
  });
129
133
 
130
134
  it('should not paste countries which are not in the select', () => {
131
135
  simulatePaste(component.find('input'), '+9992342343423');
132
- expect(select().props().value).toBe('+39');
136
+ expect(select().props().value.value).toStrictEqual('+39');
133
137
  expect(input().prop('value')).toBe('123456789');
134
138
  expect(props.onChange).not.toHaveBeenCalled();
135
139
  });
136
140
 
137
141
  it("should not paste numbers which doesn't start with the country code", () => {
138
142
  simulatePaste(component.find('input'), '0+36303932551');
139
- expect(select().props().value).toBe('+39');
143
+ expect(select().props().value.value).toStrictEqual('+39');
140
144
  expect(input().prop('value')).toBe('123456789');
141
145
  expect(props.onChange).not.toHaveBeenCalled();
142
146
  });
143
147
 
144
148
  it("should not paste numbers which doesn't contain a country code", () => {
145
149
  simulatePaste(component.find('input'), '06303932551');
146
- expect(select().props().value).toBe('+39');
150
+ expect(select().props().value.value).toStrictEqual('+39');
147
151
  expect(input().prop('value')).toBe('123456789');
148
152
  expect(props.onChange).not.toHaveBeenCalled();
149
153
  });
@@ -157,7 +161,7 @@ describe('Given a telephone number component', () => {
157
161
  });
158
162
 
159
163
  it('should set the select to the longest matching prefix', () => {
160
- expect(select.props().value).toBe('+1868');
164
+ expect(select.props().value.value).toStrictEqual('+1868');
161
165
  });
162
166
 
163
167
  it('should set the number input to the rest of the number', () => {
@@ -173,7 +177,7 @@ describe('Given a telephone number component', () => {
173
177
  });
174
178
 
175
179
  it('should empty the select', () => {
176
- expect(select.props().value).toBeNull();
180
+ expect(select.props().value).toBeUndefined();
177
181
  });
178
182
 
179
183
  it('should put the whole value in the input without the plus', () => {
@@ -187,7 +191,7 @@ describe('Given a telephone number component', () => {
187
191
  select = component.find(PREFIX_SELECT_SELECTOR);
188
192
  input = component.find(NUMBER_SELECTOR);
189
193
 
190
- expect(select.props().value).toBe('+44');
194
+ expect(select.props().value.value).toStrictEqual('+44');
191
195
  expect(input.prop('value')).toBe('');
192
196
  });
193
197
  });
@@ -250,7 +254,7 @@ describe('Given a telephone number component', () => {
250
254
  });
251
255
 
252
256
  it('should use the prefix of the supplied value', () => {
253
- expect(select.props().value).toBe('+1');
257
+ expect(select.props().value.value).toBe('+1');
254
258
  });
255
259
  });
256
260
 
@@ -264,7 +268,7 @@ describe('Given a telephone number component', () => {
264
268
  });
265
269
 
266
270
  it('should default the prefix to the local country', () => {
267
- expect(select.props().value).toBe('+34');
271
+ expect(select.props().value.value).toBe('+34');
268
272
  });
269
273
  });
270
274
 
@@ -277,7 +281,7 @@ describe('Given a telephone number component', () => {
277
281
  });
278
282
 
279
283
  it('should override locale prefix with country specific prefix', () => {
280
- expect(select.props().value).toBe('+1');
284
+ expect(select.props().value.value).toBe('+1');
281
285
  });
282
286
  });
283
287
  });
@@ -345,7 +349,7 @@ describe('Given a telephone number component', () => {
345
349
 
346
350
  it('renders Select component with expected props', () => {
347
351
  const select = component.find(PREFIX_SELECT_SELECTOR);
348
- expect(select.prop('className')).toBe('custom-class');
352
+ expect(select.prop('className')).toStrictEqual('custom-class');
349
353
  });
350
354
  });
351
355
  });
@@ -1,12 +1,4 @@
1
- export type Country = {
2
- name: string;
3
- iso2: string;
4
- iso3: string;
5
- phone: string;
6
- phoneFormat?: string;
7
- };
8
-
9
- const countries: Country[] = [
1
+ const countries = [
10
2
  {
11
3
  name: 'Afghanistan',
12
4
  iso2: 'AF',
@@ -0,0 +1,12 @@
1
+ import countries from './countries';
2
+
3
+ describe('Given a list of countries', () => {
4
+ countries.forEach((country) => {
5
+ it('each country should have a valid format', () => {
6
+ expect(country).toHaveProperty('phone');
7
+ expect(country).toHaveProperty('name');
8
+ expect(country).toHaveProperty('iso3');
9
+ expect(country).toHaveProperty('iso2');
10
+ });
11
+ });
12
+ });
@@ -0,0 +1,4 @@
1
+ const DIGITS_MATCH = /^$|^(\+)|([\d]+)/g;
2
+
3
+ export const cleanNumber = (number) =>
4
+ (number.match(DIGITS_MATCH) && number.match(DIGITS_MATCH).join('')) || '';
@@ -1,7 +1,5 @@
1
- import type { Country } from '../../data/countries';
2
-
3
1
  // Reference fro localeCompare : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
4
- const filterCountriesByIso3 = (countries: Country[], iso3Codes: string[]) => {
2
+ const filterCountriesByIso3 = (countries, iso3Codes) => {
5
3
  const iso3CodesSet = new Set(iso3Codes);
6
4
  return countries.filter((country) => !iso3CodesSet.has(country.iso3));
7
5
  };
@@ -9,10 +7,11 @@ const filterCountriesByIso3 = (countries: Country[], iso3Codes: string[]) => {
9
7
  /**
10
8
  * Removes the countries sepecified in the second param
11
9
  *
12
- * @param countries list of country metadata objects
13
- * @param disabledCountries list of iso3 country codes to remove from the list
10
+ * @param {Array} countries: list of country metadata objects
11
+ * @param {Array} disabledCountries: list of iso3 country codes to remove from the list
12
+ * @returns
14
13
  */
15
- export const excludeCountries = (countries: Country[], disabledCountries: string[]) => {
14
+ export const excludeCountries = (countries, disabledCountries) => {
16
15
  return disabledCountries.length > 0
17
16
  ? filterCountriesByIso3(countries, disabledCountries)
18
17
  : countries;
@@ -4,7 +4,7 @@ import { excludeCountries } from './excludeCountries';
4
4
 
5
5
  describe('Exclude countries', () => {
6
6
  it('should return all the countries of list is empty', () => {
7
- const remove: string[] = [];
7
+ const remove = [];
8
8
  const filteredCountries = excludeCountries(countries, remove);
9
9
  expect(filteredCountries).toHaveLength(countries.length);
10
10
  });
@@ -12,7 +12,7 @@ describe('explodeNumberModel', () => {
12
12
 
13
13
  it('should return an exploded number for three digit prefix', () => {
14
14
  expect(explodeNumberModel('+3727573135343')).toStrictEqual({
15
- format: undefined,
15
+ format: '',
16
16
  prefix: '+372',
17
17
  suffix: '7573135343',
18
18
  });
@@ -0,0 +1,27 @@
1
+ import { findCountryByPrefix } from '../findCountryByPrefix';
2
+
3
+ /**
4
+ * Given a sting in a valid format ex:'+447573135343' it returns an object of shape
5
+ * {prefix=+44 ,suffix=7573135343}
6
+ *
7
+ * @param {string} number - a string that defines a phone number.
8
+ * @returns {{prefix: (string|*), suffix: string, format: string}}
9
+ */
10
+ export const explodeNumberModel = (number) => {
11
+ let prefix = '';
12
+ let suffix = '';
13
+ let format = '';
14
+ const country = findCountryByPrefix(number);
15
+
16
+ if (country) {
17
+ prefix = country.phone;
18
+ suffix = number.slice(country.phone.length);
19
+ format = country.phoneFormat || '';
20
+ } else {
21
+ prefix = '';
22
+ suffix = number.slice(1);
23
+ format = '';
24
+ }
25
+
26
+ return { prefix, suffix, format };
27
+ };
@@ -0,0 +1,36 @@
1
+ import { filterOptionsForQuery } from '..';
2
+
3
+ const OPTIONS = [
4
+ {
5
+ name: 'test1',
6
+ iso2: 'TT',
7
+ iso3: 'TT1',
8
+ phone: '+93',
9
+ },
10
+ {
11
+ name: 'something',
12
+ iso2: 'ST',
13
+ iso3: 'SMT',
14
+ phone: '+33',
15
+ },
16
+ ];
17
+
18
+ describe('filterOptionsForQuery', () => {
19
+ it('filters options based on all properties', () => {
20
+ const option1 = OPTIONS[0];
21
+ const option2 = OPTIONS[1];
22
+
23
+ expect(filterOptionsForQuery(OPTIONS, option1.name)).toStrictEqual([option1]);
24
+ expect(filterOptionsForQuery(OPTIONS, option1.iso2)).toStrictEqual([option1]);
25
+ expect(filterOptionsForQuery(OPTIONS, option1.iso3)).toStrictEqual([option1]);
26
+ expect(filterOptionsForQuery(OPTIONS, option1.phone)).toStrictEqual([option1]);
27
+ expect(filterOptionsForQuery(OPTIONS, option2.name)).toStrictEqual([option2]);
28
+ expect(filterOptionsForQuery(OPTIONS, option2.iso2)).toStrictEqual([option2]);
29
+ expect(filterOptionsForQuery(OPTIONS, option2.iso3)).toStrictEqual([option2]);
30
+ expect(filterOptionsForQuery(OPTIONS, option2.phone)).toStrictEqual([option2]);
31
+ });
32
+
33
+ it('should return an emtpy array if option cannot be found', () => {
34
+ expect(filterOptionsForQuery(OPTIONS, 'AA')).toStrictEqual([]);
35
+ });
36
+ });
@@ -0,0 +1,11 @@
1
+ import { isOptionAndFitsQuery } from '../isOptionAndFitsQuery';
2
+
3
+ /**
4
+ * Filters a set of options based on search string
5
+ *
6
+ * @param options
7
+ * @param query
8
+ * @returns {*}
9
+ */
10
+ export const filterOptionsForQuery = (options, query) =>
11
+ options.filter((option) => isOptionAndFitsQuery(option, query));
@@ -14,6 +14,7 @@ describe('findCountryByCode', () => {
14
14
 
15
15
  it('should return null for invalid code', () => {
16
16
  expect(findCountryByCode('Wrong')).toBeNull();
17
+ expect(findCountryByCode(null)).toBeNull();
17
18
  expect(findCountryByCode('')).toBeNull();
18
19
  expect(findCountryByCode(' ')).toBeNull();
19
20
  });
@@ -0,0 +1,10 @@
1
+ import countries from '../../data/countries';
2
+ import { longestMatchingPrefix } from '../longestMatchingPrefix';
3
+
4
+ export const findCountryByCode = (code) => {
5
+ let matchingCodes;
6
+ if (code && code.length === 2) {
7
+ matchingCodes = countries.filter((country) => code.toUpperCase() === country.iso2);
8
+ }
9
+ return matchingCodes && matchingCodes.length > 0 ? longestMatchingPrefix(matchingCodes) : null;
10
+ };
@@ -0,0 +1,11 @@
1
+ import countries from '../../data/countries';
2
+ import { longestMatchingPrefix } from '../longestMatchingPrefix';
3
+
4
+ export const findCountryByPrefix = (number) => {
5
+ let matchingCodes = null;
6
+ if (number && number.length > 1) {
7
+ matchingCodes = countries.filter((country) => number.indexOf(country.phone) === 0);
8
+ }
9
+
10
+ return matchingCodes && matchingCodes.length > 0 ? longestMatchingPrefix(matchingCodes) : null;
11
+ };
@@ -0,0 +1,26 @@
1
+ import { isArray } from '@transferwise/neptune-validation';
2
+
3
+ export const groupCountriesByPrefix = (countries) => {
4
+ const groupedArray = countries.reduce((accumulator, country) => {
5
+ const { name, iso2, iso3, phone } = country;
6
+ if (accumulator[phone]) {
7
+ const previousValue = accumulator[phone];
8
+ accumulator[phone] = {
9
+ ...previousValue,
10
+ name: isArray(previousValue.name)
11
+ ? [...previousValue.name, name]
12
+ : [previousValue.name, name],
13
+ iso2: isArray(previousValue.iso2)
14
+ ? [...previousValue.iso2, iso2]
15
+ : [previousValue.iso2, iso2],
16
+ iso3: isArray(previousValue.iso3)
17
+ ? [...previousValue.iso3, iso3]
18
+ : [previousValue.iso3, iso3],
19
+ };
20
+ } else {
21
+ accumulator[phone] = country;
22
+ }
23
+ return accumulator;
24
+ }, {});
25
+ return Object.values(groupedArray);
26
+ };
@@ -0,0 +1,67 @@
1
+ import { groupCountriesByPrefix } from '.';
2
+
3
+ const countries = [
4
+ {
5
+ name: 'Canada',
6
+ iso2: 'CA',
7
+ iso3: 'CAN',
8
+ phone: '+1',
9
+ },
10
+ {
11
+ name: 'United States of America',
12
+ iso2: 'US',
13
+ iso3: 'USA',
14
+ phone: '+1',
15
+ },
16
+ {
17
+ name: 'United States Minor Outlying Islands',
18
+ iso2: 'UM',
19
+ iso3: 'UMI',
20
+ phone: '+1',
21
+ },
22
+ {
23
+ name: 'United Kingdom',
24
+ iso2: 'GB',
25
+ iso3: 'GBR',
26
+ phone: '+44',
27
+ },
28
+ {
29
+ name: 'Guernsey',
30
+ iso2: 'GG',
31
+ iso3: 'GGY',
32
+ phone: '+44',
33
+ },
34
+ {
35
+ name: 'Guinea',
36
+ iso2: 'GN',
37
+ iso3: 'GIN',
38
+ phone: '+224',
39
+ },
40
+ ];
41
+
42
+ const groupedCountries = [
43
+ {
44
+ name: ['Canada', 'United States of America', 'United States Minor Outlying Islands'],
45
+ iso2: ['CA', 'US', 'UM'],
46
+ iso3: ['CAN', 'USA', 'UMI'],
47
+ phone: '+1',
48
+ },
49
+ {
50
+ name: ['United Kingdom', 'Guernsey'],
51
+ iso2: ['GB', 'GG'],
52
+ iso3: ['GBR', 'GGY'],
53
+ phone: '+44',
54
+ },
55
+ {
56
+ name: 'Guinea',
57
+ iso2: 'GN',
58
+ iso3: 'GIN',
59
+ phone: '+224',
60
+ },
61
+ ];
62
+
63
+ describe('groupCountriesByPrefix', () => {
64
+ it('groups countries by prefix', () => {
65
+ expect(groupCountriesByPrefix(countries)).toStrictEqual(groupedCountries);
66
+ });
67
+ });
@@ -4,6 +4,8 @@ export { explodeNumberModel } from './explodeNumberModel';
4
4
  export { longestMatchingPrefix } from './longestMatchingPrefix';
5
5
  export { findCountryByPrefix } from './findCountryByPrefix';
6
6
  export { findCountryByCode } from './findCountryByCode';
7
+ export { filterOptionsForQuery } from './filterOptionsForQuery';
8
+ export { isOptionAndFitsQuery } from './isOptionAndFitsQuery';
7
9
  export { cleanNumber } from './cleanNumber';
8
10
  export { isStringNumeric } from './isStringNumeric';
9
11
  export { sortArrayByProperty } from './sortArrayByProperty';
@@ -0,0 +1 @@
1
+ export { isOptionAndFitsQuery, startsWith } from './isOptionAndFitsQuery';