├── .github └── workflows │ ├── codecov.yml │ ├── eslint.yml │ ├── gh-pages-test.yml │ └── gh-pages.yml ├── .gitignore ├── .prettierrc.json ├── .resources └── images │ └── working.gif ├── CNAME ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── package.json ├── packages └── rc-grid │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.json │ ├── .storybook │ ├── global.css │ ├── main.js │ └── preview.js │ ├── README.md │ ├── babel.config.js │ ├── jest.config.ts │ ├── package.json │ ├── src │ ├── AutoSize.tsx │ ├── Cell.tsx │ ├── Context.ts │ ├── DataGrid.tsx │ ├── HeaderCell.tsx │ ├── HeaderRow.tsx │ ├── Icon.tsx │ ├── Row.tsx │ ├── index.ts │ ├── plugins │ │ └── UniversalToolbar.tsx │ ├── types.ts │ └── utils │ │ ├── browser.ts │ │ ├── debounce.ts │ │ └── theme.ts │ ├── stories │ ├── big-data.stories.tsx │ ├── cell-editor.stories.tsx │ ├── cells-merge.stories.tsx │ ├── disable-select-cell.stories.tsx │ ├── empty-rows.stories.tsx │ ├── expandable-row.stories.tsx │ ├── expandable-tree-row.stories.tsx │ ├── filter-data.stories.tsx │ ├── foot.stories.tsx │ ├── header-merge.stories.tsx │ ├── header-resizable.stories.tsx │ ├── select-multiple.stories.tsx │ ├── select-single.stories.tsx │ ├── sort-data.stories.tsx │ └── utils.ts │ ├── tests │ ├── __mocks__ │ │ └── ResizeObserver.mock.ts │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── bash-grid.test.tsx.snap │ │ │ ├── browser-utils.test.tsx.snap │ │ │ ├── cells-merge.test.tsx.snap │ │ │ ├── drag-grid.test.tsx.snap │ │ │ ├── expandable-grid.test.tsx.snap │ │ │ ├── foot-grid.test.tsx.snap │ │ │ ├── select-multiple.test.tsx.snap │ │ │ └── tree-grid.test.tsx.snap │ │ ├── bash-grid.test.tsx │ │ ├── browser-utils.test.tsx │ │ ├── cells-merge.test.tsx │ │ ├── debounce.test.ts │ │ ├── drag-grid.test.tsx │ │ ├── expandable-grid.test.tsx │ │ ├── foot-grid.test.tsx │ │ ├── select-multiple.test.tsx │ │ └── tree-grid.test.tsx │ └── setup.ts │ └── tsconfig.json ├── website ├── .editorconfig ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .umirc.ts ├── docs │ ├── getting-started │ │ ├── api.md │ │ ├── api.zh-CN.md │ │ ├── change-log.md │ │ ├── change-log.zh-CN.md │ │ ├── example │ │ │ ├── cells-merge.md │ │ │ ├── cells-merge.zh-CN.md │ │ │ ├── dark.md │ │ │ ├── dark.zh-CN.md │ │ │ ├── editor.md │ │ │ ├── editor.zh-CN.md │ │ │ ├── empty-rows.md │ │ │ ├── empty-rows.zh-CN.md │ │ │ ├── expandable-row.md │ │ │ ├── expandable-row.zh-CN.md │ │ │ ├── fixed-column.md │ │ │ ├── fixed-column.zh-CN.md │ │ │ ├── header-merge.md │ │ │ ├── header-merge.zh-CN.md │ │ │ ├── header-resizable.md │ │ │ ├── header-resizable.zh-CN.md │ │ │ ├── scroll-to.md │ │ │ ├── scroll-to.zh-CN.md │ │ │ ├── select-multiple.md │ │ │ ├── select-multiple.zh-CN.md │ │ │ ├── simple.md │ │ │ ├── simple.zh-CN.md │ │ │ ├── sort-data.md │ │ │ └── sort-data.zh-CN.md │ │ ├── introduce.md │ │ └── introduce.zh-CN.md │ ├── index.md │ └── index.zh-CN.md ├── embed │ ├── api.md │ └── api.zh-CN.md ├── package.json ├── src │ ├── cells-merge.tsx │ ├── dark.tsx │ ├── editor.tsx │ ├── empty-rows.tsx │ ├── expandable-row.tsx │ ├── fixed-column.tsx │ ├── header-merge.tsx │ ├── header-resizable.tsx │ ├── scroll-to.tsx │ ├── select-multiple.tsx │ ├── simple.tsx │ └── sort-data.tsx ├── tsconfig.json └── typings.d.ts └── yarn.lock /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [14.x] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: yarn 20 | - run: yarn test:codecov -------------------------------------------------------------------------------- /.github/workflows/eslint.yml: -------------------------------------------------------------------------------- 1 | name: eslint all 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [14.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: yarn 21 | - run: yarn lint:script -------------------------------------------------------------------------------- /.github/workflows/gh-pages-test.yml: -------------------------------------------------------------------------------- 1 | name: Test Build Deploy GitHub Pages 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build-and-deploy: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | with: 12 | persist-credentials: false 13 | 14 | - name: Build 15 | run: yarn && yarn build:docs && cp CNAME website/dist/ -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - canary 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | with: 14 | persist-credentials: false 15 | 16 | - name: Build 17 | run: yarn && yarn build:docs && cp CNAME website/dist/ 18 | 19 | - name: Deploy 20 | uses: JamesIves/github-pages-deploy-action@releases/v3 21 | with: 22 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} 23 | BRANCH: gh-pages 24 | FOLDER: website/dist/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Dependency directories 11 | node_modules 12 | 13 | storybook-static 14 | 15 | # husky 16 | .husky 17 | front 18 | 19 | 20 | # yarn 21 | .yarn 22 | .pnp.js 23 | .yarnrc.yml -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.resources/images/working.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HighPerformanceComponent/rc-grid/236014d4b544478546e067ec2009981f6ac58ee6/.resources/images/working.gif -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | rc-grid.lif.ink -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | zhangjin0908@hotmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-present HighPerformanceComponent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rc-grid 表格组件 2 | 3 | [![codecov](https://codecov.io/gh/HighPerformanceComponent/rc-grid/branch/canary/graph/badge.svg?token=0YTUDE0V88)](https://codecov.io/gh/HighPerformanceComponent/rc-grid) 4 | ![type-badge](https://img.shields.io/npm/types/react-data-grid) 5 | ![eslint](https://github.com/HighPerformanceComponent/rc-grid/actions/workflows/eslint.yml/badge.svg) 6 | ![MIT](https://img.shields.io/npm/l/@lifhcp/rc-grid) 7 | ![Size](https://img.shields.io/bundlephobia/min/@lifhcp/rc-grid) 8 | [![Version](https://img.shields.io/npm/v/@lifhcp/rc-grid)](https://www.npmjs.com/package/@lifhcp/rc-grid) 9 | 10 | ![working](/.resources/images/working.gif) 11 | 12 | 高性能的 React 表格组件, 在不丢失表格的强大功能的情况下,并且保持高性能 13 | 14 | # 安装使用 15 | 16 | ``` 17 | npm install @lifhcp/rc-grid --save 18 | ``` 19 | 20 | ``` 21 | yarn add @lifhcp/rc-grid 22 | ``` 23 | 24 | ## Browsers support 25 | 26 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | [Electron](http://godban.github.io/browsers-support-badges/)
Electron | 27 | | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | 28 | | last 2 versions | last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions 29 | 30 | 31 | ## 功能对比 32 | 33 | 34 | |功能描述 | rc-grid | ag-grid | react-data-grid | ant design | handsontable | 35 | |--------|-------- |------- |--------- |------ |----------- | 36 | |固定左 | ✅ | ✅ | ✅ |✅ | ✅ 37 | |右列 | ✅ | ✅ | ❌ |✅ | ✅ 38 | |列可拖拽改变大小| ✅ | ✅ | ✅ |⚠️ 需要自定义列头 | ✅ 39 | |列头合并 | ✅ | ✅ | ❌ | ✅ | ✅ 40 | |虚拟滚动 | ✅ | ✅ | ✅ |⚠️ 需要重写渲染 | ✅ 41 | |单元格编辑 | ✅ | ✅ | ✅ | ⚠️ 需要重写cell | ✅ 42 | |可展开表格 | ✅ | ✅ | ✅ | ✅ | ❌ 43 | |树形表格 | ✅ | ✅ | ✅ | ✅ | ❌ 44 | |开源协议 | MIT | MIT | MIT | MIT | 商业 45 | 46 | > 注明: 此数据仅供参照,组件库可能是随着更新,而添加新的功能 47 | 48 | ## 关于里程碑 49 | 50 | 保持每个月月底完成一次里程碑的开发,并且发布到 npm 仓库中, 我们所有的开发全部托管在 github 上进行开发 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rc-project", 3 | "repository": "https://github.com/jhoneybee/rc-grid.git", 4 | "private": true, 5 | "author": "jhoneybee ", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "yarn workspace @lifhcp/rc-grid start", 9 | "start:docs": "yarn workspace @lifhcp/rc-grid build && yarn workspace website start", 10 | "build": "yarn workspace @lifhcp/rc-grid build", 11 | "lint:script": "yarn workspace @lifhcp/rc-grid lint:script", 12 | "build:storybook": "yarn workspace @lifhcp/rc-grid build-storybook", 13 | "build:docs": "yarn workspace @lifhcp/rc-grid build && yarn workspace website build", 14 | "test": "yarn workspace @lifhcp/rc-grid jest", 15 | "test:codecov": "yarn workspace @lifhcp/rc-grid test:codecov" 16 | }, 17 | "devDependencies": { 18 | "husky": ">=6", 19 | "lint-staged": "^11.0.0" 20 | }, 21 | "workspaces": [ 22 | "packages/*", 23 | "website" 24 | ], 25 | "resolutions": { 26 | "@types/react": "^17.0.11" 27 | }, 28 | "lint-staged": { 29 | "**/*.{js,jsx,tsx,ts,less,json}": [ 30 | "prettier --write" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/rc-grid/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | jest: true 6 | }, 7 | extends: [ 8 | 'plugin:react/recommended', 9 | 'airbnb', 10 | 'prettier', 11 | 'plugin:import/typescript', 12 | ], 13 | globals: { 14 | JSX: true, 15 | }, 16 | parser: '@typescript-eslint/parser', 17 | parserOptions: { 18 | ecmaFeatures: { 19 | jsx: true, 20 | }, 21 | ecmaVersion: 12, 22 | sourceType: 'module', 23 | }, 24 | plugins: ['react', '@typescript-eslint'], 25 | rules: { 26 | indent: ['error', 4], 27 | 'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }], 28 | 'import/no-extraneous-dependencies': [ 29 | 'error', 30 | { 31 | devDependencies: true, 32 | optionalDependencies: true, 33 | peerDependencies: true, 34 | }, 35 | ], 36 | 'import/extensions': [ 37 | 'error', 38 | 'ignorePackages', 39 | { 40 | js: 'never', 41 | mjs: 'never', 42 | jsx: 'never', 43 | ts: 'never', 44 | tsx: 'never', 45 | }, 46 | ], 47 | 'no-use-before-define': 'off', 48 | '@typescript-eslint/no-use-before-define': ['error'], 49 | 'no-unused-vars': 'off', 50 | '@typescript-eslint/no-unused-vars': 'error', 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/rc-grid/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Dependency directories 11 | node_modules 12 | 13 | storybook-static 14 | 15 | # husky 16 | .husky 17 | 18 | # lock 19 | yarn.lock 20 | 21 | dist 22 | 23 | 24 | # test 25 | coverage -------------------------------------------------------------------------------- /packages/rc-grid/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/rc-grid/.storybook/global.css: -------------------------------------------------------------------------------- 1 | #root { 2 | height: 100vh; 3 | padding: 8px; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | padding: 0px !important; 9 | } 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/rc-grid/.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: [ 3 | '../stories/**/*.stories.mdx', 4 | '../stories/**/*.stories.@(js|jsx|ts|tsx)', 5 | ], 6 | addons: ['@storybook/addon-links', '@storybook/addon-essentials'], 7 | typescript: { 8 | reactDocgen: 'none', 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /packages/rc-grid/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | require('./global.css') 2 | 3 | export const parameters = { 4 | actions: { argTypesRegex: '^on[A-Z].*' }, 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/, 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/rc-grid/README.md: -------------------------------------------------------------------------------- 1 | # rc-grid 表格组件 2 | 3 | [![codecov](https://codecov.io/gh/HighPerformanceComponent/rc-grid/branch/canary/graph/badge.svg?token=0YTUDE0V88)](https://codecov.io/gh/HighPerformanceComponent/rc-grid) 4 | ![type-badge](https://img.shields.io/npm/types/react-data-grid) 5 | ![eslint](https://github.com/HighPerformanceComponent/rc-grid/actions/workflows/eslint.yml/badge.svg) 6 | ![MIT](https://img.shields.io/npm/l/@lifhcp/rc-grid) 7 | ![Size](https://img.shields.io/bundlephobia/min/@lifhcp/rc-grid) 8 | [![Version](https://img.shields.io/npm/v/@lifhcp/rc-grid)](https://www.npmjs.com/package/@lifhcp/rc-grid) 9 | 10 | ![working](/.resources/images/working.gif) 11 | 12 | 高性能的 React 表格组件, 在不丢失表格的强大功能的情况下,并且保持高性能 13 | 14 | # 安装使用 15 | 16 | ``` 17 | npm install @lifhcp/rc-grid --save 18 | ``` 19 | 20 | ``` 21 | yarn add @lifhcp/rc-grid 22 | ``` 23 | 24 | ## Browsers support 25 | 26 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | [Electron](http://godban.github.io/browsers-support-badges/)
Electron | 27 | | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | 28 | | last 2 versions | last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions 29 | 30 | 31 | ## 功能对比 32 | 33 | 34 | |功能描述 | rc-grid | ag-grid | react-data-grid | ant design | handsontable | 35 | |--------|-------- |------- |--------- |------ |----------- | 36 | |固定左 | ✅ | ✅ | ✅ |✅ | ✅ 37 | |右列 | ✅ | ✅ | ❌ |✅ | ✅ 38 | |列可拖拽改变大小| ✅ | ✅ | ✅ |⚠️ 需要自定义列头 | ✅ 39 | |列头合并 | ✅ | ✅ | ❌ | ✅ | ✅ 40 | |虚拟滚动 | ✅ | ✅ | ✅ |⚠️ 需要重写渲染 | ✅ 41 | |单元格编辑 | ✅ | ✅ | ✅ | ⚠️ 需要重写cell | ✅ 42 | |可展开表格 | ✅ | ✅ | ✅ | ✅ | ❌ 43 | |树形表格 | ✅ | ✅ | ✅ | ✅ | ❌ 44 | |开源协议 | MIT | MIT | MIT | MIT | 商业 45 | 46 | > 注明: 此数据仅供参照,组件库可能是随着更新,而添加新的功能 47 | 48 | ## 关于里程碑 49 | 50 | 保持每个月月底完成一次里程碑的开发,并且发布到 npm 仓库中, 我们所有的开发全部托管在 github 上进行开发 51 | -------------------------------------------------------------------------------- /packages/rc-grid/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', { 3 | targets: { 4 | node: 'current', 5 | } 6 | }], '@babel/preset-react', '@babel/preset-typescript'] 7 | }; -------------------------------------------------------------------------------- /packages/rc-grid/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/tmp/jest_rs", 15 | 16 | // Automatically clear mock calls and instances between every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: true, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: "coverage", 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | // coverageProvider: "babel", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // Force coverage collection from ignored files using an array of glob patterns 54 | // forceCoverageMatch: [], 55 | 56 | // A path to a module which exports an async function that is triggered once before all test suites 57 | // globalSetup: undefined, 58 | 59 | // A path to a module which exports an async function that is triggered once after all test suites 60 | // globalTeardown: undefined, 61 | 62 | // A set of global variables that need to be available in all test environments 63 | // globals: {}, 64 | 65 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 66 | // maxWorkers: "50%", 67 | 68 | // An array of directory names to be searched recursively up from the requiring module's location 69 | // moduleDirectories: [ 70 | // "node_modules" 71 | // ], 72 | 73 | // An array of file extensions your modules use 74 | // moduleFileExtensions: [ 75 | // "js", 76 | // "jsx", 77 | // "ts", 78 | // "tsx", 79 | // "json", 80 | // "node" 81 | // ], 82 | 83 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 84 | // moduleNameMapper: {}, 85 | 86 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 87 | // modulePathIgnorePatterns: [], 88 | 89 | // Activates notifications for test results 90 | // notify: false, 91 | 92 | // An enum that specifies notification mode. Requires { notify: true } 93 | // notifyMode: "failure-change", 94 | 95 | // A preset that is used as a base for Jest's configuration 96 | // preset: undefined, 97 | 98 | // Run tests from one or more projects 99 | // projects: undefined, 100 | 101 | // Use this configuration option to add custom reporters to Jest 102 | // reporters: undefined, 103 | 104 | // Automatically reset mock state between every test 105 | // resetMocks: false, 106 | 107 | // Reset the module registry before running each individual test 108 | // resetModules: false, 109 | 110 | // A path to a custom resolver 111 | // resolver: undefined, 112 | 113 | // Automatically restore mock state between every test 114 | // restoreMocks: false, 115 | 116 | // The root directory that Jest should scan for tests and modules within 117 | // rootDir: undefined, 118 | 119 | // A list of paths to directories that Jest should use to search for files in 120 | // roots: [ 121 | // "" 122 | // ], 123 | 124 | // Allows you to use a custom runner instead of Jest's default test runner 125 | // runner: "jest-runner", 126 | 127 | // The paths to modules that run some code to configure or set up the testing environment before each test 128 | setupFiles: [ 129 | "./tests/setup.ts" 130 | ], 131 | 132 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 133 | // setupFilesAfterEnv: [], 134 | 135 | // The number of seconds after which a test is considered as slow and reported as such in the results. 136 | // slowTestThreshold: 5, 137 | 138 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 139 | // snapshotSerializers: [], 140 | 141 | // The test environment that will be used for testing 142 | testEnvironment: "jsdom", 143 | 144 | // Options that will be passed to the testEnvironment 145 | // testEnvironmentOptions: {}, 146 | 147 | // Adds a location field to test results 148 | // testLocationInResults: false, 149 | 150 | // The glob patterns Jest uses to detect test files 151 | // testMatch: [ 152 | // "**/__tests__/**/*.[jt]s?(x)", 153 | // "**/?(*.)+(spec|test).[tj]s?(x)" 154 | // ], 155 | 156 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 157 | // testPathIgnorePatterns: [ 158 | // "/node_modules/" 159 | // ], 160 | 161 | // The regexp pattern or array of patterns that Jest uses to detect test files 162 | // testRegex: [], 163 | 164 | // This option allows the use of a custom results processor 165 | // testResultsProcessor: undefined, 166 | 167 | // This option allows use of a custom test runner 168 | // testRunner: "jest-circus/runner", 169 | 170 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 171 | // testURL: "http://localhost", 172 | 173 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 174 | // timers: "real", 175 | 176 | // A map from regular expressions to paths to transformers 177 | // transform: undefined, 178 | 179 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 180 | // transformIgnorePatterns: [ 181 | // "/node_modules/", 182 | // "\\.pnp\\.[^\\/]+$" 183 | // ], 184 | 185 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 186 | // unmockedModulePathPatterns: undefined, 187 | 188 | // Indicates whether each individual test should be reported during the run 189 | // verbose: undefined, 190 | 191 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 192 | // watchPathIgnorePatterns: [], 193 | 194 | // Whether to use watchman for file crawling 195 | // watchman: true, 196 | }; 197 | -------------------------------------------------------------------------------- /packages/rc-grid/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lifhcp/rc-grid", 3 | "version": "1.2.2", 4 | "license": "MIT", 5 | "main": "dist/index.js", 6 | "repository": "https://github.com/HighPerformanceComponent/rc-grid.git", 7 | "homepage": "https://rc-grid.lif.ink/", 8 | "author": "jhoneybee ", 9 | "files": [ 10 | "dist" 11 | ], 12 | "scripts": { 13 | "start": "start-storybook -p 8000", 14 | "build": "tsc", 15 | "build:storybook": "build-storybook", 16 | "prepare": "cd ../../ && husky install front/.husky", 17 | "lint:script": "eslint src --ext .js,.jsx,.ts,.tsx", 18 | "test": "jest", 19 | "test:codecov": "jest --ci --coverage && codecov", 20 | "prepublishOnly": "yarn build" 21 | }, 22 | "dependencies": { 23 | "styled-components": "^5.3.0" 24 | }, 25 | "peerDependencies": { 26 | "react": "^17.0.2", 27 | "react-dom": "^17.0.2" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.14.8", 31 | "@babel/preset-env": "^7.14.8", 32 | "@babel/preset-react": "^7.14.5", 33 | "@babel/preset-typescript": "^7.14.5", 34 | "@storybook/addon-actions": "^6.3.2", 35 | "@storybook/addon-essentials": "^6.3.2", 36 | "@storybook/addon-links": "^6.3.2", 37 | "@storybook/react": "^6.3.2", 38 | "@testing-library/jest-dom": "^5.14.1", 39 | "@testing-library/react": "^12.0.0", 40 | "@types/jest": "^26.0.24", 41 | "@types/react": "^17.0.11", 42 | "@types/styled-components": "^5.1.10", 43 | "@typescript-eslint/eslint-plugin": "^4.28.0", 44 | "@typescript-eslint/parser": "^4.28.0", 45 | "babel-jest": "^27.0.6", 46 | "babel-loader": "^8.2.2", 47 | "codecov": "^3.8.3", 48 | "eslint": "^7.29.0", 49 | "eslint-config-airbnb": "^18.2.1", 50 | "eslint-config-prettier": "^8.3.0", 51 | "eslint-plugin-import": "^2.23.4", 52 | "eslint-plugin-jsx-a11y": "^6.4.1", 53 | "eslint-plugin-prettier": "^3.4.0", 54 | "eslint-plugin-react": "^7.24.0", 55 | "eslint-plugin-react-hooks": "^4.2.0", 56 | "immer": "^9.0.5", 57 | "jest": "^27.0.6", 58 | "prettier": "^2.3.1", 59 | "prettier-eslint": "^12.0.0", 60 | "ts-jest": "^27.0.4", 61 | "ts-loader": "^9.2.3", 62 | "ts-node": "^10.1.0", 63 | "typescript": "^4.3.4" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/rc-grid/src/AutoSize.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, useLayoutEffect, useRef, useState } from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | width: 100%; 6 | height: 100%; 7 | ` 8 | 9 | interface AutoSizeProps 10 | extends Omit, 'children'> { 11 | children: (width: number, height: number) => ReactNode 12 | } 13 | 14 | const AutoSize = (props: AutoSizeProps) => { 15 | const { children, ...restProps } = props 16 | 17 | const ref = useRef(null) 18 | 19 | const [width, setWidth] = useState(0) 20 | const [height, setHeight] = useState(0) 21 | 22 | useLayoutEffect(() => { 23 | const observer = new ResizeObserver(() => { 24 | const rect = ref.current.getBoundingClientRect() 25 | setWidth(rect.width) 26 | setHeight(rect.height) 27 | }) 28 | observer.observe(ref.current) 29 | return () => { 30 | observer.unobserve(ref.current) 31 | observer.disconnect() 32 | } 33 | }, []) 34 | return ( 35 | 40 | {children(width, height)} 41 | 42 | ) 43 | } 44 | 45 | export default AutoSize 46 | -------------------------------------------------------------------------------- /packages/rc-grid/src/Context.ts: -------------------------------------------------------------------------------- 1 | import { createContext, Dispatch, Key } from 'react' 2 | import type { EditorChange, SortColumn } from './types' 3 | 4 | type Action = 5 | | { 6 | type: 'setSelectPosition' 7 | payload: { 8 | x: number 9 | y: string 10 | } 11 | } 12 | | { 13 | type: 'setEditorChange' 14 | payload: EditorChange[] 15 | } 16 | | { 17 | type: 'setSortColumn' 18 | payload: SortColumn[] 19 | } 20 | | { 21 | type: 'setExpandableKey' 22 | payload: Key[] 23 | } 24 | | { 25 | type: 'setEditPosition' 26 | payload: { 27 | rowKey?: string 28 | colName?: string 29 | } 30 | } 31 | | { 32 | type: 'setExpandableTreeKey' 33 | payload: Key[] 34 | } 35 | export interface State { 36 | selectPosition?: { 37 | x: number 38 | y: string 39 | } 40 | editPosition?: { 41 | rowKey?: string 42 | colName?: string 43 | } 44 | editorChange: EditorChange[] 45 | expandableKey: Key[] 46 | expandableTreeKey: Key[] 47 | sortColumns: SortColumn[] 48 | id: number 49 | } 50 | 51 | const Context = createContext<{ 52 | state: State 53 | dispatch: Dispatch 54 | }>({ 55 | state: { 56 | editorChange: [], 57 | sortColumns: [], 58 | expandableKey: [], 59 | expandableTreeKey: [], 60 | id: 0, 61 | }, 62 | dispatch: () => null, 63 | }) 64 | 65 | export function reducer(state: State, action: Action): State { 66 | if (action.type === 'setSelectPosition') { 67 | return { ...state, selectPosition: action.payload } 68 | } 69 | if (action.type === 'setEditPosition') { 70 | return { ...state, editPosition: action.payload } 71 | } 72 | 73 | if (action.type === 'setEditorChange') { 74 | return { ...state, editorChange: action.payload } 75 | } 76 | 77 | if (action.type === 'setSortColumn') { 78 | return { ...state, sortColumns: action.payload } 79 | } 80 | 81 | if (action.type === 'setExpandableKey') { 82 | return { ...state, expandableKey: action.payload } 83 | } 84 | 85 | if (action.type === 'setExpandableTreeKey') { 86 | return { ...state, expandableTreeKey: action.payload } 87 | } 88 | 89 | throw Error(`reducer unknown type [${(action as any).type}]`) 90 | } 91 | 92 | export default Context 93 | -------------------------------------------------------------------------------- /packages/rc-grid/src/HeaderRow.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode, CSSProperties, useMemo } from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { DataGridProps } from './types' 5 | import HeaderCell from './HeaderCell' 6 | 7 | interface GridHeaderRowProps extends React.HTMLAttributes { 8 | styled: CSSProperties 9 | } 10 | 11 | const GridHeaderRow = styled.div.attrs((props) => ({ 12 | style: props.styled, 13 | }))` 14 | position: sticky; 15 | z-index: 10; 16 | user-select: none; 17 | ` 18 | 19 | 20 | interface HeaderRowProps 21 | extends Pick, 'style'> { 22 | scrollWidth: number 23 | styled: CSSProperties 24 | scrollLeft: number 25 | gridProps: DataGridProps 26 | } 27 | 28 | function HeaderRow({ 29 | style = {}, 30 | styled: tempStyled = {}, 31 | scrollLeft, 32 | scrollWidth, 33 | gridProps: { 34 | rows, 35 | width, 36 | columns = [], 37 | defaultColumnWidth, 38 | estimatedColumnWidth, 39 | cacheRemoveCount, 40 | onHeaderCellRender = ({ headerCell }) => [headerCell], 41 | onHeaderDrop, 42 | onHeaderDragOver, 43 | onHeaderResizable, 44 | onSort, 45 | }, 46 | }: HeaderRowProps) { 47 | const fixedColumns = useMemo( 48 | () => columns.filter((ele) => ele.fixed), 49 | [columns] 50 | ) 51 | 52 | const leftFixedColumns = fixedColumns.filter((ele) => ele.fixed === 'left') 53 | const rightFixedColumns = fixedColumns.filter( 54 | (ele) => ele.fixed === 'right' 55 | ) 56 | 57 | const renderCell = () => { 58 | const result: Array = [] 59 | let left = 0 60 | columns.some((column, index) => { 61 | const columnWidth = column.width || defaultColumnWidth 62 | 63 | if ( 64 | left < scrollLeft - estimatedColumnWidth * cacheRemoveCount && 65 | column.fixed === undefined 66 | ) { 67 | left += columnWidth 68 | return false 69 | } 70 | 71 | if ( 72 | left + columnWidth > 73 | width + 74 | scrollLeft + 75 | estimatedColumnWidth * cacheRemoveCount && 76 | column.fixed === undefined 77 | ) { 78 | left += columnWidth 79 | return false 80 | } 81 | 82 | const cellStyled: CSSProperties = { 83 | left, 84 | width: columnWidth, 85 | position: column.fixed ? 'sticky' : undefined, 86 | zIndex: column.fixed ? 11 : undefined, 87 | } 88 | 89 | if (column.fixed === 'right') { 90 | cellStyled.left = undefined 91 | cellStyled.float = 'right' 92 | cellStyled.right = 93 | scrollWidth - left - (column.width || defaultColumnWidth) 94 | } 95 | 96 | const headerCell = onHeaderCellRender({ 97 | index, 98 | column, 99 | headerCell: ( 100 | 0 && 104 | leftFixedColumns[leftFixedColumns.length - 1] 105 | .name === column.name 106 | } 107 | isLastRightFixed={ 108 | rightFixedColumns.length > 0 && 109 | rightFixedColumns[0].name === column.name 110 | } 111 | styled={cellStyled} 112 | column={column} 113 | gridProps={{ 114 | rows, 115 | width, 116 | columns, 117 | defaultColumnWidth, 118 | estimatedColumnWidth, 119 | cacheRemoveCount, 120 | onHeaderCellRender, 121 | onHeaderResizable, 122 | onHeaderDrop, 123 | onHeaderDragOver, 124 | onSort, 125 | }} 126 | > 127 | {column.title} 128 | 129 | ), 130 | }) 131 | result.push(headerCell) 132 | left += columnWidth 133 | return false 134 | }) 135 | 136 | return result 137 | } 138 | 139 | return ( 140 | 145 | {renderCell()} 146 | 147 | ) 148 | } 149 | 150 | export default HeaderRow 151 | -------------------------------------------------------------------------------- /packages/rc-grid/src/Icon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | export interface IconProps 5 | extends Omit, 'children'> {} 6 | 7 | const IconStyle = styled.i.attrs(() => {})` 8 | width: 16px; 9 | height: 16px; 10 | ` 11 | 12 | export const SortDownIcon = ({ style }: IconProps) => ( 13 | 14 | 22 | 26 | 27 | 28 | ) 29 | 30 | export const SortUpIcon = ({ style }: IconProps) => ( 31 | 32 | 40 | 44 | 45 | 46 | ) 47 | 48 | export const useChevronRightIcon = () => ( 49 | 57 | 61 | 62 | ) 63 | 64 | export const useChevronDownIcon = () => ( 65 | 73 | 77 | 78 | ) 79 | -------------------------------------------------------------------------------- /packages/rc-grid/src/Row.tsx: -------------------------------------------------------------------------------- 1 | import React, { CSSProperties, ReactNode, useContext, useMemo } from 'react' 2 | import styled from 'styled-components' 3 | import { Row as RowType, DataGridProps } from './types' 4 | import Context from './Context' 5 | import Cell from './Cell' 6 | 7 | interface GridRowProps extends React.HTMLAttributes { 8 | styled: CSSProperties 9 | isSelect: boolean 10 | } 11 | 12 | const GridRow = styled.div.attrs((props) => ({ 13 | style: props.styled, 14 | }))` 15 | position: absolute; 16 | background-color: ${({ isSelect, theme}) => { 17 | if (isSelect) { 18 | return theme['grid-row-background-color-select']; 19 | } 20 | return theme['grid-row-background-color'] 21 | }}; 22 | :hover { 23 | > div { 24 | background-color: ${({ theme }) => theme['grid-row-background-color:hover']}; 25 | } 26 | } 27 | ` 28 | 29 | GridRow.defaultProps = { 30 | theme: { 31 | 'grid-row-background-color': 'inherit', 32 | 'grid-row-background-color-select': 'hsl(0deg 0% 95%)', 33 | 'grid-row-background-color:hover': 'hsl(0deg 0% 95%)' 34 | } 35 | } 36 | 37 | 38 | interface RowProps 39 | extends Pick, 'style'> { 40 | rows: readonly RowType[] 41 | rowIndex: number 42 | rowIndexCode: string 43 | width: number 44 | level: number 45 | scrollLeft: number 46 | scrollWidth: number 47 | styled: CSSProperties 48 | gridProps: DataGridProps 49 | } 50 | 51 | function Row({ 52 | rows, 53 | rowIndex, 54 | scrollLeft, 55 | scrollWidth, 56 | styled: tempStyled = {}, 57 | rowIndexCode, 58 | level, 59 | gridProps, 60 | }: RowProps) { 61 | const { 62 | defaultColumnWidth, 63 | columns = [], 64 | estimatedColumnWidth, 65 | cacheRemoveCount, 66 | width, 67 | onRowClick, 68 | onRowDoubleClick, 69 | } = gridProps 70 | const { cells, key, height } = rows[rowIndex] 71 | const { state, dispatch } = useContext(Context) 72 | const fixedColumns = useMemo(() => columns.filter((ele) => ele.fixed), [ 73 | columns, 74 | ]) 75 | 76 | const leftFixedColumns = fixedColumns.filter((ele) => ele.fixed === 'left') 77 | const rightFixedColumns = fixedColumns.filter( 78 | (ele) => ele.fixed === 'right' 79 | ) 80 | 81 | const renderCell = useMemo(() => { 82 | const result: Array = [] 83 | let left = 0 84 | const isMergeCell: Array = [] 85 | columns.some((column, index) => { 86 | let columnWidth = column.width || defaultColumnWidth 87 | if ( 88 | left < scrollLeft - estimatedColumnWidth * cacheRemoveCount && 89 | column.fixed === undefined 90 | ) { 91 | left += columnWidth 92 | return false 93 | } 94 | 95 | if ( 96 | left + columnWidth > 97 | width + 98 | scrollLeft + 99 | estimatedColumnWidth * cacheRemoveCount && 100 | column.fixed === undefined 101 | ) { 102 | left += columnWidth 103 | return false 104 | } 105 | 106 | if (isMergeCell.includes(index)) { 107 | return false 108 | } 109 | 110 | let isCellSpan = false 111 | const cell = cells.find((ele) => ele.name === column.name) 112 | 113 | const colSpan = cell?.colSpan || 0 114 | 115 | if (colSpan > 0) { 116 | for (let i = 0; i < colSpan; i += 1) { 117 | const columnIndex = index + i + 1 118 | isMergeCell.push(columnIndex) 119 | columnWidth += 120 | columns[columnIndex].width || defaultColumnWidth 121 | isCellSpan = true 122 | } 123 | } 124 | 125 | const rowSpan = cell?.rowSpan || 0 126 | let rowHeight = height || 35 127 | if (rowSpan > 0) { 128 | for (let i = 0; i < rowSpan; i += 1) { 129 | rowHeight += rows[rowIndex + i + 1].height || 35 130 | isCellSpan = true 131 | } 132 | } 133 | 134 | const txt = cell?.value || '' 135 | 136 | const isSelect = 137 | state.selectPosition?.x === index && 138 | state.selectPosition?.y === `${rowIndexCode}-${rowIndex}` 139 | 140 | let zIndex 141 | 142 | if (isCellSpan) { 143 | zIndex = 1 144 | } 145 | if (column.fixed) { 146 | zIndex = 2 147 | } 148 | 149 | const cellStyled: CSSProperties = { 150 | left, 151 | zIndex, 152 | width: columnWidth, 153 | height: rowHeight, 154 | lineHeight: `${rowHeight}px`, 155 | } 156 | 157 | if (column.fixed === 'right') { 158 | cellStyled.left = undefined 159 | cellStyled.float = 'right' 160 | cellStyled.right = 161 | scrollWidth - left - (column.width || defaultColumnWidth) 162 | } 163 | 164 | result.push( 165 | 166 | key={`${key}-${column.name}`} 167 | column={column} 168 | level={level} 169 | style={cell?.style} 170 | styled={{ 171 | ...cellStyled, 172 | position: column.fixed ? 'sticky' : undefined, 173 | }} 174 | isLastFeftFixed={ 175 | leftFixedColumns.length > 0 && 176 | leftFixedColumns[leftFixedColumns.length - 1].name === 177 | column.name 178 | } 179 | isLastRightFixed={ 180 | rightFixedColumns.length > 0 && 181 | rightFixedColumns[0].name === column.name 182 | } 183 | isSelect={isSelect} 184 | onClick={() => { 185 | if (column.isSelect?.(cell) !== false) { 186 | dispatch({ 187 | type: 'setSelectPosition', 188 | payload: { 189 | x: index, 190 | y: `${rowIndexCode}-${rowIndex}`, 191 | }, 192 | }) 193 | } 194 | }} 195 | row={rows[rowIndex]} 196 | value={txt} 197 | girdProps={gridProps} 198 | onFocus={() => { 199 | // if (column.isSelect?.(cell) !== false) { 200 | // dispatch({ 201 | // type: 'setSelectPosition', 202 | // payload: { 203 | // x: index, 204 | // y: `${rowIndexCode}-${rowIndex}`, 205 | // }, 206 | // }) 207 | // } 208 | }} 209 | /> 210 | ) 211 | 212 | left += columnWidth 213 | 214 | return false 215 | }) 216 | return result 217 | }, [ 218 | columns, 219 | estimatedColumnWidth, 220 | cacheRemoveCount, 221 | scrollLeft, 222 | state.selectPosition, 223 | ]) 224 | 225 | return ( 226 | { 231 | onRowClick?.(rows[rowIndex]) 232 | }} 233 | onDoubleClick={() => { 234 | onRowDoubleClick?.(rows[rowIndex]) 235 | }} 236 | > 237 | {renderCell} 238 | 239 | ) 240 | } 241 | 242 | export default Row 243 | -------------------------------------------------------------------------------- /packages/rc-grid/src/index.ts: -------------------------------------------------------------------------------- 1 | import DataGrid from './DataGrid' 2 | import AutoSize from './AutoSize' 3 | import { dark } from './utils/theme' 4 | 5 | export type { Row, Column, Cell, EditorProps, DataGridProps, GridHandle } from './types' 6 | export { AutoSize } 7 | 8 | const Theme = { 9 | dark 10 | } 11 | export { Theme } 12 | 13 | export default DataGrid 14 | -------------------------------------------------------------------------------- /packages/rc-grid/src/plugins/UniversalToolbar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | 4 | const InputContainer = styled.div` 5 | position: absolute; 6 | z-index: 20; 7 | width: 500px; 8 | height: 28px; 9 | left: calc(50% - (500px / 2)); 10 | background-color: #ddd; 11 | padding: 2px; 12 | box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 13 | 0 9px 28px 8px #0000000d; 14 | > input { 15 | width: 100%; 16 | height: 100%; 17 | box-sizing: border-box; 18 | border-radius: 2px; 19 | outline: none; 20 | } 21 | ` 22 | 23 | interface UniversalToolbarProps { 24 | value: string 25 | onChange: (value: string) => void 26 | onBlur: () => void 27 | } 28 | 29 | // 表格通用的工具栏 30 | function UniversalToolbar({ 31 | onChange, 32 | onBlur, 33 | value: tempValue, 34 | }: UniversalToolbarProps) { 35 | const [value, setValue] = useState(tempValue) 36 | return ( 37 | 40 | { 45 | setValue(target.value) 46 | }} 47 | onBlur={() => { 48 | onBlur() 49 | onChange?.(`${value}`) 50 | }} 51 | onKeyDown={(e) => { 52 | if (e.key === 'Enter') { 53 | onChange?.(`${value}`) 54 | } 55 | if (e.key === 'Escape') { 56 | onBlur() 57 | } 58 | }} 59 | /> 60 | 61 | ) 62 | } 63 | 64 | export default UniversalToolbar 65 | -------------------------------------------------------------------------------- /packages/rc-grid/src/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComponentType, 3 | CSSProperties, 4 | HTMLAttributes, 5 | ReactNode, 6 | DragEvent, 7 | Key, 8 | MutableRefObject, 9 | } from 'react' 10 | 11 | type SharedDivProps = Pick< 12 | HTMLAttributes, 13 | 'className' | 'style' 14 | > 15 | 16 | type SortDirection = 'ASC' | 'DESC' 17 | 18 | export interface SortColumn { 19 | readonly columnKey: string 20 | readonly direction: SortDirection 21 | } 22 | 23 | type SelectParam = { 24 | row: Row 25 | mode: 'single' | 'multiple' 26 | selected: boolean 27 | onSelected: (row: Row, mode: 'single' | 'multiple') => void 28 | } 29 | 30 | export interface GridHandle { 31 | /** HTML 元素 */ 32 | element: HTMLDivElement | null; 33 | /** 滚动到指定的列 */ 34 | scrollToColumn: (colIdx: number) => void; 35 | /** 滚动到指定的行 */ 36 | scrollToRow: (rowIdx: number) => void; 37 | /** 选中 */ 38 | selectCell: (position: { 39 | rowKey: string 40 | colName: string 41 | }, enableEditor?: boolean | null) => void 42 | } 43 | 44 | export interface DataGridProps extends SharedDivProps { 45 | /** 表格的行数据信息 */ 46 | rows: readonly Row[] 47 | /** 列的信息 */ 48 | columns: readonly Column[] 49 | /** 表格的高度信息 */ 50 | height?: number 51 | /** 表格的宽度信息 */ 52 | width?: number 53 | /** 表格 header 的默认高度 */ 54 | headerRowHeight?: number 55 | /** 预估表格的行的平均值 */ 56 | estimatedRowHeight?: number 57 | /** 预估表格的列的平均值 */ 58 | estimatedColumnWidth?: number 59 | /** 缓存要移除的条目数量 (PS: 值越大,滚动起来越真实不会白屏幕,但是会导致性能变差) */ 60 | cacheRemoveCount?: number 61 | /** 默认列的宽度信息 */ 62 | defaultColumnWidth?: number 63 | /** 表格的方法 */ 64 | grid?: MutableRefObject 65 | /** 选中的配置信息 */ 66 | select?: { 67 | /** 选择模式 */ 68 | mode?: 'single' | 'multiple' 69 | /** 选中组件 */ 70 | component: (selectParam: SelectParam) => ReactNode 71 | /** 渲染选中组件信息 */ 72 | headerComponent?: (mode: 'single' | 'multiple') => ReactNode 73 | } 74 | /** 默认选中的数据信息 */ 75 | selectedRows?: Key[] 76 | /** 展开的配置信息 */ 77 | expandable?: { 78 | /** 展示树形数据时,每层缩进的宽度,以 rem 为单位 */ 79 | indentSize?: number 80 | /** 指定树形结构的列名 */ 81 | childrenColumnName?: string 82 | /** 是否显示可展开按钮 */ 83 | isExpandable?: (row: Row) => boolean 84 | /** 渲染的实际内容 */ 85 | expandedRowRender?: (row: Row, style: CSSProperties) => ReactNode 86 | } 87 | /** 底部栏的信息 */ 88 | footRows?: Row 89 | /** 改变选中的数据触发的事件 */ 90 | onChangeSelectedRows?: (keys: Key[]) => void 91 | /** 用户编辑触发的数据 */ 92 | onEditorChangeSave?: (change: EditorChange) => void 93 | /** 渲染表格头部的单元格 */ 94 | onHeaderCellRender?: (param: HeaderCellRenderParam) => ReactNode[] 95 | /** 渲染表格的头部的行信息 */ 96 | onHeaderRowRender?: (node: JSX.Element) => ReactNode 97 | /** 表格列头部改变宽度触发的事件 */ 98 | onHeaderResizable?: (column: Column[]) => void 99 | /** 数据空的时候渲染对应的数据信息 */ 100 | onEmptyRowsRenderer?: () => ReactNode 101 | /** 表格执行排序的时候触发的方法 */ 102 | onSort?: (sortColumn: SortColumn[]) => void 103 | /** 拖拽用户表头触发的事件 */ 104 | onHeaderDrop?: (source: Column, target: Column) => void 105 | /** 允许当期表头进行放置 */ 106 | onHeaderDragOver?: (event: DragEvent) => boolean 107 | /** 表格行的点击事件 */ 108 | onRowClick?: (row: Row) => void 109 | /** 表格行的双击事件 */ 110 | onRowDoubleClick?: (row: Row) => void 111 | /** 树形节点的子节点信息 */ 112 | onChildrenRows?: (row: Row) => readonly Row[] 113 | } 114 | 115 | export interface Row { 116 | /** 表格的唯一 key */ 117 | key: string 118 | /** 表格行的高度 */ 119 | height: number 120 | /** 表格的单元格信息 */ 121 | cells: Cell[] 122 | /** 任何 JSON 数据 */ 123 | object?: T 124 | } 125 | 126 | export interface CellStyle 127 | extends Omit< 128 | CSSProperties, 129 | 'left' | 'zIndex' | 'width' | 'height' | 'position' | 'display' 130 | > {} 131 | 132 | export interface Cell { 133 | /** 对应 Column 的 name 属性 */ 134 | name: string 135 | /** 实际显示的值信息 */ 136 | value: string 137 | /** 合并列的数量 */ 138 | colSpan?: number 139 | /** 合并行的数量 */ 140 | rowSpan?: number 141 | /** css 样式 */ 142 | style?: CellStyle 143 | } 144 | 145 | export interface EditorChange { 146 | /** 当前行的数据 */ 147 | row: Row 148 | /** 改变的字段信息 */ 149 | changeValue: R 150 | } 151 | 152 | export type EditorValue = number | string | boolean 153 | 154 | export interface EditorProps { 155 | style: CSSProperties 156 | /** 当前编辑框的值 */ 157 | value: EditorValue 158 | /** 当内容编辑完成后触发的事件 */ 159 | onEditCompleted: (value: EditorValue) => void 160 | } 161 | 162 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 163 | export interface Column { 164 | /** 列数据在数据项中对应的路径 */ 165 | readonly name: string 166 | /** 列头显示文字 */ 167 | readonly title: ReactNode 168 | /** 列宽度 */ 169 | width?: number 170 | /** 对其方式 */ 171 | readonly align?: 'left' | 'right' | 'center' 172 | /** 固定列 */ 173 | readonly fixed?: 'left' | 'right' 174 | /** 当前列是否只读 */ 175 | readonly readonly?: boolean | ((row: Row) => boolean) 176 | /** 是否支持排序 */ 177 | readonly sort?: boolean 178 | /** 列是否可以拖拽改变大小 */ 179 | readonly resizable?: boolean 180 | /** 表格的编辑按钮 */ 181 | readonly editor?: ComponentType | null 182 | /** 是否允许选中 */ 183 | readonly isSelect?: (cell: Cell) => boolean 184 | /** 渲染表格的单元格信息 */ 185 | readonly render?: (text: EditorValue, row: Row) => ReactNode 186 | } 187 | 188 | export interface HeaderCellRenderParam { 189 | column: Column 190 | index: number 191 | headerCell: JSX.Element 192 | } 193 | -------------------------------------------------------------------------------- /packages/rc-grid/src/utils/browser.ts: -------------------------------------------------------------------------------- 1 | // from https://github.com/ag-grid/ag-grid/blob/latest/community-modules/core/src/ts/utils/browser.ts 2 | 3 | let browserScrollbarWidth: number 4 | let invisibleScrollbar: boolean 5 | 6 | function initScrollbarWidthAndVisibility(): void { 7 | const { body } = document 8 | const div = document.createElement('div') 9 | div.style.width = '100px' 10 | div.style.height = '100px' 11 | div.style.opacity = '0' 12 | div.style.overflow = 'scroll' 13 | ;(div.style as any).msOverflowStyle = 'scrollbar' // needed for WinJS apps 14 | div.style.position = 'absolute' 15 | 16 | body.appendChild(div) 17 | 18 | let width: number = div.offsetWidth - div.clientWidth 19 | 20 | // if width is 0 and client width is 0, means the DOM isn't ready 21 | if (width === 0 && div.clientWidth === 0) { 22 | width = undefined 23 | } 24 | 25 | // remove div 26 | if (div.parentNode) { 27 | div.parentNode.removeChild(div) 28 | } 29 | 30 | browserScrollbarWidth = width 31 | 32 | invisibleScrollbar = width === 0 || width === undefined 33 | } 34 | 35 | export function getScrollbarWidth(): number { 36 | if (browserScrollbarWidth === undefined) { 37 | initScrollbarWidthAndVisibility() 38 | } 39 | return browserScrollbarWidth 40 | } 41 | 42 | export function isInvisibleScrollbar(): boolean { 43 | if (invisibleScrollbar === undefined) { 44 | initScrollbarWidthAndVisibility() 45 | } 46 | return invisibleScrollbar 47 | } 48 | 49 | let clipboard: HTMLInputElement = null 50 | 51 | /** 52 | * 写入剪贴板内容 53 | */ 54 | export const writeClipboardText = (text: string) => { 55 | if (clipboard === null) { 56 | clipboard = document.createElement("input"); 57 | clipboard.style.cssText = 'position: absolute; top: -9999px;'; 58 | document.body.appendChild(clipboard); 59 | } 60 | clipboard.value = text; 61 | clipboard.select(); 62 | document.execCommand('copy'); 63 | } -------------------------------------------------------------------------------- /packages/rc-grid/src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | interface Option { 2 | // 最大等待时间 3 | maxTime?: number 4 | } 5 | 6 | const debounce = (fn: Function, time: number, option?: Option) => { 7 | // 最后一次调用的参数 8 | let lastArgs: any; 9 | 10 | let lastInvokeTime: number; 11 | let invokeSelf: any; 12 | 13 | // 调用的返回信息 14 | let invokeResult: any; 15 | 16 | let lastCallTime: number = 0 17 | 18 | let timeout: any; 19 | 20 | const invokeFunc = () => { 21 | lastCallTime = Date.now() 22 | const result = fn.apply(invokeSelf, lastArgs) 23 | invokeResult = result 24 | } 25 | 26 | const shouldInvoke = (currentTime: number) => { 27 | const lastInvokeDiff = currentTime - lastInvokeTime 28 | lastInvokeTime = currentTime 29 | 30 | if (timeout) { 31 | clearTimeout(timeout) 32 | } 33 | 34 | if (lastInvokeDiff >= time) { 35 | return true 36 | } 37 | 38 | if (option?.maxTime && currentTime - lastCallTime > option.maxTime ) { 39 | return true 40 | } 41 | 42 | timeout = setTimeout(() => { 43 | invokeFunc() 44 | }, time - lastInvokeDiff) 45 | 46 | return false 47 | } 48 | 49 | function debounced(...args: any[]) { 50 | invokeSelf = this 51 | lastArgs = args 52 | const isInvoking = shouldInvoke(Date.now()) 53 | if (isInvoking) { 54 | invokeFunc() 55 | } 56 | return invokeResult 57 | } 58 | 59 | return debounced 60 | } 61 | 62 | export default debounce -------------------------------------------------------------------------------- /packages/rc-grid/src/utils/theme.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Theme { 3 | 'grid-border': string 4 | 'grid-text-color': string 5 | 'grid-row-background-color': string 6 | 'grid-row-background-color-select': string 7 | 'grid-row-background-color:hover': string 8 | 'grid-row-cell-border-right': string 9 | 'grid-row-cell-border-bottom': string 10 | 'grid-row-cell-background-color-readonly': string 11 | 'grid-row-cell-select': string 12 | 'grid-header-cell-border-right': string 13 | 'grid-header-cell-border-bottom': string 14 | 'grid-header-cell-background-color': string 15 | } 16 | 17 | export const dark: Theme = { 18 | 'grid-border': '1px solid hsl(0deg 0% 40%)', 19 | 'grid-text-color': '#fff', 20 | 'grid-row-background-color': '#000', 21 | 'grid-row-background-color-select': 'hsl(0deg 0% 30%)', 22 | 'grid-row-background-color:hover': 'hsl(0deg 0% 30%)', 23 | 'grid-row-cell-border-right': '1px solid hsl(0deg 0% 40%)', 24 | 'grid-row-cell-border-bottom': '1px solid hsl(0deg 0% 40%)', 25 | 'grid-row-cell-background-color-readonly': 'hsl(0deg 0% 20%)', 26 | 'grid-row-cell-select': 'inset 0 0 0 1px #66afe9', 27 | 'grid-header-cell-border-right': '1px solid hsl(0deg 0% 40%)', 28 | 'grid-header-cell-border-bottom': '1px solid hsl(0deg 0% 40%)', 29 | 'grid-header-cell-background-color': 'hsl(0deg 0% 15%)' 30 | } -------------------------------------------------------------------------------- /packages/rc-grid/stories/big-data.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 5 | import { onHeaderDrop } from './utils' 6 | 7 | const rows: Array> = [] 8 | const columns: Array> = [] 9 | 10 | columns.push({ 11 | name: `0`, 12 | title: `字段 - 0`, 13 | fixed: 'left', 14 | }) 15 | 16 | for (let i = 2; i < 1000; i += 1) { 17 | columns.push({ 18 | name: `${i}`, 19 | title: `字段 - ${i}`, 20 | }) 21 | } 22 | 23 | columns.push({ 24 | name: `1`, 25 | title: `字段 - 1`, 26 | fixed: 'right', 27 | }) 28 | 29 | for (let i = 0; i < 5000; i += 1) { 30 | const cells: Array = [] 31 | 32 | for (let y = 0; y < 1000; y += 1) { 33 | if (i === 3 && y === 2) { 34 | cells.push({ 35 | name: `${y}`, 36 | value: `${i} - ${y}`, 37 | style: {}, 38 | }) 39 | } else if (i === 8 && y === 2) { 40 | cells.push({ 41 | name: `${y}`, 42 | value: `${i} - ${y}`, 43 | style: {}, 44 | }) 45 | } else { 46 | cells.push({ 47 | name: `${y}`, 48 | value: `${i} - ${y}`, 49 | }) 50 | } 51 | } 52 | rows.push({ 53 | height: 35, 54 | key: `${i}`, 55 | cells, 56 | }) 57 | } 58 | 59 | const RowDataGrid = () => { 60 | const [cols, setCols] = useState[]>(columns) 61 | return ( 62 | 63 | {(width, height) => ( 64 | 65 | rows={rows} 66 | columns={cols} 67 | width={width} 68 | height={height} 69 | onHeaderDrop={(source, target) => { 70 | setCols(onHeaderDrop(cols, source, target)) 71 | }} 72 | /> 73 | )} 74 | 75 | ) 76 | } 77 | export default { 78 | component: RowDataGrid, 79 | title: 'Demos', 80 | } as Meta 81 | 82 | export const BigData: React.VFC<{}> = () => 83 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/cell-editor.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import styled from 'styled-components' 5 | import DataGrid, { Row, Column, Cell, EditorProps, AutoSize } from '../src' 6 | import { onHeaderDrop } from './utils' 7 | 8 | const rows: Array> = [] 9 | 10 | const Input = ({ style, value: tempValue, onEditCompleted }: EditorProps) => { 11 | const [value, setValue] = useState(tempValue) 12 | return ( 13 | { 19 | onEditCompleted(value) 20 | }} 21 | onChange={(e) => { 22 | const text = e.target.value 23 | setValue(text) 24 | }} 25 | /> 26 | ) 27 | } 28 | 29 | const columns: Array> = [ 30 | { 31 | name: `0`, 32 | title: `姓名`, 33 | editor: Input, 34 | width: 120, 35 | readonly: true, 36 | }, 37 | { 38 | name: `1`, 39 | title: `年龄`, 40 | width: 120, 41 | editor: Input, 42 | readonly: (row) => { 43 | if (((row.key as unknown) as number) % 2 === 0) { 44 | return true 45 | } 46 | return false 47 | }, 48 | }, 49 | { 50 | name: `2`, 51 | title: `身份证号`, 52 | width: 120, 53 | editor: Input, 54 | }, 55 | { 56 | name: `3`, 57 | title: `家庭地址`, 58 | width: 120, 59 | editor: Input, 60 | }, 61 | { 62 | name: `4`, 63 | title: `家庭电话号码`, 64 | editor: Input, 65 | width: 120, 66 | }, 67 | { 68 | name: `5`, 69 | title: `家庭人员数量`, 70 | editor: Input, 71 | width: 120, 72 | }, 73 | { 74 | name: `6`, 75 | title: `公司名称`, 76 | editor: Input, 77 | }, 78 | { 79 | name: `7`, 80 | title: `公司地址`, 81 | editor: Input, 82 | }, 83 | { 84 | name: `8`, 85 | title: `公司电话号码`, 86 | editor: Input, 87 | }, 88 | ] 89 | 90 | const GridHeaderCell = styled.div` 91 | display: inline-block; 92 | position: absolute; 93 | border-right: 1px solid #ddd; 94 | border-bottom: 1px solid #ddd; 95 | box-sizing: border-box; 96 | background-color: hsl(0deg 0% 97.5%); 97 | /** 优化 webkit 中的渲染效率 */ 98 | content-visibility: auto; 99 | padding: 0px 8px; 100 | white-space: nowrap; 101 | text-overflow: ellipsis; 102 | ` 103 | 104 | for (let i = 0; i < 5000; i += 1) { 105 | const cells: Array = [] 106 | 107 | const object: any = {} 108 | for (let y = 0; y < columns.length; y += 1) { 109 | object[`${y}`] = `${i} - ${y}` 110 | if (i === 3 && y === 2) { 111 | cells.push({ 112 | name: `${y}`, 113 | value: `${i} - ${y}`, 114 | style: {}, 115 | }) 116 | } else if (i === 8 && y === 2) { 117 | cells.push({ 118 | name: `${y}`, 119 | value: `${i} - ${y}`, 120 | style: {}, 121 | }) 122 | } else { 123 | cells.push({ 124 | name: `${y}`, 125 | value: `${i} - ${y}`, 126 | }) 127 | } 128 | } 129 | rows.push({ 130 | height: 35, 131 | key: `${i}`, 132 | object, 133 | cells, 134 | }) 135 | } 136 | 137 | const RowDataGrid = () => { 138 | const [cols, setCols] = useState(columns) 139 | return ( 140 | 141 | {(width, height) => ( 142 | 143 | rows={rows} 144 | width={width} 145 | height={height} 146 | columns={cols} 147 | onHeaderRowRender={(node) => { 148 | const { styled: tempStyled, ...restProps } = node.props 149 | return React.cloneElement(node, { 150 | ...restProps, 151 | styled: { 152 | ...tempStyled, 153 | top: 35, 154 | }, 155 | key: node.key, 156 | }) 157 | }} 158 | onEditorChangeSave={(change) => { 159 | console.log(change) 160 | }} 161 | onHeaderDrop={(source, target) => { 162 | setCols(onHeaderDrop(cols, source, target)) 163 | }} 164 | onHeaderCellRender={({ headerCell, index }) => { 165 | const { 166 | styled: tempStyled, 167 | ...restProps 168 | } = headerCell.props 169 | if (index === 0) { 170 | return [ 171 | 185 | 人员资料 186 | , 187 | headerCell, 188 | ] 189 | } 190 | 191 | if (index > 5) { 192 | return [ 193 | React.cloneElement(headerCell, { 194 | ...restProps, 195 | styled: { 196 | ...tempStyled, 197 | top: -35, 198 | height: 35 * 2, 199 | }, 200 | }), 201 | ] 202 | } 203 | return [ 204 | React.cloneElement(headerCell, { 205 | ...restProps, 206 | styled: { 207 | ...tempStyled, 208 | }, 209 | }), 210 | ] 211 | }} 212 | /> 213 | )} 214 | 215 | ) 216 | } 217 | 218 | export default { 219 | component: RowDataGrid, 220 | title: 'Demos', 221 | } as Meta 222 | 223 | export const CellEditor: React.VFC<{}> = () => 224 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/cells-merge.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 5 | 6 | const rows: Array> = [] 7 | const columns: Array> = [] 8 | 9 | columns.push({ 10 | name: `0`, 11 | title: `字段 - 0`, 12 | fixed: 'left', 13 | }) 14 | 15 | for (let i = 2; i < 1000; i += 1) { 16 | columns.push({ 17 | name: `${i}`, 18 | title: `字段 - ${i}`, 19 | }) 20 | } 21 | 22 | columns.push({ 23 | name: `1`, 24 | title: `字段 - 1`, 25 | fixed: 'right', 26 | }) 27 | 28 | for (let i = 0; i < 5000; i += 1) { 29 | const cells: Array = [] 30 | 31 | for (let y = 0; y < 1000; y += 1) { 32 | if (i === 3 && y === 2) { 33 | cells.push({ 34 | name: `${y}`, 35 | value: `${i} - ${y}`, 36 | rowSpan: 2, 37 | colSpan: 2, 38 | }) 39 | } else if (i === 8 && y === 2) { 40 | cells.push({ 41 | name: `${y}`, 42 | value: `${i} - ${y}`, 43 | rowSpan: 2, 44 | }) 45 | } else { 46 | cells.push({ 47 | name: `${y}`, 48 | value: `${i} - ${y}`, 49 | }) 50 | } 51 | } 52 | rows.push({ 53 | height: 35, 54 | key: `${i}`, 55 | cells, 56 | }) 57 | } 58 | 59 | const RowDataGrid = () => ( 60 | 61 | {(width, height) => ( 62 | 63 | width={width} 64 | height={height} 65 | rows={rows} 66 | columns={columns} 67 | /> 68 | )} 69 | 70 | ) 71 | 72 | export default { 73 | component: RowDataGrid, 74 | title: 'Demos', 75 | } as Meta 76 | 77 | 78 | export const CellMerge: React.VFC<{}> = () => 79 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/disable-select-cell.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import styled from 'styled-components' 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | 7 | const rows: Array> = [] 8 | const columns: Array> = [ 9 | { 10 | name: `0`, 11 | title: `姓名`, 12 | width: 120, 13 | isSelect: () => false, 14 | }, 15 | { 16 | name: `1`, 17 | width: 120, 18 | title: `年龄`, 19 | }, 20 | { 21 | name: `2`, 22 | width: 120, 23 | title: `身份证号`, 24 | }, 25 | { 26 | name: `3`, 27 | width: 120, 28 | title: `家庭地址`, 29 | }, 30 | { 31 | name: `4`, 32 | width: 120, 33 | title: `家庭电话号码`, 34 | }, 35 | { 36 | name: `5`, 37 | width: 120, 38 | title: `家庭人员数量`, 39 | }, 40 | { 41 | name: `6`, 42 | title: `公司名称`, 43 | }, 44 | { 45 | name: `7`, 46 | title: `公司地址`, 47 | }, 48 | { 49 | name: `8`, 50 | title: `公司电话号码`, 51 | }, 52 | ] 53 | 54 | const GridHeaderCell = styled.div` 55 | display: inline-block; 56 | position: absolute; 57 | border-right: 1px solid #ddd; 58 | border-bottom: 1px solid #ddd; 59 | box-sizing: border-box; 60 | background-color: hsl(0deg 0% 97.5%); 61 | /** 优化 webkit 中的渲染效率 */ 62 | content-visibility: auto; 63 | padding: 0px 8px; 64 | white-space: nowrap; 65 | text-overflow: ellipsis; 66 | ` 67 | 68 | for (let i = 0; i < 5000; i += 1) { 69 | const cells: Array = [] 70 | 71 | for (let y = 0; y < columns.length; y += 1) { 72 | if (y === 0) { 73 | cells.push({ 74 | name: `${y}`, 75 | value: `${i} - ${y}`, 76 | }) 77 | } 78 | if (i === 3 && y === 2) { 79 | cells.push({ 80 | name: `${y}`, 81 | value: `${i} - ${y}`, 82 | style: {}, 83 | }) 84 | } else if (i === 8 && y === 2) { 85 | cells.push({ 86 | name: `${y}`, 87 | value: `${i} - ${y}`, 88 | style: {}, 89 | }) 90 | } else { 91 | cells.push({ 92 | name: `${y}`, 93 | value: `${i} - ${y}`, 94 | }) 95 | } 96 | } 97 | rows.push({ 98 | height: 35, 99 | key: `${i}`, 100 | cells, 101 | }) 102 | } 103 | 104 | const RowDataGrid = () => ( 105 | 106 | {(width, height) => ( 107 | 108 | rows={rows} 109 | width={width} 110 | height={height} 111 | columns={columns} 112 | onHeaderRowRender={(node) => { 113 | const { styled: tempStyled, ...restProps } = node.props 114 | return React.cloneElement(node, { 115 | ...restProps, 116 | styled: { 117 | ...tempStyled, 118 | top: 35, 119 | }, 120 | key: node.key, 121 | }) 122 | }} 123 | onHeaderCellRender={({ headerCell, index }) => { 124 | const { 125 | styled: tempStyled, 126 | ...restProps 127 | } = headerCell.props 128 | if (index === 0) { 129 | return [ 130 | 144 | 人员资料 145 | , 146 | headerCell, 147 | ] 148 | } 149 | 150 | if (index > 5) { 151 | return [ 152 | React.cloneElement(headerCell, { 153 | ...restProps, 154 | styled: { 155 | ...tempStyled, 156 | top: -35, 157 | height: 35 * 2, 158 | }, 159 | }), 160 | ] 161 | } 162 | return [ 163 | React.cloneElement(headerCell, { 164 | ...restProps, 165 | styled: { 166 | ...tempStyled, 167 | }, 168 | }), 169 | ] 170 | }} 171 | /> 172 | )} 173 | 174 | ) 175 | 176 | export default { 177 | component: RowDataGrid, 178 | title: 'Demos', 179 | } as Meta 180 | 181 | export const DisableSelectCell: React.VFC<{}> = () => 182 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/empty-rows.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import DataGrid, { Row, Column, AutoSize } from '../src' 5 | 6 | const rows: Array> = [] 7 | const columns: Array> = [] 8 | 9 | columns.push({ 10 | name: `0`, 11 | title: `字段 - 0`, 12 | fixed: 'left', 13 | }) 14 | 15 | for (let i = 2; i < 1000; i += 1) { 16 | columns.push({ 17 | name: `${i}`, 18 | title: `字段 - ${i}`, 19 | }) 20 | } 21 | 22 | columns.push({ 23 | name: `1`, 24 | title: `字段 - 1`, 25 | fixed: 'right', 26 | }) 27 | 28 | const RowDataGrid = () => ( 29 | 30 | {(width, height) => ( 31 | 32 | rows={rows} 33 | width={width} 34 | height={height} 35 | columns={columns} 36 | onEmptyRowsRenderer={() => ( 37 |
46 | 无任何数据 47 |
48 | )} 49 | /> 50 | )} 51 |
52 | ) 53 | 54 | export default { 55 | component: RowDataGrid, 56 | title: 'Demos', 57 | } as Meta 58 | 59 | export const EmptyRows: React.VFC<{}> = () => 60 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/expandable-row.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 5 | 6 | const rows: Array> = [] 7 | const tempColumns: Array> = [] 8 | 9 | tempColumns.push({ 10 | name: `0`, 11 | title: `字段 - 0`, 12 | fixed: 'left', 13 | }) 14 | 15 | for (let i = 2; i < 20; i += 1) { 16 | tempColumns.push({ 17 | name: `${i}`, 18 | title: `字段 - ${i}`, 19 | }) 20 | } 21 | 22 | tempColumns.push({ 23 | name: `1`, 24 | title: `字段 - 1`, 25 | fixed: 'right', 26 | }) 27 | 28 | for (let i = 0; i < 500; i += 1) { 29 | const cells: Array = [] 30 | 31 | const object: any = {} 32 | for (let y = 0; y < tempColumns.length; y += 1) { 33 | object[`${y}`] = `${i} - ${y}` 34 | if (i === 3 && y === 2) { 35 | cells.push({ 36 | name: `${y}`, 37 | value: `${i} - ${y}`, 38 | style: {}, 39 | }) 40 | } else if (i === 8 && y === 2) { 41 | cells.push({ 42 | name: `${y}`, 43 | value: `${i} - ${y}`, 44 | style: {}, 45 | }) 46 | } else { 47 | cells.push({ 48 | name: `${y}`, 49 | value: `${i} - ${y}`, 50 | }) 51 | } 52 | } 53 | rows.push({ 54 | height: 35, 55 | key: `${i}`, 56 | object, 57 | cells, 58 | }) 59 | } 60 | 61 | const RowDataGrid = () => ( 62 | 63 | {(width, height) => ( 64 | 65 | rows={rows} 66 | width={width} 67 | height={height} 68 | columns={tempColumns} 69 | expandable={{ 70 | expandedRowRender: (row, style) => ( 71 |
72 | {' '} 73 | 这是一个展开的内容信息 {JSON.stringify(row)}{' '} 74 |
75 | ), 76 | }} 77 | /> 78 | )} 79 |
80 | ) 81 | 82 | export default { 83 | component: RowDataGrid, 84 | title: 'Demos', 85 | } as Meta 86 | 87 | export const expandable: React.VFC<{}> = () => 88 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/expandable-tree-row.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 5 | 6 | const rows: Array> = [] 7 | const tempColumns: Array> = [] 8 | 9 | tempColumns.push({ 10 | name: `0`, 11 | title: `字段 - 0`, 12 | fixed: 'left', 13 | }) 14 | 15 | for (let i = 2; i < 20; i += 1) { 16 | tempColumns.push({ 17 | name: `${i}`, 18 | title: `字段 - ${i}`, 19 | }) 20 | } 21 | 22 | tempColumns.push({ 23 | name: `1`, 24 | title: `字段 - 1`, 25 | fixed: 'right', 26 | }) 27 | 28 | for (let i = 0; i < 500; i += 1) { 29 | const cells: Array = [] 30 | 31 | const object: any = {} 32 | for (let y = 0; y < tempColumns.length; y += 1) { 33 | object[`${y}`] = `${i} - ${y}` 34 | if (i === 3 && y === 2) { 35 | cells.push({ 36 | name: `${y}`, 37 | value: `${i} - ${y}`, 38 | style: {}, 39 | }) 40 | } else if (i === 8 && y === 2) { 41 | cells.push({ 42 | name: `${y}`, 43 | value: `${i} - ${y}`, 44 | style: {}, 45 | }) 46 | } else { 47 | cells.push({ 48 | name: `${y}`, 49 | value: `${i} - ${y}`, 50 | }) 51 | } 52 | } 53 | rows.push({ 54 | height: 35, 55 | key: `${i}`, 56 | object, 57 | cells, 58 | }) 59 | } 60 | 61 | const RowDataGrid = () => ( 62 | 63 | {(width, height) => ( 64 | 65 | rows={rows} 66 | width={width} 67 | height={height} 68 | columns={tempColumns} 69 | expandable={{ 70 | childrenColumnName: '2', 71 | expandedRowRender: (row, style) => ( 72 |
73 | 这是一个展开的内容信息 {JSON.stringify(row)}{' '} 74 |
75 | ), 76 | }} 77 | onChildrenRows={(row) => { 78 | const tempRow = [] 79 | for (let i = 500; i < 510; i += 1) { 80 | const cells: Array = [] 81 | const object: any = {} 82 | for (let y = 0; y < tempColumns.length; y += 1) { 83 | object[`${y}`] = `${i} - ${y}` 84 | if (i === 3 && y === 2) { 85 | cells.push({ 86 | name: `${y}`, 87 | value: `${i} - ${y}`, 88 | style: {}, 89 | }) 90 | } else if (i === 8 && y === 2) { 91 | cells.push({ 92 | name: `${y}`, 93 | value: `${i} - ${y}`, 94 | style: {}, 95 | }) 96 | } else { 97 | cells.push({ 98 | name: `${y}`, 99 | value: `${i} - ${y}`, 100 | }) 101 | } 102 | } 103 | tempRow.push({ 104 | height: 35, 105 | key: `${row.key}-${i}`, 106 | object, 107 | cells, 108 | }) 109 | } 110 | return tempRow 111 | }} 112 | /> 113 | )} 114 |
115 | ) 116 | 117 | export default { 118 | component: RowDataGrid, 119 | title: 'Demos', 120 | } as Meta 121 | 122 | export const TreeRow: React.VFC<{}> = () => 123 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/filter-data.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | 7 | const rows: Array> = [] 8 | const tempColumns: Array> = [] 9 | 10 | tempColumns.push({ 11 | name: `0`, 12 | title: `字段 - 0`, 13 | fixed: 'left', 14 | }) 15 | 16 | for (let i = 2; i < 20; i += 1) { 17 | tempColumns.push({ 18 | name: `${i}`, 19 | title: `字段 - ${i}`, 20 | }) 21 | } 22 | 23 | tempColumns.push({ 24 | name: `1`, 25 | title: `字段 - 1`, 26 | fixed: 'right', 27 | }) 28 | 29 | for (let i = 0; i < 500; i += 1) { 30 | const cells: Array = [] 31 | 32 | const object: any = {} 33 | for (let y = 0; y < tempColumns.length; y += 1) { 34 | object[`${y}`] = `${i} - ${y}` 35 | if (i === 3 && y === 2) { 36 | cells.push({ 37 | name: `${y}`, 38 | value: `${i} - ${y} test`, 39 | style: {}, 40 | }) 41 | } else if (i === 8 && y === 2) { 42 | cells.push({ 43 | name: `${y}`, 44 | value: `${i} - ${y}`, 45 | style: {}, 46 | }) 47 | } else { 48 | cells.push({ 49 | name: `${y}`, 50 | value: `${i} - ${y}`, 51 | }) 52 | } 53 | } 54 | rows.push({ 55 | height: 35, 56 | key: `${i}`, 57 | object, 58 | cells, 59 | }) 60 | } 61 | 62 | const RowDataGrid = () => { 63 | const [datas] = useState[]>(produce(rows, () => {})) 64 | return ( 65 | 66 | {(width, height) => ( 67 | 68 | width={width} 69 | height={height} 70 | rows={datas} 71 | columns={tempColumns} 72 | /> 73 | )} 74 | 75 | ) 76 | } 77 | 78 | export default { 79 | component: RowDataGrid, 80 | title: 'Demos', 81 | } as Meta 82 | 83 | export const FilterData: React.VFC<{}> = () => 84 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/foot.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import DataGrid, { Row, Cell, AutoSize } from '../src' 5 | 6 | const rows: any[] = [] 7 | 8 | for (let i=0; i < 100; i += 1) { 9 | 10 | const cells: Cell[] = [{ 11 | name: 'id', 12 | value: `${i}`, 13 | },{ 14 | name: 'userName', 15 | value: `my name ${i}`, 16 | },{ 17 | name: 'address', 18 | value: `address ${i}`, 19 | },{ 20 | name: 'email', 21 | value: `E-Mail ${i}`, 22 | }, { 23 | name: 'mobilePhone', 24 | value: `Mobile Phone ${i}`, 25 | }] 26 | const row: Row = { 27 | height: 35, 28 | key: `key-${i}`, 29 | cells, 30 | } 31 | rows.push(row) 32 | } 33 | 34 | const footRows = { 35 | height: 35, 36 | key: 'footRows', 37 | cells: [{ 38 | name: 'id', 39 | value: `count: 10000`, 40 | },{ 41 | name: 'userName', 42 | value: `max: zhangj`, 43 | },{ 44 | name: 'address', 45 | value: `min: WuHan`, 46 | },{ 47 | name: 'email', 48 | value: `test@email.com`, 49 | }, { 50 | name: 'mobilePhone', 51 | value: '+86 110', 52 | }], 53 | } 54 | 55 | const FootGrid = () => { 56 | const columns: any[] = [{ 57 | name: 'id', 58 | title: 'id' 59 | },{ 60 | name: 'userName', 61 | title: 'User Name' 62 | }, { 63 | name: 'address', 64 | title: 'Address' 65 | }, { 66 | name: 'email', 67 | title: 'E-Mail' 68 | }, { 69 | name: 'mobilePhone', 70 | title: 'Mobile Phone' 71 | }] 72 | return ( 73 | 74 | {(width) => ( 75 | 76 | columns={columns} 77 | footRows={footRows} 78 | rows={rows} 79 | width={width} 80 | height={500} 81 | /> 82 | )} 83 | 84 | 85 | ) 86 | } 87 | 88 | export default { 89 | component: FootGrid, 90 | title: 'Demos', 91 | } as Meta 92 | 93 | export const Foot: React.VFC<{}> = () => -------------------------------------------------------------------------------- /packages/rc-grid/stories/header-merge.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Meta } from '@storybook/react' 3 | 4 | import styled from 'styled-components' 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | 7 | const rows: Array> = [] 8 | const columns: Array> = [ 9 | { 10 | name: `0`, 11 | title: `姓名`, 12 | width: 120, 13 | }, 14 | { 15 | name: `1`, 16 | title: `年龄`, 17 | width: 120, 18 | }, 19 | { 20 | name: `2`, 21 | title: `身份证号`, 22 | width: 120, 23 | }, 24 | { 25 | name: `3`, 26 | title: `家庭地址`, 27 | width: 120, 28 | }, 29 | { 30 | name: `4`, 31 | title: `家庭电话号码`, 32 | width: 120, 33 | }, 34 | { 35 | name: `5`, 36 | title: `家庭人员数量`, 37 | width: 120, 38 | }, 39 | { 40 | name: `6`, 41 | title: `公司地址`, 42 | }, 43 | ] 44 | 45 | const GridHeaderCell = styled.div` 46 | display: inline-block; 47 | position: absolute; 48 | border-right: 1px solid #ddd; 49 | border-bottom: 1px solid #ddd; 50 | box-sizing: border-box; 51 | background-color: hsl(0deg 0% 97.5%); 52 | /** 优化 webkit 中的渲染效率 */ 53 | content-visibility: auto; 54 | padding: 0px 8px; 55 | white-space: nowrap; 56 | text-overflow: ellipsis; 57 | ` 58 | 59 | for (let i = 0; i < 5000; i += 1) { 60 | const cells: Array = [] 61 | 62 | for (let y = 0; y < columns.length; y += 1) { 63 | if (i === 3 && y === 2) { 64 | cells.push({ 65 | name: `${y}`, 66 | value: `${i} - ${y}`, 67 | style: {}, 68 | }) 69 | } else if (i === 8 && y === 2) { 70 | cells.push({ 71 | name: `${y}`, 72 | value: `${i} - ${y}`, 73 | style: {}, 74 | }) 75 | } else { 76 | cells.push({ 77 | name: `${y}`, 78 | value: `${i} - ${y}`, 79 | }) 80 | } 81 | } 82 | rows.push({ 83 | height: 35, 84 | key: `${i}`, 85 | cells, 86 | }) 87 | } 88 | 89 | const RowDataGrid = () => ( 90 | 91 | {(width, height) => ( 92 | 93 | rows={rows} 94 | width={width} 95 | height={height} 96 | columns={columns} 97 | onHeaderRowRender={(node) => { 98 | const { styled: tempStyled, ...restProps } = node.props 99 | return React.cloneElement(node, { 100 | ...restProps, 101 | styled: { 102 | ...tempStyled, 103 | top: 35, 104 | }, 105 | key: node.key, 106 | }) 107 | }} 108 | onHeaderCellRender={({ headerCell, index }) => { 109 | const { 110 | styled: tempStyled, 111 | ...restProps 112 | } = headerCell.props 113 | if (index === 0) { 114 | return [ 115 | 129 | 人员资料 130 | , 131 | headerCell, 132 | ] 133 | } 134 | 135 | if (index > 5) { 136 | return [ 137 | React.cloneElement(headerCell, { 138 | ...restProps, 139 | styled: { 140 | ...tempStyled, 141 | top: -35, 142 | height: 35 * 2, 143 | }, 144 | }), 145 | ] 146 | } 147 | return [ 148 | React.cloneElement(headerCell, { 149 | ...restProps, 150 | styled: { 151 | ...tempStyled, 152 | }, 153 | }), 154 | ] 155 | }} 156 | /> 157 | )} 158 | 159 | ) 160 | 161 | export default { 162 | component: RowDataGrid, 163 | title: 'Demos', 164 | } as Meta 165 | 166 | export const HeaderMerge: React.VFC<{}> = () => 167 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/header-resizable.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | 7 | const rows: Array> = [] 8 | const tempColumns: Array> = [] 9 | 10 | tempColumns.push({ 11 | name: `0`, 12 | title: `字段 - 0`, 13 | fixed: 'left', 14 | }) 15 | 16 | for (let i = 2; i < 20; i += 1) { 17 | tempColumns.push({ 18 | name: `${i}`, 19 | title: `字段 - ${i}`, 20 | resizable: true, 21 | sort: true, 22 | }) 23 | } 24 | 25 | tempColumns.push({ 26 | name: `1`, 27 | title: `字段 - 1`, 28 | fixed: 'right', 29 | }) 30 | 31 | for (let i = 0; i < 500; i += 1) { 32 | const cells: Array = [] 33 | 34 | const object: any = {} 35 | for (let y = 0; y < tempColumns.length; y += 1) { 36 | object[`${y}`] = `${i} - ${y}` 37 | if (i === 3 && y === 2) { 38 | cells.push({ 39 | name: `${y}`, 40 | value: `${i} - ${y}`, 41 | style: {}, 42 | }) 43 | } else if (i === 8 && y === 2) { 44 | cells.push({ 45 | name: `${y}`, 46 | value: `${i} - ${y}`, 47 | style: {}, 48 | }) 49 | } else { 50 | cells.push({ 51 | name: `${y}`, 52 | value: `${i} - ${y}`, 53 | }) 54 | } 55 | } 56 | rows.push({ 57 | height: 35, 58 | key: `${i}`, 59 | object, 60 | cells, 61 | }) 62 | } 63 | 64 | const RowDataGrid = () => { 65 | const oldData = useRef[]>(produce(rows, () => {})) 66 | const [columns, setColumns] = useState>>(tempColumns) 67 | const [datas, setDatas] = useState[]>(produce(rows, () => {})) 68 | return ( 69 | 70 | {(width, height) => ( 71 | 72 | rows={datas} 73 | width={width} 74 | height={height} 75 | columns={columns} 76 | onSort={(sort) => { 77 | if (sort.length === 0) { 78 | setDatas(oldData.current) 79 | } 80 | if (sort.length > 0) { 81 | const { direction } = sort[0] 82 | const { columnKey } = sort[0] 83 | const rowsData = produce(datas, (newData) => { 84 | newData.sort((a, b) => { 85 | const aData: string = a.object[columnKey] 86 | const bData: string = b.object[columnKey] 87 | 88 | if (direction === 'ASC') { 89 | if ( 90 | aData.toUpperCase() > 91 | bData.toUpperCase() 92 | ) { 93 | return 1 94 | } 95 | return -1 96 | } 97 | if (direction === 'DESC') { 98 | if ( 99 | aData.toUpperCase() < 100 | bData.toUpperCase() 101 | ) { 102 | return 1 103 | } 104 | } 105 | return -1 106 | }) 107 | }) 108 | setDatas(rowsData) 109 | } 110 | }} 111 | onHeaderResizable={(changeColumns) => { 112 | setColumns(changeColumns) 113 | }} 114 | /> 115 | )} 116 | 117 | ) 118 | } 119 | 120 | export default { 121 | component: RowDataGrid, 122 | title: 'Demos', 123 | } as Meta 124 | 125 | export const HeaderResizable: React.VFC<{}> = () => 126 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/select-multiple.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { Key, useRef, useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | import { onHeaderDrop } from './utils' 7 | 8 | const rows: Array> = [] 9 | const tempColumns: Array> = [] 10 | 11 | tempColumns.push({ 12 | name: `0`, 13 | title: `字段 - 0`, 14 | fixed: 'left', 15 | }) 16 | 17 | for (let i = 2; i < 20; i += 1) { 18 | tempColumns.push({ 19 | name: `${i}`, 20 | title: `字段 - ${i}`, 21 | sort: true, 22 | }) 23 | } 24 | 25 | tempColumns.push({ 26 | name: `1`, 27 | title: `字段 - 1`, 28 | fixed: 'right', 29 | }) 30 | 31 | for (let i = 0; i < 500; i += 1) { 32 | const cells: Array = [] 33 | 34 | const object: any = {} 35 | for (let y = 0; y < tempColumns.length; y += 1) { 36 | object[`${y}`] = `${i} - ${y}` 37 | if (i === 3 && y === 2) { 38 | cells.push({ 39 | name: `${y}`, 40 | value: `${i} - ${y}`, 41 | style: {}, 42 | }) 43 | } else if (i === 8 && y === 2) { 44 | cells.push({ 45 | name: `${y}`, 46 | value: `${i} - ${y}`, 47 | style: {}, 48 | }) 49 | } else { 50 | cells.push({ 51 | name: `${y}`, 52 | value: `${i} - ${y}`, 53 | }) 54 | } 55 | } 56 | rows.push({ 57 | height: 35, 58 | key: `${i}`, 59 | object, 60 | cells, 61 | }) 62 | } 63 | 64 | const RowDataGrid = () => { 65 | const oldData = useRef[]>(produce(rows, () => {})) 66 | const [datas, setDatas] = useState[]>(produce(rows, () => {})) 67 | const [cols, setCols] = useState[]>(tempColumns) 68 | const [select, setSelect] = useState([]) 69 | return ( 70 | 71 | {(width, height) => ( 72 | 73 | rows={datas} 74 | width={width} 75 | height={height} 76 | columns={cols} 77 | selectedRows={select} 78 | onChangeSelectedRows={setSelect} 79 | select={{ 80 | mode: 'multiple', 81 | component: (param) => { 82 | const { onSelected, row, selected, mode } = param 83 | return ( 84 | { 91 | onSelected(row, mode) 92 | }} 93 | /> 94 | ) 95 | }, 96 | headerComponent: () => ( 97 | 103 | ), 104 | }} 105 | onHeaderDrop={(source, target) => { 106 | setCols(onHeaderDrop(cols, source, target)) 107 | }} 108 | onSort={(sort) => { 109 | if (sort.length === 0) { 110 | setDatas(oldData.current) 111 | } 112 | if (sort.length > 0) { 113 | const { direction } = sort[0] 114 | const { columnKey } = sort[0] 115 | const rowsData = produce(datas, (newData) => { 116 | newData.sort((a, b) => { 117 | const aData: string = a.object[columnKey] 118 | const bData: string = b.object[columnKey] 119 | 120 | if (direction === 'ASC') { 121 | if ( 122 | aData.toUpperCase() > 123 | bData.toUpperCase() 124 | ) { 125 | return 1 126 | } 127 | return -1 128 | } 129 | if (direction === 'DESC') { 130 | if ( 131 | aData.toUpperCase() < 132 | bData.toUpperCase() 133 | ) { 134 | return 1 135 | } 136 | } 137 | return -1 138 | }) 139 | }) 140 | setDatas(rowsData) 141 | } 142 | }} 143 | /> 144 | )} 145 | 146 | ) 147 | } 148 | 149 | export default { 150 | component: RowDataGrid, 151 | title: 'Demos', 152 | } as Meta 153 | 154 | export const MultipleSelectRow: React.VFC<{}> = () => 155 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/select-single.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { Key, useRef, useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | import { onHeaderDrop } from './utils' 7 | 8 | const rows: Array> = [] 9 | const tempColumns: Array> = [] 10 | 11 | tempColumns.push({ 12 | name: `0`, 13 | title: `字段 - 0`, 14 | fixed: 'left', 15 | }) 16 | 17 | for (let i = 2; i < 20; i += 1) { 18 | tempColumns.push({ 19 | name: `${i}`, 20 | title: `字段 - ${i}`, 21 | sort: true, 22 | }) 23 | } 24 | 25 | tempColumns.push({ 26 | name: `1`, 27 | title: `字段 - 1`, 28 | fixed: 'right', 29 | }) 30 | 31 | for (let i = 0; i < 500; i += 1) { 32 | const cells: Array = [] 33 | 34 | const object: any = {} 35 | for (let y = 0; y < tempColumns.length; y += 1) { 36 | object[`${y}`] = `${i} - ${y}` 37 | if (i === 3 && y === 2) { 38 | cells.push({ 39 | name: `${y}`, 40 | value: `${i} - ${y}`, 41 | style: {}, 42 | }) 43 | } else if (i === 8 && y === 2) { 44 | cells.push({ 45 | name: `${y}`, 46 | value: `${i} - ${y}`, 47 | style: {}, 48 | }) 49 | } else { 50 | cells.push({ 51 | name: `${y}`, 52 | value: `${i} - ${y}`, 53 | }) 54 | } 55 | } 56 | rows.push({ 57 | height: 35, 58 | key: `${i}`, 59 | object, 60 | cells, 61 | }) 62 | } 63 | 64 | const RowDataGrid = () => { 65 | const oldData = useRef[]>(produce(rows, () => {})) 66 | const [datas, setDatas] = useState[]>(produce(rows, () => {})) 67 | const [cols, setCols] = useState[]>(tempColumns) 68 | const [select, setSelect] = useState([]) 69 | return ( 70 | 71 | {(width, height) => ( 72 | 73 | rows={datas} 74 | width={width} 75 | height={height} 76 | columns={cols} 77 | selectedRows={select} 78 | onChangeSelectedRows={setSelect} 79 | select={{ 80 | mode: 'single', 81 | component: (param) => { 82 | const { onSelected, row, selected, mode} = param 83 | return ( 84 | { 91 | onSelected(row, mode) 92 | }} 93 | /> 94 | ) 95 | } 96 | }} 97 | onHeaderDrop={(source, target) => { 98 | setCols(onHeaderDrop(cols, source, target)) 99 | }} 100 | onSort={(sort) => { 101 | if (sort.length === 0) { 102 | setDatas(oldData.current) 103 | } 104 | if (sort.length > 0) { 105 | const { direction } = sort[0] 106 | const { columnKey } = sort[0] 107 | const rowsData = produce(datas, (newData) => { 108 | newData.sort((a, b) => { 109 | const aData: string = a.object[columnKey] 110 | const bData: string = b.object[columnKey] 111 | 112 | if (direction === 'ASC') { 113 | if ( 114 | aData.toUpperCase() > 115 | bData.toUpperCase() 116 | ) { 117 | return 1 118 | } 119 | return -1 120 | } 121 | if (direction === 'DESC') { 122 | if ( 123 | aData.toUpperCase() < 124 | bData.toUpperCase() 125 | ) { 126 | return 1 127 | } 128 | } 129 | return -1 130 | }) 131 | }) 132 | setDatas(rowsData) 133 | } 134 | }} 135 | /> 136 | )} 137 | 138 | ) 139 | } 140 | 141 | export default { 142 | component: RowDataGrid, 143 | title: 'Demos', 144 | } as Meta 145 | 146 | export const SelectRow: React.VFC<{}> = () => 147 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/sort-data.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { Meta } from '@storybook/react' 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Row, Column, Cell, AutoSize } from '../src' 6 | import { onHeaderDrop } from './utils' 7 | 8 | const rows: Array> = [] 9 | const tempColumns: Array> = [] 10 | 11 | tempColumns.push({ 12 | name: `0`, 13 | title: `字段 - 0`, 14 | fixed: 'left', 15 | }) 16 | 17 | for (let i = 2; i < 20; i += 1) { 18 | tempColumns.push({ 19 | name: `${i}`, 20 | title: `字段 - ${i}`, 21 | sort: true, 22 | }) 23 | } 24 | 25 | tempColumns.push({ 26 | name: `1`, 27 | title: `字段 - 1`, 28 | fixed: 'right', 29 | }) 30 | 31 | for (let i = 0; i < 500; i += 1) { 32 | const cells: Array = [] 33 | 34 | const object: any = {} 35 | for (let y = 0; y < tempColumns.length; y += 1) { 36 | object[`${y}`] = `${i} - ${y}` 37 | if (i === 3 && y === 2) { 38 | cells.push({ 39 | name: `${y}`, 40 | value: `${i} - ${y}`, 41 | style: {}, 42 | }) 43 | } else if (i === 8 && y === 2) { 44 | cells.push({ 45 | name: `${y}`, 46 | value: `${i} - ${y}`, 47 | style: {}, 48 | }) 49 | } else { 50 | cells.push({ 51 | name: `${y}`, 52 | value: `${i} - ${y}`, 53 | }) 54 | } 55 | } 56 | rows.push({ 57 | height: 35, 58 | key: `${i}`, 59 | object, 60 | cells, 61 | }) 62 | } 63 | 64 | const RowDataGrid = () => { 65 | const oldData = useRef[]>(produce(rows, () => {})) 66 | const [datas, setDatas] = useState[]>(produce(rows, () => {})) 67 | const [cols, setCols] = useState[]>(tempColumns) 68 | return ( 69 | 70 | {(width, height) => ( 71 | 72 | rows={datas} 73 | width={width} 74 | height={height} 75 | columns={cols} 76 | onHeaderDrop={(source, target) => { 77 | setCols(onHeaderDrop(cols, source, target)) 78 | }} 79 | onSort={(sort) => { 80 | if (sort.length === 0) { 81 | setDatas(oldData.current) 82 | } 83 | if (sort.length > 0) { 84 | const { direction } = sort[0] 85 | const { columnKey } = sort[0] 86 | const rowsData = produce(datas, (newData) => { 87 | newData.sort((a, b) => { 88 | const aData: string = a.object[columnKey] 89 | const bData: string = b.object[columnKey] 90 | 91 | if (direction === 'ASC') { 92 | if ( 93 | aData.toUpperCase() > 94 | bData.toUpperCase() 95 | ) { 96 | return 1 97 | } 98 | return -1 99 | } 100 | if (direction === 'DESC') { 101 | if ( 102 | aData.toUpperCase() < 103 | bData.toUpperCase() 104 | ) { 105 | return 1 106 | } 107 | } 108 | return -1 109 | }) 110 | }) 111 | setDatas(rowsData) 112 | } 113 | }} 114 | /> 115 | )} 116 | 117 | ) 118 | } 119 | 120 | export default { 121 | component: RowDataGrid, 122 | title: 'Demos', 123 | } as Meta 124 | 125 | export const SortData: React.VFC<{}> = () => 126 | -------------------------------------------------------------------------------- /packages/rc-grid/stories/utils.ts: -------------------------------------------------------------------------------- 1 | import produce from 'immer' 2 | import { Column } from '../src' 3 | 4 | // eslint-disable-next-line import/prefer-default-export 5 | export function onHeaderDrop( 6 | columns: Column[], 7 | source: Column, 8 | target: Column 9 | ) { 10 | const sourceIndex = columns.findIndex((ele) => ele.name === source.name) 11 | const targetIndex = columns.findIndex((ele) => ele.name === target.name) 12 | return produce(columns, (changeColumns) => { 13 | changeColumns.splice(sourceIndex, 1) 14 | changeColumns.splice(targetIndex, 0, source) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /packages/rc-grid/tests/__mocks__/ResizeObserver.mock.ts: -------------------------------------------------------------------------------- 1 | const ResizeObserverMock = jest.fn((callback: Function) => { 2 | callback() 3 | return ({ 4 | observe: jest.fn(), 5 | unobserve: jest.fn(), 6 | disconnect: jest.fn(), 7 | }) 8 | }); 9 | 10 | export default ResizeObserverMock -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/__snapshots__/browser-utils.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`isInvisibleScrollbar test 1`] = `true`; 4 | 5 | exports[`writeClipboardText test 1`] = ` 6 | 7 | 10 | 11 | `; 12 | -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/browser-utils.test.tsx: -------------------------------------------------------------------------------- 1 | import { isInvisibleScrollbar, writeClipboardText } from '../../src/utils/browser' 2 | 3 | test('isInvisibleScrollbar test', () => { 4 | expect(isInvisibleScrollbar()).toMatchSnapshot() 5 | }) 6 | 7 | test('writeClipboardText test', async () => { 8 | writeClipboardText('test 123'); 9 | expect(document.body).toMatchSnapshot() 10 | }) -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/cells-merge.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from '@testing-library/react' 3 | 4 | import DataGrid, { Row, Column, Cell } from '../../src' 5 | 6 | const rows: Array> = [] 7 | const columns: Array> = [] 8 | 9 | columns.push({ 10 | name: `0`, 11 | title: `字段 - 0`, 12 | fixed: 'left', 13 | }) 14 | 15 | for (let i = 2; i < 1000; i += 1) { 16 | columns.push({ 17 | name: `${i}`, 18 | title: `字段 - ${i}`, 19 | }) 20 | } 21 | 22 | columns.push({ 23 | name: `1`, 24 | title: `字段 - 1`, 25 | fixed: 'right', 26 | }) 27 | 28 | for (let i = 0; i < 5000; i += 1) { 29 | const cells: Array = [] 30 | 31 | for (let y = 0; y < 1000; y += 1) { 32 | if (i === 3 && y === 2) { 33 | cells.push({ 34 | name: `${y}`, 35 | value: `${i} - ${y}`, 36 | rowSpan: 2, 37 | colSpan: 2, 38 | }) 39 | } else if (i === 8 && y === 2) { 40 | cells.push({ 41 | name: `${y}`, 42 | value: `${i} - ${y}`, 43 | rowSpan: 2, 44 | }) 45 | } else { 46 | cells.push({ 47 | name: `${y}`, 48 | value: `${i} - ${y}`, 49 | }) 50 | } 51 | } 52 | rows.push({ 53 | height: 35, 54 | key: `${i}`, 55 | cells, 56 | }) 57 | } 58 | 59 | 60 | test('cells merge test', async () => { 61 | const { container } = render( 62 | 63 | rows={rows} 64 | columns={columns} 65 | /> 66 | ) 67 | expect(container).toMatchSnapshot() 68 | }) 69 | 70 | -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/debounce.test.ts: -------------------------------------------------------------------------------- 1 | import debounce from '../../src/utils/debounce' 2 | 3 | test('test debounce 3 click',async () => { 4 | const mockCallBack = jest.fn(); 5 | const newFn = debounce(mockCallBack, 500, { maxTime: 500}) 6 | newFn() 7 | newFn() 8 | newFn() 9 | await new Promise((resolve) => { 10 | setTimeout(() => { 11 | resolve() 12 | }, 600); 13 | }) 14 | 15 | expect(mockCallBack.mock.calls.length).toEqual(2); 16 | }) 17 | 18 | test('test debounce 2 click delay 500ms',async () => { 19 | const mockCallBack = jest.fn(); 20 | const newFn = debounce(mockCallBack, 500, { maxTime: 500}) 21 | newFn() 22 | await new Promise((resolve) => { 23 | setTimeout(() => { 24 | resolve() 25 | }, 600); 26 | }) 27 | newFn() 28 | expect(mockCallBack.mock.calls.length).toEqual(2); 29 | }) -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/drag-grid.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { render, fireEvent, waitFor } from "@testing-library/react" 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Column, DataGridProps, Row, Cell, EditorProps } from '../../src/index' 6 | 7 | function onHeaderDrop( 8 | columns: Column[], 9 | source: Column, 10 | target: Column 11 | ) { 12 | const sourceIndex = columns.findIndex((ele) => ele.name === source.name) 13 | const targetIndex = columns.findIndex((ele) => ele.name === target.name) 14 | return produce(columns, (changeColumns) => { 15 | changeColumns.splice(sourceIndex, 1) 16 | changeColumns.splice(targetIndex, 0, source) 17 | }) 18 | } 19 | 20 | 21 | const Input = ({ style, value: tempValue, onEditCompleted }: EditorProps) => { 22 | const [value, setValue] = useState(tempValue) 23 | return ( 24 | { 30 | onEditCompleted(value) 31 | }} 32 | onChange={(e) => { 33 | const text = e.target.value 34 | setValue(text) 35 | }} 36 | /> 37 | ) 38 | } 39 | 40 | 41 | const rowsData: any[] = [] 42 | 43 | for (let i = 0; i < 10000; i += 1) { 44 | 45 | const cells: Cell[] = [{ 46 | name: 'id', 47 | value: `${i}`, 48 | }, { 49 | name: 'userName', 50 | value: `my name ${i}`, 51 | }, { 52 | name: 'address', 53 | value: `address ${i}`, 54 | }, { 55 | name: 'email', 56 | value: `E-Mail ${i}`, 57 | }, { 58 | name: 'mobilePhone', 59 | value: `Mobile Phone ${i}`, 60 | }] 61 | const row: Row = { 62 | height: 35, 63 | key: `key-${i}`, 64 | cells, 65 | object: { 66 | id: `${i}`, 67 | userName: `my name ${i}`, 68 | address: `address ${i}`, 69 | email: `E-Mail ${i}`, 70 | mobilePhone: `Mobile Phone ${i}` 71 | } 72 | } 73 | rowsData.push(row) 74 | } 75 | 76 | 77 | interface BashGridProps extends Omit, 'rows' | 'columns'> { 78 | /** 表格的行数据信息 */ 79 | rows?: Row[] 80 | /** 列的信息 */ 81 | columns?: Column[] 82 | } 83 | 84 | function BashGrid(props: BashGridProps) { 85 | const { columns, rows, ...restProps} = props 86 | 87 | const oldData = useRef[]>(produce(rows, () => { })) 88 | const [datas, setDatas] = useState[]>(produce(rows, () => { })) 89 | const [cols, setCols] = useState[]>(columns) 90 | 91 | return ( 92 | 93 | // eslint-disable-next-line react/jsx-props-no-spreading 94 | {...restProps} 95 | columns={cols} 96 | rows={datas} 97 | onHeaderResizable={(newCols) => { 98 | setCols(newCols) 99 | }} 100 | onHeaderDrop={(source, target) => { 101 | setCols(onHeaderDrop(cols, source, target)) 102 | }} 103 | onSort={(sort) => { 104 | if (sort.length === 0) { 105 | setDatas(oldData.current) 106 | } 107 | if (sort.length > 0) { 108 | const { direction } = sort[0] 109 | const { columnKey } = sort[0] 110 | const tempRowData = produce(datas, (newData) => { 111 | newData.sort((a, b) => { 112 | const aData: string = a.object[columnKey] 113 | const bData: string = b.object[columnKey] 114 | 115 | if (direction === 'ASC') { 116 | if ( 117 | aData.toUpperCase() > 118 | bData.toUpperCase() 119 | ) { 120 | return 1 121 | } 122 | return -1 123 | } 124 | if (direction === 'DESC') { 125 | if ( 126 | aData.toUpperCase() < 127 | bData.toUpperCase() 128 | ) { 129 | return 1 130 | } 131 | } 132 | return -1 133 | }) 134 | }) 135 | setDatas(tempRowData) 136 | } 137 | }} 138 | /> 139 | ) 140 | } 141 | 142 | BashGrid.defaultProps = { 143 | columns: [{ 144 | name: 'id', 145 | title: 'id', 146 | sort: true, 147 | resizable: true, 148 | fixed: 'left' 149 | }, { 150 | name: 'userName', 151 | sort: true, 152 | resizable: true, 153 | title: 'User Name', 154 | editor: Input 155 | }, { 156 | name: 'address', 157 | sort: true, 158 | resizable: true, 159 | title: 'Address' 160 | }, { 161 | name: 'email', 162 | sort: true, 163 | resizable: true, 164 | title: 'E-Mail' 165 | }, { 166 | name: 'mobilePhone', 167 | title: 'Mobile Phone', 168 | sort: true, 169 | resizable: true, 170 | fixed: 'right' 171 | }], 172 | rows: [] 173 | } 174 | 175 | test('grid header drag', async () => { 176 | const { container, findByText, getByText } = render(()) 177 | 178 | await waitFor(() => getByText('User Name')) 179 | 180 | const address = await findByText('Address') 181 | const userName = await findByText('User Name') 182 | 183 | 184 | fireEvent.dragStart(address, { 185 | dataTransfer: { 186 | setData: jest.fn(), 187 | getData: () => 'userName' 188 | } 189 | }) 190 | 191 | 192 | fireEvent.dragEnter(userName, { 193 | dataTransfer: { 194 | getData: () => 'address' 195 | } 196 | }) 197 | fireEvent.dragLeave(userName) 198 | fireEvent.dragOver(userName) 199 | fireEvent.drop(userName, { 200 | dataTransfer: { 201 | name: 'address', 202 | getData: () => 'address' 203 | } 204 | }) 205 | 206 | expect(container).toMatchSnapshot() 207 | }) -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/expandable-grid.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react' 2 | import { render, waitFor, fireEvent } from "@testing-library/react" 3 | import produce from 'immer' 4 | 5 | import DataGrid, { Column, DataGridProps, Row, Cell, EditorProps } from '../../src/index' 6 | 7 | 8 | const Input = ({ style, value: tempValue, onEditCompleted }: EditorProps) => { 9 | const [value, setValue] = useState(tempValue) 10 | return ( 11 | { 17 | onEditCompleted(value) 18 | }} 19 | onChange={(e) => { 20 | const text = e.target.value 21 | setValue(text) 22 | }} 23 | /> 24 | ) 25 | } 26 | 27 | 28 | const rowsData: any[] = [] 29 | 30 | for (let i = 0; i < 10000; i += 1) { 31 | 32 | const cells: Cell[] = [{ 33 | name: 'id', 34 | value: `${i}`, 35 | }, { 36 | name: 'userName', 37 | value: `my name ${i}`, 38 | }, { 39 | name: 'address', 40 | value: `address ${i}`, 41 | }, { 42 | name: 'email', 43 | value: `E-Mail ${i}`, 44 | }, { 45 | name: 'mobilePhone', 46 | value: `Mobile Phone ${i}`, 47 | }] 48 | const row: Row = { 49 | height: 35, 50 | key: `key-${i}`, 51 | cells, 52 | object: { 53 | id: `${i}`, 54 | userName: `my name ${i}`, 55 | address: `address ${i}`, 56 | email: `E-Mail ${i}`, 57 | mobilePhone: `Mobile Phone ${i}` 58 | } 59 | } 60 | rowsData.push(row) 61 | } 62 | 63 | 64 | interface BashGridProps extends Omit, 'rows' | 'columns'> { 65 | /** 表格的行数据信息 */ 66 | rows?: Row[] 67 | /** 列的信息 */ 68 | columns?: Column[] 69 | } 70 | 71 | function BashGrid(props: BashGridProps) { 72 | const { columns, rows, ...restProps} = props 73 | 74 | const oldData = useRef[]>(produce(rows, () => { })) 75 | const [datas, setDatas] = useState[]>(produce(rows, () => { })) 76 | const [col, setCol] = useState[]>(columns) 77 | 78 | return ( 79 | 80 | // eslint-disable-next-line react/jsx-props-no-spreading 81 | {...restProps} 82 | columns={col} 83 | rows={datas} 84 | onHeaderResizable={(newCols) => { 85 | setCol(newCols) 86 | }} 87 | expandable={{ 88 | isExpandable: (row) => { 89 | const { id } = row.object as any 90 | if (id === '1') { 91 | return false 92 | } 93 | return true 94 | }, 95 | expandedRowRender: (row, style) => ( 96 |
97 | {' '} 98 | 这是一个展开的内容信息 {JSON.stringify(row)}{' '} 99 |
100 | ), 101 | }} 102 | onSort={(sort) => { 103 | if (sort.length === 0) { 104 | setDatas(oldData.current) 105 | } 106 | if (sort.length > 0) { 107 | const { direction } = sort[0] 108 | const { columnKey } = sort[0] 109 | const tempRowData = produce(datas, (newData) => { 110 | newData.sort((a, b) => { 111 | const aData: string = a.object[columnKey] 112 | const bData: string = b.object[columnKey] 113 | 114 | if (direction === 'ASC') { 115 | if ( 116 | aData.toUpperCase() > 117 | bData.toUpperCase() 118 | ) { 119 | return 1 120 | } 121 | return -1 122 | } 123 | if (direction === 'DESC') { 124 | if ( 125 | aData.toUpperCase() < 126 | bData.toUpperCase() 127 | ) { 128 | return 1 129 | } 130 | } 131 | return -1 132 | }) 133 | }) 134 | setDatas(tempRowData) 135 | } 136 | }} 137 | /> 138 | ) 139 | } 140 | 141 | 142 | BashGrid.defaultProps = { 143 | columns: [{ 144 | name: 'id', 145 | title: 'id', 146 | sort: true, 147 | resizable: true, 148 | fixed: 'left' 149 | }, { 150 | name: 'userName', 151 | sort: true, 152 | resizable: true, 153 | title: 'User Name', 154 | editor: Input 155 | }, { 156 | name: 'address', 157 | sort: true, 158 | resizable: true, 159 | title: 'Address' 160 | }, { 161 | name: 'email', 162 | sort: true, 163 | resizable: true, 164 | title: 'E-Mail' 165 | }, { 166 | name: 'mobilePhone', 167 | title: 'Mobile Phone', 168 | sort: true, 169 | resizable: true, 170 | fixed: 'right' 171 | }], 172 | rows: rowsData 173 | } 174 | 175 | 176 | test('expandable grid test', async () => { 177 | const { container, getAllByRole } =render(( 178 | 179 | )) 180 | await waitFor(() => getAllByRole('row')) 181 | const expandableIcon = getAllByRole('row')[0].children[0].children[0] 182 | fireEvent.click(expandableIcon) 183 | fireEvent.click(expandableIcon) 184 | fireEvent.click(expandableIcon) 185 | 186 | const twoExpandableIcon = getAllByRole('row')[3].children[0].children[0] 187 | fireEvent.click(twoExpandableIcon) 188 | fireEvent.click(twoExpandableIcon) 189 | fireEvent.click(twoExpandableIcon) 190 | expect(container).toMatchSnapshot() 191 | }) -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/foot-grid.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render, waitFor } from '@testing-library/react' 3 | 4 | import DataGrid, { Row, Cell, AutoSize } from '../../src' 5 | 6 | const rows: any[] = [] 7 | 8 | for (let i=0; i < 100; i += 1) { 9 | 10 | const cells: Cell[] = [{ 11 | name: 'id', 12 | value: `${i}`, 13 | },{ 14 | name: 'userName', 15 | value: `my name ${i}`, 16 | },{ 17 | name: 'address', 18 | value: `address ${i}`, 19 | },{ 20 | name: 'email', 21 | value: `E-Mail ${i}`, 22 | }, { 23 | name: 'mobilePhone', 24 | value: `Mobile Phone ${i}`, 25 | }] 26 | const row: Row = { 27 | height: 35, 28 | key: `key-${i}`, 29 | cells, 30 | } 31 | rows.push(row) 32 | } 33 | 34 | const footRows = { 35 | height: 35, 36 | key: 'footRows', 37 | cells: [{ 38 | name: 'id', 39 | value: `count: 10000`, 40 | },{ 41 | name: 'userName', 42 | value: `max: zhangj`, 43 | },{ 44 | name: 'address', 45 | value: `min: WuHan`, 46 | },{ 47 | name: 'email', 48 | value: `test@email.com`, 49 | }, { 50 | name: 'mobilePhone', 51 | value: '+86 110', 52 | }], 53 | } 54 | 55 | const FootGrid = () => { 56 | const columns: any[] = [{ 57 | name: 'id', 58 | title: 'id' 59 | },{ 60 | name: 'userName', 61 | title: 'User Name' 62 | }, { 63 | name: 'address', 64 | title: 'Address' 65 | }, { 66 | name: 'email', 67 | title: 'E-Mail' 68 | }, { 69 | name: 'mobilePhone', 70 | title: 'Mobile Phone' 71 | }] 72 | return ( 73 | 74 | {(width) => ( 75 | 76 | columns={columns} 77 | footRows={footRows} 78 | rows={rows} 79 | width={width} 80 | height={500} 81 | /> 82 | )} 83 | 84 | 85 | ) 86 | } 87 | 88 | test('foot grid test', async () => { 89 | const { container, getByRole } = render() 90 | await waitFor(() => getByRole('grid')) 91 | expect(container).toMatchSnapshot() 92 | }) -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/select-multiple.test.tsx: -------------------------------------------------------------------------------- 1 | import React, { Key, useState } from 'react' 2 | import { render, waitFor, fireEvent } from '@testing-library/react' 3 | 4 | 5 | import DataGrid, { Row, Column, Cell } from '../../src' 6 | 7 | const rows: Array> = [] 8 | const tempColumns: Array> = [] 9 | 10 | tempColumns.push({ 11 | name: `0`, 12 | title: `字段 - 0`, 13 | fixed: 'left', 14 | }) 15 | 16 | for (let i = 2; i < 20; i += 1) { 17 | tempColumns.push({ 18 | name: `${i}`, 19 | title: `字段 - ${i}`, 20 | sort: true, 21 | }) 22 | } 23 | 24 | tempColumns.push({ 25 | name: `1`, 26 | title: `字段 - 1`, 27 | fixed: 'right', 28 | }) 29 | 30 | for (let i = 0; i < 500; i += 1) { 31 | const cells: Array = [] 32 | 33 | const object: any = {} 34 | for (let y = 0; y < tempColumns.length; y += 1) { 35 | object[`${y}`] = `${i} - ${y}` 36 | if (i === 3 && y === 2) { 37 | cells.push({ 38 | name: `${y}`, 39 | value: `${i} - ${y}`, 40 | style: {}, 41 | }) 42 | } else if (i === 8 && y === 2) { 43 | cells.push({ 44 | name: `${y}`, 45 | value: `${i} - ${y}`, 46 | style: {}, 47 | }) 48 | } else { 49 | cells.push({ 50 | name: `${y}`, 51 | value: `${i} - ${y}`, 52 | }) 53 | } 54 | } 55 | rows.push({ 56 | height: 35, 57 | key: `${i}`, 58 | object, 59 | cells, 60 | }) 61 | } 62 | 63 | const RowDataGrid = () => { 64 | const [select, setSelect] = useState([]) 65 | return ( 66 | 67 | rows={rows} 68 | columns={tempColumns} 69 | selectedRows={select} 70 | onChangeSelectedRows={setSelect} 71 | select={{ 72 | mode: 'multiple', 73 | component: (param) => { 74 | const { onSelected, row, selected, mode } = param 75 | return ( 76 | { 84 | onSelected(row, mode) 85 | }} 86 | /> 87 | ) 88 | }, 89 | headerComponent: () => ( 90 | 96 | ), 97 | }} 98 | /> 99 | ) 100 | } 101 | 102 | 103 | test('select-multiple test', async () => { 104 | const { container, getByRole} = render() 105 | await waitFor(() => getByRole('grid')) 106 | const checkbox = container.querySelectorAll('.test-checkbox') 107 | fireEvent.click(checkbox[0]) 108 | fireEvent.click(checkbox[1]) 109 | fireEvent.click(checkbox[2]) 110 | fireEvent.click(checkbox[2]) 111 | expect(container).toMatchSnapshot() 112 | }) 113 | -------------------------------------------------------------------------------- /packages/rc-grid/tests/__tests__/tree-grid.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render, waitFor, fireEvent } from '@testing-library/react' 3 | import DataGrid, { Cell, Column, Row, } from '../../src' 4 | 5 | 6 | const rows: Array> = [] 7 | const tempColumns: Array> = [] 8 | 9 | tempColumns.push({ 10 | name: `0`, 11 | title: `字段 - 0`, 12 | fixed: 'left', 13 | }) 14 | 15 | for (let i = 2; i < 20; i += 1) { 16 | tempColumns.push({ 17 | name: `${i}`, 18 | title: `字段 - ${i}`, 19 | }) 20 | } 21 | 22 | tempColumns.push({ 23 | name: `1`, 24 | title: `字段 - 1`, 25 | fixed: 'right', 26 | }) 27 | 28 | for (let i = 0; i < 500; i += 1) { 29 | const cells: Array = [] 30 | 31 | const object: any = {} 32 | for (let y = 0; y < tempColumns.length; y += 1) { 33 | object[`${y}`] = `${i} - ${y}` 34 | if (i === 3 && y === 2) { 35 | cells.push({ 36 | name: `${y}`, 37 | value: `${i} - ${y}`, 38 | style: {}, 39 | }) 40 | } else if (i === 8 && y === 2) { 41 | cells.push({ 42 | name: `${y}`, 43 | value: `${i} - ${y}`, 44 | style: {}, 45 | }) 46 | } else { 47 | cells.push({ 48 | name: `${y}`, 49 | value: `${i} - ${y}`, 50 | }) 51 | } 52 | } 53 | rows.push({ 54 | height: 35, 55 | key: `${i}`, 56 | object, 57 | cells, 58 | }) 59 | } 60 | 61 | const RowDataGrid = () => ( 62 | 63 | rows={rows} 64 | columns={tempColumns} 65 | expandable={{ 66 | childrenColumnName: '2', 67 | isExpandable: (row) => row.key === '2' 68 | }} 69 | onChildrenRows={(row) => { 70 | const tempRow = [] 71 | for (let i = 500; i < 510; i += 1) { 72 | const cells: Array = [] 73 | const object: any = {} 74 | for (let y = 0; y < tempColumns.length; y += 1) { 75 | object[`${y}`] = `${i} - ${y}` 76 | if (i === 3 && y === 2) { 77 | cells.push({ 78 | name: `${y}`, 79 | value: `${i} - ${y}`, 80 | style: {}, 81 | }) 82 | } else if (i === 8 && y === 2) { 83 | cells.push({ 84 | name: `${y}`, 85 | value: `${i} - ${y}`, 86 | style: {}, 87 | }) 88 | } else { 89 | cells.push({ 90 | name: `${y}`, 91 | value: `${i} - ${y}`, 92 | }) 93 | } 94 | } 95 | tempRow.push({ 96 | height: 35, 97 | key: `${row.key}-${i}`, 98 | object, 99 | cells, 100 | }) 101 | } 102 | return tempRow 103 | }} 104 | /> 105 | ) 106 | 107 | test('tree test', async () => { 108 | const { container, getByText } = render() 109 | await waitFor(() => getByText('2 - 2')) 110 | 111 | const dom = getByText('2 - 2').children[0] 112 | fireEvent.click(dom) 113 | fireEvent.click(dom) 114 | fireEvent.click(dom) 115 | expect(container).toMatchSnapshot() 116 | }) -------------------------------------------------------------------------------- /packages/rc-grid/tests/setup.ts: -------------------------------------------------------------------------------- 1 | import ResizeObserverMock from './__mocks__/ResizeObserver.mock' 2 | 3 | /** polyfill ResizeObserver mock */ 4 | global.ResizeObserver = ResizeObserverMock 5 | 6 | /** polyfill execCommand mock */ 7 | global.document.execCommand = () => true; 8 | 9 | /** polyfill scrollTo mock */ 10 | Element.prototype.scrollTo = () => {} 11 | 12 | -------------------------------------------------------------------------------- /packages/rc-grid/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "noImplicitAny": true, 5 | "module": "commonjs", 6 | "target": "es5", 7 | "lib": ["ESNext", "DOM", "DOM.Iterable"], 8 | "jsx": "react", 9 | "declaration": true, 10 | "allowJs": true, 11 | "esModuleInterop": true, 12 | "moduleResolution": "node", 13 | "allowSyntheticDefaultImports": true 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "tests" ,"**/*.spec.ts", "**/*.test.tsx"] 17 | } 18 | -------------------------------------------------------------------------------- /website/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | .umi 18 | .umi-production 19 | .env.local 20 | 21 | # ide 22 | /.vscode 23 | /.idea 24 | -------------------------------------------------------------------------------- /website/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.svg 2 | **/*.ejs 3 | **/*.html 4 | package.json 5 | .umi 6 | .umi-production 7 | -------------------------------------------------------------------------------- /website/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /website/.umirc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'dumi'; 2 | 3 | export default defineConfig({ 4 | title: 'Rc-Grid', 5 | mode: 'site', 6 | favicon: 'https://user-images.githubusercontent.com/24241052/87370901-bdf6a880-c5b6-11ea-9237-56625c403237.png', 7 | logo: 'https://user-images.githubusercontent.com/24241052/87370901-bdf6a880-c5b6-11ea-9237-56625c403237.png', 8 | navs: [ 9 | null, 10 | { title: 'GitHub', path: 'https://github.com/HighPerformanceComponent/rc-grid' }, 11 | ] 12 | }); 13 | -------------------------------------------------------------------------------- /website/docs/getting-started/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Description 3 | order: 2 4 | --- 5 | 6 | # API 7 | 8 | -------------------------------------------------------------------------------- /website/docs/getting-started/api.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 描述 3 | order: 2 4 | --- 5 | 6 | # API 7 | 8 | -------------------------------------------------------------------------------- /website/docs/getting-started/change-log.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Change Log 3 | order: 3 4 | --- 5 | 6 | # Change Log 7 | 8 | `rc-grid `strictly follows [Semantic Versioning 2.0.0](https://semver.org/). 9 | 10 | ### Release Schedule 11 | 12 | - Weekly release: patch version at the end of every week for routine bugfix (anytime for urgent bugfix). 13 | - Monthly release: minor version at the end of every month for new features. 14 | - Major version release is not included in this schedule for breaking change and new features. 15 | -------------------------------------------------------------------------------- /website/docs/getting-started/change-log.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 更新日志 3 | order: 3 4 | --- 5 | 6 | # 更新日志 7 | 8 | `rc-grid` 严格遵循 [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/) 语义化版本规范。 9 | 10 | ### 发布周期 11 | 12 | - 修订版本号:每周末会进行日常 bugfix 更新。(如果有紧急的 bugfix,则任何时候都可发布) 13 | - 次版本号:每月发布一个带有新特性的向下兼容的版本。 14 | - 主版本号:含有破坏性更新和新特性,不在发布周期内。 15 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/cells-merge.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Cell Merge 8 | order: 3 9 | --- 10 | 11 | # Cell Merge 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/cells-merge.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 单元格合并 8 | order: 3 9 | --- 10 | 11 | # 单元格合并 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/dark.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Dark 8 | order: 10 9 | --- 10 | 11 | # Dark 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/dark.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 黑暗模式 8 | order: 10 9 | --- 10 | 11 | # 黑暗模式主题 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/editor.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Cell Editor 8 | order: 2 9 | --- 10 | 11 | # Cell Editor 12 | 13 | 14 | 15 | Need to create a component of `ComponentType`, such as the following 16 | 17 | ```jsx | pure 18 | const Input = ({ style, value: tempValue, onEditCompleted }: EditorProps) => { 19 | const [value, setValue] = useState(tempValue) 20 | return ( 21 | { 27 | onEditCompleted(value) 28 | }} 29 | onChange={(e) => { 30 | const text = e.target.value 31 | setValue(text) 32 | }} 33 | /> 34 | ) 35 | } 36 | ``` 37 | 38 | > `onEditCompleted` indicates when the editing is completed and switched to the normal display state 39 | 40 | Then set the column attribute `editor` 41 | 42 | ```jsx | pure 43 | const columns: Column[] = [{ 44 | name: 'id', 45 | title: 'id' 46 | },{ 47 | name: 'userName', 48 | title: 'User Name', 49 | editor: Input, 50 | }, { 51 | name: 'address', 52 | title: 'Address', 53 | editor: Input, 54 | }, { 55 | name: 'email', 56 | title: 'E-Mail', 57 | editor: Input, 58 | }, { 59 | name: 'mobilePhone', 60 | title: 'Mobile Phone', 61 | editor: Input, 62 | }] 63 | ``` 64 | 65 | In this way, you can get the corresponding saved data in `onEditorChangeSave` 66 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/editor.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 可编辑表格 8 | order: 2 9 | --- 10 | 11 | # 简单的表格 12 | 13 | 14 | 15 | 16 | 需要创建一个 `ComponentType` 的组件, 例如以下 17 | 18 | ```jsx | pure 19 | const Input = ({ style, value: tempValue, onEditCompleted }: EditorProps) => { 20 | const [value, setValue] = useState(tempValue) 21 | return ( 22 | { 28 | onEditCompleted(value) 29 | }} 30 | onChange={(e) => { 31 | const text = e.target.value 32 | setValue(text) 33 | }} 34 | /> 35 | ) 36 | } 37 | ``` 38 | 39 | > `onEditCompleted` 表示在何时进行完成编辑,并且切换到正常显示的状态 40 | 41 | 42 | 然后在设置 列的属性 `editor` 43 | 44 | ```jsx | pure 45 | const columns: Column[] = [{ 46 | name: 'id', 47 | title: 'id' 48 | },{ 49 | name: 'userName', 50 | title: 'User Name', 51 | editor: Input, 52 | }, { 53 | name: 'address', 54 | title: 'Address', 55 | editor: Input, 56 | }, { 57 | name: 'email', 58 | title: 'E-Mail', 59 | editor: Input, 60 | }, { 61 | name: 'mobilePhone', 62 | title: 'Mobile Phone', 63 | editor: Input, 64 | }] 65 | ``` 66 | 67 | 这样就可以在 `onEditorChangeSave` 中获取对应保存的数据 68 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/empty-rows.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Empty Rows 8 | order: 5 9 | --- 10 | 11 | # Empty Rows 12 | 13 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/empty-rows.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 空数据显示 8 | order: 5 9 | --- 10 | 11 | # 空数据显示 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/expandable-row.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Expandable 8 | order: 6 9 | --- 10 | 11 | # Expandable 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/expandable-row.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 可展开表格 8 | order: 6 9 | --- 10 | 11 | # 可展开表格 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/fixed-column.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Fixed Column 8 | order: 4 9 | --- 10 | 11 | # Fixed Column 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/fixed-column.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 固定列 8 | order: 4 9 | --- 10 | 11 | # 固定列 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/header-merge.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Header Merge 8 | order: 7 9 | --- 10 | 11 | # Header Merge 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/header-merge.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 列头合并 8 | order: 7 9 | --- 10 | 11 | # 列头合并 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/header-resizable.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Header Resizable 8 | order: 8 9 | --- 10 | 11 | # Header Resizable 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/header-resizable.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 列的宽度可调整 8 | order: 8 9 | --- 10 | 11 | # 列的宽度可调整 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/scroll-to.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Scroll To 8 | order: 9 9 | --- 10 | 11 | # Scroll To 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/scroll-to.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 滚动到指定位置 8 | order: 9 9 | --- 10 | 11 | # 滚动到指定位置 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/select-multiple.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Select Multiple 8 | order: 8 9 | --- 10 | 11 | # Select Multiple 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/select-multiple.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 多选 8 | order: 8 9 | --- 10 | 11 | # 表格的多选 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/simple.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Simple 8 | order: 1 9 | --- 10 | 11 | # Simple 12 | 13 | 14 | 15 | > Open the console, you can see the saved content information 16 | 17 | ## Column 18 | 19 | When setting the `column`, you need to pay attention to the two attributes `name` and `title` which are necessary fields. One is the name of the corresponding data, and the other is the hidden content of the table. 20 | 21 | > For more attribute information, see [API](/getting-started/api) description 22 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/simple.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 简单表格 8 | order: 1 9 | --- 10 | 11 | # 简单的表格 12 | 13 | 14 | 15 | > 打开控制台, 可以看保存的内容信息 16 | 17 | ## Column 说明 18 | 19 | 在设置 `columns` 的时候, 需要注意一下两个属性 `name` 和 `title` 这两个是必要字段,一个是表示对应数据的名称,一个是表格头部的内容 20 | 21 | > 更多的属性信息,见 [API](/zh-CN/getting-started/api) 说明 22 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/sort-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: Example 7 | title: Sort Data 8 | order: 1 9 | --- 10 | 11 | # Sort Data 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/example/sort-data.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: -1 5 | group: 6 | title: 例子 7 | title: 数据排序 8 | order: 1 9 | --- 10 | 11 | # 数据排序 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/getting-started/introduce.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: Docs 4 | order: 1 5 | path: /getting-started 6 | title: Introduce 7 | order: 1 8 | --- 9 | 10 | # Rc-Grid Component 11 | 12 | Rc-Grid is a component library based on React and styled-components. It is mainly a grid in enterprise-level mid- and back-end products. 13 | 14 | ## ✨ Feature 15 | 16 | - 📦 High-quality React components out of the box, and support virtual scrolling by default. 17 | - 🛡 Use TypeScript to develop, provide a complete type definition file 18 | - ⚙️ Powerful functions, can meet a variety of different needs 19 | 20 | ## Browsers support 21 | 22 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | [Electron](http://godban.github.io/browsers-support-badges/)
Electron | 23 | | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | 24 | | last 2 versions | last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions 25 | 26 | > Browsers that do not currently support IE, if you need IE, you need to find the corresponding polyfill, this is not a simple process. 27 | 28 | ## Compare to others 29 | 30 | |Description | rc-grid | ag-grid | react-data-grid | ant design | handsontable | 31 | |--------|-------- |------- |--------- |------ |----------- | 32 | |Fixed left column | ✅ | ✅ | ✅ |✅ | ✅ 33 | |Fixed right column | ✅ | ✅ | ❌ |✅ | ✅ 34 | |Columns can be dragged to change size| ✅ | ✅ | ✅ |⚠️ need rewrite header | ✅ 35 | |Column header merge | ✅ | ✅ | ❌ | ✅ | ✅ 36 | |Virtual scroll | ✅ | ✅ | ✅ |⚠️ need rewrite render | ✅ 37 | |Cell editing | ✅ | ✅ | ✅ | ⚠️ need rewrite cell | ✅ 38 | |Expandable | ✅ | ✅ | ✅ | ✅ | ❌ 39 | |Tree Grid | ✅ | ✅ | ✅ | ✅ | ❌ 40 | |Open source agreement | MIT | MIT | MIT | MIT | Business 41 | 42 | > Note: This data is for reference only, the component library may be updated with new functions added. 43 | 44 | ## Installation 45 | 46 | ### Use npm or yarn installation 47 | 48 | We recommend using npm or yarn for development. Not only can it be easily debugged in the development environment, but it can also be packaged and deployed in the production environment with confidence, and enjoy the many benefits of the entire ecosystem and tool chain. 49 | 50 | ```sh 51 | npm install @lifhcp/rc-grid --save 52 | ``` 53 | 54 | ```sh 55 | yarn add @lifhcp/rc-grid 56 | ``` 57 | 58 | ### Browser import 59 | 60 | If you want to import rc-grid in the browser, you need to compile the corresponding file with `webpack`, and then use global variables. 61 | 62 | ## Community help 63 | 64 | If you encounter problems during use, you can seek help through the following channels, and we also encourage experienced users to provide help to newcomers through the following channels. 65 | 66 | - [GitHub Discussions](https://github.com/HighPerformanceComponent/rc-grid/discussions) 67 | -------------------------------------------------------------------------------- /website/docs/getting-started/introduce.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | nav: 3 | title: 文档 4 | order: 1 5 | path: /getting-started 6 | title: 介绍 7 | order: 1 8 | --- 9 | 10 | # Rc-Grid 组件 11 | 12 | Rc-Grid 是基于 React 和 styled-components 的组件库,主要企业级中后台产品中的表格。 13 | 14 | ## ✨ 特性 15 | 16 | - 📦 开箱即用的高质量 React 组件, 并且默认支持虚拟滚动。 17 | - 🛡 使用 TypeScript 开发,提供完整的类型定义文件。 18 | - ⚙️ 组件功能丰富,支持固定列,可展开表格,表格树,拖拽列头,排序等等... 19 | 20 | 21 | ## 浏览器支持 22 | 23 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | [Electron](http://godban.github.io/browsers-support-badges/)
Electron | 24 | | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | 25 | | last 2 versions | last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions 26 | 27 | > 目前不支持 IE 的浏览器,如果需要兼容 IE 需要寻找对应的 polyfills, 这并不是一个简单的过程 28 | 29 | ## 与其他开源表格进行对比 30 | 31 | 32 | |功能描述 | rc-grid | ag-grid | react-data-grid | ant design | handsontable | 33 | |--------|-------- |------- |--------- |------ |----------- | 34 | |固定左 | ✅ | ✅ | ✅ |✅ | ✅ 35 | |右列 | ✅ | ✅ | ❌ |✅ | ✅ 36 | |列可拖拽改变大小| ✅ | ✅ | ✅ |⚠️ 需要自定义列头 | ✅ 37 | |列头合并 | ✅ | ✅ | ❌ | ✅ | ✅ 38 | |虚拟滚动 | ✅ | ✅ | ✅ |⚠️ 需要重写渲染 | ✅ 39 | |单元格编辑 | ✅ | ✅ | ✅ | ⚠️ 需要重写cell | ✅ 40 | |可展开表格 | ✅ | ✅ | ✅ | ✅ | ❌ 41 | |树形表格 | ✅ | ✅ | ✅ | ✅ | ❌ 42 | |开源协议 | MIT | MIT | MIT | MIT | 商业 43 | 44 | > 注明: 此数据仅供参照,组件库可能是随着更新,而添加新的功能 45 | 46 | ## 安装 47 | 48 | ### 使用 npm 或 yarn 安装 49 | 50 | 我们推荐使用 npm 或 yarn 的方式进行开发,不仅可在开发环境轻松调试,也可放心地在生产环境打包部署使用,享受整个生态圈和工具链带来的诸多好处。 51 | 52 | ``` 53 | npm install @lifhcp/rc-grid --save 54 | ``` 55 | 56 | ``` 57 | yarn add @lifhcp/rc-grid 58 | ``` 59 | 60 | 如果你的网络环境不佳,推荐使用 [cnpm](https://github.com/cnpm/cnpm) 。 61 | 62 | ### 浏览器引入 63 | 64 | 如果要在浏览器中引入 rc-grid 需要自己用 `webpack` 编译对应的文件,然后使用全局变量即可 65 | 66 | ### 社区互助 67 | 68 | 如果您在使用的过程中碰到问题,可以通过下面几个途径寻求帮助,同时我们也鼓励资深用户通过下面的途径给新人提供帮助。 69 | 70 | - [GitHub Discussions](https://github.com/HighPerformanceComponent/rc-grid/discussions) 71 | -------------------------------------------------------------------------------- /website/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | hero: 3 | title: Rc-Grid 4 | desc: High performance grid components out of the box 5 | actions: 6 | - text: Getting Started 7 | link: /getting-started/introduce 8 | features: 9 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/881dc458-f20b-407b-947a-95104b5ec82b/k79dm8ih_w144_h144.png 10 | title: Feature 11 | desc: Out of the box, it is easy to get started with very few configurations. 12 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/d60657df-0822-4631-9d7c-e7a869c2f21c/k79dmz3q_w126_h126.png 13 | title: High performance 14 | desc: Virtual scrolling of rows and columns improves table performance 15 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/d1ee0c6f-5aed-4a45-a507-339a4bfe076c/k7bjsocq_w144_h144.png 16 | title: Open source 17 | desc: The code is hosted in github, open source agreement MIT 18 | footer: Open-source MIT Licensed | Copyright © 2021
Powered by [HighPerformanceComponent](https://github.com/HighPerformanceComponent) 19 | --- 20 | 21 | ## Browsers support 22 | 23 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | [Electron](http://godban.github.io/browsers-support-badges/)
Electron | 24 | | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | 25 | | last 2 versions | last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions 26 | -------------------------------------------------------------------------------- /website/docs/index.zh-CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | hero: 3 | title: Rc-Grid 4 | desc: 高性能的 React 表格组件, 在不丢失表格的强大功能的情况下,并且保持高性能 5 | actions: 6 | - text: 开始使用 7 | link: /zh-CN/getting-started/introduce 8 | features: 9 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/881dc458-f20b-407b-947a-95104b5ec82b/k79dm8ih_w144_h144.png 10 | title: 功能 11 | desc: 开箱即用,在极少的配置上,能很容易的上手。 12 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/d60657df-0822-4631-9d7c-e7a869c2f21c/k79dmz3q_w126_h126.png 13 | title: 高性能 14 | desc: 行和列的虚拟滚动,提升表格的性能 15 | - icon: https://gw.alipayobjects.com/zos/bmw-prod/d1ee0c6f-5aed-4a45-a507-339a4bfe076c/k7bjsocq_w144_h144.png 16 | title: 开源 17 | desc: 代码托管在 GitHub 中,采用 MIT 协议,完全开源 18 | footer: Open-source MIT Licensed | Copyright © 2021
Powered by [HighPerformanceComponent](https://github.com/HighPerformanceComponent) 19 | --- 20 | 21 | ## 浏览器支持 22 | 23 | | [IE / Edge](http://godban.github.io/browsers-support-badges/)
Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [iOS Safari](http://godban.github.io/browsers-support-badges/)
iOS Safari | [Samsung](http://godban.github.io/browsers-support-badges/)
Samsung | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | [Electron](http://godban.github.io/browsers-support-badges/)
Electron | 24 | | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | 25 | | last 2 versions | last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions| last 2 versions 26 | -------------------------------------------------------------------------------- /website/embed/api.md: -------------------------------------------------------------------------------- 1 | 2 | # Grid 3 | 4 | |Property | Description | Type | Default 5 | |----------- |------------------ |--------- |---------- 6 | | rows | Row data information of the table |`Row[]` | - 7 | | columns | Column information | `Column[]` | - 8 | | height | Table height | `number` | 9 | | width | The width of the table | `number` | 10 | | headerRowHeight | The default height of the table header | `number` | 11 | | estimatedRowHeight | Estimate the average of the rows of the table | `number` | 12 | | estimatedColumnWidth | Estimate the average value of the columns of the table | `number` | 13 | | cacheRemoveCount | Number of entries to be removed from the cache | `number` | 14 | | defaultColumnWidth | Default column width information | `number` | 15 | | expandable | Expanded configuration information | `Expandable` | 16 | | select | Select component settings | 17 | | onEditorChangeSave | User edit triggered data | `(change: EditorChange) => void` | 18 | | onHeaderCellRender | Render the cell at the head of the table | `(param: HeaderCellRenderParam) => ReactNode[]` | 19 | | onHeaderRowRender | Render the row information of the header of the table | `(node: JSX.Element) => ReactNode` | 20 | | onHeaderResizable | Event triggered by changing the width of the table column header | `(column: Column[]) => void` | 21 | | onEmptyRowsRenderer | Render the corresponding data information when the data is empty | `() => ReactNode`| 22 | | onSort | The method to be triggered when the table is sorted | `(sortColumn: SortColumn[]) => void` | 23 | | onHeaderDrop | Event triggered by dragging the user header | `(source: Column, target: Column) => void` | 24 | | onHeaderDragOver | Allow the current header to be placed | `(event: DragEvent) => boolean` 25 | | onRowClick | Click event of table row | `(row: Row) => void` 26 | | onRowDoubleClick | Double-click event of table row | `(row: Row) => void` 27 | | onChildrenRows | Child node information of tree node | `onChildrenRows?: (row: Row) => readonly Row[]` 28 | 29 | ## Expandable 30 | 31 | |Property | Description | Type | Default 32 | |----------- |------------------ |--------- |---------- 33 | | indentSize | When displaying tree data, the width of each level of indentation, with rem as the unit | `number` | 34 | | childrenColumnName | Specify the column name of the tree structure | `string` | 35 | | isExpandable | Whether to show expandable buttons | `(row: Row) => boolean` | 36 | | expandedRowRender | The actual content of the rendered expansion| `(row: Row, style: CSSProperties) => ReactNode` | 37 | 38 | ## Column 39 | 40 | |Property | Description | Type | Default 41 | |----------- |------------------ |--------- |---------- 42 | | name | The corresponding path of the column data in the data item | `string` |- 43 | | title | Column header display text | `ReactNode` | 44 | | width | Column width | `number` | 45 | | align | Column alignment | `'left'` \| `'right'` \| `'center'` 46 | | fixed | Fixed column | `'left'` \| `'right'` | - 47 | | readonly | Whether the current column is read-only | `boolean` \| `((row: Row) => boolean)` | 48 | | sort | Does it support sorting | `boolean` | - 49 | | resizable | Whether the column can be dragged to change the size | `boolean` | - 50 | | editor | Content editor | `ComponentType` \| `null` | 51 | | isSelect | Allow selection | `(cell: Cell) => boolean` | 52 | | render | Custom cell information | `(text: EditorValue, row: Row) => ReactNode` 53 | 54 | ## Row 55 | 56 | |Property | Description | Type | Default 57 | |----------- |------------------ |--------- |---------- 58 | | key | Unique key | `string` | 59 | | height | Row height | `number` | 60 | | cells | cells information | `Cell[]` | 61 | | object | Bound JSON data | `T` | 62 | 63 | 64 | ## Cell 65 | 66 | |Property | Description | Type | Default 67 | |----------- |------------------ |--------- |---------- 68 | | name | Corresponding to the name attribute of Column | `string` | - 69 | | value | Display value | `string` | - 70 | | colSpan | merged columns | `number` | - 71 | | rowSpan | Merge rows | `number` | - 72 | | style | css style | `CellStyle` | - 73 | 74 | 75 | ## EditorChange 76 | 77 | |Property | Description | Type | Default 78 | |----------- |------------------ |--------- |---------- 79 | | row | Row data | `Row` | - 80 | | changeValue | Changed field information | `R` | - 81 | 82 | 83 | > For more detailed information, see https://github.com/HighPerformanceComponent/rc-grid/blob/canary/packages/rc-grid/src/types.ts 84 | -------------------------------------------------------------------------------- /website/embed/api.zh-CN.md: -------------------------------------------------------------------------------- 1 | 2 | # 表格属性 3 | 4 | |属性 | 说明 | 类型 | 默认值 5 | |----------- |------------------ |--------- |---------- 6 | | rows | 表格的行数据信息 |`Row[]` | - 7 | | columns | 列的信息 | `Column[]` | - 8 | | height | 表格的高度 | `number` | 9 | | width | 表格的宽度 | `number` | 10 | | headerRowHeight | 表格 header 的默认高度 | `number` | 11 | | estimatedRowHeight | 预估表格的行的平均值 | `number` | 12 | | estimatedColumnWidth | 预估表格的列的平均值 | `number` | 13 | | cacheRemoveCount | 缓存要移除的条目数量 | `number` | 14 | | defaultColumnWidth | 默认列的宽度信息 | `number` | 15 | | expandable | 展开的配置信息 | `Expandable` | 16 | | select | 选中的组件设置 | 17 | | onEditorChangeSave | 用户编辑触发的数据 | `(change: EditorChange) => void` | 18 | | onHeaderCellRender | 渲染表格头部的单元格 | `(param: HeaderCellRenderParam) => ReactNode[]` | 19 | | onHeaderRowRender | 渲染表格的头部的行信息 | `(node: JSX.Element) => ReactNode` | 20 | | onHeaderResizable | 表格列头部改变宽度触发的事件 | `(column: Column[]) => void` | 21 | | onEmptyRowsRenderer | 数据空的时候渲染对应的数据信息 | `() => ReactNode`| 22 | | onSort | 表格执行排序的时候触发的方法 | `(sortColumn: SortColumn[]) => void` | 23 | | onHeaderDrop | 拖拽用户表头触发的事件 | `(source: Column, target: Column) => void` | 24 | | onHeaderDragOver | 允许当期表头进行放置 | `(event: DragEvent) => boolean` 25 | | onRowClick | 表格行的点击事件 | `(row: Row) => void` 26 | | onRowDoubleClick | 表格行的双击事件 | `(row: Row) => void` 27 | | onChildrenRows | 树形节点的子节点信息 | `onChildrenRows?: (row: Row) => readonly Row[]` 28 | 29 | ## Expandable 可展开属性配置 30 | 31 | |属性 | 说明 | 类型 | 默认值 32 | |----------- |------------------ |--------- |---------- 33 | | indentSize | 展示树形数据时,每层缩进的宽度,以 rem 为单位 | `number` | 34 | | childrenColumnName | 指定树形结构的列名 | `string` | 35 | | isExpandable | 是否显示可展开按钮 | `(row: Row) => boolean` | 36 | | expandedRowRender | 渲染的展开的实际内容| `(row: Row, style: CSSProperties) => ReactNode` | 37 | 38 | ## Column 表格列 39 | 40 | |属性 | 说明 | 类型 | 默认值 41 | |----------- |------------------ |--------- |---------- 42 | | name | 列数据在数据项中对应的路径 | `string` |- 43 | | title | 列头显示文字 | `ReactNode` | 44 | | width | 列宽度 | `number` | 45 | | align | 对其方式 | `'left'` \| `'right'` \| `'center'` 46 | | fixed | 固定列 | `'left'` \| `'right'` | - 47 | | readonly | 当前列是否只读 | `boolean` \| `((row: Row) => boolean)` | 48 | | sort | 是否支持排序 | `boolean` | - 49 | | resizable | 列是否可以拖拽改变大小 | `boolean` | - 50 | | editor | 表格的编辑按钮 | `ComponentType` \| `null` | 51 | | isSelect | 是否允许选中 | `(cell: Cell) => boolean` | 52 | | render | 渲染表格的单元格信息 | `(text: EditorValue, row: Row) => ReactNode` 53 | 54 | ## Row 表格数据行的信息 55 | 56 | |属性 | 说明 | 类型 | 默认值 57 | |----------- |------------------ |--------- |---------- 58 | | key | 表格的唯一 key | `string` | 59 | | height | 表格行的高度 | `number` | 60 | | cells | 表格的单元格信息 | `Cell[]` | 61 | | object | 绑定的JSON 数据 | `T` | 62 | 63 | 64 | ## Cell 表格数据的单元格 65 | 66 | |属性 | 说明 | 类型 | 默认值 67 | |----------- |------------------ |--------- |---------- 68 | | name | 对应 Column 的 name 属性 | `string` | - 69 | | value | 实际显示的值信息 | `string` | - 70 | | colSpan | 合并列的数量 | `number` | - 71 | | rowSpan | 合并行的数量 | `number` | - 72 | | style | css 样式 | `CellStyle` | - 73 | 74 | 75 | ## EditorChange 属性 76 | 77 | |属性 | 说明 | 类型 | 默认值 78 | |----------- |------------------ |--------- |---------- 79 | | row | 当前行的数据 | `Row` | - 80 | | changeValue | 改变的字段信息 | `R` | - 81 | 82 | 83 | > 更多详细的信息见 https://github.com/HighPerformanceComponent/rc-grid/blob/canary/packages/rc-grid/src/types.ts 84 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "website", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "start": "dumi dev", 7 | "build": "dumi build", 8 | "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"" 9 | }, 10 | "main": "dist/index.js", 11 | "module": "dist/index.esm.js", 12 | "typings": "dist/index.d.ts", 13 | "lint-staged": { 14 | "*.{js,jsx,less,md,json}": [ 15 | "prettier --write" 16 | ], 17 | "*.ts?(x)": [ 18 | "prettier --parser=typescript --write" 19 | ] 20 | }, 21 | "devDependencies": { 22 | "dumi": "^1.0.13", 23 | "yorkie": "^2.0.0" 24 | }, 25 | "dependencies": { 26 | "@lifhcp/rc-grid": "*", 27 | "react": "^17.0.2", 28 | "react-dom": "^17.0.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /website/src/cells-merge.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Cell Merge 3 | * desc: The cells of the table can be merged 4 | * title.zh-CN: 单元格合并 5 | * desc.zh-CN: 通过设置 `rowSpan` 和 `colSpan` 来进行单元格合并 6 | */ 7 | import React from 'react' 8 | 9 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 10 | 11 | const rows: Array> = [] 12 | const columns: Array> = [] 13 | 14 | columns.push({ 15 | name: `0`, 16 | title: `字段 - 0`, 17 | // fixed: 'left', 18 | }) 19 | 20 | for (let i = 2; i < 1000; i += 1) { 21 | columns.push({ 22 | name: `${i}`, 23 | title: `字段 - ${i}`, 24 | }) 25 | } 26 | 27 | columns.push({ 28 | name: `1`, 29 | title: `字段 - 1`, 30 | // fixed: 'right', 31 | }) 32 | 33 | for (let i = 0; i < 5000; i += 1) { 34 | const cells: Array = [] 35 | 36 | for (let y = 0; y < 1000; y += 1) { 37 | if (i === 3 && y === 2) { 38 | cells.push({ 39 | name: `${y}`, 40 | value: `${i} - ${y}`, 41 | rowSpan: 2, 42 | colSpan: 2, 43 | }) 44 | } else if (i === 8 && y === 2) { 45 | cells.push({ 46 | name: `${y}`, 47 | value: `${i} - ${y}`, 48 | rowSpan: 2, 49 | }) 50 | } else { 51 | cells.push({ 52 | name: `${y}`, 53 | value: `${i} - ${y}`, 54 | }) 55 | } 56 | } 57 | rows.push({ 58 | height: 35, 59 | key: `${i}`, 60 | cells, 61 | }) 62 | } 63 | 64 | const RowDataGrid = () => ( 65 | 66 | {(width) => ( 67 | 68 | width={width} 69 | height={500} 70 | rows={rows} 71 | columns={columns} 72 | /> 73 | )} 74 | 75 | ) 76 | 77 | export default RowDataGrid 78 | -------------------------------------------------------------------------------- /website/src/dark.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Dark table 3 | * desc: Dark theme mode 4 | * title.zh-CN: 暗黑模式 5 | * desc.zh-CN: 暗黑模式的主题 6 | */ 7 | 8 | 9 | import React from 'react' 10 | import DataGrid, { Cell, Row, AutoSize, Theme } from '@lifhcp/rc-grid' 11 | import { ThemeProvider } from 'styled-components' 12 | 13 | const rows: any[] = [] 14 | 15 | for (let i=0; i < 10000; i += 1) { 16 | 17 | const cells: Cell[] = [{ 18 | name: 'id', 19 | value: `${i}`, 20 | },{ 21 | name: 'userName', 22 | value: `my name ${i}`, 23 | },{ 24 | name: 'address', 25 | value: `address ${i}`, 26 | },{ 27 | name: 'email', 28 | value: `E-Mail ${i}`, 29 | }, { 30 | name: 'mobilePhone', 31 | value: `Mobile Phone ${i}`, 32 | }] 33 | const row: Row = { 34 | height: 35, 35 | key: `key-${i}`, 36 | cells, 37 | } 38 | rows.push(row) 39 | } 40 | 41 | const Dark = () => { 42 | const columns: any[] = [{ 43 | name: 'id', 44 | title: 'id' 45 | },{ 46 | name: 'userName', 47 | title: 'User Name' 48 | }, { 49 | name: 'address', 50 | title: 'Address' 51 | }, { 52 | name: 'email', 53 | title: 'E-Mail' 54 | }, { 55 | name: 'mobilePhone', 56 | title: 'Mobile Phone' 57 | }] 58 | return ( 59 | 60 | 61 | {(width) => ( 62 | 63 | columns={columns} 64 | rows={rows} 65 | width={width} 66 | height={500} 67 | /> 68 | )} 69 | 70 | 71 | 72 | ) 73 | } 74 | 75 | export default Dark 76 | -------------------------------------------------------------------------------- /website/src/editor.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Cell editing 3 | * desc: Provide a cell-editable table, Double click to edit 4 | * title.zh-CN: 单元格编辑 5 | * desc.zh-CN: 这是一个简单的自定义单元格编辑的表格, 双击可进行表格编辑 6 | */ 7 | import React, { useState } from 'react' 8 | import DataGrid, { Cell, Row, AutoSize, EditorProps, Column } from '@lifhcp/rc-grid' 9 | 10 | 11 | const Input = ({ style, value: tempValue, onEditCompleted }: EditorProps) => { 12 | const [value, setValue] = useState(tempValue) 13 | return ( 14 | { 20 | onEditCompleted(value) 21 | }} 22 | onChange={(e) => { 23 | const text = e.target.value 24 | setValue(text) 25 | }} 26 | /> 27 | ) 28 | } 29 | 30 | 31 | const rows: any[] = [] 32 | 33 | for (let i=0; i < 10000; i += 1) { 34 | 35 | const cells: Cell[] = [{ 36 | name: 'id', 37 | value: `${i}`, 38 | },{ 39 | name: 'userName', 40 | value: `my name ${i}`, 41 | },{ 42 | name: 'address', 43 | value: `address ${i}`, 44 | },{ 45 | name: 'email', 46 | value: `E-Mail ${i}`, 47 | }, { 48 | name: 'mobilePhone', 49 | value: `Mobile Phone ${i}`, 50 | }] 51 | const row: Row = { 52 | height: 35, 53 | key: `key-${i}`, 54 | cells, 55 | } 56 | rows.push(row) 57 | } 58 | 59 | const Simple = () => { 60 | const columns: Column[] = [{ 61 | name: 'id', 62 | title: 'id' 63 | },{ 64 | name: 'userName', 65 | title: 'User Name', 66 | editor: Input, 67 | }, { 68 | name: 'address', 69 | title: 'Address', 70 | editor: Input, 71 | }, { 72 | name: 'email', 73 | title: 'E-Mail', 74 | editor: Input, 75 | }, { 76 | name: 'mobilePhone', 77 | title: 'Mobile Phone', 78 | editor: Input, 79 | }] 80 | return ( 81 | 82 | {(width) => ( 83 | 84 | columns={columns} 85 | rows={rows} 86 | width={width} 87 | height={500} 88 | onEditorChangeSave={(change) => { 89 | console.log(JSON.stringify(change)) 90 | }} 91 | /> 92 | )} 93 | 94 | 95 | ) 96 | } 97 | 98 | export default Simple 99 | -------------------------------------------------------------------------------- /website/src/empty-rows.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Empty Rows 3 | * desc: When the data is empty, you can customize the display content through `onEmptyRowsRenderer` 4 | * title.zh-CN: 空数据显示 5 | * desc.zh-CN: 在空数据的时候可以通过 `onEmptyRowsRenderer` 自定义显示内容 6 | */ 7 | import React from 'react' 8 | 9 | import DataGrid, { Row, Column, AutoSize } from '@lifhcp/rc-grid' 10 | 11 | const rows: Array> = [] 12 | const columns: Array> = [] 13 | 14 | columns.push({ 15 | name: `0`, 16 | title: `字段 - 0`, 17 | fixed: 'left', 18 | }) 19 | 20 | for (let i = 2; i < 1000; i += 1) { 21 | columns.push({ 22 | name: `${i}`, 23 | title: `字段 - ${i}`, 24 | }) 25 | } 26 | 27 | columns.push({ 28 | name: `1`, 29 | title: `字段 - 1`, 30 | fixed: 'right', 31 | }) 32 | 33 | const EmptyRows = () => ( 34 | 35 | {(width) => ( 36 | 37 | rows={rows} 38 | width={width} 39 | height={500} 40 | columns={columns} 41 | onEmptyRowsRenderer={() => ( 42 |
51 | 无任何数据 52 |
53 | )} 54 | /> 55 | )} 56 |
57 | ) 58 | 59 | export default EmptyRows; -------------------------------------------------------------------------------- /website/src/expandable-row.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Expandable 3 | * desc: Users can click expand to display the details of the bank 4 | * title.zh-CN: 可展开表格 5 | * desc.zh-CN: 用户点击展开,可以展开行的详细信息 6 | */ 7 | 8 | import React from 'react' 9 | 10 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 11 | 12 | const rows: Array> = [] 13 | const tempColumns: Array> = [] 14 | 15 | tempColumns.push({ 16 | name: `0`, 17 | title: `字段 - 0`, 18 | fixed: 'left', 19 | }) 20 | 21 | for (let i = 2; i < 20; i += 1) { 22 | tempColumns.push({ 23 | name: `${i}`, 24 | title: `字段 - ${i}`, 25 | }) 26 | } 27 | 28 | tempColumns.push({ 29 | name: `1`, 30 | title: `字段 - 1`, 31 | fixed: 'right', 32 | }) 33 | 34 | for (let i = 0; i < 500; i += 1) { 35 | const cells: Array = [] 36 | 37 | const object: any = {} 38 | for (let y = 0; y < tempColumns.length; y += 1) { 39 | object[`${y}`] = `${i} - ${y}` 40 | if (i === 3 && y === 2) { 41 | cells.push({ 42 | name: `${y}`, 43 | value: `${i} - ${y}`, 44 | style: {}, 45 | }) 46 | } else if (i === 8 && y === 2) { 47 | cells.push({ 48 | name: `${y}`, 49 | value: `${i} - ${y}`, 50 | style: {}, 51 | }) 52 | } else { 53 | cells.push({ 54 | name: `${y}`, 55 | value: `${i} - ${y}`, 56 | }) 57 | } 58 | } 59 | rows.push({ 60 | height: 35, 61 | key: `${i}`, 62 | object, 63 | cells, 64 | }) 65 | } 66 | 67 | const RowExpandableDataGrid = () => ( 68 | 69 | {(width) => ( 70 | 71 | rows={rows} 72 | width={width} 73 | height={500} 74 | columns={tempColumns} 75 | expandable={{ 76 | expandedRowRender: (row, style) => ( 77 |
78 | {' '} 79 | 这是一个展开的内容信息 {JSON.stringify(row)}{' '} 80 |
81 | ), 82 | }} 83 | /> 84 | )} 85 |
86 | ) 87 | 88 | export default RowExpandableDataGrid -------------------------------------------------------------------------------- /website/src/fixed-column.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Fixed Column 3 | * desc: You can fix the left and right columns of the table 4 | * title.zh-CN: 固定列 5 | * desc.zh-CN: 可以使用列的 `fixed` 属性来固定表格列 6 | */ 7 | 8 | import React from 'react' 9 | 10 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 11 | 12 | const rows: Array> = [] 13 | const columns: Array> = [] 14 | 15 | columns.push({ 16 | name: `0`, 17 | title: `字段 - 0`, 18 | fixed: 'left', 19 | }) 20 | 21 | for (let i = 2; i < 1000; i += 1) { 22 | columns.push({ 23 | name: `${i}`, 24 | title: `字段 - ${i}`, 25 | }) 26 | } 27 | 28 | columns.push({ 29 | name: `1`, 30 | title: `字段 - 1`, 31 | fixed: 'right', 32 | }) 33 | 34 | for (let i = 0; i < 5000; i += 1) { 35 | const cells: Array = [] 36 | 37 | for (let y = 0; y < 1000; y += 1) { 38 | if (i === 3 && y === 2) { 39 | cells.push({ 40 | name: `${y}`, 41 | value: `${i} - ${y}`, 42 | // rowSpan: 2, 43 | // colSpan: 2, 44 | }) 45 | } else if (i === 8 && y === 2) { 46 | cells.push({ 47 | name: `${y}`, 48 | value: `${i} - ${y}`, 49 | // rowSpan: 2, 50 | }) 51 | } else { 52 | cells.push({ 53 | name: `${y}`, 54 | value: `${i} - ${y}`, 55 | }) 56 | } 57 | } 58 | rows.push({ 59 | height: 35, 60 | key: `${i}`, 61 | cells, 62 | }) 63 | } 64 | 65 | const FixedColumn = () => ( 66 | 67 | {(width) => ( 68 | 69 | width={width} 70 | height={500} 71 | rows={rows} 72 | columns={columns} 73 | /> 74 | )} 75 | 76 | ) 77 | 78 | export default FixedColumn 79 | -------------------------------------------------------------------------------- /website/src/header-merge.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Header Merge 3 | * desc: You can customize the merge column header information 4 | * title.zh-CN: 列头合并 5 | * desc.zh-CN: 可以自定义合并列头信息 6 | */ 7 | 8 | import React from 'react' 9 | 10 | import styled from 'styled-components' 11 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 12 | 13 | const rows: Array> = [] 14 | const columns: Array> = [ 15 | { 16 | name: `0`, 17 | title: `姓名`, 18 | width: 120, 19 | }, 20 | { 21 | name: `1`, 22 | title: `年龄`, 23 | width: 120, 24 | }, 25 | { 26 | name: `2`, 27 | title: `身份证号`, 28 | width: 120, 29 | }, 30 | { 31 | name: `3`, 32 | title: `家庭地址`, 33 | width: 120, 34 | }, 35 | { 36 | name: `4`, 37 | title: `家庭电话号码`, 38 | width: 120, 39 | }, 40 | { 41 | name: `5`, 42 | title: `家庭人员数量`, 43 | width: 120, 44 | }, 45 | { 46 | name: `6`, 47 | title: `公司地址`, 48 | }, 49 | ] 50 | 51 | const GridHeaderCell = styled.div` 52 | display: inline-block; 53 | position: absolute; 54 | border-right: 1px solid #ddd; 55 | border-bottom: 1px solid #ddd; 56 | box-sizing: border-box; 57 | background-color: hsl(0deg 0% 97.5%); 58 | /** 优化 webkit 中的渲染效率 */ 59 | content-visibility: auto; 60 | padding: 0px 8px; 61 | white-space: nowrap; 62 | text-overflow: ellipsis; 63 | ` 64 | 65 | for (let i = 0; i < 5000; i += 1) { 66 | const cells: Array = [] 67 | 68 | for (let y = 0; y < columns.length; y += 1) { 69 | if (i === 3 && y === 2) { 70 | cells.push({ 71 | name: `${y}`, 72 | value: `${i} - ${y}`, 73 | style: {}, 74 | }) 75 | } else if (i === 8 && y === 2) { 76 | cells.push({ 77 | name: `${y}`, 78 | value: `${i} - ${y}`, 79 | style: {}, 80 | }) 81 | } else { 82 | cells.push({ 83 | name: `${y}`, 84 | value: `${i} - ${y}`, 85 | }) 86 | } 87 | } 88 | rows.push({ 89 | height: 35, 90 | key: `${i}`, 91 | cells, 92 | }) 93 | } 94 | 95 | const HeaderMergeDataGrid = () => ( 96 | 97 | {(width) => ( 98 | 99 | rows={rows} 100 | width={width} 101 | height={500} 102 | columns={columns} 103 | onHeaderRowRender={(node) => { 104 | const { styled: tempStyled, ...restProps } = node.props 105 | return React.cloneElement(node, { 106 | ...restProps, 107 | styled: { 108 | ...tempStyled, 109 | top: 35, 110 | }, 111 | key: node.key, 112 | }) 113 | }} 114 | onHeaderCellRender={({ headerCell, index }) => { 115 | const { 116 | styled: tempStyled, 117 | ...restProps 118 | } = headerCell.props 119 | if (index === 0) { 120 | return [ 121 | 135 | 人员资料 136 | , 137 | headerCell, 138 | ] 139 | } 140 | 141 | if (index > 5) { 142 | return [ 143 | React.cloneElement(headerCell, { 144 | ...restProps, 145 | styled: { 146 | ...tempStyled, 147 | top: -35, 148 | height: 35 * 2, 149 | }, 150 | }), 151 | ] 152 | } 153 | return [ 154 | React.cloneElement(headerCell, { 155 | ...restProps, 156 | styled: { 157 | ...tempStyled, 158 | }, 159 | }), 160 | ] 161 | }} 162 | /> 163 | )} 164 | 165 | ) 166 | 167 | export default HeaderMergeDataGrid 168 | -------------------------------------------------------------------------------- /website/src/header-resizable.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Header Resizable 3 | * desc: The width information of table columns can be changed 4 | * title.zh-CN: 列的宽度可调整 5 | * desc.zh-CN: 可改变表格列的宽度信息 6 | */ 7 | 8 | import React, { useState } from 'react' 9 | 10 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 11 | 12 | const rows: Array> = [] 13 | const tempColumns: Array> = [] 14 | 15 | tempColumns.push({ 16 | name: `0`, 17 | title: `字段 - 0`, 18 | fixed: 'left', 19 | }) 20 | 21 | for (let i = 2; i < 20; i += 1) { 22 | tempColumns.push({ 23 | name: `${i}`, 24 | title: `字段 - ${i}`, 25 | resizable: true, 26 | sort: true, 27 | }) 28 | } 29 | 30 | tempColumns.push({ 31 | name: `1`, 32 | title: `字段 - 1`, 33 | fixed: 'right', 34 | }) 35 | 36 | for (let i = 0; i < 500; i += 1) { 37 | const cells: Array = [] 38 | 39 | const object: any = {} 40 | for (let y = 0; y < tempColumns.length; y += 1) { 41 | object[`${y}`] = `${i} - ${y}` 42 | if (i === 3 && y === 2) { 43 | cells.push({ 44 | name: `${y}`, 45 | value: `${i} - ${y}`, 46 | style: {}, 47 | }) 48 | } else if (i === 8 && y === 2) { 49 | cells.push({ 50 | name: `${y}`, 51 | value: `${i} - ${y}`, 52 | style: {}, 53 | }) 54 | } else { 55 | cells.push({ 56 | name: `${y}`, 57 | value: `${i} - ${y}`, 58 | }) 59 | } 60 | } 61 | rows.push({ 62 | height: 35, 63 | key: `${i}`, 64 | object, 65 | cells, 66 | }) 67 | } 68 | 69 | const HeaderResizable = () => { 70 | const [columns, setColumns] = useState>>(tempColumns) 71 | return ( 72 | 73 | {(width) => ( 74 | 75 | rows={rows} 76 | width={width} 77 | height={500} 78 | columns={columns} 79 | onHeaderResizable={(changeColumns) => { 80 | setColumns(changeColumns) 81 | }} 82 | /> 83 | )} 84 | 85 | ) 86 | } 87 | 88 | export default HeaderResizable -------------------------------------------------------------------------------- /website/src/scroll-to.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Scroll To 3 | * desc: Scroll to the specified location information 4 | * title.zh-CN: 滚动到指定位置 5 | * desc.zh-CN: 可通过 `scrollToRow` 或则 `scrollToColumn` 来进行使用 6 | */ 7 | 8 | import React, { useRef } from 'react' 9 | import DataGrid, { Cell, Row, AutoSize, GridHandle } from '@lifhcp/rc-grid' 10 | 11 | const rows: any[] = [] 12 | 13 | for (let i=0; i < 10000; i += 1) { 14 | 15 | const cells: Cell[] = [{ 16 | name: 'id', 17 | value: `${i}`, 18 | },{ 19 | name: 'userName', 20 | value: `my name ${i}`, 21 | },{ 22 | name: 'address', 23 | value: `address ${i}`, 24 | },{ 25 | name: 'email', 26 | value: `E-Mail ${i}`, 27 | }, { 28 | name: 'mobilePhone', 29 | value: `Mobile Phone ${i}`, 30 | }] 31 | const row: Row = { 32 | height: 35, 33 | key: `key-${i}`, 34 | cells, 35 | } 36 | rows.push(row) 37 | } 38 | 39 | const ScrollTo = () => { 40 | const columns: any[] = [{ 41 | name: 'id', 42 | title: 'id' 43 | },{ 44 | name: 'userName', 45 | title: 'User Name' 46 | }, { 47 | name: 'address', 48 | title: 'Address' 49 | }, { 50 | name: 'email', 51 | title: 'E-Mail' 52 | }, { 53 | name: 'mobilePhone', 54 | title: 'Mobile Phone' 55 | }] 56 | const grid = useRef(null) 57 | 58 | const rowIndex = useRef(12) 59 | return ( 60 | <> 61 | { 65 | rowIndex.current = e.target.valueAsNumber 66 | }} 67 | /> 68 | 75 | 76 | {(width) => ( 77 | 78 | grid={grid} 79 | columns={columns} 80 | rows={rows} 81 | width={width} 82 | height={500} 83 | /> 84 | )} 85 | 86 | 87 | ) 88 | } 89 | 90 | export default ScrollTo 91 | -------------------------------------------------------------------------------- /website/src/select-multiple.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Select Multiple 3 | * desc: Select items can be set by configuring the properties of 'select' 4 | * title.zh-CN: 多选 5 | * desc.zh-CN: 可通过配置 `select` 的属性来设置选择项 6 | */ 7 | 8 | 9 | import React, { Key, useState } from 'react' 10 | 11 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 12 | 13 | const rows: Array> = [] 14 | const tempColumns: Array> = [] 15 | 16 | tempColumns.push({ 17 | name: `0`, 18 | title: `字段 - 0`, 19 | }) 20 | 21 | for (let i = 2; i < 20; i += 1) { 22 | tempColumns.push({ 23 | name: `${i}`, 24 | title: `字段 - ${i}`, 25 | sort: true, 26 | }) 27 | } 28 | 29 | tempColumns.push({ 30 | name: `1`, 31 | title: `字段 - 1`, 32 | }) 33 | 34 | for (let i = 0; i < 500; i += 1) { 35 | const cells: Array = [] 36 | 37 | const object: any = {} 38 | for (let y = 0; y < tempColumns.length; y += 1) { 39 | object[`${y}`] = `${i} - ${y}` 40 | if (i === 3 && y === 2) { 41 | cells.push({ 42 | name: `${y}`, 43 | value: `${i} - ${y}`, 44 | style: {}, 45 | }) 46 | } else if (i === 8 && y === 2) { 47 | cells.push({ 48 | name: `${y}`, 49 | value: `${i} - ${y}`, 50 | style: {}, 51 | }) 52 | } else { 53 | cells.push({ 54 | name: `${y}`, 55 | value: `${i} - ${y}`, 56 | }) 57 | } 58 | } 59 | rows.push({ 60 | height: 35, 61 | key: `${i}`, 62 | object, 63 | cells, 64 | }) 65 | } 66 | 67 | const SelectMultiple = () => { 68 | const [select, setSelect] = useState([]) 69 | return ( 70 | 71 | {(width) => ( 72 | 73 | rows={rows} 74 | width={width} 75 | height={500} 76 | columns={tempColumns} 77 | selectedRows={select} 78 | onChangeSelectedRows={setSelect} 79 | select={{ 80 | mode: 'multiple', 81 | component: (param) => { 82 | const { onSelected, row, selected, mode } = param 83 | return ( 84 | { 91 | onSelected(row, mode) 92 | }} 93 | /> 94 | ) 95 | } 96 | }} 97 | /> 98 | )} 99 | 100 | ) 101 | } 102 | 103 | export default SelectMultiple -------------------------------------------------------------------------------- /website/src/simple.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Basic table 3 | * desc: This is the basic simple table 4 | * title.zh-CN: 简单表格 5 | * desc.zh-CN: 这是一个简单的表格,展示如何加载数据,以及如何设置列 6 | */ 7 | 8 | 9 | import React from 'react' 10 | import DataGrid, { Cell, Row, AutoSize } from '@lifhcp/rc-grid' 11 | 12 | 13 | const rows: any[] = [] 14 | 15 | for (let i=0; i < 10000; i += 1) { 16 | 17 | const cells: Cell[] = [{ 18 | name: 'id', 19 | value: `${i}`, 20 | },{ 21 | name: 'userName', 22 | value: `my name ${i}`, 23 | },{ 24 | name: 'address', 25 | value: `address ${i}`, 26 | },{ 27 | name: 'email', 28 | value: `E-Mail ${i}`, 29 | }, { 30 | name: 'mobilePhone', 31 | value: `Mobile Phone ${i}`, 32 | }] 33 | const row: Row = { 34 | height: 35, 35 | key: `key-${i}`, 36 | cells, 37 | } 38 | rows.push(row) 39 | } 40 | 41 | const Simple = () => { 42 | const columns: any[] = [{ 43 | name: 'id', 44 | title: 'id' 45 | },{ 46 | name: 'userName', 47 | title: 'User Name' 48 | }, { 49 | name: 'address', 50 | title: 'Address' 51 | }, { 52 | name: 'email', 53 | title: 'E-Mail' 54 | }, { 55 | name: 'mobilePhone', 56 | title: 'Mobile Phone' 57 | }] 58 | return ( 59 | 60 | {(width) => ( 61 | 62 | columns={columns} 63 | rows={rows} 64 | width={width} 65 | height={500} 66 | /> 67 | )} 68 | 69 | 70 | ) 71 | } 72 | 73 | export default Simple 74 | -------------------------------------------------------------------------------- /website/src/sort-data.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * title: Sort Data 3 | * desc: Click on the column header to sort the data 4 | * title.zh-CN: 数据排序 5 | * desc.zh-CN: 点击列头,可对数据进行排序 6 | */ 7 | 8 | 9 | import React, { useRef, useState } from 'react' 10 | 11 | import produce from 'immer' 12 | 13 | import DataGrid, { Row, Column, Cell, AutoSize } from '@lifhcp/rc-grid' 14 | 15 | const rows: Array> = [] 16 | const tempColumns: Array> = [] 17 | 18 | tempColumns.push({ 19 | name: `0`, 20 | title: `字段 - 0`, 21 | fixed: 'left', 22 | }) 23 | 24 | for (let i = 2; i < 20; i += 1) { 25 | tempColumns.push({ 26 | name: `${i}`, 27 | title: `字段 - ${i}`, 28 | sort: true, 29 | }) 30 | } 31 | 32 | tempColumns.push({ 33 | name: `1`, 34 | title: `字段 - 1`, 35 | fixed: 'right', 36 | }) 37 | 38 | for (let i = 0; i < 500; i += 1) { 39 | const cells: Array = [] 40 | 41 | const object: any = {} 42 | for (let y = 0; y < tempColumns.length; y += 1) { 43 | object[`${y}`] = `${i} - ${y}` 44 | if (i === 3 && y === 2) { 45 | cells.push({ 46 | name: `${y}`, 47 | value: `${i} - ${y}`, 48 | style: {}, 49 | }) 50 | } else if (i === 8 && y === 2) { 51 | cells.push({ 52 | name: `${y}`, 53 | value: `${i} - ${y}`, 54 | style: {}, 55 | }) 56 | } else { 57 | cells.push({ 58 | name: `${y}`, 59 | value: `${i} - ${y}`, 60 | }) 61 | } 62 | } 63 | rows.push({ 64 | height: 35, 65 | key: `${i}`, 66 | object, 67 | cells, 68 | }) 69 | } 70 | 71 | const SortData = () => { 72 | const oldData = useRef[]>(produce(rows, () => {})) 73 | const [datas, setDatas] = useState[]>(produce(rows, () => {})) 74 | return ( 75 | 76 | {(width) => ( 77 | 78 | rows={datas} 79 | width={width} 80 | height={500} 81 | columns={tempColumns} 82 | onSort={(sort) => { 83 | if (sort.length === 0) { 84 | setDatas(oldData.current) 85 | } 86 | if (sort.length > 0) { 87 | const { direction } = sort[0] 88 | const { columnKey } = sort[0] 89 | const rowsData = produce(datas, (newData) => { 90 | newData.sort((a, b) => { 91 | const aData: string = a.object[columnKey] 92 | const bData: string = b.object[columnKey] 93 | 94 | if (direction === 'ASC') { 95 | if ( 96 | aData.toUpperCase() > 97 | bData.toUpperCase() 98 | ) { 99 | return 1 100 | } 101 | return -1 102 | } 103 | if (direction === 'DESC') { 104 | if ( 105 | aData.toUpperCase() < 106 | bData.toUpperCase() 107 | ) { 108 | return 1 109 | } 110 | } 111 | return -1 112 | }) 113 | }) 114 | setDatas(rowsData) 115 | } 116 | }} 117 | /> 118 | )} 119 | 120 | ) 121 | } 122 | 123 | 124 | export default SortData 125 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "baseUrl": "./", 10 | "strict": true, 11 | "paths": { 12 | "@/*": ["src/*"], 13 | "@@/*": ["src/.umi/*"] 14 | }, 15 | "allowSyntheticDefaultImports": true 16 | }, 17 | "exclude": [ 18 | "node_modules", 19 | "lib", 20 | "es", 21 | "dist", 22 | "typings", 23 | "**/__test__", 24 | "test", 25 | "docs", 26 | "tests" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /website/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | --------------------------------------------------------------------------------