Skip to content

Commit dec919b

Browse files
jquenseakx
andauthored
feat: Add proper Typescript support (react-bootstrap#5251)
* TypeScript all the things (react-bootstrap#5150) * Add Typescript tooling via react-overlays * Add @types/ via akx/autotypes * Mostly mechanically merge JavaScript and TypeScript files * Fix typings in all TS files * BootstrapModalManager: add types + fix marging -> margin typo * Amend test and build configuration * Address some review comments * Address more review comments * Move simple-types-test into tests/ and fix things to have it typecheck * Update dev deps for e.g. Eslint 7+ compat & fix type issues * chore: fix types build output (react-bootstrap#5204) * Publish v1.1.0-rc.0 * fix up * lint * fix defaults Co-authored-by: Aarni Koskela <[email protected]>
1 parent d8cbaea commit dec919b

File tree

241 files changed

+3447
-3659
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

241 files changed

+3447
-3659
lines changed

.babelrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ module.exports = api => {
3030
removePropTypes: !dev,
3131
},
3232
],
33+
'@babel/preset-typescript',
3334
],
3435
plugins: [env === 'test' && 'istanbul'].filter(Boolean),
3536
};

.eslintrc

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
{
22
"extends": [
3-
"@react-bootstrap"
3+
"@react-bootstrap",
4+
"4catalyzer-typescript",
5+
"prettier",
6+
"prettier/react",
7+
"prettier/@typescript-eslint"
48
],
59
"plugins": [
610
"prettier"
711
],
812
"rules": {
9-
"prettier/prettier": "error"
13+
"import/extensions": "off",
14+
"prettier/prettier": "warn"
1015
}
1116
}

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,6 @@ typings/
9090

9191
*~
9292
.DS_Store
93+
94+
# Generated types
95+
/types

.mocharc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"require": "test/server/babel-register.js"
3+
}

