Skip to content

Commit 89eaaec

Browse files
Add antd 5.x support (GH-28)
2 parents 47a6b96 + 77c6764 commit 89eaaec

File tree

18 files changed

+1450
-238
lines changed

18 files changed

+1450
-238
lines changed

.github/workflows/tests.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
name: Tests
22

3-
on: push
3+
on:
4+
push:
5+
branches: [ master ]
6+
pull_request:
7+
branches: [ master ]
48

59
jobs:
610
tests:

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ package-lock.json
66
.idea/
77

88
# Build files
9-
dist/
9+
legacy/
10+
index*
1011

1112
# Tarballs
1213
*.tgz

README.md

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
# antd-phone-input
22

3-
Advanced Phone Number Input for [Ant Design](https://github.com/ant-design/ant-design).
4-
53
[![npm](https://img.shields.io/npm/v/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input)
4+
[![antd](https://img.shields.io/badge/antd-3.x%20%7C%204.x%20%7C%205.x-blue)](https://github.com/ant-design/ant-design)
65
[![types](https://img.shields.io/npm/types/antd-phone-input)](https://www.npmjs.com/package/antd-phone-input)
76
[![License](https://img.shields.io/npm/l/antd-phone-input)](https://github.com/ArtyomVancyan/antd-phone-input/blob/master/LICENSE)
87
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com)
98
[![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)
109

10+
Advanced phone input component for [Ant Design](https://github.com/ant-design/ant-design) that provides support for all
11+
countries and is compatible with [`antd`](https://github.com/ant-design/ant-design) 3 and higher versions. It has
12+
built-in support for area codes and provides validation to ensure that the entered numbers are valid. This open-source
13+
project is designed to simplify the process of collecting phone numbers from users.
14+
1115
## Install
1216

1317
```shell
@@ -20,12 +24,17 @@ yarn add antd-phone-input
2024

2125
## Usage
2226

23-
### Antd 4.x
27+
As mentioned above, this component is compatible with `3.x`, `4.x`, and after
28+
the [v0.1.4](https://github.com/ArtyomVancyan/antd-phone-input/releases/tag/v0.1.4) release, `5.x` versions of Ant
29+
Design. The latest one does not require any additional actions for loading the styles as it
30+
uses [`cssinjs`](https://github.com/ant-design/cssinjs) ecosystem.
31+
32+
### Antd 5.x
2433

2534
```javascript
2635
import React from "react";
27-
import PhoneInput from "antd-phone-input";
2836
import FormItem from "antd/es/form/FormItem";
37+
import PhoneInput from "antd-phone-input";
2938

3039
const Demo = () => {
3140
return (
@@ -36,22 +45,25 @@ const Demo = () => {
3645
}
3746
```
3847

48+
![latest](https://user-images.githubusercontent.com/44609997/227775101-72b03e76-52bc-421d-8e75-a03c9d0d6d08.png)
49+
50+
### Antd 4.x and older
51+
52+
For `4.x` and older versions, you should use the `legacy` endpoint.
53+
54+
```javascript
55+
import PhoneInput from "antd-phone-input/legacy";
56+
```
57+
3958
For including the styles, you should import them in the main `less` file after importing either
4059
the `antd/dist/antd.less` or `antd/dist/antd.dark.less` styles.
4160

4261
```diff
4362
@import "~antd/dist/antd";
44-
+ @import "~antd-phone-input/dist/style";
63+
+ @import "~antd-phone-input/legacy/style";
4564
```
4665

47-
![light-dark-screenshots](https://user-images.githubusercontent.com/44609997/222975662-a2726b5f-954f-4a93-ac28-0339b432fa72.png)
48-
49-
### Antd 5.x
50-
51-
```ascii
52-
v5.x does not have support yet
53-
this issue is covered in GH-20
54-
```
66+
![legacy](https://user-images.githubusercontent.com/44609997/227775155-9e22bc63-2148-4714-ba8a-9bb4e44c0128.png)
5567

5668
## Value
5769

jestconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"transform": {
33
"^.+\\.(t|j)sx?$": "ts-jest"
44
},
5-
"testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.([jt]sx?)$",
5+
"testRegex": "/tests/.*\\.test\\.([jt]sx?)$",
66
"moduleNameMapper": {
77
"^.+\\.((?:c|le|s[ca])ss)$": "identity-obj-proxy"
88
},

package.json

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "0.1.3",
2+
"version": "0.1.4",
33
"name": "antd-phone-input",
44
"description": "Advanced Phone Number Input for Ant Design",
55
"keywords": [
@@ -23,16 +23,36 @@
2323
"type": "git",
2424
"url": "https://github.com/ArtyomVancyan/antd-phone-input"
2525
},
26-
"main": "dist/index.cjs.js",
27-
"module": "dist/index.esm.js",
28-
"types": "dist/index.d.ts",
26+
"exports": {
27+
".": {
28+
"import": "./index.esm.js",
29+
"require": "./index.cjs.js",
30+
"types": {
31+
"default": "./index.d.ts"
32+
}
33+
},
34+
"./legacy": {
35+
"import": "./legacy/index.esm.js",
36+
"require": "./legacy/index.cjs.js",
37+
"types": {
38+
"default": "./legacy/index.d.ts"
39+
}
40+
},
41+
"./legacy/style": {
42+
"default": "./legacy/style.less"
43+
},
44+
"./package.json": "./package.json"
45+
},
2946
"files": [
30-
"dist",
31-
"LICENSE"
47+
"index*",
48+
"legacy",
49+
"LICENSE",
50+
"README.md"
3251
],
3352
"scripts": {
34-
"build": "rm -rf dist && npm run build:cjs && cp src/*.less dist/",
35-
"build:cjs": "rollup -c --configPlugin @rollup/plugin-typescript",
53+
"build": "npm run build:clean && npm run build:rollup && cp src/legacy/*.less legacy",
54+
"build:rollup": "rollup -c --configPlugin @rollup/plugin-typescript",
55+
"build:clean": "rm -r legacy index* || true",
3656
"test": "jest --config jestconfig.json"
3757
},
3858
"license": "MIT",
@@ -41,14 +61,16 @@
4161
"react": ">=16"
4262
},
4363
"devDependencies": {
64+
"@rollup/plugin-alias": "^4.0.3",
4465
"@rollup/plugin-json": "^6.0.0",
4566
"@rollup/plugin-typescript": "^11.0.0",
4667
"@testing-library/react": "^14.0.0",
4768
"@testing-library/user-event": "^14.4.3",
4869
"@types/jest": "^29.4.0",
4970
"@types/node": "^18.14.1",
5071
"@types/react": "^18.0.28",
51-
"antd": "^4.24.8",
72+
"antd": "npm:antd@^5.3.2",
73+
"antd4": "npm:antd@^4.24.8",
5274
"identity-obj-proxy": "^3.0.0",
5375
"jest": "^29.4.3",
5476
"jest-environment-jsdom": "^29.4.3",

rollup.config.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
import dts from "rollup-plugin-dts";
22
import json from "@rollup/plugin-json";
3+
import alias from "@rollup/plugin-alias";
34
import postcss from "rollup-plugin-postcss";
45
import typescript from "@rollup/plugin-typescript";
56
import {readFileSync} from "fs";
67

78
const pkg = JSON.parse(readFileSync("./package.json") as unknown as string);
89

9-
const input = "src/index.tsx";
10-
const cjsOutput = {file: pkg.main, format: "cjs", exports: "auto"};
11-
const esmOutput = {file: pkg.module, format: "es"};
12-
const dtsOutput = {file: pkg.types, format: "es"};
10+
const input4 = "src/legacy/index.tsx";
11+
const input5 = "src/index.tsx";
12+
const cjsInput4 = {file: "legacy/index.cjs.js", format: "cjs", exports: "auto"};
13+
const esmInput4 = {file: "legacy/index.esm.js", format: "es"};
14+
const dtsInput4 = {file: "legacy/index.d.ts", format: "es"};
15+
const cjsInput5 = {file: "index.cjs.js", format: "cjs", exports: "auto"};
16+
const esmInput5 = {file: "index.esm.js", format: "es"};
17+
const dtsInput5 = {file: "index.d.ts", format: "es"};
1318

1419
const jsonPlugin = json();
1520
const cssPlugin = postcss();
1621
const tsPlugin = typescript();
22+
const aliasPlugin = alias({entries: {"antd/lib": "antd/es"}});
1723

1824
const external = [
1925
...Object.keys({...pkg.dependencies, ...pkg.peerDependencies}),
2026
/^react($|\/)/,
21-
/^antd($|\/)/,
22-
/\.css$/,
27+
/^antd($|\/es\/)/,
2328
];
2429

2530
export default [
26-
{input, output: cjsOutput, plugins: [tsPlugin, jsonPlugin, cssPlugin], external},
27-
{input, output: esmOutput, plugins: [tsPlugin, jsonPlugin, cssPlugin], external},
28-
{input, output: dtsOutput, plugins: [dts()], external: [/\.css$/]},
31+
{input: input4, output: cjsInput4, plugins: [tsPlugin, jsonPlugin, cssPlugin], external},
32+
{input: input4, output: esmInput4, plugins: [tsPlugin, jsonPlugin, cssPlugin], external},
33+
{input: input4, output: dtsInput4, plugins: [dts()], external: [/\.css$/]},
34+
{input: input5, output: cjsInput5, plugins: [tsPlugin, jsonPlugin, cssPlugin, aliasPlugin], external},
35+
{input: input5, output: esmInput5, plugins: [tsPlugin, jsonPlugin, cssPlugin, aliasPlugin], external},
36+
{input: input5, output: dtsInput5, plugins: [dts()], external: [/\.css$/]},
2937
];

src/index.tsx

Lines changed: 60 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,71 @@
1-
import {useMemo, useState} from "react";
2-
import ReactPhoneInput from "react-phone-input-2";
1+
import {useContext, useEffect, useMemo} from "react";
2+
import theme from "antd/lib/theme";
3+
import genComponentStyleHook from "antd/lib/input/style";
4+
import {FormItemInputContext} from "antd/lib/form/context";
5+
import {getStatusClassNames} from "antd/lib/_util/statusUtils";
36

4-
import {ParsePhoneNumber, PhoneInputProps, ReactPhoneOnChange, ReactPhoneOnMount} from "./types";
7+
import InputLegacy from "./legacy";
8+
import {PhoneInputProps} from "./types";
59

6-
import masks from "./phoneMasks.json";
7-
import timezones from "./timezones.json";
8-
import validations from "./validations.json";
9-
10-
import "react-phone-input-2/lib/style.css";
11-
12-
type ISO2Code = keyof typeof masks;
13-
type Timezone = keyof typeof timezones;
14-
15-
const getDefaultISO2Code = () => {
16-
/** Returns the default ISO2 code based on the user's timezone */
17-
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone as Timezone;
18-
return (timezones[timezone] || "").toLowerCase() || "us";
19-
}
20-
21-
const parsePhoneNumber: ParsePhoneNumber = (value, data, formattedNumber) => {
22-
const isoCode = data?.countryCode;
23-
const countryCodePattern = /\+\d+/;
24-
const areaCodePattern = /\((\d+)\)/;
25-
26-
/** Parses the matching partials of the phone number by predefined regex patterns */
27-
const countryCodeMatch = formattedNumber ? (formattedNumber.match(countryCodePattern) || []) : [];
28-
const areaCodeMatch = formattedNumber ? (formattedNumber.match(areaCodePattern) || []) : [];
29-
30-
/** Converts the parsed values of the country and area codes to integers if values present */
31-
const countryCode = countryCodeMatch.length > 0 ? parseInt(countryCodeMatch[0]) : null;
32-
const areaCode = areaCodeMatch.length > 1 ? parseInt(areaCodeMatch[1]) : null;
33-
34-
/** Parses the phone number by removing the country and area codes from the formatted value */
35-
const phoneNumberPattern = new RegExp(`^${countryCode}${(areaCode || "")}(\\d+)`);
36-
const phoneNumberMatch = value ? (value.match(phoneNumberPattern) || []) : [];
37-
const phoneNumber = phoneNumberMatch.length > 1 ? phoneNumberMatch[1] : null;
38-
39-
/** Checks if both the area code and phone number length satisfy the validation rules */
40-
const rules = validations[isoCode as ISO2Code] || {areaCode: [], phoneNumber: []};
41-
const valid = [
42-
rules.areaCode.includes((areaCode || "").toString().length),
43-
rules.phoneNumber.includes((phoneNumber || "").toString().length),
44-
].every(Boolean);
45-
46-
return {countryCode, areaCode, phoneNumber, isoCode, valid};
47-
}
48-
49-
const PhoneInput = ({
50-
value,
51-
style,
52-
country,
53-
className,
54-
size = "middle",
55-
onPressEnter = () => null,
56-
onMount: handleMount = () => null,
57-
onChange: handleChange = () => null,
58-
...reactPhoneInputProps
59-
}: PhoneInputProps) => {
60-
const [currentCode, setCurrentCode] = useState("");
61-
62-
const countryCode = useMemo(() => country || getDefaultISO2Code(), [country]);
63-
64-
const rawPhone = useMemo(() => {
65-
const {countryCode, areaCode, phoneNumber} = {...value};
66-
return [countryCode, areaCode, phoneNumber].map(v => v || "").join("");
67-
}, [value]);
10+
const PhoneInput = (inputLegacyProps: PhoneInputProps) => {
11+
const {token} = theme.useToken();
12+
const {status}: any = useContext(FormItemInputContext);
13+
const [_1, inputCls] = genComponentStyleHook("ant-input");
14+
const [_2, dropdownCls] = genComponentStyleHook("ant-dropdown");
6815

6916
const inputClass = useMemo(() => {
70-
const suffix = {small: "sm", middle: "", large: "lg"}[size];
71-
return "ant-input" + (suffix ? " ant-input-" + suffix : "");
72-
}, [size]);
73-
74-
const onChange: ReactPhoneOnChange = (value, data, event, formattedNumber) => {
75-
const metadata = parsePhoneNumber(value, data, formattedNumber);
76-
const code = metadata.isoCode as ISO2Code;
77-
78-
if (code !== currentCode) {
79-
/** Clears phone number when the country is selected manually */
80-
handleChange({...metadata, areaCode: null, phoneNumber: null}, event);
81-
setCurrentCode(code);
82-
return;
17+
return `${inputCls} ` + getStatusClassNames("ant-input", status);
18+
}, [inputCls, status]);
19+
20+
const dropdownClass = useMemo(() => "ant-dropdown " + dropdownCls, [dropdownCls]);
21+
22+
useEffect(() => {
23+
/** Load antd 5.x styles dynamically observing the theme change */
24+
for (let styleSheet of document.styleSheets) {
25+
let rule: any;
26+
for (rule of styleSheet.cssRules || styleSheet.rules) {
27+
if (rule.selectorText === ".react-tel-input .country-list") {
28+
rule.style.boxShadow = token.boxShadow;
29+
rule.style.backgroundColor = token.colorBgElevated;
30+
}
31+
if (rule.selectorText === ".react-tel-input .selected-flag") {
32+
rule.style.borderColor = token.colorBorder;
33+
}
34+
if (rule.selectorText === ".react-tel-input .country-list .search") {
35+
rule.style.backgroundColor = token.colorBgElevated;
36+
}
37+
if (rule.selectorText === ".react-tel-input .country-list .country") {
38+
rule.style.borderRadius = token.borderRadiusOuter + "px";
39+
}
40+
if (rule.selectorText === ".react-tel-input .country-list .country-name") {
41+
rule.style.color = token.colorText;
42+
}
43+
if (rule.selectorText === ".react-tel-input .country-list .country .dial-code") {
44+
rule.style.color = token.colorTextDescription;
45+
}
46+
if (rule.selectorText === ".react-tel-input .country-list .country:hover") {
47+
rule.style.backgroundColor = token.colorBgTextHover;
48+
}
49+
if (rule.selectorText === ".react-tel-input .country-list .country.highlight") {
50+
rule.style.backgroundColor = token.colorPrimaryBg;
51+
}
52+
if (rule.selectorText === `:where(.${inputCls}).ant-input`) {
53+
rule.selectorText += "\n,.react-tel-input .country-list .search-box";
54+
rule.style.backgroundColor = token.colorBgElevated;
55+
}
56+
if (rule.selectorText === `:where(.${inputCls}).ant-input:hover`) {
57+
rule.selectorText += "\n,.react-tel-input .country-list .search-box:focus";
58+
rule.selectorText += "\n,.react-tel-input .country-list .search-box:hover";
59+
}
60+
}
8361
}
84-
85-
handleChange(metadata, event);
86-
}
87-
88-
const onMount: ReactPhoneOnMount = (rawValue, {countryCode, ...event}, formattedNumber) => {
89-
const metadata = parsePhoneNumber(rawValue, {countryCode}, formattedNumber);
90-
/** Initiates the current country code with the code of initial value */
91-
setCurrentCode(metadata.isoCode as ISO2Code);
92-
/** Initializes the existing value */
93-
handleChange(metadata, event);
94-
handleMount(metadata);
95-
}
62+
}, [inputCls, token])
9663

9764
return (
98-
<ReactPhoneInput
99-
/** Static properties for stable functionality */
100-
masks={masks}
101-
value={rawPhone}
102-
enableAreaCodes
103-
disableSearchIcon
104-
/** Static properties providing dynamic behavior */
105-
onMount={onMount}
106-
onChange={onChange}
107-
country={countryCode}
65+
<InputLegacy
66+
{...inputLegacyProps}
10867
inputClass={inputClass}
109-
/** Dynamic properties for customization */
110-
{...reactPhoneInputProps}
111-
containerStyle={style}
112-
containerClass={className}
113-
onEnterKeyPress={onPressEnter}
68+
dropdownClass={dropdownClass}
11469
/>
11570
)
11671
}

0 commit comments

Comments
 (0)