From 5c759cbfb6018921487e59d82929b6c389a001c2 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Tue, 28 Feb 2023 12:20:43 +0400 Subject: [PATCH 01/19] Add initial versions of the sections --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d8b905f..20d7aca 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,49 @@ # antd-phone-input -Advanced Phone Number Input for Ant Design + +Advanced Phone Number Input for [Ant Design](https://github.com/ant-design/ant-design). + +[![npm](https://img.shields.io/npm/v/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) +[![types](https://img.shields.io/npm/types/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) +[![License](https://img.shields.io/npm/l/antd-phone-input)](LICENSE) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com) + +## Install + +```shell +npm i antd-phone-input +``` + +```shell +yarn add antd-phone-input +``` + +## Usage + +```javascript +import React from "react"; +import PhoneInput from "antd-phone-input"; +import FormItem from "antd/es/form/FormItem"; + +const Demo = () => { + return ( + + + + ) +} +``` + +## Props + +| Property | Description | Type | Default | +|----------|-------------|------|---------| +| - | - | - | - | + +## Contribute + +Any contribution is welcome. If you have any ideas or suggestions, feel free to open an issue or a pull request. And +don't forget to add tests for your changes. + +## License + +Copyright (C) 2023 Artyom Vancyan. [MIT](LICENSE) From 3fc10a8913bef7be01bff3448bc17f71cf0956dd Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Tue, 28 Feb 2023 15:55:30 +0400 Subject: [PATCH 02/19] Fix code indentation --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 20d7aca..3c98968 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,11 @@ import PhoneInput from "antd-phone-input"; import FormItem from "antd/es/form/FormItem"; const Demo = () => { - return ( - - - - ) + return ( + + + + ) } ``` From fccf2235af89e7ebd354e617a72109ad5530a1fb Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Tue, 28 Feb 2023 21:33:41 +0400 Subject: [PATCH 03/19] Downgrade to Antd 4.x --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b473660..0933a92 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@types/node": "^18.14.1", "@types/react": "^18.0.28", "@types/rollup-plugin-less": "^1.1.2", - "antd": "^5.2.2", + "antd": "^4.24.8", "identity-obj-proxy": "^3.0.0", "jest": "^29.4.3", "jest-environment-jsdom": "^29.4.3", From d6434fb7debfd6fdba79273804cff92bc1d3de54 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Tue, 28 Feb 2023 22:09:00 +0400 Subject: [PATCH 04/19] GH-16: Extend `antd` styles --- package.json | 1 - rollup.config.ts | 13 ++-- src/index.less | 164 ++++++++++++++++++++++-------------------- tests/common.test.tsx | 4 +- 4 files changed, 94 insertions(+), 88 deletions(-) diff --git a/package.json b/package.json index 0933a92..68e25e8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "rollup": "3.17.2", "rollup-plugin-dts": "^5.2.0", "rollup-plugin-less": "^1.1.3", - "rollup-plugin-node-resolve": "^5.2.0", "rollup-plugin-postcss": "^4.0.2", "ts-jest": "^29.0.5", "tslib": "^2.5.0", diff --git a/rollup.config.ts b/rollup.config.ts index 2ba1855..2171a22 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -2,7 +2,6 @@ import dts from "rollup-plugin-dts"; import less from "rollup-plugin-less"; import json from "@rollup/plugin-json"; import postcss from "rollup-plugin-postcss"; -import resolve from "rollup-plugin-node-resolve"; import typescript from "@rollup/plugin-typescript"; import {readFileSync} from "fs"; @@ -14,19 +13,19 @@ const esmOutput = {file: pkg.module, format: "es"}; const dtsOutput = {file: pkg.types, format: "es"}; const jsonPlugin = json(); -const cssPlugin = postcss(); const tsPlugin = typescript(); -const resolvePlugin = resolve(); -const lessPlugin = less({insert: true, output: false}); +const cssPlugin = postcss({exclude: /\.less$/, include: /\.css$/}); +const lessPlugin = less({insert: true, output: false, option: {javascriptEnabled: true}}); const external = [ ...Object.keys({...pkg.dependencies, ...pkg.peerDependencies}), /^react($|\/)/, /^antd($|\/)/, + /\.css$/, ]; export default [ - {input, output: cjsOutput, plugins: [tsPlugin, jsonPlugin, resolvePlugin, cssPlugin], external}, - {input, output: esmOutput, plugins: [tsPlugin, jsonPlugin, resolvePlugin, cssPlugin], external}, - {input, output: dtsOutput, plugins: [dts(), lessPlugin], external: [/\.(?:le|c)ss$/]}, + {input, output: cjsOutput, plugins: [tsPlugin, jsonPlugin, cssPlugin, lessPlugin], external}, + {input, output: esmOutput, plugins: [tsPlugin, jsonPlugin, cssPlugin, lessPlugin], external}, + {input, output: dtsOutput, plugins: [dts()], external: [/\.(le|c)ss$/]}, ]; diff --git a/src/index.less b/src/index.less index 0558d84..5ad10d2 100644 --- a/src/index.less +++ b/src/index.less @@ -1,79 +1,87 @@ -@import "~react-phone-input-2/lib/style.css"; +@import "../node_modules/antd/lib/style/themes/variable"; +@import "../node_modules/antd/lib/input/style/index"; -//.phone-number-form-item { -// -// &.ant-form-item-has-error { -// .react-tel-input { -// .form-control { -// border-color: @error-color; -// -// &:hover { -// border-color: @error-color; -// } -// -// &:focus { -// border-color: @error-color; -// } -// } -// } -// } -// -// .react-tel-input { -// -// .form-control { -// z-index: 3; -// width: 100%; -// border-radius: @border-radius-base; -// line-height: @line-height-base; -// height: inherit; -// background: transparent; -// border-color: @border-color-base; -// -// &:hover { -// border-color: @input-hover-border-color; -// } -// -// &:focus { -// border-color: @primary-color; -// } -// } -// -// .country-list { -// margin: 3px; -// border-radius: 2px; -// width: calc(100% - 6px); -// box-shadow: 0 3px 6px -4px rgba(0, 0, 0, .12), -// 0 6px 16px 0 rgba(0, 0, 0, .08), -// 0 9px 28px 8px rgba(0, 0, 0, .05); -// -// .search { -// padding: @spacing-2 @spacing-2 @spacing-1 @spacing-2; -// -// .search-box { -// .ant-input(); -// } -// } -// } -// -// .flag-dropdown, -// .flag-dropdown.open { -// border: 0; -// width: 100%; -// border-radius: 0; -// background-color: transparent; -// -// .arrow { -// display: none; -// } -// -// .selected-flag { -// z-index: 4; -// padding: 0; -// display: flex; -// justify-content: center; -// border-right: @border-base; -// background-color: transparent; -// } -// } -// } -//} +.ant-form-item { + &.ant-form-item-has-error { + .react-tel-input { + .form-control { + border-color: @error-color; + + &:hover { + border-color: @error-color; + } + + &:focus { + border-color: @error-color; + } + } + } + } +} + +.react-tel-input { + .form-control { + z-index: 3; + width: 100%; + border-radius: @border-radius-base; + line-height: @line-height-base; + height: inherit; + background: transparent; + border-color: @border-color-base; + + &:hover { + border-color: @input-hover-border-color; + } + + &:focus { + border-color: @primary-color; + } + + &.open { + z-index: 6 !important; + transition: 0s !important; + } + } + + .country-list { + margin: 3px; + border-radius: 2px; + width: calc(100% - 6px); + box-shadow: 0 3px 6px -4px rgba(0, 0, 0, .12), + 0 6px 16px 0 rgba(0, 0, 0, .08), + 0 9px 28px 8px rgba(0, 0, 0, .05); + + .search { + padding: 10px 10px 5px 10px; + + .search-box { + .ant-input(); + } + } + } + + .flag-dropdown, + .flag-dropdown.open { + border: 0; + width: 100%; + border-radius: 0; + background-color: @component-background; + + .arrow { + display: none; + } + + .selected-flag { + z-index: 4; + padding: 0; + display: flex; + justify-content: center; + background-color: transparent; + border-right: @border-width-base @border-style-base @border-color-base; + } + } + + .flag-dropdown.open { + z-index: 5 !important; + } +} diff --git a/tests/common.test.tsx b/tests/common.test.tsx index 04124f8..2dc4cda 100644 --- a/tests/common.test.tsx +++ b/tests/common.test.tsx @@ -1,6 +1,6 @@ import {render} from "@testing-library/react"; -import PhoneNumberInput from "../src"; +import PhoneInput from "../src"; Object.defineProperty(window, "matchMedia", { writable: true, @@ -18,6 +18,6 @@ Object.defineProperty(window, "matchMedia", { describe("PhoneNumberInput render", () => { it("renders without crashing", () => { - render() + render() }) }) From 33332f7cbf77bdda2d2f7cee5f608b8fa7aa4558 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Tue, 28 Feb 2023 22:15:49 +0400 Subject: [PATCH 05/19] GH-17: Implement `size` prop of antd input --- src/index.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 14f04e7..f5e2b76 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -18,7 +18,8 @@ type PhoneNumber = { phoneNumber?: string, } -type PhoneNumberInputProps = { +type PhoneInputProps = { + size?: "small" | "middle" | "large", value?: PhoneNumber | object, onChange?: (value: PhoneNumber) => void, } @@ -37,10 +38,16 @@ const getDefaultISO2Code = () => { return timezones[timezone].toLowerCase() || "us"; } -const PhoneInput = ({value = {}, onChange: handleChange}: PhoneNumberInputProps) => { +const PhoneInput = ({value = {}, size = "middle", onChange: handleChange}: PhoneInputProps) => { const [currentCode, setCurrentCode] = useState(""); + const rawPhone = useMemo(() => Object.values(value).map(v => v || "").join(""), [value]); + const inputClass = useMemo(() => { + const suffix = {small: "sm", middle: "", large: "lg"}[size]; + return "ant-input" + (suffix ? " ant-input-" + suffix : ""); + }, [size]); + const onChange: OnChangeFunction = (value, data, _, formattedValue) => { const code: ISO2Code = data?.countryCode; const countryCodePattern = /\+\d+/; @@ -67,7 +74,7 @@ const PhoneInput = ({value = {}, onChange: handleChange}: PhoneNumberInputProps) } if (handleChange) handleChange({countryCode, areaCode, phoneNumber}); - }; + } return ( From 5d22045501172ced72b43124672bd5a07c668f6f Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Wed, 1 Mar 2023 13:23:58 +0400 Subject: [PATCH 06/19] GH-17: Inherit most React Phone properties --- src/index.tsx | 67 +++++++++++++++++++++++++-------------------------- src/types.ts | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 src/types.ts diff --git a/src/index.tsx b/src/index.tsx index f5e2b76..6de12e8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,46 +1,37 @@ -import {ChangeEvent, useMemo, useState} from "react"; +import {useMemo, useState} from "react"; import ReactPhoneInput from "react-phone-input-2"; +import {PhoneInputProps, ReactPhoneOnChange, ReactPhoneOnMount} from "./types"; + import masks from "./phoneMasks.json"; import timezones from "./timezones.json"; -import validations from "./validations.json"; import "react-phone-input-2/lib/style.css"; import "./index.less"; -type CountryData = { - countryCode: ISO2Code, -} - -type PhoneNumber = { - countryCode?: number | null, - areaCode?: number | null, - phoneNumber?: string, -} - -type PhoneInputProps = { - size?: "small" | "middle" | "large", - value?: PhoneNumber | object, - onChange?: (value: PhoneNumber) => void, -} - -type OnChangeFunction = { - (number: string, data: CountryData, event: Event, formattedNumber: string): void, -} - +type ISO2Code = keyof typeof masks; type Timezone = keyof typeof timezones; -type ISO2Code = keyof typeof validations; -type Event = ChangeEvent; const getDefaultISO2Code = () => { /** Returns the default ISO2 code based on the user's timezone */ - const timezone: Timezone = Intl.DateTimeFormat().resolvedOptions().timeZone as Timezone; + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone as Timezone; return timezones[timezone].toLowerCase() || "us"; } -const PhoneInput = ({value = {}, size = "middle", onChange: handleChange}: PhoneInputProps) => { +const PhoneInput = ({ + style, + country, + className, + value = {}, + size = "middle", + onMount: handleMount = () => null, + onChange: handleChange = () => null, + ...reactPhoneInputProps + }: PhoneInputProps) => { const [currentCode, setCurrentCode] = useState(""); + const defaultCountry = useMemo(() => country || getDefaultISO2Code(), [country]); + const rawPhone = useMemo(() => Object.values(value).map(v => v || "").join(""), [value]); const inputClass = useMemo(() => { @@ -48,8 +39,8 @@ const PhoneInput = ({value = {}, size = "middle", onChange: handleChange}: Phone return "ant-input" + (suffix ? " ant-input-" + suffix : ""); }, [size]); - const onChange: OnChangeFunction = (value, data, _, formattedValue) => { - const code: ISO2Code = data?.countryCode; + const onChange: ReactPhoneOnChange = (value, data, event, formattedValue) => { + const code = data?.countryCode as ISO2Code; const countryCodePattern = /\+\d+/; const areaCodePattern = /\((\d+)\)/; @@ -68,24 +59,32 @@ const PhoneInput = ({value = {}, size = "middle", onChange: handleChange}: Phone /** Clear phone number when the country is selected manually */ if (currentCode !== undefined && code !== currentCode) { - if (handleChange) handleChange({countryCode, areaCode: null, phoneNumber: ""}); + handleChange({countryCode, areaCode: null, phoneNumber: ""}, event); setCurrentCode(code); return; } - if (handleChange) handleChange({countryCode, areaCode, phoneNumber}); + handleChange({countryCode, areaCode, phoneNumber}, event); } + const onMount: ReactPhoneOnMount = (_1, _2, _3) => handleMount(value); + return ( ) } diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..719bd65 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,62 @@ +import {ChangeEvent, CSSProperties, FocusEvent, InputHTMLAttributes, KeyboardEvent, MouseEvent} from "react"; + +export interface CountryData { + countryCode: string, +} + +export interface PhoneNumber { + countryCode?: number | null, + areaCode?: number | null, + phoneNumber?: string, +} + +export interface AntInputProps { + size?: "small" | "middle" | "large", + value?: PhoneNumber | {}, + style?: CSSProperties, + className?: string, + disabled?: boolean, +} + +export interface ReactPhoneInputProps { + inputProps?: InputHTMLAttributes, + searchPlaceholder?: string, + searchNotFound?: string, + placeholder?: string, + enableSearch?: boolean, + disableDropdown?: boolean, + country?: string, + regions?: string[], + onlyCountries?: string[], + excludeCountries?: string[], + preferredCountries?: string[], +} + +export interface ReactPhoneEventsProps { + onChange?: (value: PhoneNumber, event: ChangeEvent) => void; + + onFocus?(event: FocusEvent, value: PhoneNumber): void; + + onBlur?(event: FocusEvent, value: PhoneNumber): void; + + onClick?(event: MouseEvent, value: PhoneNumber): void; + + onMount?(value: PhoneNumber): void; + + onKeyDown?(event: KeyboardEvent): void; + + onEnterKeyPress?(event: KeyboardEvent): void; +} + +export interface ReactPhoneOnChange { + (value: string, data: CountryData, event: ChangeEvent, formattedNumber: string): void; +} + +export interface ReactPhoneOnMount { + (value: string, event: ChangeEvent, formattedNumber: string): void; +} + +export interface PhoneInputProps extends AntInputProps, ReactPhoneInputProps, ReactPhoneEventsProps { + // TODO add onValidate: https://github.com/ArtyomVancyan/antd-phone-input/issues/19 + // onValidate?: (value: PhoneNumber) => boolean; +} From 167e340589489344d42b9873207b5de1f6dcf54b Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Wed, 1 Mar 2023 14:10:26 +0400 Subject: [PATCH 07/19] GH-17: Rename `onEnterKeyPress` to `onPressEnter` --- src/index.tsx | 2 ++ src/types.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 6de12e8..5e38b96 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -24,6 +24,7 @@ const PhoneInput = ({ className, value = {}, size = "middle", + onPressEnter = () => null, onMount: handleMount = () => null, onChange: handleChange = () => null, ...reactPhoneInputProps @@ -85,6 +86,7 @@ const PhoneInput = ({ {...reactPhoneInputProps} containerStyle={style} containerClass={className} + onEnterKeyPress={onPressEnter} /> ) } diff --git a/src/types.ts b/src/types.ts index 719bd65..6aea028 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,6 +18,12 @@ export interface AntInputProps { disabled?: boolean, } +export interface AntInputEventsProps { + onChange?(value: PhoneNumber, event: ChangeEvent): void; + + onPressEnter?(event: KeyboardEvent): void; +} + export interface ReactPhoneInputProps { inputProps?: InputHTMLAttributes, searchPlaceholder?: string, @@ -33,19 +39,15 @@ export interface ReactPhoneInputProps { } export interface ReactPhoneEventsProps { - onChange?: (value: PhoneNumber, event: ChangeEvent) => void; - onFocus?(event: FocusEvent, value: PhoneNumber): void; - onBlur?(event: FocusEvent, value: PhoneNumber): void; - onClick?(event: MouseEvent, value: PhoneNumber): void; - onMount?(value: PhoneNumber): void; + onBlur?(event: FocusEvent, value: PhoneNumber): void; onKeyDown?(event: KeyboardEvent): void; - onEnterKeyPress?(event: KeyboardEvent): void; + onMount?(value: PhoneNumber): void; } export interface ReactPhoneOnChange { @@ -56,7 +58,7 @@ export interface ReactPhoneOnMount { (value: string, event: ChangeEvent, formattedNumber: string): void; } -export interface PhoneInputProps extends AntInputProps, ReactPhoneInputProps, ReactPhoneEventsProps { +export interface PhoneInputProps extends AntInputProps, AntInputEventsProps, ReactPhoneInputProps, ReactPhoneEventsProps { // TODO add onValidate: https://github.com/ArtyomVancyan/antd-phone-input/issues/19 // onValidate?: (value: PhoneNumber) => boolean; } From 729aa66f9c3f9884d78b6d9e2ccfba2bba555422 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Wed, 1 Mar 2023 23:54:30 +0400 Subject: [PATCH 08/19] GH-15: Sync the docs with current functionality --- README.md | 36 ++++++++++++++++++++++++++++++++---- src/types.ts | 2 +- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3c98968..54ccb7c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Advanced Phone Number Input for [Ant Design](https://github.com/ant-design/ant-d [![npm](https://img.shields.io/npm/v/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) [![types](https://img.shields.io/npm/types/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) -[![License](https://img.shields.io/npm/l/antd-phone-input)](LICENSE) +[![License](https://img.shields.io/npm/l/antd-phone-input)](https://github.com/ArtyomVancyan/antd-phone-input/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com) ## Install @@ -35,9 +35,31 @@ const Demo = () => { ## Props -| Property | Description | Type | Default | -|----------|-------------|------|---------| -| - | - | - | - | +| Property | Description | Type | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------|------------------------| +| size | Either `large`, `middle` or `small`. Default value is `middle`. See at ant [docs][antInputProps] for more. | string | +| value | An object containing the parts of phone number. E.g. `value={{countryCode: 1, areaCode: 702, phoneNumber: "1234567"}}`. | object | +| style | Applies CSS styles to the container element. | CSSProperties | +| className | The value will be assigned to the container element. | string | +| disabled | Disables the whole input component. | boolean | +| enableSearch | Enables search in the country selection dropdown menu. Default value is `false`. | boolean | +| disableDropdown | Disables the manual country selection through the dropdown menu. | boolean | +| inputProps | [HTML properties of input][htmlInputProps] to pass into the input. E.g. `inputProps={{autoFocus: true}}`. | InputHTMLAttributes | +| searchPlaceholder | The value is shown if `enableSearch` is `true`. Default value is `search`. | string | +| searchNotFound | The value is shown if `enableSearch` is `true` and the query does not match any country. Default value is `No entries to show`. | string | +| placeholder | Custom placeholder. Default placeholder is `1 (702) 123-4567`. | string | +| country | Country code to be selected by default. By default, it will show the flag of the user's country. | string | +| regions | Show only the countries of the specified regions. See the list of [available regions][reactPhoneRegions]. | string[] | +| onlyCountries | Country codes to be included in the list. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | +| excludeCountries | Country codes to be excluded from the list of countries. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | +| preferredCountries | Country codes to be at the top of the list. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | +| onChange | Callback when the user is inputting. See at ant [docs][antInputProps] for more. | function(value, event) | +| onPressEnter | The callback function that is triggered when Enter key is pressed. | function(event) | +| onFocus | The callback is triggered when the input element is focused. | function(event, value) | +| onClick | The callback is triggered when the user clicks on the input element. | function(event, value) | +| onBlur | The callback is triggered when the input element gets blurred or unfocused. | function(event, value) | +| onKeyDown | The callback is triggered when any key is pressed down. | function(event) | +| onMount | The callback is triggered once the component gets mounted. | function(event) | ## Contribute @@ -47,3 +69,9 @@ don't forget to add tests for your changes. ## License Copyright (C) 2023 Artyom Vancyan. [MIT](LICENSE) + +[antInputProps]:https://ant.design/components/input#input + +[reactPhoneRegions]:https://github.com/bl00mber/react-phone-input-2#regions + +[htmlInputProps]:https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attributes diff --git a/src/types.ts b/src/types.ts index 6aea028..172e07f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -12,7 +12,7 @@ export interface PhoneNumber { export interface AntInputProps { size?: "small" | "middle" | "large", - value?: PhoneNumber | {}, + value?: PhoneNumber | object, style?: CSSProperties, className?: string, disabled?: boolean, From 97769e569d766868f9eb514dc36072b28515326c Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 13:19:32 +0400 Subject: [PATCH 09/19] GH-15: Fix the property names in examples --- README.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 54ccb7c..1fe1a42 100644 --- a/README.md +++ b/README.md @@ -35,31 +35,31 @@ const Demo = () => { ## Props -| Property | Description | Type | -|--------------------|---------------------------------------------------------------------------------------------------------------------------------|------------------------| -| size | Either `large`, `middle` or `small`. Default value is `middle`. See at ant [docs][antInputProps] for more. | string | -| value | An object containing the parts of phone number. E.g. `value={{countryCode: 1, areaCode: 702, phoneNumber: "1234567"}}`. | object | -| style | Applies CSS styles to the container element. | CSSProperties | -| className | The value will be assigned to the container element. | string | -| disabled | Disables the whole input component. | boolean | -| enableSearch | Enables search in the country selection dropdown menu. Default value is `false`. | boolean | -| disableDropdown | Disables the manual country selection through the dropdown menu. | boolean | -| inputProps | [HTML properties of input][htmlInputProps] to pass into the input. E.g. `inputProps={{autoFocus: true}}`. | InputHTMLAttributes | -| searchPlaceholder | The value is shown if `enableSearch` is `true`. Default value is `search`. | string | -| searchNotFound | The value is shown if `enableSearch` is `true` and the query does not match any country. Default value is `No entries to show`. | string | -| placeholder | Custom placeholder. Default placeholder is `1 (702) 123-4567`. | string | -| country | Country code to be selected by default. By default, it will show the flag of the user's country. | string | -| regions | Show only the countries of the specified regions. See the list of [available regions][reactPhoneRegions]. | string[] | -| onlyCountries | Country codes to be included in the list. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | -| excludeCountries | Country codes to be excluded from the list of countries. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | -| preferredCountries | Country codes to be at the top of the list. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | -| onChange | Callback when the user is inputting. See at ant [docs][antInputProps] for more. | function(value, event) | -| onPressEnter | The callback function that is triggered when Enter key is pressed. | function(event) | -| onFocus | The callback is triggered when the input element is focused. | function(event, value) | -| onClick | The callback is triggered when the user clicks on the input element. | function(event, value) | -| onBlur | The callback is triggered when the input element gets blurred or unfocused. | function(event, value) | -| onKeyDown | The callback is triggered when any key is pressed down. | function(event) | -| onMount | The callback is triggered once the component gets mounted. | function(event) | +| Property | Description | Type | +|--------------------|---------------------------------------------------------------------------------------------------------------------------------|---------------------| +| size | Either `large`, `middle` or `small`. Default value is `middle`. See at ant [docs][antInputProps] for more. | string | +| value | An object containing the parts of phone number. E.g. `value={{countryCode: 1, areaCode: 702, phoneNumber: "1234567"}}`. | object | +| style | Applies CSS styles to the container element. | CSSProperties | +| className | The value will be assigned to the container element. | string | +| disabled | Disables the whole input component. | boolean | +| enableSearch | Enables search in the country selection dropdown menu. Default value is `false`. | boolean | +| disableDropdown | Disables the manual country selection through the dropdown menu. | boolean | +| inputProps | [HTML properties of input][htmlInputProps] to pass into the input. E.g. `inputProps={{autoFocus: true}}`. | InputHTMLAttributes | +| searchPlaceholder | The value is shown if `enableSearch` is `true`. Default value is `search`. | string | +| searchNotFound | The value is shown if `enableSearch` is `true` and the query does not match any country. Default value is `No entries to show`. | string | +| placeholder | Custom placeholder. Default placeholder is `1 (702) 123-4567`. | string | +| country | Country code to be selected by default. By default, it will show the flag of the user's country. | string | +| regions | Show only the countries of the specified regions. See the list of [available regions][reactPhoneRegions]. | string[] | +| onlyCountries | Country codes to be included in the list. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | +| excludeCountries | Country codes to be excluded from the list of countries. E.g. `excludeCountries={['us', 'ca', 'uk']}`. | string[] | +| preferredCountries | Country codes to be at the top of the list. E.g. `preferredCountries={['us', 'ca', 'uk']}`. | string[] | +| onChange | Callback when the user is inputting. See at ant [docs][antInputProps] for more. | function(value, e) | +| onPressEnter | The callback function that is triggered when Enter key is pressed. | function(e) | +| onFocus | The callback is triggered when the input element is focused. | function(e, value) | +| onClick | The callback is triggered when the user clicks on the input element. | function(e, value) | +| onBlur | The callback is triggered when the input element gets blurred or unfocused. | function(e, value) | +| onKeyDown | The callback is triggered when any key is pressed down. | function(e) | +| onMount | The callback is triggered once the component gets mounted. | function(e) | ## Contribute From dfc1dd5252708f4a0e91f5cdce838ac25fb7e763 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 16:10:36 +0400 Subject: [PATCH 10/19] Add `isoCode` to the value --- src/index.tsx | 62 ++++++++++++++++++++++++++++++--------------------- src/types.ts | 7 +++++- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 5e38b96..4a6579c 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,7 @@ import {useMemo, useState} from "react"; import ReactPhoneInput from "react-phone-input-2"; -import {PhoneInputProps, ReactPhoneOnChange, ReactPhoneOnMount} from "./types"; +import {ParsePhoneNumber, PhoneInputProps, ReactPhoneOnChange, ReactPhoneOnMount} from "./types"; import masks from "./phoneMasks.json"; import timezones from "./timezones.json"; @@ -18,6 +18,27 @@ const getDefaultISO2Code = () => { return timezones[timezone].toLowerCase() || "us"; } +const parsePhoneNumber: ParsePhoneNumber = (value, data, formattedNumber) => { + const isoCode = data?.countryCode; + const countryCodePattern = /\+\d+/; + const areaCodePattern = /\((\d+)\)/; + + /** Parse the matching partials of the phone number by predefined regex patterns */ + const countryCodeMatch = formattedNumber ? (formattedNumber.match(countryCodePattern) || []) : []; + const areaCodeMatch = formattedNumber ? (formattedNumber.match(areaCodePattern) || []) : []; + + /** Convert the parsed values of the country and area codes to integers if values present */ + const countryCode = countryCodeMatch.length > 0 ? parseInt(countryCodeMatch[0]) : null; + const areaCode = areaCodeMatch.length > 1 ? parseInt(areaCodeMatch[1]) : null; + + /** Parse the phone number by removing the country and area codes from the formatted value */ + const phoneNumberPattern = new RegExp(`^${countryCode}${(areaCode || "")}(\\d+)`); + const phoneNumberMatch = value ? (value.match(phoneNumberPattern) || []) : []; + const phoneNumber = phoneNumberMatch.length > 1 ? phoneNumberMatch[1] : null; + + return {countryCode, areaCode, phoneNumber, isoCode}; +} + const PhoneInput = ({ style, country, @@ -31,41 +52,30 @@ const PhoneInput = ({ }: PhoneInputProps) => { const [currentCode, setCurrentCode] = useState(""); - const defaultCountry = useMemo(() => country || getDefaultISO2Code(), [country]); + const countryCode = useMemo(() => country || getDefaultISO2Code(), [country]); - const rawPhone = useMemo(() => Object.values(value).map(v => v || "").join(""), [value]); + const rawPhone = useMemo(() => { + const {countryCode, areaCode, phoneNumber} = {...value}; + return [countryCode, areaCode, phoneNumber].map(v => v || "").join(""); + }, [value]); const inputClass = useMemo(() => { const suffix = {small: "sm", middle: "", large: "lg"}[size]; return "ant-input" + (suffix ? " ant-input-" + suffix : ""); }, [size]); - const onChange: ReactPhoneOnChange = (value, data, event, formattedValue) => { - const code = data?.countryCode as ISO2Code; - const countryCodePattern = /\+\d+/; - const areaCodePattern = /\((\d+)\)/; - - /** Parse the matching partials of the phone number by predefined regex patterns */ - const countryCodeMatch = formattedValue ? (formattedValue.match(countryCodePattern) || []) : []; - const areaCodeMatch = formattedValue ? (formattedValue.match(areaCodePattern) || []) : []; - - /** Convert the parsed values of the country and area codes to integers if values present */ - const countryCode = countryCodeMatch.length > 0 ? parseInt(countryCodeMatch[0]) : null; - const areaCode = areaCodeMatch.length > 1 ? parseInt(areaCodeMatch[1]) : null; - - /** Parse the phone number by removing the country and area codes from the formatted value */ - const phoneNumberPattern = new RegExp(`^${countryCode}${(areaCode || "")}(\\d+)`); - const phoneNumberMatch = value ? (value.match(phoneNumberPattern) || []) : []; - const phoneNumber = phoneNumberMatch.length > 1 ? phoneNumberMatch[1] : ""; + const onChange: ReactPhoneOnChange = (value, data, event, formattedNumber) => { + const metadata = parsePhoneNumber(value, data, formattedNumber); + const code = metadata.isoCode as ISO2Code; - /** Clear phone number when the country is selected manually */ - if (currentCode !== undefined && code !== currentCode) { - handleChange({countryCode, areaCode: null, phoneNumber: ""}, event); + if (code !== currentCode) { + /** Clear phone number when the country is selected manually */ + handleChange({...metadata, areaCode: null, phoneNumber: null}, event); setCurrentCode(code); - return; } - handleChange({countryCode, areaCode, phoneNumber}, event); + handleChange(metadata, event); + return metadata; } const onMount: ReactPhoneOnMount = (_1, _2, _3) => handleMount(value); @@ -80,8 +90,8 @@ const PhoneInput = ({ /** Static properties providing dynamic behavior */ onMount={onMount} onChange={onChange} + country={countryCode} inputClass={inputClass} - country={defaultCountry} /** Dynamic properties for customization */ {...reactPhoneInputProps} containerStyle={style} diff --git a/src/types.ts b/src/types.ts index 172e07f..8ca7af4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,7 +7,8 @@ export interface CountryData { export interface PhoneNumber { countryCode?: number | null, areaCode?: number | null, - phoneNumber?: string, + phoneNumber?: string | null, + isoCode?: string, } export interface AntInputProps { @@ -58,6 +59,10 @@ export interface ReactPhoneOnMount { (value: string, event: ChangeEvent, formattedNumber: string): void; } +export interface ParsePhoneNumber { + (value: string, data: CountryData, formattedNumber: string): PhoneNumber; +} + export interface PhoneInputProps extends AntInputProps, AntInputEventsProps, ReactPhoneInputProps, ReactPhoneEventsProps { // TODO add onValidate: https://github.com/ArtyomVancyan/antd-phone-input/issues/19 // onValidate?: (value: PhoneNumber) => boolean; From 2798444b4c83666162558193ef4f8212f626ebbd Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 16:12:33 +0400 Subject: [PATCH 11/19] Fix form initialization for FormItem --- src/index.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 4a6579c..9b59480 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -78,7 +78,12 @@ const PhoneInput = ({ return metadata; } - const onMount: ReactPhoneOnMount = (_1, _2, _3) => handleMount(value); + const onMount: ReactPhoneOnMount = (value, event, formattedNumber) => { + const metadata = parsePhoneNumber(value, {countryCode}, formattedNumber); + /** Initiates the existing value when Antd FormItem is used */ + handleChange(metadata, event); + handleMount(metadata); + } return ( Date: Thu, 2 Mar 2023 16:51:06 +0400 Subject: [PATCH 12/19] GH-15: Add about the value format --- README.md | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1fe1a42..5b9c4d2 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,41 @@ yarn add antd-phone-input ## Usage +### Antd 4.x + ```javascript import React from "react"; import PhoneInput from "antd-phone-input"; import FormItem from "antd/es/form/FormItem"; const Demo = () => { - return ( - - - - ) + return ( + + + + ) +} +``` + +### Antd 5.x + +```ascii +v5.x does not have support yet +this issue is covered in GH-20 +``` + +## Value + +The value of the component is an object containing the parts of a phone number. This format of value gives a wide range +of opportunities for handling the data in your custom way. For example, you can easily merge the parts of the phone +number into a single string. + +```json +{ + "countryCode": 1, + "areaCode": 702, + "phoneNumber": "1234567", + "isoCode": "us" } ``` From ba9d2c46f6aab738b37d6426dc70614319217983 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 17:09:39 +0400 Subject: [PATCH 13/19] Remove the returned value from onChange --- src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.tsx b/src/index.tsx index 9b59480..ccb7588 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -72,10 +72,10 @@ const PhoneInput = ({ /** Clear phone number when the country is selected manually */ handleChange({...metadata, areaCode: null, phoneNumber: null}, event); setCurrentCode(code); + return; } handleChange(metadata, event); - return metadata; } const onMount: ReactPhoneOnMount = (value, event, formattedNumber) => { From 9b8cc91adf2b8a72380fd1e5ecd876ce5c412e25 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 19:15:48 +0400 Subject: [PATCH 14/19] Polish the code before release --- README.md | 10 +++++----- src/index.tsx | 2 +- src/types.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5b9c4d2..e1598eb 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,11 @@ import PhoneInput from "antd-phone-input"; import FormItem from "antd/es/form/FormItem"; const Demo = () => { - return ( - - - - ) + return ( + + + + ) } ``` diff --git a/src/index.tsx b/src/index.tsx index ccb7588..36f005b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -40,10 +40,10 @@ const parsePhoneNumber: ParsePhoneNumber = (value, data, formattedNumber) => { } const PhoneInput = ({ + value, style, country, className, - value = {}, size = "middle", onPressEnter = () => null, onMount: handleMount = () => null, diff --git a/src/types.ts b/src/types.ts index 8ca7af4..26d5ab1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -13,7 +13,7 @@ export interface PhoneNumber { export interface AntInputProps { size?: "small" | "middle" | "large", - value?: PhoneNumber | object, + value?: PhoneNumber, style?: CSSProperties, className?: string, disabled?: boolean, @@ -56,7 +56,7 @@ export interface ReactPhoneOnChange { } export interface ReactPhoneOnMount { - (value: string, event: ChangeEvent, formattedNumber: string): void; + (value: string, event: ChangeEvent & CountryData, formattedNumber: string): void; } export interface ParsePhoneNumber { From 2cc3d4606e16ff209ea22c3ecff84f298d9d335d Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 19:24:24 +0400 Subject: [PATCH 15/19] Fix isoCode when using default value --- src/index.tsx | 6 +++--- tests/common.test.tsx | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 36f005b..19210bd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -78,10 +78,10 @@ const PhoneInput = ({ handleChange(metadata, event); } - const onMount: ReactPhoneOnMount = (value, event, formattedNumber) => { - const metadata = parsePhoneNumber(value, {countryCode}, formattedNumber); + const onMount: ReactPhoneOnMount = (rawValue, {countryCode, ...event}, formattedNumber) => { + const metadata = parsePhoneNumber(rawValue, {countryCode}, formattedNumber); /** Initiates the existing value when Antd FormItem is used */ - handleChange(metadata, event); + if (value === undefined) handleChange(metadata, event); handleMount(metadata); } diff --git a/tests/common.test.tsx b/tests/common.test.tsx index 2dc4cda..08a0c7b 100644 --- a/tests/common.test.tsx +++ b/tests/common.test.tsx @@ -1,3 +1,4 @@ +import assert from "assert"; import {render} from "@testing-library/react"; import PhoneInput from "../src"; @@ -14,10 +15,22 @@ Object.defineProperty(window, "matchMedia", { removeEventListener: jest.fn(), dispatchEvent: jest.fn(), })), -}); +}) + +describe("", () => { + it("Renders without crashing", () => { + render(); + }) -describe("PhoneNumberInput render", () => { - it("renders without crashing", () => { - render() + it("Renders with an initial value", () => { + render( { + assert(value.countryCode === 1); + assert(value.areaCode === 702); + assert(value.phoneNumber === "1234567"); + assert(value.isoCode === "us"); + }} + value={{countryCode: 1, areaCode: 702, phoneNumber: "1234567"}} + />); }) }) From aa6fddfbda3ff66d9c73c4963a09b71f22b8afda Mon Sep 17 00:00:00 2001 From: Artyom Vancyan <44609997+ArtyomVancyan@users.noreply.github.com> Date: Thu, 2 Mar 2023 21:15:35 +0400 Subject: [PATCH 16/19] GH-21: Setup CI for running tests on GHA (GH-22) --- .github/workflows/tests.yml | 18 ++++++++++++++++++ README.md | 1 + src/index.tsx | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..1e8d35a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,18 @@ +name: Tests + +on: push + +jobs: + tests: + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install dependencies + run: yarn && yarn install + + - name: Run tests + run: yarn test diff --git a/README.md b/README.md index e1598eb..92e03de 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Advanced Phone Number Input for [Ant Design](https://github.com/ant-design/ant-d [![types](https://img.shields.io/npm/types/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) [![License](https://img.shields.io/npm/l/antd-phone-input)](https://github.com/ArtyomVancyan/antd-phone-input/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com) +[![Tests](https://github.com/ArtyomVancyan/antd-phone-input/actions/workflows/tests.yml/badge.svg)](https://github.com/ArtyomVancyan/antd-phone-input/actions/workflows/tests.yml) ## Install diff --git a/src/index.tsx b/src/index.tsx index 19210bd..62dd9fc 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,7 +15,7 @@ type Timezone = keyof typeof timezones; const getDefaultISO2Code = () => { /** Returns the default ISO2 code based on the user's timezone */ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone as Timezone; - return timezones[timezone].toLowerCase() || "us"; + return (timezones[timezone] || "").toLowerCase() || "us"; } const parsePhoneNumber: ParsePhoneNumber = (value, data, formattedNumber) => { From 8463802b94ad6fa2ecc71858f5ab00ea2c7f1c23 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 23:31:15 +0400 Subject: [PATCH 17/19] Add two more tests for functionality check --- package.json | 1 + tests/common.test.tsx | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 68e25e8..a39ca6b 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-typescript": "^11.0.0", "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.4.3", "@types/jest": "^29.4.0", "@types/node": "^18.14.1", "@types/react": "^18.0.28", diff --git a/tests/common.test.tsx b/tests/common.test.tsx index 08a0c7b..f4cdd08 100644 --- a/tests/common.test.tsx +++ b/tests/common.test.tsx @@ -1,5 +1,9 @@ import assert from "assert"; -import {render} from "@testing-library/react"; +import Form from "antd/lib/form"; +import Button from "antd/lib/button"; +import FormItem from "antd/lib/form/FormItem"; +import userEvent from "@testing-library/user-event"; +import {render, screen} from "@testing-library/react"; import PhoneInput from "../src"; @@ -17,7 +21,7 @@ Object.defineProperty(window, "matchMedia", { })), }) -describe("", () => { +describe("Checks the basic rendering and functionality", () => { it("Renders without crashing", () => { render(); }) @@ -33,4 +37,34 @@ describe("", () => { value={{countryCode: 1, areaCode: 702, phoneNumber: "1234567"}} />); }) + + it("Checks the component on user input", async () => { + render( { + assert(value.isoCode === "us"); + }} + country="us" + />); + const input = screen.getByDisplayValue("+1"); + await userEvent.type(input, "702123456789"); + assert(input.getAttribute("value") === "+1 (702) 123 4567"); + }) + + it("Uses the input with FormItem", async () => { + render(
{ + assert(phone.countryCode === 1); + assert(phone.areaCode === 702); + assert(phone.phoneNumber === "1234567"); + assert(phone.isoCode === "us"); + }}> + + + + +
); + const input = screen.getByDisplayValue("+1"); + await userEvent.type(input, "702123456789"); + assert(input.getAttribute("value") === "+1 (702) 123 4567"); + screen.getByTestId("button").click(); + }) }) From 40e8042b9c91929b03a1801d9bb5e7408d254eaf Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 23:32:46 +0400 Subject: [PATCH 18/19] Upgrade the version --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a39ca6b..8fb3ded 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "version": "0.1.0", "name": "antd-phone-input", "version": "0.0.2", "main": "dist/index.cjs.js", From 5aceb4a870972b2ee5ec325009e1dd339c94d454 Mon Sep 17 00:00:00 2001 From: Artyom Vancyan Date: Thu, 2 Mar 2023 23:38:01 +0400 Subject: [PATCH 19/19] Add the project metadata for npm --- package.json | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8fb3ded..3a10d78 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,26 @@ { "version": "0.1.0", "name": "antd-phone-input", - "version": "0.0.2", + "description": "Advanced Phone Number Input for Ant Design", + "keywords": [ + "ant", + "antd", + "react", + "design", + "frontend", + "component", + "components", + "phone-input", + "phone-number" + ], + "homepage": "https://github.com/ArtyomVancyan/antd-phone-input", + "bugs": { + "url": "https://github.com/ArtyomVancyan/antd-phone-input/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/ArtyomVancyan/antd-phone-input" + }, "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts",