@transferwise/components 46.6.0 → 46.8.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.
- package/build/index.esm.js +290 -346
- package/build/index.esm.js.map +1 -1
- package/build/index.js +290 -345
- package/build/index.js.map +1 -1
- package/build/main.css +107 -17
- package/build/styles/inputs/Input.css +0 -4
- package/build/styles/inputs/SelectInput.css +6 -1
- package/build/styles/inputs/TextArea.css +0 -4
- package/build/styles/main.css +107 -17
- package/build/styles/segmentedControl/SegmentedControl.css +101 -0
- package/build/styles/select/Select.css +0 -4
- package/build/types/common/locale/index.d.ts +26 -43
- package/build/types/common/locale/index.d.ts.map +1 -1
- package/build/types/index.d.ts +3 -0
- package/build/types/index.d.ts.map +1 -1
- package/build/types/inputs/SelectInput.d.ts +6 -5
- package/build/types/inputs/SelectInput.d.ts.map +1 -1
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +22 -27
- package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
- package/build/types/phoneNumberInput/data/countries.d.ts +5 -10
- package/build/types/phoneNumberInput/data/countries.d.ts.map +1 -1
- package/build/types/phoneNumberInput/index.d.ts +1 -1
- package/build/types/phoneNumberInput/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +8 -1
- package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts +8 -4
- package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/findCountryByCode/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/findCountryByPrefix/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts +2 -1
- package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/groupCountriesByPrefix/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/index.d.ts +11 -13
- package/build/types/phoneNumberInput/utils/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts +6 -1
- package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +2 -1
- package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts +7 -1
- package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts.map +1 -1
- package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
- package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
- package/build/types/segmentedControl/SegmentedControl.d.ts +31 -0
- package/build/types/segmentedControl/SegmentedControl.d.ts.map +1 -0
- package/build/types/segmentedControl/index.d.ts +3 -0
- package/build/types/segmentedControl/index.d.ts.map +1 -0
- package/package.json +7 -17
- package/src/common/locale/{index.spec.js → index.spec.ts} +4 -4
- package/src/common/locale/index.ts +96 -0
- package/src/index.ts +3 -0
- package/src/inputs/Input.css +0 -4
- package/src/inputs/SelectInput.css +6 -1
- package/src/inputs/SelectInput.less +8 -1
- package/src/inputs/SelectInput.spec.tsx +26 -0
- package/src/inputs/SelectInput.story.tsx +73 -1
- package/src/inputs/SelectInput.tsx +104 -85
- package/src/inputs/TextArea.css +0 -4
- package/src/main.css +107 -17
- package/src/main.less +1 -0
- package/src/phoneNumberInput/PhoneNumberInput.spec.js +18 -22
- package/src/phoneNumberInput/PhoneNumberInput.tsx +193 -0
- package/src/phoneNumberInput/data/{countries.js → countries.ts} +9 -1
- package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +3 -0
- package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.spec.js → excludeCountries.spec.ts} +1 -1
- package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.js → excludeCountries.ts} +6 -5
- package/src/phoneNumberInput/utils/explodeNumberModel/{explodeNumberModel.spec.js → explodeNumberModel.spec.ts} +1 -1
- package/src/phoneNumberInput/utils/explodeNumberModel/index.ts +24 -0
- package/src/phoneNumberInput/utils/findCountryByCode/{findCountryByCode.spec.js → findCountryByCode.spec.ts} +0 -1
- package/src/phoneNumberInput/utils/findCountryByCode/index.ts +12 -0
- package/src/phoneNumberInput/utils/findCountryByPrefix/index.ts +12 -0
- package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.ts +102 -0
- package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +12 -0
- package/src/phoneNumberInput/utils/{index.js → index.ts} +0 -2
- package/src/phoneNumberInput/utils/isStringNumeric/{isStringNumeric.spec.js → isStringNumeric.spec.ts} +0 -1
- package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.ts +1 -0
- package/src/phoneNumberInput/utils/isValidPhoneNumber/{isValidPhoneNumber.spec.js → isValidPhoneNumber.spec.ts} +1 -1
- package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.ts +7 -0
- package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +4 -0
- package/src/phoneNumberInput/utils/setDefaultPrefix/index.ts +20 -0
- package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +6 -0
- package/src/segmentedControl/SegmentedControl.css +101 -0
- package/src/segmentedControl/SegmentedControl.less +101 -0
- package/src/segmentedControl/SegmentedControl.spec.tsx +106 -0
- package/src/segmentedControl/SegmentedControl.story.tsx +55 -0
- package/src/segmentedControl/SegmentedControl.tsx +175 -0
- package/src/segmentedControl/index.ts +2 -0
- package/src/select/Select.css +0 -4
- package/src/ssr.spec.js +17 -0
- package/src/withDisplayFormat/WithDisplayFormat.spec.js +1 -1
- package/src/withDisplayFormat/WithDisplayFormat.tsx +1 -1
- package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts +0 -2
- package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts.map +0 -1
- package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts +0 -2
- package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts.map +0 -1
- package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts +0 -3
- package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts.map +0 -1
- package/build/types/utilities/wrapInFragment.d.ts +0 -3
- package/build/types/utilities/wrapInFragment.d.ts.map +0 -1
- package/src/common/locale/index.js +0 -139
- package/src/phoneNumberInput/PhoneNumberInput.js +0 -210
- package/src/phoneNumberInput/data/countries.spec.js +0 -12
- package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.js +0 -4
- package/src/phoneNumberInput/utils/explodeNumberModel/index.js +0 -27
- package/src/phoneNumberInput/utils/filterOptionsForQuery/filterOptionsForQuery.spec.js +0 -36
- package/src/phoneNumberInput/utils/filterOptionsForQuery/index.js +0 -11
- package/src/phoneNumberInput/utils/findCountryByCode/index.js +0 -10
- package/src/phoneNumberInput/utils/findCountryByPrefix/index.js +0 -11
- package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.js +0 -26
- package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.js +0 -67
- package/src/phoneNumberInput/utils/isOptionAndFitsQuery/index.js +0 -1
- package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.js +0 -25
- package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.spec.js +0 -66
- package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.js +0 -1
- package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.js +0 -10
- package/src/phoneNumberInput/utils/longestMatchingPrefix/index.js +0 -2
- package/src/phoneNumberInput/utils/setDefaultPrefix/index.js +0 -25
- package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.js +0 -3
- package/src/utilities/wrapInFragment.tsx +0 -3
- /package/src/phoneNumberInput/{PhoneNumberInput.story.js → PhoneNumberInput.story.tsx} +0 -0
- /package/src/phoneNumberInput/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/cleanNumber/{cleanNumber.spec.js → cleanNumber.spec.ts} +0 -0
- /package/src/phoneNumberInput/utils/cleanNumber/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/excludeCountries/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/findCountryByPrefix/{findCountryByPrefix.spec.js → findCountryByPrefix.spec.ts} +0 -0
- /package/src/phoneNumberInput/utils/groupCountriesByPrefix/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/isStringNumeric/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/isValidPhoneNumber/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/longestMatchingPrefix/{longestMatchingPrefix.spec.js → longestMatchingPrefix.spec.ts} +0 -0
- /package/src/phoneNumberInput/utils/setDefaultPrefix/{setDefaultPrefix.spec.js → setDefaultPrefix.spec.ts} +0 -0
- /package/src/phoneNumberInput/utils/sortArrayByProperty/{index.js → index.ts} +0 -0
- /package/src/phoneNumberInput/utils/sortArrayByProperty/{sortArrayByProperty.spec.js → sortArrayByProperty.spec.ts} +0 -0
|
@@ -37,11 +37,7 @@ 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).
|
|
41
|
-
value: '+44',
|
|
42
|
-
note: 'GBR, GGY, IMN, JEY',
|
|
43
|
-
label: '+44',
|
|
44
|
-
});
|
|
40
|
+
expect(select.props().value).toBe('+44');
|
|
45
41
|
});
|
|
46
42
|
|
|
47
43
|
it('should set number control to empty', () => {
|
|
@@ -49,11 +45,11 @@ describe('Given a telephone number component', () => {
|
|
|
49
45
|
});
|
|
50
46
|
|
|
51
47
|
it('should not disable the select', () => {
|
|
52
|
-
expect(select.prop('disabled')).
|
|
48
|
+
expect(select.prop('disabled')).toBeFalsy();
|
|
53
49
|
});
|
|
54
50
|
|
|
55
51
|
it('should not disable the input', () => {
|
|
56
|
-
expect(input.prop('disabled')).
|
|
52
|
+
expect(input.prop('disabled')).toBeFalsy();
|
|
57
53
|
});
|
|
58
54
|
});
|
|
59
55
|
|
|
@@ -65,7 +61,7 @@ describe('Given a telephone number component', () => {
|
|
|
65
61
|
});
|
|
66
62
|
|
|
67
63
|
it('should set control values correctly', () => {
|
|
68
|
-
expect(select.props().value
|
|
64
|
+
expect(select.props().value).toBe('+39');
|
|
69
65
|
expect(input.prop('value')).toBe('123456789');
|
|
70
66
|
});
|
|
71
67
|
});
|
|
@@ -92,8 +88,8 @@ describe('Given a telephone number component', () => {
|
|
|
92
88
|
input = component.find(NUMBER_SELECTOR);
|
|
93
89
|
});
|
|
94
90
|
|
|
95
|
-
it('should render input with
|
|
96
|
-
expect(input.prop('id')).
|
|
91
|
+
it('should render input with unspecified id', () => {
|
|
92
|
+
expect(input.prop('id')).toBeUndefined();
|
|
97
93
|
});
|
|
98
94
|
});
|
|
99
95
|
|
|
@@ -118,7 +114,7 @@ describe('Given a telephone number component', () => {
|
|
|
118
114
|
it(`${number} code should update the value properly`, () => {
|
|
119
115
|
simulatePaste(component.find('input'), number);
|
|
120
116
|
|
|
121
|
-
expect(select().props().value
|
|
117
|
+
expect(select().props().value).toBe(countryCode);
|
|
122
118
|
expect(input().prop('value')).toBe(localNumber);
|
|
123
119
|
expect(props.onChange).toHaveBeenCalledWith(number.replace(/(\s|-)+/g, ''), countryCode);
|
|
124
120
|
});
|
|
@@ -126,28 +122,28 @@ describe('Given a telephone number component', () => {
|
|
|
126
122
|
|
|
127
123
|
it('should not paste invalid characters', () => {
|
|
128
124
|
simulatePaste(component.find('input'), '+36asdasdasd');
|
|
129
|
-
expect(select().props().value
|
|
125
|
+
expect(select().props().value).toBe('+39');
|
|
130
126
|
expect(input().prop('value')).toBe('123456789');
|
|
131
127
|
expect(props.onChange).not.toHaveBeenCalled();
|
|
132
128
|
});
|
|
133
129
|
|
|
134
130
|
it('should not paste countries which are not in the select', () => {
|
|
135
131
|
simulatePaste(component.find('input'), '+9992342343423');
|
|
136
|
-
expect(select().props().value
|
|
132
|
+
expect(select().props().value).toBe('+39');
|
|
137
133
|
expect(input().prop('value')).toBe('123456789');
|
|
138
134
|
expect(props.onChange).not.toHaveBeenCalled();
|
|
139
135
|
});
|
|
140
136
|
|
|
141
137
|
it("should not paste numbers which doesn't start with the country code", () => {
|
|
142
138
|
simulatePaste(component.find('input'), '0+36303932551');
|
|
143
|
-
expect(select().props().value
|
|
139
|
+
expect(select().props().value).toBe('+39');
|
|
144
140
|
expect(input().prop('value')).toBe('123456789');
|
|
145
141
|
expect(props.onChange).not.toHaveBeenCalled();
|
|
146
142
|
});
|
|
147
143
|
|
|
148
144
|
it("should not paste numbers which doesn't contain a country code", () => {
|
|
149
145
|
simulatePaste(component.find('input'), '06303932551');
|
|
150
|
-
expect(select().props().value
|
|
146
|
+
expect(select().props().value).toBe('+39');
|
|
151
147
|
expect(input().prop('value')).toBe('123456789');
|
|
152
148
|
expect(props.onChange).not.toHaveBeenCalled();
|
|
153
149
|
});
|
|
@@ -161,7 +157,7 @@ describe('Given a telephone number component', () => {
|
|
|
161
157
|
});
|
|
162
158
|
|
|
163
159
|
it('should set the select to the longest matching prefix', () => {
|
|
164
|
-
expect(select.props().value
|
|
160
|
+
expect(select.props().value).toBe('+1868');
|
|
165
161
|
});
|
|
166
162
|
|
|
167
163
|
it('should set the number input to the rest of the number', () => {
|
|
@@ -177,7 +173,7 @@ describe('Given a telephone number component', () => {
|
|
|
177
173
|
});
|
|
178
174
|
|
|
179
175
|
it('should empty the select', () => {
|
|
180
|
-
expect(select.props().value).
|
|
176
|
+
expect(select.props().value).toBeNull();
|
|
181
177
|
});
|
|
182
178
|
|
|
183
179
|
it('should put the whole value in the input without the plus', () => {
|
|
@@ -191,7 +187,7 @@ describe('Given a telephone number component', () => {
|
|
|
191
187
|
select = component.find(PREFIX_SELECT_SELECTOR);
|
|
192
188
|
input = component.find(NUMBER_SELECTOR);
|
|
193
189
|
|
|
194
|
-
expect(select.props().value
|
|
190
|
+
expect(select.props().value).toBe('+44');
|
|
195
191
|
expect(input.prop('value')).toBe('');
|
|
196
192
|
});
|
|
197
193
|
});
|
|
@@ -254,7 +250,7 @@ describe('Given a telephone number component', () => {
|
|
|
254
250
|
});
|
|
255
251
|
|
|
256
252
|
it('should use the prefix of the supplied value', () => {
|
|
257
|
-
expect(select.props().value
|
|
253
|
+
expect(select.props().value).toBe('+1');
|
|
258
254
|
});
|
|
259
255
|
});
|
|
260
256
|
|
|
@@ -268,7 +264,7 @@ describe('Given a telephone number component', () => {
|
|
|
268
264
|
});
|
|
269
265
|
|
|
270
266
|
it('should default the prefix to the local country', () => {
|
|
271
|
-
expect(select.props().value
|
|
267
|
+
expect(select.props().value).toBe('+34');
|
|
272
268
|
});
|
|
273
269
|
});
|
|
274
270
|
|
|
@@ -281,7 +277,7 @@ describe('Given a telephone number component', () => {
|
|
|
281
277
|
});
|
|
282
278
|
|
|
283
279
|
it('should override locale prefix with country specific prefix', () => {
|
|
284
|
-
expect(select.props().value
|
|
280
|
+
expect(select.props().value).toBe('+1');
|
|
285
281
|
});
|
|
286
282
|
});
|
|
287
283
|
});
|
|
@@ -349,7 +345,7 @@ describe('Given a telephone number component', () => {
|
|
|
349
345
|
|
|
350
346
|
it('renders Select component with expected props', () => {
|
|
351
347
|
const select = component.find(PREFIX_SELECT_SELECTOR);
|
|
352
|
-
expect(select.prop('className')).
|
|
348
|
+
expect(select.prop('className')).toBe('custom-class');
|
|
353
349
|
});
|
|
354
350
|
});
|
|
355
351
|
});
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { useState, useEffect, useMemo } from 'react';
|
|
2
|
+
import { useIntl } from 'react-intl';
|
|
3
|
+
|
|
4
|
+
import { Size, SizeLarge, SizeMedium, SizeSmall } from '../common';
|
|
5
|
+
import { SelectInput, SelectInputOptionContent, SelectInputProps } from '../inputs/SelectInput';
|
|
6
|
+
|
|
7
|
+
import countries from './data/countries';
|
|
8
|
+
import {
|
|
9
|
+
explodeNumberModel,
|
|
10
|
+
isValidPhoneNumber,
|
|
11
|
+
cleanNumber,
|
|
12
|
+
setDefaultPrefix,
|
|
13
|
+
sortArrayByProperty,
|
|
14
|
+
groupCountriesByPrefix,
|
|
15
|
+
excludeCountries,
|
|
16
|
+
findCountryByPrefix,
|
|
17
|
+
} from './utils';
|
|
18
|
+
import { PhoneNumber } from './utils/explodeNumberModel';
|
|
19
|
+
|
|
20
|
+
const ALLOWED_PHONE_CHARS = /^$|^[\d-\s]+$/;
|
|
21
|
+
|
|
22
|
+
export interface PhoneNumberInputProps {
|
|
23
|
+
id?: string;
|
|
24
|
+
required?: boolean;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
initialValue?: string;
|
|
27
|
+
onChange: (value: string | null, prefix: string) => void;
|
|
28
|
+
onFocus?: React.FocusEventHandler<HTMLInputElement>;
|
|
29
|
+
onBlur?: React.FocusEventHandler<HTMLInputElement>;
|
|
30
|
+
countryCode?: string;
|
|
31
|
+
searchPlaceholder?: string;
|
|
32
|
+
size?: SizeSmall | SizeMedium | SizeLarge;
|
|
33
|
+
placeholder?: string;
|
|
34
|
+
selectProps?: Partial<SelectInputProps<string | null>>;
|
|
35
|
+
/** List of iso3 codes of countries to remove from the list */
|
|
36
|
+
disabledCountries?: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const defaultSelectProps = {} satisfies PhoneNumberInputProps['selectProps'];
|
|
40
|
+
const defaultDisabledCountries = [] satisfies PhoneNumberInputProps['disabledCountries'];
|
|
41
|
+
|
|
42
|
+
const PhoneNumberInput = ({
|
|
43
|
+
id,
|
|
44
|
+
required,
|
|
45
|
+
disabled,
|
|
46
|
+
initialValue,
|
|
47
|
+
onChange,
|
|
48
|
+
onFocus,
|
|
49
|
+
onBlur,
|
|
50
|
+
countryCode,
|
|
51
|
+
searchPlaceholder = 'Prefix',
|
|
52
|
+
size = Size.MEDIUM,
|
|
53
|
+
placeholder,
|
|
54
|
+
selectProps = defaultSelectProps,
|
|
55
|
+
disabledCountries = defaultDisabledCountries,
|
|
56
|
+
}: PhoneNumberInputProps) => {
|
|
57
|
+
const { locale } = useIntl();
|
|
58
|
+
|
|
59
|
+
const [internalValue, setInternalValue] = useState<PhoneNumber>(() => {
|
|
60
|
+
const cleanValue = initialValue ? cleanNumber(initialValue) : null;
|
|
61
|
+
|
|
62
|
+
if (!cleanValue || !isValidPhoneNumber(cleanValue)) {
|
|
63
|
+
return {
|
|
64
|
+
prefix: setDefaultPrefix(locale, countryCode),
|
|
65
|
+
suffix: '',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return explodeNumberModel(cleanValue);
|
|
70
|
+
});
|
|
71
|
+
const [broadcastedValue, setBroadcastedValue] = useState<PhoneNumber | null>(null);
|
|
72
|
+
|
|
73
|
+
const countriesByPrefix = useMemo(
|
|
74
|
+
() =>
|
|
75
|
+
groupCountriesByPrefix(
|
|
76
|
+
sortArrayByProperty(excludeCountries(countries, disabledCountries), 'iso3'),
|
|
77
|
+
),
|
|
78
|
+
[disabledCountries],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const onSuffixChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
|
82
|
+
const suffix = event.target.value;
|
|
83
|
+
if (ALLOWED_PHONE_CHARS.test(suffix)) {
|
|
84
|
+
setInternalValue((prev) => ({ ...prev, suffix }));
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const onPaste: React.ClipboardEventHandler<HTMLInputElement> = (event) => {
|
|
89
|
+
if (!event.nativeEvent.clipboardData) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const pastedValue = (event.nativeEvent.clipboardData.getData('text/plain') || '').replace(
|
|
94
|
+
/(\s|-)+/g,
|
|
95
|
+
'',
|
|
96
|
+
);
|
|
97
|
+
const pastedNumber = explodeNumberModel(pastedValue);
|
|
98
|
+
|
|
99
|
+
if (
|
|
100
|
+
pastedNumber.prefix != null &&
|
|
101
|
+
countriesByPrefix.has(pastedNumber.prefix) &&
|
|
102
|
+
ALLOWED_PHONE_CHARS.test(pastedNumber.suffix)
|
|
103
|
+
) {
|
|
104
|
+
setInternalValue(pastedNumber);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (broadcastedValue === null) {
|
|
110
|
+
return setBroadcastedValue(internalValue);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const internalPhoneNumber = `${internalValue.prefix ?? ''}${internalValue.suffix}`;
|
|
114
|
+
const broadcastedPhoneNumber = `${broadcastedValue.prefix ?? ''}${broadcastedValue.suffix}`;
|
|
115
|
+
|
|
116
|
+
if (internalPhoneNumber === broadcastedPhoneNumber) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const newValue = isValidPhoneNumber(internalPhoneNumber)
|
|
121
|
+
? cleanNumber(internalPhoneNumber)
|
|
122
|
+
: null;
|
|
123
|
+
|
|
124
|
+
onChange(
|
|
125
|
+
newValue,
|
|
126
|
+
internalValue.prefix ?? '', // TODO: Allow `null` in public API
|
|
127
|
+
);
|
|
128
|
+
setBroadcastedValue(internalValue);
|
|
129
|
+
}, [onChange, broadcastedValue, internalValue]);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<div className="tw-telephone">
|
|
133
|
+
<div className="tw-telephone__country-select">
|
|
134
|
+
<SelectInput
|
|
135
|
+
placeholder="Select an option…"
|
|
136
|
+
items={[...countriesByPrefix].map(([prefix, countries]) => ({
|
|
137
|
+
type: 'option',
|
|
138
|
+
value: prefix,
|
|
139
|
+
filterMatchers: [
|
|
140
|
+
prefix,
|
|
141
|
+
...countries.map((country) => country.name),
|
|
142
|
+
...countries.map((country) => country.iso3),
|
|
143
|
+
],
|
|
144
|
+
}))}
|
|
145
|
+
value={internalValue.prefix}
|
|
146
|
+
renderValue={(prefix, withinTrigger) => (
|
|
147
|
+
<SelectInputOptionContent
|
|
148
|
+
title={prefix}
|
|
149
|
+
note={
|
|
150
|
+
withinTrigger
|
|
151
|
+
? undefined
|
|
152
|
+
: countriesByPrefix
|
|
153
|
+
.get(prefix)
|
|
154
|
+
?.map((country) => country.iso3)
|
|
155
|
+
.join(', ')
|
|
156
|
+
}
|
|
157
|
+
/>
|
|
158
|
+
)}
|
|
159
|
+
filterable
|
|
160
|
+
filterPlaceholder={searchPlaceholder}
|
|
161
|
+
disabled={disabled}
|
|
162
|
+
size={size}
|
|
163
|
+
onChange={(prefix) => {
|
|
164
|
+
const country = prefix != null ? findCountryByPrefix(prefix) : null;
|
|
165
|
+
setInternalValue((prev) => ({ ...prev, prefix, format: country?.phoneFormat }));
|
|
166
|
+
}}
|
|
167
|
+
{...selectProps}
|
|
168
|
+
/>
|
|
169
|
+
</div>
|
|
170
|
+
<div className="tw-telephone__number-input">
|
|
171
|
+
<div className={`input-group input-group-${size}`}>
|
|
172
|
+
<input
|
|
173
|
+
id={id}
|
|
174
|
+
autoComplete="tel-national"
|
|
175
|
+
name="phoneNumber"
|
|
176
|
+
inputMode="numeric"
|
|
177
|
+
value={internalValue.suffix}
|
|
178
|
+
className="form-control"
|
|
179
|
+
disabled={disabled}
|
|
180
|
+
required={required}
|
|
181
|
+
placeholder={placeholder}
|
|
182
|
+
onChange={onSuffixChange}
|
|
183
|
+
onPaste={onPaste}
|
|
184
|
+
onFocus={onFocus}
|
|
185
|
+
onBlur={onBlur}
|
|
186
|
+
/>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export default PhoneNumberInput;
|
|
@@ -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 = [];
|
|
7
|
+
const remove: string[] = [];
|
|
8
8
|
const filteredCountries = excludeCountries(countries, remove);
|
|
9
9
|
expect(filteredCountries).toHaveLength(countries.length);
|
|
10
10
|
});
|
package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.js → excludeCountries.ts}
RENAMED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { Country } from '../../data/countries';
|
|
2
|
+
|
|
1
3
|
// Reference fro localeCompare : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare
|
|
2
|
-
const filterCountriesByIso3 = (countries, iso3Codes) => {
|
|
4
|
+
const filterCountriesByIso3 = (countries: Country[], iso3Codes: string[]) => {
|
|
3
5
|
const iso3CodesSet = new Set(iso3Codes);
|
|
4
6
|
return countries.filter((country) => !iso3CodesSet.has(country.iso3));
|
|
5
7
|
};
|
|
@@ -7,11 +9,10 @@ const filterCountriesByIso3 = (countries, iso3Codes) => {
|
|
|
7
9
|
/**
|
|
8
10
|
* Removes the countries sepecified in the second param
|
|
9
11
|
*
|
|
10
|
-
* @param
|
|
11
|
-
* @param
|
|
12
|
-
* @returns
|
|
12
|
+
* @param countries list of country metadata objects
|
|
13
|
+
* @param disabledCountries list of iso3 country codes to remove from the list
|
|
13
14
|
*/
|
|
14
|
-
export const excludeCountries = (countries, disabledCountries) => {
|
|
15
|
+
export const excludeCountries = (countries: Country[], disabledCountries: string[]) => {
|
|
15
16
|
return disabledCountries.length > 0
|
|
16
17
|
? filterCountriesByIso3(countries, disabledCountries)
|
|
17
18
|
: countries;
|
|
@@ -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:
|
|
15
|
+
format: undefined,
|
|
16
16
|
prefix: '+372',
|
|
17
17
|
suffix: '7573135343',
|
|
18
18
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { findCountryByPrefix } from '../findCountryByPrefix';
|
|
2
|
+
|
|
3
|
+
export interface PhoneNumber {
|
|
4
|
+
prefix: string | null;
|
|
5
|
+
suffix: string;
|
|
6
|
+
format?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param number Phone number in a format like "+447573135343"
|
|
11
|
+
*/
|
|
12
|
+
export const explodeNumberModel = (number: string): PhoneNumber => {
|
|
13
|
+
const country = findCountryByPrefix(number);
|
|
14
|
+
return country
|
|
15
|
+
? {
|
|
16
|
+
prefix: country.phone,
|
|
17
|
+
suffix: number.slice(country.phone.length),
|
|
18
|
+
format: country.phoneFormat,
|
|
19
|
+
}
|
|
20
|
+
: {
|
|
21
|
+
prefix: null,
|
|
22
|
+
suffix: number.slice(1),
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -14,7 +14,6 @@ describe('findCountryByCode', () => {
|
|
|
14
14
|
|
|
15
15
|
it('should return null for invalid code', () => {
|
|
16
16
|
expect(findCountryByCode('Wrong')).toBeNull();
|
|
17
|
-
expect(findCountryByCode(null)).toBeNull();
|
|
18
17
|
expect(findCountryByCode('')).toBeNull();
|
|
19
18
|
expect(findCountryByCode(' ')).toBeNull();
|
|
20
19
|
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import countries from '../../data/countries';
|
|
2
|
+
import { longestMatchingPrefix } from '../longestMatchingPrefix';
|
|
3
|
+
|
|
4
|
+
export const findCountryByCode = (code: string) => {
|
|
5
|
+
if (code.length === 2) {
|
|
6
|
+
const matchingCodes = countries.filter((country) => code.toUpperCase() === country.iso2);
|
|
7
|
+
if (matchingCodes.length > 0) {
|
|
8
|
+
return longestMatchingPrefix(matchingCodes);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import countries from '../../data/countries';
|
|
2
|
+
import { longestMatchingPrefix } from '../longestMatchingPrefix';
|
|
3
|
+
|
|
4
|
+
export const findCountryByPrefix = (number: string) => {
|
|
5
|
+
if (number.length > 1) {
|
|
6
|
+
const matchingCodes = countries.filter((country) => number.startsWith(country.phone));
|
|
7
|
+
if (matchingCodes.length > 0) {
|
|
8
|
+
return longestMatchingPrefix(matchingCodes);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Country } from '../../data/countries';
|
|
2
|
+
|
|
3
|
+
import { groupCountriesByPrefix } from '.';
|
|
4
|
+
|
|
5
|
+
const countries = [
|
|
6
|
+
{
|
|
7
|
+
name: 'Canada',
|
|
8
|
+
iso2: 'CA',
|
|
9
|
+
iso3: 'CAN',
|
|
10
|
+
phone: '+1',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'United States of America',
|
|
14
|
+
iso2: 'US',
|
|
15
|
+
iso3: 'USA',
|
|
16
|
+
phone: '+1',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: 'United States Minor Outlying Islands',
|
|
20
|
+
iso2: 'UM',
|
|
21
|
+
iso3: 'UMI',
|
|
22
|
+
phone: '+1',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'United Kingdom',
|
|
26
|
+
iso2: 'GB',
|
|
27
|
+
iso3: 'GBR',
|
|
28
|
+
phone: '+44',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'Guernsey',
|
|
32
|
+
iso2: 'GG',
|
|
33
|
+
iso3: 'GGY',
|
|
34
|
+
phone: '+44',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'Guinea',
|
|
38
|
+
iso2: 'GN',
|
|
39
|
+
iso3: 'GIN',
|
|
40
|
+
phone: '+224',
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const groupedCountries = new Map<string, Country[]>([
|
|
45
|
+
[
|
|
46
|
+
'+1',
|
|
47
|
+
[
|
|
48
|
+
{
|
|
49
|
+
name: 'Canada',
|
|
50
|
+
iso2: 'CA',
|
|
51
|
+
iso3: 'CAN',
|
|
52
|
+
phone: '+1',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'United States of America',
|
|
56
|
+
iso2: 'US',
|
|
57
|
+
iso3: 'USA',
|
|
58
|
+
phone: '+1',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'United States Minor Outlying Islands',
|
|
62
|
+
iso2: 'UM',
|
|
63
|
+
iso3: 'UMI',
|
|
64
|
+
phone: '+1',
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
'+44',
|
|
70
|
+
[
|
|
71
|
+
{
|
|
72
|
+
name: 'United Kingdom',
|
|
73
|
+
iso2: 'GB',
|
|
74
|
+
iso3: 'GBR',
|
|
75
|
+
phone: '+44',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'Guernsey',
|
|
79
|
+
iso2: 'GG',
|
|
80
|
+
iso3: 'GGY',
|
|
81
|
+
phone: '+44',
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
'+224',
|
|
87
|
+
[
|
|
88
|
+
{
|
|
89
|
+
name: 'Guinea',
|
|
90
|
+
iso2: 'GN',
|
|
91
|
+
iso3: 'GIN',
|
|
92
|
+
phone: '+224',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
],
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
describe('groupCountriesByPrefix', () => {
|
|
99
|
+
it('groups countries by prefix', () => {
|
|
100
|
+
expect(groupCountriesByPrefix(countries)).toStrictEqual(groupedCountries);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Country } from '../../data/countries';
|
|
2
|
+
|
|
3
|
+
export const groupCountriesByPrefix = (countries: Country[]) => {
|
|
4
|
+
const countriesByPrefix = new Map<string, Country[]>();
|
|
5
|
+
countries.forEach((country) => {
|
|
6
|
+
countriesByPrefix.set(country.phone, [
|
|
7
|
+
...(countriesByPrefix.get(country.phone) ?? []),
|
|
8
|
+
country,
|
|
9
|
+
]);
|
|
10
|
+
});
|
|
11
|
+
return countriesByPrefix;
|
|
12
|
+
};
|
|
@@ -4,8 +4,6 @@ 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';
|
|
9
7
|
export { cleanNumber } from './cleanNumber';
|
|
10
8
|
export { isStringNumeric } from './isStringNumeric';
|
|
11
9
|
export { sortArrayByProperty } from './sortArrayByProperty';
|
|
@@ -4,7 +4,6 @@ describe('isStringNumeric', () => {
|
|
|
4
4
|
it('should return true when numeric sting is provided', () => {
|
|
5
5
|
expect(isStringNumeric('+23456')).toBe(true);
|
|
6
6
|
expect(isStringNumeric('23456')).toBe(true);
|
|
7
|
-
expect(isStringNumeric(23456)).toBe(true);
|
|
8
7
|
});
|
|
9
8
|
|
|
10
9
|
it('should return false when string is provided', () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const isStringNumeric = (value: string) => /^\+?[\d-\s]+$/.test(value);
|
|
@@ -11,7 +11,7 @@ describe('isValidPhoneNumber', () => {
|
|
|
11
11
|
|
|
12
12
|
it('should return false for invalid numbers', () => {
|
|
13
13
|
expect(isValidPhoneNumber('+441')).toBe(false);
|
|
14
|
-
expect(isValidPhoneNumber(44)).toBe(false);
|
|
14
|
+
expect(isValidPhoneNumber('44')).toBe(false);
|
|
15
15
|
expect(isValidPhoneNumber('44123')).toBe(false);
|
|
16
16
|
});
|
|
17
17
|
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param phoneNumber
|
|
4
|
+
* @returns True if number that starts with "+" and contains a mix of digits and spaces with at least 4 digits.
|
|
5
|
+
*/
|
|
6
|
+
export const isValidPhoneNumber = (phoneNumber: string) =>
|
|
7
|
+
/^\+[\d-\s]+$/.test(phoneNumber) && (phoneNumber.match(/\d+/g)?.join('').length ?? 0) >= 4;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getCountryFromLocale } from '../../../common/locale';
|
|
2
|
+
import { findCountryByCode } from '../findCountryByCode';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Default phone code, the UK one `+44`
|
|
6
|
+
*/
|
|
7
|
+
const DEFAULT_PHONE_CODE = '+44';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Given a valid locale it returns the correspondent prefix if found or +44 otherwise.
|
|
11
|
+
*
|
|
12
|
+
* @param locale BCP 47 language tag of locale, e.g. `"es-ES"`.
|
|
13
|
+
* @param countryCode Two-letter country code (ISO 3166-1 alpha-2).
|
|
14
|
+
*/
|
|
15
|
+
export const setDefaultPrefix = (locale: string, countryCode?: string) => {
|
|
16
|
+
const country =
|
|
17
|
+
(countryCode != null ? findCountryByCode(countryCode) : null) ??
|
|
18
|
+
findCountryByCode(getCountryFromLocale(locale) ?? locale);
|
|
19
|
+
return country?.phone ?? DEFAULT_PHONE_CODE;
|
|
20
|
+
};
|