diff --git a/bin/packages/js/components/phone-number-input/build-data.js b/bin/packages/js/components/phone-number-input/build-data.js new file mode 100644 index 00000000000..ee5935838f3 --- /dev/null +++ b/bin/packages/js/components/phone-number-input/build-data.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node +const https = require( 'https' ); +const vm = require( 'vm' ); +const fs = require( 'fs' ); +const path = require( 'path' ); + +const intlUrl = + 'https://raw.githubusercontent.com/jackocnr/intl-tel-input/master/src/js/data.js'; +const phoneUrl = + 'https://raw.githubusercontent.com/AfterShip/phone/master/src/data/country_phone_data.ts'; + +const fetch = ( url ) => + new Promise( ( resolve, reject ) => { + https + .get( url, ( res ) => { + let body = ''; + + res.on( 'data', ( chunk ) => { + body += chunk; + } ); + + res.on( 'end', () => { + resolve( body ); + } ); + } ) + .on( 'error', reject ); + } ); + +const numberOrString = ( str ) => + Number( str ).toString().length !== str.length ? str : Number( str ); + +const evaluate = ( code ) => { + const script = new vm.Script( code ); + const context = vm.createContext(); + + script.runInContext( context ); + + return context; +}; + +const parse = ( data /*: any[]*/ ) /*: DataType*/ => + data.reduce( + ( acc, item ) => ( { + ...acc, + [ item[ 0 ] ]: { + alpha2: item[ 0 ], + code: item[ 1 ].toString(), + priority: item[ 2 ] || 0, + start: item[ 3 ]?.map( String ), + lengths: item[ 4 ], + }, + } ), + {} + ); + +const saveToFile = ( data ) => { + const dataString = JSON.stringify( data ).replace( /null/g, '' ); + const parseString = parse.toString().replace( / \/\*(.+?)\*\//g, '$1' ); + + const code = [ + '// Do not edit this file directly.', + '// Generated by /bin/packages/js/components/phone-number-input/build-data.js', + '', + '/* eslint-disable */', + '', + 'import type { DataType } from "./types";', + '', + `const parse = ${ parseString }`, + '', + `const data = ${ dataString }`, + '', + 'export default parse(data);', + ].join( '\n' ); + + const filePath = path.resolve( + 'packages/js/components/src/phone-number-input/data.ts' + ); + + fs.writeFileSync( filePath, code ); +}; + +( async () => { + const intlData = await fetch( intlUrl ).then( evaluate ); + const phoneData = await fetch( phoneUrl ) + .then( ( data ) => 'var data = ' + data.substring( 15 ) ) + .then( evaluate ); + + // Convert phoneData array to object + const phoneCountries = phoneData.data.reduce( + ( acc, item ) => ( { + ...acc, + [ item.alpha2.toLowerCase() ]: item, + } ), + {} + ); + + // Traverse intlData to create a new array with required fields + const countries = intlData.allCountries.map( ( item ) => { + const phoneCountry = phoneCountries[ item.iso2 ]; + const result = [ + item.iso2.toUpperCase(), // alpha2 + Number( item.dialCode ), // code + /* [2] priority */ + /* [3] start */ + /* [4] lengths */ + , + , + , + ]; + + if ( item.priority ) { + result[ 2 ] = item.priority; + } + + const areaCodes = item.areaCodes || []; + const beginWith = phoneCountry?.mobile_begin_with || []; + if ( areaCodes.length || beginWith.length ) { + result[ 3 ] = [ ...new Set( [ ...areaCodes, ...beginWith ] ) ].map( + numberOrString + ); + } + + if ( phoneCountry?.phone_number_lengths ) { + result[ 4 ] = phoneCountry.phone_number_lengths; + } + + return result; + } ); + + saveToFile( countries ); +} )(); diff --git a/packages/js/components/changelog/add-phone-number-input-component b/packages/js/components/changelog/add-phone-number-input-component new file mode 100644 index 00000000000..a4168ae2af2 --- /dev/null +++ b/packages/js/components/changelog/add-phone-number-input-component @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +An international phone number input with country selection, and mobile phone numbers validation. \ No newline at end of file diff --git a/packages/js/components/src/index.ts b/packages/js/components/src/index.ts index 827296029f0..7c91e5121b9 100644 --- a/packages/js/components/src/index.ts +++ b/packages/js/components/src/index.ts @@ -104,6 +104,7 @@ export { SelectTreeMenuSlot as __experimentalSelectTreeMenuSlot, } from './experimental-select-tree-control'; export { default as TreeSelectControl } from './tree-select-control'; +export { default as PhoneNumberInput } from './phone-number-input'; // Exports below can be removed once the @woocommerce/product-editor package is released. export { diff --git a/packages/js/components/src/phone-number-input/README.md b/packages/js/components/src/phone-number-input/README.md new file mode 100644 index 00000000000..bb931a71a2b --- /dev/null +++ b/packages/js/components/src/phone-number-input/README.md @@ -0,0 +1,32 @@ +# PhoneNumberInput + +An international phone number input with a country code select and a phone textfield which supports numbers, spaces and hyphens. And returns the full number as it is, in E.164 format, and the selected country alpha2. + +Includes mobile phone numbers validation. + +## Usage + +```jsx + setState( value ) } +/> +``` + +### Props + +| Name | Type | Default | Description | +| ---------------- | -------- | ----------------------- | ------------------------------------------------------------------------------------------------------- | +| `value` | String | `undefined` | (Required) Phone number with spaces and hyphens | +| `onChange` | Function | `undefined` | (Required) Callback function when the value changes | +| `id` | String | `undefined` | ID for the input element, to bind a `