Skip to content

Commit cd9f262

Browse files
feat: typescript support (patw0929#389)
* build(npm): add TypeScript 4.3.5 w/ basic project setup * feat(types): add module definitions * feat(types): use jQuery for underscore.deferred According underscore.deferred's author, its API is very similar to jQuery's Deferred. * test(types): check definitions The .d.ts files aren't tested in jest. We’ll have to use tsd instead. * build(types): add definition file copy operation The tsc command won't help us here, but this will. - Manual copy: https://stackoverflow.com/a/56440335 - Script args: https://apple.stackexchange.com/a/228045 Using gulp hasn't been considered due to dependency bloat. * refactor(npm): split build script into readable fragments For easier maintenance. * chore(lint): add TypeScript configs * docs(readme): mention TypeScript support
1 parent 1b5957d commit cd9f262

26 files changed

+1866
-67
lines changed

.eslintrc.js

+57-1
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,68 @@ module.exports = {
7575
],
7676
'no-plusplus': ['error', { allowForLoopAfterthoughts: true }],
7777
},
78-
7978
globals: {
8079
__DEVELOPMENT__: true,
8180
__CLIENT__: true,
8281
__SERVER__: true,
8382
__DISABLE_SSR__: true,
8483
__DEVTOOLS__: true,
8584
},
85+
overrides: [
86+
// typescript .d.ts config
87+
{
88+
extends: [
89+
'eslint:recommended',
90+
'plugin:@typescript-eslint/eslint-recommended',
91+
'plugin:@typescript-eslint/recommended',
92+
],
93+
files: ['**/*.d.ts'],
94+
parser: '@typescript-eslint/parser',
95+
plugins: [
96+
'@typescript-eslint',
97+
'eslint-plugin-import',
98+
'eslint-plugin-react',
99+
],
100+
rules: {
101+
'@typescript-eslint/explicit-module-boundary-types': 'off',
102+
'@typescript-eslint/no-explicit-any': 'off',
103+
'@typescript-eslint/no-empty-interface': 'off',
104+
'import/order': [
105+
'error',
106+
{
107+
alphabetize: {
108+
order: 'asc',
109+
},
110+
groups: [['external', 'builtin'], 'parent', 'sibling'],
111+
'newlines-between': 'always',
112+
},
113+
],
114+
'react/sort-comp': [
115+
2,
116+
{
117+
order: [
118+
'static-methods',
119+
'instance-variables',
120+
'instance-methods',
121+
'lifecycle',
122+
'everything-else',
123+
'render',
124+
],
125+
},
126+
],
127+
'spaced-comment': [
128+
'error',
129+
'always',
130+
{
131+
line: {
132+
markers: ['#region', '#endregion', 'region', 'endregion'],
133+
},
134+
},
135+
],
136+
},
137+
settings: {
138+
'import/resolver': 'eslint-import-resolver-typescript',
139+
},
140+
},
141+
],
86142
}

README.md

