From 8decd69dbc2fba999955eb804a35ca32e47bd327 Mon Sep 17 00:00:00 2001 From: Tonny Dong Date: Thu, 16 Apr 2026 22:02:36 -0700 Subject: [PATCH 1/3] chore: upgrade dependencies, add TypeScript support, and restructure build output - Upgrade all dev dependencies (ESLint 9, Jest 30, Prettier 3, Husky 9, etc.) - Replace node-sass with sass, react-test-renderer with @testing-library/react - Migrate ESLint from .eslintrc to flat config (eslint.config.mjs) with TS parser - Add TypeScript: convert all src/ and spec/ files from JS to TS/TSX - Keep Babel for JS output, add tsc for type declarations (dist/types/) - Restructure build output into dist/ (cjs, esm, types, styles.css) - Upgrade Gatsby website to v4 + React 18 with updated plugins and examples - Move spec files to dedicated spec/ directory --- .babelrc | 1 + .eslintignore | 6 - .eslintrc | 7 - .gitignore | 4 +- .husky/pre-commit | 1 + .nvmrc | 2 +- .prettierrc | 7 +- README.md | 38 +- eslint.config.mjs | 53 + package.json | 134 +- .../BaseTable.spec.tsx | 69 +- spec/__snapshots__/BaseTable.spec.tsx.snap | 2612 +++ spec/tsconfig.json | 12 + src/{AutoResizer.js => AutoResizer.tsx} | 25 +- src/{BaseTable.js => BaseTable.tsx} | 807 +- src/{Column.js => Column.tsx} | 10 +- src/{ColumnManager.js => ColumnManager.ts} | 103 +- src/{ColumnResizer.js => ColumnResizer.tsx} | 164 +- src/{ExpandIcon.js => ExpandIcon.tsx} | 47 +- src/{GridTable.js => GridTable.tsx} | 156 +- src/{SortIndicator.js => SortIndicator.tsx} | 11 +- src/{SortOrder.js => SortOrder.ts} | 2 +- src/{TableCell.js => TableCell.tsx} | 13 +- src/TableHeader.js | 81 - src/TableHeader.tsx | 108 + src/TableHeaderCell.js | 15 - src/TableHeaderCell.tsx | 25 + src/{TableHeaderRow.js => TableHeaderRow.tsx} | 34 +- src/TableRow.js | 194 - src/TableRow.tsx | 233 + src/__snapshots__/BaseTable.spec.js.snap | 5003 ----- src/{index.js => index.ts} | 20 + src/types.ts | 129 + src/{utils.js => utils.ts} | 131 +- tsconfig.json | 21 + types/index.d.ts | 666 - website/package.json | 87 +- website/src/components/ActionPanel.js | 4 +- website/src/components/CodeBlock.js | 4 +- website/src/components/CodeEditor.js | 25 +- website/src/components/CodePreview.js | 11 +- website/src/components/Header.js | 21 +- website/src/components/Html.js | 7 +- website/src/components/Playground.js | 5 +- website/src/examples/custom-cell.js | 32 +- website/src/examples/dynamic-row-heights.js | 28 +- website/src/pages/index.js | 10 +- website/src/utils/baseScope.js | 6 +- website/src/utils/urlHash.js | 5 +- website/yarn.lock | 18285 ++++++++-------- yarn.lock | 12379 +++++------ 51 files changed, 18350 insertions(+), 23503 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc create mode 100644 .husky/pre-commit create mode 100644 eslint.config.mjs rename src/BaseTable.spec.js => spec/BaseTable.spec.tsx (54%) create mode 100644 spec/__snapshots__/BaseTable.spec.tsx.snap create mode 100644 spec/tsconfig.json rename src/{AutoResizer.js => AutoResizer.tsx} (64%) rename src/{BaseTable.js => BaseTable.tsx} (63%) rename src/{Column.js => Column.tsx} (90%) rename src/{ColumnManager.js => ColumnManager.ts} (55%) rename src/{ColumnResizer.js => ColumnResizer.tsx} (60%) rename src/{ExpandIcon.js => ExpandIcon.tsx} (60%) rename src/{GridTable.js => GridTable.tsx} (52%) rename src/{SortIndicator.js => SortIndicator.tsx} (73%) rename src/{SortOrder.js => SortOrder.ts} (94%) rename src/{TableCell.js => TableCell.tsx} (58%) delete mode 100644 src/TableHeader.js create mode 100644 src/TableHeader.tsx delete mode 100644 src/TableHeaderCell.js create mode 100644 src/TableHeaderCell.tsx rename src/{TableHeaderRow.js => TableHeaderRow.tsx} (53%) delete mode 100644 src/TableRow.js create mode 100644 src/TableRow.tsx delete mode 100644 src/__snapshots__/BaseTable.spec.js.snap rename src/{index.js => index.ts} (60%) create mode 100644 src/types.ts rename src/{utils.js => utils.ts} (58%) create mode 100644 tsconfig.json delete mode 100644 types/index.d.ts diff --git a/.babelrc b/.babelrc index 84a6e357..ebb14168 100644 --- a/.babelrc +++ b/.babelrc @@ -7,6 +7,7 @@ }, ], "@babel/preset-react", + "@babel/preset-typescript", ], "plugins": [ "@babel/plugin-proposal-class-properties", diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 2a6c1300..00000000 --- a/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -lib/ -es/ -website/ -node_modules/ -package-lock.json -yarn.lock diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index bf42dace..00000000 --- a/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": ["react-app", "plugin:prettier/recommended", "prettier"], - "plugins": ["prettier"], - "rules": { - "react/prop-types": 2 - } -} diff --git a/.gitignore b/.gitignore index 5f6fd19b..5c2b00c4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,7 +12,5 @@ npm-debug.log.* yarn-error.log yarn-error.log.* -styles.css -es/ -lib/ +dist/ coverage/ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..2312dc58 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.nvmrc b/.nvmrc index 5007551b..a45fd52c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -10.16.0 +24 diff --git a/.prettierrc b/.prettierrc index adfa02ea..a5190251 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,7 @@ { - "semi": true, "singleQuote": true, - "trailingComma": "es5", - "printWidth": 120 + "quoteProps": "preserve", + "printWidth": 120, + "arrowParens": "always", + "jsxBracketSameLine": false } diff --git a/README.md b/README.md index b55c3cd6..4fa31575 100644 --- a/README.md +++ b/README.md @@ -19,22 +19,19 @@ yarn add react-base-table ## Usage -```js +```tsx import BaseTable, { Column } from 'react-base-table' -import 'react-base-table/styles.css' -// Important: if you fail to import react-base-table/styles.css then -// BaseTable will not render as advertised in the included examples. -// For advanced styling see link below: -// https://github.com/Autodesk/react-base-table#advance - ... +import 'react-base-table/dist/styles.css' + ... -... ``` +> **Note:** You must import the stylesheet for BaseTable to render correctly. For advanced styling see [below](#advance). + Learn more at the [website](https://autodesk.github.io/react-base-table/) ### unique key @@ -64,11 +61,17 @@ Things getting worse with the introduction of React hooks, we use primitive stat Here is an [example](https://autodesk.github.io/react-base-table/playground#MYewdgzgLgBKA2BXAtpGBeGBzApmHATgIZQ4DCISqEAFAIwAMAlAFCiSwAmJRG2ehEjgAiPGghSQANDABMDZixY4AHgAcQBLjgBmRRPFg0mGAHwwA3ixhxw0GAG1QiMKQIyIOKBRduAunwASjhEwFAAdIieAMpQQjSKNuz2DgCWWGCaOABiLmGp4B5eAJIZWblg+eABmMGhEVE4sfFQBIg4rEl2sGlgAFY4YRRUYEVQxf2D3pSSNTB1YZExcaQ0evCenTAEXogEYDA01jYwADymxydnnKkAbjDQAJ7wOOgWFjBqRJw3YFgAXDAACwyG4QNTwIiPQEAch0LxUMJkfSiUFSOkeFFceCgsPBoRwAFoAEZeADuODwMJgAF8aRcrldTsTEFAoOAYOAyPBUsAANZvYxmB5eHzYgjiEC+QgwADUMDoTHpstOAHoWWzwAzGTZTpDSfBtTrdakwGpWZdjTYoI81K8AETAAAWgz5xJAKntlqtztdOE4b3SmR2FSqYBp3uNXKdRD+rwsOGFnnGZRDeTR4BoOHCcQIuAivv5-qVkauqqNxtKwcTOnTBQOptsI1syC+O1LZ1V+pwho7eqIBorOtOpvNUA7VxtdvQjpd-PdnonJ0LfP9gcmQxmqAjVqu0djuDeifQ5mTEwGm5GWZzRDzXnCK+LO935aX56mMFUrV43DiMHZTaSDAnC6KaqQZmAfZdgOPZDp2Ny3HBpwACoDi8MA6KkKj+sBPBvL+RA0jAQblHW4ATMMkgUK2t7xiRaaVBB9J9pRqBLhY4ScRI1AOAwfjPlaBEAOJeG4gomCetjSgQAnGs44rrhe0zNgA-FJ4owICLggZh+CcLJZZwbqrEHBxXFbpADh0PxMCvlapwmZYnEPhZEAOLINl2caDkWU55kjG5ADMnlGWcjlmS5AUOECIWRmqqHEi8FZqtqrARkAA) to demonstrate -## Browser Support +## TypeScript + +This package is written in TypeScript and ships type declarations out of the box. All public types are exported from the main entry point: + +```ts +import type { ColumnShape, RowData, SortOrderValue } from 'react-base-table' +``` -`BaseTable` is well tested on all modern browsers and IE11. _You have to polyfill `Array.prototype.findIndex` to make it works on IE_ +## Browser Support -**The [examples](https://autodesk.github.io/react-base-table/examples) don't work on IE as they are powered by [react-runner](https://github.com/nihgwu/react-runner) which is a `react-live` like library but only for modern browsers.** +`BaseTable` is tested on all modern browsers (Chrome, Firefox, Safari, Edge). IE is no longer supported. ## Advance @@ -90,7 +93,7 @@ $column-padding: 7.5px; $show-frozen-rows-shadow: false; $show-frozen-columns-shadow: true; -@import '~react-base-table/es/_BaseTable.scss'; +@import 'react-base-table/dist/esm/_BaseTable.scss'; .#{$table-prefix} { &:not(.#{$table-prefix}--show-left-shadow) { @@ -140,7 +143,16 @@ We are using a advanced table component based on `BaseTable` internally, with mu ## Development -We use `Yarn` as the package manager, checkout this repo and run `yarn` under both root folder and `website` folder in install packages, then run `yarn start` to start the demo site powered by `Gatsby` +Requires **Node.js >= 18** (Node 24 recommended). We use `Yarn` as the package manager. + +```bash +yarn install --ignore-scripts +cd website && yarn install +cd .. +yarn start +``` + +This starts the demo site powered by Gatsby. ## Contributing diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..6cec6d06 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,53 @@ +import js from '@eslint/js'; +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import reactPlugin from 'eslint-plugin-react'; +import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; +import importPlugin from 'eslint-plugin-import'; +import prettierPlugin from 'eslint-plugin-prettier'; +import prettierConfig from 'eslint-config-prettier'; + +export default [ + { + ignores: ['dist/', 'website/', 'node_modules/'], + }, + js.configs.recommended, + prettierConfig, + { + files: ['src/**/*.{ts,tsx}', 'spec/**/*.{ts,tsx}'], + plugins: { + react: reactPlugin, + 'jsx-a11y': jsxA11yPlugin, + import: importPlugin, + prettier: prettierPlugin, + }, + languageOptions: { + parser: tsParser, + ecmaVersion: 2020, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + globals: { + ...globals.browser, + ...globals.es2020, + ...globals.jest, + }, + }, + settings: { + react: { + version: 'detect', + }, + }, + rules: { + 'react/prop-types': 2, + 'react/jsx-uses-react': 'error', + 'react/jsx-uses-vars': 'error', + 'prettier/prettier': 'error', + 'no-unused-vars': 'off', + 'no-undef': 'off', + }, + }, +]; diff --git a/package.json b/package.json index bd60e821..31d99157 100644 --- a/package.json +++ b/package.json @@ -2,16 +2,13 @@ "name": "react-base-table", "version": "1.13.5", "description": "a react table component to display large data set with high performance and flexibility", - "main": "lib/index.js", - "module": "es/index.js", - "types": "types/index.d.ts", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "types": "dist/types/index.d.ts", "files": [ - "lib/", - "es/", - "types/", - "styles.css" + "dist/" ], - "author": "Neo Nie ", + "author": "Autodesk, Inc.", "license": "MIT", "repository": { "type": "git", @@ -20,82 +17,87 @@ "scripts": { "start": "cd website && npm start", "deploy": "cd website && npm run deploy", - "lint": "eslint ./src/**/*.js", - "clean": "rimraf lib es styles.css", - "build:js": "cross-env NODE_ENV=production babel src -d lib --ignore '**/*.spec.js','__snapshots__' --copy-files --source-maps", - "build:es": "cross-env BABEL_ENV=es NODE_ENV=production babel src -d es --ignore '**/*.spec.js','__snapshots__' --copy-files --source-maps", - "build:css": "node-sass src/_BaseTable.scss ./styles.css --output-style expanded", - "build": "npm run build:js && npm run build:es && npm run build:css", - "format": "prettier --write 'src/**/*.{js,scss}'", + "lint": "eslint ./src/**/*.{ts,tsx}", + "clean": "rimraf dist", + "build:js": "cross-env NODE_ENV=production babel src --extensions '.ts,.tsx' -d dist/cjs --ignore '**/*.spec.ts','**/*.spec.tsx' --copy-files --source-maps", + "build:es": "cross-env BABEL_ENV=es NODE_ENV=production babel src --extensions '.ts,.tsx' -d dist/esm --ignore '**/*.spec.ts','**/*.spec.tsx' --copy-files --source-maps", + "build:types": "tsc --emitDeclarationOnly", + "build:css": "sass src/_BaseTable.scss dist/styles.css --style expanded --no-source-map", + "build": "npm run build:js && npm run build:es && npm run build:types && npm run build:css", + "format": "prettier --write 'src/**/*.{ts,tsx,scss}'", "prebuild": "npm run clean", - "precommit": "lint-staged", - "prepush": "npm run test", "prepublish": "npm run build && npm run test", - "test": "jest" + "test": "jest", + "prepare": "husky" }, "lint-staged": { - "packages/**/*.scss": [ - "prettier --write", - "git add" + "src/**/*.scss": [ + "prettier --write" ], - "packages/**/*.js": [ + "src/**/*.{ts,tsx}": [ "prettier --write", - "eslint -c .eslintrc", - "git add" + "eslint" ] }, "dependencies": { - "@babel/runtime": "^7.0.0", - "classnames": "^2.2.5", - "memoize-one": "^5.0.0", - "prop-types": "^15.7.0", - "react-virtualized-auto-sizer": "^1.0.2", - "react-window": "^1.8.2" + "@babel/runtime": "^7.29.2", + "classnames": "^2.5.1", + "memoize-one": "^6.0.0", + "prop-types": "^15.8.1", + "react-virtualized-auto-sizer": "^1.0.26", + "react-window": "^1.8.11" }, "peerDependencies": { - "react": "^16.0.0 || ^17.0.0", - "react-dom": "^16.0.0 || ^17.0.0" + "react": ">= 17.0.2 < 20", + "react-dom": ">= 17.0.2 < 20" }, "devDependencies": { - "@babel/cli": "^7.0.0", - "@babel/core": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/preset-env": "^7.0.0", - "@babel/preset-react": "^7.0.0", - "@types/react": "^16.9.46", - "babel-core": "^7.0.0-bridge.0", - "babel-eslint": "^9.0.0", - "babel-jest": "^23.4.2", - "babel-plugin-syntax-trailing-function-commas": "^6.22.0", - "cross-env": "^5.2.0", - "eslint": "^5.6.0", - "eslint-config-prettier": "^3.0.1", - "eslint-config-react-app": "^3.0.8", - "eslint-plugin-flowtype": "^3.4.2", - "eslint-plugin-import": "^2.14.0", - "eslint-plugin-jsx-a11y": "^6.1.1", - "eslint-plugin-prettier": "^2.6.2", - "eslint-plugin-react": "^7.11.1", - "husky": "^0.14.3", - "jest": "^23.5.0", - "lerna": "^3.2.1", - "lint-staged": "^7.2.2", - "node-sass": "^4.9.3", - "prettier": "^1.14.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-test-renderer": "^17.0.2", - "rimraf": "^2.6.2" + "@babel/cli": "^7.28.6", + "@babel/core": "^7.29.0", + "@babel/eslint-parser": "^7.28.6", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", + "@babel/plugin-transform-runtime": "^7.29.0", + "@babel/preset-env": "^7.29.2", + "@babel/preset-react": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "^10.0.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/react": "^16.3.2", + "@types/prop-types": "^15.7.15", + "@types/react": "^19.2.14", + "@types/react-window": "1.8.8", + "@typescript-eslint/parser": "^8.58.2", + "babel-jest": "^30.3.0", + "cross-env": "^10.1.0", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-react": "^7.37.5", + "globals": "^17.5.0", + "husky": "^9.1.7", + "jest": "^30.3.0", + "jest-environment-jsdom": "^30.3.0", + "lint-staged": "^16.4.0", + "prettier": "^3.8.3", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "rimraf": "^6.1.3", + "sass": "^1.98.0", + "typescript": "^6.0.3", + "typescript-eslint": "^8.58.2" }, "jest": { + "testEnvironment": "jsdom", "roots": [ - "/src" + "/spec" ], - "testRegex": ".*.spec\\.js$", + "testRegex": ".*.spec\\.tsx?$", "transform": { - "^.+\\.jsx?$": "babel-jest" + "^.+\\.tsx?$": "babel-jest" }, "transformIgnorePatterns": [ "/node_modules/" diff --git a/src/BaseTable.spec.js b/spec/BaseTable.spec.tsx similarity index 54% rename from src/BaseTable.spec.js rename to spec/BaseTable.spec.tsx index 30cf66fc..32fd07e7 100644 --- a/src/BaseTable.spec.js +++ b/spec/BaseTable.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import renderer from 'react-test-renderer'; +import { render } from '@testing-library/react'; -import BaseTable from './BaseTable'; +import BaseTable from '../src/BaseTable'; const RENDERER = () => null; @@ -33,123 +33,126 @@ const data = [ }, ]; -const Table = props => ; +const Table = (props: any) => ; -describe('Table', function() { +function renderSnapshot(element: React.ReactElement) { + const { container } = render(element); + return container.firstChild; +} + +describe('Table', function () { test('renders correctly', () => { - const tree = renderer.create().toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive className', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive style', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive children', () => { - const tree = renderer - .create( -
- - -
- ) - .toJSON(); + const tree = renderSnapshot( + + + +
, + ); expect(tree).toMatchSnapshot(); }); test('table can receive empty data', () => { - const tree = renderer.create().toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can specific a different rowKey', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive width', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive height', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive rowHeight', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive headerHeight', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can be set to fixed', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can be set to disabled', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can hide the header', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can freeze rows', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive an emptyRenderer callback', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive an headerRenderer callback', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive an rowRenderer callback', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive headerClassName', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive rowClassName', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive expandColumnKey', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive defaultExpandedRowKeys', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); test('table can receive expandedRowKeys', () => { - const tree = renderer.create(
).toJSON(); + const tree = renderSnapshot(
); expect(tree).toMatchSnapshot(); }); }); diff --git a/spec/__snapshots__/BaseTable.spec.tsx.snap b/spec/__snapshots__/BaseTable.spec.tsx.snap new file mode 100644 index 00000000..d9973a59 --- /dev/null +++ b/spec/__snapshots__/BaseTable.spec.tsx.snap @@ -0,0 +1,2612 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`Table renders correctly 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can be set to disabled 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can be set to fixed 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can freeze rows 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+`; + +exports[`Table table can hide the header 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive an emptyRenderer callback 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive an headerRenderer callback 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive an rowRenderer callback 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive children 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive className 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive defaultExpandedRowKeys 1`] = ` +
+
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive empty data 1`] = ` +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive expandColumnKey 1`] = ` +
+
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive expandedRowKeys 1`] = ` +
+
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive headerClassName 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive headerHeight 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive height 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive rowClassName 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive rowHeight 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive style 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can receive width 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; + +exports[`Table table can specific a different rowKey 1`] = ` +
+
+
+
+
+
+
+ 1 +
+
+
+
+ 1 +
+
+
+
+
+
+ 2 +
+
+
+
+ 2 +
+
+
+
+
+
+
+
+
+
+ code +
+
+
+
+ name +
+
+
+
+
+
+
+
+`; diff --git a/spec/tsconfig.json b/spec/tsconfig.json new file mode 100644 index 00000000..f06c13ee --- /dev/null +++ b/spec/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "declaration": false, + "emitDeclarationOnly": false, + "noEmit": true, + "rootDir": "..", + "types": ["jest"] + }, + "include": ["."], + "references": [{ "path": ".." }] +} diff --git a/src/AutoResizer.js b/src/AutoResizer.tsx similarity index 64% rename from src/AutoResizer.js rename to src/AutoResizer.tsx index 9a627b2d..7b7c3779 100644 --- a/src/AutoResizer.js +++ b/src/AutoResizer.tsx @@ -2,27 +2,40 @@ import React from 'react'; import PropTypes from 'prop-types'; import AutoSizer from 'react-virtualized-auto-sizer'; +export interface AutoResizerProps { + className?: string; + width?: number; + height?: number; + children: (size: { width: number; height: number }) => React.ReactNode; + onResize?: (size: { width: number; height: number }) => void; +} + /** * Decorator component that automatically adjusts the width and height of a single child */ -const AutoResizer = ({ className, width, height, children, onResize }) => { +const AutoResizer: React.FC = ({ className, width, height, children, onResize }) => { const disableWidth = typeof width === 'number'; const disableHeight = typeof height === 'number'; if (disableWidth && disableHeight) { return (
- {children({ width, height })} + {children({ width: width!, height: height! })}
); } return ( - - {size => + + {(size: { width: number; height: number }) => children({ - width: disableWidth ? width : size.width, - height: disableHeight ? height : size.height, + width: disableWidth ? width! : size.width, + height: disableHeight ? height! : size.height, }) } diff --git a/src/BaseTable.js b/src/BaseTable.tsx similarity index 63% rename from src/BaseTable.js rename to src/BaseTable.tsx index 3b52cb29..15d37ec1 100644 --- a/src/BaseTable.js +++ b/src/BaseTable.tsx @@ -31,16 +31,31 @@ import { noop, } from './utils'; -const getColumns = memoize((columns, children) => columns || normalizeColumns(children)); - -const getContainerStyle = (width, maxWidth, height) => ({ +import type { + ColumnShape, + RowData, + RowKey, + ScrollArgs, + RowsRenderedArgs, + SortByShape, + SortState, + TableComponents, + RowEventHandlers, +} from './types'; + +const getColumns = memoize( + (columns: ColumnShape[] | undefined, children: React.ReactNode): ColumnShape[] => + columns || normalizeColumns(children), +); + +const getContainerStyle = (width: number, maxWidth: number, height: number): React.CSSProperties => ({ width, maxWidth, height, overflow: 'hidden', }); -const DEFAULT_COMPONENTS = { +const DEFAULT_COMPONENTS: Record> = { TableCell, TableHeaderCell, ExpandIcon, @@ -49,14 +64,222 @@ const DEFAULT_COMPONENTS = { const RESIZE_THROTTLE_WAIT = 50; -// used for memoization -const EMPTY_ARRAY = []; +const EMPTY_ARRAY: any[] = []; + +export interface BaseTableProps { + classPrefix?: string; + className?: string; + style?: React.CSSProperties; + children?: React.ReactNode; + columns?: ColumnShape[]; + data: RowData[]; + frozenData?: RowData[]; + rowKey: string | number; + width: number; + height?: number; + maxHeight?: number; + rowHeight?: number; + estimatedRowHeight?: number | ((args: { rowData: RowData; rowIndex: number }) => number); + headerHeight: number | number[]; + footerHeight?: number; + fixed?: boolean; + disabled?: boolean; + overlayRenderer?: React.ComponentType | React.ReactElement; + emptyRenderer?: React.ComponentType | React.ReactElement; + footerRenderer?: React.ComponentType | React.ReactElement; + headerRenderer?: React.ComponentType | React.ReactElement; + rowRenderer?: React.ComponentType | React.ReactElement; + headerClassName?: string | ((args: { columns: ColumnShape[]; headerIndex: number }) => string); + rowClassName?: string | ((args: { columns: ColumnShape[]; rowData: RowData; rowIndex: number }) => string); + headerProps?: Record | ((args: { columns: ColumnShape[]; headerIndex: number }) => Record); + headerCellProps?: + | Record + | ((args: { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + headerIndex: number; + }) => Record); + rowProps?: + | Record + | ((args: { columns: ColumnShape[]; rowData: RowData; rowIndex: number }) => Record); + cellProps?: + | Record + | ((args: { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: RowData; + rowIndex: number; + }) => Record); + expandIconProps?: + | Record + | ((args: { + rowData: RowData; + rowIndex: number; + depth: number; + expandable: boolean; + expanded: boolean; + }) => Record); + expandColumnKey?: string; + defaultExpandedRowKeys?: RowKey[]; + expandedRowKeys?: RowKey[]; + onRowExpand?: (args: { expanded: boolean; rowData: RowData; rowIndex: number; rowKey: RowKey }) => void; + onExpandedRowsChange?: (expandedRowKeys: RowKey[]) => void; + sortBy?: SortByShape; + sortState?: SortState; + onColumnSort?: (args: { column: ColumnShape; key: string; order: string }) => void; + onColumnResize?: (args: { column: ColumnShape; width: number }) => void; + onColumnResizeEnd?: (args: { column: ColumnShape; width: number }) => void; + useIsScrolling?: boolean; + overscanRowCount?: number; + getScrollbarSize?: () => number; + onScroll?: (args: ScrollArgs) => void; + onEndReached?: (args: { distanceFromEnd: number }) => void; + onEndReachedThreshold?: number; + onRowsRendered?: (args: RowsRenderedArgs) => void; + onScrollbarPresenceChange?: (args: { size: number; horizontal: boolean; vertical: boolean }) => void; + rowEventHandlers?: RowEventHandlers; + ignoreFunctionInColumnCompare?: boolean; + components?: TableComponents; +} + +interface BaseTableState { + scrollbarSize: number; + hoveredRowKey: RowKey | null; + resizingKey: string | null; + resizingWidth: number; + expandedRowKeys: RowKey[]; +} /** * React table component */ -class BaseTable extends React.PureComponent { - constructor(props) { +class BaseTable extends React.PureComponent { + static Column = Column; + static PlaceholderKey = ColumnManager.PlaceholderKey; + + static defaultProps = { + classPrefix: 'BaseTable', + rowKey: 'id', + data: [], + frozenData: [], + fixed: false, + headerHeight: 50, + rowHeight: 50, + footerHeight: 0, + defaultExpandedRowKeys: [], + sortBy: {}, + useIsScrolling: false, + overscanRowCount: 1, + onEndReachedThreshold: 500, + getScrollbarSize: defaultGetScrollbarSize, + ignoreFunctionInColumnCompare: true, + + onScroll: noop, + onRowsRendered: noop, + onScrollbarPresenceChange: noop, + onRowExpand: noop, + onExpandedRowsChange: noop, + onColumnSort: noop, + onColumnResize: noop, + onColumnResizeEnd: noop, + }; + + static propTypes = { + classPrefix: PropTypes.string, + className: PropTypes.string, + style: PropTypes.object, + children: PropTypes.node, + columns: PropTypes.arrayOf(PropTypes.shape(Column.propTypes as any)), + data: PropTypes.array.isRequired, + frozenData: PropTypes.array, + rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number, + maxHeight: PropTypes.number, + rowHeight: PropTypes.number, + estimatedRowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, + footerHeight: PropTypes.number, + fixed: PropTypes.bool, + disabled: PropTypes.bool, + overlayRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), + emptyRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), + footerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), + headerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), + rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), + headerClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + headerProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + headerCellProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + rowProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + cellProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + expandIconProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), + expandColumnKey: PropTypes.string, + defaultExpandedRowKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + expandedRowKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), + onRowExpand: PropTypes.func, + onExpandedRowsChange: PropTypes.func, + sortBy: PropTypes.shape({ + key: PropTypes.string, + order: PropTypes.oneOf([SortOrder.ASC, SortOrder.DESC]), + }), + sortState: PropTypes.object, + onColumnSort: PropTypes.func, + onColumnResize: PropTypes.func, + onColumnResizeEnd: PropTypes.func, + useIsScrolling: PropTypes.bool, + overscanRowCount: PropTypes.number, + getScrollbarSize: PropTypes.func, + onScroll: PropTypes.func, + onEndReached: PropTypes.func, + onEndReachedThreshold: PropTypes.number, + onRowsRendered: PropTypes.func, + onScrollbarPresenceChange: PropTypes.func, + rowEventHandlers: PropTypes.object, + ignoreFunctionInColumnCompare: PropTypes.bool, + components: PropTypes.shape({ + TableCell: PropTypes.elementType, + TableHeaderCell: PropTypes.elementType, + ExpandIcon: PropTypes.elementType, + SortIndicator: PropTypes.elementType, + }), + }; + + columnManager: ColumnManager; + tableNode: HTMLDivElement | null = null; + table: GridTable | null = null; + leftTable: GridTable | null = null; + rightTable: GridTable | null = null; + + _isResetting: boolean; + _resetIndex: number | null; + _rowHeightMap: Record; + _rowHeightMapBuffer: Record; + _mainRowHeightMap: Record; + _leftRowHeightMap: Record; + _rightRowHeightMap: Record; + _scroll: { scrollLeft: number; scrollTop: number }; + _scrollHeight: number; + _lastScannedRowIndex: number; + _hasDataChangedSinceEndReached: boolean; + _data: RowData[]; + _depthMap: Record; + _horizontalScrollbarSize: number; + _verticalScrollbarSize: number; + _scrollbarPresenceChanged: boolean; + _totalRowsHeight: number; + + _getLeftTableContainerStyle: typeof getContainerStyle; + _getRightTableContainerStyle: typeof getContainerStyle; + _flattenOnKeys: (tree: RowData[], keys: RowKey[], dataKey: string | number) => RowData[]; + _resetColumnManager: (columns: ColumnShape[], fixed: boolean) => void; + _getEstimatedTotalRowsHeight: typeof getEstimatedTotalRowsHeight; + _getRowHeight: (rowIndex: number) => number; + _updateRowHeights: () => void; + + constructor(props: BaseTableProps) { super(props); const { columns, children, defaultExpandedRowKeys } = props; @@ -65,9 +288,9 @@ class BaseTable extends React.PureComponent { hoveredRowKey: null, resizingKey: null, resizingWidth: 0, - expandedRowKeys: cloneArray(defaultExpandedRowKeys), + expandedRowKeys: cloneArray(defaultExpandedRowKeys || []), }; - this.columnManager = new ColumnManager(getColumns(columns, children), props.fixed); + this.columnManager = new ColumnManager(getColumns(columns, children), props.fixed!); this._setContainerRef = this._setContainerRef.bind(this); this._setMainTableRef = this._setMainTableRef.bind(this); @@ -94,12 +317,12 @@ class BaseTable extends React.PureComponent { this._getLeftTableContainerStyle = memoize(getContainerStyle); this._getRightTableContainerStyle = memoize(getContainerStyle); - this._flattenOnKeys = memoize((tree, keys, dataKey) => { + this._flattenOnKeys = memoize((tree: RowData[], keys: RowKey[], dataKey: string | number) => { this._depthMap = {}; - return flattenOnKeys(tree, keys, this._depthMap, dataKey); + return flattenOnKeys(tree, keys, this._depthMap, dataKey as string); }); this._resetColumnManager = memoize( - (columns, fixed) => { + (columns: ColumnShape[], fixed: boolean) => { this.columnManager.reset(columns, fixed); if (this.props.estimatedRowHeight && fixed) { @@ -111,7 +334,7 @@ class BaseTable extends React.PureComponent { } } }, - (newArgs, lastArgs) => isObjectEqual(newArgs, lastArgs, this.props.ignoreFunctionInColumnCompare) + (newArgs: any, lastArgs: any) => isObjectEqual(newArgs, lastArgs, this.props.ignoreFunctionInColumnCompare), ); this._isResetting = false; @@ -122,11 +345,11 @@ class BaseTable extends React.PureComponent { this._leftRowHeightMap = {}; this._rightRowHeightMap = {}; this._getEstimatedTotalRowsHeight = memoize(getEstimatedTotalRowsHeight); - this._getRowHeight = this._getRowHeight.bind(this); + this._getRowHeight = this.__getRowHeight.bind(this); this._updateRowHeights = debounce(() => { this._isResetting = true; this._rowHeightMap = { ...this._rowHeightMap, ...this._rowHeightMapBuffer }; - this.resetAfterRowIndex(this._resetIndex, false); + this.resetAfterRowIndex(this._resetIndex!, false); this._rowHeightMapBuffer = {}; this._resetIndex = null; this.forceUpdateTable(); @@ -145,26 +368,27 @@ class BaseTable extends React.PureComponent { this._horizontalScrollbarSize = 0; this._verticalScrollbarSize = 0; this._scrollbarPresenceChanged = false; + this._totalRowsHeight = 0; } /** * Get the DOM node of the table */ - getDOMNode() { + getDOMNode(): HTMLDivElement | null { return this.tableNode; } /** * Get the column manager */ - getColumnManager() { + getColumnManager(): ColumnManager { return this.columnManager; } /** * Get internal `expandedRowKeys` state */ - getExpandedRowKeys() { + getExpandedRowKeys(): RowKey[] { const { expandedRowKeys } = this.props; return expandedRowKeys !== undefined ? expandedRowKeys || EMPTY_ARRAY : this.state.expandedRowKeys; } @@ -183,7 +407,7 @@ class BaseTable extends React.PureComponent { /** * Get the total height of all rows, including expanded rows. */ - getTotalRowsHeight() { + getTotalRowsHeight(): number { const { rowHeight, estimatedRowHeight } = this.props; if (estimatedRowHeight) { @@ -191,22 +415,18 @@ class BaseTable extends React.PureComponent { ? this.table.getTotalRowsHeight() : this._getEstimatedTotalRowsHeight(this._data, estimatedRowHeight); } - return this._data.length * rowHeight; + return this._data.length * rowHeight!; } /** * Get the total width of all columns. */ - getTotalColumnsWidth() { + getTotalColumnsWidth(): number { return this.columnManager.getColumnsWidth(); } /** * Forcefully re-render the inner Grid component. - * - * Calling `forceUpdate` on `Table` may not re-render the inner Grid since it uses `shallowCompare` as a performance optimization. - * Use this method if you want to manually trigger a re-render. - * This may be appropriate if the underlying row data has changed but the row sizes themselves have not. */ forceUpdateTable() { this.table && this.table.forceUpdateTable(); @@ -215,12 +435,9 @@ class BaseTable extends React.PureComponent { } /** - * Reset cached offsets for positioning after a specific rowIndex, should be used only in dynamic mode(estimatedRowHeight is provided) - * - * @param {number} rowIndex - * @param {boolean} shouldForceUpdate + * Reset cached offsets for positioning after a specific rowIndex */ - resetAfterRowIndex(rowIndex = 0, shouldForceUpdate = true) { + resetAfterRowIndex(rowIndex: number = 0, shouldForceUpdate: boolean = true) { if (!this.props.estimatedRowHeight) return; this.table && this.table.resetAfterRowIndex(rowIndex, shouldForceUpdate); @@ -229,7 +446,7 @@ class BaseTable extends React.PureComponent { } /** - * Reset row height cache, useful if `data` changed entirely, should be used only in dynamic mode(estimatedRowHeight is provided) + * Reset row height cache */ resetRowHeightCache() { if (!this.props.estimatedRowHeight) return; @@ -244,11 +461,8 @@ class BaseTable extends React.PureComponent { /** * Scroll to the specified offset. - * Useful for animating position changes. - * - * @param {object} offset */ - scrollToPosition(offset) { + scrollToPosition(offset: { scrollLeft: number; scrollTop: number }) { this._scroll = offset; this.table && this.table.scrollToPosition(offset); @@ -258,10 +472,8 @@ class BaseTable extends React.PureComponent { /** * Scroll to the specified offset vertically. - * - * @param {number} scrollTop */ - scrollToTop(scrollTop) { + scrollToTop(scrollTop: number) { this._scroll.scrollTop = scrollTop; this.table && this.table.scrollToPosition(this._scroll); @@ -271,10 +483,8 @@ class BaseTable extends React.PureComponent { /** * Scroll to the specified offset horizontally. - * - * @param {number} scrollLeft */ - scrollToLeft(scrollLeft) { + scrollToLeft(scrollLeft: number) { this._scroll.scrollLeft = scrollLeft; this.table && this.table.scrollToPosition(this._scroll); @@ -282,19 +492,8 @@ class BaseTable extends React.PureComponent { /** * Scroll to the specified row. - * By default, the table will scroll as little as possible to ensure the row is visible. - * You can control the alignment of the row though by specifying an align property. Acceptable values are: - * - * - `auto` (default) - Scroll as little as possible to ensure the row is visible. - * - `smart` - Same as `auto` if it is less than one viewport away, or it's the same as`center`. - * - `center` - Center align the row within the table. - * - `end` - Align the row to the bottom side of the table. - * - `start` - Align the row to the top side of the table. - * - * @param {number} rowIndex - * @param {string} align */ - scrollToRow(rowIndex = 0, align = 'auto') { + scrollToRow(rowIndex: number = 0, align: string = 'auto') { this.table && this.table.scrollToRow(rowIndex, align); this.leftTable && this.leftTable.scrollToRow(rowIndex, align); this.rightTable && this.rightTable.scrollToRow(rowIndex, align); @@ -302,12 +501,8 @@ class BaseTable extends React.PureComponent { /** * Set `expandedRowKeys` manually. - * This method is available only if `expandedRowKeys` is uncontrolled. - * - * @param {array} expandedRowKeys */ - setExpandedRowKeys(expandedRowKeys) { - // if `expandedRowKeys` is controlled + setExpandedRowKeys(expandedRowKeys: RowKey[]) { if (this.props.expandedRowKeys !== undefined) return; this.setState({ @@ -315,24 +510,48 @@ class BaseTable extends React.PureComponent { }); } - renderExpandIcon({ rowData, rowIndex, depth, onExpand }) { + renderExpandIcon({ + rowData, + rowIndex, + depth, + onExpand, + }: { + rowData: RowData; + rowIndex: number; + depth: number; + onExpand: (expanded: boolean) => void; + }) { const { rowKey, expandColumnKey, expandIconProps } = this.props; if (!expandColumnKey) return null; const expandable = rowIndex >= 0 && hasChildren(rowData); - const expanded = rowIndex >= 0 && this.getExpandedRowKeys().indexOf(rowData[rowKey]) >= 0; + const expanded = rowIndex >= 0 && this.getExpandedRowKeys().indexOf(rowData[rowKey as string]) >= 0; const extraProps = callOrReturn(expandIconProps, { rowData, rowIndex, depth, expandable, expanded }); - const ExpandIcon = this._getComponent('ExpandIcon'); + const ExpandIconComp = this._getComponent('ExpandIcon'); - return ; + return ( + + ); } - renderRow({ isScrolling, columns, rowData, rowIndex, style }) { + renderRow({ + isScrolling, + columns, + rowData, + rowIndex, + style, + }: { + isScrolling: boolean; + columns: ColumnShape[]; + rowData: RowData; + rowIndex: number; + style: React.CSSProperties; + }) { const { rowClassName, rowRenderer, rowEventHandlers, expandColumnKey, estimatedRowHeight } = this.props; const rowClass = callOrReturn(rowClassName, { columns, rowData, rowIndex }); const extraProps = callOrReturn(this.props.rowProps, { columns, rowData, rowIndex }); - const rowKey = rowData[this.props.rowKey]; + const rowKey = rowData[this.props.rowKey as string]; const depth = this._depthMap[rowKey] || 0; const className = cn(this._prefixClass('row'), rowClass, { @@ -359,25 +578,23 @@ class BaseTable extends React.PureComponent { depth, rowEventHandlers, rowRenderer, - // for frozen rows we use fixed rowHeight estimatedRowHeight: rowIndex >= 0 ? estimatedRowHeight : undefined, getIsResetting: this._getIsResetting, cellRenderer: this.renderRowCell, expandIconRenderer: this.renderExpandIcon, onRowExpand: this._handleRowExpand, - // for fixed table, we need to sync the hover state across the inner tables - onRowHover: hasFrozenColumns ? this._handleRowHover : null, + onRowHover: hasFrozenColumns ? this._handleRowHover : undefined, onRowHeightChange: hasFrozenColumns ? this._handleFrozenRowHeightChange : this._handleRowHeightChange, }; return ; } - renderRowCell({ isScrolling, columns, column, columnIndex, rowData, rowIndex, expandIcon }) { + renderRowCell({ isScrolling, columns, column, columnIndex, rowData, rowIndex, expandIcon }: any) { if (column[ColumnManager.PlaceholderKey]) { return (
@@ -385,13 +602,16 @@ class BaseTable extends React.PureComponent { } const { className, dataKey, dataGetter, cellRenderer } = column; - const TableCell = this._getComponent('TableCell'); + const TableCellComp = this._getComponent('TableCell'); const cellData = dataGetter ? dataGetter({ columns, column, columnIndex, rowData, rowIndex }) : getValue(rowData, dataKey); const cellProps = { isScrolling, cellData, columns, column, columnIndex, rowData, rowIndex, container: this }; - const cell = renderElement(cellRenderer || , cellProps); + const cell = renderElement( + cellRenderer || , + cellProps, + ); const cellCls = callOrReturn(className, { cellData, columns, column, columnIndex, rowData, rowIndex }); const cls = cn(this._prefixClass('row-cell'), cellCls, { @@ -405,7 +625,7 @@ class BaseTable extends React.PureComponent { return ( ; } - renderHeaderCell({ columns, column, columnIndex, headerIndex, expandIcon }) { + renderHeaderCell({ columns, column, columnIndex, headerIndex, expandIcon }: any) { if (column[ColumnManager.PlaceholderKey]) { return (
, - cellProps + headerRenderer || , + cellProps, ); - let sorting, sortOrder; + let sorting: boolean, sortOrder: string; if (sortState) { const order = sortState[column.key]; sorting = order === SortOrder.ASC || order === SortOrder.DESC; sortOrder = sorting ? order : SortOrder.ASC; } else { - sorting = column.key === sortBy.key; - sortOrder = sorting ? sortBy.order : SortOrder.ASC; + sorting = column.key === sortBy!.key; + sortOrder = sorting ? sortBy!.order! : SortOrder.ASC; } const cellCls = callOrReturn(headerClassName, { columns, column, columnIndex, headerIndex }); @@ -501,7 +729,7 @@ class BaseTable extends React.PureComponent { {expandIcon} {cell} {column.sortable && ( - column.key === resizingKey); + const idx = columns.findIndex((column) => column.key === resizingKey); const column = columns[idx]; const { width: columnWidth, frozen } = column; const leftWidth = this.columnManager.recomputeColumnsWidth(columns.slice(0, idx)); @@ -697,28 +924,27 @@ class BaseTable extends React.PureComponent { classPrefix, estimatedRowHeight, } = this.props; - this._resetColumnManager(getColumns(columns, children), fixed); + this._resetColumnManager(getColumns(columns, children), fixed!); const _data = expandColumnKey ? this._flattenOnKeys(data, this.getExpandedRowKeys(), this.props.rowKey) : data; if (this._data !== _data) { this.resetAfterRowIndex(0, false); this._data = _data; } - // should be after `this._data` assigned this._calcScrollbarSizes(); this._totalRowsHeight = this.getTotalRowsHeight(); - const containerStyle = { + const containerStyle: React.CSSProperties = { ...style, width, - height: this._getTableHeight() + footerHeight, + height: this._getTableHeight() + footerHeight!, position: 'relative', }; const cls = cn(classPrefix, className, { [`${classPrefix}--fixed`]: fixed, [`${classPrefix}--expandable`]: !!expandColumnKey, [`${classPrefix}--empty`]: data.length === 0, - [`${classPrefix}--has-frozen-rows`]: frozenData.length > 0, + [`${classPrefix}--has-frozen-rows`]: frozenData!.length > 0, [`${classPrefix}--has-frozen-columns`]: this.columnManager.hasFrozenColumns(), [`${classPrefix}--disabled`]: disabled, [`${classPrefix}--dynamic`]: !!estimatedRowHeight, @@ -737,13 +963,13 @@ class BaseTable extends React.PureComponent { } componentDidMount() { - const scrollbarSize = this.props.getScrollbarSize(); + const scrollbarSize = this.props.getScrollbarSize!(); if (scrollbarSize > 0) { this.setState({ scrollbarSize }); } } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(prevProps: BaseTableProps, prevState: BaseTableState) { const { data, height, maxHeight, estimatedRowHeight } = this.props; if (data !== prevProps.data) { this._lastScannedRowIndex = -1; @@ -762,45 +988,44 @@ class BaseTable extends React.PureComponent { } } - _prefixClass(className) { + _prefixClass(className: string): string { return `${this.props.classPrefix}__${className}`; } - _setContainerRef(ref) { + _setContainerRef(ref: HTMLDivElement | null) { this.tableNode = ref; } - _setMainTableRef(ref) { + _setMainTableRef(ref: GridTable | null) { this.table = ref; } - _setLeftTableRef(ref) { + _setLeftTableRef(ref: GridTable | null) { this.leftTable = ref; } - _setRightTableRef(ref) { + _setRightTableRef(ref: GridTable | null) { this.rightTable = ref; } - _getComponent(name) { - if (this.props.components && this.props.components[name]) return this.props.components[name]; + _getComponent(name: string): React.ComponentType { + if (this.props.components && (this.props.components as any)[name]) return (this.props.components as any)[name]; return DEFAULT_COMPONENTS[name]; } - // for dynamic row height - _getRowHeight(rowIndex) { + __getRowHeight(rowIndex: number): number { const { estimatedRowHeight, rowKey } = this.props; return ( - this._rowHeightMap[this._data[rowIndex][rowKey]] || - callOrReturn(estimatedRowHeight, { rowData: this._data[rowIndex], rowIndex }) + this._rowHeightMap[this._data[rowIndex][rowKey as string]] || + callOrReturn(estimatedRowHeight!, { rowData: this._data[rowIndex], rowIndex }) ); } - _getIsResetting() { + _getIsResetting(): boolean { return this._isResetting; } - _getHeaderHeight() { + _getHeaderHeight(): number { const { headerHeight } = this.props; if (Array.isArray(headerHeight)) { return headerHeight.reduce((sum, height) => sum + height, 0); @@ -808,36 +1033,35 @@ class BaseTable extends React.PureComponent { return headerHeight; } - _getFrozenRowsHeight() { + _getFrozenRowsHeight(): number { const { frozenData, rowHeight } = this.props; - return frozenData.length * rowHeight; + return frozenData!.length * rowHeight!; } - _getTableHeight() { + _getTableHeight(): number { const { height, maxHeight, footerHeight } = this.props; - let tableHeight = height - footerHeight; + let tableHeight = height! - footerHeight!; - if (maxHeight > 0) { + if (maxHeight! > 0) { const frozenRowsHeight = this._getFrozenRowsHeight(); const totalRowsHeight = this.getTotalRowsHeight(); const headerHeight = this._getHeaderHeight(); const totalHeight = headerHeight + frozenRowsHeight + totalRowsHeight + this._horizontalScrollbarSize; - tableHeight = Math.min(totalHeight, maxHeight - footerHeight); + tableHeight = Math.min(totalHeight, maxHeight! - footerHeight!); } return tableHeight; } - _getBodyHeight() { + _getBodyHeight(): number { return this._getTableHeight() - this._getHeaderHeight() - this._getFrozenRowsHeight(); } - _getFrozenContainerHeight() { + _getFrozenContainerHeight(): number { const { maxHeight } = this.props; const tableHeight = this._getTableHeight() - (this._data.length > 0 ? this._horizontalScrollbarSize : 0); - // in auto height mode tableHeight = totalHeight - if (maxHeight > 0) return tableHeight; + if (maxHeight! > 0) return tableHeight; const totalHeight = this.getTotalRowsHeight() + this._getHeaderHeight() + this._getFrozenRowsHeight(); return Math.min(tableHeight, totalHeight); @@ -857,7 +1081,6 @@ class BaseTable extends React.PureComponent { this._horizontalScrollbarSize = 0; this._verticalScrollbarSize = 0; } else { - // we have to set `this._horizontalScrollbarSize` before calling `this._getBodyHeight` if (!fixed || totalColumnsWidth <= width - scrollbarSize) { this._horizontalScrollbarSize = 0; this._verticalScrollbarSize = totalRowsHeight > this._getBodyHeight() ? scrollbarSize : 0; @@ -890,7 +1113,7 @@ class BaseTable extends React.PureComponent { const { onScrollbarPresenceChange } = this.props; this._scrollbarPresenceChanged = false; - onScrollbarPresenceChange({ + onScrollbarPresenceChange!({ size: this.state.scrollbarSize, horizontal: this._horizontalScrollbarSize > 0, vertical: this._verticalScrollbarSize > 0, @@ -908,7 +1131,7 @@ class BaseTable extends React.PureComponent { const distanceFromEnd = scrollHeight - scrollTop - clientHeight + this._horizontalScrollbarSize; if ( this._lastScannedRowIndex >= 0 && - distanceFromEnd <= onEndReachedThreshold && + distanceFromEnd <= onEndReachedThreshold! && (this._hasDataChangedSinceEndReached || scrollHeight !== this._scrollHeight) ) { this._hasDataChangedSinceEndReached = false; @@ -917,23 +1140,23 @@ class BaseTable extends React.PureComponent { } } - _handleScroll(args) { + _handleScroll(args: any) { const lastScrollTop = this._scroll.scrollTop; this.scrollToPosition(args); - this.props.onScroll(args); + this.props.onScroll!(args); if (args.scrollTop > lastScrollTop) this._maybeCallOnEndReached(); } - _handleVerticalScroll({ scrollTop }) { + _handleVerticalScroll({ scrollTop }: { scrollTop: number }) { const lastScrollTop = this._scroll.scrollTop; if (scrollTop !== lastScrollTop) this.scrollToTop(scrollTop); if (scrollTop > lastScrollTop) this._maybeCallOnEndReached(); } - _handleRowsRendered(args) { - this.props.onRowsRendered(args); + _handleRowsRendered(args: RowsRenderedArgs) { + this.props.onRowsRendered!(args); if (args.overscanStopIndex > this._lastScannedRowIndex) { this._lastScannedRowIndex = args.overscanStopIndex; @@ -941,37 +1164,46 @@ class BaseTable extends React.PureComponent { } } - _handleRowHover({ hovered, rowKey }) { + _handleRowHover({ hovered, rowKey }: { hovered: boolean; rowKey: RowKey }) { this.setState({ hoveredRowKey: hovered ? rowKey : null }); } - _handleRowExpand({ expanded, rowData, rowIndex, rowKey }) { + _handleRowExpand({ + expanded, + rowData, + rowIndex, + rowKey, + }: { + expanded: boolean; + rowData: RowData; + rowIndex: number; + rowKey: RowKey; + }) { const expandedRowKeys = cloneArray(this.getExpandedRowKeys()); if (expanded) { - if (!expandedRowKeys.indexOf(rowKey) >= 0) expandedRowKeys.push(rowKey); + if (expandedRowKeys.indexOf(rowKey) < 0) expandedRowKeys.push(rowKey); } else { const index = expandedRowKeys.indexOf(rowKey); if (index > -1) { expandedRowKeys.splice(index, 1); } } - // if `expandedRowKeys` is uncontrolled, update internal state if (this.props.expandedRowKeys === undefined) { this.setState({ expandedRowKeys }); } - this.props.onRowExpand({ expanded, rowData, rowIndex, rowKey }); - this.props.onExpandedRowsChange(expandedRowKeys); + this.props.onRowExpand!({ expanded, rowData, rowIndex, rowKey }); + this.props.onExpandedRowsChange!(expandedRowKeys); } - _handleColumnResize({ key }, width) { + _handleColumnResize({ key }: { key: string }, width: number) { this.columnManager.setColumnWidth(key, width); this.setState({ resizingWidth: width }); const column = this.columnManager.getColumn(key); - this.props.onColumnResize({ column, width }); + this.props.onColumnResize!({ column, width }); } - _handleColumnResizeStart({ key }) { + _handleColumnResizeStart({ key }: { key: string }) { this.setState({ resizingKey: key }); } @@ -982,25 +1214,25 @@ class BaseTable extends React.PureComponent { if (!resizingKey || !resizingWidth) return; const column = this.columnManager.getColumn(resizingKey); - this.props.onColumnResizeEnd({ column, width: resizingWidth }); + this.props.onColumnResizeEnd!({ column, width: resizingWidth }); } - _handleColumnSort(event) { - const key = event.currentTarget.dataset.key; + _handleColumnSort(event: React.MouseEvent) { + const key = (event.currentTarget as HTMLElement).dataset.key!; const { sortBy, sortState, onColumnSort } = this.props; - let order = SortOrder.ASC; + let order: string = SortOrder.ASC; if (sortState) { order = sortState[key] === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; - } else if (key === sortBy.key) { - order = sortBy.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; + } else if (key === sortBy!.key) { + order = sortBy!.order === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC; } const column = this.columnManager.getColumn(key); - onColumnSort({ column, key, order }); + onColumnSort!({ column, key, order }); } - _handleFrozenRowHeightChange(rowKey, size, rowIndex, frozen) { + _handleFrozenRowHeightChange(rowKey: RowKey, size: number, rowIndex: number, frozen: any) { if (!frozen) { this._mainRowHeightMap[rowKey] = size; } else if (frozen === FrozenDirection.RIGHT) { @@ -1012,7 +1244,7 @@ class BaseTable extends React.PureComponent { const height = Math.max( this._mainRowHeightMap[rowKey] || 0, this._leftRowHeightMap[rowKey] || 0, - this._rightRowHeightMap[rowKey] || 0 + this._rightRowHeightMap[rowKey] || 0, ); if (this._rowHeightMap[rowKey] !== height) { @@ -1020,7 +1252,7 @@ class BaseTable extends React.PureComponent { } } - _handleRowHeightChange(rowKey, size, rowIndex) { + _handleRowHeightChange(rowKey: RowKey, size: number, rowIndex: number) { if (this._resetIndex === null) this._resetIndex = rowIndex; else if (this._resetIndex > rowIndex) this._resetIndex = rowIndex; @@ -1029,289 +1261,4 @@ class BaseTable extends React.PureComponent { } } -BaseTable.Column = Column; -BaseTable.PlaceholderKey = ColumnManager.PlaceholderKey; - -BaseTable.defaultProps = { - classPrefix: 'BaseTable', - rowKey: 'id', - data: [], - frozenData: [], - fixed: false, - headerHeight: 50, - rowHeight: 50, - footerHeight: 0, - defaultExpandedRowKeys: [], - sortBy: {}, - useIsScrolling: false, - overscanRowCount: 1, - onEndReachedThreshold: 500, - getScrollbarSize: defaultGetScrollbarSize, - ignoreFunctionInColumnCompare: true, - - onScroll: noop, - onRowsRendered: noop, - onScrollbarPresenceChange: noop, - onRowExpand: noop, - onExpandedRowsChange: noop, - onColumnSort: noop, - onColumnResize: noop, - onColumnResizeEnd: noop, -}; - -BaseTable.propTypes = { - /** - * Prefix for table's inner className - */ - classPrefix: PropTypes.string, - /** - * Class name for the table - */ - className: PropTypes.string, - /** - * Custom style for the table - */ - style: PropTypes.object, - /** - * A collection of Column - */ - children: PropTypes.node, - /** - * Columns for the table - */ - columns: PropTypes.arrayOf(PropTypes.shape(Column.propTypes)), - /** - * The data for the table - */ - data: PropTypes.array.isRequired, - /** - * The data be frozen to top, `rowIndex` is negative and started from `-1` - */ - frozenData: PropTypes.array, - /** - * The key field of each data item - */ - rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - /** - * The width of the table - */ - width: PropTypes.number.isRequired, - /** - * The height of the table, will be ignored if `maxHeight` is set - */ - height: PropTypes.number, - /** - * The max height of the table, the table's height will auto change when data changes, - * will turns to vertical scroll if reaches the max height - */ - maxHeight: PropTypes.number, - /** - * The height of each table row, will be only used by frozen rows if `estimatedRowHeight` is set - */ - rowHeight: PropTypes.number, - /** - * Estimated row height, the real height will be measure dynamically according to the content - * The callback is of the shape of `({ rowData, rowIndex }) => number` - */ - estimatedRowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - /** - * The height of the table header, set to 0 to hide the header, could be an array to render multi headers. - */ - headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, - /** - * The height of the table footer - */ - footerHeight: PropTypes.number, - /** - * Whether the width of the columns are fixed or flexible - */ - fixed: PropTypes.bool, - /** - * Whether the table is disabled - */ - disabled: PropTypes.bool, - /** - * Custom renderer on top of the table component - */ - overlayRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - /** - * Custom renderer when the length of data is 0 - */ - emptyRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - /** - * Custom footer renderer, available only if `footerHeight` is larger then 0 - */ - footerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - /** - * Custom header renderer - * The renderer receives props `{ cells, columns, headerIndex }` - */ - headerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - /** - * Custom row renderer - * The renderer receives props `{ isScrolling, cells, columns, rowData, rowIndex, depth }` - */ - rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - /** - * Class name for the table header, could be a callback to return the class name - * The callback is of the shape of `({ columns, headerIndex }) => string` - */ - headerClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - /** - * Class name for the table row, could be a callback to return the class name - * The callback is of the shape of `({ columns, rowData, rowIndex }) => string` - */ - rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - /** - * Extra props applied to header element - * The handler is of the shape of `({ columns, headerIndex }) object` - */ - headerProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - /** - * Extra props applied to header cell element - * The handler is of the shape of `({ columns, column, columnIndex, headerIndex }) => object` - */ - headerCellProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - /** - * Extra props applied to row element - * The handler is of the shape of `({ columns, rowData, rowIndex }) => object` - */ - rowProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - /** - * Extra props applied to row cell element - * The handler is of the shape of `({ columns, column, columnIndex, rowData, rowIndex }) => object` - */ - cellProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - /** - * Extra props applied to ExpandIcon component - * The handler is of the shape of `({ rowData, rowIndex, depth, expandable, expanded }) => object` - */ - expandIconProps: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), - /** - * The key for the expand column which render the expand icon if the data is a tree - */ - expandColumnKey: PropTypes.string, - /** - * Default expanded row keys when initialize the table - */ - defaultExpandedRowKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), - /** - * Controlled expanded row keys - */ - expandedRowKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), - /** - * A callback function when expand or collapse a tree node - * The handler is of the shape of `({ expanded, rowData, rowIndex, rowKey }) => *` - */ - onRowExpand: PropTypes.func, - /** - * A callback function when the expanded row keys changed - * The handler is of the shape of `(expandedRowKeys) => *` - */ - onExpandedRowsChange: PropTypes.func, - /** - * The sort state for the table, will be ignored if `sortState` is set - */ - sortBy: PropTypes.shape({ - /** - * Sort key - */ - key: PropTypes.string, - /** - * Sort order - */ - order: PropTypes.oneOf([SortOrder.ASC, SortOrder.DESC]), - }), - /** - * Multiple columns sort state for the table - * - * example: - * ```js - * { - * 'column-0': SortOrder.ASC, - * 'column-1': SortOrder.DESC, - * } - * ``` - */ - sortState: PropTypes.object, - /** - * A callback function for the header cell click event - * The handler is of the shape of `({ column, key, order }) => *` - */ - onColumnSort: PropTypes.func, - /** - * A callback function when resizing the column width - * The handler is of the shape of `({ column, width }) => *` - */ - onColumnResize: PropTypes.func, - /** - * A callback function when resizing the column width ends - * The handler is of the shape of `({ column, width }) => *` - */ - onColumnResizeEnd: PropTypes.func, - /** - * Adds an additional isScrolling parameter to the row renderer. - * This parameter can be used to show a placeholder row while scrolling. - */ - useIsScrolling: PropTypes.bool, - /** - * Number of rows to render above/below the visible bounds of the list - */ - overscanRowCount: PropTypes.number, - /** - * Custom scrollbar size measurement - */ - getScrollbarSize: PropTypes.func, - /** - * A callback function when scrolling the table - * The handler is of the shape of `({ scrollLeft, scrollTop, horizontalScrollDirection, verticalScrollDirection, scrollUpdateWasRequested }) => *` - * - * `scrollLeft` and `scrollTop` are numbers. - * - * `horizontalDirection` and `verticalDirection` are either `forward` or `backward`. - * - * `scrollUpdateWasRequested` is a boolean. This value is true if the scroll was caused by `scrollTo*`, - * and false if it was the result of a user interaction in the browser. - */ - onScroll: PropTypes.func, - /** - * A callback function when scrolling the table within `onEndReachedThreshold` of the bottom - * The handler is of the shape of `({ distanceFromEnd }) => *` - */ - onEndReached: PropTypes.func, - /** - * Threshold in pixels for calling `onEndReached`. - */ - onEndReachedThreshold: PropTypes.number, - /** - * A callback function with information about the slice of rows that were just rendered - * The handler is of the shape of `({ overscanStartIndex, overscanStopIndex, startIndex, stopIndex }) => *` - */ - onRowsRendered: PropTypes.func, - /** - * A callback function when the scrollbar presence state changed - * The handler is of the shape of `({ size, vertical, horizontal }) => *` - */ - onScrollbarPresenceChange: PropTypes.func, - /** - * A object for the row event handlers - * Each of the keys is row event name, like `onClick`, `onDoubleClick` and etc. - * Each of the handlers is of the shape of `({ rowData, rowIndex, rowKey, event }) => *` - */ - rowEventHandlers: PropTypes.object, - /** - * whether to ignore function properties while comparing column definition - */ - ignoreFunctionInColumnCompare: PropTypes.bool, - /** - * A object for the custom components, like `ExpandIcon` and `SortIndicator` - */ - components: PropTypes.shape({ - TableCell: PropTypes.elementType, - TableHeaderCell: PropTypes.elementType, - ExpandIcon: PropTypes.elementType, - SortIndicator: PropTypes.elementType, - }), -}; - export default BaseTable; diff --git a/src/Column.js b/src/Column.tsx similarity index 90% rename from src/Column.js rename to src/Column.tsx index 0102df84..b12e3dec 100644 --- a/src/Column.js +++ b/src/Column.tsx @@ -1,13 +1,15 @@ import React from 'react'; import PropTypes from 'prop-types'; -export const Alignment = { +import type { AlignmentValue, FrozenDirectionValue } from './types'; + +export const Alignment: Record = { LEFT: 'left', CENTER: 'center', RIGHT: 'right', }; -export const FrozenDirection = { +export const FrozenDirection: Record = { LEFT: 'left', RIGHT: 'right', DEFAULT: true, @@ -99,7 +101,7 @@ Column.propTypes = { headerRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), }; -Column.Alignment = Alignment; -Column.FrozenDirection = FrozenDirection; +(Column as any).Alignment = Alignment; +(Column as any).FrozenDirection = FrozenDirection; export default Column; diff --git a/src/ColumnManager.js b/src/ColumnManager.ts similarity index 55% rename from src/ColumnManager.js rename to src/ColumnManager.ts index dcc5fba2..bfff1757 100644 --- a/src/ColumnManager.js +++ b/src/ColumnManager.ts @@ -1,23 +1,36 @@ import { FrozenDirection } from './Column'; +import type { ColumnShape } from './types'; + export default class ColumnManager { - constructor(columns, fixed) { + static PlaceholderKey = '__placeholder__'; + + _origColumns: ColumnShape[]; + _columns: ColumnShape[]; + _fixed: boolean; + _cached: Record; + _columnStyles: Record; + + constructor(columns: ColumnShape[], fixed: boolean) { this._origColumns = []; + this._columns = []; + this._fixed = false; + this._cached = {}; + this._columnStyles = {}; this.reset(columns, fixed); } - _cache(key, fn) { + _cache(key: string, fn: () => T): T { if (key in this._cached) return this._cached[key]; this._cached[key] = fn(); return this._cached[key]; } - reset(columns, fixed) { - this._columns = columns.map(column => { + reset(columns: ColumnShape[], fixed: boolean): void { + this._columns = columns.map((column) => { let width = column.width; if (column.resizable) { - // don't reset column's `width` if `width` prop doesn't change - const idx = this._origColumns.findIndex(x => x.key === column.key); + const idx = this._origColumns.findIndex((x) => x.key === column.key); if (idx >= 0 && this._origColumns[idx].width === column.width) { width = this._columns[idx].width; } @@ -30,59 +43,58 @@ export default class ColumnManager { this._columnStyles = this.recomputeColumnStyles(); } - resetCache() { + resetCache(): void { this._cached = {}; } - getOriginalColumns() { + getOriginalColumns(): ColumnShape[] { return this._origColumns; } - getColumns() { + getColumns(): ColumnShape[] { return this._columns; } - getVisibleColumns() { + getVisibleColumns(): ColumnShape[] { return this._cache('visibleColumns', () => { - return this._columns.filter(column => !column.hidden); + return this._columns.filter((column) => !column.hidden); }); } - hasFrozenColumns() { + hasFrozenColumns(): boolean { return this._cache('hasFrozenColumns', () => { - return this._fixed && this.getVisibleColumns().some(column => !!column.frozen); + return this._fixed && this.getVisibleColumns().some((column) => !!column.frozen); }); } - hasLeftFrozenColumns() { + hasLeftFrozenColumns(): boolean { return this._cache('hasLeftFrozenColumns', () => { return ( this._fixed && - this.getVisibleColumns().some(column => column.frozen === FrozenDirection.LEFT || column.frozen === true) + this.getVisibleColumns().some((column) => column.frozen === FrozenDirection.LEFT || column.frozen === true) ); }); } - hasRightFrozenColumns() { + hasRightFrozenColumns(): boolean { return this._cache('hasRightFrozenColumns', () => { - return this._fixed && this.getVisibleColumns().some(column => column.frozen === FrozenDirection.RIGHT); + return this._fixed && this.getVisibleColumns().some((column) => column.frozen === FrozenDirection.RIGHT); }); } - getMainColumns() { + getMainColumns(): ColumnShape[] { return this._cache('mainColumns', () => { const columns = this.getVisibleColumns(); if (!this.hasFrozenColumns()) return columns; - const mainColumns = []; - this.getLeftFrozenColumns().forEach(column => { - //columns placeholder for the fixed table above them + const mainColumns: ColumnShape[] = []; + this.getLeftFrozenColumns().forEach((column) => { mainColumns.push({ ...column, [ColumnManager.PlaceholderKey]: true }); }); - this.getVisibleColumns().forEach(column => { + this.getVisibleColumns().forEach((column) => { if (!column.frozen) mainColumns.push(column); }); - this.getRightFrozenColumns().forEach(column => { + this.getRightFrozenColumns().forEach((column) => { mainColumns.push({ ...column, [ColumnManager.PlaceholderKey]: true }); }); @@ -90,65 +102,65 @@ export default class ColumnManager { }); } - getLeftFrozenColumns() { + getLeftFrozenColumns(): ColumnShape[] { return this._cache('leftFrozenColumns', () => { if (!this._fixed) return []; return this.getVisibleColumns().filter( - column => column.frozen === FrozenDirection.LEFT || column.frozen === true + (column) => column.frozen === FrozenDirection.LEFT || column.frozen === true, ); }); } - getRightFrozenColumns() { + getRightFrozenColumns(): ColumnShape[] { return this._cache('rightFrozenColumns', () => { if (!this._fixed) return []; - return this.getVisibleColumns().filter(column => column.frozen === FrozenDirection.RIGHT); + return this.getVisibleColumns().filter((column) => column.frozen === FrozenDirection.RIGHT); }); } - getColumn(key) { - const idx = this._columns.findIndex(column => column.key === key); + getColumn(key: string): ColumnShape { + const idx = this._columns.findIndex((column) => column.key === key); return this._columns[idx]; } - getColumnsWidth() { + getColumnsWidth(): number { return this._cache('columnsWidth', () => { return this.recomputeColumnsWidth(this.getVisibleColumns()); }); } - getLeftFrozenColumnsWidth() { + getLeftFrozenColumnsWidth(): number { return this._cache('leftFrozenColumnsWidth', () => { return this.recomputeColumnsWidth(this.getLeftFrozenColumns()); }); } - getRightFrozenColumnsWidth() { + getRightFrozenColumnsWidth(): number { return this._cache('rightFrozenColumnsWidth', () => { return this.recomputeColumnsWidth(this.getRightFrozenColumns()); }); } - recomputeColumnsWidth(columns) { + recomputeColumnsWidth(columns: ColumnShape[]): number { return columns.reduce((width, column) => width + column.width, 0); } - setColumnWidth(key, width) { + setColumnWidth(key: string, width: number): void { const column = this.getColumn(key); column.width = width; this._cached = {}; this._columnStyles[column.key] = this.recomputeColumnStyle(column); } - getColumnStyle(key) { + getColumnStyle(key: string): React.CSSProperties { return this._columnStyles[key]; } - getColumnStyles() { + getColumnStyles(): Record { return this._columnStyles; } - recomputeColumnStyle(column) { + recomputeColumnStyle(column: ColumnShape): React.CSSProperties { let flexGrow = 0; let flexShrink = 0; if (!this._fixed) { @@ -158,7 +170,7 @@ export default class ColumnManager { // workaround for Flex bug on IE: https://github.com/philipwalton/flexbugs#flexbug-7 const flexValue = `${flexGrow} ${flexShrink} auto`; - const style = { + const style: any = { ...column.style, flex: flexValue, msFlex: flexValue, @@ -177,12 +189,13 @@ export default class ColumnManager { return style; } - recomputeColumnStyles() { - return this._columns.reduce((styles, column) => { - styles[column.key] = this.recomputeColumnStyle(column); - return styles; - }, {}); + recomputeColumnStyles(): Record { + return this._columns.reduce( + (styles, column) => { + styles[column.key] = this.recomputeColumnStyle(column); + return styles; + }, + {} as Record, + ); } } - -ColumnManager.PlaceholderKey = '__placeholder__'; diff --git a/src/ColumnResizer.js b/src/ColumnResizer.tsx similarity index 60% rename from src/ColumnResizer.js rename to src/ColumnResizer.tsx index ab522015..7aac0c60 100644 --- a/src/ColumnResizer.js +++ b/src/ColumnResizer.tsx @@ -3,12 +3,14 @@ import PropTypes from 'prop-types'; import { noop, addClassName, removeClassName } from './utils'; +import type { ColumnShape } from './types'; + const INVALID_VALUE = null; // copied from https://github.com/mzabriskie/react-draggable/blob/master/lib/utils/domFns.js -export function addUserSelectStyles(doc) { +export function addUserSelectStyles(doc: Document) { if (!doc) return; - let styleEl = doc.getElementById('react-draggable-style-el'); + let styleEl = doc.getElementById('react-draggable-style-el') as HTMLStyleElement | null; if (!styleEl) { styleEl = doc.createElement('style'); styleEl.type = 'text/css'; @@ -20,15 +22,13 @@ export function addUserSelectStyles(doc) { if (doc.body) addClassName(doc.body, 'react-draggable-transparent-selection'); } -export function removeUserSelectStyles(doc) { +export function removeUserSelectStyles(doc: Document) { if (!doc) return; try { if (doc.body) removeClassName(doc.body, 'react-draggable-transparent-selection'); - if (doc.selection) { - doc.selection.empty(); + if ((doc as any).selection) { + (doc as any).selection.empty(); } else { - // Remove selection caused by scroll, unless it's a focused input - // (we use doc.defaultView in case we're in an iframe) const selection = (doc.defaultView || window).getSelection(); if (selection && selection.type !== 'Caret') { selection.removeAllRanges(); @@ -54,17 +54,66 @@ const eventsFor = { let dragEventFor = eventsFor.mouse; +export interface ColumnResizerProps { + style?: React.CSSProperties; + column?: ColumnShape; + onResizeStart?: (column: ColumnShape) => void; + onResize?: (column: ColumnShape, width: number) => void; + onResizeStop?: (column: ColumnShape) => void; + minWidth?: number; + className?: string; + [key: string]: any; +} + /** * ColumnResizer for BaseTable */ -class ColumnResizer extends React.PureComponent { - constructor(props) { +class ColumnResizer extends React.PureComponent { + isDragging = false; + lastX: number | null = INVALID_VALUE; + width = 0; + handleRef: HTMLDivElement | null = null; + + static defaultProps = { + onResizeStart: noop, + onResize: noop, + onResizeStop: noop, + minWidth: 30, + }; + + static propTypes = { + /** + * Custom style for the drag handler + */ + style: PropTypes.object, + /** + * The column object to be dragged + */ + column: PropTypes.object, + /** + * A callback function when resizing started + * The callback is of the shape of `(column) => *` + */ + onResizeStart: PropTypes.func, + /** + * A callback function when resizing the column + * The callback is of the shape of `(column, width) => *` + */ + onResize: PropTypes.func, + /** + * A callback function when resizing stopped + * The callback is of the shape of `(column) => *` + */ + onResizeStop: PropTypes.func, + /** + * Minimum width of the column could be resized to if the column's `minWidth` is not set + */ + minWidth: PropTypes.number, + }; + + constructor(props: ColumnResizerProps) { super(props); - this.isDragging = false; - this.lastX = INVALID_VALUE; - this.width = 0; - this._setHandleRef = this._setHandleRef.bind(this); this._handleClick = this._handleClick.bind(this); this._handleMouseDown = this._handleMouseDown.bind(this); @@ -113,70 +162,70 @@ class ColumnResizer extends React.PureComponent { ); } - _setHandleRef(ref) { + _setHandleRef(ref: HTMLDivElement | null) { this.handleRef = ref; } - _handleClick(e) { + _handleClick(e: React.MouseEvent) { e.stopPropagation(); } - _handleMouseDown(e) { + _handleMouseDown(e: React.MouseEvent) { dragEventFor = eventsFor.mouse; - this._handleDragStart(e); + this._handleDragStart(e as any); } - _handleMouseUp(e) { + _handleMouseUp(e: React.MouseEvent) { dragEventFor = eventsFor.mouse; - this._handleDragStop(e); + this._handleDragStop(e as any); } - _handleTouchStart(e) { + _handleTouchStart(e: React.TouchEvent) { dragEventFor = eventsFor.touch; - this._handleDragStart(e); + this._handleDragStart(e as any); } - _handleTouchEnd(e) { + _handleTouchEnd(e: React.TouchEvent) { dragEventFor = eventsFor.touch; - this._handleDragStop(e); + this._handleDragStop(e as any); } - _handleDragStart(e) { + _handleDragStart(e: any) { if (typeof e.button === 'number' && e.button !== 0) return; this.isDragging = true; this.lastX = INVALID_VALUE; - this.width = this.props.column.width; - this.props.onResizeStart(this.props.column); + this.width = this.props.column!.width; + this.props.onResizeStart!(this.props.column!); - const { ownerDocument } = this.handleRef; + const { ownerDocument } = this.handleRef!; addUserSelectStyles(ownerDocument); ownerDocument.addEventListener(dragEventFor.move, this._handleDrag); ownerDocument.addEventListener(dragEventFor.stop, this._handleDragStop); } - _handleDragStop(e) { + _handleDragStop(e: any) { if (!this.isDragging) return; this.isDragging = false; - this.props.onResizeStop(this.props.column); + this.props.onResizeStop!(this.props.column!); - const { ownerDocument } = this.handleRef; + const { ownerDocument } = this.handleRef!; removeUserSelectStyles(ownerDocument); ownerDocument.removeEventListener(dragEventFor.move, this._handleDrag); ownerDocument.removeEventListener(dragEventFor.stop, this._handleDragStop); } - _handleDrag(e) { + _handleDrag(e: any) { let clientX = e.clientX; if (e.type === eventsFor.touch.move) { e.preventDefault(); if (e.targetTouches && e.targetTouches[0]) clientX = e.targetTouches[0].clientX; } - const { offsetParent } = this.handleRef; - const offsetParentRect = offsetParent.getBoundingClientRect(); - const x = clientX + offsetParent.scrollLeft - offsetParentRect.left; + const { offsetParent } = this.handleRef!; + const offsetParentRect = (offsetParent as HTMLElement).getBoundingClientRect(); + const x = clientX + (offsetParent as HTMLElement).scrollLeft - offsetParentRect.left; if (this.lastX === INVALID_VALUE) { this.lastX = x; @@ -184,8 +233,8 @@ class ColumnResizer extends React.PureComponent { } const { column, minWidth: MIN_WIDTH } = this.props; - const { width, maxWidth, minWidth = MIN_WIDTH } = column; - const movedX = x - this.lastX; + const { width, maxWidth, minWidth = MIN_WIDTH } = column!; + const movedX = x - this.lastX!; if (!movedX) return; this.width = this.width + movedX; @@ -194,50 +243,13 @@ class ColumnResizer extends React.PureComponent { let newWidth = this.width; if (maxWidth && newWidth > maxWidth) { newWidth = maxWidth; - } else if (newWidth < minWidth) { - newWidth = minWidth; + } else if (newWidth < minWidth!) { + newWidth = minWidth!; } if (newWidth === width) return; - this.props.onResize(column, newWidth); + this.props.onResize!(column!, newWidth); } } -ColumnResizer.defaultProps = { - onResizeStart: noop, - onResize: noop, - onResizeStop: noop, - minWidth: 30, -}; - -ColumnResizer.propTypes = { - /** - * Custom style for the drag handler - */ - style: PropTypes.object, - /** - * The column object to be dragged - */ - column: PropTypes.object, - /** - * A callback function when resizing started - * The callback is of the shape of `(column) => *` - */ - onResizeStart: PropTypes.func, - /** - * A callback function when resizing the column - * The callback is of the shape of `(column, width) => *` - */ - onResize: PropTypes.func, - /** - * A callback function when resizing stopped - * The callback is of the shape of `(column) => *` - */ - onResizeStop: PropTypes.func, - /** - * Minimum width of the column could be resized to if the column's `minWidth` is not set - */ - minWidth: PropTypes.number, -}; - export default ColumnResizer; diff --git a/src/ExpandIcon.js b/src/ExpandIcon.tsx similarity index 60% rename from src/ExpandIcon.js rename to src/ExpandIcon.tsx index b1e63a44..d2dcb0c4 100644 --- a/src/ExpandIcon.js +++ b/src/ExpandIcon.tsx @@ -2,11 +2,33 @@ import React from 'react'; import PropTypes from 'prop-types'; import cn from 'classnames'; +export interface ExpandIconProps { + expandable?: boolean; + expanded?: boolean; + indentSize?: number; + depth?: number; + onExpand?: (expanded: boolean) => void; + [key: string]: any; +} + /** * default ExpandIcon for BaseTable */ -class ExpandIcon extends React.PureComponent { - constructor(props) { +class ExpandIcon extends React.PureComponent { + static defaultProps = { + depth: 0, + indentSize: 16, + }; + + static propTypes = { + expandable: PropTypes.bool, + expanded: PropTypes.bool, + indentSize: PropTypes.number, + depth: PropTypes.number, + onExpand: PropTypes.func, + }; + + constructor(props: ExpandIconProps) { super(props); this._handleClick = this._handleClick.bind(this); @@ -23,7 +45,7 @@ class ExpandIcon extends React.PureComponent {
{expandable && '\u25B8'} @@ -44,25 +66,12 @@ class ExpandIcon extends React.PureComponent { ); } - _handleClick(e) { + _handleClick(e: React.MouseEvent) { e.stopPropagation(); e.preventDefault(); const { onExpand, expanded } = this.props; - onExpand(!expanded); + onExpand!(!expanded); } } -ExpandIcon.defaultProps = { - depth: 0, - indentSize: 16, -}; - -ExpandIcon.propTypes = { - expandable: PropTypes.bool, - expanded: PropTypes.bool, - indentSize: PropTypes.number, - depth: PropTypes.number, - onExpand: PropTypes.func, -}; - export default ExpandIcon; diff --git a/src/GridTable.js b/src/GridTable.tsx similarity index 52% rename from src/GridTable.js rename to src/GridTable.tsx index b23ec257..94a9094a 100644 --- a/src/GridTable.js +++ b/src/GridTable.tsx @@ -7,11 +7,75 @@ import memoize from 'memoize-one'; import Header from './TableHeader'; import { getEstimatedTotalRowsHeight } from './utils'; +import type { ColumnShape, RowData, RowKey } from './types'; + +export interface GridTableProps { + containerStyle?: React.CSSProperties; + classPrefix?: string; + className?: string; + width: number; + height: number; + headerHeight: number | number[]; + headerWidth: number; + bodyWidth: number; + rowHeight: number; + estimatedRowHeight?: number | ((args: { rowData: RowData; rowIndex: number }) => number); + getRowHeight?: (rowIndex: number) => number; + columns: ColumnShape[]; + data: RowData[]; + frozenData?: RowData[]; + rowKey: string | number; + useIsScrolling?: boolean; + overscanRowCount?: number; + hoveredRowKey?: RowKey | null; + style?: React.CSSProperties; + onScrollbarPresenceChange?: (...args: any[]) => void; + onScroll?: (...args: any[]) => void; + onRowsRendered?: (args: any) => void; + headerRenderer: (args: any) => React.ReactNode; + rowRenderer: (args: any) => React.ReactNode; + [key: string]: any; +} + /** * A wrapper of the Grid for internal only */ -class GridTable extends React.PureComponent { - constructor(props) { +class GridTable extends React.PureComponent { + headerRef: InstanceType | null = null; + bodyRef: InstanceType | InstanceType | null = null; + innerRef: HTMLElement | null = null; + + _resetColumnWidthCache: (bodyWidth: number) => void; + _getEstimatedTotalRowsHeight: typeof getEstimatedTotalRowsHeight; + + static propTypes = { + containerStyle: PropTypes.object, + classPrefix: PropTypes.string, + className: PropTypes.string, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, + headerWidth: PropTypes.number.isRequired, + bodyWidth: PropTypes.number.isRequired, + rowHeight: PropTypes.number.isRequired, + estimatedRowHeight: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), + getRowHeight: PropTypes.func, + columns: PropTypes.arrayOf(PropTypes.object).isRequired, + data: PropTypes.array.isRequired, + frozenData: PropTypes.array, + rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + useIsScrolling: PropTypes.bool, + overscanRowCount: PropTypes.number, + hoveredRowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + style: PropTypes.object, + onScrollbarPresenceChange: PropTypes.func, + onScroll: PropTypes.func, + onRowsRendered: PropTypes.func, + headerRenderer: PropTypes.func.isRequired, + rowRenderer: PropTypes.func.isRequired, + }; + + constructor(props: GridTableProps) { super(props); this._setHeaderRef = this._setHeaderRef.bind(this); @@ -20,18 +84,18 @@ class GridTable extends React.PureComponent { this._itemKey = this._itemKey.bind(this); this._getBodyWidth = this._getBodyWidth.bind(this); this._handleItemsRendered = this._handleItemsRendered.bind(this); - this._resetColumnWidthCache = memoize(bodyWidth => { + this._resetColumnWidthCache = memoize((_bodyWidth: number) => { if (!this.props.estimatedRowHeight) return; - this.bodyRef && this.bodyRef.resetAfterColumnIndex(0, false); + this.bodyRef && (this.bodyRef as any).resetAfterColumnIndex(0, false); }); this._getEstimatedTotalRowsHeight = memoize(getEstimatedTotalRowsHeight); this.renderRow = this.renderRow.bind(this); } - resetAfterRowIndex(rowIndex = 0, shouldForceUpdate) { + resetAfterRowIndex(rowIndex: number = 0, shouldForceUpdate?: boolean) { if (!this.props.estimatedRowHeight) return; - this.bodyRef && this.bodyRef.resetAfterRowIndex(rowIndex, shouldForceUpdate); + this.bodyRef && (this.bodyRef as any).resetAfterRowIndex(rowIndex, shouldForceUpdate); } forceUpdateTable() { @@ -39,25 +103,25 @@ class GridTable extends React.PureComponent { this.bodyRef && this.bodyRef.forceUpdate(); } - scrollToPosition(args) { - this.headerRef && this.headerRef.scrollTo(args.scrollLeft); - this.bodyRef && this.bodyRef.scrollTo(args); + scrollToPosition(args: { scrollLeft?: number; scrollTop?: number }) { + this.headerRef && this.headerRef.scrollTo(args.scrollLeft || 0); + this.bodyRef && this.bodyRef.scrollTo(args as any); } - scrollToTop(scrollTop) { - this.bodyRef && this.bodyRef.scrollTo({ scrollTop }); + scrollToTop(scrollTop: number) { + this.bodyRef && this.bodyRef.scrollTo({ scrollTop } as any); } - scrollToLeft(scrollLeft) { + scrollToLeft(scrollLeft: number) { this.headerRef && this.headerRef.scrollTo(scrollLeft); - this.bodyRef && this.bodyRef.scrollToPosition({ scrollLeft }); + this.bodyRef && (this.bodyRef as any).scrollToPosition({ scrollLeft }); } - scrollToRow(rowIndex = 0, align = 'auto') { - this.bodyRef && this.bodyRef.scrollToItem({ rowIndex, align }); + scrollToRow(rowIndex: number = 0, align: string = 'auto') { + this.bodyRef && (this.bodyRef as any).scrollToItem({ rowIndex, align }); } - getTotalRowsHeight() { + getTotalRowsHeight(): number { const { data, rowHeight, estimatedRowHeight } = this.props; if (estimatedRowHeight) { @@ -68,7 +132,7 @@ class GridTable extends React.PureComponent { return data.length * rowHeight; } - renderRow(args) { + renderRow(args: any) { const { data, columns, rowRenderer } = this.props; const rowData = data[args.rowIndex]; return rowRenderer({ ...args, columns, rowData }); @@ -98,11 +162,11 @@ class GridTable extends React.PureComponent { ...rest } = this.props; const headerHeight = this._getHeaderHeight(); - const frozenRowCount = frozenData.length; + const frozenRowCount = frozenData ? frozenData.length : 0; const frozenRowsHeight = rowHeight * frozenRowCount; const cls = cn(`${classPrefix}__table`, className); const containerProps = containerStyle ? { style: containerStyle } : null; - const Grid = estimatedRowHeight ? VariableSizeGrid : FixedSizeGrid; + const Grid: any = estimatedRowHeight ? VariableSizeGrid : FixedSizeGrid; this._resetColumnWidthCache(bodyWidth); return ( @@ -117,7 +181,7 @@ class GridTable extends React.PureComponent { frozenData={frozenData} width={width} height={Math.max(height - headerHeight - frozenRowsHeight, 0)} - rowHeight={estimatedRowHeight ? getRowHeight : rowHeight} + rowHeight={estimatedRowHeight ? getRowHeight! : rowHeight} estimatedRowHeight={typeof estimatedRowHeight === 'function' ? undefined : estimatedRowHeight} rowCount={data.length} overscanRowCount={overscanRowCount} @@ -131,8 +195,6 @@ class GridTable extends React.PureComponent { children={this.renderRow} /> {headerHeight + frozenRowsHeight > 0 && ( - // put header after body and reverse the display order via css - // to prevent header's shadow being covered by body
| null) { this.headerRef = ref; } - _setBodyRef(ref) { + _setBodyRef(ref: any) { this.bodyRef = ref; } - _setInnerRef(ref) { + _setInnerRef(ref: HTMLElement | null) { this.innerRef = ref; } - _itemKey({ rowIndex }) { + _itemKey({ rowIndex }: { rowIndex: number }) { const { data, rowKey } = this.props; - return data[rowIndex][rowKey]; + return data[rowIndex][rowKey as string]; } - _getHeaderHeight() { + _getHeaderHeight(): number { const { headerHeight } = this.props; if (Array.isArray(headerHeight)) { return headerHeight.reduce((sum, height) => sum + height, 0); @@ -178,12 +240,17 @@ class GridTable extends React.PureComponent { return headerHeight; } - _getBodyWidth() { + _getBodyWidth(): number { return this.props.bodyWidth; } - _handleItemsRendered({ overscanRowStartIndex, overscanRowStopIndex, visibleRowStartIndex, visibleRowStopIndex }) { - this.props.onRowsRendered({ + _handleItemsRendered({ + overscanRowStartIndex, + overscanRowStopIndex, + visibleRowStartIndex, + visibleRowStopIndex, + }: any) { + this.props.onRowsRendered!({ overscanStartIndex: overscanRowStartIndex, overscanStopIndex: overscanRowStopIndex, startIndex: visibleRowStartIndex, @@ -192,31 +259,4 @@ class GridTable extends React.PureComponent { } } -GridTable.propTypes = { - containerStyle: PropTypes.object, - classPrefix: PropTypes.string, - className: PropTypes.string, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, - headerWidth: PropTypes.number.isRequired, - bodyWidth: PropTypes.number.isRequired, - rowHeight: PropTypes.number.isRequired, - estimatedRowHeight: PropTypes.oneOfType([PropTypes.func, PropTypes.number]), - getRowHeight: PropTypes.func, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - data: PropTypes.array.isRequired, - frozenData: PropTypes.array, - rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - useIsScrolling: PropTypes.bool, - overscanRowCount: PropTypes.number, - hoveredRowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - style: PropTypes.object, - onScrollbarPresenceChange: PropTypes.func, - onScroll: PropTypes.func, - onRowsRendered: PropTypes.func, - headerRenderer: PropTypes.func.isRequired, - rowRenderer: PropTypes.func.isRequired, -}; - export default GridTable; diff --git a/src/SortIndicator.js b/src/SortIndicator.tsx similarity index 73% rename from src/SortIndicator.js rename to src/SortIndicator.tsx index 05deb9c5..8a52f9de 100644 --- a/src/SortIndicator.js +++ b/src/SortIndicator.tsx @@ -4,10 +4,19 @@ import cn from 'classnames'; import SortOrder from './SortOrder'; +import type { SortOrderValue } from './types'; + +export interface SortIndicatorProps { + sortOrder?: SortOrderValue; + className?: string; + style?: React.CSSProperties; + [key: string]: any; +} + /** * default SortIndicator for BaseTable */ -const SortIndicator = ({ sortOrder, className, style }) => { +const SortIndicator: React.FC = ({ sortOrder, className, style }) => { const cls = cn('BaseTable__sort-indicator', className, { 'BaseTable__sort-indicator--descending': sortOrder === SortOrder.DESC, }); diff --git a/src/SortOrder.js b/src/SortOrder.ts similarity index 94% rename from src/SortOrder.js rename to src/SortOrder.ts index b1f39227..e95989cf 100644 --- a/src/SortOrder.js +++ b/src/SortOrder.ts @@ -10,6 +10,6 @@ const SortOrder = { * Sort data in descending order */ DESC: 'desc', -}; +} as const; export default SortOrder; diff --git a/src/TableCell.js b/src/TableCell.tsx similarity index 58% rename from src/TableCell.js rename to src/TableCell.tsx index 566b2a0c..feb15bea 100644 --- a/src/TableCell.js +++ b/src/TableCell.tsx @@ -2,10 +2,21 @@ import React from 'react'; import PropTypes from 'prop-types'; import { toString } from './utils'; +import type { ColumnShape, RowData } from './types'; + +export interface TableCellProps { + className?: string; + cellData?: any; + column?: ColumnShape; + columnIndex?: number; + rowData?: RowData; + rowIndex?: number; +} + /** * Cell component for BaseTable */ -const TableCell = ({ className, cellData, column, columnIndex, rowData, rowIndex }) => ( +const TableCell: React.FC = ({ className, cellData, column, columnIndex, rowData, rowIndex }) => (
{React.isValidElement(cellData) ? cellData : toString(cellData)}
); diff --git a/src/TableHeader.js b/src/TableHeader.js deleted file mode 100644 index bb3154cb..00000000 --- a/src/TableHeader.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -class TableHeader extends React.PureComponent { - constructor(props) { - super(props); - - this.renderHeaderRow = this.renderHeaderRow.bind(this); - this.renderFrozenRow = this.renderFrozenRow.bind(this); - this._setRef = this._setRef.bind(this); - } - - scrollTo(offset) { - requestAnimationFrame(() => { - if (this.headerRef) this.headerRef.scrollLeft = offset; - }); - } - - renderHeaderRow(height, index) { - const { columns, headerRenderer } = this.props; - if (height <= 0) return null; - - const style = { width: '100%', height }; - return headerRenderer({ style, columns, headerIndex: index }); - } - - renderFrozenRow(rowData, index) { - const { columns, rowHeight, rowRenderer } = this.props; - const style = { width: '100%', height: rowHeight }; - // for frozen row the `rowIndex` is negative - const rowIndex = -index - 1; - return rowRenderer({ style, columns, rowData, rowIndex }); - } - - render() { - const { className, width, height, rowWidth, headerHeight, frozenData } = this.props; - if (height <= 0) return null; - - const style = { - width, - height: height, - position: 'relative', - overflow: 'hidden', - }; - - const innerStyle = { - width: rowWidth, - height, - }; - - const rowHeights = Array.isArray(headerHeight) ? headerHeight : [headerHeight]; - return ( -
-
- {rowHeights.map(this.renderHeaderRow)} - {frozenData.map(this.renderFrozenRow)} -
-
- ); - } - - _setRef(ref) { - this.headerRef = ref; - } -} - -TableHeader.propTypes = { - className: PropTypes.string, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, - rowWidth: PropTypes.number.isRequired, - rowHeight: PropTypes.number.isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - data: PropTypes.array.isRequired, - frozenData: PropTypes.array, - headerRenderer: PropTypes.func.isRequired, - rowRenderer: PropTypes.func.isRequired, -}; - -export default TableHeader; diff --git a/src/TableHeader.tsx b/src/TableHeader.tsx new file mode 100644 index 00000000..a203f0dc --- /dev/null +++ b/src/TableHeader.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import type { ColumnShape, RowData } from './types'; + +export interface TableHeaderProps { + className?: string; + width: number; + height: number; + headerHeight: number | number[]; + rowWidth: number; + rowHeight: number; + columns: ColumnShape[]; + data: RowData[]; + frozenData?: RowData[]; + headerRenderer: (args: { + style: React.CSSProperties; + columns: ColumnShape[]; + headerIndex: number; + }) => React.ReactNode; + rowRenderer: (args: { + style: React.CSSProperties; + columns: ColumnShape[]; + rowData: RowData; + rowIndex: number; + }) => React.ReactNode; + hoveredRowKey?: string | number | null; +} + +class TableHeader extends React.PureComponent { + headerRef: HTMLDivElement | null = null; + + static propTypes = { + className: PropTypes.string, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + headerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]).isRequired, + rowWidth: PropTypes.number.isRequired, + rowHeight: PropTypes.number.isRequired, + columns: PropTypes.arrayOf(PropTypes.object).isRequired, + data: PropTypes.array.isRequired, + frozenData: PropTypes.array, + headerRenderer: PropTypes.func.isRequired, + rowRenderer: PropTypes.func.isRequired, + }; + + constructor(props: TableHeaderProps) { + super(props); + + this.renderHeaderRow = this.renderHeaderRow.bind(this); + this.renderFrozenRow = this.renderFrozenRow.bind(this); + this._setRef = this._setRef.bind(this); + } + + scrollTo(offset: number) { + requestAnimationFrame(() => { + if (this.headerRef) this.headerRef.scrollLeft = offset; + }); + } + + renderHeaderRow(height: number, index: number) { + const { columns, headerRenderer } = this.props; + if (height <= 0) return null; + + const style: React.CSSProperties = { width: '100%', height }; + return headerRenderer({ style, columns, headerIndex: index }); + } + + renderFrozenRow(rowData: RowData, index: number) { + const { columns, rowHeight, rowRenderer } = this.props; + const style: React.CSSProperties = { width: '100%', height: rowHeight }; + const rowIndex = -index - 1; + return rowRenderer({ style, columns, rowData, rowIndex }); + } + + render() { + const { className, width, height, rowWidth, headerHeight, frozenData } = this.props; + if (height <= 0) return null; + + const style: React.CSSProperties = { + width, + height: height, + position: 'relative', + overflow: 'hidden', + }; + + const innerStyle: React.CSSProperties = { + width: rowWidth, + height, + }; + + const rowHeights = Array.isArray(headerHeight) ? headerHeight : [headerHeight]; + return ( +
+
+ {rowHeights.map(this.renderHeaderRow)} + {frozenData && frozenData.map(this.renderFrozenRow)} +
+
+ ); + } + + _setRef(ref: HTMLDivElement | null) { + this.headerRef = ref; + } +} + +export default TableHeader; diff --git a/src/TableHeaderCell.js b/src/TableHeaderCell.js deleted file mode 100644 index d14c3fa8..00000000 --- a/src/TableHeaderCell.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -/** - * HeaderCell component for BaseTable - */ -const TableHeaderCell = ({ className, column, columnIndex }) =>
{column.title}
; - -TableHeaderCell.propTypes = { - className: PropTypes.string, - column: PropTypes.object, - columnIndex: PropTypes.number, -}; - -export default TableHeaderCell; diff --git a/src/TableHeaderCell.tsx b/src/TableHeaderCell.tsx new file mode 100644 index 00000000..a27bbe81 --- /dev/null +++ b/src/TableHeaderCell.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import type { ColumnShape } from './types'; + +export interface TableHeaderCellProps { + className?: string; + column?: ColumnShape; + columnIndex?: number; +} + +/** + * HeaderCell component for BaseTable + */ +const TableHeaderCell: React.FC = ({ className, column, columnIndex }) => ( +
{column?.title}
+); + +TableHeaderCell.propTypes = { + className: PropTypes.string, + column: PropTypes.object, + columnIndex: PropTypes.number, +}; + +export default TableHeaderCell; diff --git a/src/TableHeaderRow.js b/src/TableHeaderRow.tsx similarity index 53% rename from src/TableHeaderRow.js rename to src/TableHeaderRow.tsx index 82afb46f..b1b0e695 100644 --- a/src/TableHeaderRow.js +++ b/src/TableHeaderRow.tsx @@ -3,10 +3,26 @@ import PropTypes from 'prop-types'; import { renderElement } from './utils'; +import type { ColumnShape } from './types'; + +export interface TableHeaderRowProps { + isScrolling?: boolean; + className?: string; + style?: React.CSSProperties; + columns: ColumnShape[]; + headerIndex?: number; + cellRenderer?: (args: any) => React.ReactNode; + headerRenderer?: React.ComponentType | React.ReactElement | ((props: any) => React.ReactNode); + expandColumnKey?: string; + expandIcon?: React.ComponentType; + tagName?: React.ElementType; + [key: string]: any; +} + /** * HeaderRow component for BaseTable */ -const TableHeaderRow = ({ +const TableHeaderRow: React.FC = ({ className, style, columns, @@ -15,21 +31,21 @@ const TableHeaderRow = ({ headerRenderer, expandColumnKey, expandIcon: ExpandIcon, - tagName: Tag, + tagName: Tag = 'div', ...rest }) => { - let cells = columns.map((column, columnIndex) => - cellRenderer({ + let cells: React.ReactNode = columns.map((column, columnIndex) => + cellRenderer!({ columns, column, columnIndex, headerIndex, - expandIcon: column.key === expandColumnKey && , - }) + expandIcon: column.key === expandColumnKey && ExpandIcon && , + }), ); if (headerRenderer) { - cells = renderElement(headerRenderer, { cells, columns, headerIndex }); + cells = renderElement(headerRenderer as any, { cells, columns, headerIndex }); } return ( @@ -39,10 +55,6 @@ const TableHeaderRow = ({ ); }; -TableHeaderRow.defaultProps = { - tagName: 'div', -}; - TableHeaderRow.propTypes = { isScrolling: PropTypes.bool, className: PropTypes.string, diff --git a/src/TableRow.js b/src/TableRow.js deleted file mode 100644 index be5e8ff0..00000000 --- a/src/TableRow.js +++ /dev/null @@ -1,194 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import { renderElement } from './utils'; - -/** - * Row component for BaseTable - */ -class TableRow extends React.PureComponent { - constructor(props) { - super(props); - - this.state = { - measured: false, - }; - - this._setRef = this._setRef.bind(this); - this._handleExpand = this._handleExpand.bind(this); - } - - componentDidMount() { - this.props.estimatedRowHeight && this.props.rowIndex >= 0 && this._measureHeight(true); - } - - componentDidUpdate(prevProps, prevState) { - if ( - this.props.estimatedRowHeight && - this.props.rowIndex >= 0 && - // should not re-measure if it's updated after measured and reset - !this.props.getIsResetting() && - this.state.measured && - prevState.measured - ) { - this.setState({ measured: false }, () => this._measureHeight()); - } - } - - render() { - /* eslint-disable no-unused-vars */ - const { - isScrolling, - className, - style, - columns, - rowIndex, - rowData, - expandColumnKey, - depth, - rowEventHandlers, - estimatedRowHeight, - rowRenderer, - cellRenderer, - expandIconRenderer, - tagName: Tag, - // omit the following from rest - rowKey, - getIsResetting, - onRowHover, - onRowExpand, - onRowHeightChange, - ...rest - } = this.props; - /* eslint-enable no-unused-vars */ - - const expandIcon = expandIconRenderer({ rowData, rowIndex, depth, onExpand: this._handleExpand }); - let cells = columns.map((column, columnIndex) => - cellRenderer({ - isScrolling, - columns, - column, - columnIndex, - rowData, - rowIndex, - expandIcon: column.key === expandColumnKey && expandIcon, - }) - ); - - if (rowRenderer) { - cells = renderElement(rowRenderer, { isScrolling, cells, columns, rowData, rowIndex, depth }); - } - - const eventHandlers = this._getEventHandlers(rowEventHandlers); - - if (estimatedRowHeight && rowIndex >= 0) { - const { height, ...otherStyles } = style; - return ( - - {cells} - - ); - } - - return ( - - {cells} - - ); - } - - _setRef(ref) { - this.ref = ref; - } - - _handleExpand(expanded) { - const { onRowExpand, rowData, rowIndex, rowKey } = this.props; - onRowExpand && onRowExpand({ expanded, rowData, rowIndex, rowKey }); - } - - _measureHeight(initialMeasure) { - if (!this.ref) return; - - const { style, rowKey, onRowHeightChange, rowIndex, columns } = this.props; - const height = this.ref.getBoundingClientRect().height; - this.setState({ measured: true }, () => { - if (initialMeasure || height !== style.height) - onRowHeightChange(rowKey, height, rowIndex, columns[0] && !columns[0].__placeholder__ && columns[0].frozen); - }); - } - - _getEventHandlers(handlers = {}) { - const { rowData, rowIndex, rowKey, onRowHover } = this.props; - const eventHandlers = {}; - Object.keys(handlers).forEach(eventKey => { - const callback = handlers[eventKey]; - if (typeof callback === 'function') { - eventHandlers[eventKey] = event => { - callback({ rowData, rowIndex, rowKey, event }); - }; - } - }); - - if (onRowHover) { - const mouseEnterHandler = eventHandlers['onMouseEnter']; - eventHandlers['onMouseEnter'] = event => { - onRowHover({ - hovered: true, - rowData, - rowIndex, - rowKey, - event, - }); - mouseEnterHandler && mouseEnterHandler(event); - }; - - const mouseLeaveHandler = eventHandlers['onMouseLeave']; - eventHandlers['onMouseLeave'] = event => { - onRowHover({ - hovered: false, - rowData, - rowIndex, - rowKey, - event, - }); - mouseLeaveHandler && mouseLeaveHandler(event); - }; - } - - return eventHandlers; - } -} - -TableRow.defaultProps = { - tagName: 'div', -}; - -TableRow.propTypes = { - isScrolling: PropTypes.bool, - className: PropTypes.string, - style: PropTypes.object, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - rowData: PropTypes.object.isRequired, - rowIndex: PropTypes.number.isRequired, - rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), - expandColumnKey: PropTypes.string, - depth: PropTypes.number, - rowEventHandlers: PropTypes.object, - rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), - cellRenderer: PropTypes.func, - expandIconRenderer: PropTypes.func, - estimatedRowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), - getIsResetting: PropTypes.func, - onRowHover: PropTypes.func, - onRowExpand: PropTypes.func, - onRowHeightChange: PropTypes.func, - tagName: PropTypes.elementType, -}; - -export default TableRow; diff --git a/src/TableRow.tsx b/src/TableRow.tsx new file mode 100644 index 00000000..af8f1b08 --- /dev/null +++ b/src/TableRow.tsx @@ -0,0 +1,233 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { renderElement } from './utils'; + +import type { ColumnShape, RowData, RowKey, RowEventHandlers } from './types'; + +export interface TableRowProps { + isScrolling?: boolean; + className?: string; + style?: React.CSSProperties; + columns: ColumnShape[]; + rowData: RowData; + rowIndex: number; + rowKey?: RowKey; + expandColumnKey?: string; + depth?: number; + rowEventHandlers?: RowEventHandlers; + rowRenderer?: React.ComponentType | React.ReactElement | ((props: any) => React.ReactNode); + cellRenderer?: (args: any) => React.ReactNode; + expandIconRenderer?: (args: any) => React.ReactNode; + estimatedRowHeight?: number | ((args: { rowData: RowData; rowIndex: number }) => number); + getIsResetting?: () => boolean; + onRowHover?: (args: { + hovered: boolean; + rowData: RowData; + rowIndex: number; + rowKey: RowKey; + event: React.SyntheticEvent; + }) => void; + onRowExpand?: (args: { expanded: boolean; rowData: RowData; rowIndex: number; rowKey: RowKey }) => void; + onRowHeightChange?: (rowKey: RowKey, height: number, rowIndex: number, frozen?: any) => void; + tagName?: React.ElementType; + [key: string]: any; +} + +interface TableRowState { + measured: boolean; +} + +/** + * Row component for BaseTable + */ +class TableRow extends React.PureComponent { + ref: HTMLElement | null = null; + + static defaultProps = { + tagName: 'div', + }; + + static propTypes = { + isScrolling: PropTypes.bool, + className: PropTypes.string, + style: PropTypes.object, + columns: PropTypes.arrayOf(PropTypes.object).isRequired, + rowData: PropTypes.object.isRequired, + rowIndex: PropTypes.number.isRequired, + rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + expandColumnKey: PropTypes.string, + depth: PropTypes.number, + rowEventHandlers: PropTypes.object, + rowRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.element]), + cellRenderer: PropTypes.func, + expandIconRenderer: PropTypes.func, + estimatedRowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + getIsResetting: PropTypes.func, + onRowHover: PropTypes.func, + onRowExpand: PropTypes.func, + onRowHeightChange: PropTypes.func, + tagName: PropTypes.elementType, + }; + + constructor(props: TableRowProps) { + super(props); + + this.state = { + measured: false, + }; + + this._setRef = this._setRef.bind(this); + this._handleExpand = this._handleExpand.bind(this); + } + + componentDidMount() { + this.props.estimatedRowHeight && this.props.rowIndex >= 0 && this._measureHeight(true); + } + + componentDidUpdate(prevProps: TableRowProps, prevState: TableRowState) { + if ( + this.props.estimatedRowHeight && + this.props.rowIndex >= 0 && + !this.props.getIsResetting!() && + this.state.measured && + prevState.measured + ) { + this.setState({ measured: false }, () => this._measureHeight()); + } + } + + render() { + const { + isScrolling, + className, + style, + columns, + rowIndex, + rowData, + expandColumnKey, + depth, + rowEventHandlers, + estimatedRowHeight, + rowRenderer, + cellRenderer, + expandIconRenderer, + tagName: Tag = 'div', + // omit the following from rest + rowKey, + getIsResetting, + onRowHover, + onRowExpand, + onRowHeightChange, + ...rest + } = this.props; + + const expandIcon = expandIconRenderer!({ rowData, rowIndex, depth, onExpand: this._handleExpand }); + let cells: React.ReactNode = columns.map((column, columnIndex) => + cellRenderer!({ + isScrolling, + columns, + column, + columnIndex, + rowData, + rowIndex, + expandIcon: column.key === expandColumnKey && expandIcon, + }), + ); + + if (rowRenderer) { + cells = renderElement(rowRenderer as any, { isScrolling, cells, columns, rowData, rowIndex, depth }); + } + + const eventHandlers = this._getEventHandlers(rowEventHandlers); + + if (estimatedRowHeight && rowIndex >= 0) { + const { height, ...otherStyles } = style || ({} as any); + return ( + + {cells} + + ); + } + + return ( + + {cells} + + ); + } + + _setRef(ref: HTMLElement | null) { + this.ref = ref; + } + + _handleExpand(expanded: boolean) { + const { onRowExpand, rowData, rowIndex, rowKey } = this.props; + onRowExpand && onRowExpand({ expanded, rowData, rowIndex, rowKey: rowKey! }); + } + + _measureHeight(initialMeasure?: boolean) { + if (!this.ref) return; + + const { style, rowKey, onRowHeightChange, rowIndex, columns } = this.props; + const height = this.ref.getBoundingClientRect().height; + this.setState({ measured: true }, () => { + if (initialMeasure || height !== (style as any)?.height) + onRowHeightChange!( + rowKey!, + height, + rowIndex, + columns[0] && !(columns[0] as any).__placeholder__ && columns[0].frozen, + ); + }); + } + + _getEventHandlers(handlers: Record = {}) { + const { rowData, rowIndex, rowKey, onRowHover } = this.props; + const eventHandlers: Record void> = {}; + Object.keys(handlers).forEach((eventKey) => { + const callback = handlers[eventKey]; + if (typeof callback === 'function') { + eventHandlers[eventKey] = (event: React.SyntheticEvent) => { + callback({ rowData, rowIndex, rowKey, event }); + }; + } + }); + + if (onRowHover) { + const mouseEnterHandler = eventHandlers['onMouseEnter']; + eventHandlers['onMouseEnter'] = (event: React.SyntheticEvent) => { + onRowHover({ + hovered: true, + rowData, + rowIndex, + rowKey: rowKey!, + event, + }); + mouseEnterHandler && mouseEnterHandler(event); + }; + + const mouseLeaveHandler = eventHandlers['onMouseLeave']; + eventHandlers['onMouseLeave'] = (event: React.SyntheticEvent) => { + onRowHover({ + hovered: false, + rowData, + rowIndex, + rowKey: rowKey!, + event, + }); + mouseLeaveHandler && mouseLeaveHandler(event); + }; + } + + return eventHandlers; + } +} + +export default TableRow; diff --git a/src/__snapshots__/BaseTable.spec.js.snap b/src/__snapshots__/BaseTable.spec.js.snap deleted file mode 100644 index bf15b1a7..00000000 --- a/src/__snapshots__/BaseTable.spec.js.snap +++ /dev/null @@ -1,5003 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Table renders correctly 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can be set to disabled 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can be set to fixed 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can freeze rows 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-`; - -exports[`Table table can hide the header 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive an emptyRenderer callback 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive an headerRenderer callback 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive an rowRenderer callback 1`] = ` -
-
-
-
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive children 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive className 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive defaultExpandedRowKeys 1`] = ` -
-
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive empty data 1`] = ` -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive expandColumnKey 1`] = ` -
-
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive expandedRowKeys 1`] = ` -
-
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive headerClassName 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive headerHeight 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive height 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive rowClassName 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive rowHeight 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive style 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can receive width 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; - -exports[`Table table can specific a different rowKey 1`] = ` -
-
-
-
-
-
-
- 1 -
-
-
-
- 1 -
-
-
-
-
-
- 2 -
-
-
-
- 2 -
-
-
-
-
-
-
-
-
-
- code -
-
-
-
- name -
-
-
-
-
-
-
-
-`; diff --git a/src/index.js b/src/index.ts similarity index 60% rename from src/index.js rename to src/index.ts index 3ea68e6e..1d904b6b 100644 --- a/src/index.js +++ b/src/index.ts @@ -17,3 +17,23 @@ export { getScrollbarSize, getValue, } from './utils'; + +export type { + ColumnShape, + RowData, + RowKey, + SortOrderValue, + AlignmentValue, + FrozenDirectionValue, + CellRendererProps, + HeaderRendererProps, + RowRendererProps, + RowEventHandlerParams, + RowEventHandlers, + CallOrReturn, + ScrollArgs, + RowsRenderedArgs, + SortByShape, + SortState, + TableComponents, +} from './types'; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..eee0981f --- /dev/null +++ b/src/types.ts @@ -0,0 +1,129 @@ +import React from 'react'; + +// --- Enum-like constants --- + +export type SortOrderValue = 'asc' | 'desc'; + +export type AlignmentValue = 'left' | 'center' | 'right'; + +export type FrozenDirectionValue = 'left' | 'right' | true | false; + +// --- Row Data --- + +export type RowKey = string | number; + +export interface RowData { + [key: string]: any; + children?: RowData[]; +} + +// --- Column Shape --- + +export interface ColumnShape { + key: string; + className?: string | ((args: CellRendererProps) => string); + headerClassName?: string | ((args: HeaderRendererProps) => string); + style?: React.CSSProperties; + title?: React.ReactNode; + dataKey?: string; + dataGetter?: (args: { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: RowData; + rowIndex: number; + }) => any; + align?: AlignmentValue; + flexGrow?: number; + flexShrink?: number; + width: number; + maxWidth?: number; + minWidth?: number; + frozen?: FrozenDirectionValue; + hidden?: boolean; + resizable?: boolean; + sortable?: boolean; + cellRenderer?: React.ComponentType | React.ReactElement; + headerRenderer?: React.ComponentType | React.ReactElement; + [key: string]: any; +} + +// --- Callback / Renderer Props --- + +export interface CellRendererProps { + cellData?: any; + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + rowData: RowData; + rowIndex: number; + container?: any; + isScrolling?: boolean; +} + +export interface HeaderRendererProps { + columns: ColumnShape[]; + column: ColumnShape; + columnIndex: number; + headerIndex: number; + container?: any; +} + +export interface RowRendererProps { + isScrolling?: boolean; + cells: React.ReactNode; + columns: ColumnShape[]; + rowData: RowData; + rowIndex: number; + depth: number; +} + +export interface RowEventHandlerParams { + rowData: RowData; + rowIndex: number; + rowKey: RowKey; + event: React.SyntheticEvent; +} + +export type RowEventHandlers = Record void>; + +// --- Utility type: value or function returning value --- + +export type CallOrReturn = T | ((...args: Args) => T); + +// --- Scroll event --- + +export interface ScrollArgs { + scrollLeft: number; + scrollTop: number; + horizontalScrollDirection?: 'forward' | 'backward'; + verticalScrollDirection?: 'forward' | 'backward'; + scrollUpdateWasRequested?: boolean; +} + +// --- Rows rendered event --- + +export interface RowsRenderedArgs { + overscanStartIndex: number; + overscanStopIndex: number; + startIndex: number; + stopIndex: number; +} + +// --- Sort --- + +export interface SortByShape { + key?: string; + order?: SortOrderValue; +} + +export type SortState = Record; + +// --- Components override --- + +export interface TableComponents { + TableCell?: React.ElementType; + TableHeaderCell?: React.ElementType; + ExpandIcon?: React.ElementType; + SortIndicator?: React.ElementType; +} diff --git a/src/utils.js b/src/utils.ts similarity index 58% rename from src/utils.js rename to src/utils.ts index 94cacc6e..3a2b39d0 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -1,33 +1,38 @@ import React from 'react'; -export function renderElement(renderer, props) { +import type { ColumnShape, RowData } from './types'; + +export function renderElement( + renderer: React.ReactElement | React.ComponentType | null | undefined, + props?: Record, +): React.ReactNode { if (React.isValidElement(renderer)) { if (!props) return renderer; return React.cloneElement(renderer, props); } else if (typeof renderer === 'function') { - if (renderer.prototype && renderer.prototype.isReactComponent) { - return React.createElement(renderer, props); - } else if (renderer.defaultProps) { - return renderer({ ...renderer.defaultProps, ...props }); + if ((renderer as any).prototype && (renderer as any).prototype.isReactComponent) { + return React.createElement(renderer as React.ComponentType, props); + } else if ((renderer as any).defaultProps) { + return (renderer as Function)({ ...(renderer as any).defaultProps, ...props }); } - return renderer(props); + return (renderer as Function)(props); } else { return null; } } -export function normalizeColumns(elements) { - const columns = []; - React.Children.forEach(elements, element => { +export function normalizeColumns(elements: React.ReactNode): ColumnShape[] { + const columns: ColumnShape[] = []; + React.Children.forEach(elements, (element) => { if (React.isValidElement(element) && element.key) { - const column = { ...element.props, key: element.key }; + const column = { ...(element.props as Record), key: element.key } as ColumnShape; columns.push(column); } }); return columns; } -export function isObjectEqual(objA, objB, ignoreFunction = true) { +export function isObjectEqual(objA: any, objB: any, ignoreFunction: boolean = true): boolean { if (objA === objB) return true; if (objA === null && objB === null) return true; if (objA === null || objB === null) return false; @@ -62,21 +67,26 @@ export function isObjectEqual(objA, objB, ignoreFunction = true) { return true; } -export function callOrReturn(funcOrValue, ...args) { - return typeof funcOrValue === 'function' ? funcOrValue(...args) : funcOrValue; +export function callOrReturn(funcOrValue: T | ((...args: any[]) => T), ...args: any[]): T { + return typeof funcOrValue === 'function' ? (funcOrValue as Function)(...args) : funcOrValue; } -export function hasChildren(data) { +export function hasChildren(data: RowData): boolean { return Array.isArray(data.children) && data.children.length > 0; } -export function unflatten(array, rootId = null, dataKey = 'id', parentKey = 'parentId') { - const tree = []; - const childrenMap = {}; +export function unflatten( + array: RowData[], + rootId: any = null, + dataKey: string = 'id', + parentKey: string = 'parentId', +): RowData[] { + const tree: RowData[] = []; + const childrenMap: Record = {}; const length = array.length; for (let i = 0; i < length; i++) { - const item = { ...array[i] }; + const item: RowData = { ...array[i] }; const id = item[dataKey]; const parentId = item[parentKey]; @@ -98,22 +108,27 @@ export function unflatten(array, rootId = null, dataKey = 'id', parentKey = 'par return tree; } -export function flattenOnKeys(tree, keys, depthMap = {}, dataKey = 'id') { +export function flattenOnKeys( + tree: RowData[], + keys: any[], + depthMap: Record = {}, + dataKey: string = 'id', +): RowData[] { if (!keys || !keys.length) return tree; - const array = []; - const keysSet = new Set(); - keys.forEach(x => keysSet.add(x)); + const array: RowData[] = []; + const keysSet = new Set(); + keys.forEach((x) => keysSet.add(x)); - let stack = [].concat(tree); - stack.forEach(x => (depthMap[x[dataKey]] = 0)); + let stack = ([] as RowData[]).concat(tree); + stack.forEach((x) => (depthMap[x[dataKey]] = 0)); while (stack.length > 0) { - const item = stack.shift(); + const item = stack.shift()!; array.push(item); if (keysSet.has(item[dataKey]) && Array.isArray(item.children) && item.children.length > 0) { - stack = [].concat(item.children, stack); - item.children.forEach(x => (depthMap[x[dataKey]] = depthMap[item[dataKey]] + 1)); + stack = ([] as RowData[]).concat(item.children, stack); + item.children.forEach((x: RowData) => (depthMap[x[dataKey]] = depthMap[item[dataKey]] + 1)); } } @@ -123,22 +138,22 @@ export function flattenOnKeys(tree, keys, depthMap = {}, dataKey = 'id') { // Babel7 changed the behavior of @babel/plugin-transform-spread in https://github.com/babel/babel/pull/6763 // [...array] is transpiled to array.concat() while it was [].concat(array) before // this change breaks immutable array(seamless-immutable), [...array] should always return mutable array -export function cloneArray(array) { +export function cloneArray(array: T[]): T[] { if (!Array.isArray(array)) return []; - return [].concat(array); + return ([] as T[]).concat(array); } -export function noop() {} +export function noop(): void {} -export function toString(value) { +export function toString(value: any): string { if (typeof value === 'string') return value; if (value === null || value === undefined) return ''; - return value.toString ? value.toString() : ''; + return typeof value.toString === 'function' ? value.toString() : ''; } -function getPathSegments(path) { +function getPathSegments(path: string): string[] { const pathArray = path.split('.'); - const parts = []; + const parts: string[] = []; for (let i = 0; i < pathArray.length; i++) { let p = pathArray[i]; @@ -155,7 +170,7 @@ function getPathSegments(path) { } // changed from https://github.com/sindresorhus/dot-prop/blob/master/index.js -export function getValue(object, path, defaultValue) { +export function getValue(object: any, path: string, defaultValue?: any): any { if (object === null || typeof object !== 'object' || typeof path !== 'string') { return defaultValue; } @@ -182,41 +197,44 @@ export function getValue(object, path, defaultValue) { } // copied from https://www.30secondsofcode.org/js/s/debounce -export const debounce = (fn, ms = 0) => { - let timeoutId; - return function(...args) { +export const debounce = (fn: (...args: any[]) => void, ms: number = 0) => { + let timeoutId: ReturnType; + return function (this: any, ...args: any[]) { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn.apply(this, args), ms); }; }; // copied from https://www.30secondsofcode.org/js/s/throttle -export const throttle = (fn, wait) => { - let inThrottle, lastFn, lastTime; - return function() { +export const throttle = (fn: (...args: any[]) => void, wait: number) => { + let inThrottle: boolean, lastFn: ReturnType, lastTime: number; + return function (this: any) { const context = this, args = arguments; if (!inThrottle) { - fn.apply(context, args); + fn.apply(context, args as any); lastTime = Date.now(); inThrottle = true; } else { clearTimeout(lastFn); - lastFn = setTimeout(function() { - if (Date.now() - lastTime >= wait) { - fn.apply(context, args); - lastTime = Date.now(); - } - }, Math.max(wait - (Date.now() - lastTime), 0)); + lastFn = setTimeout( + function () { + if (Date.now() - lastTime >= wait) { + fn.apply(context, args as any); + lastTime = Date.now(); + } + }, + Math.max(wait - (Date.now() - lastTime), 0), + ); } }; }; // copied from https://github.com/react-bootstrap/dom-helpers -let scrollbarSize; -export function getScrollbarSize(recalculate) { +let scrollbarSize: number | undefined; +export function getScrollbarSize(recalculate?: boolean): number { if ((!scrollbarSize && scrollbarSize !== 0) || recalculate) { - if (typeof window !== 'undefined' && window.document && window.document.createElement) { + if (typeof window !== 'undefined' && window.document && typeof window.document.createElement === 'function') { let scrollDiv = document.createElement('div'); scrollDiv.style.position = 'absolute'; @@ -231,10 +249,10 @@ export function getScrollbarSize(recalculate) { } } - return scrollbarSize; + return scrollbarSize as number; } -export function addClassName(el, className) { +export function addClassName(el: HTMLElement, className: string): void { if (el.classList) { el.classList.add(className); } else { @@ -244,7 +262,7 @@ export function addClassName(el, className) { } } -export function removeClassName(el, className) { +export function removeClassName(el: HTMLElement, className: string): void { if (el.classList) { el.classList.remove(className); } else { @@ -252,7 +270,10 @@ export function removeClassName(el, className) { } } -export function getEstimatedTotalRowsHeight(data, estimatedRowHeight) { +export function getEstimatedTotalRowsHeight( + data: RowData[], + estimatedRowHeight: number | ((args: { rowData: RowData; rowIndex: number }) => number), +): number { return typeof estimatedRowHeight === 'function' ? data.reduce((height, rowData, rowIndex) => height + estimatedRowHeight({ rowData, rowIndex }), 0) : data.length * estimatedRowHeight; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9327f078 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react", + "declaration": true, + "declarationDir": "dist/types", + "emitDeclarationOnly": true, + "outDir": "dist/types", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "website", "spec"] +} diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index 2b1287df..00000000 --- a/types/index.d.ts +++ /dev/null @@ -1,666 +0,0 @@ -declare module 'react-base-table' { - export type SortOrder = 'asc' | 'desc'; - - export type Alignment = 'left' | 'right' | 'center'; - - export type FrozenDirection = 'left' | 'right' | true | false; - - export type RowKey = string | number; - - export type Size = { width: number; height: number }; - - export type CallOrReturn = T | (P extends any[] ? (...p: P) => T : (p: P) => T); - - export interface ColumnShape { - /** - * Unique key for each column - */ - key: string | number; - /** - * Class name for the column cell - */ - className?: CallOrReturn< - string, - { - cellData: any; - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - rowData: T; - rowIndex: number; - } - >; - /** - * Class name for the column header - */ - headerClassName?: CallOrReturn< - string, - { - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - headerIndex: number; - } - >; - /** - * Custom style for the column cell, including the header cells - */ - style?: React.CSSProperties; - /** - * Title of the column header - */ - title?: string; - /** - * Data key for the cell value, could be "a.b.c" - */ - dataKey?: string; - /** - * Custom cell data getter - * The handler is of the shape of `({ columns, column, columnIndex, rowData, rowIndex }) => node` - */ - dataGetter?: CallOrReturn< - React.ReactNode, - { - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - rowData: T; - rowIndex: number; - } - >; - /** - * Alignment of the column cell - */ - align?: Alignment; - /** - * Flex grow style, defaults to 0 - */ - flexGrow?: number; - /** - * Flex shrink style, defaults to 1 for flexible table and 0 for fixed table - */ - flexShrink?: number; - /** - * The width of the column, gutter width is not included - */ - width: number; - /** - * Maximum width of the column, used if the column is resizable - */ - maxWidth?: number; - /** - * Minimum width of the column, used if the column is resizable - */ - minWidth?: number; - /** - * Whether the column is frozen and what's the frozen side - */ - frozen?: FrozenDirection; - /** - * Whether the column is hidden - */ - hidden?: boolean; - /** - * Whether the column is resizable, defaults to false - */ - resizable?: boolean; - /** - * Whether the column is sortable, defaults to false - */ - sortable?: boolean; - /** - * Custom column cell renderer - * The renderer receives props `{ cellData, columns, column, columnIndex, rowData, rowIndex, container, isScrolling }` - */ - cellRenderer?: CallOrReturn< - React.ReactNode, - { - cellData: any; - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - rowData: T; - rowIndex: number; - container: BaseTable; - isScrolling?: boolean; - } - >; - /** - * Custom column header renderer - * The renderer receives props `{ columns, column, columnIndex, headerIndex, container }` - */ - headerRenderer?: CallOrReturn< - React.ReactNode, - { - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - headerIndex: number; - container: BaseTable; - } - >; - [key: string]: any; - } - - export class Column extends React.Component> { - static readonly Alignment: { - readonly LEFT: 'left'; - readonly CENTER: 'center'; - readonly RIGHT: 'right'; - }; - static readonly FrozenDirection: { - readonly LEFT: 'left'; - readonly RIGHT: 'right'; - readonly DEFAULT: true; - readonly NONE: false; - }; - } - - export interface BaseTableProps { - /** - * Prefix for table's inner className - */ - classPrefix?: string; - /** - * Class name for the table - */ - className?: string; - /** - * Custom style for the table - */ - style?: React.CSSProperties; - /** - * A collection of Column - */ - children?: React.ReactNode; - /** - * Columns for the table - */ - columns?: ColumnShape[]; - /** - * The data for the table - */ - data?: T[]; - /** - * The data to be frozen to top, `rowIndex` is negative and starts from `-1` - */ - frozenData?: T[]; - /** - * The key field of each data item - */ - rowKey?: RowKey; - /** - * The width of the table - */ - width: number; - /** - * The height of the table, will be ignored if `maxHeight` is set - */ - height?: number; - /** - * The max height of the table, the table's height will auto change when data changes, - * will turns to vertical scroll if reaches the max height - */ - maxHeight?: number; - /** - * The height of each table row, will be ignored if `estimatedRowHeight` is set - */ - rowHeight?: number; - /** - * Estimated row height, the real height will be measure dynamically according to the content - * The callback is of the shape of `({ rowData, rowIndex }) => number` - */ - estimatedRowHeight?: CallOrReturn< - number, - { - rowData: T; - rowIndex: number; - } - >; - /** - * The height of the table header, set to 0 to hide the header, could be an array to render multi headers. - */ - headerHeight?: number | number[]; - /** - * The height of the table footer - */ - footerHeight?: number; - /** - * Whether the width of the columns are fixed or flexible - */ - fixed?: boolean; - /** - * Whether the table is disabled - */ - disabled?: boolean; - /** - * Custom renderer on top of the table component - */ - overlayRenderer?: CallOrReturn; - /** - * Custom renderer when the length of data is 0 - */ - emptyRenderer?: CallOrReturn; - /** - * Custom footer renderer, available only if `footerHeight` is larger then 0 - */ - footerRenderer?: CallOrReturn; - /** - * Custom header renderer - * The renderer receives props `{ cells, columns, headerIndex }` - */ - headerRenderer?: CallOrReturn< - React.ReactNode, - { - cells: React.ReactNode[]; - columns: ColumnShape; - headerIndex: number; - } - >; - /** - * Custom row renderer - * The renderer receives props `{ isScrolling, cells, columns, rowData, rowIndex, depth }` - */ - rowRenderer?: CallOrReturn< - React.ReactNode, - { - isScrolling?: boolean; - cells: React.ReactNode[]; - columns: ColumnShape; - rowData: T; - rowIndex: number; - depth: number; - } - >; - /** - * Class name for the table header, could be a callback to return the class name - * The callback is of the shape of `({ columns, headerIndex }) => string` - */ - headerClassName?: CallOrReturn< - string, - { - columns: ColumnShape[]; - headerIndex: number; - } - >; - /** - * Class name for the table row, could be a callback to return the class name - * The callback is of the shape of `({ columns, rowData, rowIndex }) => string` - */ - rowClassName?: CallOrReturn< - string, - { - columns: ColumnShape[]; - rowData: T; - rowIndex: number; - } - >; - /** - * Extra props applied to header element - * The handler is of the shape of `({ columns, headerIndex }) object` - */ - headerProps?: CallOrReturn< - object, - { - columns: ColumnShape[]; - headerIndex: number; - } - >; - /** - * Extra props applied to header cell element - * The handler is of the shape of `({ columns, column, columnIndex, headerIndex }) => object` - */ - headerCellProps?: CallOrReturn< - object, - { - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - headerIndex: number; - } - >; - /** - * Extra props applied to row element - * The handler is of the shape of `({ columns, rowData, rowIndex }) => object` - */ - rowProps?: CallOrReturn< - object, - { - columns: ColumnShape[]; - rowData: T; - rowIndex: number; - } - >; - /** - * Extra props applied to row cell element - * The handler is of the shape of `({ columns, column, columnIndex, rowData, rowIndex }) => object` - */ - cellProps?: CallOrReturn< - object, - { - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - rowData: T; - rowIndex: number; - } - >; - /** - * Extra props applied to ExpandIcon component - * The handler is of the shape of `({ rowData, rowIndex, depth, expandable, expanded }) => object` - */ - expandIconProps?: CallOrReturn< - object, - { - rowData: T; - rowIndex: number; - depth: number; - expandable: boolean; - expanded: boolean; - } - >; - /** - * The key for the expand column which render the expand icon if the data is a tree - */ - expandColumnKey?: string; - /** - * Default expanded row keys when initialize the table - */ - defaultExpandedRowKeys?: RowKey[]; - /** - * Controlled expanded row keys - */ - expandedRowKeys?: RowKey[]; - /** - * A callback function when expand or collapse a tree node - * The handler is of the shape of `({ expanded, rowData, rowIndex, rowKey }) => *` - */ - onRowExpand?: (args: { expanded: boolean; rowData: T; rowIndex: number; rowKey: RowKey }) => void; - /** - * A callback function when the expanded row keys changed - * The handler is of the shape of `(expandedRowKeys) => *` - */ - onExpandedRowsChange?: (expandedRowKeys: RowKey[]) => void; - /** - * The sort state for the table, will be ignored if `sortState` is set - */ - sortBy?: { - key: string | number; - order: SortOrder; - }; - /** - * Multiple columns sort state for the table - * - * example: - * ```js - * { - * 'column-0': SortOrder.ASC, - * 'column-1': SortOrder.DESC, - * } - * ``` - */ - sortState?: { - [key in string | number]: SortOrder; - }; - /** - * A callback function for the header cell click event - * The handler is of the shape of `({ column, key, order }) => *` - */ - onColumnSort?: (args: { column: ColumnShape; key: string | number; order: SortOrder }) => void; - /** - * A callback function when resizing the column width - * The handler is of the shape of `({ column, width }) => *` - */ - onColumnResize?: (args: { column: ColumnShape; width: number }) => void; - /** - * A callback function when resizing the column width ends - * The handler is of the shape of `({ column, width }) => *` - */ - onColumnResizeEnd?: (args: { column: ColumnShape; width: number }) => void; - /** - * Adds an additional isScrolling parameter to the row renderer. - * This parameter can be used to show a placeholder row while scrolling. - */ - useIsScrolling?: boolean; - /** - * Number of rows to render above/below the visible bounds of the list - */ - overscanRowCount?: number; - /** - * Custom scrollbar size measurement - */ - getScrollbarSize?: () => number; - /** - * A callback function when scrolling the table - * The handler is of the shape of `({ scrollLeft, scrollTop, horizontalScrollDirection, verticalScrollDirection, scrollUpdateWasRequested }) => *` - * - * `scrollLeft` and `scrollTop` are numbers. - * - * `horizontalDirection` and `verticalDirection` are either `forward` or `backward`. - * - * `scrollUpdateWasRequested` is a boolean. This value is true if the scroll was caused by `scrollTo*`, - * and false if it was the result of a user interaction in the browser. - */ - onScroll?: (args: { - scrollLeft: number; - scrollTop: number; - horizontalScrollDirection: 'forward' | 'backward'; - verticalScrollDirection: 'forward' | 'backward'; - scrollUpdateWasRequested: boolean; - }) => void; - /** - * A callback function when scrolling the table within `onEndReachedThreshold` of the bottom - * The handler is of the shape of `({ distanceFromEnd }) => *` - */ - onEndReached?: (args: { distanceFromEnd: number }) => void; - /** - * Threshold in pixels for calling `onEndReached`. - */ - onEndReachedThreshold?: number; - /** - * A callback function with information about the slice of rows that were just rendered - * The handler is of the shape of `({ overscanStartIndex, overscanStopIndex, startIndex, stopIndex }) => *` - */ - onRowsRendered?: (args: { - overscanStartIndex: number; - overscanStopIndex: number; - startIndex: number; - stopIndex: number; - }) => void; - /** - * A callback function when the scrollbar presence state changed - * The handler is of the shape of `({ size, vertical, horizontal }) => *` - */ - onScrollbarPresenceChange?: (args: { size: number; vertical: boolean; horizontal: boolean }) => void; - /** - * A object for the row event handlers - * Each of the keys is row event name, like `onClick`, `onDoubleClick` and etc. - * Each of the handlers is of the shape of `({ rowData, rowIndex, rowKey, event }) => *` - */ - rowEventHandlers?: { - [key: string]: (args: { rowData: T; rowIndex: number; rowKey: RowKey; event: React.SyntheticEvent }) => void; - }; - /** - * whether to ignore function properties while comparing column definition - */ - ignoreFunctionInColumnCompare?: boolean; - /** - * A object for the custom components, like `ExpandIcon` and `SortIndicator` - */ - components?: TableComponents; - [key: string]: any; - } - - export interface TableComponents { - TableCell?: React.ElementType<{ - className: string; - isScrolling?: boolean; - cellData: any; - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - rowData: T; - rowIndex: number; - container: BaseTable; - }>; - TableHeaderCell?: React.ElementType<{ - className: string; - columns: ColumnShape[]; - column: ColumnShape; - columnIndex: number; - headerIndex: number; - container: BaseTable; - }>; - ExpandIcon?: React.ElementType<{ - depth: number; - expandable: boolean; - expanded: boolean; - onExpand: (expanded: boolean) => void; - [key: string]: any; - }>; - SortIndicator?: React.ElementType<{ - sortOrder: SortOrder; - className: string; - }>; - } - - export default class BaseTable extends React.Component, any> { - static readonly Column: typeof Column; - static readonly PlaceholderKey = '__placeholder__'; - static defaultProps: Partial; - static propTypes: React.WeakValidationMap; - - /** - * Get the DOM node of the table - */ - getDOMNode(): HTMLDivElement | null; - /** - * Get the column manager - */ - getColumnManager(): any; - /** - * Get internal `expandedRowKeys` state - */ - getExpandedRowKeys(): RowKey[]; - /** - * Get the expanded state, fallback to normal state if not expandable. - */ - getExpandedState(): { - expandedData: T[]; - expandedRowKeys: RowKey[]; - expandedDepthMap: { [key in RowKey]: number }; - }; - /** - * Get the total height of all rows, including expanded rows. - */ - getTotalRowsHeight(): number; - /** - * Get the total width of all columns. - */ - getTotalColumnsWidth(): number; - /** - * Forcefully re-render the inner Grid component. - * - * Calling `forceUpdate` on `Table` may not re-render the inner Grid since it uses `shallowCompare` as a performance optimization. - * Use this method if you want to manually trigger a re-render. - * This may be appropriate if the underlying row data has changed but the row sizes themselves have not. - */ - forceUpdateTable(): void; - /** - * Reset cached offsets for positioning after a specific rowIndex, should be used only in dynamic mode(estimatedRowHeight is provided) - */ - resetAfterRowIndex(rowIndex?: number, shouldForceUpdate?: boolean): void; - /** - * Reset row height cache, useful if `data` changed entirely, should be used only in dynamic mode(estimatedRowHeight is provided) - */ - resetRowHeightCache(): void; - /** - * Scroll to the specified offset. - * Useful for animating position changes. - */ - scrollToPosition(offset: { scrollLeft: number; scrollTop: number }): void; - /** - * Scroll to the specified offset vertically. - */ - scrollToTop(scrollTop: number): void; - /** - * Scroll to the specified offset horizontally. - */ - scrollToLeft(scrollLeft: number): void; - /** - * Scroll to the specified row. - * By default, the table will scroll as little as possible to ensure the row is visible. - * You can control the alignment of the row though by specifying an align property. Acceptable values are: - * - * - `auto` (default) - Scroll as little as possible to ensure the row is visible. - * - `smart` - Same as `auto` if it is less than one viewport away, or it's the same as`center`. - * - `center` - Center align the row within the table. - * - `end` - Align the row to the bottom side of the table. - * - `start` - Align the row to the top side of the table. - */ - scrollToRow(rowIndex?: number, align?: 'auto' | 'smart' | 'center' | 'end' | 'start'): void; - /** - * Set `expandedRowKeys` manually. - * This method is available only if `expandedRowKeys` is uncontrolled. - */ - setExpandedRowKeys(expandedRowKeys: RowKey[]): void; - } - - export interface AutoResizerProps { - /** - * Class name for the component - */ - className?: string; - /** - * the width of the component, will be the container's width if not set - */ - width?: number; - /** - * the height of the component, will be the container's width if not set - */ - height?: number; - /** - * A callback function to render the children component - * The handler is of the shape of `({ width, height }) => node` - */ - children: (size: Size) => React.ReactNode; - /** - * A callback function when the size of the table container changed if the width and height are not set - * The handler is of the shape of `({ width, height }) => *` - */ - onResize?: (size: Size) => void; - } - - export const AutoResizer: React.FC; - - export function renderElement( - renderer: React.ReactElement | ((props: Partial) => React.ReactNode), - props?: T - ): React.ReactNode; - - export function normalizeColumns(elements: React.ReactNode[]): ColumnShape[]; - - export function isObjectEqual(objA: object, objB: object, ignoreFunction?: boolean): boolean; - - export function callOrReturn(funcOrValue: CallOrReturn, ...args: P): T; - - export function hasChildren(data: object): boolean; - - export function unflatten( - array: T[], - rootId?: any, - dataKey?: string, - parentKey?: string - ): (T & { children?: T[] })[]; - - export function flattenOnKeys( - tree: T[], - keys?: RowKey[], - depthMap?: { [key in RowKey]: number }, - dataKey?: string - ): T[]; - - export function getValue(object: any, path?: string, defaultValue?: any): any; - - export function getScrollbarSize(recalculate?: boolean): number; -} diff --git a/website/package.json b/website/package.json index 8e9fb8a5..afed1dce 100644 --- a/website/package.json +++ b/website/package.json @@ -1,54 +1,55 @@ { "name": "react-base-table-website", "description": "BaseTable website", - "version": "1.0.0", - "author": "Neo Nie ", + "version": "2.0.0", + "author": "Autodesk, Inc.", "homepage": "https://autodesk.github.io/react-base-table/", "dependencies": { - "classnames": "^2.2.6", - "clipboard": "^2.0.4", - "faker": "^4.1.0", - "gatsby": "^2.13.1", - "gatsby-plugin-catch-links": "^2.1.0", - "gatsby-plugin-google-analytics": "^2.1.1", - "gatsby-plugin-lodash": "^3.1.0", - "gatsby-plugin-manifest": "^2.2.1", - "gatsby-plugin-nprogress": "^2.1.0", - "gatsby-plugin-react-helmet": "^3.1.0", - "gatsby-plugin-remove-trailing-slashes": "^2.1.0", - "gatsby-plugin-styled-components": "^3.1.0", - "gatsby-remark-copy-linked-files": "^2.1.0", - "gatsby-source-filesystem": "^2.1.2", - "gatsby-transformer-code": "^0.1.0", - "gatsby-transformer-react-docgen": "^4.1.0", - "gatsby-transformer-remark": "^2.6.0", - "lz-string": "^1.4.4", - "minireset.css": "^0.0.5", - "prop-types": "^15.7.2", - "react": "^16.8.5", - "react-dom": "^16.8.5", - "react-helmet": "^5.2.0", - "react-inspector": "^2.3.1", - "react-live-runner": "^0.7.3", - "react-overlays": "^1.2.0", - "react-sortable-hoc": "^1.9.1", - "react-texty": "^0.1.0", - "rehype-react": "^3.1.0", + "@faker-js/faker": "^8.4.1", + "classnames": "^2.5.1", + "clipboard": "^2.0.11", + "gatsby": "^4.25.9", + "gatsby-plugin-catch-links": "^4.25.0", + "gatsby-plugin-google-analytics": "^4.25.0", + "gatsby-plugin-lodash": "^5.25.0", + "gatsby-plugin-manifest": "^4.25.0", + "gatsby-plugin-nprogress": "^4.25.0", + "gatsby-plugin-react-helmet": "^5.25.0", + "gatsby-plugin-remove-trailing-slashes": "^4.19.0", + "gatsby-plugin-styled-components": "^5.25.0", + "gatsby-remark-copy-linked-files": "^5.25.0", + "gatsby-source-filesystem": "^4.25.0", + "gatsby-transformer-code": "^0.2.0", + "gatsby-transformer-react-docgen": "^7.25.0", + "gatsby-transformer-remark": "^5.25.1", + "lz-string": "^1.5.0", + "minireset.css": "^0.0.7", + "prop-types": "^15.8.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-helmet": "^6.1.0", + "react-inspector": "^6.0.2", + "react-is": "^19.2.5", + "react-live-runner": "^1.0.7", + "react-overlays": "^5.2.1", + "react-sortable-hoc": "^2.0.0", + "react-texty": "^0.6.0", + "rehype-react": "^6.2.1", "seamless-immutable": "^7.1.4", - "slugify": "^1.3.4", - "styled-components": "^4.3.2" + "slugify": "^1.6.6", + "styled-components": "^5.3.11" }, "devDependencies": { - "babel-plugin-styled-components": "^1.10.6", - "gh-pages": "^2.0.1", - "lint-staged": "^9.0.1", - "prettier": "^1.18.2", - "rimraf": "^2.6.3" + "babel-plugin-styled-components": "^2.1.4", + "gh-pages": "^6.3.0", + "lint-staged": "^16.4.0", + "prettier": "^3.8.3", + "rimraf": "^6.1.3" }, "browserslist": [ "> 1%", - "IE >= 9", - "last 2 versions" + "last 2 versions", + "not dead" ], "scripts": { "start": "gatsby develop", @@ -62,12 +63,10 @@ }, "lint-staged": { "*.js": [ - "prettier --write", - "git add" + "prettier --write" ], "*.css": [ - "prettier --write", - "git add" + "prettier --write" ] } } diff --git a/website/src/components/ActionPanel.js b/website/src/components/ActionPanel.js index 40fa7d08..b3c71add 100644 --- a/website/src/components/ActionPanel.js +++ b/website/src/components/ActionPanel.js @@ -1,6 +1,6 @@ import React from 'react' import styled from 'styled-components' -import Inspector from 'react-inspector' +import { Inspector } from 'react-inspector' import CornerButton from './CornerButton' @@ -54,7 +54,7 @@ class ActionPanel extends React.Component { ) } - onAction = action => { + onAction = (action) => { this.setState(({ actions }) => ({ actions: [action, ...actions.slice(0, 99)], })) diff --git a/website/src/components/CodeBlock.js b/website/src/components/CodeBlock.js index 73abf78f..ac442a9e 100644 --- a/website/src/components/CodeBlock.js +++ b/website/src/components/CodeBlock.js @@ -22,7 +22,9 @@ const StyledCode = styled(Code)` const CodeBlock = ({ code = '', language = 'jsx', ...rest }) => ( - + + {code.trim()} + diff --git a/website/src/components/CodeEditor.js b/website/src/components/CodeEditor.js index 95a1074c..7d516ed5 100644 --- a/website/src/components/CodeEditor.js +++ b/website/src/components/CodeEditor.js @@ -16,7 +16,13 @@ const EditorContainer = styled.div` ` const StyledEditor = styled(Editor)` - font-family: source-code-pro, Menlo, Monaco, Consolas, Courier New, monospace; + font-family: + source-code-pro, + Menlo, + Monaco, + Consolas, + Courier New, + monospace; font-size: 1.4rem; white-space: pre; background: #222; @@ -35,16 +41,23 @@ const StyledEditor = styled(Editor)` const CodeEditor = ({ sourceCode, language, onChange, ...rest }) => { const [code, setCode] = useState(sourceCode) const debouncedChange = useMemo(() => debounce(onChange, 300), [onChange]) - const handleChange = useCallback(code => { - setCode(code) - debouncedChange(code) - }, [debouncedChange]) + const handleChange = useCallback( + (code) => { + setCode(code) + debouncedChange(code) + }, + [debouncedChange] + ) useEffect(() => setCode(sourceCode), [sourceCode]) return ( - + diff --git a/website/src/components/CodePreview.js b/website/src/components/CodePreview.js index fdb449b3..f3c3b6fa 100644 --- a/website/src/components/CodePreview.js +++ b/website/src/components/CodePreview.js @@ -54,14 +54,13 @@ const CodePreview = ({ ...rest }) => { const { action, channel } = useMemo(createActionChannel, []) - const scope = useMemo(() => ({ ...baseScope, action, ..._scope }), [ - action, - _scope, - ]) + const scope = useMemo( + () => ({ ...baseScope, action, ..._scope }), + [action, _scope] + ) const { element, error, onChange } = useLiveRunner({ - sourceCode, + initialCode: sourceCode, scope, - type, }) return ( diff --git a/website/src/components/Header.js b/website/src/components/Header.js index ac7b11d4..84d22709 100644 --- a/website/src/components/Header.js +++ b/website/src/components/Header.js @@ -51,8 +51,11 @@ const NavLink = styled(Link).attrs({ } &, &:focus { - color: ${props => - props.pathname && props.pathname.includes(props.to) ? '#fff' : '#bcc9d1'}; + color: ${(props) => + props.pathname && + props.pathname.startsWith(props.activePrefix || props.to) + ? '#fff' + : '#bcc9d1'}; } &:last-child { padding-right: 0; @@ -85,13 +88,21 @@ const Header = ({ pathname }) => { v{pkg.version} - + Docs - + API - + Examples diff --git a/website/src/components/Html.js b/website/src/components/Html.js index c8b74f2a..687b0469 100644 --- a/website/src/components/Html.js +++ b/website/src/components/Html.js @@ -4,12 +4,12 @@ import rehypeReact from 'rehype-react' import CodeBlock from './CodeBlock' import CodePreview from './CodePreview' -const parseMeta = meta => { +const parseMeta = (meta) => { const options = {} if (!meta) return options const items = meta.split(/\s+/) - items.forEach(item => { + items.forEach((item) => { if (/^[\w-]+=?$/.test(item)) options[item] = true else if (/^[\w-]+=[^=]+$/.test(item)) { const [key, value] = item.split('=') @@ -32,7 +32,7 @@ const parseMeta = meta => { return options } -const Pre = props => { +const Pre = (props) => { if (!props.children[0]) return
 
   const { children, className } = props.children[0].props
@@ -54,6 +54,7 @@ const Pre = props => {
 
 const renderAst = new rehypeReact({
   createElement: React.createElement,
+  Fragment: React.Fragment,
   components: {
     pre: Pre,
   },
diff --git a/website/src/components/Playground.js b/website/src/components/Playground.js
index b745bc9f..c7169172 100644
--- a/website/src/components/Playground.js
+++ b/website/src/components/Playground.js
@@ -50,12 +50,11 @@ const Playground = ({ scope: _scope, language, type, ...rest }) => {
   const scope = useMemo(() => ({ ...baseScope, ..._scope }), [_scope])
   const [sourceCode, setSourceCode] = useState(getCode)
   const { element, error, onChange } = useLiveRunner({
-    sourceCode,
+    initialCode: sourceCode,
     scope,
-    type,
   })
   const handleChange = useCallback(
-    code => {
+    (code) => {
       onChange(code)
       replaceState(code)
     },
diff --git a/website/src/examples/custom-cell.js b/website/src/examples/custom-cell.js
index eef0d6b8..3c636d26 100644
--- a/website/src/examples/custom-cell.js
+++ b/website/src/examples/custom-cell.js
@@ -1,24 +1,24 @@
 const dataGenerator = () => ({
-  id: faker.random.uuid(),
-  name: faker.name.findName(),
-  gender: faker.random.boolean() ? 'male' : 'female',
+  id: faker.string.uuid(),
+  name: faker.person.fullName(),
+  gender: faker.datatype.boolean() ? 'male' : 'female',
   score: {
-    math: faker.random.number(70) + 30,
+    math: faker.number.int({ min: 30, max: 100 }),
   },
-  birthday: faker.date.between(1995, 2005),
-  attachments: faker.random.number(5),
+  birthday: faker.date.between({ from: '1995-01-01', to: '2005-12-31' }),
+  attachments: faker.number.int({ max: 5 }),
   description: faker.lorem.sentence(),
   email: faker.internet.email(),
-  country: faker.address.country(),
+  country: faker.location.country(),
   address: {
-    street: faker.address.streetAddress(),
-    city: faker.address.city(),
-    zipCode: faker.address.zipCode(),
+    street: faker.location.streetAddress(),
+    city: faker.location.city(),
+    zipCode: faker.location.zipCode(),
   },
 })
 
 const GenderContainer = styled.div`
-  background-color: ${props =>
+  background-color: ${(props) =>
     props.gender === 'male' ? 'lightblue' : 'pink'};
   color: white;
   border-radius: 3px;
@@ -37,7 +37,7 @@ const Gender = ({ gender }) => (
 )
 
 const Score = styled.span`
-  color: ${props => (props.score >= 60 ? 'green' : 'red')};
+  color: ${(props) => (props.score >= 60 ? 'green' : 'red')};
 `
 
 const Attachment = styled.div`
@@ -80,7 +80,9 @@ export default class App extends React.Component {
       width: 60,
       align: Column.Alignment.CENTER,
       sortable: false,
-      cellRenderer: ({ cellData: score }) => {score},
+      cellRenderer: ({ cellData: score }) => (
+        {score}
+      ),
     },
     {
       key: 'gender',
@@ -151,7 +153,7 @@ export default class App extends React.Component {