@transferwise/components 0.0.0-experimental-778ee44 → 0.0.0-experimental-ae2a4e7

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 (153) hide show
  1. package/build/index.esm.js +226 -327
  2. package/build/index.esm.js.map +1 -1
  3. package/build/index.js +227 -328
  4. package/build/index.js.map +1 -1
  5. package/build/types/common/locale/index.d.ts +26 -43
  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 -3
  10. package/build/types/common/textFormat/getCursorPositionAfterKeystroke/getCursorPositionAfterKeystroke.d.ts.map +1 -1
  11. package/build/types/common/textFormat/getSymbolsInPatternWithPosition/getSymbolsInPatternWithPosition.d.ts +1 -5
  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 +0 -2
  16. package/build/types/index.d.ts.map +1 -1
  17. package/build/types/inputWithDisplayFormat/InputWithDisplayFormat.d.ts +11 -5
  18. package/build/types/inputWithDisplayFormat/InputWithDisplayFormat.d.ts.map +1 -1
  19. package/build/types/inputWithDisplayFormat/index.d.ts +1 -2
  20. package/build/types/inputWithDisplayFormat/index.d.ts.map +1 -1
  21. package/build/types/inputs/Input.d.ts +6 -8
  22. package/build/types/inputs/Input.d.ts.map +1 -1
  23. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts +30 -27
  24. package/build/types/phoneNumberInput/PhoneNumberInput.d.ts.map +1 -1
  25. package/build/types/phoneNumberInput/data/countries.d.ts +5 -10
  26. package/build/types/phoneNumberInput/data/countries.d.ts.map +1 -1
  27. package/build/types/phoneNumberInput/index.d.ts +1 -1
  28. package/build/types/phoneNumberInput/index.d.ts.map +1 -1
  29. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts +1 -1
  30. package/build/types/phoneNumberInput/utils/cleanNumber/cleanNumber.d.ts.map +1 -1
  31. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts +1 -1
  32. package/build/types/phoneNumberInput/utils/cleanNumber/index.d.ts.map +1 -1
  33. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts +8 -1
  34. package/build/types/phoneNumberInput/utils/excludeCountries/excludeCountries.d.ts.map +1 -1
  35. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts +1 -1
  36. package/build/types/phoneNumberInput/utils/excludeCountries/index.d.ts.map +1 -1
  37. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts +8 -4
  38. package/build/types/phoneNumberInput/utils/explodeNumberModel/index.d.ts.map +1 -1
  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 +2 -1
  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 +11 -13
  48. package/build/types/phoneNumberInput/utils/index.d.ts.map +1 -1
  49. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts +1 -1
  50. package/build/types/phoneNumberInput/utils/isStringNumeric/index.d.ts.map +1 -1
  51. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts +1 -1
  52. package/build/types/phoneNumberInput/utils/isStringNumeric/isStringNumeric.d.ts.map +1 -1
  53. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts +1 -1
  54. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/index.d.ts.map +1 -1
  55. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts +6 -1
  56. package/build/types/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.d.ts.map +1 -1
  57. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts +2 -1
  58. package/build/types/phoneNumberInput/utils/longestMatchingPrefix/index.d.ts.map +1 -1
  59. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts +7 -1
  60. package/build/types/phoneNumberInput/utils/setDefaultPrefix/index.d.ts.map +1 -1
  61. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts +1 -1
  62. package/build/types/phoneNumberInput/utils/sortArrayByProperty/index.d.ts.map +1 -1
  63. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts +1 -1
  64. package/build/types/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.d.ts.map +1 -1
  65. package/build/types/textareaWithDisplayFormat/TextareaWithDisplayFormat.d.ts +11 -5
  66. package/build/types/textareaWithDisplayFormat/TextareaWithDisplayFormat.d.ts.map +1 -1
  67. package/build/types/textareaWithDisplayFormat/index.d.ts +1 -2
  68. package/build/types/textareaWithDisplayFormat/index.d.ts.map +1 -1
  69. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts +82 -55
  70. package/build/types/withDisplayFormat/WithDisplayFormat.d.ts.map +1 -1
  71. package/build/types/withDisplayFormat/index.d.ts +1 -2
  72. package/build/types/withDisplayFormat/index.d.ts.map +1 -1
  73. package/package.json +1 -1
  74. package/src/common/locale/{index.spec.js → index.spec.ts} +0 -4
  75. package/src/common/locale/index.ts +96 -0
  76. package/src/common/textFormat/formatWithPattern/{formatWithPattern.ts → formatWithPattern.js} +4 -8
  77. package/src/common/textFormat/getCursorPositionAfterKeystroke/{getCursorPositionAfterKeystroke.ts → getCursorPositionAfterKeystroke.js} +8 -9
  78. package/src/common/textFormat/getSymbolsInPatternWithPosition/{getSymbolsInPatternWithPosition.ts → getSymbolsInPatternWithPosition.js} +2 -7
  79. package/src/common/textFormat/unformatWithPattern/{unformatWithPattern.ts → unformatWithPattern.js} +2 -3
  80. package/src/index.ts +0 -2
  81. package/src/inputWithDisplayFormat/InputWithDisplayFormat.js +14 -0
  82. package/src/inputWithDisplayFormat/index.js +1 -0
  83. package/src/inputs/Input.tsx +11 -6
  84. package/src/phoneNumberInput/PhoneNumberInput.spec.js +18 -22
  85. package/src/phoneNumberInput/PhoneNumberInput.tsx +201 -0
  86. package/src/phoneNumberInput/data/{countries.js → countries.ts} +9 -1
  87. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.ts +3 -0
  88. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.spec.js → excludeCountries.spec.ts} +1 -1
  89. package/src/phoneNumberInput/utils/excludeCountries/{excludeCountries.js → excludeCountries.ts} +6 -5
  90. package/src/phoneNumberInput/utils/explodeNumberModel/{explodeNumberModel.spec.js → explodeNumberModel.spec.ts} +1 -1
  91. package/src/phoneNumberInput/utils/explodeNumberModel/index.ts +25 -0
  92. package/src/phoneNumberInput/utils/findCountryByCode/{findCountryByCode.spec.js → findCountryByCode.spec.ts} +0 -1
  93. package/src/phoneNumberInput/utils/findCountryByCode/index.ts +12 -0
  94. package/src/phoneNumberInput/utils/findCountryByPrefix/index.ts +12 -0
  95. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.ts +102 -0
  96. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.ts +12 -0
  97. package/src/phoneNumberInput/utils/{index.js → index.ts} +0 -2
  98. package/src/phoneNumberInput/utils/isStringNumeric/{isStringNumeric.spec.js → isStringNumeric.spec.ts} +0 -1
  99. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.ts +1 -0
  100. package/src/phoneNumberInput/utils/isValidPhoneNumber/{isValidPhoneNumber.spec.js → isValidPhoneNumber.spec.ts} +1 -1
  101. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.ts +7 -0
  102. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.ts +4 -0
  103. package/src/phoneNumberInput/utils/setDefaultPrefix/index.ts +20 -0
  104. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.ts +6 -0
  105. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.js +14 -0
  106. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.spec.js +1 -3
  107. package/src/textareaWithDisplayFormat/index.js +1 -0
  108. package/src/withDisplayFormat/{WithDisplayFormat.tsx → WithDisplayFormat.js} +104 -111
  109. package/src/withDisplayFormat/index.js +1 -0
  110. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts +0 -2
  111. package/build/types/phoneNumberInput/utils/filterOptionsForQuery/index.d.ts.map +0 -1
  112. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts +0 -2
  113. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/index.d.ts.map +0 -1
  114. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts +0 -3
  115. package/build/types/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.d.ts.map +0 -1
  116. package/src/common/locale/index.js +0 -139
  117. package/src/inputWithDisplayFormat/InputWithDisplayFormat.tsx +0 -11
  118. package/src/inputWithDisplayFormat/index.ts +0 -2
  119. package/src/phoneNumberInput/PhoneNumberInput.js +0 -210
  120. package/src/phoneNumberInput/data/countries.spec.js +0 -12
  121. package/src/phoneNumberInput/utils/cleanNumber/cleanNumber.js +0 -4
  122. package/src/phoneNumberInput/utils/explodeNumberModel/index.js +0 -27
  123. package/src/phoneNumberInput/utils/filterOptionsForQuery/filterOptionsForQuery.spec.js +0 -36
  124. package/src/phoneNumberInput/utils/filterOptionsForQuery/index.js +0 -11
  125. package/src/phoneNumberInput/utils/findCountryByCode/index.js +0 -10
  126. package/src/phoneNumberInput/utils/findCountryByPrefix/index.js +0 -11
  127. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.js +0 -26
  128. package/src/phoneNumberInput/utils/groupCountriesByPrefix/groupCountriesByPrefix.spec.js +0 -67
  129. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/index.js +0 -1
  130. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.js +0 -25
  131. package/src/phoneNumberInput/utils/isOptionAndFitsQuery/isOptionAndFitsQuery.spec.js +0 -66
  132. package/src/phoneNumberInput/utils/isStringNumeric/isStringNumeric.js +0 -1
  133. package/src/phoneNumberInput/utils/isValidPhoneNumber/isValidPhoneNumber.js +0 -10
  134. package/src/phoneNumberInput/utils/longestMatchingPrefix/index.js +0 -2
  135. package/src/phoneNumberInput/utils/setDefaultPrefix/index.js +0 -25
  136. package/src/phoneNumberInput/utils/sortArrayByProperty/sortArrayByProperty.js +0 -3
  137. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.story.tsx +0 -32
  138. package/src/textareaWithDisplayFormat/TextareaWithDisplayFormat.tsx +0 -11
  139. package/src/textareaWithDisplayFormat/index.ts +0 -2
  140. package/src/withDisplayFormat/index.ts +0 -2
  141. /package/src/phoneNumberInput/{PhoneNumberInput.story.js → PhoneNumberInput.story.tsx} +0 -0
  142. /package/src/phoneNumberInput/{index.js → index.ts} +0 -0
  143. /package/src/phoneNumberInput/utils/cleanNumber/{cleanNumber.spec.js → cleanNumber.spec.ts} +0 -0
  144. /package/src/phoneNumberInput/utils/cleanNumber/{index.js → index.ts} +0 -0
  145. /package/src/phoneNumberInput/utils/excludeCountries/{index.js → index.ts} +0 -0
  146. /package/src/phoneNumberInput/utils/findCountryByPrefix/{findCountryByPrefix.spec.js → findCountryByPrefix.spec.ts} +0 -0
  147. /package/src/phoneNumberInput/utils/groupCountriesByPrefix/{index.js → index.ts} +0 -0
  148. /package/src/phoneNumberInput/utils/isStringNumeric/{index.js → index.ts} +0 -0
  149. /package/src/phoneNumberInput/utils/isValidPhoneNumber/{index.js → index.ts} +0 -0
  150. /package/src/phoneNumberInput/utils/longestMatchingPrefix/{longestMatchingPrefix.spec.js → longestMatchingPrefix.spec.ts} +0 -0
  151. /package/src/phoneNumberInput/utils/setDefaultPrefix/{setDefaultPrefix.spec.js → setDefaultPrefix.spec.ts} +0 -0
  152. /package/src/phoneNumberInput/utils/sortArrayByProperty/{index.js → index.ts} +0 -0
  153. /package/src/phoneNumberInput/utils/sortArrayByProperty/{sortArrayByProperty.spec.js → sortArrayByProperty.spec.ts} +0 -0
@@ -0,0 +1,201 @@
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
+ } from './utils';
17
+ import { PhoneNumber } from './utils/explodeNumberModel';
18
+
19
+ const ALLOWED_PHONE_CHARS = /^$|^[\d-\s]+$/;
20
+
21
+ export interface PhoneNumberPrefixOptionItem {
22
+ header?: never;
23
+ value: string;
24
+ label: string;
25
+ currency: string;
26
+ note?: string;
27
+ searchable?: string;
28
+ }
29
+
30
+ export interface PhoneNumberInputProps {
31
+ id?: string;
32
+ required?: boolean;
33
+ disabled?: boolean;
34
+ initialValue?: string;
35
+ onChange: (value: string | null, prefix: string) => void;
36
+ onFocus?: React.FocusEventHandler<HTMLInputElement>;
37
+ onBlur?: React.FocusEventHandler<HTMLInputElement>;
38
+ countryCode?: string;
39
+ searchPlaceholder?: string;
40
+ size?: SizeSmall | SizeMedium | SizeLarge;
41
+ placeholder?: string;
42
+ selectProps?: Partial<SelectInputProps<string | null>>;
43
+ /** List of iso3 codes of countries to remove from the list */
44
+ disabledCountries?: string[];
45
+ }
46
+
47
+ const defaultSelectProps = {} satisfies PhoneNumberInputProps['selectProps'];
48
+ const defaultDisabledCountries = [] satisfies PhoneNumberInputProps['disabledCountries'];
49
+
50
+ const PhoneNumberInput = ({
51
+ id,
52
+ required,
53
+ disabled,
54
+ initialValue,
55
+ onChange,
56
+ onFocus,
57
+ onBlur,
58
+ countryCode,
59
+ searchPlaceholder = 'Prefix',
60
+ size = Size.MEDIUM,
61
+ placeholder,
62
+ selectProps = defaultSelectProps,
63
+ disabledCountries = defaultDisabledCountries,
64
+ }: PhoneNumberInputProps) => {
65
+ const { locale } = useIntl();
66
+
67
+ const [internalValue, setInternalValue] = useState<PhoneNumber>(() => {
68
+ const cleanValue = initialValue ? cleanNumber(initialValue) : null;
69
+
70
+ if (!cleanValue || !isValidPhoneNumber(cleanValue)) {
71
+ return {
72
+ prefix: setDefaultPrefix(locale, countryCode),
73
+ suffix: '',
74
+ format: undefined,
75
+ };
76
+ }
77
+
78
+ return explodeNumberModel(cleanValue);
79
+ });
80
+ const [broadcastedValue, setBroadcastedValue] = useState<PhoneNumber | null>(null);
81
+
82
+ const countriesByPrefix = useMemo(
83
+ () =>
84
+ groupCountriesByPrefix(
85
+ sortArrayByProperty(excludeCountries(countries, disabledCountries), 'iso3'),
86
+ ),
87
+ [disabledCountries],
88
+ );
89
+
90
+ const onSuffixChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
91
+ const suffix = event.target.value;
92
+ if (ALLOWED_PHONE_CHARS.test(suffix)) {
93
+ setInternalValue((prev) => ({ ...prev, suffix }));
94
+ }
95
+ };
96
+
97
+ const onPaste: React.ClipboardEventHandler<HTMLInputElement> = (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 pastedNumber = explodeNumberModel(pastedValue);
107
+
108
+ if (
109
+ pastedNumber.prefix != null &&
110
+ countriesByPrefix.has(pastedNumber.prefix) &&
111
+ ALLOWED_PHONE_CHARS.test(pastedNumber.suffix)
112
+ ) {
113
+ setInternalValue(pastedNumber);
114
+ }
115
+ };
116
+
117
+ useEffect(() => {
118
+ if (broadcastedValue === null) {
119
+ return setBroadcastedValue(internalValue);
120
+ }
121
+
122
+ const internalPhoneNumber = `${internalValue.prefix ?? ''}${internalValue.suffix}`;
123
+ const broadcastedPhoneNumber = `${broadcastedValue.prefix ?? ''}${broadcastedValue.suffix}`;
124
+
125
+ if (internalPhoneNumber === broadcastedPhoneNumber) {
126
+ return;
127
+ }
128
+
129
+ const newValue = isValidPhoneNumber(internalPhoneNumber)
130
+ ? cleanNumber(internalPhoneNumber)
131
+ : null;
132
+
133
+ onChange(
134
+ newValue,
135
+ internalValue.prefix ?? '', // TODO: Allow `null` in public API
136
+ );
137
+ setBroadcastedValue(internalValue);
138
+ }, [onChange, broadcastedValue, internalValue]);
139
+
140
+ return (
141
+ <div className="tw-telephone">
142
+ <div className="tw-telephone__country-select">
143
+ <SelectInput
144
+ placeholder="Select an option…"
145
+ items={[...countriesByPrefix].map(([prefix, countries]) => ({
146
+ type: 'option',
147
+ value: prefix,
148
+ filterMatchers: [
149
+ prefix,
150
+ ...countries.map((country) => country.name),
151
+ ...countries.map((country) => country.iso3),
152
+ ],
153
+ }))}
154
+ value={internalValue.prefix}
155
+ renderValue={(prefix, withinTrigger) => (
156
+ <SelectInputOptionContent
157
+ title={prefix}
158
+ note={
159
+ withinTrigger
160
+ ? undefined
161
+ : countriesByPrefix
162
+ .get(prefix)
163
+ ?.map((country) => country.iso3)
164
+ .join(', ')
165
+ }
166
+ />
167
+ )}
168
+ filterable
169
+ filterPlaceholder={searchPlaceholder}
170
+ disabled={disabled}
171
+ size={size}
172
+ onChange={(prefix) => {
173
+ setInternalValue((prev) => ({ ...prev, prefix }));
174
+ }}
175
+ {...selectProps}
176
+ />
177
+ </div>
178
+ <div className="tw-telephone__number-input">
179
+ <div className={`input-group input-group-${size}`}>
180
+ <input
181
+ id={id}
182
+ autoComplete="tel-national"
183
+ name="phoneNumber"
184
+ inputMode="numeric"
185
+ value={internalValue.suffix}
186
+ className="form-control"
187
+ disabled={disabled}
188
+ required={required}
189
+ placeholder={placeholder}
190
+ onChange={onSuffixChange}
191
+ onPaste={onPaste}
192
+ onFocus={onFocus}
193
+ onBlur={onBlur}
194
+ />
195
+ </div>
196
+ </div>
197
+ </div>
198
+ );
199
+ };
200
+
201
+ export default PhoneNumberInput;
@@ -1,4 +1,12 @@
1
- const countries = [
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[] = [
2
10
  {
3
11
  name: 'Afghanistan',
4
12
  iso2: 'AF',
@@ -0,0 +1,3 @@
1
+ const DIGITS_MATCH = /^$|^(\+)|([\d]+)/g;
2
+
3
+ export const cleanNumber = (number: string) => number.match(DIGITS_MATCH)?.join('') ?? '';
@@ -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
  });
@@ -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 {Array} countries: list of country metadata objects
11
- * @param {Array} disabledCountries: list of iso3 country codes to remove from the list
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,25 @@
1
+ import { findCountryByPrefix } from '../findCountryByPrefix';
2
+
3
+ export interface PhoneNumber {
4
+ prefix: string | null;
5
+ suffix: string;
6
+ format: string | undefined;
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
+ format: undefined,
24
+ };
25
+ };
@@ -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,4 @@
1
+ import { Country } from '../../data/countries';
2
+
3
+ export const longestMatchingPrefix = (matchingCodes: Country[]) =>
4
+ matchingCodes.reduce((a, b) => (a.phone.length > b.phone.length ? a : b));
@@ -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.
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
+ };
@@ -0,0 +1,6 @@
1
+ export function sortArrayByProperty<T extends Record<PropertyKey, string>>(
2
+ arrayToSort: T[],
3
+ property: keyof T,
4
+ ) {
5
+ return [...arrayToSort].sort((a, b) => a[property].localeCompare(b[property]));
6
+ }
@@ -0,0 +1,14 @@
1
+ import PropTypes from 'prop-types';
2
+
3
+ import WithDisplayFormat from '../withDisplayFormat';
4
+
5
+ const TextareaWithDisplayFormat = (props) => (
6
+ <WithDisplayFormat {...props} render={(renderProps) => <textarea {...renderProps} />} />
7
+ );
8
+
9
+ TextareaWithDisplayFormat.propTypes = {
10
+ displayPattern: PropTypes.string.isRequired,
11
+ onChange: PropTypes.func.isRequired,
12
+ };
13
+
14
+ export default TextareaWithDisplayFormat;
@@ -1,7 +1,5 @@
1
1
  import { shallow } from 'enzyme';
2
2
 
3
- import { TextArea } from '../inputs/TextArea';
4
-
5
3
  import TextareaWithDisplayFormat from '.';
6
4
 
7
5
  describe('TextareaWithDisplayFormat', () => {
@@ -12,6 +10,6 @@ describe('TextareaWithDisplayFormat', () => {
12
10
  .find('WithDisplayFormat')
13
11
  .renderProp('render')({ value: 'test' });
14
12
 
15
- expect(view.find(TextArea).props('value')).toStrictEqual({ value: 'test' });
13
+ expect(view.find('textarea').props('value')).toStrictEqual({ value: 'test' });
16
14
  });
17
15
  });
@@ -0,0 +1 @@
1
+ export { default } from './TextareaWithDisplayFormat';