├── .github └── workflows │ ├── playground.yml │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── babel.config.js ├── examples ├── README.md ├── antd4.x │ ├── craco.config.ts │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Demo.tsx │ │ ├── index.tsx │ │ └── themes │ │ │ ├── Dark.tsx │ │ │ └── Light.tsx │ └── tsconfig.json ├── antd5.x-next │ ├── .gitignore │ ├── next-env.d.ts │ ├── next.config.mjs │ ├── package.json │ ├── src │ │ └── app │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ └── tsconfig.json ├── antd5.x │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── Demo.tsx │ │ └── index.tsx │ └── tsconfig.json └── deploy.sh ├── jestconfig.json ├── package.json ├── scripts ├── prepare-package.ts └── tsconfig.json ├── src ├── index.tsx ├── locale.ts ├── resources │ └── stylesheet.json ├── styles.ts └── types.ts ├── tests └── antd.test.tsx ├── tox.ini └── tsconfig.json /.github/workflows/playground.yml: -------------------------------------------------------------------------------- 1 | name: playground 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | deploy: 9 | environment: 10 | name: Playground 11 | url: https://playground.typesnippet.org/antd-phone-input-5.x 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Run deployment script on server 15 | uses: appleboy/ssh-action@master 16 | with: 17 | host: ${{ secrets.HOST }} 18 | username: ${{ secrets.USERNAME }} 19 | key: ${{ secrets.KEY_ED25519 }} 20 | port: ${{ secrets.PORT }} 21 | script: bash ~/antd-phone-input/examples/deploy.sh -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to NPM 2 | 3 | on: 4 | release: 5 | types: [ published ] 6 | 7 | jobs: 8 | deploy: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v3 15 | 16 | - name: Set up Node 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: 20.x 20 | registry-url: https://registry.npmjs.org/ 21 | 22 | - name: Install dependencies 23 | run: yarn && yarn install 24 | 25 | - name: Build and publish 26 | env: 27 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 28 | run: yarn build && yarn publish 29 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | tests: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [ 16.x, 18.x, 20.x ] 17 | antd-version: [ 424, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 5100, 5110 ] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v2 25 | 26 | - name: Install Tox 27 | run: | 28 | pip install --upgrade pip 29 | pip install tox tox-gh-actions 30 | 31 | - name: Set up Node 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: ${{ matrix.node-version }} 35 | registry-url: https://registry.npmjs.org/ 36 | 37 | - name: Install dependencies 38 | run: npm install 39 | 40 | - name: Run Tox Jest 41 | run: tox -e antd_v${{ matrix.antd-version }}_jest 42 | 43 | build: 44 | 45 | runs-on: ubuntu-latest 46 | 47 | strategy: 48 | matrix: 49 | antd-version: [ 424, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 5100, 5110 ] 50 | 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v3 54 | 55 | - name: Set up Python 56 | uses: actions/setup-python@v2 57 | 58 | - name: Install Tox 59 | run: | 60 | pip install --upgrade pip 61 | pip install tox tox-gh-actions 62 | 63 | - name: Set up Node 64 | uses: actions/setup-node@v3 65 | with: 66 | node-version: 16.x 67 | registry-url: https://registry.npmjs.org/ 68 | 69 | - name: Build and Package 70 | run: | 71 | npm install 72 | npm run build 73 | npm pack 74 | 75 | - name: Run Tox 76 | run: tox -e antd_v${{ matrix.antd-version }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node artifact files 2 | node_modules/ 3 | package-lock.json 4 | 5 | # JetBrains IDE 6 | .idea/ 7 | 8 | # Build files 9 | ./metadata/ 10 | ./styles* 11 | ./types* 12 | ./index* 13 | 14 | # Pycache 15 | __pycache__/ 16 | *.py[cod] 17 | *$py.class 18 | 19 | # Tarballs 20 | *.tgz 21 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | scripts 3 | tests -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Artyom Vancyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Antd Phone Input 2 | 3 | [![npm](https://img.shields.io/npm/v/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) 4 | [![Playground](https://img.shields.io/badge/playground-blue.svg?logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB0PSIxNzEzNTE0OTc5MTUzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE2MjciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTc2OCA1MDYuMDI2NjY3djExLjk0NjY2NmEzMi40MjY2NjcgMzIuNDI2NjY3IDAgMCAxLTE1Ljc4NjY2NyAyNy43MzMzMzRMMzcwLjM0NjY2NyA3NjhjLTIzLjA0IDEzLjY1MzMzMy0zNC45ODY2NjcgMTMuNjUzMzMzLTQ1LjIyNjY2NyA3LjY4bC0xMC42NjY2NjctNS45NzMzMzNhMzIuNDI2NjY3IDMyLjQyNjY2NyAwIDAgMS0xNS43ODY2NjYtMjYuODhWMjgxLjE3MzMzM2EzMi40MjY2NjcgMzIuNDI2NjY3IDAgMCAxIDE1Ljc4NjY2Ni0yNy43MzMzMzNsMTAuNjY2NjY3LTUuOTczMzMzYzEwLjI0LTUuOTczMzMzIDIyLjE4NjY2Ny01Ljk3MzMzMyA1Mi4wNTMzMzMgMTEuNTJsMzc1LjA0IDIxOS4zMDY2NjZhMzIuNDI2NjY3IDMyLjQyNjY2NyAwIDAgMSAxNS43ODY2NjcgMjcuNzMzMzM0eiIgcC1pZD0iMTYyOCIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC5zZWFyY2hfaW5kZXguMC5pMS40NzE5M2E4MVdiYjYyWiIgY2xhc3M9IiIgZmlsbD0iI2ZmZmZmZiI+PC9wYXRoPjwvc3ZnPg==)](https://playground.typesnippet.org/antd-phone-input-5.x) 5 | [![antd](https://img.shields.io/badge/antd-4.x%20%7C%205.x-blue)](https://github.com/ant-design/ant-design) 6 | [![types](https://img.shields.io/npm/types/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input) 7 | [![License](https://img.shields.io/npm/l/antd-phone-input)](https://github.com/typesnippet/antd-phone-input/blob/master/LICENSE) 8 | [![Tests](https://github.com/typesnippet/antd-phone-input/actions/workflows/tests.yml/badge.svg)](https://github.com/typesnippet/antd-phone-input/actions/workflows/tests.yml) 9 | 10 |

Advanced phone input component for Material UI that leverages the react-phone-hooks supporting all countries. The package is compatible with antd 4 and 5 versions. It provides built-in support for area codes and strict validation.

11 | 12 |

13 | 14 | Antd Phone Input 15 | 16 |

