├── .eslintrc ├── .gitignore ├── .npmignore ├── .stylintrc ├── .travis.yml ├── LICENSE ├── README.md ├── babel.config.js ├── docs ├── 0b39d1e64af23fe5437238402d08496a.svg ├── 35071d00819547a959ef3450c129d77e.eot ├── 37f4597594857b017901209aae0a60e1.svg ├── 3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf ├── 7942d80077eec2eb49ed4379fc389d70.svg ├── 81436770636b45508203b3022075ae73.ttf ├── 8854c448a7903a7159ef7743634bae8c.svg ├── 8a53d21a4d9aa1aac2bf15093bd748c4.woff ├── a61e4252663ca88ab29e9ae288b91ef1.svg ├── af39e41be700d6148c61a6c1ffc84215.svg ├── build │ └── bundle.05fa11ab.js ├── bundle.js ├── bundle.js.map ├── d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff ├── d9c6d360d27eac625da0405245ec9f0d.eot ├── index.html └── main.css ├── package.json ├── setupTests.js ├── src ├── Loader.jsx ├── Table.jsx ├── TableBody.jsx ├── TableCell.jsx ├── TableHeader.jsx ├── TableHeaderCell.jsx ├── TableRow.jsx ├── TableTemplate.jsx ├── context.js └── index.js ├── styleguide.config.js ├── styleguide ├── components │ ├── FormGroup.jsx │ ├── StyleGuideRenderer.jsx │ ├── Text.jsx │ └── Wrapper.jsx ├── examples │ ├── 10000Rows.md │ ├── BaseTable.md │ ├── DynamicHeader.md │ ├── Expand.jsx │ ├── Expand.md │ ├── FixedColumns.jsx │ ├── FixedColumns.md │ ├── FixedHeader.md │ ├── Hoverable.md │ ├── LoaderCustom.md │ ├── LoaderDefault.md │ ├── Minimalism.md │ ├── NoDataCustom.md │ ├── NoDataCustomText.md │ ├── NoDataDefault.md │ ├── NoHeader.md │ ├── Pagination.md │ ├── README.md │ ├── Selection.jsx │ ├── Selection.md │ └── Sortable.md ├── setup.js └── styles.css ├── test └── index.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "trendmicro", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | yarn.lock 5 | /.nyc_output 6 | /coverage 7 | /lib 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | /.nyc_output 3 | /coverage 4 | -------------------------------------------------------------------------------- /.stylintrc: -------------------------------------------------------------------------------- 1 | // 2 | // https://github.com/rossPatton/stylint 3 | // 4 | { 5 | "blocks": false, 6 | "brackets": "always", 7 | "colons": "always", 8 | "colors": false, 9 | "commaSpace": "always", 10 | "commentSpace": false, 11 | "cssLiteral": "never", 12 | "depthLimit": false, 13 | "duplicates": false, 14 | "efficient": "always", 15 | "extendPref": false, 16 | "globalDupe": false, 17 | "indentPref": false, 18 | "leadingZero": "never", 19 | "maxErrors": false, 20 | "maxWarnings": false, 21 | "mixed": false, 22 | "namingConvention": false, 23 | "namingConventionStrict": false, 24 | "none": "never", 25 | "noImportant": true, 26 | "parenSpace": false, 27 | "placeholders": "always", 28 | "prefixVarsWithDollar": "always", 29 | "quotePref": false, 30 | "semicolons": "always", 31 | "sortOrder": false, 32 | "stackedProperties": "never", 33 | "trailingWhitespace": "never", 34 | "universal": false, 35 | "valid": true, 36 | "zeroUnits": "never", 37 | "zIndexNormalize": false 38 | } 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | group: edge 4 | 5 | language: node_js 6 | 7 | os: 8 | - linux 9 | 10 | node_js: 11 | - '8' 12 | - '10' 13 | 14 | before_install: 15 | - npm install -g npm 16 | - npm --version 17 | 18 | after_success: 19 | - npm run coveralls 20 | - npm run coverage-clean 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2019 Trend Micro Inc. 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-table [![build status](https://travis-ci.org/trendmicro-frontend/react-table.svg?branch=master)](https://travis-ci.org/trendmicro-frontend/react-table) [![Coverage Status](https://coveralls.io/repos/github/trendmicro-frontend/react-table/badge.svg?branch=master)](https://coveralls.io/github/trendmicro-frontend/react-table?branch=master) 2 | 3 | [![NPM](https://nodei.co/npm/@trendmicro/react-table.png?downloads=true&stars=true)](https://nodei.co/npm/@trendmicro/react-table/) 4 | 5 | React Table 6 | 7 | Demo: https://trendmicro-frontend.github.io/react-table 8 | 9 | ## Version 1.x is no longer maintained by 2019/12/06 10 | [Friendly reminder] Please migrate to 2+ asap. 11 | 12 | ## Installation 13 | 14 | 1. Install the latest version of [react](https://github.com/facebook/react) and [react-table](https://github.com/trendmicro-frontend/react-table): 15 | 16 | ``` 17 | npm install --save react @trendmicro/react-table @trendmicro/react-paginations 18 | ``` 19 | 20 | 2. At this point you can import `@trendmicro/react-table` and its styles in your application as follows: 21 | 22 | ```js 23 | import TableTemplate, { TableWrapper, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '@trendmicro/react-table'; 24 | ``` 25 | 26 | ## Usage 27 | 28 | ### Table Template 29 | 30 | ```js 31 | 38 | ``` 39 | 40 | ### Custom render 41 | 42 | ```js 43 | 49 | {({ cells, data, loader, emptyBody, tableWidth }) => { 50 | return ( 51 | 52 | 53 | 54 | { 55 | cells.map((cell, index) => { 56 | const key = `table_header_cell_${index}`; 57 | const { 58 | title, 59 | width: cellWidth, 60 | } = cell; 61 | return ( 62 | 66 | { title } 67 | 68 | ); 69 | }) 70 | } 71 | 72 | 73 | 74 | 79 | { 80 | data.map((row, index) => { 81 | const rowKey = `table_row${index}`; 82 | return ( 83 | 84 | { 85 | cells.map((cell, index) => { 86 | const key = `${rowKey}_cell${index}`; 87 | const cellValue = _get(row, cell.dataKey); 88 | return ( 89 | 93 | { typeof cell.render === 'function' ? cell.render(cellValue, row, index) : cellValue } 94 | 95 | ); 96 | }) 97 | } 98 | 99 | ); 100 | }) 101 | } 102 | 103 | 104 | 105 | ); 106 | }} 107 | 108 | ``` 109 | 110 | ## API 111 | 112 | ### Properties 113 | 114 | #### TableWrapper 115 | Name | Type | Default | Description 116 | :--- | :--- | :------ | :---------- 117 | minimalist | Boolean | false | Specify whether the table should not be bordered. 118 | columns | Object[] | [] | The columns config of table, see Column below for details. 119 | data | Object[] | [] | Data record array to be rendered. 120 | emptyRender | Function | () => { return 'No Data'; } | Empty content render function. 121 | emptyText | String | 'No Data' | The text when data is null. 122 | height | Number | | The height of the table. 123 | loading | Boolean | false | Whether table is loading. 124 | loaderRender | Function | | Loading content render function. 125 | width | Number(required) | | The width of the table. 126 | 127 | #### TableHeaderCell 128 | Name | Type | Default | Description 129 | :--- | :--- | :------ | :---------- 130 | width | Number(required) | | The width of the table. 131 | 132 | #### TableCell 133 | Name | Type | Default | Description 134 | :--- | :--- | :------ | :---------- 135 | width | Number(required) | | The width of the table. 136 | 137 | 138 | #### TableTemplate 139 | 140 | Name | Type | Default | Description 141 | :--- | :--- | :------ | :---------- 142 | minimalist | Boolean | false | Specify whether the table should not be bordered. 143 | columns | Object[] | [] | The columns config of table, see Column below for details. 144 | data | Object[] | [] | Data record array to be rendered. 145 | emptyRender | Function | () => { return 'No Data'; } | Empty content render function. 146 | emptyText | String | 'No Data' | The text when data is null. 147 | height | Number | | The height of the table. 148 | hideHeader | Boolean | false | Whether table head is hiden. 149 | hoverable | Boolean | false | Whether use row hover style. 150 | loading | Boolean | false | Whether table is loading. 151 | loaderRender | Function | | Loading content render function. 152 | useFixedHeader | Boolean | false | Whether table head is fixed. 153 | width | Number(required) | | The width of the table. 154 | 155 | #### Column 156 | 157 | Name | Type | Default | Description 158 | :--- | :----- | :------ | :---------- 159 | title | React Node or Function(): React Node | | Title of this column. 160 | dataKey | String | | Display field of the data record. 161 | width | String or Number | 150 | Width of the specific proportion calculation according to the width of the columns. 162 | render | Function(value, record, rowIndex) | | The render function of cell, has two params: the text of this cell, the record of this row, it's return a react node. 163 | 164 | ## License 165 | 166 | MIT 167 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: '@trendmicro/babel-config', 3 | presets: [ 4 | '@babel/preset-env', 5 | '@babel/preset-react' 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /docs/35071d00819547a959ef3450c129d77e.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/35071d00819547a959ef3450c129d77e.eot -------------------------------------------------------------------------------- /docs/37f4597594857b017901209aae0a60e1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf -------------------------------------------------------------------------------- /docs/7942d80077eec2eb49ed4379fc389d70.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /docs/81436770636b45508203b3022075ae73.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/81436770636b45508203b3022075ae73.ttf -------------------------------------------------------------------------------- /docs/8854c448a7903a7159ef7743634bae8c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff -------------------------------------------------------------------------------- /docs/a61e4252663ca88ab29e9ae288b91ef1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /docs/d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/d6c7e9d3e5adb7a5261c5ad9f7d3caaa.woff -------------------------------------------------------------------------------- /docs/d9c6d360d27eac625da0405245ec9f0d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/d9c6d360d27eac625da0405245ec9f0d.eot -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | React Table v2.0.2
-------------------------------------------------------------------------------- /docs/main.css: -------------------------------------------------------------------------------- 1 | .table-wrapper---1t2D3 { 2 | box-sizing: border-box; 3 | line-height: 20px; 4 | display: inline-block; 5 | position: relative; 6 | } 7 | .table-wrapper---1t2D3 *, 8 | .table-wrapper---1t2D3 *:before, 9 | .table-wrapper---1t2D3 *:after { 10 | box-sizing: inherit; 11 | } 12 | .table---2aBqd { 13 | display: flex; 14 | flex-direction: column; 15 | } 16 | .table---2aBqd .thead---1v6TQ { 17 | flex: 0 0 auto; 18 | overflow: hidden; 19 | } 20 | .table---2aBqd .thead---1v6TQ .tr---3XEXR { 21 | display: inline-flex; 22 | } 23 | .table---2aBqd .thead---1v6TQ .tr---3XEXR .th---2-0kV { 24 | padding: 8px 12px; 25 | flex: 1 0 auto; 26 | color: #777; 27 | font-weight: bold; 28 | } 29 | .table---2aBqd .tbody---3q9RK { 30 | flex: 1 1 auto; 31 | } 32 | .table---2aBqd .tbody---3q9RK .tr---3XEXR { 33 | display: inline-flex; 34 | } 35 | .table---2aBqd .tbody---3q9RK .tr---3XEXR .td---EVNU5 { 36 | padding: 8px 12px; 37 | flex: 1 0 auto; 38 | } 39 | .table---2aBqd .tbody---3q9RK .tr---3XEXR:last-child + .tr-expand---sEKPQ { 40 | border-top: 1px solid #ddd; 41 | border-bottom: none; 42 | } 43 | .tr-expand---sEKPQ { 44 | border-bottom: 1px solid #ddd; 45 | white-space: normal; 46 | } 47 | .table-bordered---1cbV4.table-wrapper---1t2D3 { 48 | border: 1px solid #ddd; 49 | } 50 | .table-bordered---1cbV4 .table---2aBqd .thead---1v6TQ .tr---3XEXR .th---2-0kV { 51 | background-color: #eee; 52 | border: 1px solid #ddd; 53 | border-width: 0 1px 2px 0; 54 | border-bottom-color: #ccc; 55 | } 56 | .table-bordered---1cbV4 .table---2aBqd .thead---1v6TQ .tr---3XEXR .th---2-0kV:first-child { 57 | border-left-width: 0; 58 | } 59 | .table-bordered---1cbV4 .table---2aBqd .thead---1v6TQ .tr---3XEXR .th---2-0kV:last-of-type { 60 | border-right-width: 0; 61 | } 62 | .table-bordered---1cbV4 .table---2aBqd .tbody---3q9RK .tr---3XEXR .td---EVNU5 { 63 | border: 1px solid #ddd; 64 | border-width: 0 1px 1px 0; 65 | } 66 | .table-bordered---1cbV4 .table---2aBqd .tbody---3q9RK .tr---3XEXR .td---EVNU5:first-child { 67 | border-left-width: 0; 68 | } 69 | .table-bordered---1cbV4 .table---2aBqd .tbody---3q9RK .tr---3XEXR .td---EVNU5:last-of-type { 70 | border-right-width: 0; 71 | } 72 | .table-bordered---1cbV4 .table---2aBqd .tbody---3q9RK .tr---3XEXR:last-of-type .td---EVNU5 { 73 | border-bottom-width: 0; 74 | } 75 | .table-hover---3yCb6 .table---2aBqd .tbody---3q9RK .tr---3XEXR:hover { 76 | background-color: #e6f4fc; 77 | } 78 | .table-minimalism---1Tk6v.table-wrapper---1t2D3 { 79 | border: 0; 80 | } 81 | .table-minimalism---1Tk6v .table---2aBqd .thead---1v6TQ .tr---3XEXR .th---2-0kV { 82 | border-bottom: 2px solid #ccc; 83 | } 84 | .table-minimalism---1Tk6v .table---2aBqd .tbody---3q9RK .tr---3XEXR .td---EVNU5 { 85 | border-bottom: 1px solid #ddd; 86 | } 87 | .table-no-data---2nuKv .table---2aBqd .tbody---3q9RK .table-placeholder---3xuV7 { 88 | text-align: center; 89 | padding: 44px 12px; 90 | color: #999; 91 | } 92 | .table-no-data-loader---35BPN { 93 | height: 108px; 94 | } 95 | .loader-overlay---IQP-L { 96 | background-color: rgba(255,255,255,0.8); 97 | cursor: wait; 98 | position: absolute; 99 | top: 0; 100 | bottom: 0; 101 | left: 0; 102 | right: 0; 103 | } 104 | .loader-overlay---IQP-L .loader---3Kv7h, 105 | .loader-overlay---IQP-L .loader-small---37wpB, 106 | .loader-overlay---IQP-L .loader-large---2W2Dw { 107 | position: absolute; 108 | top: 50%; 109 | left: 50%; 110 | } 111 | .loader-overlay---IQP-L .loader---3Kv7h.loader-large---2W2Dw { 112 | margin-top: -28px; 113 | margin-left: -28px; 114 | } 115 | .loader-overlay---IQP-L .loader---3Kv7h.loader-small---37wpB { 116 | margin-top: -8px; 117 | margin-left: -8px; 118 | } 119 | .loader-overlay---IQP-L .loader---3Kv7h { 120 | margin-top: -16px; 121 | margin-left: -16px; 122 | } 123 | .table---2aBqd + .loader-overlay---IQP-L { 124 | top: 38px; 125 | } 126 | .loader-overlay---IQP-L.no-header---3oIYg { 127 | top: 0; 128 | } 129 | .loader---3Kv7h { 130 | display: inline-block; 131 | margin: 0 auto; 132 | position: relative; 133 | text-indent: -9999em; 134 | vertical-align: top; 135 | border: 2px solid rgba(0,0,0,0.2); 136 | border-left-color: rgba(0,0,0,0.8); 137 | transform: translateZ(0); 138 | animation: spinner---2cR6i 1s infinite linear; 139 | border-radius: 50%; 140 | width: 32px; 141 | height: 32px; 142 | } 143 | .loader-large---2W2Dw { 144 | width: 56px; 145 | height: 56px; 146 | } 147 | .loader-small---37wpB { 148 | width: 16px; 149 | height: 16px; 150 | } 151 | @-moz-keyframes spinner---2cR6i { 152 | 0% { 153 | transform: rotate(0deg); 154 | } 155 | 100% { 156 | transform: rotate(360deg); 157 | } 158 | } 159 | @-webkit-keyframes spinner---2cR6i { 160 | 0% { 161 | transform: rotate(0deg); 162 | } 163 | 100% { 164 | transform: rotate(360deg); 165 | } 166 | } 167 | @-o-keyframes spinner---2cR6i { 168 | 0% { 169 | transform: rotate(0deg); 170 | } 171 | 100% { 172 | transform: rotate(360deg); 173 | } 174 | } 175 | @keyframes spinner---2cR6i { 176 | 0% { 177 | transform: rotate(0deg); 178 | } 179 | 100% { 180 | transform: rotate(360deg); 181 | } 182 | } 183 | 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@trendmicro/react-table", 3 | "version": "2.0.2", 4 | "description": "Trend Micro Components: React Table", 5 | "main": "lib/index.js", 6 | "files": [ 7 | "dist", 8 | "lib" 9 | ], 10 | "scripts": { 11 | "prepare": "npm run lint && npm test && npm run clean && npm run build && npm run styleguide:build", 12 | "build": "webpack-cli", 13 | "clean": "rm -f {lib,dist}/*", 14 | "demo": "http-server -p 8000 docs/", 15 | "lint": "npm run eslint && npm run stylint", 16 | "eslint": "eslint --ext .js --ext .jsx *.js src test", 17 | "stylint": "stylint src", 18 | "test": "tap test/*.js --node-arg=--require --node-arg=@babel/register --node-arg=--require --node-arg=@babel/polyfill", 19 | "coveralls": "tap test/*.js --coverage --coverage-report=text-lcov --nyc-arg=--require --nyc-arg=@babel/register --nyc-arg=--require --nyc-arg=@babel/polyfill | coveralls", 20 | "dev": "npm run styleguide", 21 | "styleguide": "styleguidist server", 22 | "styleguide:build": "styleguidist build" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/trendmicro-frontend/react-table.git" 27 | }, 28 | "author": "Tina C Lin", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/trendmicro-frontend/react-table/issues" 32 | }, 33 | "homepage": "https://github.com/trendmicro-frontend/react-table", 34 | "keywords": [ 35 | "react", 36 | "react-table" 37 | ], 38 | "peerDependencies": { 39 | "react": ">=16.8.0" 40 | }, 41 | "dependencies": { 42 | "lodash.get": "^4.4.2", 43 | "prop-types": "^15.5.8", 44 | "react-custom-scrollbars": "^4.2.1", 45 | "styled-components": "^5.0.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/cli": "~7.6.0", 49 | "@babel/core": "~7.6.0", 50 | "@babel/polyfill": "~7.4.4", 51 | "@babel/preset-env": "~7.6.0", 52 | "@babel/preset-react": "~7.0.0", 53 | "@babel/register": "~7.6.0", 54 | "@trendmicro/babel-config": "~1.0.0-alpha", 55 | "@trendmicro/react-anchor": "~0.5.6", 56 | "@trendmicro/react-checkbox": "~3.4.1", 57 | "@trendmicro/react-buttons": "~1.3.1", 58 | "@trendmicro/react-paginations": "^0.6.1", 59 | "babel-eslint": "~10.0.3", 60 | "babel-loader": "~8.0.6", 61 | "clean-css": "~4.2.1", 62 | "clean-css-cli": "~4.3.0", 63 | "core-js": "~3.2.1", 64 | "coveralls": "~3.0.6", 65 | "cross-env": "~5.2.1", 66 | "css-loader": "~3.2.0", 67 | "enzyme": "~3.10.0", 68 | "enzyme-adapter-react-16": "~1.14.0", 69 | "eslint": "~6.4.0", 70 | "eslint-config-trendmicro": "~1.4.1", 71 | "eslint-loader": "~3.0.0", 72 | "eslint-plugin-import": "~2.18.2", 73 | "eslint-plugin-jsx-a11y": "~6.2.3", 74 | "eslint-plugin-react": "~7.14.3", 75 | "extract-text-webpack-plugin": "~3.0.2", 76 | "file-loader": "~4.2.0", 77 | "find-imports": "~1.1.0", 78 | "html-webpack-plugin": "~3.2.0", 79 | "http-server": "~0.11.1", 80 | "jsdom": "~15.1.1", 81 | "lodash": "~4.17.15", 82 | "mini-css-extract-plugin": "~0.8.0", 83 | "react": "~16.11.0", 84 | "react-custom-scrollbars": "~4.2.1", 85 | "react-dom": "~16.11.0", 86 | "react-github-corner": "~2.3.0", 87 | "react-styleguidist": "9.0.4", 88 | "react-window": "1.8.5", 89 | "regenerator-runtime": "~0.13.3", 90 | "style-loader": "~1.0.0", 91 | "styled-components": "~4.3.2", 92 | "stylint": "~2.0.0", 93 | "stylus": "~0.54.7", 94 | "stylus-loader": "~3.0.2", 95 | "tap": "~14.6.4", 96 | "trendmicro-ui": "~0.5.2", 97 | "url-loader": "~2.1.0", 98 | "webpack": "~4.40.2", 99 | "webpack-cli": "~3.3.8", 100 | "webpack-dev-server": "~3.8.1" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /setupTests.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | import { JSDOM } from 'jsdom'; 4 | 5 | // React 16 Enzyme adapter 6 | Enzyme.configure({ adapter: new Adapter() }); 7 | 8 | // Ignore `.styl` files 9 | require.extensions['.styl'] = () => { 10 | return; 11 | }; 12 | 13 | // JSDOM 14 | const jsdom = new JSDOM(''); 15 | const { window } = jsdom; 16 | 17 | const copyProps = (src, target) => { 18 | const props = Object.getOwnPropertyNames(src) 19 | .filter(prop => typeof target[prop] === 'undefined') 20 | .reduce((result, prop) => ({ 21 | ...result, 22 | [prop]: Object.getOwnPropertyDescriptor(src, prop) 23 | }), {}); 24 | Object.defineProperties(target, props); 25 | }; 26 | 27 | global.window = window; 28 | global.document = window.document; 29 | global.navigator = { 30 | userAgent: 'node.js' 31 | }; 32 | 33 | copyProps(window, global); 34 | -------------------------------------------------------------------------------- /src/Loader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled, { keyframes } from 'styled-components'; 3 | 4 | const Loader = React.forwardRef((props, ref) => { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | }); 11 | 12 | const spinner = keyframes` 13 | 0% { 14 | transform: rotate(0deg); 15 | } 16 | 100% { 17 | transform: rotate(360deg); 18 | } 19 | `; 20 | 21 | const LoaderOverlay = styled.div` 22 | background-color: rgba(255, 255, 255, .8); 23 | cursor: wait; 24 | position: absolute; 25 | top: 0; 26 | bottom: 0; 27 | left: 0; 28 | right: 0; 29 | `; 30 | 31 | const LoaderIcon = styled.div` 32 | position: absolute; 33 | top: 50%; 34 | left: 50%; 35 | width: 56px; 36 | height: 56px; 37 | margin-top: -28px; 38 | margin-left: -28px; 39 | text-indent: -9999em; 40 | border: 2px solid rgba(0, 0, 0, .2); 41 | border-left-color: rgba(0, 0, 0, .8); 42 | border-radius: 50%; 43 | transform: translateZ(0); 44 | animation: ${spinner} 1s infinite linear; 45 | `; 46 | 47 | export default Loader; 48 | -------------------------------------------------------------------------------- /src/Table.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import styled from 'styled-components'; 4 | import isEqual from 'lodash/isEqual'; 5 | import TableContext from './context'; 6 | import Loader from './Loader'; 7 | 8 | class Table extends Component { 9 | static propTypes = { 10 | minimalist: PropTypes.bool, 11 | columns: PropTypes.array, 12 | data: PropTypes.array, 13 | emptyText: PropTypes.string, 14 | emptyRender: PropTypes.func, 15 | height: PropTypes.number, 16 | loading: PropTypes.bool, 17 | loaderRender: PropTypes.func, 18 | width: PropTypes.number.isRequired, 19 | }; 20 | 21 | static defaultProps = { 22 | columns: [], 23 | data: [], 24 | height: 0, 25 | emptyText: 'No Data', 26 | loaderRender: () => { 27 | return ( 28 | 29 | ); 30 | }, 31 | }; 32 | 33 | constructor(props) { 34 | super(props); 35 | 36 | this.state = { 37 | prevColumns: [], 38 | thisColumns: [], 39 | }; 40 | } 41 | 42 | static getDerivedStateFromProps(props, state) { 43 | const columnsAreChanged = !isEqual( 44 | props.columns, 45 | state.prevColumns 46 | ); 47 | 48 | if (columnsAreChanged) { 49 | const { columns: initColumns, width: tableWidth } = props; 50 | const columns = initColumns.map(column => { 51 | let columnWidth = column.width; 52 | if (typeof columnWidth === 'string') { 53 | const lastChar = columnWidth.substr(columnWidth.length - 1); 54 | if (lastChar === '%') { 55 | columnWidth = tableWidth * (parseFloat(columnWidth) / 100); 56 | return { 57 | ...column, 58 | width: columnWidth 59 | }; 60 | } 61 | } 62 | return column; 63 | }); 64 | const customWidthColumns = columns.filter(column => !!column.width); 65 | const customWidthColumnsTotalWidth = customWidthColumns.reduce((accumulator, column) => accumulator + column.width, 0); 66 | let averageWidth = (tableWidth - customWidthColumnsTotalWidth) / (columns.length - customWidthColumns.length); 67 | averageWidth = averageWidth <= 0 ? 150 : averageWidth; 68 | const parsedColumns = columns.map(column => { 69 | if (!!column.width) { 70 | return column; 71 | } 72 | return { 73 | ...column, 74 | width: averageWidth 75 | }; 76 | }); 77 | 78 | return { 79 | prevColumns: props.columns, 80 | thisColumns: parsedColumns, 81 | }; 82 | } 83 | return null; 84 | } 85 | 86 | renderLoader = () => { 87 | const { loaderRender } = this.props; 88 | return loaderRender(); 89 | }; 90 | 91 | renderEmptyBody = () => { 92 | const { 93 | emptyRender, 94 | emptyText, 95 | minimalist, 96 | } = this.props; 97 | const defaultEmptyBody = (text) => { 98 | return ( 99 | 100 | { text } 101 | 102 | ); 103 | }; 104 | const emptyBody = emptyRender ? emptyRender() : defaultEmptyBody(emptyText); 105 | return emptyBody; 106 | }; 107 | 108 | render() { 109 | const { 110 | thisColumns, 111 | } = this.state; 112 | const { 113 | minimalist, 114 | children, 115 | data, 116 | height, 117 | width, 118 | ...props 119 | } = this.props; 120 | const loader = this.renderLoader(); 121 | const emptyBody = this.renderEmptyBody(); 122 | const tableHeight = !!height ? `${height}px` : 'auto'; 123 | const tableWidth = !!width ? `${width}px` : 'auto'; 124 | 125 | const context = { 126 | minimalist, 127 | }; 128 | 129 | return ( 130 | 131 | 136 | { typeof children === 'function' 137 | ? children({ 138 | cells: thisColumns, 139 | data: data, 140 | loader: loader, 141 | emptyBody: emptyBody, 142 | tableWidth: width, 143 | }) 144 | : children 145 | } 146 | { !minimalist && ( 147 | 148 | 149 | 150 | 151 | 152 | 153 | )} 154 | 155 | 156 | ); 157 | } 158 | } 159 | 160 | const WrapperStyle = styled.div` 161 | position: relative; 162 | display: flex; 163 | flex-direction: column; 164 | line-height: 20px; 165 | height: ${props => props.height}; 166 | width: ${props => props.width}; 167 | box-sizing: border-box; 168 | *, *:before, *:after { 169 | box-sizing: inherit; 170 | } 171 | `; 172 | 173 | const EmptyBodyStyle = styled.div` 174 | text-align: center; 175 | padding: 44px 12px; 176 | color: #999; 177 | `; 178 | 179 | const VerticalLine = styled.div` 180 | border: none; 181 | border-left: 1px solid #ddd; 182 | height: 100%; 183 | width: 1px; 184 | `; 185 | 186 | const HorizontalLine = styled.div` 187 | border: none; 188 | border-top: 1px solid #ddd; 189 | height: 1px; 190 | width: 100%; 191 | `; 192 | 193 | const BorderTop = styled(HorizontalLine)` 194 | position: absolute; 195 | top: 0; 196 | `; 197 | const BorderRight = styled(VerticalLine)` 198 | position: absolute; 199 | top: 0; 200 | right: 0; 201 | `; 202 | const BorderBottom = styled(HorizontalLine)` 203 | position: absolute; 204 | bottom: 0; 205 | `; 206 | const BorderLeft = styled(VerticalLine)` 207 | position: absolute; 208 | top: 0; 209 | left: 0; 210 | `; 211 | 212 | export default Table; 213 | -------------------------------------------------------------------------------- /src/TableBody.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const TableBody = React.forwardRef(({ 5 | children, 6 | ...props 7 | }, ref) => { 8 | return ( 9 | 13 | { children } 14 | 15 | ); 16 | }); 17 | 18 | const BodyStyle = styled.div` 19 | flex: 1 1 auto; 20 | `; 21 | 22 | export default TableBody; 23 | -------------------------------------------------------------------------------- /src/TableCell.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import styled, { css } from 'styled-components'; 4 | import { useTableContext } from './context'; 5 | 6 | const TableCell = React.forwardRef(({ 7 | children, 8 | width, 9 | ...props 10 | }, ref) => { 11 | const { 12 | minimalist, 13 | } = useTableContext(); 14 | 15 | return ( 16 | 22 | { children } 23 | 24 | ); 25 | }); 26 | 27 | TableCell.propTypes = { 28 | width: PropTypes.number.isRequired, 29 | }; 30 | 31 | const CellStyle = styled.div` 32 | padding: 8px 12px; 33 | flex: 1 0 auto; 34 | width: ${props => props.width}px; 35 | 36 | ${props => !props.minimalist && css` 37 | border-right: 1px solid #ddd; 38 | border-bottom: 1px solid #ddd; 39 | `} 40 | 41 | ${props => props.minimalist && css` 42 | border-bottom: 1px solid #ddd; 43 | `} 44 | `; 45 | 46 | export default TableCell; 47 | -------------------------------------------------------------------------------- /src/TableHeader.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import React from 'react'; 3 | 4 | const TableHeader = React.forwardRef(({ 5 | children, 6 | ...props 7 | }, ref) => { 8 | return ( 9 | 13 | { children } 14 | 15 | ); 16 | }); 17 | 18 | const HeaderStyle = styled.div` 19 | flex: 0 0 auto; 20 | overflow: hidden; 21 | `; 22 | 23 | export default TableHeader; 24 | -------------------------------------------------------------------------------- /src/TableHeaderCell.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import styled, { css } from 'styled-components'; 4 | import { useTableContext } from './context'; 5 | 6 | const TableHeaderCell = React.forwardRef(({ 7 | children, 8 | width, 9 | ...props 10 | }, ref) => { 11 | const { 12 | minimalist, 13 | } = useTableContext(); 14 | 15 | return ( 16 | 22 | { children } 23 | 24 | ); 25 | }); 26 | 27 | TableHeaderCell.propTypes = { 28 | width: PropTypes.number.isRequired, 29 | }; 30 | 31 | const HeaderCellStyle = styled.div` 32 | padding: 8px 12px; 33 | flex: 1 0 auto; 34 | color: #777; 35 | font-weight: bold; 36 | width: ${props => props.width}px; 37 | 38 | ${props => !props.minimalist && css` 39 | background-color: #EEEEEE; 40 | border-right: 1px solid #ddd; 41 | border-bottom: 2px solid #ccc; 42 | `} 43 | 44 | ${props => props.minimalist && css` 45 | border-bottom: 2px solid #ccc; 46 | `} 47 | `; 48 | 49 | export default TableHeaderCell; 50 | -------------------------------------------------------------------------------- /src/TableRow.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | const TableRow = React.forwardRef(({ 5 | children, 6 | ...props 7 | }, ref) => { 8 | return ( 9 | 13 | { children } 14 | 15 | ); 16 | }); 17 | 18 | const RowStyle = styled.div` 19 | display: flex; 20 | `; 21 | 22 | 23 | export default TableRow; 24 | -------------------------------------------------------------------------------- /src/TableTemplate.jsx: -------------------------------------------------------------------------------- 1 | import _get from 'lodash/get'; 2 | import PropTypes from 'prop-types'; 3 | import React, { Component } from 'react'; 4 | import { Scrollbars } from 'react-custom-scrollbars'; 5 | import styled, { css } from 'styled-components'; 6 | import TableWrapper from './Table'; 7 | import TableHeader from './TableHeader'; 8 | import TableBody from './TableBody'; 9 | import TableRow from './TableRow'; 10 | import TableCell from './TableCell'; 11 | import TableHeaderCell from './TableHeaderCell'; 12 | 13 | class TableTemplate extends Component { 14 | static propTypes = { 15 | minimalist: PropTypes.bool, 16 | columns: PropTypes.array, 17 | data: PropTypes.array, 18 | emptyText: PropTypes.string, 19 | emptyRender: PropTypes.func, 20 | height: PropTypes.number, 21 | hideHeader: PropTypes.bool, 22 | hoverable: PropTypes.bool, 23 | loading: PropTypes.bool, 24 | loaderRender: PropTypes.func, 25 | useFixedHeader: PropTypes.bool, 26 | width: PropTypes.number.isRequired, 27 | }; 28 | 29 | static defaultProps = { 30 | data: [], 31 | }; 32 | 33 | constructor(props) { 34 | super(props); 35 | this.tableHeaderRef = React.createRef(); 36 | } 37 | 38 | onScroll = (e) => { 39 | const scrollLeft = e.target.scrollLeft; 40 | if (!!this.tableHeaderRef && this.tableHeaderRef.current.scrollLeft !== scrollLeft) { 41 | this.tableHeaderRef.current.scrollLeft = scrollLeft; 42 | } 43 | }; 44 | 45 | renderHeader = ({ cells: columns }) => { 46 | return ( 47 | 48 | 49 | { 50 | columns.map((column, index) => { 51 | const key = `table_header_cell_${index}`; 52 | const { 53 | title, 54 | width: cellWidth, 55 | } = column; 56 | return ( 57 | 61 | { typeof title === 'function' ? title(column) : title } 62 | 63 | ); 64 | }) 65 | } 66 | 67 | 68 | ); 69 | }; 70 | 71 | renderBody = ({ cells: columns, data, emptyBody }) => { 72 | const { 73 | hoverable, 74 | } = this.props; 75 | const showEmpty = (data.length === 0); 76 | 77 | return ( 78 | 79 | { showEmpty && emptyBody } 80 | { 81 | data.map((row, index) => { 82 | const rowKey = `table_row${index}`; 83 | return ( 84 | 88 | { 89 | columns.map((column, index) => { 90 | const key = `${rowKey}_cell${index}`; 91 | const cellValue = _get(row, column.dataKey); 92 | const cell = (typeof column.render === 'function' ? column.render(cellValue, row, index) : cellValue); 93 | return ( 94 | 98 | { cell } 99 | 100 | ); 101 | }) 102 | } 103 | 104 | ); 105 | }) 106 | } 107 | 108 | ); 109 | }; 110 | 111 | render() { 112 | const { 113 | data, 114 | minimalist, 115 | height, 116 | hideHeader, 117 | loading, 118 | useFixedHeader, 119 | width, 120 | columns, 121 | ...props 122 | } = this.props; 123 | 124 | return ( 125 | 133 | { 134 | ({ cells, data, emptyBody, loader }) => { 135 | return ( 136 | 137 | { !hideHeader && useFixedHeader && this.renderHeader({ cells, data }) } 138 | 143 | { !hideHeader && !useFixedHeader && this.renderHeader({ cells, data }) } 144 |
145 | { this.renderBody({ cells, data, emptyBody }) } 146 | { loading && loader } 147 |
148 |
149 |
150 | ); 151 | } 152 | } 153 |
154 | ); 155 | } 156 | } 157 | 158 | const StyledTableCell = styled(TableCell)``; 159 | 160 | const StyledTableRow = styled(TableRow)` 161 | ${props => props.hoverable && css` 162 | &:hover { 163 | ${StyledTableCell} { 164 | background-color: #e6f4fc; 165 | } 166 | } 167 | `} 168 | `; 169 | 170 | export default TableTemplate; 171 | -------------------------------------------------------------------------------- /src/context.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TableContext = React.createContext({ 4 | minimalist: false, 5 | }); 6 | export const useTableContext = () => React.useContext(TableContext); 7 | 8 | export default TableContext; 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import TableTemplate from './TableTemplate'; 2 | 3 | export { default as TableWrapper } from './Table'; 4 | export { default as TableHeader } from './TableHeader'; 5 | export { default as TableBody } from './TableBody'; 6 | export { default as TableRow } from './TableRow'; 7 | export { default as TableCell } from './TableCell'; 8 | export { default as TableHeaderCell } from './TableHeaderCell'; 9 | 10 | export default TableTemplate; 11 | -------------------------------------------------------------------------------- /styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const webpack = require('webpack'); 4 | const pkg = require('./package.json'); 5 | const babelConfig = require('./babel.config'); 6 | 7 | const webpackConfig = { 8 | mode: 'development', 9 | devtool: 'cheap-module-eval-source-map', 10 | devServer: { 11 | disableHostCheck: true, 12 | contentBase: path.resolve(__dirname, 'docs'), 13 | }, 14 | entry: path.resolve(__dirname, 'src/index.js'), 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.jsx?$/, 19 | loader: 'eslint-loader', 20 | enforce: 'pre', 21 | exclude: /node_modules/ 22 | }, 23 | { 24 | test: /\.jsx?$/, 25 | loader: 'babel-loader', 26 | // XXX: due to untranspiled code in styleguide's node_modules, it needs to include them 27 | // https://github.com/styleguidist/react-styleguidist/blob/f14e8e7f403aa32045b9f61e4e4f1a7776146d8b/examples/ie11/styleguide.config.js#L4 28 | exclude: /node_modules\/(?!(ansi-styles|strip-ansi|ansi-regex|react-dev-utils|chalk|regexpu-core|unicode-match-property-ecmascript|unicode-match-property-value-ecmascript|acorn-jsx)\/).*/, 29 | options: babelConfig 30 | }, 31 | { 32 | test: /\.styl$/, 33 | use: [ 34 | MiniCssExtractPlugin.loader, 35 | { 36 | loader: 'css-loader', 37 | options: { 38 | modules: { 39 | localIdentName: '[local]---[hash:base64:5]', 40 | }, 41 | importLoaders: 1, 42 | localsConvention: 'camelCase', 43 | } 44 | }, 45 | 'stylus-loader' 46 | ] 47 | }, 48 | { 49 | test: /\.css$/, 50 | use: [ 51 | 'style-loader', 52 | 'css-loader' 53 | ] 54 | }, 55 | { 56 | test: /\.(png|jpg|svg)$/, 57 | loader: 'url-loader' 58 | }, 59 | { 60 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 61 | loader: 'url-loader', 62 | options: { 63 | limit: 10000, 64 | mimetype: 'application/font-woff' 65 | } 66 | }, 67 | { 68 | test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 69 | loader: 'file-loader' 70 | } 71 | ] 72 | }, 73 | plugins: [ 74 | new webpack.DefinePlugin({ 75 | 'process.env': { 76 | // This has effect on the react lib size 77 | NODE_ENV: JSON.stringify('production') 78 | } 79 | }), 80 | new MiniCssExtractPlugin({ 81 | filename: '[name].css' 82 | }) 83 | ], 84 | resolve: { 85 | extensions: ['.js', '.json', '.jsx'] 86 | } 87 | }; 88 | 89 | module.exports = { 90 | title: `React Table v${pkg.version}`, 91 | sections: [ 92 | { 93 | name: 'Base Table', 94 | content: path.resolve(__dirname, 'styleguide/examples/BaseTable.md') 95 | }, 96 | { 97 | name: 'Dynamic Header', 98 | content: path.resolve(__dirname, 'styleguide/examples/DynamicHeader.md') 99 | }, 100 | { 101 | name: 'Expand', 102 | content: path.resolve(__dirname, 'styleguide/examples/Expand.md'), 103 | }, 104 | { 105 | name: 'Fixed Columns', 106 | content: path.resolve(__dirname, 'styleguide/examples/FixedColumns.md'), 107 | }, 108 | { 109 | name: 'Fixed Header', 110 | content: path.resolve(__dirname, 'styleguide/examples/FixedHeader.md'), 111 | }, 112 | { 113 | name: 'Hoverable', 114 | content: path.resolve(__dirname, 'styleguide/examples/Hoverable.md'), 115 | }, 116 | { 117 | name: 'Loader - Default', 118 | content: path.resolve(__dirname, 'styleguide/examples/LoaderDefault.md'), 119 | }, 120 | { 121 | name: 'Loader - Custom', 122 | content: path.resolve(__dirname, 'styleguide/examples/LoaderCustom.md'), 123 | }, 124 | { 125 | name: 'Minimalism', 126 | content: path.resolve(__dirname, 'styleguide/examples/Minimalism.md'), 127 | }, 128 | { 129 | name: 'No Header', 130 | content: path.resolve(__dirname, 'styleguide/examples/NoHeader.md'), 131 | }, 132 | { 133 | name: 'No Data - Default', 134 | content: path.resolve(__dirname, 'styleguide/examples/NoDataDefault.md'), 135 | }, 136 | { 137 | name: 'No Data - Custom', 138 | content: path.resolve(__dirname, 'styleguide/examples/NoDataCustom.md'), 139 | }, 140 | { 141 | name: 'No Data - Custom Text', 142 | content: path.resolve(__dirname, 'styleguide/examples/NoDataCustomText.md'), 143 | }, 144 | { 145 | name: 'Pagination', 146 | content: path.resolve(__dirname, 'styleguide/examples/Pagination.md'), 147 | }, 148 | { 149 | name: 'Selection', 150 | content: path.resolve(__dirname, 'styleguide/examples/Selection.md'), 151 | }, 152 | { 153 | name: 'Sortable', 154 | content: path.resolve(__dirname, 'styleguide/examples/Sortable.md'), 155 | }, 156 | { 157 | name: '10000 rows', 158 | content: path.resolve(__dirname, 'styleguide/examples/10000Rows.md'), 159 | }, 160 | ], 161 | require: [ 162 | '@babel/polyfill', 163 | path.resolve(__dirname, 'styleguide/setup.js'), 164 | path.resolve(__dirname, 'styleguide/styles.css') 165 | ], 166 | ribbon: { 167 | url: pkg.homepage, 168 | text: 'Fork me on GitHub' 169 | }, 170 | serverPort: 8080, 171 | exampleMode: 'collapse', 172 | usageMode: 'expand', 173 | showSidebar: true, 174 | styleguideComponents: { 175 | StyleGuideRenderer: path.join(__dirname, 'styleguide/components/StyleGuideRenderer.jsx'), 176 | Wrapper: path.join(__dirname, 'styleguide/components/Wrapper.jsx'), 177 | }, 178 | styleguideDir: 'docs/', 179 | webpackConfig: webpackConfig 180 | }; 181 | -------------------------------------------------------------------------------- /styleguide/components/FormGroup.jsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | const FormGroup = styled.div` 4 | margin-bottom: 12px; 5 | `; 6 | 7 | export default FormGroup; 8 | -------------------------------------------------------------------------------- /styleguide/components/StyleGuideRenderer.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import GitHubCorner from 'react-github-corner'; 4 | import styled from 'styled-components'; 5 | import pkg from '../../package.json'; 6 | 7 | const Root = styled.div` 8 | min-height: 100vh; 9 | background-color: #fff; 10 | padding-left: 240px; 11 | `; 12 | 13 | const Main = styled.main` 14 | padding: 16px 32px; 15 | margin: 0 auto; 16 | display: block; 17 | `; 18 | 19 | const Sidebar = styled.div` 20 | background-color: #f5f5f5; 21 | border: #e8e8e8 solid; 22 | border-width: 0 1px 0 0; 23 | position: fixed; 24 | top: 0; 25 | left: 0; 26 | bottom: 0; 27 | width: 240px; 28 | overflow: auto; 29 | -webkit-overflow-scrolling: touch; 30 | `; 31 | 32 | const Block = styled.div``; 33 | 34 | const TextBlock = styled(Block)` 35 | padding: 16px; 36 | border-bottom: 1px #e8e8e8 solid; 37 | color: #333; 38 | margin: 0; 39 | font-size: 18px; 40 | font-weight: normal; 41 | `; 42 | 43 | const StyleGuideRenderer = ({ 44 | title, 45 | toc, 46 | children, 47 | }) => { 48 | return ( 49 | 50 | 51 | 52 | 53 | {title} 54 | 55 | 56 | {toc} 57 | 58 | 59 |
60 | {children} 61 |
62 |
63 | ); 64 | }; 65 | 66 | StyleGuideRenderer.propTypes = { 67 | title: PropTypes.string, 68 | toc: PropTypes.node, 69 | }; 70 | 71 | export default StyleGuideRenderer; 72 | -------------------------------------------------------------------------------- /styleguide/components/Text.jsx: -------------------------------------------------------------------------------- 1 | import styled, { css } from 'styled-components'; 2 | 3 | const Text = styled.div`${({ 4 | fontFamily = 'Arial, "Helvetica Neue", Helvetica, sans-serif', 5 | size = 'inherit', 6 | color = 'inherit', 7 | bold 8 | }) => css` 9 | display: inline-block; 10 | color: ${color}; 11 | background-color: transparent; 12 | vertical-align: middle; 13 | font-family: ${fontFamily}; 14 | font-size: ${Number(size) > 0 ? `${size}px` : size}; 15 | font-weight: ${!!bold ? 'bold' : 'inherit'}; 16 | `}`; 17 | 18 | export default Text; 19 | -------------------------------------------------------------------------------- /styleguide/components/Wrapper.jsx: -------------------------------------------------------------------------------- 1 | const Wrapper = ({ children }) => { 2 | return children; 3 | }; 4 | 5 | export default Wrapper; 6 | -------------------------------------------------------------------------------- /styleguide/examples/10000Rows.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const data = []; 3 | for (let i = 1; i <= 10000; i++) { 4 | data.push({ 5 | eventType: `Virus/Malware_${i}`, 6 | affectedDevices: 20 + i, 7 | detections: 10 + i 8 | }); 9 | } 10 | 11 | const columns = [ 12 | { title: 'Event Type', dataKey: 'eventType' }, 13 | { title: 'Affected Devices', dataKey: 'affectedDevices' }, 14 | { title: 'Detections', dataKey: 'detections', width: 300 } 15 | ]; 16 | 17 | const CustomScrollbars = ({ onScroll, forwardedRef, style, children }) => { 18 | const refSetter = React.useCallback(scrollbarsRef => { 19 | if (scrollbarsRef) { 20 | forwardedRef(scrollbarsRef.view); 21 | } else { 22 | forwardedRef(null); 23 | } 24 | }, []); 25 | 26 | return ( 27 | 32 | {children} 33 | 34 | ); 35 | }; 36 | 37 | const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => ( 38 | 39 | )); 40 | 41 | 46 | {({ cells, data, tableWidth }) => { 47 | return ( 48 | 49 | 50 | 51 | { 52 | cells.map((cell, index) => { 53 | const key = `table_header_cell_${index}`; 54 | const { 55 | title, 56 | width: cellWidth, 57 | } = cell; 58 | return ( 59 | 63 | { title } 64 | 65 | ); 66 | }) 67 | } 68 | 69 | 70 | 71 | 79 | {({ data, index: rowIndex, style }) => { 80 | const rowData = data[rowIndex]; 81 | return ( 82 | 83 | { 84 | cells.map((cell, cellIndex) => { 85 | const key = `table_row${rowIndex}_cell${cellIndex}`; 86 | const { width: cellWidth } = cell; 87 | const cellValue = _get(rowData, cell.dataKey); 88 | return ( 89 | 93 | { cellValue } 94 | 95 | ); 96 | }) 97 | } 98 | 99 | ); 100 | }} 101 | 102 | 103 | 104 | ); 105 | }} 106 | 107 | ``` -------------------------------------------------------------------------------- /styleguide/examples/BaseTable.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices' 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections' 14 | } 15 | ]; 16 | 17 | const data = [ 18 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 19 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 20 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 21 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 22 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 23 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 24 | ]; 25 | 26 | 31 | ``` -------------------------------------------------------------------------------- /styleguide/examples/DynamicHeader.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | initialState = { 3 | data: [ 4 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 5 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 6 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 7 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 8 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 9 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 10 | ] 11 | }; 12 | 13 | const handleClickDelete = (row) => (e) => { 14 | const { data } = state; 15 | const index = data.findIndex(o => o.id === row.id); 16 | data.splice(index, 1); 17 | setState({ data }); 18 | }; 19 | 20 | const renderEventTypeCell = () => { 21 | const { data } = state; 22 | return `Event Type (${data.length})`; 23 | }; 24 | 25 | const renderActionCell = (value, row) => { 26 | return ( 27 | 30 | ); 31 | }; 32 | 33 | const columns = [ 34 | { title: renderEventTypeCell, dataKey: 'eventType', width: '30%' }, 35 | { title: 'Affected Devices', dataKey: 'affectedDevices' }, 36 | { title: 'Detections', dataKey: 'detections' }, 37 | { title: 'Delete', render: renderActionCell, width: 64 } 38 | ]; 39 | 40 | 46 | ``` -------------------------------------------------------------------------------- /styleguide/examples/Expand.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import _concat from 'lodash/concat'; 4 | import _filter from 'lodash/filter'; 5 | import _get from 'lodash/get'; 6 | import _includes from 'lodash/includes'; 7 | import { Scrollbars } from 'react-custom-scrollbars'; 8 | import Anchor from '@trendmicro/react-anchor'; 9 | import TableTemplate, { TableWrapper, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '../../src'; 10 | 11 | const data = [ 12 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 13 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 14 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 15 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 16 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 17 | { id: 6, eventType: 'Application Control', affectedDevices: 30, detections: 111 }, 18 | { id: 7, eventType: 'Predictive Machine Learning', affectedDevices: 40, detections: 0 }, 19 | { id: 8, eventType: 'Behavior Monitoring', affectedDevices: 22, detections: 333 }, 20 | { id: 9, eventType: 'Device Ontrol', affectedDevices: 9, detections: 555 }, 21 | { id: 10, eventType: 'Ransomware Summary', affectedDevices: 0, detections: 66 }, 22 | { id: 11, eventType: 'Agent Status', affectedDevices: 2, detections: 789 }, 23 | { id: 12, eventType: 'Security Risk Detections Over Time', affectedDevices: 66, detections: 34 }, 24 | { id: 13, eventType: 'Action Center', affectedDevices: 32, detections: 2234 }, 25 | { id: 14, eventType: 'License Status', affectedDevices: 8, detections: 34325 }, 26 | { id: 15, eventType: 'Component Status', affectedDevices: 12, detections: 46465 }, 27 | { id: 16, eventType: 'Outbreak Defense', affectedDevices: 12, detections: 123 }, 28 | { id: 17, eventType: 'Test long long long long long long long long long long long long long long long content', affectedDevices: 11, detections: 345 }, 29 | { id: 18, eventType: 'Computer Status', affectedDevices: 90, detections: 466 }, 30 | { id: 19, eventType: 'Mobile Devices', affectedDevices: 100, detections: 234 }, 31 | { id: 20, eventType: 'Desktops', affectedDevices: 102, detections: 477 }, 32 | { id: 21, eventType: 'Servers', affectedDevices: 33, detections: 235 } 33 | ]; 34 | 35 | const subData = []; 36 | for (let i = 1; i <= 5; i++) { 37 | subData.push({ 38 | id: i, 39 | app: `chrome_${i}`, 40 | vendor: `google_${i}` 41 | }); 42 | } 43 | 44 | const subColumns = [ 45 | { title: 'Application Name', dataKey: 'app' }, 46 | { title: 'Vendor Name', dataKey: 'vendor' } 47 | ]; 48 | 49 | class Expand extends Component { 50 | constructor(props) { 51 | super(props); 52 | 53 | this.scrollbarRef = React.createRef(); 54 | this.state = { 55 | expandedRowKeys: [], 56 | }; 57 | } 58 | 59 | handleExpandedRowRender = (record, key) => { 60 | return ( 61 | 62 |
Sub content
63 | 70 |
71 | ); 72 | }; 73 | 74 | handleToggleDetails = (record) => (e) => { 75 | e.preventDefault(); 76 | e.stopPropagation(); 77 | 78 | const { expandedRowKeys } = this.state; 79 | const isIdInSelectedList = _includes(expandedRowKeys, record.id); 80 | const newExpandedRowList = isIdInSelectedList 81 | ? _filter(expandedRowKeys, id => id !== record.id) 82 | : _concat(expandedRowKeys, record.id); 83 | 84 | this.setState({ expandedRowKeys: newExpandedRowList }); 85 | }; 86 | 87 | handleRenderActionColumn = (text, record) => { 88 | const { expandedRowKeys } = this.state; 89 | const expanded = (expandedRowKeys.indexOf(record.id) >= 0); 90 | return ( 91 | 92 | 93 | 94 | ); 95 | }; 96 | 97 | render() { 98 | const columns = [ 99 | { dataKey: 'id', render: this.handleRenderActionColumn, width: 40 }, 100 | { title: 'Event Type', dataKey: 'eventType', width: 150 }, 101 | { title: 'Affected Devices', dataKey: 'affectedDevices' }, 102 | { title: 'Detections', dataKey: 'detections' } 103 | ]; 104 | 105 | return ( 106 | 112 | {({ cells, data, tableWidth }) => { 113 | return ( 114 | 115 | 116 | 117 | { 118 | cells.map((cell, index) => { 119 | const key = `table_header_cell_${index}`; 120 | const { 121 | title, 122 | width: cellWidth, 123 | } = cell; 124 | return ( 125 | 129 | { title } 130 | 131 | ); 132 | }) 133 | } 134 | 135 | 136 | 137 | 142 | { 143 | data.map((row, index) => { 144 | const rowKey = `table_row${index}`; 145 | const isExpanded = _includes(this.state.expandedRowKeys, row.id); 146 | return ( 147 | 148 | 149 | { 150 | cells.map((cell, index) => { 151 | const key = `${rowKey}_cell${index}`; 152 | const cellValue = _get(row, cell.dataKey); 153 | return ( 154 | 158 | { typeof cell.render === 'function' ? cell.render(cellValue, row, index) : cellValue } 159 | 160 | ); 161 | }) 162 | } 163 | 164 | { isExpanded && ( 165 | 166 | { this.handleExpandedRowRender(row, index) } 167 | 168 | )} 169 | 170 | ); 171 | }) 172 | } 173 | 174 | 175 | 176 | ); 177 | }} 178 | 179 | ); 180 | } 181 | } 182 | 183 | const StyledTableRow = styled(TableRow)` 184 | &:hover { 185 | background-color: #e6f4fc; 186 | } 187 | `; 188 | 189 | const ExpandedRowStyle = styled.div` 190 | padding: 16px 16px 16px 52px; 191 | border-bottom: 1px solid #ddd; 192 | &:last-child { 193 | border-bottom-width: 0; 194 | } 195 | `; 196 | 197 | const ExpandIcon = styled.div` 198 | cursor: pointer; 199 | display: inline-block; 200 | width: 16px; 201 | height: 16px; 202 | text-align: center; 203 | line-height: 16px; 204 | border: 1px solid #e9e9e9; 205 | -webkit-user-select: none; 206 | -moz-user-select: none; 207 | -ms-user-select: none; 208 | user-select: none; 209 | background: #fff; 210 | 211 | ${props => props.expanded && css` 212 | :after { 213 | content: '-'; 214 | } 215 | `} 216 | ${props => !props.expanded && css` 217 | :after { 218 | content: '+'; 219 | } 220 | `} 221 | `; 222 | 223 | export default Expand; 224 | -------------------------------------------------------------------------------- /styleguide/examples/Expand.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | 3 | ``` -------------------------------------------------------------------------------- /styleguide/examples/FixedColumns.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import Checkbox from '@trendmicro/react-checkbox'; 4 | import ensureArray from 'ensure-array'; 5 | import _concat from 'lodash/concat'; 6 | import _filter from 'lodash/filter'; 7 | import _get from 'lodash/get'; 8 | import _includes from 'lodash/includes'; 9 | import { Scrollbars } from 'react-custom-scrollbars'; 10 | import { FixedSizeList as ListTable } from 'react-window'; 11 | import { TableWrapper, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '../../src'; 12 | 13 | const data = []; 14 | for (let i = 1; i <= 1000; i++) { 15 | data.push({ 16 | id: i, 17 | eventType: `Virus/Malware_${i}`, 18 | affectedDevices: 20 + i, 19 | detections: 10 + i 20 | }); 21 | } 22 | 23 | const CustomScrollbars = ({ 24 | onScroll, 25 | forwardedRef, 26 | children, 27 | style, 28 | ...props 29 | }) => { 30 | const refSetter = React.useCallback(scrollbarsRef => { 31 | if (scrollbarsRef) { 32 | forwardedRef(scrollbarsRef); 33 | } else { 34 | forwardedRef(null); 35 | } 36 | }, [forwardedRef]); 37 | 38 | return ( 39 | 45 | { children } 46 | 47 | ); 48 | }; 49 | 50 | const CustomMainTableScrollbarsVirtualList = React.forwardRef((props, ref) => { 51 | return ( 52 | 53 | ); 54 | }); 55 | 56 | const CustomLeftTableScrollbarsVirtualList = React.forwardRef((props, ref) => { 57 | return ( 58 |
} 62 | /> 63 | ); 64 | }); 65 | 66 | class Selection extends Component { 67 | constructor(props) { 68 | super(props); 69 | 70 | this.mainTableHeaderRef = React.createRef(); 71 | this.mainTableScrollbarRef = React.createRef(); 72 | this.leftTableScrollbarRef = React.createRef(); 73 | this.leftTableShadowRef = React.createRef(); 74 | 75 | this.state = { 76 | selectedIdList: [], 77 | currentHoverKey: null, 78 | }; 79 | } 80 | 81 | componentDidMount() { 82 | this.mainTableScrollbarRef.current.view.addEventListener('scroll', this.handleMainTableScroll); 83 | } 84 | 85 | componentWillUnmount() { 86 | this.mainTableScrollbarRef.current.view.removeEventListener('scroll', this.handleMainTableScroll); 87 | } 88 | 89 | handleMainTableScroll = (e) => { 90 | const mainTable = e.target; 91 | const scrollTop = mainTable.scrollTop; 92 | const scrollLeft = mainTable.scrollLeft; 93 | 94 | if (!!this.leftTableScrollbarRef.current && this.leftTableScrollbarRef.current.getScrollTop() !== scrollTop) { 95 | this.leftTableScrollbarRef.current.scrollTop(scrollTop); 96 | } 97 | if (!!this.mainTableHeaderRef && this.mainTableHeaderRef.current.scrollLeft !== scrollLeft) { 98 | this.mainTableHeaderRef.current.scrollLeft = scrollLeft; 99 | 100 | if (!!this.leftTableShadowRef) { 101 | if (scrollLeft === 0) { 102 | this.leftTableShadowRef.current.style.display = 'none'; 103 | } else { 104 | this.leftTableShadowRef.current.style.display = 'block'; 105 | } 106 | } 107 | } 108 | }; 109 | 110 | handleLeftTableVerticalScroll = ({ scrollOffset, scrollUpdateWasRequested }) => { 111 | if (!!this.mainTableScrollbarRef.current && !scrollUpdateWasRequested) { 112 | this.mainTableScrollbarRef.current.scrollTop(scrollOffset); 113 | } 114 | }; 115 | 116 | handleRowMouseEnter = ({ hovered, rowKey }) => (event) => { 117 | if (!hovered) { 118 | this.setState({ currentHoverKey: rowKey }); 119 | } 120 | }; 121 | 122 | handleRowMouseLeave = ({ hovered, rowKey }) => (event) => { 123 | if (hovered) { 124 | this.setState({ currentHoverKey: null }); 125 | } 126 | }; 127 | 128 | handleClickRow = (record, rowIndex) => (e) => { 129 | e.stopPropagation(); 130 | const { selectedIdList } = this.state; 131 | const isIdInSelectedList = _includes(selectedIdList, record.id); 132 | const newSelectedList = isIdInSelectedList 133 | ? _filter(selectedIdList, id => id !== record.id) 134 | : _concat(selectedIdList, record.id); 135 | 136 | this.setState({ selectedIdList: newSelectedList }); 137 | }; 138 | 139 | handleHeaderCheckbox = (e) => { 140 | e.stopPropagation(); 141 | const { checked, indeterminate } = e.target; 142 | const selectAll = (!checked && indeterminate) || checked; 143 | const selectedIdList = selectAll ? data.map(item => item.id) : []; 144 | this.setState({ selectedIdList: selectedIdList }); 145 | }; 146 | 147 | handleRowCheckbox = (e) => { 148 | return false; 149 | }; 150 | 151 | renderHeaderCheckbox = (column) => { 152 | const { selectedIdList } = this.state; 153 | const dataLength = ensureArray(data).length; 154 | const selectedLength = selectedIdList.length; 155 | const isSelectedAll = selectedLength > 0 && selectedLength === dataLength; 156 | const isChecked = (selectedLength > 0 && selectedLength < dataLength) || isSelectedAll; 157 | const isIndeterminate = selectedLength > 0 && selectedLength < dataLength; 158 | 159 | return ( 160 | 168 | ); 169 | }; 170 | 171 | renderCheckbox = (value, row) => { 172 | const checked = _includes(this.state.selectedIdList, row.id); 173 | return ( 174 | 181 | ); 182 | }; 183 | 184 | renderLeftTable = () => { 185 | const columns = [ 186 | { title: this.renderHeaderCheckbox, dataKey: 'id', render: this.renderCheckbox, width: 40 }, 187 | { title: 'Event Type', dataKey: 'eventType', width: 200 }, 188 | ]; 189 | 190 | return ( 191 |
198 | 203 | {({ cells, data, tableWidth }) => { 204 | return ( 205 | 206 | 207 | 208 | { 209 | cells.map((cell, index) => { 210 | const key = `table_header_cell_${index}`; 211 | const { 212 | title, 213 | width: cellWidth, 214 | } = cell; 215 | return ( 216 | 220 | { typeof title === 'function' ? title(cell) : title } 221 | 222 | ); 223 | }) 224 | } 225 | 226 | 227 | 228 | 238 | {({ data, index: rowIndex, style }) => { 239 | const rowData = data[rowIndex]; 240 | const rowKey = rowData.id; 241 | const checked = _includes(this.state.selectedIdList, rowKey); 242 | const hovered = this.state.currentHoverKey === rowKey; 243 | return ( 244 | 252 | { 253 | cells.map((cell, cellIndex) => { 254 | const key = `table_row${rowIndex}_cell${cellIndex}`; 255 | const { width: cellWidth, render } = cell; 256 | const cellValue = _get(rowData, cell.dataKey); 257 | return ( 258 | 263 | { typeof render === 'function' ? render(cellValue, rowData) : cellValue } 264 | 265 | ); 266 | }) 267 | } 268 | 269 | ); 270 | }} 271 | 272 | 273 | 274 | ); 275 | }} 276 | 277 |
278 | ); 279 | }; 280 | 281 | renderMainTable = () => { 282 | const columns = [ 283 | { width: 40 }, 284 | { width: 200 }, 285 | { title: 'Affected Devices', dataKey: 'affectedDevices', width: 200 }, 286 | { title: 'Detections', dataKey: 'detections', width: 300 } 287 | ]; 288 | 289 | return ( 290 | 295 | {({ cells, data, tableWidth }) => { 296 | return ( 297 | 298 | 299 | 300 | { 301 | cells.map((cell, index) => { 302 | const key = `table_header_cell_${index}`; 303 | const { 304 | title, 305 | width: cellWidth, 306 | } = cell; 307 | return ( 308 | 312 | { title } 313 | 314 | ); 315 | }) 316 | } 317 | 318 | 319 | 320 | 329 | {({ data, index: rowIndex, style }) => { 330 | const rowData = data[rowIndex]; 331 | const rowKey = rowData.id; 332 | const checked = _includes(this.state.selectedIdList, rowKey); 333 | const hovered = this.state.currentHoverKey === rowKey; 334 | return ( 335 | 343 | { 344 | cells.map((cell, cellIndex) => { 345 | const key = `table_row${rowIndex}_cell${cellIndex}`; 346 | const { width: cellWidth } = cell; 347 | const cellValue = _get(rowData, cell.dataKey); 348 | return ( 349 | 354 | { cellValue } 355 | 356 | ); 357 | }) 358 | } 359 | 360 | ); 361 | }} 362 | 363 | 364 | 365 | ); 366 | }} 367 | 368 | ); 369 | }; 370 | 371 | render() { 372 | return ( 373 |
378 | { this.renderMainTable() } 379 | 380 | { this.renderLeftTable() } 381 |
382 | ); 383 | } 384 | } 385 | 386 | const ShadowStyle = styled.div` 387 | position: absolute; 388 | bottom: 0; 389 | left: 240px; 390 | right: 0px; 391 | width: 12px; 392 | height: 100%; 393 | background: linear-gradient(to right, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0) 100%); 394 | display: none; 395 | `; 396 | 397 | const StyledTableCell = styled(TableCell)``; 398 | 399 | const StyledTableRow = styled(TableRow)` 400 | ${props => props.hover && css` 401 | ${StyledTableCell} { 402 | background-color: #e6f4fc; 403 | } 404 | `} 405 | 406 | ${props => props.active && css` 407 | ${StyledTableCell} { 408 | background-color: #fcf8da; 409 | } 410 | `} 411 | `; 412 | 413 | export default Selection; 414 | -------------------------------------------------------------------------------- /styleguide/examples/FixedColumns.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | 3 | ``` -------------------------------------------------------------------------------- /styleguide/examples/FixedHeader.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { title: 'Event Type', dataKey: 'eventType' }, 4 | { title: 'Affected Devices', dataKey: 'affectedDevices' }, 5 | { title: 'Detections', dataKey: 'detections', width: 800 } 6 | ]; 7 | 8 | const data = [ 9 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 10 | { id: 2, eventType: 'Spyware/Grayware Spyware/Grayware Spyware/Grayware Spyware/Grayware', affectedDevices: 20, detections: 634 }, 11 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 12 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 13 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 14 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 15 | ]; 16 | 17 | 25 | ``` -------------------------------------------------------------------------------- /styleguide/examples/Hoverable.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | render: (value, row) => { 6 | return ({row.eventType}); 7 | } 8 | }, 9 | { 10 | title: 'Affected Devices', 11 | dataKey: 'affectedDevices', 12 | }, 13 | { 14 | title: 'Detections', 15 | dataKey: 'detections', 16 | } 17 | ]; 18 | 19 | const data = [ 20 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 21 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 22 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 23 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 24 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 25 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 26 | ]; 27 | 28 | 34 | ``` -------------------------------------------------------------------------------- /styleguide/examples/LoaderCustom.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices', 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections', 14 | } 15 | ]; 16 | 17 | const data = [ 18 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 19 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 20 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 21 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 22 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 23 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 24 | ]; 25 | 26 | const renderLoader = () => ( 27 |
41 | Loading.... 42 |
43 | ); 44 | 45 | 52 | ``` -------------------------------------------------------------------------------- /styleguide/examples/LoaderDefault.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices', 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections', 14 | } 15 | ]; 16 | 17 | const data = [ 18 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 19 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 20 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 21 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 22 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 23 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 24 | ]; 25 | 26 | 32 | ``` -------------------------------------------------------------------------------- /styleguide/examples/Minimalism.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | render: (value, row, index) => { 6 | return ( 7 |
8 | { row.eventType } 9 |
10 | ); 11 | } 12 | }, 13 | { 14 | title: 'Affected Devices', 15 | dataKey: 'affectedDevices' 16 | }, 17 | { 18 | title: 'Detections', 19 | width: 300, 20 | dataKey: 'detections' 21 | } 22 | ]; 23 | 24 | const data = [ 25 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 26 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 27 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 28 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 29 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 30 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 31 | ]; 32 | 33 | 39 | ``` 40 | -------------------------------------------------------------------------------- /styleguide/examples/NoDataCustom.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices' 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections' 14 | } 15 | ]; 16 | 17 | ( 20 |
27 | ~ No data to display ~ 28 |
29 | )} 30 | width={800} 31 | /> 32 | ``` -------------------------------------------------------------------------------- /styleguide/examples/NoDataCustomText.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices' 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections' 14 | } 15 | ]; 16 | 17 | 22 | ``` -------------------------------------------------------------------------------- /styleguide/examples/NoDataDefault.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices' 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections' 14 | } 15 | ]; 16 | 17 | 22 | ``` -------------------------------------------------------------------------------- /styleguide/examples/NoHeader.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | const columns = [ 3 | { 4 | title: 'Event Type', 5 | dataKey: 'eventType' 6 | }, 7 | { 8 | title: 'Affected Devices', 9 | dataKey: 'affectedDevices' 10 | }, 11 | { 12 | title: 'Detections', 13 | dataKey: 'detections' 14 | } 15 | ]; 16 | 17 | const data = [ 18 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 19 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 20 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 21 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 22 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 23 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 24 | ]; 25 | 26 | 32 | ``` -------------------------------------------------------------------------------- /styleguide/examples/Pagination.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | initialState = { 3 | pagination: { 4 | page: 1, 5 | pageLength: 10 6 | } 7 | }; 8 | 9 | const columns = [ 10 | { title: 'Event Type', dataKey: 'eventType' }, 11 | { title: 'Affected Devices', dataKey: 'affectedDevices' }, 12 | { title: 'Detections', dataKey: 'detections' } 13 | ]; 14 | 15 | const data = [ 16 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 17 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 18 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 19 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 20 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 21 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 22 | ]; 23 | 24 | const fetchRecords = ({ page, pageLength }) => { 25 | // fetching data 26 | }; 27 | 28 | 29 |
40 | 43 | 54 |
55 | 63 |
64 | ``` 65 | -------------------------------------------------------------------------------- /styleguide/examples/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/styleguide/examples/README.md -------------------------------------------------------------------------------- /styleguide/examples/Selection.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import styled, { css } from 'styled-components'; 3 | import Checkbox from '@trendmicro/react-checkbox'; 4 | import ensureArray from 'ensure-array'; 5 | import _concat from 'lodash/concat'; 6 | import _filter from 'lodash/filter'; 7 | import _get from 'lodash/get'; 8 | import _includes from 'lodash/includes'; 9 | import { Scrollbars } from 'react-custom-scrollbars'; 10 | import { FixedSizeList as ListTable } from 'react-window'; 11 | import { TableWrapper, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '../../src'; 12 | 13 | const data = []; 14 | for (let i = 1; i <= 1000; i++) { 15 | data.push({ 16 | id: i, 17 | eventType: `Virus/Malware_${i}`, 18 | affectedDevices: 20 + i, 19 | detections: 10 + i 20 | }); 21 | } 22 | 23 | const CustomScrollbars = ({ 24 | onScroll, 25 | forwardedRef, 26 | style, 27 | children 28 | }) => { 29 | const refSetter = React.useCallback(scrollbarsRef => { 30 | if (scrollbarsRef) { 31 | forwardedRef(scrollbarsRef); 32 | } else { 33 | forwardedRef(null); 34 | } 35 | }, [forwardedRef]); 36 | 37 | return ( 38 | 43 | {children} 44 | 45 | ); 46 | }; 47 | 48 | const CustomScrollbarsVirtualList = React.forwardRef((props, ref) => ( 49 | 50 | )); 51 | 52 | class Selection extends Component { 53 | constructor(props) { 54 | super(props); 55 | 56 | this.scrollbarRef = React.createRef(); 57 | this.state = { 58 | selectedIdList: [], 59 | }; 60 | } 61 | 62 | handleClickRow = (record, rowIndex) => (e) => { 63 | e.stopPropagation(); 64 | const { selectedIdList } = this.state; 65 | const isIdInSelectedList = _includes(selectedIdList, record.id); 66 | const newSelectedList = isIdInSelectedList 67 | ? _filter(selectedIdList, id => id !== record.id) 68 | : _concat(selectedIdList, record.id); 69 | 70 | this.setState({ selectedIdList: newSelectedList }); 71 | }; 72 | 73 | handleHeaderCheckbox = (e) => { 74 | e.stopPropagation(); 75 | const { checked, indeterminate } = e.target; 76 | const selectAll = (!checked && indeterminate) || checked; 77 | const selectedIdList = selectAll ? data.map(item => item.id) : []; 78 | this.setState({ selectedIdList: selectedIdList }); 79 | }; 80 | 81 | handleRowCheckbox = (e) => { 82 | return false; 83 | }; 84 | 85 | renderHeaderCheckbox = (column) => { 86 | const { selectedIdList } = this.state; 87 | const dataLength = ensureArray(data).length; 88 | const selectedLength = selectedIdList.length; 89 | const isSelectedAll = selectedLength > 0 && selectedLength === dataLength; 90 | const isChecked = (selectedLength > 0 && selectedLength < dataLength) || isSelectedAll; 91 | const isIndeterminate = selectedLength > 0 && selectedLength < dataLength; 92 | 93 | return ( 94 | 102 | ); 103 | }; 104 | 105 | renderCheckbox = (value, row) => { 106 | const checked = _includes(this.state.selectedIdList, row.id); 107 | return ( 108 | 115 | ); 116 | }; 117 | 118 | render() { 119 | const columns = [ 120 | { title: this.renderHeaderCheckbox, dataKey: 'id', render: this.renderCheckbox, width: 38 }, 121 | { title: 'Event Type', dataKey: 'eventType' }, 122 | { title: 'Affected Devices', dataKey: 'affectedDevices' }, 123 | { title: 'Detections', dataKey: 'detections' } 124 | ]; 125 | 126 | return ( 127 | 132 | {({ cells, data, tableWidth }) => { 133 | return ( 134 | 135 | 136 | 137 | { 138 | cells.map((cell, index) => { 139 | const key = `table_header_cell_${index}`; 140 | const { 141 | title, 142 | width: cellWidth, 143 | } = cell; 144 | return ( 145 | 149 | { typeof title === 'function' ? title(cell) : title } 150 | 151 | ); 152 | }) 153 | } 154 | 155 | 156 | 157 | 166 | {({ data, index: rowIndex, style }) => { 167 | const rowData = data[rowIndex]; 168 | const checked = _includes(this.state.selectedIdList, rowData.id); 169 | return ( 170 | 175 | { 176 | cells.map((cell, cellIndex) => { 177 | const key = `table_row${rowIndex}_cell${cellIndex}`; 178 | const { width: cellWidth, render } = cell; 179 | const cellValue = _get(rowData, cell.dataKey); 180 | return ( 181 | 185 | { typeof render === 'function' ? render(cellValue, rowData) : cellValue } 186 | 187 | ); 188 | }) 189 | } 190 | 191 | ); 192 | }} 193 | 194 | 195 | 196 | ); 197 | }} 198 | 199 | ); 200 | } 201 | } 202 | 203 | const StyledTableRow = styled(TableRow)` 204 | &:hover { 205 | background-color: #e6f4fc; 206 | } 207 | 208 | ${props => props.active && css` 209 | background-color: #fcf8da; 210 | `} 211 | `; 212 | 213 | export default Selection; 214 | -------------------------------------------------------------------------------- /styleguide/examples/Selection.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | 3 | ``` -------------------------------------------------------------------------------- /styleguide/examples/Sortable.md: -------------------------------------------------------------------------------- 1 | ```jsx 2 | initialState = { 3 | sortColumnKey: 'eventType', 4 | sortOrder: 'asc' 5 | }; 6 | 7 | const columns = [ 8 | { 9 | sortable: true, 10 | titleText: 'Event Type', 11 | sortKey: 'eventType', 12 | render: (value, row) => { 13 | return ({row.eventType}); 14 | } 15 | }, 16 | { 17 | sortable: true, 18 | titleText: 'Affected Devices', 19 | dataKey: 'affectedDevices', 20 | sortKey: 'affectedDevices' 21 | }, 22 | { 23 | sortable: true, 24 | titleText: 'Detections', 25 | dataKey: 'detections', 26 | sortKey: 'detections' 27 | } 28 | ]; 29 | 30 | const data = [ 31 | { id: 1, eventType: 'Virus/Malware', affectedDevices: 20, detections: 634 }, 32 | { id: 2, eventType: 'Spyware/Grayware', affectedDevices: 20, detections: 634 }, 33 | { id: 3, eventType: 'URL Filtering', affectedDevices: 15, detections: 598 }, 34 | { id: 4, eventType: 'Web Reputation', affectedDevices: 15, detections: 598 }, 35 | { id: 5, eventType: 'Network Virus', affectedDevices: 15, detections: 497 }, 36 | { id: 6, eventType: 'Application Control', affectedDevices: 0, detections: 0 } 37 | ]; 38 | 39 | const toggleSortOrder = (column) => (event) => { 40 | event.stopPropagation(); 41 | event.preventDefault(); 42 | const sortColumnKey = column.sortKey; 43 | const sortOrder = (state.sortOrder === 'desc') ? 'asc' : 'desc'; 44 | setState({ sortColumnKey, sortOrder }); 45 | }; 46 | 47 | const renderSortableTableHeader = (column) => { 48 | const { sortColumnKey, sortOrder } = state; 49 | const isSortingKey = (column.sortKey === sortColumnKey); 50 | const nextSortOrder = sortOrder === 'desc' ? '↑' : '↓'; 51 | return ( 52 |
56 |
57 | { column.titleText } 58 |
59 | { 60 | isSortingKey && 61 |
{ nextSortOrder }
62 | } 63 |
64 | ); 65 | }; 66 | 67 | const sortableColumns = columns.map((column, index) => { 68 | if (column.sortable) { 69 | return { 70 | ...column, 71 | title: renderSortableTableHeader 72 | }; 73 | } 74 | return column; 75 | }); 76 | 77 | const sortableData = _orderBy(data, [state.sortColumnKey], [state.sortOrder]); 78 | 79 | 84 | ``` -------------------------------------------------------------------------------- /styleguide/setup.js: -------------------------------------------------------------------------------- 1 | import '@trendmicro/react-buttons/dist/react-buttons.css'; 2 | import '@trendmicro/react-paginations/dist/react-paginations.css'; 3 | import Anchor from '@trendmicro/react-anchor'; 4 | import { Button } from '@trendmicro/react-buttons'; 5 | import Checkbox from '@trendmicro/react-checkbox'; 6 | import { TablePagination } from '@trendmicro/react-paginations'; 7 | import ensureArray from 'ensure-array'; 8 | import _concat from 'lodash/concat'; 9 | import _filter from 'lodash/filter'; 10 | import _get from 'lodash/get'; 11 | import _includes from 'lodash/includes'; 12 | import _orderBy from 'lodash/orderBy'; 13 | import { Fragment } from 'react'; 14 | import { Scrollbars } from 'react-custom-scrollbars'; 15 | import { FixedSizeList as ListTable } from 'react-window'; 16 | import FormGroup from './components/FormGroup'; 17 | import Text from './components/Text'; 18 | import TableTemplate, { TableWrapper, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } from '../src'; 19 | 20 | import Selection from './examples/Selection'; 21 | import Expand from './examples/Expand'; 22 | import FixedColumns from './examples/FixedColumns'; 23 | 24 | global._concat = _concat; 25 | global._filter = _filter; 26 | global._get = _get; 27 | global._includes = _includes; 28 | global._orderBy = _orderBy; 29 | global.Anchor = Anchor; 30 | global.Button = Button; 31 | global.Checkbox = Checkbox; 32 | global.ensureArray = ensureArray; 33 | global.Fragment = Fragment; 34 | global.FormGroup = FormGroup; 35 | global.Scrollbars = Scrollbars; 36 | global.ListTable = ListTable; 37 | global.TableTemplate = TableTemplate; 38 | global.TableWrapper = TableWrapper; 39 | global.TableHeader = TableHeader; 40 | global.TableBody = TableBody; 41 | global.TableRow = TableRow; 42 | global.TableCell = TableCell; 43 | global.TableHeaderCell = TableHeaderCell; 44 | global.TablePagination = TablePagination; 45 | global.Text = Text; 46 | 47 | global.SelectionTable = Selection; 48 | global.ExpandTable = Expand; 49 | global.FixedColumns = FixedColumns; 50 | -------------------------------------------------------------------------------- /styleguide/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; 3 | font-size: 13px; 4 | } 5 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { test } from 'tap'; 2 | 3 | test('noop', (t) => { 4 | t.end(); 5 | }); 6 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const findImports = require('find-imports'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const webpack = require('webpack'); 5 | const pkg = require('./package.json'); 6 | const babelConfig = require('./babel.config'); 7 | 8 | const publicname = pkg.name.replace(/^@\w+\//, ''); // Strip out "@trendmicro/" from package name 9 | const banner = [ 10 | publicname + ' v' + pkg.version, 11 | '(c) ' + new Date().getFullYear() + ' Trend Micro Inc.', 12 | pkg.license, 13 | pkg.homepage 14 | ].join(' | '); 15 | const localClassPrefix = publicname.replace(/^react-/, ''); // Strip out "react-" from publicname 16 | 17 | module.exports = { 18 | mode: 'development', 19 | devtool: 'source-map', 20 | entry: { 21 | [publicname]: path.resolve(__dirname, 'src/index.js') 22 | }, 23 | output: { 24 | path: path.join(__dirname, 'lib'), 25 | filename: 'index.js', 26 | libraryTarget: 'commonjs2' 27 | }, 28 | externals: [] 29 | .concat(findImports(['src/**/*.{js,jsx}'], { flatten: true })) 30 | .concat(Object.keys(pkg.peerDependencies)) 31 | .concat(Object.keys(pkg.dependencies)), 32 | module: { 33 | rules: [ 34 | { 35 | test: /\.jsx?$/, 36 | loader: 'eslint-loader', 37 | enforce: 'pre', 38 | exclude: /node_modules/ 39 | }, 40 | { 41 | test: /\.jsx?$/, 42 | loader: 'babel-loader', 43 | exclude: /node_modules/, 44 | options: babelConfig 45 | }, 46 | { 47 | test: /\.styl$/, 48 | use: [ 49 | MiniCssExtractPlugin.loader, 50 | { 51 | loader: 'css-loader', 52 | options: { 53 | modules: { 54 | localIdentName: `${localClassPrefix}---[local]---[hash:base64:5]`, 55 | }, 56 | importLoaders: 1, 57 | localsConvention: 'camelCase', 58 | } 59 | }, 60 | 'stylus-loader' 61 | ] 62 | }, 63 | { 64 | test: /\.css$/, 65 | use: [ 66 | 'style-loader', 67 | 'css-loader' 68 | ] 69 | }, 70 | { 71 | test: /\.(png|jpg|svg)$/, 72 | loader: 'url-loader' 73 | }, 74 | { 75 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 76 | loader: 'url-loader', 77 | options: { 78 | limit: 10000, 79 | mimetype: 'application/font-woff' 80 | } 81 | }, 82 | { 83 | test: /\.(ttf|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 84 | loader: 'file-loader' 85 | } 86 | ] 87 | }, 88 | plugins: [ 89 | new webpack.DefinePlugin({ 90 | 'process.env': { 91 | // This has effect on the react lib size 92 | NODE_ENV: JSON.stringify('production') 93 | } 94 | }), 95 | new MiniCssExtractPlugin({ 96 | filename: '../dist/[name].css', 97 | }), 98 | new webpack.BannerPlugin(banner) 99 | ], 100 | resolve: { 101 | extensions: ['.js', '.json', '.jsx'] 102 | } 103 | }; 104 | --------------------------------------------------------------------------------