+47-4
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,15 @@ Then open [`localhost:3000`](http://localhost:3000) in a browser.
4040
yarn add react-intl-tel-input
4141
```
4242

43+
44+
### TypeScript
45+
46+
`react-intl-tel-input` ships with official type declarations out of the box.
47+
48+
4349
## Usage
4450

51+
### JavaScript
4552
```javascript
4653
import IntlTelInput from 'react-intl-tel-input';
4754
import 'react-intl-tel-input/dist/main.css';
@@ -52,6 +59,40 @@ import 'react-intl-tel-input/dist/main.css';
5259
/>
5360
```
5461

62+
### TypeScript
63+
```tsx
64+
import * as IntlTelInput from 'react-intl-tel-input';
65+
import 'react-intl-tel-input/dist/main.css';
66+
67+
<IntlTelInput
68+
containerClassName="intl-tel-input"
69+
inputClassName="form-control"
70+
/>
71+
```
72+
73+
If your `tsconfig.json` contains the following config:
74+
75+
**tsconfig.json**
76+
```json
77+
{
78+
"compilerOptions": {
79+
"esModuleInterop": true,
80+
"allowSyntheticDefaultImports": true,
81+
}
82+
}
83+
```
84+
85+
... use the default import workflow instead:
86+
```tsx
87+
import IntlTelInput from 'react-intl-tel-input';
88+
import 'react-intl-tel-input/dist/main.css';
89+
90+
<IntlTelInput
91+
containerClassName="intl-tel-input"
92+
inputClassName="form-control"
93+
/>
94+
```
95+
5596
### Properties
5697

5798
Please see the [Demo Page](https://patw0929.github.io/react-intl-tel-input/)
@@ -67,11 +108,13 @@ You can prepare a distribution build using `yarn build`.
67108

68109
Any kind of contribution including proposals, doc improvements, enhancements, bug fixes are always welcome.
69110

70-
To contribute to react-intl-tel-input, clone this repo locally and commit your code on a separate branch. Please write tests for your code, and run the linter before opening a pull-request:
111+
To contribute to `react-intl-tel-input`, clone this repo locally and commit your code on a separate branch. Please write tests for your code, and run the linter before opening a pull-request:
71112

72-
```
73-
yarn test
74-
yarn run lint
113+
```bash
114+
yarn test # if you are enhancing the JavaScript modules
115+
yarn test:ts # if you are enhancing the TypeScript type declarations
116+
yarn tsd # in addition to test:ts, also check that the type declarations work as intended
117+
yarn lint
75118
```
76119

77120
Also, please let us know if you encounter any issue by filing an [issue](https://github.com/patw0929/react-intl-tel-input/issues).

package.json

+25-4
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@
3030
"url": "https://github.com/patw0929/react-intl-tel-input/issues"
3131
},
3232
"main": "dist/index.js",
33+
"types": "dist/index.d.ts",
3334
"peerDependencies": {
3435
"react": ">15.4.2 <17.0.0",
3536
"react-dom": ">15.4.2 <17.0.0"
3637
},
3738
"files": [
3839
"dist/**/*"
3940
],
41+
"tsd": {
42+
"directory": "src/components/__tests__"
43+
},
4044
"dependencies": {
4145
"classnames": "^2.2.5",
4246
"libphonenumber-js-utils": "^8.10.5",
@@ -54,6 +58,12 @@
5458
"@babel/preset-react": "^7.0.0",
5559
"@commitlint/cli": "^8.3.5",
5660
"@commitlint/config-conventional": "^8.3.4",
61+
"@types/jquery": "^3.5.6",
62+
"@types/node": "^14.0.13",
63+
"@types/react": "^16.9.56",
64+
"@types/react-dom": "^16.9.9",
65+
"@typescript-eslint/eslint-plugin": "^4.29.0",
66+
"@typescript-eslint/parser": "^4.29.0",
5767
"babel-core": "^7.0.0-bridge.0",
5868
"babel-eslint": "^10.0.1",
5969
"babel-jest": "^23.6.0",
@@ -67,7 +77,9 @@
6777
"eslint-config-airbnb": "~17.1.0",
6878
"eslint-config-airbnb-base": "~13.1.0",
6979
"eslint-config-prettier": "^6.10.0",
70-
"eslint-plugin-import": "^2.14.0",
80+
"eslint-import-resolver-typescript": "^2.4.0",
81+
"eslint-loader": "^2.1.1",
82+
"eslint-plugin-import": "^2.23.4",
7183
"eslint-plugin-jsx-a11y": "^6.1.1",
7284
"eslint-plugin-prettier": "^3.1.2",
7385
"eslint-plugin-react": "^7.11.0",
@@ -86,20 +98,29 @@
8698
"rimraf": "2.5.4",
8799
"sass": "^1.37.4",
88100
"semantic-release": "^17.0.4",
89-
"sinon": "^1.17.4"
101+
"sinon": "^1.17.4",
102+
"tsd": "^0.17.0",
103+
"typescript": "^4.3.5"
90104
},
91105
"scripts": {
92106
"prebuild": "yarn run clean",
93-
"build": "BABEL_ENV=production babel src -d dist && cp -r ./src/*.png ./dist && sass ./src/intlTelInput.scss ./dist/main.css",
107+
"build": "yarn compile:js && yarn compile:dts && yarn compile:css && yarn compile:png",
108+
"compile:js": "BABEL_ENV=production babel src -d dist",
109+
"compile:dts": "rsync -avh --include='*/' --include='*.d.ts' --exclude='*' src/ dist --prune-empty-dirs",
110+
"compile:css": "sass ./src/intlTelInput.scss ./dist/main.css",
111+
"compile:png": "cp -r -v ./src/*.png ./dist",
94112
"clean": "rimraf dist",
95113
"website:start": "yarn workspace website run start",
96114
"website:dryrun": "yarn workspace website run deploy:dryrun",
97115
"website:deploy": "yarn workspace website run deploy",
98-
"lint": "eslint src website/.storybook *.js",
116+
"lint": "eslint src website/.storybook --ext .js,.d.ts",
99117
"coverage": "yarn test --coverage",
100118
"coverage-upload": "NODE_ENV=development cat coverage/lcov.info | yarn coveralls",
101119
"test": "jest src",
102120
"test:watch": "jest src --watchAll --coverage",
121+
"test:ts": "tsc --project ./tsconfig.test.json",
122+
"test:ts-watch": "tsc --watch --project ./tsconfig.test.json",
123+
"tsd": "tsd",
103124
"footprint": "yarn build && yarn packwatch",
104125
"lint:commits": "yarn commitlint --from HEAD --to HEAD --verbose"
105126
},

src/components/AllCountries.d.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { CountryData } from '../types'
2+
3+
type ExternalCountry = [
4+
CountryData['name'],
5+
CountryData['iso2'],
6+
CountryData['dialCode'],
7+
CountryData['priority'],
8+
CountryData['areaCodes'],
9+
]
10+
11+
interface AllCountriesStatic {
12+
initialize(externalCountriesList: ExternalCountry[]): void
13+
getCountries(): CountryData[]
14+
}
15+
16+
declare const AllCountries: AllCountriesStatic
17+
18+
export default AllCountries

src/components/CountryList.d.ts

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react'
2+
3+
import { CountryData } from '../types'
4+
5+
export interface CountryListProps {
6+
setFlag?: (iso2: string) => void
7+
countries?: CountryData[]
8+
inputTop?: number
9+
inputOuterHeight?: number
10+
preferredCountries?: CountryData
11+
highlightedCountry?: number
12+
changeHighlightCountry?: (
13+
showDropdown: boolean,
14+
selectedIndex: number,
15+
) => void
16+
showDropdown?: boolean
17+
isMobile?: boolean
18+
dropdownContainer?: string
19+
}
20+
21+
export interface CountryListState {}
22+
23+
export default class CountryList extends React.Component<
24+
CountryListProps,
25+
CountryListState
26+
> {
27+
listElement?: HTMLUListElement | null
28+
29+
setDropdownPosition(): void
30+
31+
appendListItem(
32+
countries: CountryData[],
33+
isPreferred?: boolean,
34+
): React.ReactNode
35+
36+
handleMouseOver: (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void
37+
}

src/components/FlagBox.d.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react'
2+
3+
export interface FlagBoxProps {
4+
dialCode: string
5+
isoCode: string
6+
name: string
7+
onMouseOver?: (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void
8+
onFocus?: (event: React.FocusEvent<HTMLLIElement>) => void
9+
onClick?: (event: React.MouseEvent<HTMLLIElement, MouseEvent>) => void
10+
flagRef?: (instance: HTMLDivElement | null) => void
11+
innerFlagRef?: (instance: HTMLDivElement | null) => void
12+
countryClass: string
13+
}
14+
15+
declare const FlagBox: React.FunctionComponent<FlagBoxProps>
16+
17+
export default FlagBox

src/components/FlagDropDown.d.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import React from 'react'
2+
3+
import { CountryData } from '../types'
4+
5+
import CountryList from './CountryList'
6+
7+
export interface FlagDropDownProps {
8+
allowDropdown?: boolean
9+
dropdownContainer?: React.ElementType | string
10+
separateDialCode?: boolean
11+
dialCode?: string
12+
countryCode?: string
13+
showDropdown?: boolean
14+
clickSelectedFlag?: (
15+
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
16+
) => void
17+
handleSelectedFlagKeydown?: (
18+
event: React.KeyboardEvent<HTMLDivElement>,
19+
) => void
20+
isMobile?: boolean
21+
setFlag?: (iso2: string) => void
22+
countries?: CountryData[]
23+
inputTop?: number
24+
inputOuterHeight?: number
25+
preferredCountries?: CountryData[]
26+
highlightedCountry?: number
27+
changeHighlightCountry?: (
28+
showDropdown: boolean,
29+
selectedIndex: number,
30+
) => void
31+
titleTip?: string
32+
refCallback: (instance: HTMLDivElement | null) => void
33+
}
34+
35+
export interface FlagDropDownState {}
36+
37+
export default class FlagDropDown extends React.Component<
38+
FlagDropDownProps,
39+
FlagDropDownState
40+
> {
41+
countryList?: CountryList | null
42+
43+
genSelectedDialCode: () => React.ReactNode
44+
45+
genArrow: () => React.ReactNode
46+
47+
genFlagClassName: () => string
48+
49+
genCountryList: () => React.ReactNode
50+
}

0 commit comments

Comments
 (0)