17 | 18 | ## Value 19 | 20 | The value of the component is an object containing the parts of the phone number. This format of value gives a wide range of opportunities for handling the data in your desired way. 21 | 22 | ```javascript 23 | { 24 | countryCode: 1, 25 | areaCode: "702", 26 | phoneNumber: "1234567", 27 | isoCode: "us", 28 | valid: function valid(strict) 29 | } 30 | ``` 31 | 32 | ## Validation 33 | 34 | The validation is checked by the `valid` function of the value object that returns a boolean value. An example with the [react-hook-form](https://www.npmjs.com/package/react-hook-form) is shown [here](https://github.com/typesnippet/antd-phone-input/issues/64#issuecomment-1855867861). 35 | 36 | ```javascript 37 | import React from "react"; 38 | import PhoneInput from "antd-phone-input"; 39 | import FormItem from "antd/es/form/FormItem"; 40 | 41 | const validator = (_, {valid}) => { 42 | // if (valid(true)) return Promise.resolve(); // strict validation 43 | if (valid()) return Promise.resolve(); // non-strict validation 44 | return Promise.reject("Invalid phone number"); 45 | } 46 | 47 | const Demo = () => { 48 | return ( 49 | 50 | 51 | 52 | ) 53 | } 54 | 55 | export default Demo; 56 | ``` 57 | 58 | The `valid` function primarily checks if a phone number has a length appropriate for its specified country. In addition, a more comprehensive validation can be performed, including verifying the dial and area codes' accuracy for the selected country. To activate the strict validation, pass `true` as the first argument to the `valid` function. 59 | 60 | ## Localization 61 | 62 | The package provides a built-in localization feature that allows you to change the language of the component. The `locale` function returns the language object that can be passed to the `ConfigProvider` component of Ant Design. 63 | 64 | ```javascript 65 | import PhoneInput, {locale} from "antd-phone-input"; 66 | 67 | 68 | 69 | 70 | ``` 71 | 72 | NOTE: If you use localization in the [documented](https://ant.design/docs/react/i18n) way, you should replace the object passed to the `locale` property with the `locale` function, specifying the desired language code. 73 | 74 | ## Props 75 | 76 | Apart from the phone-specific properties described below, all [Input](https://ant.design/components/input#input) properties supported by the used Ant Design version can be applied to the phone input component. 77 | 78 | | Property | Description | Type | 79 | |--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------| 80 | | value | An object containing a parsed phone number or the raw number. This also applies to the `initialValue` property of [Form.Item](https://ant.design/components/form#formitem). | [object](#value) / string | 81 | | country | Country code to be selected by default. By default, it will show the flag of the user's country. | string | 82 | | distinct | Show the distinct list of country codes not listing all area codes. | boolean | 83 | | enableArrow | Shows an arrow next to the country flag. Default value is `false`. | boolean | 84 | | enableSearch | Enables search in the country selection dropdown menu. Default value is `false`. | boolean | 85 | | searchNotFound | The value is shown if `enableSearch` is `true` and the query does not match any country. Default value is `No country found`. | string | 86 | | searchPlaceholder | The value is shown if `enableSearch` is `true`. Default value is `Search country`. | string | 87 | | disableDropdown | Disables the manual country selection through the dropdown menu. | boolean | 88 | | disableParentheses | Disables parentheses from the input masks. Default enabled. | boolean | 89 | | onlyCountries | Country codes to be included in the list. E.g. `onlyCountries={['us', 'ca', 'uk']}`. | string[] | 90 | | excludeCountries | Country codes to be excluded from the list of countries. E.g. `excludeCountries={['us', 'ca', 'uk']}`. | string[] | 91 | | preferredCountries | Country codes to be at the top of the list. E.g. `preferredCountries={['us', 'ca', 'uk']}`. | string[] | 92 | | dropdownRender | Customize the dropdown menu content. | (menu: ReactNode) => ReactNode | 93 | | onChange | The only difference from the original `onChange` is that value comes first. | function(value, event) | 94 | | onMount | The callback is triggered once the component gets mounted. | function(value) | 95 | 96 | ## Contribute 97 | 98 | Any contribution is welcome. Don't hesitate to open an issue or discussion if you have questions about your project's usage and integration. For ideas or suggestions, please open a pull request. Your name will shine on our contributors' list. Be proud of what you build! 99 | 100 | ## License 101 | 102 | Copyright (C) 2023 Artyom Vancyan. [MIT](LICENSE) 103 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | targets: { 7 | node: "current", 8 | }, 9 | }, 10 | ], 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | antd4.x and antd5.x examples allow you to play with the phone input with different numbers and preview them. They also 4 | allow you to switch between light and dark themes and test them out. 5 | 6 | ## Running the examples 7 | 8 | For running the antd4.x example, please use node v14 or below (npm <= v6). 9 | 10 | ```bash 11 | cd examples/antd4.x 12 | nvm use 14 13 | npm install 14 | npm start 15 | ``` 16 | 17 | And for running the antd5.x example, please use node v14 or above (npm >= v6). 18 | 19 | ```bash 20 | cd examples/antd5.x 21 | nvm use 16 # this is optional 22 | npm install 23 | npm start 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/antd4.x/craco.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: [ 3 | { 4 | plugin: require("craco-less"), 5 | options: { 6 | lessLoaderOptions: { 7 | lessOptions: { 8 | javascriptEnabled: true, 9 | }, 10 | }, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /examples/antd4.x/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@craco/craco": "^7.0.0", 7 | "@types/react": "^18.2.0", 8 | "@types/react-dom": "^18.2.0", 9 | "antd": "^4.24.8", 10 | "antd-phone-input": "^0.3.13", 11 | "craco-less": "^2.0.0", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-scripts": "^5.0.1", 15 | "typescript": "^4.9.5" 16 | }, 17 | "scripts": { 18 | "start": "craco start", 19 | "build": "craco build" 20 | }, 21 | "eslintConfig": { 22 | "extends": [ 23 | "react-app" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/antd4.x/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Playground 4.x 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/antd4.x/src/Demo.tsx: -------------------------------------------------------------------------------- 1 | import {useCallback, useMemo, useState} from "react"; 2 | import copy from "copy-to-clipboard"; 3 | import Form from "antd/es/form"; 4 | import Alert from "antd/es/alert"; 5 | import Button from "antd/es/button"; 6 | import Switch from "antd/es/switch"; 7 | import Card from "antd/es/card/Card"; 8 | import Divider from "antd/es/divider"; 9 | import {useForm} from "antd/es/form/Form"; 10 | import PhoneInput from "antd-phone-input"; 11 | import FormItem from "antd/es/form/FormItem"; 12 | import Title from "antd/es/typography/Title"; 13 | import Paragraph from "antd/es/typography/Paragraph"; 14 | import SunOutlined from "@ant-design/icons/SunOutlined"; 15 | import MoonOutlined from "@ant-design/icons/MoonOutlined"; 16 | import CopyOutlined from "@ant-design/icons/CopyOutlined"; 17 | import CheckOutlined from "@ant-design/icons/CheckOutlined"; 18 | 19 | const Demo = () => { 20 | const [form] = useForm(); 21 | const [value, setValue] = useState(null); 22 | const [arrow, setArrow] = useState(false); 23 | const [strict, setStrict] = useState(false); 24 | const [search, setSearch] = useState(false); 25 | const [copied, setCopied] = useState(false); 26 | const [dropdown, setDropdown] = useState(true); 27 | const [distinct, setDistinct] = useState(false); 28 | const [disabled, setDisabled] = useState(false); 29 | const [parentheses, setParentheses] = useState(true); 30 | 31 | const validator = useCallback((_: any, {valid}: any) => { 32 | if (valid(strict)) return Promise.resolve(); 33 | return Promise.reject("Invalid phone number"); 34 | }, [strict]) 35 | 36 | const code = useMemo(() => { 37 | let code = " { 50 | const pathname = window.location.pathname.replace(/\/$/, ''); 51 | if (pathname.endsWith("/dark")) { 52 | window.location.replace(pathname.replace('/dark', '')); 53 | } else { 54 | window.location.replace(pathname + "/dark"); 55 | } 56 | } 57 | 58 | const handleFinish = ({phone}: any) => setValue(phone); 59 | 60 | return ( 61 | 68 |
75 | 76 | Ant Phone Input Playground 77 | 78 | 79 | This is a playground for the Ant Phone Input component. You can change the settings and see how 80 | the component behaves. Also, see the code for the component and the value it returns. 81 | 82 | Settings 83 |
84 | 85 | setDropdown(!dropdown)} 90 | /> 91 | 92 | 93 | setParentheses(!parentheses)} 98 | /> 99 | 100 |
101 |
102 | 103 | setSearch(!search)} 108 | /> 109 | 110 | 111 | setArrow(!arrow)} 115 | /> 116 | 117 |
118 |
119 | 120 | } 123 | unCheckedChildren={} 124 | defaultChecked={window.location.pathname.endsWith("/dark")} 125 | /> 126 | 127 | 128 | setStrict(!strict)} 132 | /> 133 | 134 |
135 |
136 | 137 | setDisabled(!disabled)}/> 138 | 139 | 140 | setDistinct(!distinct)}/> 141 | 142 |
143 | Code 144 |
145 |
164 | Component 165 |
166 | 167 | 175 | 176 | {value && ( 177 |
182 |                             {JSON.stringify(value, null, 2)}
183 |                         
184 | )} 185 |
186 | 187 | 188 |
189 |
190 | 194 | If your application uses 5.x version of Ant Design, you should use this  195 | playground  197 | server to test the component. 198 | } 199 | /> 200 |
201 | 202 |
207 |
208 | © Made with ❤️ by  209 | 210 | TypeSnippet 211 | 212 |
213 | 222 |
223 | 224 | Find the  225 | 227 | source code 228 | 229 |  of this playground server on our GitHub repo. 230 | 231 |
232 |
233 |
234 | ) 235 | } 236 | 237 | export default Demo; 238 | -------------------------------------------------------------------------------- /examples/antd4.x/src/index.tsx: -------------------------------------------------------------------------------- 1 | import {lazy} from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | 4 | const Light = lazy(() => import("./themes/Light")); 5 | const Dark = lazy(() => import("./themes/Dark")); 6 | 7 | const App = () => { 8 | return window.location.pathname.endsWith("/dark") ? : ; 9 | } 10 | 11 | const elem = document.getElementById("root"); 12 | const root = ReactDOM.createRoot(elem as Element); 13 | root.render(); 14 | -------------------------------------------------------------------------------- /examples/antd4.x/src/themes/Dark.tsx: -------------------------------------------------------------------------------- 1 | import "antd/dist/antd.dark.less"; 2 | import Demo from "../Demo"; 3 | 4 | export default Demo; 5 | -------------------------------------------------------------------------------- /examples/antd4.x/src/themes/Light.tsx: -------------------------------------------------------------------------------- 1 | import "antd/dist/antd.less"; 2 | import Demo from "../Demo"; 3 | 4 | export default Demo; 5 | -------------------------------------------------------------------------------- /examples/antd4.x/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "noFallthroughCasesInSwitch": true, 5 | "noImplicitAny": true, 6 | "noImplicitOverride": true, 7 | "noImplicitReturns": false, 8 | "noImplicitThis": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "strict": true, 12 | "strictBindCallApply": true, 13 | "strictFunctionTypes": true, 14 | "strictNullChecks": true, 15 | "strictPropertyInitialization": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "outDir": "dist", 20 | "allowSyntheticDefaultImports": true, 21 | "esModuleInterop": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "isolatedModules": true, 24 | "jsx": "react-jsx", 25 | "lib": [ 26 | "esnext", 27 | "dom" 28 | ], 29 | "target": "es6" 30 | }, 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/antd5.x-next/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | *-lock.json 6 | *.lock 7 | 8 | # next.js 9 | /.next/ 10 | 11 | # production 12 | /build 13 | -------------------------------------------------------------------------------- /examples/antd5.x-next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /examples/antd5.x-next/next.config.mjs: -------------------------------------------------------------------------------- 1 | const nextConfig = {}; 2 | 3 | export default nextConfig; 4 | -------------------------------------------------------------------------------- /examples/antd5.x-next/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antd5.x-next", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start" 9 | }, 10 | "dependencies": { 11 | "antd": "^5.21.3", 12 | "antd-phone-input": "^0.3.10", 13 | "next": "^13.1.1", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^18", 19 | "@types/react": "^18.3.1", 20 | "@types/react-dom": "^18.3.1", 21 | "typescript": "^5" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/antd5.x-next/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import {ReactNode} from "react"; 2 | 3 | export default function RootLayout({children}: Readonly<{ children: ReactNode }>) { 4 | return ( 5 | 6 | 7 | {children} 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /examples/antd5.x-next/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import dynamic from "next/dynamic"; 2 | 3 | const PhoneInput = dynamic(() => import("antd-phone-input"), {ssr: false}); 4 | 5 | export default function Home() { 6 | return ; 7 | } 8 | -------------------------------------------------------------------------------- /examples/antd5.x-next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "incremental": true, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ] 24 | }, 25 | "include": [ 26 | "**/*.ts", 27 | "**/*.tsx", 28 | ".next/types/**/*.ts" 29 | ], 30 | "exclude": [ 31 | "node_modules" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/antd5.x/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@types/react": "^18.2.0", 7 | "@types/react-dom": "^18.2.0", 8 | "antd": "^5.8.3", 9 | "antd-phone-input": "^0.3.13", 10 | "copy-to-clipboard": "^3.3.3", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "^5.0.1", 14 | "typescript": "^4.9.5" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app" 23 | ] 24 | }, 25 | "browserslist": { 26 | "production": [ 27 | ">0.2%", 28 | "not dead", 29 | "not op_mini all" 30 | ], 31 | "development": [ 32 | "last 1 chrome version", 33 | "last 1 firefox version", 34 | "last 1 safari version" 35 | ] 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/antd5.x/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Playground 5.x 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/antd5.x/src/Demo.tsx: -------------------------------------------------------------------------------- 1 | import {useCallback, useMemo, useState} from "react"; 2 | import copy from "copy-to-clipboard"; 3 | import Form from "antd/es/form"; 4 | import theme from "antd/es/theme"; 5 | import Button from "antd/es/button"; 6 | import Switch from "antd/es/switch"; 7 | import Card from "antd/es/card/Card"; 8 | import Divider from "antd/es/divider"; 9 | import Alert from "antd/es/alert/Alert"; 10 | import {useForm} from "antd/es/form/Form"; 11 | import PhoneInput from "antd-phone-input"; 12 | import FormItem from "antd/es/form/FormItem"; 13 | import Title from "antd/es/typography/Title"; 14 | import Paragraph from "antd/es/typography/Paragraph"; 15 | import ConfigProvider from "antd/es/config-provider"; 16 | import SunOutlined from "@ant-design/icons/SunOutlined"; 17 | import MoonOutlined from "@ant-design/icons/MoonOutlined"; 18 | import CopyOutlined from "@ant-design/icons/CopyOutlined"; 19 | import CheckOutlined from "@ant-design/icons/CheckOutlined"; 20 | 21 | import "antd/dist/reset.css"; 22 | 23 | const Demo = () => { 24 | const [form] = useForm(); 25 | const [value, setValue] = useState(null); 26 | const [arrow, setArrow] = useState(false); 27 | const [strict, setStrict] = useState(false); 28 | const [search, setSearch] = useState(false); 29 | const [copied, setCopied] = useState(false); 30 | const [dropdown, setDropdown] = useState(true); 31 | const [distinct, setDistinct] = useState(false); 32 | const [disabled, setDisabled] = useState(false); 33 | const [parentheses, setParentheses] = useState(true); 34 | const [algorithm, setAlgorithm] = useState("defaultAlgorithm"); 35 | 36 | const validator = useCallback((_: any, {valid}: any) => { 37 | if (valid(strict)) return Promise.resolve(); 38 | return Promise.reject("Invalid phone number"); 39 | }, [strict]) 40 | 41 | const code = useMemo(() => { 42 | let code = " { 55 | if (algorithm === "defaultAlgorithm") { 56 | setAlgorithm("darkAlgorithm"); 57 | } else { 58 | setAlgorithm("defaultAlgorithm"); 59 | } 60 | } 61 | 62 | const handleFinish = ({phone}: any) => setValue(phone); 63 | 64 | return ( 65 | 67 | 74 |
81 | 82 | Ant Phone Input Playground 83 | 84 | 85 | This is a playground for the Ant Phone Input component. You can change the settings and see how 86 | the component behaves. Also, see the code for the component and the value it returns. 87 | 88 | Settings 89 |
90 | 91 | setDropdown(!dropdown)} 96 | /> 97 | 98 | 99 | setParentheses(!parentheses)} 104 | /> 105 | 106 |
107 |
108 | 109 | setSearch(!search)} 114 | /> 115 | 116 | 117 | setArrow(!arrow)} 121 | /> 122 | 123 |
124 |
125 | 126 | } 129 | unCheckedChildren={} 130 | /> 131 | 132 | 133 | setStrict(!strict)} 137 | /> 138 | 139 |
140 |
141 | 142 | setDistinct(!distinct)}/> 143 | 144 | 145 | setDisabled(!disabled)}/> 146 | 147 |
148 | Code 149 |
150 |
169 | Component 170 |
171 | 172 | 180 | 181 | {value && ( 182 |
187 |                                 {JSON.stringify(value, null, 2)}
188 |                             
189 | )} 190 |
191 | 192 | 193 |
194 |
195 | 199 | If your application uses 4.x version of Ant Design, you should use this  200 | playground  202 | server to test the component. 203 | } 204 | /> 205 |
206 | 207 |
212 |
213 | © Made with ❤️ by  214 | 215 | TypeSnippet 216 | 217 |
218 | 227 |
228 | 229 | Find the  230 | 232 | source code 233 | 234 |  of this playground server on our GitHub repo. 235 | 236 |
237 |
238 |
239 |
240 | ) 241 | } 242 | 243 | export default Demo; 244 | -------------------------------------------------------------------------------- /examples/antd5.x/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | 3 | import Demo from "./Demo"; 4 | 5 | const elem = document.getElementById("root"); 6 | const root = ReactDOM.createRoot(elem as Element); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /examples/antd5.x/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "alwaysStrict": true, 4 | "noFallthroughCasesInSwitch": true, 5 | "noImplicitAny": true, 6 | "noImplicitOverride": true, 7 | "noImplicitReturns": false, 8 | "noImplicitThis": true, 9 | "noUnusedLocals": true, 10 | "noUnusedParameters": true, 11 | "strict": true, 12 | "strictBindCallApply": true, 13 | "strictFunctionTypes": true, 14 | "strictNullChecks": true, 15 | "strictPropertyInitialization": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "outDir": "dist", 20 | "allowSyntheticDefaultImports": true, 21 | "esModuleInterop": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "isolatedModules": true, 24 | "jsx": "react-jsx", 25 | "lib": [ 26 | "esnext", 27 | "dom" 28 | ], 29 | "target": "es6" 30 | }, 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd ~/antd-phone-input/ 3 | git restore . 4 | git pull 5 | 6 | cd ~/antd-phone-input/examples/antd4.x 7 | rm -r node_modules package-lock.json 8 | npm install --legacy-peer-deps && npm run build 9 | 10 | cd ~/antd-phone-input/examples/antd5.x 11 | rm -r node_modules package-lock.json 12 | npm install && npm run build 13 | 14 | sudo rm -r /var/www/playground/antd-phone-input/* 15 | sudo mkdir /var/www/playground/antd-phone-input/antd4.x 16 | sudo mkdir /var/www/playground/antd-phone-input/antd5.x 17 | sudo cp -r ~/antd-phone-input/examples/antd4.x/build/* /var/www/playground/antd-phone-input/antd4.x 18 | sudo cp -r ~/antd-phone-input/examples/antd5.x/build/* /var/www/playground/antd-phone-input/antd5.x 19 | 20 | sudo service nginx restart 21 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.tsx?$": "ts-jest", 4 | "^.+\\.jsx?$": "babel-jest" 5 | }, 6 | "testRegex": "/tests/.*\\.test\\.(tsx?)$", 7 | "moduleNameMapper": { 8 | "^antd/es/(.+)$": "antd/lib/$1" 9 | }, 10 | "transformIgnorePatterns": [ 11 | "/node_modules/(?!(react-phone-hooks)/)" 12 | ], 13 | "modulePathIgnorePatterns": [ 14 | "/examples" 15 | ], 16 | "moduleFileExtensions": [ 17 | "ts", 18 | "tsx", 19 | "js", 20 | "jsx", 21 | "json", 22 | "node" 23 | ], 24 | "testEnvironment": "jsdom" 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14", 3 | "name": "antd-phone-input", 4 | "description": "Advanced, highly customizable phone input component for Ant Design.", 5 | "keywords": [ 6 | "ant", 7 | "antd", 8 | "react", 9 | "phone", 10 | "input", 11 | "number", 12 | "design", 13 | "advanced", 14 | "component", 15 | "ant-design", 16 | "customizable", 17 | "phone-number" 18 | ], 19 | "homepage": "https://github.com/typesnippet/antd-phone-input", 20 | "bugs": { 21 | "url": "https://github.com/typesnippet/antd-phone-input/issues" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "https://github.com/typesnippet/antd-phone-input" 26 | }, 27 | "exports": { 28 | ".": { 29 | "import": "./index.js", 30 | "require": "./index.cjs.js", 31 | "types": { 32 | "default": "./index.d.ts" 33 | } 34 | }, 35 | "./types": { 36 | "import": "./types.js", 37 | "require": "./types.cjs.js", 38 | "types": { 39 | "default": "./types.d.ts" 40 | } 41 | }, 42 | "./styles": { 43 | "import": "./styles.js", 44 | "require": "./styles.cjs.js", 45 | "types": { 46 | "default": "./styles.d.ts" 47 | } 48 | }, 49 | "./locale": { 50 | "import": "./locale.js", 51 | "require": "./locale.cjs.js", 52 | "types": { 53 | "default": "./locale.d.ts" 54 | } 55 | }, 56 | "./package.json": "./package.json" 57 | }, 58 | "files": [ 59 | "index*", 60 | "types*", 61 | "styles*", 62 | "locale*", 63 | "LICENSE", 64 | "resources", 65 | "README.md" 66 | ], 67 | "scripts": { 68 | "rename": "bash -c 'for file in *.js; do mv $file \"${file%.js}.$0.js\"; done'", 69 | "build": "tsc --module commonjs && npm run rename -- cjs && tsc --declaration", 70 | "prebuild": "rm -r resources index* locale* types* styles* || true", 71 | "postpack": "tsx scripts/prepare-package.ts", 72 | "test": "jest --config jestconfig.json", 73 | "postbuild": "cp -r src/resources ." 74 | }, 75 | "license": "MIT", 76 | "peerDependencies": { 77 | "antd": ">=4", 78 | "react": ">=16" 79 | }, 80 | "dependencies": { 81 | "react-phone-hooks": "^0.1.14" 82 | }, 83 | "devDependencies": { 84 | "@babel/core": "^7.26.0", 85 | "@babel/preset-env": "^7.26.0", 86 | "@testing-library/react": "^14.0.0", 87 | "@testing-library/user-event": "^14.5.1", 88 | "@types/jest": "^29.5.7", 89 | "@types/react": "^18.2.34", 90 | "antd": "*", 91 | "babel-jest": "^29.7.0", 92 | "jest": "^29.7.0", 93 | "jest-environment-jsdom": "^29.7.0", 94 | "react": "^18.0.0", 95 | "react-dom": "^18.0.0", 96 | "ts-jest": "^29.1.1", 97 | "tslib": "^2.6.2", 98 | "tsx": "^3.12.10", 99 | "typescript": "^5.2.2" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /scripts/prepare-package.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | const packageJson = JSON.parse(fs.readFileSync("package.json", "utf8")); 4 | const version = packageJson.version; 5 | const name = packageJson.name; 6 | 7 | for (const pkgFile of ["examples/antd4.x/package.json", "examples/antd5.x/package.json"]) { 8 | const packageJson = JSON.parse(fs.readFileSync(pkgFile, "utf8")); 9 | packageJson.dependencies[name] = `file:../../${name}-${version}.tgz`; 10 | fs.writeFileSync(pkgFile, JSON.stringify(packageJson, null, 2)); 11 | } 12 | -------------------------------------------------------------------------------- /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noUnusedLocals": true, 4 | "noUnusedParameters": true, 5 | "resolveJsonModule": true, 6 | "esModuleInterop": true, 7 | "lib": [ 8 | "esnext" 9 | ] 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | ChangeEvent, 5 | forwardRef, 6 | KeyboardEvent, 7 | useCallback, 8 | useContext, 9 | useEffect, 10 | useMemo, 11 | useRef, 12 | useState 13 | } from "react"; 14 | import useFormInstance from "antd/es/form/hooks/useFormInstance"; 15 | import {ConfigContext} from "antd/es/config-provider"; 16 | import {FormContext} from "antd/es/form/context"; 17 | import {useWatch} from "antd/es/form/Form"; 18 | import version from "antd/es/version"; 19 | import Select from "antd/es/select"; 20 | import Input from "antd/es/input"; 21 | 22 | import { 23 | checkValidity, 24 | cleanInput, 25 | displayFormat, 26 | getCountry, 27 | getDefaultISO2Code, 28 | getFormattedNumber, 29 | getMetadata, 30 | getRawValue, 31 | parsePhoneNumber, 32 | useMask, 33 | usePhone, 34 | } from "react-phone-hooks"; 35 | 36 | import locale from "./locale"; 37 | import {injectMergedStyles} from "./styles"; 38 | import {PhoneInputProps, PhoneNumber} from "./types"; 39 | 40 | const [major, minor, _] = version.split(".").map(Number); 41 | const isV5x = major === 5; 42 | const isV5x25 = isV5x && minor >= 25; 43 | 44 | const PhoneInput = forwardRef(({ 45 | value: initialValue = "", 46 | country = getDefaultISO2Code(), 47 | distinct = false, 48 | disabled = false, 49 | enableArrow = false, 50 | enableSearch = false, 51 | disableDropdown = false, 52 | disableParentheses = false, 53 | onlyCountries = [], 54 | excludeCountries = [], 55 | preferredCountries = [], 56 | searchNotFound: defaultSearchNotFound = "No country found", 57 | searchPlaceholder: defaultSearchPlaceholder = "Search country", 58 | dropdownRender = (node) => node, 59 | onMount: handleMount = () => null, 60 | onInput: handleInput = () => null, 61 | onChange: handleChange = () => null, 62 | onKeyDown: handleKeyDown = () => null, 63 | ...antInputProps 64 | }: PhoneInputProps, forwardedRef: any) => { 65 | const formInstance = useFormInstance(); 66 | const {locale = {}, getPrefixCls} = useContext(ConfigContext); 67 | const formContext = useContext(FormContext); 68 | const inputRef = useRef(null); 69 | const searchRef = useRef(null); 70 | const selectedRef = useRef(false); 71 | const initiatedRef = useRef(false); 72 | const [query, setQuery] = useState(""); 73 | const [minWidth, setMinWidth] = useState(0); 74 | const [countryCode, setCountryCode] = useState(country); 75 | 76 | const { 77 | locale: localeIdentifier, 78 | searchNotFound = defaultSearchNotFound, 79 | searchPlaceholder = defaultSearchPlaceholder, 80 | countries = new Proxy({}, ({get: (_: any, prop: any) => prop})), 81 | } = (locale as any).PhoneInput || {}; 82 | 83 | const prefixCls = getPrefixCls(); 84 | injectMergedStyles(prefixCls); 85 | 86 | const { 87 | value, 88 | pattern, 89 | metadata, 90 | setValue, 91 | countriesList, 92 | } = usePhone({ 93 | query, 94 | country, 95 | distinct, 96 | countryCode, 97 | initialValue, 98 | onlyCountries, 99 | excludeCountries, 100 | preferredCountries, 101 | disableParentheses, 102 | locale: localeIdentifier, 103 | }); 104 | 105 | const { 106 | onInput: onInputMaskHandler, 107 | onKeyDown: onKeyDownMaskHandler, 108 | } = useMask(pattern); 109 | 110 | const selectValue = useMemo(() => { 111 | let metadata = getMetadata(getRawValue(value), countriesList); 112 | metadata = metadata || getCountry(countryCode as any); 113 | return ({...metadata})?.[0] + ({...metadata})?.[2]; 114 | }, [countriesList, countryCode, value]) 115 | 116 | const namePath = useMemo(() => { 117 | let path = []; 118 | let formName = (formContext as any)?.name || ""; 119 | let fieldName = (antInputProps as any)?.id || ""; 120 | if (formName) { 121 | path.push(formName); 122 | fieldName = fieldName.slice(formName.length + 1); 123 | } 124 | return path.concat(fieldName.split("_")); 125 | }, [antInputProps, formContext]) 126 | 127 | const phoneValue = useWatch(namePath, formInstance); 128 | 129 | const setFieldValue = useCallback((value: PhoneNumber) => { 130 | if (formInstance) formInstance.setFieldValue(namePath, value); 131 | }, [formInstance, namePath]) 132 | 133 | const onKeyDown = useCallback((event: KeyboardEvent) => { 134 | onKeyDownMaskHandler(event); 135 | handleKeyDown(event); 136 | }, [handleKeyDown, onKeyDownMaskHandler]) 137 | 138 | const onChange = useCallback((event: ChangeEvent) => { 139 | const formattedNumber = selectedRef.current ? event.target.value : getFormattedNumber(event.target.value, pattern); 140 | selectedRef.current = false; 141 | const phoneMetadata = parsePhoneNumber(formattedNumber, countriesList); 142 | setCountryCode(phoneMetadata.isoCode as any); 143 | setValue(formattedNumber); 144 | setQuery(""); 145 | handleChange({...phoneMetadata, valid: (strict: boolean) => checkValidity(phoneMetadata, strict)}, event); 146 | }, [countriesList, handleChange, pattern, setValue]) 147 | 148 | const onInput = useCallback((event: ChangeEvent) => { 149 | onInputMaskHandler(event); 150 | handleInput(event); 151 | }, [onInputMaskHandler, handleInput]) 152 | 153 | const onMount = useCallback((value: PhoneNumber) => { 154 | setFieldValue(value); 155 | handleMount(value); 156 | }, [handleMount, setFieldValue]) 157 | 158 | const onDropdownVisibleChange = useCallback((open: boolean) => { 159 | if (open && enableSearch) setTimeout(() => searchRef.current.focus(), 100); 160 | }, [enableSearch]) 161 | 162 | const ref = useCallback((node: any) => { 163 | [forwardedRef, inputRef].forEach((ref) => { 164 | if (typeof ref === "function") ref(node); 165 | else if (ref != null) ref.current = node; 166 | }) 167 | }, [forwardedRef]) 168 | 169 | useEffect(() => { 170 | const rawValue = getRawValue(phoneValue); 171 | const metadata = getMetadata(rawValue); 172 | // Skip if value has not been updated by `setFieldValue`. 173 | if (!metadata?.[3] || rawValue === getRawValue(value)) return; 174 | const formattedNumber = getFormattedNumber(rawValue, metadata?.[3] as string); 175 | const phoneMetadata = parsePhoneNumber(formattedNumber); 176 | setFieldValue({...phoneMetadata, valid: (strict: boolean) => checkValidity(phoneMetadata, strict)}); 177 | setCountryCode(metadata?.[0] as string); 178 | setValue(formattedNumber); 179 | }, [phoneValue, value, setFieldValue, setValue]) 180 | 181 | useEffect(() => { 182 | if (initiatedRef.current) return; 183 | initiatedRef.current = true; 184 | let initialValue = getRawValue(value); 185 | if (!initialValue.startsWith(metadata?.[2] as string)) { 186 | initialValue = metadata?.[2] as string; 187 | } 188 | const formattedNumber = getFormattedNumber(initialValue, pattern); 189 | const phoneMetadata = parsePhoneNumber(formattedNumber, countriesList); 190 | onMount({...phoneMetadata, valid: (strict: boolean) => checkValidity(phoneMetadata, strict)}); 191 | setCountryCode(phoneMetadata.isoCode as any); 192 | setValue(formattedNumber); 193 | }, [countriesList, metadata, onMount, pattern, setValue, value]) 194 | 195 | const suffixIcon = useMemo(() => { 196 | return enableArrow && ( 197 | 198 | 200 | 202 | 203 | 204 | ); 205 | }, [enableArrow]) 206 | 207 | const countriesSelect = useMemo(() => ( 208 | setQuery(target.value)} 239 | /> 240 | )} 241 | {countriesList.length ? menu : ( 242 |
{searchNotFound}
243 | )} 244 | 245 | )})} 246 | > 247 | 253 |
254 | {suffixIcon} 255 |
} 256 | /> 257 | {countriesList.map(([iso, name, dial, pattern]) => { 258 | const mask = disableParentheses ? pattern.replace(/[()]/g, "") : pattern; 259 | return ( 260 | 264 |
265 | {suffixIcon} 266 |
} 267 | children={
268 |
269 | {countries[name]} {displayFormat(mask)} 270 |
} 271 | /> 272 | ) 273 | })} 274 | 275 | ), [selectValue, suffixIcon, countryCode, query, disabled, disableParentheses, disableDropdown, onDropdownVisibleChange, minWidth, searchNotFound, countries, countriesList, setFieldValue, setValue, prefixCls, enableSearch, searchPlaceholder]) 276 | 277 | return ( 278 |
setMinWidth(node?.offsetWidth || 0)}> 280 | 291 |
292 | ) 293 | }) 294 | 295 | export default PhoneInput; 296 | export {PhoneInputProps, PhoneNumber, locale}; 297 | -------------------------------------------------------------------------------- /src/locale.ts: -------------------------------------------------------------------------------- 1 | import arEG from "antd/es/locale/ar_EG"; 2 | import bnBD from "antd/es/locale/bn_BD"; 3 | import csCZ from "antd/es/locale/cs_CZ"; 4 | import elGR from "antd/es/locale/el_GR"; 5 | import esES from "antd/es/locale/es_ES"; 6 | import faIR from "antd/es/locale/fa_IR"; 7 | import frCA from "antd/es/locale/fr_CA"; 8 | import glES from "antd/es/locale/gl_ES"; 9 | import hrHR from "antd/es/locale/hr_HR"; 10 | import idID from "antd/es/locale/id_ID"; 11 | import jaJP from "antd/es/locale/ja_JP"; 12 | import kmKH from "antd/es/locale/km_KH"; 13 | import koKR from "antd/es/locale/ko_KR"; 14 | import lvLV from "antd/es/locale/lv_LV"; 15 | import mnMN from "antd/es/locale/mn_MN"; 16 | import nbNO from "antd/es/locale/nb_NO"; 17 | import nlNL from "antd/es/locale/nl_NL"; 18 | import ptPT from "antd/es/locale/pt_PT"; 19 | import siLK from "antd/es/locale/si_LK"; 20 | import srRS from "antd/es/locale/sr_RS"; 21 | import thTH from "antd/es/locale/th_TH"; 22 | import ukUA from "antd/es/locale/uk_UA"; 23 | import viVN from "antd/es/locale/vi_VN"; 24 | import zhTW from "antd/es/locale/zh_TW"; 25 | import azAZ from "antd/es/locale/az_AZ"; 26 | import byBY from "antd/es/locale/by_BY"; 27 | import daDK from "antd/es/locale/da_DK"; 28 | import enGB from "antd/es/locale/en_GB"; 29 | import etEE from "antd/es/locale/et_EE"; 30 | import fiFI from "antd/es/locale/fi_FI"; 31 | import frFR from "antd/es/locale/fr_FR"; 32 | import heIL from "antd/es/locale/he_IL"; 33 | import huHU from "antd/es/locale/hu_HU"; 34 | import isIS from "antd/es/locale/is_IS"; 35 | import kaGE from "antd/es/locale/ka_GE"; 36 | import kmrIQ from "antd/es/locale/kmr_IQ"; 37 | import kuIQ from "antd/es/locale/ku_IQ"; 38 | import mkMK from "antd/es/locale/mk_MK"; 39 | import msMY from "antd/es/locale/ms_MY"; 40 | import neNP from "antd/es/locale/ne_NP"; 41 | import plPL from "antd/es/locale/pl_PL"; 42 | import roRO from "antd/es/locale/ro_RO"; 43 | import skSK from "antd/es/locale/sk_SK"; 44 | import svSE from "antd/es/locale/sv_SE"; 45 | import tkTK from "antd/es/locale/tk_TK"; 46 | import urPK from "antd/es/locale/ur_PK"; 47 | import zhCN from "antd/es/locale/zh_CN"; 48 | import bgBG from "antd/es/locale/bg_BG"; 49 | import caES from "antd/es/locale/ca_ES"; 50 | import deDE from "antd/es/locale/de_DE"; 51 | import enUS from "antd/es/locale/en_US"; 52 | import frBE from "antd/es/locale/fr_BE"; 53 | import gaIE from "antd/es/locale/ga_IE"; 54 | import hiIN from "antd/es/locale/hi_IN"; 55 | import hyAM from "antd/es/locale/hy_AM"; 56 | import itIT from "antd/es/locale/it_IT"; 57 | import kkKZ from "antd/es/locale/kk_KZ"; 58 | import knIN from "antd/es/locale/kn_IN"; 59 | import ltLT from "antd/es/locale/lt_LT"; 60 | import mlIN from "antd/es/locale/ml_IN"; 61 | import nlBE from "antd/es/locale/nl_BE"; 62 | import ptBR from "antd/es/locale/pt_BR"; 63 | import ruRU from "antd/es/locale/ru_RU"; 64 | import slSI from "antd/es/locale/sl_SI"; 65 | import taIN from "antd/es/locale/ta_IN"; 66 | import trTR from "antd/es/locale/tr_TR"; 67 | import zhHK from "antd/es/locale/zh_HK"; 68 | import * as phoneLocale from "react-phone-hooks/locale"; 69 | 70 | const locale = { 71 | arEG, bnBD, csCZ, elGR, esES, faIR, frCA, glES, hrHR, idID, jaJP, kmKH, koKR, lvLV, 72 | mnMN, nbNO, nlNL, ptPT, siLK, srRS, thTH, ukUA, viVN, zhTW, azAZ, byBY, daDK, enGB, 73 | etEE, fiFI, frFR, heIL, huHU, isIS, kaGE, kmrIQ, kuIQ, mkMK, msMY, neNP, plPL, roRO, 74 | skSK, svSE, tkTK, urPK, zhCN, bgBG, caES, deDE, enUS, frBE, gaIE, hiIN, hyAM, itIT, 75 | kkKZ, knIN, ltLT, mlIN, nlBE, ptBR, ruRU, slSI, taIN, trTR, zhHK, 76 | } 77 | 78 | type Locale = keyof typeof locale; 79 | 80 | export default (lang: Locale) => ({ 81 | ...locale[lang], 82 | PhoneInput: { 83 | ...(phoneLocale as any)[lang], 84 | locale: lang, 85 | }, 86 | }) 87 | -------------------------------------------------------------------------------- /src/resources/stylesheet.json: -------------------------------------------------------------------------------- 1 | { 2 | ".ant-phone-input-select-item": { 3 | "display": "flex", 4 | "column-gap": "10px", 5 | "align-items": "center" 6 | }, 7 | ".ant-phone-input-search-wrapper .ant-input": { 8 | "margin": "0 3px 6px 3px", 9 | "width": "calc(100% - 6px)" 10 | }, 11 | ".ant-phone-input-search-wrapper .ant-select-item-empty": { 12 | "margin": "0 6px 6px 6px" 13 | }, 14 | ".ant-phone-input-wrapper .ant-select-selector": { 15 | "padding": "0 11px !important", 16 | "height": "unset !important", 17 | "border": "none !important" 18 | }, 19 | ".ant-phone-input-wrapper .ant-select-selection-item": { 20 | "padding": "0 !important" 21 | }, 22 | ".ant-phone-input-wrapper .ant-input-group-addon *": { 23 | "display": "flex", 24 | "align-items": "center", 25 | "justify-content": "center" 26 | } 27 | } -------------------------------------------------------------------------------- /src/styles.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {injectStyles, jsonToCss} from "react-phone-hooks/styles"; 4 | import commonStyles from "react-phone-hooks/stylesheet.json"; 5 | import {defaultPrefixCls} from "antd/es/config-provider"; 6 | 7 | import customStyles from "./resources/stylesheet.json"; 8 | 9 | let prefix: any = null; 10 | 11 | export const injectMergedStyles = (prefixCls: any = null) => { 12 | const stylesheet = customStyles as { [key: string]: any }; 13 | if (prefixCls && prefixCls !== defaultPrefixCls) { 14 | if (prefix === prefixCls) return; 15 | Object.entries(stylesheet).forEach(([k, value]) => { 16 | const key = k.replace(/ant(?=-)/g, prefixCls); 17 | stylesheet[key] = value; 18 | delete stylesheet[k]; 19 | }) 20 | prefix = prefixCls; 21 | } 22 | return injectStyles(jsonToCss(Object.assign(commonStyles, stylesheet))); 23 | } 24 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import {ChangeEvent, KeyboardEvent, ReactNode} from "react"; 4 | import types from "react-phone-hooks/types"; 5 | import {InputProps} from "antd/es/input"; 6 | 7 | export type PhoneNumber = types.PhoneNumber; 8 | 9 | export interface PhoneInputProps extends Omit { 10 | value?: PhoneNumber | string; 11 | 12 | country?: string; 13 | 14 | distinct?: boolean; 15 | 16 | enableArrow?: boolean; 17 | 18 | enableSearch?: boolean; 19 | 20 | searchNotFound?: string; 21 | 22 | searchPlaceholder?: string; 23 | 24 | disableDropdown?: boolean; 25 | 26 | disableParentheses?: boolean; 27 | 28 | onlyCountries?: string[]; 29 | 30 | excludeCountries?: string[]; 31 | 32 | preferredCountries?: string[]; 33 | 34 | dropdownRender?: (menu: ReactNode) => ReactNode; 35 | 36 | onMount?(value: PhoneNumber): void; 37 | 38 | onInput?(event: ChangeEvent): void; 39 | 40 | onKeyDown?(event: KeyboardEvent): void; 41 | 42 | /** NOTE: This differs from the antd Input onChange interface */ 43 | onChange?(value: PhoneNumber, event: ChangeEvent): void; 44 | } 45 | -------------------------------------------------------------------------------- /tests/antd.test.tsx: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import Form from "antd/lib/form"; 3 | import Button from "antd/lib/button"; 4 | import FormItem from "antd/lib/form/FormItem"; 5 | import ConfigProvider from "antd/lib/config-provider"; 6 | import userEvent from "@testing-library/user-event"; 7 | import {act, render, screen} from "@testing-library/react"; 8 | 9 | import PhoneInput, {locale} from "../src"; 10 | 11 | Object.defineProperty(console, "warn", { 12 | value: jest.fn(), 13 | }) 14 | 15 | Object.defineProperty(window, "matchMedia", { 16 | value: jest.fn().mockImplementation((): any => ({ 17 | addListener: jest.fn(), 18 | removeListener: jest.fn(), 19 | })), 20 | }) 21 | 22 | function inputHasError(parent: any = document) { 23 | const inputGroup = parent.querySelector(".ant-input-group-wrapper"); 24 | return inputGroup.className.includes("ant-input-group-wrapper-status-error"); 25 | } 26 | 27 | describe("Checking the basic rendering and functionality", () => { 28 | it("Rendering without crashing", () => { 29 | render(); 30 | }) 31 | 32 | it("Rendering with strict raw value", () => { 33 | render(); 34 | assert(screen.getByDisplayValue("+1 (702) 123 4567")); 35 | }) 36 | 37 | it("Localization support check", async () => { 38 | const {container, getByText} = render( 39 | 40 | ); 41 | await act(async () => { 42 | await userEvent.click(container.querySelector(".flag") as any); 43 | }); 44 | assert(!!getByText(/Arménie[\S\s]+\+374/)); 45 | }) 46 | 47 | it("Rendering with an initial value", () => { 48 | render( { 50 | assert(value.countryCode === 1); 51 | assert(value.areaCode === "702"); 52 | assert(value.phoneNumber === "1234567"); 53 | assert(value.isoCode === "us"); 54 | assert(value.valid()); 55 | }} 56 | value={{countryCode: 1, areaCode: "702", phoneNumber: "1234567"}} 57 | />); 58 | assert(screen.getByDisplayValue("+1 (702) 123 4567")); 59 | }) 60 | 61 | it("Rendering with a raw initial value", () => { 62 | render(
63 | 64 | 65 | 66 |
); 67 | assert(screen.getByDisplayValue("+1 (702) 123 4567")); 68 | }) 69 | 70 | it("Checking the component on user input", async () => { 71 | render( { 73 | assert(value.isoCode === "us"); 74 | }} 75 | country="us" 76 | />); 77 | const input = screen.getByDisplayValue("+1"); 78 | await userEvent.type(input, "907123456789"); 79 | assert(input.getAttribute("value") === "+1 (907) 123 4567"); 80 | }) 81 | 82 | it("Using the input with FormItem", async () => { 83 | render(
{ 84 | assert(phone.countryCode === 1); 85 | assert(phone.areaCode === "907"); 86 | assert(phone.phoneNumber === "1234567"); 87 | assert(phone.isoCode === "us"); 88 | }}> 89 | 90 | 91 | 92 | 93 |
); 94 | const input = screen.getByDisplayValue("+1"); 95 | await userEvent.type(input, "907123456789"); 96 | assert(input.getAttribute("value") === "+1 (907) 123 4567"); 97 | screen.getByTestId("button").click(); 98 | }) 99 | 100 | it("Checking input validation with FormItem", async () => { 101 | render(
102 | { 104 | assert(valid()); 105 | return Promise.resolve(); 106 | } 107 | }]}> 108 | 109 | 110 | 111 |
); 112 | await userEvent.click(screen.getByTestId("button")); 113 | }) 114 | 115 | it("Checking form with initial value", async () => { 116 | render(
117 | 118 | 119 | 120 |
); 121 | const input = screen.getByDisplayValue("+1 (702)"); 122 | await userEvent.type(input, "1234567"); 123 | assert(input.getAttribute("value") === "+1 (702) 123 4567"); 124 | }) 125 | 126 | it("Using `prefixCls` with ConfigProvider", () => { 127 | render( 128 | 129 | ); 130 | const input = screen.getByTestId("input"); 131 | assert(!input.outerHTML.includes("ant-input")); 132 | assert(input.outerHTML.includes("custom-prefix-input")); 133 | }) 134 | 135 | it("Checking field value setters", async () => { 136 | const FormWrapper = () => { 137 | const [form] = Form.useForm(); 138 | 139 | const setFieldObjectValue = () => { 140 | form.setFieldValue("phone", { 141 | countryCode: 48, 142 | areaCode: "111", 143 | phoneNumber: "111111", 144 | isoCode: "pl" 145 | }); 146 | } 147 | 148 | const setFieldRawValue = () => { 149 | form.setFieldValue("phone", "+1 (234) 234 2342"); 150 | } 151 | 152 | return ( 153 |
154 | 155 | 156 | 157 | 158 | 159 | 160 |
161 | ) 162 | } 163 | 164 | render(); 165 | const form = screen.getByTestId("form"); 166 | const submit = screen.getByTestId("submit"); 167 | const input = screen.getByDisplayValue("+1 (702)"); 168 | const setString = screen.getByTestId("set-string"); 169 | const setObject = screen.getByTestId("set-object"); 170 | 171 | await userEvent.click(setString); 172 | await userEvent.click(submit); 173 | await act(async () => { 174 | await new Promise(r => setTimeout(r, 100)); 175 | }) 176 | assert(!inputHasError(form)); // valid 177 | assert(input.getAttribute("value") === "+1 (234) 234 2342"); 178 | 179 | await userEvent.click(setObject); 180 | await userEvent.click(submit); 181 | await act(async () => { 182 | await new Promise(r => setTimeout(r, 100)); 183 | }) 184 | assert(!inputHasError(form)); // valid 185 | assert(input.getAttribute("value") === "+48 (111) 111 111"); 186 | }) 187 | 188 | it("Checking validation with casual form actions", async () => { 189 | render(
190 | { 192 | if (valid()) return Promise.resolve(); 193 | return Promise.reject("Invalid phone number"); 194 | } 195 | }]}> 196 | 197 | 198 | 199 | 200 |
); 201 | 202 | const form = screen.getByTestId("form"); 203 | const reset = screen.getByTestId("reset"); 204 | const submit = screen.getByTestId("submit"); 205 | 206 | assert(!inputHasError(form)); // valid 207 | await userEvent.click(reset); 208 | assert(!inputHasError(form)); // valid 209 | await userEvent.click(submit); 210 | await act(async () => { 211 | await new Promise(r => setTimeout(r, 100)); 212 | }) 213 | assert(inputHasError(form)); // invalid 214 | await userEvent.click(reset); 215 | assert(!inputHasError(form)); // valid 216 | await userEvent.click(reset); 217 | assert(!inputHasError(form)); // valid 218 | await userEvent.click(submit); 219 | await act(async () => { 220 | await new Promise(r => setTimeout(r, 100)); 221 | }) 222 | assert(inputHasError(form)); // invalid 223 | await userEvent.click(submit); 224 | await act(async () => { 225 | await new Promise(r => setTimeout(r, 100)); 226 | }) 227 | assert(inputHasError(form)); // invalid 228 | }, 25000) 229 | 230 | it("Checking validation with casual inputs and actions", async () => { 231 | render(
232 | { 234 | if (valid()) return Promise.resolve(); 235 | return Promise.reject("Invalid phone number"); 236 | } 237 | }]}> 238 | 239 | 240 | 241 | 242 |
); 243 | 244 | const form = screen.getByTestId("form"); 245 | const reset = screen.getByTestId("reset"); 246 | const submit = screen.getByTestId("submit"); 247 | const input = screen.getByDisplayValue("+1"); 248 | 249 | await userEvent.type(input, "90712345"); 250 | await act(async () => { 251 | await new Promise(r => setTimeout(r, 100)); 252 | }) 253 | assert(inputHasError(form)); // invalid 254 | await userEvent.type(input, "6"); 255 | await act(async () => { 256 | await new Promise(r => setTimeout(r, 100)); 257 | }) 258 | assert(inputHasError(form)); // invalid 259 | await userEvent.type(input, "7"); 260 | await act(async () => { 261 | await new Promise(r => setTimeout(r, 100)); 262 | }) 263 | assert(!inputHasError(form)); // valid 264 | await userEvent.click(reset); 265 | assert(!inputHasError(form)); // valid 266 | await userEvent.click(submit); 267 | await act(async () => { 268 | await new Promise(r => setTimeout(r, 100)); 269 | }) 270 | assert(inputHasError(form)); // invalid 271 | await userEvent.click(reset); 272 | assert(!inputHasError(form)); // valid 273 | }, 25000) 274 | }) 275 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | antd_v424_jest 4 | antd_v424 5 | antd_v500_jest 6 | antd_v500 7 | antd_v510_jest 8 | antd_v510 9 | antd_v520_jest 10 | antd_v520 11 | antd_v530_jest 12 | antd_v530 13 | antd_v540_jest 14 | antd_v540 15 | antd_v550_jest 16 | antd_v550 17 | antd_v560_jest 18 | antd_v560 19 | antd_v570_jest 20 | antd_v570 21 | antd_v580_jest 22 | antd_v580 23 | antd_v590_jest 24 | antd_v590 25 | antd_v5100_jest 26 | antd_v5100 27 | antd_v5110_jest 28 | antd_v5110 29 | 30 | [testenv:antd_v424_jest] 31 | allowlist_externals = npm 32 | commands = 33 | npm install 34 | npm install -D antd@4.24.14 35 | npm test 36 | 37 | [testenv:antd_v424] 38 | changedir = examples/antd4.x 39 | allowlist_externals = npm 40 | commands = 41 | npm install --legacy-peer-deps 42 | npm run build 43 | 44 | [testenv:antd_v500_jest] 45 | allowlist_externals = npm 46 | commands = 47 | npm install 48 | npm install -D antd@5.0.2 49 | npm test 50 | 51 | [testenv:antd_v500] 52 | changedir = examples/antd5.x 53 | allowlist_externals = npm 54 | commands = 55 | npm install 56 | npm install antd@5.0.2 57 | npm run build 58 | 59 | [testenv:antd_v510_jest] 60 | allowlist_externals = npm 61 | commands = 62 | npm install 63 | npm install -D antd@5.1.0 64 | npm test 65 | 66 | [testenv:antd_v510] 67 | changedir = examples/antd5.x 68 | allowlist_externals = npm 69 | commands = 70 | npm install 71 | npm install antd@5.1.0 72 | npm run build 73 | 74 | [testenv:antd_v520_jest] 75 | allowlist_externals = npm 76 | commands = 77 | npm install 78 | npm install -D antd@5.2.0 79 | npm test 80 | 81 | [testenv:antd_v520] 82 | changedir = examples/antd5.x 83 | allowlist_externals = npm 84 | commands = 85 | npm install 86 | npm install antd@5.2.0 87 | npm run build 88 | 89 | [testenv:antd_v530_jest] 90 | allowlist_externals = npm 91 | commands = 92 | npm install 93 | npm install -D antd@5.3.0 94 | npm test 95 | 96 | [testenv:antd_v530] 97 | changedir = examples/antd5.x 98 | allowlist_externals = npm 99 | commands = 100 | npm install 101 | npm install antd@5.3.0 102 | npm run build 103 | 104 | [testenv:antd_v540_jest] 105 | allowlist_externals = npm 106 | commands = 107 | npm install 108 | npm install -D antd@5.4.0 109 | npm test 110 | 111 | [testenv:antd_v540] 112 | changedir = examples/antd5.x 113 | allowlist_externals = npm 114 | commands = 115 | npm install 116 | npm install antd@5.4.0 117 | npm run build 118 | 119 | [testenv:antd_v550_jest] 120 | allowlist_externals = npm 121 | commands = 122 | npm install 123 | npm install -D antd@5.5.0 124 | npm test 125 | 126 | [testenv:antd_v550] 127 | changedir = examples/antd5.x 128 | allowlist_externals = npm 129 | commands = 130 | npm install 131 | npm install antd@5.5.0 132 | npm run build 133 | 134 | [testenv:antd_v560_jest] 135 | allowlist_externals = npm 136 | commands = 137 | npm install 138 | npm install -D antd@5.6.0 139 | npm test 140 | 141 | [testenv:antd_v560] 142 | changedir = examples/antd5.x 143 | allowlist_externals = npm 144 | commands = 145 | npm install 146 | npm install antd@5.6.0 147 | npm run build 148 | 149 | [testenv:antd_v570_jest] 150 | allowlist_externals = npm 151 | commands = 152 | npm install 153 | npm install -D antd@5.7.0 154 | npm test 155 | 156 | [testenv:antd_v570] 157 | changedir = examples/antd5.x 158 | allowlist_externals = npm 159 | commands = 160 | npm install 161 | npm install antd@5.7.0 162 | npm run build 163 | 164 | [testenv:antd_v580_jest] 165 | allowlist_externals = npm 166 | commands = 167 | npm install 168 | npm install -D antd@5.8.0 169 | npm test 170 | 171 | [testenv:antd_v580] 172 | changedir = examples/antd5.x 173 | allowlist_externals = npm 174 | commands = 175 | npm install 176 | npm install antd@5.8.0 177 | npm run build 178 | 179 | [testenv:antd_v590_jest] 180 | allowlist_externals = npm 181 | commands = 182 | npm install 183 | npm install -D antd@5.9.0 184 | npm test 185 | 186 | [testenv:antd_v590] 187 | changedir = examples/antd5.x 188 | allowlist_externals = npm 189 | commands = 190 | npm install 191 | npm install antd@5.9.0 192 | npm run build 193 | 194 | [testenv:antd_v5100_jest] 195 | allowlist_externals = npm 196 | commands = 197 | npm install 198 | npm install -D antd@5.10.0 199 | npm test 200 | 201 | [testenv:antd_v5100] 202 | changedir = examples/antd5.x 203 | allowlist_externals = npm 204 | commands = 205 | npm install 206 | npm install antd@5.10.0 207 | npm run build 208 | 209 | [testenv:antd_v5110_jest] 210 | allowlist_externals = npm 211 | commands = 212 | npm install 213 | npm install -D antd@5.11.0 214 | npm test 215 | 216 | [testenv:antd_v5110] 217 | changedir = examples/antd5.x 218 | allowlist_externals = npm 219 | commands = 220 | npm install 221 | npm install antd@5.11.0 222 | npm run build 223 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noImplicitAny": true, 4 | "noUnusedLocals": true, 5 | "noUnusedParameters": true, 6 | "strictNullChecks": true, 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "ignoreDeprecations": "5.0", 10 | "resolveJsonModule": true, 11 | "outDir": ".", 12 | "esModuleInterop": true, 13 | "jsx": "react-jsx", 14 | "lib": [ 15 | "dom", 16 | "esnext" 17 | ], 18 | "target": "es6", 19 | "skipLibCheck": true, 20 | "stripInternal": true, 21 | "experimentalDecorators": true 22 | }, 23 | "exclude": [ 24 | "node_modules", 25 | "examples", 26 | "scripts", 27 | "tests" 28 | ] 29 | } 30 | --------------------------------------------------------------------------------