karma.conf.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module.exports = (config) => {
1717
module: {
1818
rules: [
1919
{
20-
test: /\.js$/,
20+
test: /\.[tj]sx?$/,
2121
exclude: /node_modules/,
2222
use: {
2323
loader: 'babel-loader',
@@ -31,6 +31,7 @@ module.exports = (config) => {
3131
},
3232
resolve: {
3333
symlinks: false,
34+
extensions: ['.js', '.jsx', '.ts', '.tsx'],
3435
},
3536
plugins: [
3637
new DefinePlugin({

package.json

+19-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-bootstrap",
3-
"version": "1.0.1",
3+
"version": "1.1.0-rc.0",
44
"description": "Bootstrap 4 components built with React",
55
"repository": {
66
"type": "git",
@@ -10,32 +10,31 @@
1010
"sideEffects": false,
1111
"main": "lib/cjs/index.js",
1212
"module": "lib/esm/index.js",
13+
"types": "lib/esm/index.d.ts",
1314
"scripts": {
1415
"bootstrap": "yarn && yarn --cwd www",
1516
"build": "node tools/build.js",
1617
"build-docs": "yarn --cwd www build",
18+
"build-types": "yarn tsc -d --emitDeclarationOnly --outDir types",
1719
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
18-
"dtslint": "dtslint types --expectOnly",
19-
"format": "eslint . --fix && npm run prettier -- --write",
20-
"lint": "eslint . && npm run prettier -- -l",
2120
"deploy-docs": "yarn --cwd www deploy",
21+
"format": "eslint --ext tsx --ext ts src --fix",
22+
"lint": "eslint --ext tsx --ext ts src && tsc --noEmit",
2223
"prepublishOnly": "npm run build",
23-
"prettier": "prettier \"types/**/*.{ts,tsx}\"",
2424
"release": "rollout",
2525
"start": "yarn --cwd www develop",
2626
"tdd": "karma start",
27-
"test": "npm run lint && npm run dtslint && npm run test-browser && npm run test-node",
27+
"test": "npm run lint && npm run test-browser && npm run test-node",
2828
"test-browser": "cross-env NODE_ENV=test karma start --single-run",
29-
"test-node": "cross-env NODE_ENV=test-server mocha --require @babel/register test/server/*Spec.js"
29+
"test-node": "cross-env NODE_ENV=test-server mocha test/server/*Spec.js"
3030
},
3131
"husky": {
3232
"hooks": {
3333
"pre-commit": "lint-staged"
3434
}
3535
},
3636
"lint-staged": {
37-
"*.js": "eslint --fix",
38-
"types/**/*.ts": "prettier --write"
37+
"*.{js,ts,tsx}": "eslint --fix"
3938
},
4039
"prettier": {
4140
"singleQuote": true,
@@ -66,7 +65,7 @@
6665
"@babel/runtime": "^7.4.2",
6766
"@restart/context": "^2.1.4",
6867
"@restart/hooks": "^0.3.21",
69-
"@types/react": "^16.9.23",
68+
"@types/react": "^16.9.35",
7069
"classnames": "^2.2.6",
7170
"dom-helpers": "^5.1.2",
7271
"invariant": "^2.2.4",
@@ -79,11 +78,20 @@
7978
},
8079
"devDependencies": {
8180
"@4c/rollout": "^2.1.9",
81+
"@4c/tsconfig": "^0.3.1",
82+
"@babel/preset-typescript": "^7.9.0",
8283
"@babel/cli": "^7.10.4",
8384
"@babel/core": "^7.10.4",
8485
"@babel/register": "^7.10.4",
8586
"@react-bootstrap/babel-preset": "^1.2.0",
8687
"@react-bootstrap/eslint-config": "^1.3.2",
88+
"@types/classnames": "^2.2.10",
89+
"@types/invariant": "^2.2.33",
90+
"@types/prop-types": "^15.7.3",
91+
"@types/react-transition-group": "^4.2.4",
92+
"@types/warning": "^3.0.0",
93+
"@typescript-eslint/eslint-plugin": "^2.33.0",
94+
"@typescript-eslint/parser": "^2.33.0",
8795
"babel-eslint": "^10.1.0",
8896
"babel-loader": "^8.1.0",
8997
"babel-plugin-istanbul": "^6.0.0",
@@ -98,6 +106,7 @@
98106
"enzyme": "^3.11.0",
99107
"enzyme-adapter-react-16": "^1.15.2",
100108
"eslint": "^7.4.0",
109+
"eslint-config-4catalyzer-typescript": "^1.1.8",
101110
"eslint-import-resolver-node": "^0.3.4",
102111
"eslint-import-resolver-webpack": "^0.12.2",
103112
"eslint-plugin-import": "^2.22.0",

src/AbstractNav.js renamed to src/AbstractNav.tsx

+26-8
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import useMergedRefs from '@restart/hooks/useMergedRefs';
66
import NavContext from './NavContext';
77
import SelectableContext, { makeEventKey } from './SelectableContext';
88
import TabContext from './TabContext';
9+
import { BsPrefixRefForwardingComponent } from './helpers';
910

11+
// eslint-disable-next-line @typescript-eslint/no-empty-function
1012
const noop = () => {};
1113

1214
const propTypes = {
@@ -28,7 +30,21 @@ const propTypes = {
2830
activeKey: PropTypes.any,
2931
};
3032

31-
const AbstractNav = React.forwardRef(
33+
// TODO: is this correct?
34+
interface AbstractNavProps {
35+
activeKey?: any;
36+
as?: React.ElementType;
37+
getControlledId?: any;
38+
getControllerId?: any;
39+
onKeyDown?: any;
40+
onSelect?: any;
41+
parentOnSelect?: any;
42+
role?: string;
43+
}
44+
45+
type AbstractNav = BsPrefixRefForwardingComponent<'ul', AbstractNavProps>;
46+
47+
const AbstractNav: AbstractNav = React.forwardRef(
3248
(
3349
{
3450
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
@@ -38,7 +54,7 @@ const AbstractNav = React.forwardRef(
3854
role,
3955
onKeyDown,
4056
...props
41-
},
57+
}: AbstractNavProps,
4258
ref,
4359
) => {
4460
// A ref and forceUpdate for refocus, b/c we only want to trigger when needed
@@ -58,15 +74,17 @@ const AbstractNav = React.forwardRef(
5874
getControllerId = tabContext.getControllerId;
5975
}
6076

61-
const listNode = useRef(null);
77+
const listNode = useRef<HTMLElement>(null);
6278

6379
const getNextActiveChild = (offset) => {
64-
if (!listNode.current) return null;
80+
const currentListNode = listNode.current;
81+
if (!currentListNode) return null;
6582

66-
let items = qsa(listNode.current, '[data-rb-event-key]:not(.disabled)');
67-
let activeChild = listNode.current.querySelector('.active');
83+
const items = qsa(currentListNode, '[data-rb-event-key]:not(.disabled)');
84+
const activeChild = currentListNode.querySelector<HTMLElement>('.active');
85+
if (!activeChild) return null;
6886

69-
let index = items.indexOf(activeChild);
87+
const index = items.indexOf(activeChild);
7088
if (index === -1) return null;
7189

7290
let nextIndex = index + offset;
@@ -107,7 +125,7 @@ const AbstractNav = React.forwardRef(
107125

108126
useEffect(() => {
109127
if (listNode.current && needsRefocusRef.current) {
110-
let activeChild = listNode.current.querySelector(
128+
const activeChild = listNode.current.querySelector<HTMLElement>(
111129
'[data-rb-event-key].active',
112130
);
113131

src/AbstractNavItem.js renamed to src/AbstractNavItem.tsx

+24-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ import useEventCallback from '@restart/hooks/useEventCallback';
66
import warning from 'warning';
77
import NavContext from './NavContext';
88
import SelectableContext, { makeEventKey } from './SelectableContext';
9+
import { BsPrefixRefForwardingComponent } from './helpers';
10+
11+
// TODO: check this
12+
interface AbstractNavItemProps {
13+
active?: boolean;
14+
as: React.ElementType;
15+
className?: string;
16+
disabled?: boolean;
17+
eventKey?: any; // TODO: especially fix this
18+
href?: string;
19+
role?: string;
20+
id?: string;
21+
tabIndex?: number;
22+
onClick?: (e: any) => void;
23+
onSelect?: (navKey: string, e: any) => void;
24+
}
25+
26+
type AbstractNavItem = BsPrefixRefForwardingComponent<
27+
'div',
28+
AbstractNavItemProps
29+
>;
930

1031
const propTypes = {
1132
id: PropTypes.string,
@@ -28,18 +49,17 @@ const defaultProps = {
2849
disabled: false,
2950
};
3051

31-
const AbstractNavItem = React.forwardRef(
52+
const AbstractNavItem: AbstractNavItem = React.forwardRef(
3253
(
3354
{
3455
active,
3556
className,
36-
tabIndex,
3757
eventKey,
3858
onSelect,
3959
onClick,
4060
as: Component,
4161
...props
42-
},
62+
}: AbstractNavItemProps,
4363
ref,
4464
) => {
4565
const navKey = makeEventKey(eventKey, props.href);
@@ -73,7 +93,7 @@ const AbstractNavItem = React.forwardRef(
7393
}
7494

7595
if (props.role === 'tab') {
76-
props.tabIndex = isActive ? tabIndex : -1;
96+
props.tabIndex = isActive ? props.tabIndex : -1;
7797
props['aria-selected'] = isActive;
7898
}
7999

src/Accordion.js renamed to src/Accordion.tsx

+29-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ import AccordionToggle from './AccordionToggle';
77
import SelectableContext from './SelectableContext';
88
import AccordionCollapse from './AccordionCollapse';
99
import AccordionContext from './AccordionContext';
10+
import {
11+
BsPrefixPropsWithChildren,
12+
BsPrefixRefForwardingComponent,
13+
SelectCallback,
14+
} from './helpers';
15+
16+
export interface AccordionProps
17+
extends Omit<React.HTMLAttributes<HTMLElement>, 'onSelect'>,
18+
BsPrefixPropsWithChildren {
19+
activeKey?: string;
20+
defaultActiveKey?: string;
21+
onSelect?: SelectCallback;
22+
}
23+
24+
type Accordion = BsPrefixRefForwardingComponent<'div', AccordionProps> & {
25+
Toggle: typeof AccordionToggle;
26+
Collapse: typeof AccordionCollapse;
27+
};
1028

1129
const propTypes = {
1230
/** Set a custom element for this component */
@@ -22,8 +40,8 @@ const propTypes = {
2240
defaultActiveKey: PropTypes.string,
2341
};
2442

25-
const Accordion = React.forwardRef((props, ref) => {
26-
let {
43+
const Accordion = (React.forwardRef((props: AccordionProps, ref) => {
44+
const {
2745
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
2846
as: Component = 'div',
2947
activeKey,
@@ -36,25 +54,23 @@ const Accordion = React.forwardRef((props, ref) => {
3654
activeKey: 'onSelect',
3755
});
3856

39-
bsPrefix = useBootstrapPrefix(bsPrefix, 'accordion');
40-
57+
const finalClassName = classNames(
58+
className,
59+
useBootstrapPrefix(bsPrefix, 'accordion'),
60+
);
4161
return (
42-
<AccordionContext.Provider value={activeKey}>
43-
<SelectableContext.Provider value={onSelect}>
44-
<Component
45-
ref={ref}
46-
{...controlledProps}
47-
className={classNames(className, bsPrefix)}
48-
>
62+
<AccordionContext.Provider value={activeKey || null}>
63+
<SelectableContext.Provider value={onSelect || null}>
64+
<Component ref={ref} {...controlledProps} className={finalClassName}>
4965
{children}
5066
</Component>
5167
</SelectableContext.Provider>
5268
</AccordionContext.Provider>
5369
);
54-
});
70+
}) as unknown) as Accordion;
5571

72+
Accordion.displayName = 'Accordion';
5673
Accordion.propTypes = propTypes;
57-
5874
Accordion.Toggle = AccordionToggle;
5975
Accordion.Collapse = AccordionCollapse;
6076

src/AccordionCollapse.js renamed to src/AccordionCollapse.tsx

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import React, { useContext } from 'react';
22
import PropTypes from 'prop-types';
33

4-
import Collapse from './Collapse';
4+
import Collapse, { CollapseProps } from './Collapse';
55
import AccordionContext from './AccordionContext';
6+
import { BsPrefixRefForwardingComponent } from './helpers';
7+
8+
export interface AccordionCollapseProps
9+
extends React.PropsWithChildren<CollapseProps> {
10+
eventKey: string;
11+
}
12+
13+
type AccordionCollapse = BsPrefixRefForwardingComponent<
14+
'div',
15+
AccordionCollapseProps
16+
>;
617

718
const propTypes = {
819
/**
@@ -14,8 +25,8 @@ const propTypes = {
1425
children: PropTypes.element.isRequired,
1526
};
1627

17-
const AccordionCollapse = React.forwardRef(
18-
({ children, eventKey, ...props }, ref) => {
28+
const AccordionCollapse: AccordionCollapse = React.forwardRef<typeof Collapse>(
29+
({ children, eventKey, ...props }: AccordionCollapseProps, ref) => {
1930
const contextEventKey = useContext(AccordionContext);
2031

2132
return (
@@ -24,7 +35,7 @@ const AccordionCollapse = React.forwardRef(
2435
</Collapse>
2536
);
2637
},
27-
);
38+
) as any;
2839

2940
AccordionCollapse.propTypes = propTypes;
3041
AccordionCollapse.displayName = 'AccordionCollapse';
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React from 'react';
22

3-
const context = React.createContext(null);
3+
const context = React.createContext<string | null>(null);
44
context.displayName = 'AccordionContext';
55

66
export default context;

0 commit comments

Comments
 (0)