@transferwise/components 46.6.0 → 46.7.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 +186 -342
- package/build/index.esm.js.map +1 -1
- package/build/index.js +185 -341
- package/build/index.js.map +1 -1
- package/build/main.css +6 -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 +6 -17
- 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 +1 -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/package.json +3 -4
- package/src/common/locale/{index.spec.js → index.spec.ts} +4 -4
- package/src/common/locale/index.ts +96 -0
- package/src/index.ts +1 -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 +6 -17
- 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/select/Select.css +0 -4
- 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
|
@@ -12,7 +12,6 @@ import { useScreenSize } from '../common/hooks/useScreenSize';
|
|
|
12
12
|
import { PolymorphicWithOverrides } from '../common/polymorphicWithOverrides/PolymorphicWithOverrides';
|
|
13
13
|
import { Breakpoint } from '../common/propsValues/breakpoint';
|
|
14
14
|
import dateTriggerMessages from '../dateLookup/dateTrigger/DateTrigger.messages';
|
|
15
|
-
import { wrapInFragment } from '../utilities/wrapInFragment';
|
|
16
15
|
import { Merge } from '../utils';
|
|
17
16
|
|
|
18
17
|
import { InputGroup } from './InputGroup';
|
|
@@ -125,13 +124,13 @@ function filterSelectInputItems<T>(items: readonly SelectInputItem<T>[], needle:
|
|
|
125
124
|
});
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
export interface SelectInputProps<T = string> {
|
|
127
|
+
export interface SelectInputProps<T = string, M extends boolean = false> {
|
|
129
128
|
name?: string;
|
|
129
|
+
multiple?: M;
|
|
130
130
|
placeholder?: string;
|
|
131
|
-
// TODO: multiple?: boolean;
|
|
132
131
|
items: readonly SelectInputItem<NonNullable<T>>[];
|
|
133
|
-
defaultValue?: T;
|
|
134
|
-
value?: T;
|
|
132
|
+
defaultValue?: M extends true ? readonly T[] : T;
|
|
133
|
+
value?: M extends true ? readonly T[] : T;
|
|
135
134
|
compareValues?:
|
|
136
135
|
| (keyof NonNullable<T> & string)
|
|
137
136
|
| ((a: T | undefined, b: T | undefined) => boolean);
|
|
@@ -154,7 +153,7 @@ export interface SelectInputProps<T = string> {
|
|
|
154
153
|
size?: 'sm' | 'md' | 'lg';
|
|
155
154
|
className?: string;
|
|
156
155
|
onFilterChange?: (args: { query: string; queryNormalized: string | null }) => void;
|
|
157
|
-
onChange?: (value: T) => void;
|
|
156
|
+
onChange?: (value: M extends true ? T[] : T) => void;
|
|
158
157
|
onClear?: () => void;
|
|
159
158
|
}
|
|
160
159
|
|
|
@@ -186,7 +185,14 @@ const defaultRenderTrigger = (({ content, placeholderShown, clear, disabled, siz
|
|
|
186
185
|
className={className}
|
|
187
186
|
>
|
|
188
187
|
<SelectInputTriggerButton as={ButtonInput} size={size}>
|
|
189
|
-
|
|
188
|
+
<span
|
|
189
|
+
className={classNames(
|
|
190
|
+
'np-select-input-content',
|
|
191
|
+
placeholderShown && 'np-select-input-placeholder',
|
|
192
|
+
)}
|
|
193
|
+
>
|
|
194
|
+
{content}
|
|
195
|
+
</span>
|
|
190
196
|
</SelectInputTriggerButton>
|
|
191
197
|
</InputGroup>
|
|
192
198
|
)) satisfies SelectInputProps['renderTrigger'];
|
|
@@ -211,14 +217,15 @@ function SelectInputClearButton({ className, onClick }: SelectInputClearButtonPr
|
|
|
211
217
|
|
|
212
218
|
const noop = () => {};
|
|
213
219
|
|
|
214
|
-
export function SelectInput<T = string>({
|
|
220
|
+
export function SelectInput<T = string, M extends boolean = false>({
|
|
215
221
|
name,
|
|
222
|
+
multiple,
|
|
216
223
|
placeholder,
|
|
217
224
|
items,
|
|
218
225
|
defaultValue,
|
|
219
226
|
value: controlledValue,
|
|
220
227
|
compareValues,
|
|
221
|
-
renderValue =
|
|
228
|
+
renderValue = String,
|
|
222
229
|
renderFooter,
|
|
223
230
|
renderTrigger = defaultRenderTrigger,
|
|
224
231
|
filterable,
|
|
@@ -229,7 +236,7 @@ export function SelectInput<T = string>({
|
|
|
229
236
|
onFilterChange = noop,
|
|
230
237
|
onChange,
|
|
231
238
|
onClear,
|
|
232
|
-
}: SelectInputProps<T>) {
|
|
239
|
+
}: SelectInputProps<T, M>) {
|
|
233
240
|
const [open, setOpen] = useState(false);
|
|
234
241
|
|
|
235
242
|
const [filterQuery, _setFilterQuery] = useState('');
|
|
@@ -253,94 +260,106 @@ export function SelectInput<T = string>({
|
|
|
253
260
|
return (
|
|
254
261
|
<ListboxBase
|
|
255
262
|
name={name}
|
|
263
|
+
multiple={multiple}
|
|
256
264
|
defaultValue={defaultValue}
|
|
257
265
|
value={controlledValue}
|
|
258
266
|
// TODO: Remove assertion when upgrading TypeScript to v5
|
|
259
267
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
260
268
|
by={compareValues as any}
|
|
261
269
|
disabled={disabled}
|
|
262
|
-
onChange={
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
270
|
+
onChange={
|
|
271
|
+
((value) => {
|
|
272
|
+
if (!multiple) {
|
|
273
|
+
setOpen(false);
|
|
274
|
+
}
|
|
275
|
+
onChange?.(value);
|
|
276
|
+
}) satisfies SelectInputProps<T, M>['onChange']
|
|
277
|
+
}
|
|
266
278
|
>
|
|
267
|
-
{({ disabled: uiDisabled, value }) =>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (
|
|
283
|
-
event.key === ' ' ||
|
|
284
|
-
event.key === 'Enter' ||
|
|
285
|
-
event.key === 'ArrowDown' ||
|
|
286
|
-
event.key === 'ArrowUp'
|
|
287
|
-
) {
|
|
279
|
+
{({ disabled: uiDisabled, value }) => {
|
|
280
|
+
const placeholderShown =
|
|
281
|
+
multiple && Array.isArray(value) ? value.length === 0 : value == null;
|
|
282
|
+
return (
|
|
283
|
+
<OptionsOverlay
|
|
284
|
+
placement="bottom-start"
|
|
285
|
+
open={open}
|
|
286
|
+
renderTrigger={({ ref, getInteractionProps }) => (
|
|
287
|
+
<SelectInputTriggerButtonPropsContext.Provider
|
|
288
|
+
// eslint-disable-next-line react/jsx-no-constructed-context-values
|
|
289
|
+
value={{
|
|
290
|
+
ref: mergeRefs([ref, triggerRef]),
|
|
291
|
+
...mergeProps(
|
|
292
|
+
{
|
|
293
|
+
onClick: () => {
|
|
288
294
|
setOpen((prev) => !prev);
|
|
289
|
-
}
|
|
295
|
+
},
|
|
296
|
+
onKeyDown: (event: React.KeyboardEvent) => {
|
|
297
|
+
if (
|
|
298
|
+
event.key === ' ' ||
|
|
299
|
+
event.key === 'Enter' ||
|
|
300
|
+
event.key === 'ArrowDown' ||
|
|
301
|
+
event.key === 'ArrowUp'
|
|
302
|
+
) {
|
|
303
|
+
setOpen((prev) => !prev);
|
|
304
|
+
}
|
|
305
|
+
},
|
|
290
306
|
},
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
content:
|
|
298
|
-
value != null ? (
|
|
307
|
+
getInteractionProps(),
|
|
308
|
+
),
|
|
309
|
+
}}
|
|
310
|
+
>
|
|
311
|
+
{renderTrigger({
|
|
312
|
+
content: !placeholderShown ? (
|
|
299
313
|
<SelectInputOptionContentWithinTriggerContext.Provider value>
|
|
300
|
-
{
|
|
314
|
+
{multiple && Array.isArray(value)
|
|
315
|
+
? value
|
|
316
|
+
.map((option: NonNullable<T>) => renderValue(option, true))
|
|
317
|
+
.join(', ')
|
|
318
|
+
: renderValue(value as NonNullable<T>, true)}
|
|
301
319
|
</SelectInputOptionContentWithinTriggerContext.Provider>
|
|
302
320
|
) : (
|
|
303
321
|
placeholder
|
|
304
322
|
),
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
323
|
+
placeholderShown,
|
|
324
|
+
clear:
|
|
325
|
+
onClear != null
|
|
326
|
+
? () => {
|
|
327
|
+
onClear();
|
|
328
|
+
triggerRef.current?.focus({ preventScroll: true });
|
|
329
|
+
}
|
|
330
|
+
: undefined,
|
|
331
|
+
disabled: uiDisabled,
|
|
332
|
+
size,
|
|
333
|
+
className,
|
|
334
|
+
})}
|
|
335
|
+
</SelectInputTriggerButtonPropsContext.Provider>
|
|
336
|
+
)}
|
|
337
|
+
initialFocusRef={controllerRef}
|
|
338
|
+
size={filterable ? 'lg' : 'md'}
|
|
339
|
+
padding="none"
|
|
340
|
+
onClose={() => {
|
|
341
|
+
setOpen(false);
|
|
342
|
+
}}
|
|
343
|
+
onCloseEnd={() => {
|
|
344
|
+
if (filterQuery !== '') {
|
|
345
|
+
setFilterQuery('');
|
|
346
|
+
}
|
|
347
|
+
}}
|
|
348
|
+
>
|
|
349
|
+
<SelectInputOptions
|
|
350
|
+
items={items}
|
|
351
|
+
renderValue={renderValue}
|
|
352
|
+
renderFooter={renderFooter}
|
|
353
|
+
filterable={filterable}
|
|
354
|
+
filterPlaceholder={filterPlaceholder}
|
|
355
|
+
searchInputRef={searchInputRef}
|
|
356
|
+
listboxRef={listboxRef}
|
|
357
|
+
filterQuery={filterQuery}
|
|
358
|
+
onFilterChange={setFilterQuery}
|
|
359
|
+
/>
|
|
360
|
+
</OptionsOverlay>
|
|
361
|
+
);
|
|
362
|
+
}}
|
|
344
363
|
</ListboxBase>
|
|
345
364
|
);
|
|
346
365
|
}
|
|
@@ -430,7 +449,7 @@ interface SelectInputOptionsProps<T = string>
|
|
|
430
449
|
|
|
431
450
|
function SelectInputOptions<T = string>({
|
|
432
451
|
items,
|
|
433
|
-
renderValue =
|
|
452
|
+
renderValue = String,
|
|
434
453
|
renderFooter,
|
|
435
454
|
filterable = false,
|
|
436
455
|
filterPlaceholder,
|
package/src/inputs/TextArea.css
CHANGED
|
@@ -47,10 +47,6 @@
|
|
|
47
47
|
padding-top: 0 !important;
|
|
48
48
|
padding-bottom: 0 !important;
|
|
49
49
|
}.np-form-control--size-sm {
|
|
50
|
-
line-height: 1.5;
|
|
51
|
-
line-height: var(--line-height-body);
|
|
52
|
-
font-size: 1rem;
|
|
53
|
-
font-size: var(--font-size-16);
|
|
54
50
|
font-size: 0.875rem;
|
|
55
51
|
font-size: var(--font-size-14);
|
|
56
52
|
line-height: 155%;
|
package/src/main.css
CHANGED
|
@@ -74,14 +74,6 @@ div.critical-comms .critical-comms-body {
|
|
|
74
74
|
flex-wrap: wrap;
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
.tw-date-lookup-calendar > tbody > tr > td.weekend button {
|
|
78
|
-
font-weight: 400;
|
|
79
|
-
font-weight: var(--font-weight-regular);line-height: 1.5;line-height: var(--line-height-body);
|
|
80
|
-
}
|
|
81
|
-
.tw-date-lookup-calendar > tbody > tr > td.weekend button {
|
|
82
|
-
font-size: 1rem;
|
|
83
|
-
font-size: var(--font-size-16);
|
|
84
|
-
}
|
|
85
77
|
.tw-date-lookup-calendar > tbody > tr > td.weekend button {
|
|
86
78
|
font-size: 0.875rem;
|
|
87
79
|
font-size: var(--font-size-14);line-height: 155%;letter-spacing: -0.006em;font-weight: 400;font-weight: var(--font-weight-regular);
|
|
@@ -2226,10 +2218,6 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2226
2218
|
padding-bottom: 0 !important;
|
|
2227
2219
|
}
|
|
2228
2220
|
.np-form-control--size-sm {
|
|
2229
|
-
line-height: 1.5;
|
|
2230
|
-
line-height: var(--line-height-body);
|
|
2231
|
-
font-size: 1rem;
|
|
2232
|
-
font-size: var(--font-size-16);
|
|
2233
2221
|
font-size: 0.875rem;
|
|
2234
2222
|
font-size: var(--font-size-14);
|
|
2235
2223
|
line-height: 155%;
|
|
@@ -2511,10 +2499,12 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2511
2499
|
color: #5d7079;
|
|
2512
2500
|
color: var(--color-content-secondary);
|
|
2513
2501
|
}
|
|
2514
|
-
.np-select-input-
|
|
2502
|
+
.np-select-input-content {
|
|
2515
2503
|
overflow: hidden;
|
|
2516
2504
|
text-overflow: ellipsis;
|
|
2517
2505
|
white-space: nowrap;
|
|
2506
|
+
}
|
|
2507
|
+
.np-select-input-placeholder {
|
|
2518
2508
|
color: #768e9c;
|
|
2519
2509
|
color: var(--color-content-tertiary);
|
|
2520
2510
|
}
|
|
@@ -2622,6 +2612,9 @@ html:not([dir="rtl"]) .np-flow-navigation--sm .np-flow-navigation__stepper {
|
|
|
2622
2612
|
padding: var(--size-12) var(--size-16);
|
|
2623
2613
|
color: var(--color-interactive-primary);
|
|
2624
2614
|
}
|
|
2615
|
+
.np-select-input-option-container:focus {
|
|
2616
|
+
outline: none;
|
|
2617
|
+
}
|
|
2625
2618
|
.np-select-input-option-container--active {
|
|
2626
2619
|
box-shadow: inset 0 0 0 1px #c9cbce;
|
|
2627
2620
|
box-shadow: inset 0 0 0 1px var(--color-interactive-secondary);
|
|
@@ -4455,10 +4448,6 @@ html:not([dir="rtl"]) .np-navigation-option {
|
|
|
4455
4448
|
border-radius: var(--radius-small);
|
|
4456
4449
|
color: #37517e;
|
|
4457
4450
|
color: var(--color-content-primary);
|
|
4458
|
-
line-height: 1.5;
|
|
4459
|
-
line-height: var(--line-height-body);
|
|
4460
|
-
font-size: 1rem;
|
|
4461
|
-
font-size: var(--font-size-16);
|
|
4462
4451
|
font-size: 0.875rem;
|
|
4463
4452
|
font-size: var(--font-size-14);
|
|
4464
4453
|
line-height: 155%;
|
|
@@ -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;
|