├── .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 [](https://travis-ci.org/trendmicro-frontend/react-table) [](https://coveralls.io/github/trendmicro-frontend/react-table?branch=master)
2 |
3 | [](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 |
18 |
--------------------------------------------------------------------------------
/docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/3eff5a4e9fb92ca96cbf2fa77649b8c1.ttf
--------------------------------------------------------------------------------
/docs/7942d80077eec2eb49ed4379fc389d70.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/81436770636b45508203b3022075ae73.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/81436770636b45508203b3022075ae73.ttf
--------------------------------------------------------------------------------
/docs/8854c448a7903a7159ef7743634bae8c.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/trendmicro-frontend/react-table/3fb76655418163d3f9f639c4a5bc54412e871231/docs/8a53d21a4d9aa1aac2bf15093bd748c4.woff
--------------------------------------------------------------------------------
/docs/a61e4252663ca88ab29e9ae288b91ef1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
--------------------------------------------------------------------------------