@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
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { Direction } from '..';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Default language
|
|
5
|
-
*
|
|
6
|
-
* @type {string}
|
|
7
|
-
*/
|
|
8
|
-
export const DEFAULT_LANG = 'en';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Default locale
|
|
12
|
-
*
|
|
13
|
-
* @type {string}
|
|
14
|
-
*/
|
|
15
|
-
export const DEFAULT_LOCALE = 'en-GB';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Array of languages that are written from the right to the left
|
|
19
|
-
*
|
|
20
|
-
* @type {string[]}
|
|
21
|
-
*/
|
|
22
|
-
export const RTL_LANGUAGES = ['ar', 'he'];
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @deprecated The source of truth for supported languages lives in Crab.
|
|
26
|
-
* @type {string[]}
|
|
27
|
-
*/
|
|
28
|
-
export const SUPPORTED_LANGUAGES = [
|
|
29
|
-
DEFAULT_LANG,
|
|
30
|
-
'de',
|
|
31
|
-
'es',
|
|
32
|
-
'fr',
|
|
33
|
-
'hu',
|
|
34
|
-
'id',
|
|
35
|
-
'it',
|
|
36
|
-
'ja',
|
|
37
|
-
'pl',
|
|
38
|
-
'pt',
|
|
39
|
-
'ro',
|
|
40
|
-
'ru',
|
|
41
|
-
'th',
|
|
42
|
-
'tr',
|
|
43
|
-
'uk',
|
|
44
|
-
'zh',
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Verifies and adjusts locale (replace `_` with `-`)
|
|
49
|
-
* Returns null if locale is unrecognized by {Intl.Locale}
|
|
50
|
-
*
|
|
51
|
-
* @param {string} locale (`es`, `es_ES`, `en-GB`, `en`, `ja`, `ja-JP` etc)
|
|
52
|
-
* @returns {string|null}
|
|
53
|
-
*/
|
|
54
|
-
export function adjustLocale(locale) {
|
|
55
|
-
if (!locale || locale.trim().length === 0) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
const { baseName } = new Intl.Locale(locale.trim().replace('_', '-'));
|
|
60
|
-
return baseName;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
// eslint-disable-next-line no-console
|
|
63
|
-
console.error(error);
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Provides corresponding lang (iso2) for provided locale
|
|
70
|
-
* if locale is invalid or language is unsupported returns null
|
|
71
|
-
*
|
|
72
|
-
* @deprecated The use of this function almost always breaks language variants
|
|
73
|
-
* e.g. Simplified and Traditional Chinese.
|
|
74
|
-
* There should be no use case for this function.
|
|
75
|
-
* To select the correct translations from a translations object, pass the
|
|
76
|
-
* locale directly into Crab's getLocalisedMessages.
|
|
77
|
-
* @param {string} locale (`es`, `es-ES`, `en-GB`, `en`, `ja`, `ja-JP` etc)
|
|
78
|
-
* @returns {string|null} two-letter ISO639-1 language
|
|
79
|
-
*/
|
|
80
|
-
export function getLangFromLocale(locale) {
|
|
81
|
-
const adjustedLocale = adjustLocale(locale);
|
|
82
|
-
if (adjustedLocale === null) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const { language } = new Intl.Locale(adjustedLocale);
|
|
87
|
-
|
|
88
|
-
if (SUPPORTED_LANGUAGES.includes(language)) {
|
|
89
|
-
return language;
|
|
90
|
-
}
|
|
91
|
-
return null;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
// eslint-disable-next-line no-console
|
|
94
|
-
console.error(error);
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Provides corresponding country code (iso2) for locales code with explicit region value (`es-ES`, `en-GB`, `ja-JP` etc.)
|
|
101
|
-
* if the value is invalid or missing region it returns null
|
|
102
|
-
*
|
|
103
|
-
* @param {string} locale
|
|
104
|
-
* @returns {string|null}
|
|
105
|
-
*/
|
|
106
|
-
export function getCountryFromLocale(locale) {
|
|
107
|
-
const adjustedLocale = adjustLocale(locale);
|
|
108
|
-
if (adjustedLocale === null) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
const { region } = new Intl.Locale(adjustedLocale);
|
|
113
|
-
return region ?? null;
|
|
114
|
-
} catch (error) {
|
|
115
|
-
// eslint-disable-next-line no-console
|
|
116
|
-
console.error(error);
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Provides the layout direction for a given locale.
|
|
123
|
-
* If locale is invalid or language is unsupported returns Direction.LTR
|
|
124
|
-
*
|
|
125
|
-
* @param {string} locale (`es`, `es-ES`, `en-GB`, `en`, `ja`, `ja-JP` etc)
|
|
126
|
-
* @returns {Direction} The layout direction based on the locale
|
|
127
|
-
*/
|
|
128
|
-
export function getDirectionFromLocale(locale) {
|
|
129
|
-
try {
|
|
130
|
-
const adjustedLocale = adjustLocale(locale);
|
|
131
|
-
const { language } = new Intl.Locale(adjustedLocale);
|
|
132
|
-
|
|
133
|
-
return RTL_LANGUAGES.includes(language) ? Direction.RTL : Direction.LTR;
|
|
134
|
-
} catch (error) {
|
|
135
|
-
// eslint-disable-next-line no-console
|
|
136
|
-
console.error(error);
|
|
137
|
-
return Direction.LTR;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
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;
|
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,27 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,11 +0,0 @@
|
|
|
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));
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { isOptionAndFitsQuery, startsWith } from './isOptionAndFitsQuery';
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { isArray } from '@transferwise/neptune-validation';
|
|
2
|
-
/**
|
|
3
|
-
* Checks if query is contained into object properties.
|
|
4
|
-
*
|
|
5
|
-
* @param {object} option - the select option
|
|
6
|
-
* @param {string} query - the current search query
|
|
7
|
-
* @returns {boolean}
|
|
8
|
-
*/
|
|
9
|
-
export const isOptionAndFitsQuery = (option, query) =>
|
|
10
|
-
startsWith(option.iso3, query) ||
|
|
11
|
-
startsWith(option.iso2, query) ||
|
|
12
|
-
startsWith(option.name, query) ||
|
|
13
|
-
startsWith(option.phone, query);
|
|
14
|
-
|
|
15
|
-
export const startsWith = (property, query) => {
|
|
16
|
-
if (isArray(property)) {
|
|
17
|
-
return (
|
|
18
|
-
property.filter((proper) => normalizeValue(proper).indexOf(normalizeValue(query)) === 0)
|
|
19
|
-
.length > 0
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
return normalizeValue(property).indexOf(normalizeValue(query)) === 0;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const normalizeValue = (value) => value.toLowerCase().replace('+', '');
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { startsWith, isOptionAndFitsQuery } from '.';
|
|
2
|
-
|
|
3
|
-
const DATA_TEST = [
|
|
4
|
-
{
|
|
5
|
-
name: 'test1',
|
|
6
|
-
iso2: 'TT',
|
|
7
|
-
iso3: 'TT1',
|
|
8
|
-
phone: '+93',
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
name: 'test1',
|
|
12
|
-
iso2: ['TT', 'AA'],
|
|
13
|
-
iso3: 'TT1',
|
|
14
|
-
phone: '+93',
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
name: 'something',
|
|
18
|
-
iso2: 'ST',
|
|
19
|
-
iso3: 'SMT',
|
|
20
|
-
phone: '+33',
|
|
21
|
-
},
|
|
22
|
-
];
|
|
23
|
-
|
|
24
|
-
describe('isOptionAndFitsQuery', () => {
|
|
25
|
-
describe('when option is given', () => {
|
|
26
|
-
it('should return true if query is relevant', () => {
|
|
27
|
-
expect(isOptionAndFitsQuery(DATA_TEST[0], 'TT')).toBe(true);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('should return true if query is relevant and one of the array values', () => {
|
|
31
|
-
expect(isOptionAndFitsQuery(DATA_TEST[1], 'TT')).toBe(true);
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('should return false if query is not relevant and not one of the array values', () => {
|
|
35
|
-
expect(isOptionAndFitsQuery(DATA_TEST[1], 'BB')).toBe(false);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should return false if query is not relevant', () => {
|
|
39
|
-
expect(isOptionAndFitsQuery(DATA_TEST[0], 'AA')).toBe(false);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe('startWith', () => {
|
|
45
|
-
describe('when property is given', () => {
|
|
46
|
-
it('returns true if any of the values starts with the query', () => {
|
|
47
|
-
expect(startsWith('AA', 'AA')).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it(`returns false if value doesn't start with`, () => {
|
|
51
|
-
expect(startsWith('AABB', 'BB')).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should return true if query is contained in grouped options', () => {
|
|
55
|
-
expect(startsWith(['AA', 'BB'], 'BB')).toBe(true);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("returns false if any value in an array doesn't start with", () => {
|
|
59
|
-
expect(startsWith(['CCAA', 'CCBB'], 'BB')).toBe(false);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('returns false for an empty value', () => {
|
|
63
|
-
expect(startsWith('', 'BB')).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const isStringNumeric = (value) => /^\+?[\d-\s]+$/.test(value);
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
* @param phoneNumber
|
|
4
|
-
* @returns {boolean} - returns true for number that starts with '+' and contains a mix of digits and spaces with
|
|
5
|
-
* at least 4 digits.
|
|
6
|
-
*/
|
|
7
|
-
export const isValidPhoneNumber = (phoneNumber) =>
|
|
8
|
-
/^\+[\d-\s]+$/.test(phoneNumber) &&
|
|
9
|
-
phoneNumber.match(/\d+/g) &&
|
|
10
|
-
phoneNumber.match(/\d+/g).join('').length >= 4;
|