From 78616ac5dacee817ddebeea9465e840af33e46d2 Mon Sep 17 00:00:00 2001 From: Pedro Ladaria Date: Tue, 22 Oct 2024 12:49:06 +0200 Subject: [PATCH 1/5] WEB-2081 complete formatting and tests --- .../phone-number-field-lite-story.tsx | 163 +++++++++++++++ src/__stories__/phone-number-field-story.tsx | 2 +- .../phone-number-field-lite-test.tsx | 85 ++++++++ src/index.tsx | 1 + src/phone-number-field-lite.tsx | 186 ++++++++++++++++++ 5 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 src/__stories__/phone-number-field-lite-story.tsx create mode 100644 src/__tests__/phone-number-field-lite-test.tsx create mode 100644 src/phone-number-field-lite.tsx diff --git a/src/__stories__/phone-number-field-lite-story.tsx b/src/__stories__/phone-number-field-lite-story.tsx new file mode 100644 index 000000000..2a3ab2c43 --- /dev/null +++ b/src/__stories__/phone-number-field-lite-story.tsx @@ -0,0 +1,163 @@ +import * as React from 'react'; +import {Box, Text1, Stack, ResponsiveLayout, PhoneNumberFieldLite, Boxed} from '..'; +import {inspect} from 'util'; +import {phoneNumbersList} from './helpers'; + +export default { + title: 'Components/Input fields/PhoneNumberFieldLite', + parameters: {fullScreen: true}, +}; + +const getPhoneNumberSuggestions = (value: string) => + phoneNumbersList + .filter((s) => String(s).toLocaleLowerCase().startsWith(value.toLocaleLowerCase())) + .slice(0, 5); + +interface PhoneNumberFieldBaseArgs { + label: string; + placeholder: string; + prefix: string; + helperText: string; + error: boolean; + inverse: boolean; + optional: boolean; + disabled: boolean; + readOnly: boolean; + preventCopy: boolean; +} + +const defaultBaseArgs: PhoneNumberFieldBaseArgs = { + label: 'Label', + placeholder: '', + prefix: '', + helperText: '', + error: false, + inverse: false, + optional: false, + disabled: false, + readOnly: false, + preventCopy: false, +}; + +interface PhoneNumberFieldControlledArgs extends PhoneNumberFieldBaseArgs { + initialValue: string; + suggestions: boolean; +} + +export const Controlled: StoryComponent = ({ + inverse, + initialValue, + suggestions, + ...rest +}) => { + const [rawValue, setRawValue] = React.useState(initialValue); + const [value, setValue] = React.useState(undefined); + + return ( + + + + + + +
+ This is a "light" version of the PhoneNumberField component. It does not + use google's libphonenumber library to reduce bundle size. +
+ +
- Only supported countries are formatted
+
- Not all phone number types are formatted
+
- Numbers in E164 are returned unformatted
+
+ - A custom formatter can be provided via props and the formatter used + by this component is exported as `formatPhoneLite` +
+
+
+
+
+ { + setValue(value); + setRawValue(rawValue); + }} + name="phoneNumber" + autoComplete="off" + dataAttributes={{testid: 'phone-number-field'}} + getSuggestions={suggestions ? getPhoneNumberSuggestions : undefined} + {...rest} + /> + + + value: {typeof value === 'undefined' ? '' : `(${typeof value}) ${inspect(value)}`} + + + rawValue:{' '} + {typeof rawValue === 'undefined' + ? '' + : `(${typeof rawValue}) ${inspect(rawValue)}`} + + +
+
+
+ ); +}; + +Controlled.storyName = 'controlled'; +Controlled.args = { + initialValue: '654834455', + ...defaultBaseArgs, + suggestions: false, +}; + +interface PhoneNumberFieldUncontrolledArgs extends PhoneNumberFieldBaseArgs { + defaultValue: string; +} + +export const Uncontrolled: StoryComponent = ({ + inverse, + defaultValue, + ...rest +}) => { + const [rawValue, setRawValue] = React.useState(undefined); + const [value, setValue] = React.useState(undefined); + + return ( + + + + { + setValue(value); + setRawValue(rawValue); + }} + name="phoneNumber" + autoComplete="off" + dataAttributes={{testid: 'phone-number-field'}} + {...rest} + /> + + + value: {typeof value === 'undefined' ? '' : `(${typeof value}) ${inspect(value)}`} + + + rawValue:{' '} + {typeof rawValue === 'undefined' + ? '' + : `(${typeof rawValue}) ${inspect(rawValue)}`} + + + + + + ); +}; + +Uncontrolled.storyName = 'uncontrolled'; +Uncontrolled.args = { + defaultValue: '654834455', + ...defaultBaseArgs, +}; diff --git a/src/__stories__/phone-number-field-story.tsx b/src/__stories__/phone-number-field-story.tsx index d58a6ef2e..e0de8594a 100644 --- a/src/__stories__/phone-number-field-story.tsx +++ b/src/__stories__/phone-number-field-story.tsx @@ -56,7 +56,7 @@ export const Controlled: StoryComponent = ({ const [value, setValue] = React.useState(undefined); return ( - + { + const onChangeValue = jest.fn(); + const onChangeValueUsingLibphonenumber = jest.fn(); + + render( + + + + + ); + + const input = screen.getByLabelText('Phone'); + await userEvent.type(input, number); + + const referenceInput = screen.getByLabelText('Reference'); + await userEvent.type(referenceInput, number); + + expect(onChangeValue).toHaveBeenLastCalledWith(expected, expectedRaw); + + // We expect the same result as the libphonenumber version, except for the E164 format + if (!number.startsWith('+')) { + // This checks all the calls to onChangeValue (as you type) + expect(onChangeValue.mock.calls).toEqual(onChangeValueUsingLibphonenumber.mock.calls); + } +}); + +test('PhoneNumberFieldLite custom formatter', async () => { + const onChangeValue = jest.fn(); + + render( + + { + return number.replace(/\D/g, '').split('').join('-'); + }} + /> + + ); + + const input = screen.getByLabelText('Phone'); + await userEvent.type(input, '654834455'); + + expect(onChangeValue).toHaveBeenLastCalledWith('654834455', '6-5-4-8-3-4-4-5-5'); +}); diff --git a/src/index.tsx b/src/index.tsx index 3bf455af6..25bc20fb8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -136,6 +136,7 @@ export {TextFieldBase} from './text-field-base'; export {default as SearchField} from './search-field'; export {default as EmailField} from './email-field'; export {default as PhoneNumberField} from './phone-number-field'; +export {default as PhoneNumberFieldLite, formatPhoneLite} from './phone-number-field-lite'; export {default as CreditCardNumberField} from './credit-card-number-field'; export {default as CreditCardExpirationField} from './credit-card-expiration-field'; export {default as CreditCardFields} from './credit-card-fields'; diff --git a/src/phone-number-field-lite.tsx b/src/phone-number-field-lite.tsx new file mode 100644 index 000000000..198492ecd --- /dev/null +++ b/src/phone-number-field-lite.tsx @@ -0,0 +1,186 @@ +'use client'; +import * as React from 'react'; +import {useRifm} from 'rifm'; +import {useFieldProps} from './form-context'; +import {TextFieldBaseAutosuggest} from './text-field-base'; +import {useTheme} from './hooks'; +import {createChangeEvent} from './utils/dom'; +import {combineRefs} from './utils/common'; + +import type {CommonFormFieldProps} from './text-field-base'; +import type {RegionCode} from './utils/region-code'; + +/** + * Simple phone formatter for a few countries and a subset of phone numbers + * + * Formatting conditions have been adapted to exactly match libphonenumber's as you type formatting + * Not all formatting rules are implemented, only the most common ones. For a more complete solution, use PhoneNumberField + */ +export const formatPhoneLite = (regionCode: RegionCode, number: string): string => { + const digits = number.replace(/\D/g, ''); // strip non-digits + if (number.startsWith('+')) { + // E164 returned without formatting + return '+' + digits; + } + if (regionCode === 'ES') { + // https://en.wikipedia.org/wiki/Telephone_numbers_in_Spain + // Example mobile: 654 83 44 55 + // Example landline: 914 44 10 25 + if (digits.length <= 9) { + return `${digits.slice(0, 3)} ${digits.slice(3, 5)} ${digits.slice(5, 7)} ${digits.slice(7)}`.trim(); + } + return digits; + } else if (regionCode === 'BR') { + // https://en.wikipedia.org/wiki/Telephone_numbers_in_Brazil + // Example mobile: (xx) (6..9)xxxx-xxxx + // Example landline: (xx) xxxx-xxxx + if (digits.length === 11) { + return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7)}`.replace(/\D+$/, ''); + } else if (digits.length > 2 && digits.length <= 11 && digits[2] <= '5') { + return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6)}`.replace(/\D+$/, ''); + } + } else if (regionCode === 'DE') { + // https://en.wikipedia.org/wiki/Telephone_numbers_in_Germany + // Only formatting mobile numbers, landline numbers have a lot of variations: + // https://en.wikipedia.org/wiki/Telephone_numbers_in_Germany#/media/File:Karte_Telefonvorwahlen_Deutschland.png + if (digits.length >= 4 && digits.match(/^(015|016|017)/)) { + if (digits.length <= 12 && digits.startsWith('015')) { + return `${digits.slice(0, 5)} ${digits.slice(5)}`.trim(); + } else { + return `${digits.slice(0, 4)} ${digits.slice(4)}`.trim(); + } + } + } else if (regionCode === 'GB') { + // https://en.wikipedia.org/wiki/Telephone_numbers_in_the_United_Kingdom#Mobile_telephones + // Like in DE, only mobile numbers are formatted + // Example mobile: 07xxx xxxxxx + if (digits.length <= 11 && digits.startsWith('07')) { + return `${digits.slice(0, 5)} ${digits.slice(5)}`.trim(); + } + } + return digits; +}; + +type InputProps = Omit, 'value' | 'onInput'> & { + inputRef?: React.Ref; + value?: string; + defaultValue?: string; + onInput?: (event: React.FormEvent) => void; + prefix?: string; + format?: (number: string) => string; +}; + +const PhoneInput = ({ + inputRef, + value, + defaultValue, + onChange, + prefix, + format: formatFromProps, + ...other +}: InputProps) => { + const [selfValue, setSelfValue] = React.useState(defaultValue || ''); + const ref = React.useRef(null); + const {i18n} = useTheme(); + + const regionCode = i18n.phoneNumberFormattingRegionCode; + const isControlledByParent = typeof value !== 'undefined'; + const controlledValue = (isControlledByParent ? value : selfValue) as string; + + const handleChangeValue = React.useCallback( + (newFormattedValue: string) => { + if (!isControlledByParent) { + setSelfValue(newFormattedValue); + } + if (ref.current) { + onChange?.(createChangeEvent(ref.current, newFormattedValue)); + } + }, + [isControlledByParent, onChange] + ); + + const format = React.useCallback( + (number: string): string => { + if (formatFromProps) { + return formatFromProps(number); + } + return formatPhoneLite(regionCode, number); + }, + [formatFromProps, regionCode] + ); + + const rifm = useRifm({ + format, + value: controlledValue, + accept: /[\d\+]+/g, + onChange: handleChangeValue, + }); + + return ( + + ); +}; + +export interface PhoneNumberFieldProps extends CommonFormFieldProps { + onChangeValue?: (value: string, rawValue: string) => void; + prefix?: string; + getSuggestions?: (value: string) => Array; + format?: (number: string) => string; +} + +const PhoneNumberFieldLite = ({ + disabled, + error, + helperText, + name, + label, + optional, + validate, + onChange, + onChangeValue, + onBlur, + value, + defaultValue, + dataAttributes, + ...rest +}: PhoneNumberFieldProps): JSX.Element => { + const processValue = (value: string) => { + // keep only digits + return value.startsWith('+') ? value : value.replace(/\D/g, ''); + }; + + const fieldProps = useFieldProps({ + name, + label, + value, + defaultValue, + processValue, + helperText, + optional, + error, + disabled, + onBlur, + validate, + onChange, + onChangeValue, + }); + + return ( + + ); +}; + +export default PhoneNumberFieldLite; From 59f3ec30947f358fddff18befe4e1db69726fb4d Mon Sep 17 00:00:00 2001 From: Pedro Ladaria Date: Tue, 22 Oct 2024 12:59:21 +0200 Subject: [PATCH 2/5] WEB-2081 add screenshots --- ...ld-lite-2145678901-in-vivo-skin-1-snap.png | Bin 0 -> 8779 bytes ...d-lite-34654834455-in-vivo-skin-1-snap.png | Bin 0 -> 7331 bytes ...lite-654834455-in-movistar-skin-1-snap.png | Bin 0 -> 8013 bytes .../input-fields-screenshot-test.tsx | 22 ++++++++ .../phone-number-field-lite-story.tsx | 51 ++++++++++-------- src/__stories__/phone-number-field-story.tsx | 2 +- 6 files changed, 52 insertions(+), 23 deletions(-) create mode 100644 src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-2145678901-in-vivo-skin-1-snap.png create mode 100644 src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-34654834455-in-vivo-skin-1-snap.png create mode 100644 src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-654834455-in-movistar-skin-1-snap.png diff --git a/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-2145678901-in-vivo-skin-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-2145678901-in-vivo-skin-1-snap.png new file mode 100644 index 0000000000000000000000000000000000000000..af28fe5c4587b4f25f0bbaf10dde75fc96fc493e GIT binary patch literal 8779 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 G;r{{OuH-WS literal 0 HcmV?d00001 diff --git a/src/__screenshot_tests__/input-fields-screenshot-test.tsx b/src/__screenshot_tests__/input-fields-screenshot-test.tsx index 051e86988..065863be8 100644 --- a/src/__screenshot_tests__/input-fields-screenshot-test.tsx +++ b/src/__screenshot_tests__/input-fields-screenshot-test.tsx @@ -521,6 +521,28 @@ test.each` expect(await fieldWrapper.screenshot()).toMatchImageSnapshot(); }); +test.only.each` + skin | number + ${'Vivo'} | ${'2145678901'} + ${'Vivo'} | ${'+34654834455'} + ${'Movistar'} | ${'654834455'} +`('PhoneNumberFieldLite - $number in $skin skin', async ({skin, number}) => { + await openStoryPage({ + id: 'components-input-fields-phonenumberfieldlite--uncontrolled', + device: 'MOBILE_IOS', + skin, + args: {defaultValue: number}, + }); + + const fieldWrapper = await screen.findByTestId('phone-number-field-lite'); + const field = await screen.findByLabelText('Label'); + + await field.click({clickCount: 3}); + await field.type(number); + + expect(await fieldWrapper.screenshot()).toMatchImageSnapshot(); +}); + test('CreditCardExpirationField', async () => { await openStoryPage({ id: 'components-input-fields-creditcardexpirationfield--uncontrolled', diff --git a/src/__stories__/phone-number-field-lite-story.tsx b/src/__stories__/phone-number-field-lite-story.tsx index 2a3ab2c43..75717f828 100644 --- a/src/__stories__/phone-number-field-lite-story.tsx +++ b/src/__stories__/phone-number-field-lite-story.tsx @@ -44,6 +44,30 @@ interface PhoneNumberFieldControlledArgs extends PhoneNumberFieldBaseArgs { suggestions: boolean; } +const Description = () => { + return ( + + + +

+ This is a "light" version of the PhoneNumberField component. It does not use google's + libphonenumber library to reduce bundle size. +
+ +
- Only supported countries are formatted
+
- Not all phone number types are formatted
+
- Numbers in E164 are returned unformatted
+
+ - A custom formatter can be provided via props and the formatter used by this + component is exported as `formatPhoneLite` +
+
+
+
+ + ); +}; + export const Controlled: StoryComponent = ({ inverse, initialValue, @@ -57,25 +81,7 @@ export const Controlled: StoryComponent = ({ - - - -
- This is a "light" version of the PhoneNumberField component. It does not - use google's libphonenumber library to reduce bundle size. -
- -
- Only supported countries are formatted
-
- Not all phone number types are formatted
-
- Numbers in E164 are returned unformatted
-
- - A custom formatter can be provided via props and the formatter used - by this component is exported as `formatPhoneLite` -
-
-
-
-
+ { @@ -84,7 +90,7 @@ export const Controlled: StoryComponent = ({ }} name="phoneNumber" autoComplete="off" - dataAttributes={{testid: 'phone-number-field'}} + dataAttributes={{testid: 'phone-number-field-lite'}} getSuggestions={suggestions ? getPhoneNumberSuggestions : undefined} {...rest} /> @@ -125,9 +131,10 @@ export const Uncontrolled: StoryComponent = ({ const [value, setValue] = React.useState(undefined); return ( - + + { @@ -136,7 +143,7 @@ export const Uncontrolled: StoryComponent = ({ }} name="phoneNumber" autoComplete="off" - dataAttributes={{testid: 'phone-number-field'}} + dataAttributes={{testid: 'phone-number-field-lite'}} {...rest} /> diff --git a/src/__stories__/phone-number-field-story.tsx b/src/__stories__/phone-number-field-story.tsx index e0de8594a..01570e403 100644 --- a/src/__stories__/phone-number-field-story.tsx +++ b/src/__stories__/phone-number-field-story.tsx @@ -108,7 +108,7 @@ export const Uncontrolled: StoryComponent = ({ const [value, setValue] = React.useState(undefined); return ( - + Date: Mon, 28 Oct 2024 18:48:14 +0100 Subject: [PATCH 3/5] WEB-2081 e164 support --- ...d-lite-34654834455-in-vivo-skin-1-snap.png | Bin 7331 -> 7737 bytes .../phone-number-field-lite-test.tsx | 104 +++++++++------- src/phone-number-field-lite.tsx | 113 ++++++++++++++---- src/phone-number-field.tsx | 3 +- 4 files changed, 152 insertions(+), 68 deletions(-) diff --git a/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-34654834455-in-vivo-skin-1-snap.png b/src/__screenshot_tests__/__image_snapshots__/input-fields-screenshot-test-tsx-phone-number-field-lite-34654834455-in-vivo-skin-1-snap.png index fc0513bc67fef094e5f21638c6e8bc3fd2bb7c29..d15bb0ab773bc8295fef5c4714df48af6f66f5e1 100644 GIT binary patch literal 7737 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 literal 7331 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 { - const onChangeValue = jest.fn(); - const onChangeValueUsingLibphonenumber = jest.fn(); + regionCode | number | expected | expectedRaw | expectedE164 | description + ${'ZZ'} | ${'123456789012345'} | ${'123456789012345'} | ${'123456789012345'} | ${'123456789012345'} | ${'Unknown region'} + ${'ES'} | ${'654834455'} | ${'654834455'} | ${'654 83 44 55'} | ${'+34654834455'} | ${'ES mobile'} + ${'ES'} | ${'914-44/10 25'} | ${'914441025'} | ${'914 44 10 25'} | ${'+34914441025'} | ${'ES landline'} + ${'ES'} | ${'6548344556'} | ${'6548344556'} | ${'6548344556'} | ${'+346548344556'} | ${'ES mobile too long'} + ${'ES'} | ${'914-44/10 256'} | ${'9144410256'} | ${'9144410256'} | ${'+349144410256'} | ${'ES landline too long'} + ${'ES'} | ${'+34 654 834 455'} | ${'+34654834455'} | ${'+34 654 83 44 55'} | ${'+34654834455'} | ${'ES E164 mobile'} + ${'ES'} | ${'+34 914-44/10 25'} | ${'+34914441025'} | ${'+34 914 44 10 25'} | ${'+34914441025'} | ${'ES E164 landline'} + ${'BR'} | ${'21987654321'} | ${'21987654321'} | ${'(21) 98765-4321'} | ${'+5521987654321'} | ${'BR mobile'} + ${'BR'} | ${'219876543210'} | ${'219876543210'} | ${'219876543210'} | ${'+55219876543210'} | ${'BR mobile too long'} + ${'BR'} | ${'2123456789'} | ${'2123456789'} | ${'(21) 2345-6789'} | ${'+552123456789'} | ${'BR landline'} + ${'BR'} | ${'21234567890'} | ${'21234567890'} | ${'(21) 23456-7890'} | ${'+5521234567890'} | ${'BR landline long'} + ${'BR'} | ${'212345678901'} | ${'212345678901'} | ${'212345678901'} | ${'+55212345678901'} | ${'BR landline too long'} + ${'BR'} | ${'+5521987654321'} | ${'+5521987654321'} | ${'+55 21 98765-4321'} | ${'+5521987654321'} | ${'BR E164 mobile'} + ${'BR'} | ${'+34654834455'} | ${'+34654834455'} | ${'+34 654 83 44 55'} | ${'+34654834455'} | ${'BR with ES E164'} + ${'DE'} | ${'015789012345'} | ${'015789012345'} | ${'01578 9012345'} | ${'+4915789012345'} | ${'DE mobile 15'} + ${'DE'} | ${'01601234567'} | ${'01601234567'} | ${'0160 1234567'} | ${'+491601234567'} | ${'DE mobile 16'} + ${'DE'} | ${'01701234567'} | ${'01701234567'} | ${'0170 1234567'} | ${'+491701234567'} | ${'DE mobile 17'} + ${'DE'} | ${'12345678901'} | ${'12345678901'} | ${'12345678901'} | ${'+4912345678901'} | ${'DE unknown'} + ${'DE'} | ${'+4915789012345'} | ${'+4915789012345'} | ${'+49 1578 9012345'} | ${'+4915789012345'} | ${'DE E164 mobile'} + ${'DE'} | ${'+49015789012345'} | ${'+49015789012345'} | ${'+49 015789012345'} | ${'+49015789012345'} | ${'DE E164 mobile wrong zero'} + ${'GB'} | ${'07123456789'} | ${'07123456789'} | ${'07123 456789'} | ${'+447123456789'} | ${'GB mobile'} + ${'GB'} | ${'071234567890'} | ${'071234567890'} | ${'071234567890'} | ${'+4471234567890'} | ${'GB mobile too long'} + ${'GB'} | ${'+447123456789'} | ${'+447123456789'} | ${'+44 7123 456789'} | ${'+447123456789'} | ${'GB E164 mobile'} + ${'GB'} | ${'+4407123456789'} | ${'+4407123456789'} | ${'+44 07123456789'} | ${'+4407123456789'} | ${'GB E164 mobile wrong zero'} +`( + 'PhoneNumberFieldLite - $description - $number', + async ({regionCode, number, expected, expectedRaw, expectedE164}) => { + const onChangeValue = jest.fn(); + const onChangeValueE164 = jest.fn(); + const onChangeValueUsingLibphonenumber = jest.fn(); - render( - - - - - ); + render( + + + + + + ); - const input = screen.getByLabelText('Phone'); - await userEvent.type(input, number); + const input = screen.getByLabelText('Phone'); + const inputE164 = screen.getByLabelText('Phone E164'); + const referenceInput = screen.getByLabelText('Reference'); - const referenceInput = screen.getByLabelText('Reference'); - await userEvent.type(referenceInput, number); + await userEvent.type(input, number); + await userEvent.type(inputE164, number); + await userEvent.type(referenceInput, number); - expect(onChangeValue).toHaveBeenLastCalledWith(expected, expectedRaw); + expect(onChangeValue).toHaveBeenLastCalledWith(expected, expectedRaw); + expect(onChangeValueE164).toHaveBeenLastCalledWith(expectedE164, expectedRaw); - // We expect the same result as the libphonenumber version, except for the E164 format - if (!number.startsWith('+')) { - // This checks all the calls to onChangeValue (as you type) - expect(onChangeValue.mock.calls).toEqual(onChangeValueUsingLibphonenumber.mock.calls); + // We expect the same result as the libphonenumber version, except for the E164 format + if (!number.startsWith('+')) { + // This checks all the calls to onChangeValue (as you type) + expect(onChangeValue.mock.calls).toEqual(onChangeValueUsingLibphonenumber.mock.calls); + } } -}); +); test('PhoneNumberFieldLite custom formatter', async () => { const onChangeValue = jest.fn(); diff --git a/src/phone-number-field-lite.tsx b/src/phone-number-field-lite.tsx index 198492ecd..1e9fb0d02 100644 --- a/src/phone-number-field-lite.tsx +++ b/src/phone-number-field-lite.tsx @@ -10,6 +10,40 @@ import {combineRefs} from './utils/common'; import type {CommonFormFieldProps} from './text-field-base'; import type {RegionCode} from './utils/region-code'; +const COUNTRY_CODE_TO_REGION_CODE: Record = { + '+34': 'ES', + '+55': 'BR', + '+49': 'DE', + '+44': 'GB', +}; + +const REGION_CODE_TO_COUNTRY_CODE: Record = Object.fromEntries( + Object.entries(COUNTRY_CODE_TO_REGION_CODE).map(([k, v]) => [v, k]) +); + +const clean = (number: string): string => { + return number.trim().replace(/[^\d\+]/g, ''); // keep digits and "+" +}; + +const asE164 = (number: string, regionCode: RegionCode): string => { + if (number.startsWith('+')) { + return number; + } + + switch (regionCode) { + case 'ES': + return `${REGION_CODE_TO_COUNTRY_CODE[regionCode]} ${number}`; + case 'BR': + return `${REGION_CODE_TO_COUNTRY_CODE[regionCode]} ${number.replace(/[\(\)]/g, '')}`; + case 'DE': + return `${REGION_CODE_TO_COUNTRY_CODE[regionCode]} ${number.replace(/^0/, '')}`; + case 'GB': + return `${REGION_CODE_TO_COUNTRY_CODE[regionCode]} ${number.replace(/^0/, '')}`; + default: + return number; + } +}; + /** * Simple phone formatter for a few countries and a subset of phone numbers * @@ -17,48 +51,73 @@ import type {RegionCode} from './utils/region-code'; * Not all formatting rules are implemented, only the most common ones. For a more complete solution, use PhoneNumberField */ export const formatPhoneLite = (regionCode: RegionCode, number: string): string => { - const digits = number.replace(/\D/g, ''); // strip non-digits - if (number.startsWith('+')) { - // E164 returned without formatting - return '+' + digits; + const cleanNumber = clean(number); + const isE164 = cleanNumber.startsWith('+'); + let digits = cleanNumber.replace(/\D/g, ''); // keep digits only + let countryCode = ''; + let formattingRegionCode = regionCode; + + if (isE164) { + // check if the number matches a known country code + countryCode = + Object.keys(COUNTRY_CODE_TO_REGION_CODE).find((code) => cleanNumber.startsWith(code)) || ''; + + if (countryCode) { + digits = cleanNumber.slice(countryCode.length); // remove country code + formattingRegionCode = COUNTRY_CODE_TO_REGION_CODE[countryCode]; // override region code, the country code has precedence + } else { + // unknown E164 is returned without formatting + return '+' + digits; + } } - if (regionCode === 'ES') { + + if (formattingRegionCode === 'ES') { // https://en.wikipedia.org/wiki/Telephone_numbers_in_Spain // Example mobile: 654 83 44 55 // Example landline: 914 44 10 25 if (digits.length <= 9) { - return `${digits.slice(0, 3)} ${digits.slice(3, 5)} ${digits.slice(5, 7)} ${digits.slice(7)}`.trim(); + return `${countryCode} ${digits.slice(0, 3)} ${digits.slice(3, 5)} ${digits.slice(5, 7)} ${digits.slice(7)}`.trim(); } - return digits; - } else if (regionCode === 'BR') { + } else if (formattingRegionCode === 'BR') { // https://en.wikipedia.org/wiki/Telephone_numbers_in_Brazil // Example mobile: (xx) (6..9)xxxx-xxxx // Example landline: (xx) xxxx-xxxx + let national: string | undefined; if (digits.length === 11) { - return `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7)}`.replace(/\D+$/, ''); + national = `(${digits.slice(0, 2)}) ${digits.slice(2, 7)}-${digits.slice(7)}`.replace(/\D+$/, ''); } else if (digits.length > 2 && digits.length <= 11 && digits[2] <= '5') { - return `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6)}`.replace(/\D+$/, ''); + national = `(${digits.slice(0, 2)}) ${digits.slice(2, 6)}-${digits.slice(6)}`.replace(/\D+$/, ''); } - } else if (regionCode === 'DE') { + if (national) { + return isE164 ? asE164(national, formattingRegionCode) : national; + } + } else if (formattingRegionCode === 'DE') { // https://en.wikipedia.org/wiki/Telephone_numbers_in_Germany // Only formatting mobile numbers, landline numbers have a lot of variations: // https://en.wikipedia.org/wiki/Telephone_numbers_in_Germany#/media/File:Karte_Telefonvorwahlen_Deutschland.png - if (digits.length >= 4 && digits.match(/^(015|016|017)/)) { - if (digits.length <= 12 && digits.startsWith('015')) { - return `${digits.slice(0, 5)} ${digits.slice(5)}`.trim(); + // Example mobile: 0157 89012345 + // Example E164: +49 1578 9012345 + const zeroPadded = isE164 ? '0' + digits : digits; + if (zeroPadded.length >= 4 && zeroPadded.match(/^(015|016|017)/)) { + let national: string | undefined; + if (zeroPadded.length <= 12 && zeroPadded.startsWith('015')) { + national = `${zeroPadded.slice(0, 5)} ${zeroPadded.slice(5)}`.trim(); } else { - return `${digits.slice(0, 4)} ${digits.slice(4)}`.trim(); + national = `${countryCode} ${zeroPadded.slice(0, 4)} ${zeroPadded.slice(4)}`.trim(); } + return isE164 ? asE164(national, formattingRegionCode) : national; } - } else if (regionCode === 'GB') { + } else if (formattingRegionCode === 'GB') { // https://en.wikipedia.org/wiki/Telephone_numbers_in_the_United_Kingdom#Mobile_telephones // Like in DE, only mobile numbers are formatted // Example mobile: 07xxx xxxxxx - if (digits.length <= 11 && digits.startsWith('07')) { - return `${digits.slice(0, 5)} ${digits.slice(5)}`.trim(); + const zeroPadded = isE164 ? '0' + digits : digits; + if (zeroPadded.length <= 11 && zeroPadded.startsWith('07')) { + const national = `${zeroPadded.slice(0, 5)} ${zeroPadded.slice(5)}`.trim(); + return isE164 ? asE164(national, formattingRegionCode) : national; } } - return digits; + return isE164 ? `${countryCode} ${digits}` : digits; }; type InputProps = Omit, 'value' | 'onInput'> & { @@ -68,6 +127,7 @@ type InputProps = Omit, 'value' | 'o onInput?: (event: React.FormEvent) => void; prefix?: string; format?: (number: string) => string; + e164?: boolean; }; const PhoneInput = ({ @@ -77,13 +137,15 @@ const PhoneInput = ({ onChange, prefix, format: formatFromProps, + e164, ...other }: InputProps) => { const [selfValue, setSelfValue] = React.useState(defaultValue || ''); const ref = React.useRef(null); - const {i18n} = useTheme(); + const { + i18n: {phoneNumberFormattingRegionCode: regionCode}, + } = useTheme(); - const regionCode = i18n.phoneNumberFormattingRegionCode; const isControlledByParent = typeof value !== 'undefined'; const controlledValue = (isControlledByParent ? value : selfValue) as string; @@ -132,6 +194,7 @@ export interface PhoneNumberFieldProps extends CommonFormFieldProps { prefix?: string; getSuggestions?: (value: string) => Array; format?: (number: string) => string; + e164?: boolean; } const PhoneNumberFieldLite = ({ @@ -148,11 +211,15 @@ const PhoneNumberFieldLite = ({ value, defaultValue, dataAttributes, + e164, ...rest }: PhoneNumberFieldProps): JSX.Element => { + const { + i18n: {phoneNumberFormattingRegionCode}, + } = useTheme(); + const processValue = (value: string) => { - // keep only digits - return value.startsWith('+') ? value : value.replace(/\D/g, ''); + return e164 ? clean(asE164(value, phoneNumberFormattingRegionCode)) : clean(value); }; const fieldProps = useFieldProps({ diff --git a/src/phone-number-field.tsx b/src/phone-number-field.tsx index 032a5b965..4082c5821 100644 --- a/src/phone-number-field.tsx +++ b/src/phone-number-field.tsx @@ -20,12 +20,11 @@ type InputProps = Omit, 'value' | 'o defaultValue?: string; onInput?: (event: React.FormEvent) => void; prefix?: string; - e164?: boolean; }; const isValidPrefix = (prefix: string): boolean => !!prefix.match(/^\+\d+$/); -const PhoneInput = ({inputRef, value, defaultValue, onChange, prefix, e164, ...other}: InputProps) => { +const PhoneInput = ({inputRef, value, defaultValue, onChange, prefix, ...other}: InputProps) => { const [selfValue, setSelfValue] = React.useState(defaultValue ?? ''); const ref = React.useRef(null); const {i18n} = useTheme(); From 02d9632f6d847cb09c9bdd3bd64000d8d49003b6 Mon Sep 17 00:00:00 2001 From: Pedro Ladaria Date: Mon, 28 Oct 2024 18:49:06 +0100 Subject: [PATCH 4/5] WEB-2081 remove leftover --- src/__screenshot_tests__/input-fields-screenshot-test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__screenshot_tests__/input-fields-screenshot-test.tsx b/src/__screenshot_tests__/input-fields-screenshot-test.tsx index 065863be8..da4bcdcb6 100644 --- a/src/__screenshot_tests__/input-fields-screenshot-test.tsx +++ b/src/__screenshot_tests__/input-fields-screenshot-test.tsx @@ -521,7 +521,7 @@ test.each` expect(await fieldWrapper.screenshot()).toMatchImageSnapshot(); }); -test.only.each` +test.each` skin | number ${'Vivo'} | ${'2145678901'} ${'Vivo'} | ${'+34654834455'} From 7cb3c6f1eef8445bba2b247d1f246b2fba9c904e Mon Sep 17 00:00:00 2001 From: Pedro Ladaria Date: Mon, 28 Oct 2024 18:52:59 +0100 Subject: [PATCH 5/5] WEB-2081 remove leftovers --- src/phone-number-field-lite.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/phone-number-field-lite.tsx b/src/phone-number-field-lite.tsx index 1e9fb0d02..946954ada 100644 --- a/src/phone-number-field-lite.tsx +++ b/src/phone-number-field-lite.tsx @@ -127,7 +127,6 @@ type InputProps = Omit, 'value' | 'o onInput?: (event: React.FormEvent) => void; prefix?: string; format?: (number: string) => string; - e164?: boolean; }; const PhoneInput = ({ @@ -137,7 +136,6 @@ const PhoneInput = ({ onChange, prefix, format: formatFromProps, - e164, ...other }: InputProps) => { const [selfValue, setSelfValue] = React.useState(defaultValue || '');