diff --git a/packages/storybook/stories/va-telephone.stories.jsx b/packages/storybook/stories/va-telephone.stories.jsx index 309554c88..c652cc761 100644 --- a/packages/storybook/stories/va-telephone.stories.jsx +++ b/packages/storybook/stories/va-telephone.stories.jsx @@ -60,6 +60,7 @@ const Template = ({ extension, 'not-clickable': notClickable, international, + 'country-code': countryCode, vanity, tty, sms, @@ -67,14 +68,13 @@ const Template = ({ }) => { return (
- {messageAriaDescribedBy && ( - Main number to facility - )} + {messageAriaDescribedBy && Main number to facility } { await axeCheck(page); }); + it('passes an axe check for a phone number with a country code', async () => { + const page = await newE2EPage(); + await page.setContent( + '', + ); + + await axeCheck(page); + }); + it('ignores non-digits in the contact prop', async () => { const page = await newE2EPage(); - await page.setContent(''); + await page.setContent( + '', + ); const link = await page.find('va-telephone >>> a'); expect(link.getAttribute('href')).toEqual('tel:+18779551234'); @@ -118,6 +129,17 @@ describe('va-telephone', () => { expect(link.innerText).toEqual('877-955-1234, ext. 123'); }); + it('handles a phone number with a country code', async () => { + const page = await newE2EPage(); + await page.setContent( + '', + ); + + const link = await page.find('va-telephone >>> a'); + expect(link.getAttribute('href')).toEqual('tel:+630285558888'); + expect(link.innerText).toEqual('+63 (02) 8555 8888'); + }); + it('handles a 3 digit contact prop', async () => { const page = await newE2EPage(); await page.setContent(''); diff --git a/packages/web-components/src/components/va-telephone/test/va-telephone.spec.tsx b/packages/web-components/src/components/va-telephone/test/va-telephone.spec.tsx index b1a77d46b..b557bc116 100644 --- a/packages/web-components/src/components/va-telephone/test/va-telephone.spec.tsx +++ b/packages/web-components/src/components/va-telephone/test/va-telephone.spec.tsx @@ -2,57 +2,131 @@ import { VaTelephone } from '../va-telephone'; describe('formatPhoneNumber', () => { const contact = '8885551234'; + const countryCode = '63'; + const intlContact = '(02) 8555 8888'; const N11 = '911'; const extension = '123'; const vanity = 'ABCD'; it('formats a contact number with no extension', () => { - expect(VaTelephone.formatPhoneNumber(contact, null, null, null)).toBe('888-555-1234'); + expect( + VaTelephone.formatPhoneNumber({ + contact, + }), + ).toBe('888-555-1234'); }); it('formats a contact number with extension', () => { - expect(VaTelephone.formatPhoneNumber(contact, extension, null, null)).toBe( - '888-555-1234, ext. 123', - ); + expect( + VaTelephone.formatPhoneNumber({ + contact, + extension, + }), + ).toBe('888-555-1234, ext. 123'); }); it('formats a 3 digit contact number', () => { - expect(VaTelephone.formatPhoneNumber(N11, null, null, null)).toBe('911'); + expect( + VaTelephone.formatPhoneNumber({ + contact: N11, + }), + ).toBe('911'); }); it('does not use extension for 3 digit contact', () => { - expect(VaTelephone.formatPhoneNumber(N11, extension, null, null)).toBe('911'); + expect( + VaTelephone.formatPhoneNumber({ + contact: N11, + extension, + }), + ).toBe('911'); }); it('does not use international formatting for 3 digit contact', () => { - expect(VaTelephone.formatPhoneNumber(N11, extension, true, null)).toBe('911'); + expect( + VaTelephone.formatPhoneNumber({ + contact: N11, + extension, + international: true, + }), + ).toBe('911'); }); it('formats a contact number with vanity prop', () => { - expect(VaTelephone.formatPhoneNumber(contact, null, null, vanity)).toBe('888-555-ABCD (1234)'); + expect( + VaTelephone.formatPhoneNumber({ + contact, + vanity, + }), + ).toBe('888-555-ABCD (1234)'); }); it('formats a TTY number', () => { - expect(VaTelephone.formatPhoneNumber(N11, null, null, null, true)).toBe('TTY: 911'); + expect( + VaTelephone.formatPhoneNumber({ + contact: N11, + tty: true, + }), + ).toBe('TTY: 911'); + }); + + it('formats a contact number with a country code', () => { + expect( + VaTelephone.formatPhoneNumber({ + contact: intlContact, + countryCode, + }), + ).toBe('+63 (02) 8555 8888'); }); }); describe('createHref', () => { const contact = '8885551234'; + const countryCode = '63'; + const intlContact = '(02) 8555 8888'; const contactSms = '123456'; const n11 = '911'; const extension = '123'; + it('creates a tel link for a phone number', () => { - expect(VaTelephone.createHref(contact, null, null)).toBe('tel:+18885551234'); + expect( + VaTelephone.createHref({ + contact, + }), + ).toBe('tel:+18885551234'); }); + it('creates a tel link for a phone number with extension', () => { - expect(VaTelephone.createHref(contact, extension, null)).toBe( - 'tel:+18885551234,123', - ); + expect( + VaTelephone.createHref({ + contact, + extension, + }), + ).toBe('tel:+18885551234,123'); }); + + it('creates a tel link for a phone number with a country code', () => { + expect( + VaTelephone.createHref({ + contact: intlContact, + countryCode, + }), + ).toBe('tel:+630285558888'); + }); + it('creates a tel link for an N11 number', () => { - expect(VaTelephone.createHref(n11, null, null)).toBe('tel:911'); + expect( + VaTelephone.createHref({ + contact: n11, + }), + ).toBe('tel:911'); }); + it('creates an sms link for an SMS number', () => { - expect(VaTelephone.createHref(contactSms, null, true)).toBe('sms:123456'); + expect( + VaTelephone.createHref({ + contact: contactSms, + sms: true, + }), + ).toBe('sms:123456'); }); }); \ No newline at end of file diff --git a/packages/web-components/src/components/va-telephone/va-telephone.tsx b/packages/web-components/src/components/va-telephone/va-telephone.tsx index 709eaaf80..0118aab2b 100644 --- a/packages/web-components/src/components/va-telephone/va-telephone.tsx +++ b/packages/web-components/src/components/va-telephone/va-telephone.tsx @@ -41,6 +41,11 @@ export class VaTelephone { */ @Prop() international?: boolean = false; + /** + * Prepends the country code to the given contact number. Do NOT include the '+' + */ + @Prop() countryCode?: string; + /** * Indicates if this is a number meant to be called * from a teletypewriter for deaf users. @@ -75,11 +80,11 @@ export class VaTelephone { }) componentLibraryAnalytics: EventEmitter; - static cleanContact(contact: string) :string { + static cleanContact(contact: string): string { return (contact || '').replace(/[^\d]/g, ''); } - static splitContact(contact: string) :string[] { + static splitContact(contact: string): string[] { const cleanedContact = VaTelephone.cleanContact(contact); if (cleanedContact.length === 10) { // const regex = /(?\d{3})(?\d{3})(?\d{4})/g; @@ -99,17 +104,27 @@ export class VaTelephone { * Format telephone number for display. * `international` and `extension` args only work on 10 digit contacts */ - static formatPhoneNumber( - num: string, - extension: string, - international: boolean = false, - vanity: string, - tty: boolean = false, - ): string { - const splitNumber = VaTelephone.splitContact(num); + /* eslint-disable i18next/no-literal-string */ + static formatPhoneNumber({ + contact: num, + extension, + international = false, + countryCode, + vanity, + tty = false, + }: { + contact: string; + extension?: string; + international?: boolean; + countryCode?: string; + vanity?: string; + tty?: boolean; + }): string { + const splitNumber = countryCode ? [num] : VaTelephone.splitContact(num); let formattedNum = splitNumber.join(''); - if (formattedNum.length === 10) { - const [ area, local, last4 ] = splitNumber; + + if (formattedNum.length === 10 && !countryCode) { + const [area, local, last4] = splitNumber; formattedNum = `${area}-${local}-${last4}`; if (international) formattedNum = `+1-${formattedNum}`; if (extension) formattedNum = `${formattedNum}, ext. ${extension}`; @@ -117,13 +132,29 @@ export class VaTelephone { formattedNum = `${area}-${local}-${vanity} (${last4})`; } } + + if (countryCode) { + formattedNum = `+${countryCode} ${formattedNum}`; + } + if (tty) { formattedNum = `TTY: ${formattedNum}`; } + return formattedNum; } - static createHref(contact: string, extension: string, sms: boolean): string { + static createHref({ + contact, + extension, + sms, + countryCode, + }: { + contact: string; + extension?: string; + sms?: boolean; + countryCode?: string; + }): string { const cleanedContact = VaTelephone.cleanContact(contact); const isN11 = cleanedContact.length === 3; // extension format ";ext=" from RFC3966 https://tools.ietf.org/html/rfc3966#page-5 @@ -134,11 +165,15 @@ export class VaTelephone { href = `sms:${cleanedContact}`; } else if (isN11) { href = `tel:${cleanedContact}`; + } else if (countryCode) { + href = `tel:+${countryCode}${cleanedContact}`; } else { + // RFC3966 (https://www.rfc-editor.org/rfc/rfc3966#section-5.1.5) calls for the inclusion of country codes whenever possible, so we add the +1 unless we know there's a different country code (above) href = `tel:+1${cleanedContact}`; } return `${href}${extension ? `,${extension}` : ''}`; } + /* eslint-enable i18next/no-literal-string */ private handleClick(): void { this.componentLibraryAnalytics.emit({ @@ -152,37 +187,50 @@ export class VaTelephone { } render() { - const { - contact, - extension, - notClickable, - international, - tty, - vanity, - sms, - messageAriaDescribedby - } = this; - const formattedNumber = VaTelephone.formatPhoneNumber( + const { contact, extension, + notClickable, international, + countryCode, + tty, vanity, - tty - ); + sms, + messageAriaDescribedby, + } = this; + const formattedNumber = VaTelephone.formatPhoneNumber({ + contact: contact, + extension, + international, + countryCode, + vanity, + tty, + }); + const href = VaTelephone.createHref({ + contact, + extension, + sms, + countryCode, + }); // Null so we don't add the attribute if we have an empty string - const ariaDescribedbyIds = messageAriaDescribedby ? 'number-description' : null; + const ariaDescribedbyIds = messageAriaDescribedby + ? // eslint-disable-next-line i18next/no-literal-string + 'number-description' + : null; - return ( + return ( {notClickable ? ( - + ) : ( {formattedNumber}