Skip to content

Commit

Permalink
feat: Add proper Typescript support (#5251)
Browse files Browse the repository at this point in the history
* TypeScript all the things (#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 (#5204)

* Publish v1.1.0-rc.0

* fix up

* lint

* fix defaults

Co-authored-by: Aarni Koskela <akx@iki.fi>
  • Loading branch information
jquense and akx committed Jul 6, 2020
1 parent d8cbaea commit dec919b
Show file tree
Hide file tree
Showing 241 changed files with 3,447 additions and 3,659 deletions.
1 change: 1 addition & 0 deletions .babelrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module.exports = api => {
removePropTypes: !dev,
},
],
'@babel/preset-typescript',
],
plugins: [env === 'test' && 'istanbul'].filter(Boolean),
};
Expand Down
9 changes: 7 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
{
"extends": [
"@react-bootstrap"
"@react-bootstrap",
"4catalyzer-typescript",
"prettier",
"prettier/react",
"prettier/@typescript-eslint"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error"
"import/extensions": "off",
"prettier/prettier": "warn"
}
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,6 @@ typings/

*~
.DS_Store

# Generated types
/types
3 changes: 3 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"require": "test/server/babel-register.js"
}
3 changes: 2 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = (config) => {
module: {
rules: [
{
test: /\.js$/,
test: /\.[tj]sx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
Expand All @@ -31,6 +31,7 @@ module.exports = (config) => {
},
resolve: {
symlinks: false,
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
plugins: [
new DefinePlugin({
Expand Down
29 changes: 19 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-bootstrap",
"version": "1.0.1",
"version": "1.1.0-rc.0",
"description": "Bootstrap 4 components built with React",
"repository": {
"type": "git",
Expand All @@ -10,32 +10,31 @@
"sideEffects": false,
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"types": "lib/esm/index.d.ts",
"scripts": {
"bootstrap": "yarn && yarn --cwd www",
"build": "node tools/build.js",
"build-docs": "yarn --cwd www build",
"build-types": "yarn tsc -d --emitDeclarationOnly --outDir types",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"dtslint": "dtslint types --expectOnly",
"format": "eslint . --fix && npm run prettier -- --write",
"lint": "eslint . && npm run prettier -- -l",
"deploy-docs": "yarn --cwd www deploy",
"format": "eslint --ext tsx --ext ts src --fix",
"lint": "eslint --ext tsx --ext ts src && tsc --noEmit",
"prepublishOnly": "npm run build",
"prettier": "prettier \"types/**/*.{ts,tsx}\"",
"release": "rollout",
"start": "yarn --cwd www develop",
"tdd": "karma start",
"test": "npm run lint && npm run dtslint && npm run test-browser && npm run test-node",
"test": "npm run lint && npm run test-browser && npm run test-node",
"test-browser": "cross-env NODE_ENV=test karma start --single-run",
"test-node": "cross-env NODE_ENV=test-server mocha --require @babel/register test/server/*Spec.js"
"test-node": "cross-env NODE_ENV=test-server mocha test/server/*Spec.js"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.js": "eslint --fix",
"types/**/*.ts": "prettier --write"
"*.{js,ts,tsx}": "eslint --fix"
},
"prettier": {
"singleQuote": true,
Expand Down Expand Up @@ -66,7 +65,7 @@
"@babel/runtime": "^7.4.2",
"@restart/context": "^2.1.4",
"@restart/hooks": "^0.3.21",
"@types/react": "^16.9.23",
"@types/react": "^16.9.35",
"classnames": "^2.2.6",
"dom-helpers": "^5.1.2",
"invariant": "^2.2.4",
Expand All @@ -79,11 +78,20 @@
},
"devDependencies": {
"@4c/rollout": "^2.1.9",
"@4c/tsconfig": "^0.3.1",
"@babel/preset-typescript": "^7.9.0",
"@babel/cli": "^7.10.4",
"@babel/core": "^7.10.4",
"@babel/register": "^7.10.4",
"@react-bootstrap/babel-preset": "^1.2.0",
"@react-bootstrap/eslint-config": "^1.3.2",
"@types/classnames": "^2.2.10",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
"@types/react-transition-group": "^4.2.4",
"@types/warning": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-istanbul": "^6.0.0",
Expand All @@ -98,6 +106,7 @@
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2",
"eslint": "^7.4.0",
"eslint-config-4catalyzer-typescript": "^1.1.8",
"eslint-import-resolver-node": "^0.3.4",
"eslint-import-resolver-webpack": "^0.12.2",
"eslint-plugin-import": "^2.22.0",
Expand Down
34 changes: 26 additions & 8 deletions src/AbstractNav.js → src/AbstractNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import useMergedRefs from '@restart/hooks/useMergedRefs';
import NavContext from './NavContext';
import SelectableContext, { makeEventKey } from './SelectableContext';
import TabContext from './TabContext';
import { BsPrefixRefForwardingComponent } from './helpers';

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

const propTypes = {
Expand All @@ -28,7 +30,21 @@ const propTypes = {
activeKey: PropTypes.any,
};

const AbstractNav = React.forwardRef(
// TODO: is this correct?
interface AbstractNavProps {
activeKey?: any;
as?: React.ElementType;
getControlledId?: any;
getControllerId?: any;
onKeyDown?: any;
onSelect?: any;
parentOnSelect?: any;
role?: string;
}

type AbstractNav = BsPrefixRefForwardingComponent<'ul', AbstractNavProps>;

const AbstractNav: AbstractNav = React.forwardRef(
(
{
// Need to define the default "as" during prop destructuring to be compatible with styled-components github.com/react-bootstrap/react-bootstrap/issues/3595
Expand All @@ -38,7 +54,7 @@ const AbstractNav = React.forwardRef(
role,
onKeyDown,
...props
},
}: AbstractNavProps,
ref,
) => {
// A ref and forceUpdate for refocus, b/c we only want to trigger when needed
Expand All @@ -58,15 +74,17 @@ const AbstractNav = React.forwardRef(
getControllerId = tabContext.getControllerId;
}

const listNode = useRef(null);
const listNode = useRef<HTMLElement>(null);

const getNextActiveChild = (offset) => {
if (!listNode.current) return null;
const currentListNode = listNode.current;
if (!currentListNode) return null;

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

let index = items.indexOf(activeChild);
const index = items.indexOf(activeChild);
if (index === -1) return null;

let nextIndex = index + offset;
Expand Down Expand Up @@ -107,7 +125,7 @@ const AbstractNav = React.forwardRef(

useEffect(() => {
if (listNode.current && needsRefocusRef.current) {
let activeChild = listNode.current.querySelector(
const activeChild = listNode.current.querySelector<HTMLElement>(
'[data-rb-event-key].active',
);

Expand Down
28 changes: 24 additions & 4 deletions src/AbstractNavItem.js → src/AbstractNavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,27 @@ import useEventCallback from '@restart/hooks/useEventCallback';
import warning from 'warning';
import NavContext from './NavContext';
import SelectableContext, { makeEventKey } from './SelectableContext';
import { BsPrefixRefForwardingComponent } from './helpers';

// TODO: check this
interface AbstractNavItemProps {
active?: boolean;
as: React.ElementType;
className?: string;
disabled?: boolean;
eventKey?: any; // TODO: especially fix this
href?: string;
role?: string;
id?: string;
tabIndex?: number;
onClick?: (e: any) => void;
onSelect?: (navKey: string, e: any) => void;
}

type AbstractNavItem = BsPrefixRefForwardingComponent<
'div',
AbstractNavItemProps
>;

const propTypes = {
id: PropTypes.string,
Expand All @@ -28,18 +49,17 @@ const defaultProps = {
disabled: false,
};

const AbstractNavItem = React.forwardRef(
const AbstractNavItem: AbstractNavItem = React.forwardRef(
(
{
active,
className,
tabIndex,
eventKey,
onSelect,
onClick,
as: Component,
...props
},
}: AbstractNavItemProps,
ref,
) => {
const navKey = makeEventKey(eventKey, props.href);
Expand Down Expand Up @@ -73,7 +93,7 @@ const AbstractNavItem = React.forwardRef(
}

if (props.role === 'tab') {
props.tabIndex = isActive ? tabIndex : -1;
props.tabIndex = isActive ? props.tabIndex : -1;
props['aria-selected'] = isActive;
}

Expand Down
42 changes: 29 additions & 13 deletions src/Accordion.js → src/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ import AccordionToggle from './AccordionToggle';
import SelectableContext from './SelectableContext';
import AccordionCollapse from './AccordionCollapse';
import AccordionContext from './AccordionContext';
import {
BsPrefixPropsWithChildren,
BsPrefixRefForwardingComponent,
SelectCallback,
} from './helpers';

export interface AccordionProps
extends Omit<React.HTMLAttributes<HTMLElement>, 'onSelect'>,
BsPrefixPropsWithChildren {
activeKey?: string;
defaultActiveKey?: string;
onSelect?: SelectCallback;
}

type Accordion = BsPrefixRefForwardingComponent<'div', AccordionProps> & {
Toggle: typeof AccordionToggle;
Collapse: typeof AccordionCollapse;
};

const propTypes = {
/** Set a custom element for this component */
Expand All @@ -22,8 +40,8 @@ const propTypes = {
defaultActiveKey: PropTypes.string,
};

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

bsPrefix = useBootstrapPrefix(bsPrefix, 'accordion');

const finalClassName = classNames(
className,
useBootstrapPrefix(bsPrefix, 'accordion'),
);
return (
<AccordionContext.Provider value={activeKey}>
<SelectableContext.Provider value={onSelect}>
<Component
ref={ref}
{...controlledProps}
className={classNames(className, bsPrefix)}
>
<AccordionContext.Provider value={activeKey || null}>
<SelectableContext.Provider value={onSelect || null}>
<Component ref={ref} {...controlledProps} className={finalClassName}>
{children}
</Component>
</SelectableContext.Provider>
</AccordionContext.Provider>
);
});
}) as unknown) as Accordion;

Accordion.displayName = 'Accordion';
Accordion.propTypes = propTypes;

Accordion.Toggle = AccordionToggle;
Accordion.Collapse = AccordionCollapse;

Expand Down
19 changes: 15 additions & 4 deletions src/AccordionCollapse.js → src/AccordionCollapse.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import React, { useContext } from 'react';
import PropTypes from 'prop-types';

import Collapse from './Collapse';
import Collapse, { CollapseProps } from './Collapse';
import AccordionContext from './AccordionContext';
import { BsPrefixRefForwardingComponent } from './helpers';

export interface AccordionCollapseProps
extends React.PropsWithChildren<CollapseProps> {
eventKey: string;
}

type AccordionCollapse = BsPrefixRefForwardingComponent<
'div',
AccordionCollapseProps
>;

const propTypes = {
/**
Expand All @@ -14,8 +25,8 @@ const propTypes = {
children: PropTypes.element.isRequired,
};

const AccordionCollapse = React.forwardRef(
({ children, eventKey, ...props }, ref) => {
const AccordionCollapse: AccordionCollapse = React.forwardRef<typeof Collapse>(
({ children, eventKey, ...props }: AccordionCollapseProps, ref) => {
const contextEventKey = useContext(AccordionContext);

return (
Expand All @@ -24,7 +35,7 @@ const AccordionCollapse = React.forwardRef(
</Collapse>
);
},
);
) as any;

AccordionCollapse.propTypes = propTypes;
AccordionCollapse.displayName = 'AccordionCollapse';
Expand Down
2 changes: 1 addition & 1 deletion src/AccordionContext.js → src/AccordionContext.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

const context = React.createContext(null);
const context = React.createContext<string | null>(null);
context.displayName = 'AccordionContext';

export default context;
Loading

0 comments on commit dec919b

Please sign in to comment.