mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 16:30:21 +00:00
Compare commits
349
Commits
v4.2.4
...
v7.0.0-alpha.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d269b95c7 | ||
|
|
b6bd5b25f5 | ||
|
|
4d59f163c0 | ||
|
|
1f877a9c4d | ||
|
|
6c46ea8a07 | ||
|
|
796a3cbc48 | ||
|
|
c0c78d5de7 | ||
|
|
2a6dfb6ebd | ||
|
|
a99ecbd567 | ||
|
|
1ed45d2635 | ||
|
|
96ab761367 | ||
|
|
d2f1563cd5 | ||
|
|
73b4869329 | ||
|
|
3190047284 | ||
|
|
00efc98779 | ||
|
|
379d9e11d5 | ||
|
|
3fff682578 | ||
|
|
168a8e3f93 | ||
|
|
221164484f | ||
|
|
c7d2c36454 | ||
|
|
26961d0994 | ||
|
|
de6389cafc | ||
|
|
e0f9d98819 | ||
|
|
0365057446 | ||
|
|
ec25bfbb5a | ||
|
|
0a1c12faca | ||
|
|
1fddb1b97d | ||
|
|
1e04377de7 | ||
|
|
555c4fae9d | ||
|
|
0d8ca53b76 | ||
|
|
64db8e09d4 | ||
|
|
c5233c4404 | ||
|
|
d0d862f1e8 | ||
|
|
dd3f30b099 | ||
|
|
d5fc894dd7 | ||
|
|
dc771ff25f | ||
|
|
d38021d4b0 | ||
|
|
06648b2577 | ||
|
|
836f256031 | ||
|
|
188fc51336 | ||
|
|
474244ce41 | ||
|
|
b6074a9ab1 | ||
|
|
3278e1b05e | ||
|
|
2352cac4b8 | ||
|
|
d94f59a5fa | ||
|
|
7876a3876c | ||
|
|
02e65b4388 | ||
|
|
d9d49fa3de | ||
|
|
41784cd52d | ||
|
|
9048835bcc | ||
|
|
b12fc8fc88 | ||
|
|
76fde77b0f | ||
|
|
f50b0a47cf | ||
|
|
ac8b497671 | ||
|
|
c79806090a | ||
|
|
f55ce62041 | ||
|
|
4c9b44ab48 | ||
|
|
48869a65e7 | ||
|
|
3246336120 | ||
|
|
b12238d535 | ||
|
|
1f92ed8d4f | ||
|
|
cf59c644b4 | ||
|
|
a7e8e26c00 | ||
|
|
dd4c1e2df6 | ||
|
|
621c3bf092 | ||
|
|
72afabbd03 | ||
|
|
4aa07c33da | ||
|
|
d430d51888 | ||
|
|
4824b4c8cc | ||
|
|
60766cf261 | ||
|
|
fcdc3d88bc | ||
|
|
24a9efd670 | ||
|
|
bdb7d44438 | ||
|
|
7a6ed50b81 | ||
|
|
8f062550aa | ||
|
|
4e9eec3578 | ||
|
|
35b92567b5 | ||
|
|
a1f5b462d5 | ||
|
|
9e479e4d4c | ||
|
|
9fe806d8a7 | ||
|
|
083534149c | ||
|
|
8bd3daafcb | ||
|
|
6649b21abe | ||
|
|
3a8a037ee6 | ||
|
|
9e0361e511 | ||
|
|
0a62d86e5e | ||
|
|
3ff988861e | ||
|
|
f4067b86dd | ||
|
|
f70d453caa | ||
|
|
54f3cc791c | ||
|
|
05c56f0da4 | ||
|
|
ddf877bcac | ||
|
|
928ad11a07 | ||
|
|
0783837786 | ||
|
|
cf0f8bb729 | ||
|
|
1e849df999 | ||
|
|
62da8880e6 | ||
|
|
656522eb71 | ||
|
|
b0c50509ee | ||
|
|
01ca4518fc | ||
|
|
cdf806b3f6 | ||
|
|
0443644ccd | ||
|
|
f74ba3cc16 | ||
|
|
2e8596618a | ||
|
|
b2db31c9bd | ||
|
|
6a5400b247 | ||
|
|
ed6dd5af4c | ||
|
|
80aa2ea7f4 | ||
|
|
b310a2ebc4 | ||
|
|
bcfa658257 | ||
|
|
ad151b5ff0 | ||
|
|
3f1b0d36ed | ||
|
|
a6036004af | ||
|
|
913d214e94 | ||
|
|
2fbd0d49db | ||
|
|
fc47d4f258 | ||
|
|
6df336215d | ||
|
|
feec96359b | ||
|
|
33252adfab | ||
|
|
1cf9e49952 | ||
|
|
b585c4b8db | ||
|
|
710891f0c0 | ||
|
|
cb0000e588 | ||
|
|
d0bd56729b | ||
|
|
e4c819039f | ||
|
|
adc07de817 | ||
|
|
e94e88849a | ||
|
|
2dcfef884f | ||
|
|
d798584208 | ||
|
|
5759885983 | ||
|
|
e59ddc1f8d | ||
|
|
35e8636fd1 | ||
|
|
bb2f969db9 | ||
|
|
cd0222fcd2 | ||
|
|
93ceffbf26 | ||
|
|
879a6ac273 | ||
|
|
b285288849 | ||
|
|
05f4aaef2e | ||
|
|
2b9c546132 | ||
|
|
55ef8a9e7d | ||
|
|
189ee436f9 | ||
|
|
225a74770b | ||
|
|
fa425a162a | ||
|
|
e42b7bbd7d | ||
|
|
9efee144a8 | ||
|
|
56b721c185 | ||
|
|
e91bb91ea1 | ||
|
|
dadfcc121c | ||
|
|
8cec156d13 | ||
|
|
5c531095a9 | ||
|
|
8d4deb3496 | ||
|
|
646b701f93 | ||
|
|
3043cbe4b4 | ||
|
|
fcb82694bb | ||
|
|
e8c557d70e | ||
|
|
ff95c21440 | ||
|
|
2630161222 | ||
|
|
e67072edae | ||
|
|
fb569ad592 | ||
|
|
b957503612 | ||
|
|
3d9b7b1186 | ||
|
|
e3528fef38 | ||
|
|
6b192bd363 | ||
|
|
0725f2159d | ||
|
|
5d2deda246 | ||
|
|
2961eb8a18 | ||
|
|
d170adb6a7 | ||
|
|
d8aec06496 | ||
|
|
8efeef3b8b | ||
|
|
8004e7a85f | ||
|
|
84af88916a | ||
|
|
9846bf1905 | ||
|
|
bf88dda5dc | ||
|
|
7f5779a833 | ||
|
|
8792bd95a0 | ||
|
|
098b1fec51 | ||
|
|
750cfe3ac4 | ||
|
|
370f443347 | ||
|
|
67f8c9adf1 | ||
|
|
867cc25899 | ||
|
|
639630946c | ||
|
|
688afd2947 | ||
|
|
bbebf02a55 | ||
|
|
f4d3c2d7bb | ||
|
|
f8634e771e | ||
|
|
ba74ab3ce8 | ||
|
|
30ed0c82fc | ||
|
|
62f19aecf0 | ||
|
|
8dc4caff45 | ||
|
|
c2e935d75b | ||
|
|
f9a1cfae1e | ||
|
|
5ac2c1779d | ||
|
|
3b2432f535 | ||
|
|
7aa98887ef | ||
|
|
d26d82ecfb | ||
|
|
b76f9c956d | ||
|
|
2e3743b75f | ||
|
|
fdb3e77b00 | ||
|
|
d9248cef2b | ||
|
|
f4704b146c | ||
|
|
004186af24 | ||
|
|
0d0e239baa | ||
|
|
f61b19cb8a | ||
|
|
e1a46fb77e | ||
|
|
a9db5b1ac6 | ||
|
|
846fc0d84d | ||
|
|
87610c83fa | ||
|
|
a58f5c4de2 | ||
|
|
1995bba652 | ||
|
|
680747d1d0 | ||
|
|
e70b29066b | ||
|
|
9d82cee707 | ||
|
|
db1edf119d | ||
|
|
c5fa476b6d | ||
|
|
550a1f7107 | ||
|
|
42e1a12ea9 | ||
|
|
15945d28a1 | ||
|
|
eaa7215674 | ||
|
|
d2835b9e14 | ||
|
|
5691fa2e7a | ||
|
|
f4aba6a008 | ||
|
|
7bce5a8958 | ||
|
|
875d3d93d6 | ||
|
|
a0ede1593b | ||
|
|
0d43b6d601 | ||
|
|
9dbd485cad | ||
|
|
ee5fae8c6f | ||
|
|
95164311fa | ||
|
|
49c5a3d5e1 | ||
|
|
f27ec1fb34 | ||
|
|
dd26802535 | ||
|
|
97e560cb0f | ||
|
|
2ba0bf73ad | ||
|
|
ad7d31cd39 | ||
|
|
ddee18cf86 | ||
|
|
1757b4dd9f | ||
|
|
9d9b1fdee4 | ||
|
|
ab1c020ad3 | ||
|
|
0c3f2f2cd1 | ||
|
|
03538823e9 | ||
|
|
bc7005db99 | ||
|
|
42b5f5bb18 | ||
|
|
5ec616c2a3 | ||
|
|
c16df80938 | ||
|
|
a1e724c483 | ||
|
|
741660c915 | ||
|
|
1f8062501f | ||
|
|
fb51afd0ae | ||
|
|
a64c6a3a76 | ||
|
|
f502ef4e2b | ||
|
|
c58c96c84f | ||
|
|
7202776c86 | ||
|
|
734ba48e9c | ||
|
|
db84ecf789 | ||
|
|
50668fe90f | ||
|
|
52f2c9fdac | ||
|
|
3b39f523be | ||
|
|
9d85981c5b | ||
|
|
b779a98d7a | ||
|
|
1f4c9afd2b | ||
|
|
cef7fbd780 | ||
|
|
e8ebf7c780 | ||
|
|
ac20220d55 | ||
|
|
9acf8f37eb | ||
|
|
64a427d91b | ||
|
|
d21ce226aa | ||
|
|
92c9ae2196 | ||
|
|
23a031b48a | ||
|
|
bd8b273dea | ||
|
|
1a9f0e57b4 | ||
|
|
4c11248c69 | ||
|
|
784311bcd6 | ||
|
|
7b232cd6b4 | ||
|
|
533bea77b6 | ||
|
|
218cf42bb2 | ||
|
|
62287d1641 | ||
|
|
10dc68acc1 | ||
|
|
bd82468852 | ||
|
|
6d28bb2c4d | ||
|
|
9e95b331df | ||
|
|
f296b075fe | ||
|
|
a948a9a41e | ||
|
|
08dbe38ced | ||
|
|
884058b459 | ||
|
|
ee37ad7101 | ||
|
|
6b384f2a90 | ||
|
|
782e9efdfb | ||
|
|
ff952de830 | ||
|
|
149b11674b | ||
|
|
8a362e4d04 | ||
|
|
265a5c8753 | ||
|
|
5155ebad72 | ||
|
|
d7a58450b7 | ||
|
|
bb2527e280 | ||
|
|
6d5207eb4e | ||
|
|
0d397e0c29 | ||
|
|
b15f3ac3b0 | ||
|
|
48ea0df09d | ||
|
|
403fb7a708 | ||
|
|
ecd5658e4b | ||
|
|
3852227bdd | ||
|
|
1fec9b882f | ||
|
|
46c4c6fdb2 | ||
|
|
e702fc80ca | ||
|
|
beaa748187 | ||
|
|
cce77d24f2 | ||
|
|
3984a540c1 | ||
|
|
726fd52753 | ||
|
|
6a6d373526 | ||
|
|
e11fdab457 | ||
|
|
1e4f838326 | ||
|
|
aa48ef57ee | ||
|
|
22f79fcd28 | ||
|
|
ff8e24760a | ||
|
|
f1511bd27e | ||
|
|
4733997c26 | ||
|
|
c3a6e236ae | ||
|
|
83200d4e4c | ||
|
|
5e16c2cb24 | ||
|
|
66cee186a3 | ||
|
|
55bbf4bf37 | ||
|
|
10ce93344c | ||
|
|
114621a359 | ||
|
|
8de0fa420b | ||
|
|
aa77193810 | ||
|
|
92c33de7ac | ||
|
|
c3fb084550 | ||
|
|
04c71f17a8 | ||
|
|
34fa57c4d3 | ||
|
|
de1c8a6156 | ||
|
|
ab9d67d2d9 | ||
|
|
dc0713c1d1 | ||
|
|
c1004ee9e9 | ||
|
|
c0098ebc2c | ||
|
|
33645b4fea | ||
|
|
3d325dad8d | ||
|
|
f3ee89a514 | ||
|
|
90001c1b3a | ||
|
|
8d7b9c2feb | ||
|
|
f754eb0bd5 | ||
|
|
66f1b04ba6 | ||
|
|
bea4b366d6 | ||
|
|
64168eb3ef | ||
|
|
77be7c066f | ||
|
|
e9e69428a2 | ||
|
|
3c039789f1 | ||
|
|
5865f59129 | ||
|
|
c663cce719 | ||
|
|
a6f803c1a3 |
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: 'react-tools',
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
* text=auto
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
# Using v6?
|
||||
|
||||
v6 is now considered feature complete to its current abilities and limitations. We are not actively working to fix any issues for v6 any more. We will, however, merge any non-breaking pull requests submitted to fix anything in v6.
|
||||
|
||||
# Using v7?
|
||||
|
||||
Thanks for using the alpha version of React Table v7! We're very excited about it.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Codesandbox!**
|
||||
Use a new [react-table codesandbox](https://codesandbox.io/s/m5lxzzpz69) to reproduce the issue.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
# STOP
|
||||
|
||||
We do not track feature requests through Github. Please use the [Spectrum React Table Forum](https://spectrum.chat/react-table) to talk about new ideas and features.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Questions, Support & Help
|
||||
about: Looking for help? Need a question answered?
|
||||
|
||||
---
|
||||
|
||||
# STOP
|
||||
|
||||
We do not use Github to track general support. Please use our public support forum at https://spectrum.chat/react-table.
|
||||
|
||||
NOTE: If a support/help/question issue is opened, it will be closed immediately and be redirected to the forum. Thanks for helping us keep our issues clean and productive!
|
||||
+6
-3
@@ -1,8 +1,11 @@
|
||||
node_modules/
|
||||
lib/
|
||||
es/
|
||||
docs/build
|
||||
react-table.js
|
||||
react-table.min.js
|
||||
react-table.css
|
||||
*.log
|
||||
example/dist/
|
||||
|
||||
storybook-static
|
||||
.idea
|
||||
.DS_Store
|
||||
.history
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import React from 'react'
|
||||
import { configure, storiesOf } from '@kadira/storybook'
|
||||
|
||||
import './reset.css'
|
||||
import './fonts.css'
|
||||
import './layout.css'
|
||||
import '../stories/utils/prism.css'
|
||||
import 'github-markdown-css/github-markdown.css'
|
||||
import '../react-table.css'
|
||||
//
|
||||
import Readme from '../README.md'
|
||||
//
|
||||
import Simple from '../stories/Simple.js'
|
||||
import ServerSide from '../stories/ServerSide.js'
|
||||
import SubComponents from '../stories/SubComponents.js'
|
||||
import Pivoting from '../stories/Pivoting.js'
|
||||
import PivotingSubComponents from '../stories/PivotingSubComponents.js'
|
||||
import OneHundredKRows from '../stories/OneHundredKRows.js'
|
||||
//
|
||||
configure(() => {
|
||||
storiesOf('1. Docs')
|
||||
.add('Readme', () => {
|
||||
const ReadmeCmp = React.createClass({
|
||||
render () {
|
||||
return <span className='markdown-body' dangerouslySetInnerHTML={{__html: Readme}} />
|
||||
},
|
||||
componentDidMount () {
|
||||
global.Prism.highlightAll()
|
||||
}
|
||||
})
|
||||
return <ReadmeCmp />
|
||||
})
|
||||
storiesOf('2. Demos')
|
||||
.add('Client-side Data', Simple)
|
||||
.add('Server-side Data', ServerSide)
|
||||
.add('Sub Components', SubComponents)
|
||||
.add('Pivoting & Aggregation', Pivoting)
|
||||
.add('Pivoting & Aggregation w/ Sub Components', PivotingSubComponents)
|
||||
.add('100k Rows w/ Pivoting & Sub Components', OneHundredKRows)
|
||||
}, module)
|
||||
@@ -1,6 +0,0 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,600");
|
||||
body {
|
||||
background: #fff;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
body{
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
/*ol, ul {
|
||||
list-style: none;
|
||||
}*/
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
const path = require('path')
|
||||
|
||||
// load the default config generator.
|
||||
const genDefaultConfig = require('@kadira/storybook/dist/server/config/defaults/webpack.config.js')
|
||||
|
||||
module.exports = (config, env) => {
|
||||
config = genDefaultConfig(config, env)
|
||||
|
||||
Object.assign(config, {
|
||||
module: Object.assign(config.module, {
|
||||
loaders: config.module.loaders.concat([{
|
||||
test: /\.md$/,
|
||||
loader: 'html!markdown',
|
||||
include: path.resolve(__dirname, '../')
|
||||
}, {
|
||||
test: /\.html$/,
|
||||
loader: 'html',
|
||||
query: {
|
||||
minimize: true
|
||||
},
|
||||
include: path.resolve(__dirname, '../')
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
## 6.8.6
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Since `resolveData` is now capable of materializing data on it's own, the `data` prop is no longer required as a prop-type.
|
||||
|
||||
## 6.8.4
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Only run `resolveData` prop when `data` prop has changed, not any others.
|
||||
|
||||
## 6.8.3
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Allow the `resolveData` prop to alter or materialize new data when the `data` prop changes.
|
||||
|
||||
## 6.8.1
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Updated eslint and code formatting
|
||||
|
||||
## 6.7.5
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Now passes `column` to `getResizerProps` (#667)
|
||||
* NOTE: `getResizerProps` is now only called if the column is resizable
|
||||
* Fixes the `className` ordering in defaultProps for ThComponent (#673)
|
||||
* NOTE: user supplied classNames now come at the end so they can extend the defaults
|
||||
|
||||
## 6.7.4
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Fix Prop types for columns
|
||||
|
||||
## 6.7.3
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* Fix the rest of the proptypes
|
||||
|
||||
## 6.7.2
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* `getPropTypes` proptype check
|
||||
|
||||
## 6.7.1
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* `eslint-config` moved to dev deps
|
||||
|
||||
## 6.7.0
|
||||
|
||||
## 6.7.0-alpha-0
|
||||
|
||||
#### New Features
|
||||
|
||||
* Expose page/pageSize to rows/cells
|
||||
* Supply sort direction to custom sort methods
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* README updates
|
||||
* Linter cleanup
|
||||
* Added PropTypes node module
|
||||
* Deps, linting and style upgrades
|
||||
|
||||
## 6.6.0
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* moved repo to react-tools
|
||||
* Doc examples moved to codesandbox.io
|
||||
* README updates
|
||||
* CSS refacting for rt-tfoot to match rt-thead
|
||||
* CSS more specific for input and select
|
||||
|
||||
## 6.5.3
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* `onClick` proxying and eslint
|
||||
|
||||
## 6.5.2
|
||||
|
||||
#### New Features
|
||||
|
||||
* Provide onClick handleOriginal function - #406
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* README updates
|
||||
* `makePathArray` in utils - #326
|
||||
* Various fixes: #294, #376, #398, #415,
|
||||
|
||||
## 6.5.1
|
||||
|
||||
#### Fixes & Optimizations
|
||||
|
||||
* `defaultExpanded` now works correctly - #372
|
||||
* `column.getProps().rest` props are now applied correctly
|
||||
* `makeTemplateComponent` now supports `displayName` - #289
|
||||
|
||||
## 6.5.0
|
||||
|
||||
##### New Features
|
||||
|
||||
* `column.filterAll` - defaults to `false`, but when set to `true` will provide the entire array of rows to `filterMethod` as opposed to one row at a time. This allows for more fine-grained filtering using any method you can dream up. See the [Custom Filtering example](https://react-table.js.org/#/story/custom-filtering) for more info.
|
||||
|
||||
## 6.4.0
|
||||
|
||||
##### New Features
|
||||
|
||||
* `PadRowComponent` - the content rendered inside of a padding row. Defaults to a react component that renders ` `
|
||||
|
||||
## 6.3.0
|
||||
|
||||
##### New Features
|
||||
|
||||
* `defaultSortDesc` - allows you to set the default sorting direction for all columns to descending.
|
||||
* `column.defaultSortDesc` - allows you to set the default sorting direction for a specific column. Falls back to the global `defaultSortDesc` when not set at all.
|
||||
|
||||
## 6.0.0
|
||||
|
||||
##### New Features
|
||||
|
||||
* New Renderers:
|
||||
* `Aggregated` - Custom renderer for aggregated cells
|
||||
* `Pivot` - Custom renderer for Pivoted Cells (utilizes `Expander` and `PivotValue`)
|
||||
* `PivotValue` - Custom renderer for Pivot cell values (deprecates the undocumented `pivotRender` option)
|
||||
* `Expander` - Custom renderer for Pivot cell Expander
|
||||
* Added custom sorting methods per table via `defaultSortMethod` and per column via `column.sortMethod`
|
||||
* Pivot columns are now visibly separate and sorted/filtered independently.
|
||||
* Added `column.resizable` to override global table `resizable` option for specific columns.
|
||||
* Added `column.sortable` to override global table `sortable` option for specific columns.
|
||||
* Added `column.filterable` to override global table `filterable` option for specific columns.
|
||||
* Added `defaultExpanded` table option.
|
||||
* All callbacks can now be utilized without needing to hoist and manage the piece of state they export. That is what their prop counterparts are for, so now the corresponding prop is used instead of the callback to detect a "fully controlled" state.
|
||||
* Prevent transitions while column resizing for a smoother resize effect.
|
||||
* Disable text selection while resizing columns.
|
||||
|
||||
##### Breaking API Changes
|
||||
|
||||
* New Renderers:
|
||||
* `Cell` - deprecates and replaces `render`
|
||||
* `Header` - deprecates and replaces `header`
|
||||
* `Footer` - deprecates and replaces `footer`
|
||||
* `Filter`- deprecates and replaces `filterRender`
|
||||
* Callbacks now provide the destination state as the primary parameter(s). This makes hoisting and controlling the state in redux or component state much easier. eg.
|
||||
* `onSorting` no longer requires you to build your own toggle logic
|
||||
* `onResize` no longer requires you to build your own resize logic
|
||||
* Renamed `onChange` callback -> `onFetchData` which will always fire when a new data model needs to be fetched (or if not using `manual`, when new data is materialized internally).
|
||||
* Renamed `filtering` -> `filtered`
|
||||
* Renamed `sorting` -> `sorted`
|
||||
* Renamed `expandedRows` -> `expanded`
|
||||
* Renamed `resizing` -> `resized`
|
||||
* Renamed `defaultResizing` -> `defaultResized`
|
||||
* Renamed `defaultFiltering` -> `defaultFiltered`
|
||||
* Renamed `defaultSorting` -> `defaultSorted`
|
||||
* Renamed `onSortingChange` -> `onSortedChange`
|
||||
* Renamed `onFilteringChange` -> `onFilteredChange`
|
||||
* Renamed `onResize` -> `onResizedChange`
|
||||
* Renamed `onExpandRow` -> `onExpandedChange`
|
||||
* Renamed `showFilters` -> `filterable`
|
||||
* Renamed `hideFilter` -> `filterable` (Column option. Note the true/false value is now flipped.)
|
||||
* `cellInfo.row` and `rowInfo.row` now reference the materialize data for the table. To reference the original row, use `cellInfo.original` and `rowInfo.original`
|
||||
* Removed `pivotRender` column option. You can now control how the value is displayed by overriding the `PivotValueComponent` or the individual column's `PivotValue` renderer. See [Pivoting Options Story](https://react-table.js.org/?selectedKind=2.%20Demos&selectedStory=Pivoting%20Options&full=0&down=1&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel) for a reference on how to customize pivot column rendering.
|
||||
@@ -1,13 +1,6 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/tannerlinsley/react-table" target="\_parent"><img src="https://github.com/tannerlinsley/react-table/raw/master/media/Banner.png" alt="React Table Logo" style="width:450px;"/></a>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
</div>
|
||||
|
||||
# React Table
|
||||
`react-table` is a **lightweight, fast and extendable datagrid** built for React
|
||||
|
||||
Hooks for building **lightweight, fast and extendable datagrids** for React
|
||||
|
||||
<a href="https://travis-ci.org/tannerlinsley/react-table" target="\_parent">
|
||||
<img alt="" src="https://travis-ci.org/tannerlinsley/react-table.svg?branch=master" />
|
||||
@@ -15,8 +8,8 @@
|
||||
<a href="https://npmjs.com/package/react-table" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/npm/dm/react-table.svg" />
|
||||
</a>
|
||||
<a href="https://react-table-slack.herokuapp.com/" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/badge/slack-react--table-blue.svg" />
|
||||
<a href="https://spectrum.chat/react-table">
|
||||
<img alt="Join the community on Spectrum" src="https://withspectrum.github.io/badge/badge.svg" />
|
||||
</a>
|
||||
<a href="https://github.com/tannerlinsley/react-table" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/github/stars/tannerlinsley/react-table.svg?style=social&label=Star" />
|
||||
@@ -25,386 +18,148 @@
|
||||
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
|
||||
</a>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
⚠️ Version 7 is under active development and is currently in an alpha state. If you are looking for [React Table v6, click here](https://github.com/tannerlinsley/react-table/tree/v6)
|
||||
|
||||
## Features
|
||||
|
||||
- Lightweight at 7kb (and just 2kb more for styles)
|
||||
- Fully customizable JSX templating
|
||||
- Supports both Client-side & Server-side pagination and multi-sorting
|
||||
- Column Pivoting & Aggregation
|
||||
- Minimal design & easily themeable
|
||||
- Fully controllable via optional props and callbacks
|
||||
- <a href="https://medium.com/@tannerlinsley/why-i-wrote-react-table-and-the-problems-it-has-solved-for-nozzle-others-445c4e93d4a8#.axza4ixba" target="\_parent">"Why I wrote React Table and the problems it has solved for Nozzle.io</a> by Tanner Linsley
|
||||
- Lightweight
|
||||
- Headless (Fully customizable)
|
||||
- Client-side & Server-side pagination
|
||||
- Multi-sort
|
||||
- Filters
|
||||
- Pivoting & Aggregation
|
||||
- Fully controllable
|
||||
- Extensible
|
||||
- <a href="https://medium.com/@tannerlinsley/why-i-wrote-react-table-and-the-problems-it-has-solved-for-nozzle-others-445c4e93d4a8#.axza4ixba" target="\_parent">"Why I wrote React Table and the problems it has solved for Nozzle.io"</a> by Tanner Linsley
|
||||
|
||||
## <a href="http://react-table.zabapps.com/?selectedKind=2.%20Demos&selectedStory=Client-side%20Data&full=0&down=1&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel" target="\_parent">Demo</a>
|
||||
## Versions
|
||||
|
||||
- This documentation is for version 7 of react-table.
|
||||
- [View the Changelog](https://github.com/tannerlinsley/react-table/blob/master/CHANGELOG.md)
|
||||
- Previous versions:
|
||||
- [6.x.x Readme](https://github.com/tannerlinsley/react-table/tree/v6/)
|
||||
- [5.x.x Readme](https://github.com/tannerlinsley/react-table/blob/ad7d31cd3978eb45da7c6194dbab93c1e9a8594d/README.md)
|
||||
|
||||
## Sponsors
|
||||
|
||||
**React Table v7** is mostly planned and I (@tannerlinsley) am looking for Patreon support to make it a reality. It will require a decent time commitment on my part to not only implement it, but also help people migrate and continue to maintain it. If you would like to contribute to my Patreon goal for v7 and beyond, [visit my Patreon and help me out!](https://patreon.com/tannerlinsley).
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://patreon.com/tannerlinsley" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/platinum.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=react_table" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/sponsor-retool.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://patreon.com/tannerlinsley" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/gold.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://nozzle.io" target="_blank">
|
||||
<img width="300" src="https://nozzle.io/img/logo-blue.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=react_table" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/gold-placeholder.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://patreon.com/tannerlinsley" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/silver.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=react_table" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/silver-placeholder.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=react_table" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/silver-placeholder.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tryretool.com/?utm_source=sponsor&utm_campaign=react_table" target="_blank">
|
||||
<img src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/silver-placeholder.png">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Example](#example)
|
||||
- [Data](#data)
|
||||
- [Props](#props)
|
||||
- [Columns](#columns)
|
||||
- [Styles](#styles)
|
||||
- [Header Groups](#header-groups)
|
||||
- [Pivoting & Aggregation](#pivoting--aggregation)
|
||||
- [Sub Tables & Sub Components](#sub-tables--sub-components)
|
||||
- [Server-side Data](#server-side-data)
|
||||
- [Fully Controlled Component](#fully-controlled-component)
|
||||
- [Multi-sort](#multi-sort)
|
||||
- [Component Overrides](#component-overrides)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install React Table as a dependency
|
||||
|
||||
```bash
|
||||
# Yarn
|
||||
$ yarn add react-table
|
||||
|
||||
# NPM
|
||||
$ npm install react-table
|
||||
```
|
||||
|
||||
## Example
|
||||
```javascript
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
const data = [{
|
||||
name: 'Tanner Linsley',
|
||||
age: 26,
|
||||
friend: {
|
||||
name: 'Jason Maurer',
|
||||
age: 23,
|
||||
}
|
||||
},{
|
||||
...
|
||||
}]
|
||||
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
accessor: 'name' // String-based value accessors !
|
||||
}, {
|
||||
header: 'Age',
|
||||
accessor: 'age',
|
||||
render: props => <span className='number'>props.value</span> // Custom cell components!
|
||||
}, {
|
||||
header: 'Friend Name',
|
||||
accessor: d => d.friend.name // Custom value accessors!
|
||||
}, {
|
||||
header: props => <span>Friend Age</span>, // Custom header components!
|
||||
accessor: 'friend.age'
|
||||
}]
|
||||
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
/>
|
||||
```
|
||||
|
||||
## Data
|
||||
Simply pass the `data` prop anything that resembles an array or object. Client-side sorting and pagination are built in, and your table will update gracefully as you change any props. [Server-side data](#server-side-data) is also supported!
|
||||
|
||||
|
||||
## Props
|
||||
These are all of the available props (and their default values) for the main `<ReactTable />` component.
|
||||
```javascript
|
||||
{
|
||||
// General
|
||||
loading: false, // Whether to show the loading overlay or not
|
||||
defaultPageSize: 20, // The default page size (this can be changed by the user if `showPageSizeOptions` is enabled)
|
||||
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
|
||||
showPagination: true, // Shows or hides the pagination component
|
||||
showPageJump: true, // Shows or hides the pagination number input
|
||||
showPageSizeOptions: true, // Enables the user to change the page size
|
||||
pageSizeOptions: [5, 10, 20, 25, 50, 100], // The available page size options
|
||||
expanderColumnWidth: 30, // default columnWidth for the expander column
|
||||
|
||||
// Callbacks
|
||||
onChange: (state, instance) => null, // Anytime the internal state of the table changes, this will fire
|
||||
onTrClick: (row, event) => null, // Handler for row click events
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
pageText: 'Page',
|
||||
ofText: 'of',
|
||||
rowsText: 'rows',
|
||||
|
||||
// Classes
|
||||
className: '-striped -highlight', // The most top level className for the component
|
||||
tableClassName: '', // ClassName for the `table` element
|
||||
theadClassName: '', // ClassName for the `thead` element
|
||||
tbodyClassName: '', // ClassName for the `tbody` element
|
||||
trClassName: '', // ClassName for all `tr` elements
|
||||
trClassCallback: row => null, // A call back to dynamically add classes (via the classnames module) to a row element
|
||||
paginationClassName: '' // ClassName for `pagination` element
|
||||
|
||||
// Styles
|
||||
style: {}, // Main style object for the component
|
||||
tableStyle: {}, // style object for the `table` component
|
||||
theadStyle: {}, // style object for the `thead` component
|
||||
tbodyStyle: {}, // style object for the `tbody` component
|
||||
trStyle: {}, // style object for the `tr` component
|
||||
trStyleCallback: row => {}, // A call back to dynamically add styles to a row element
|
||||
thStyle: {}, // style object for the `th` component
|
||||
tdStyle: {}, // style object for the `td` component
|
||||
paginationStyle: {}, // style object for the `paginination` component
|
||||
|
||||
// Controlled Props (see Using as a Fully Controlled Component below)
|
||||
page: undefined,
|
||||
pageSize: undefined,
|
||||
sorting: undefined,
|
||||
expandedRows: undefined,
|
||||
// Controlled Callbacks
|
||||
onExpandRow: undefined,
|
||||
onPageChange: undefined,
|
||||
onPageSizeChange: undefined,
|
||||
}
|
||||
```
|
||||
|
||||
You can easily override the core defaults like so:
|
||||
2. Import the `react-table` module
|
||||
|
||||
```javascript
|
||||
import { ReactTableDefaults } from 'react-table'
|
||||
|
||||
Object.assign(ReactTableDefaults, {
|
||||
defaultPageSize: 10,
|
||||
minRows: 3,
|
||||
// etc...
|
||||
})
|
||||
import { useReactTable } from "react-table";
|
||||
```
|
||||
|
||||
Or just define them on the component per-instance
|
||||
## Examples
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
defaultPageSize={10}
|
||||
minRows={3}
|
||||
// etc...
|
||||
/>
|
||||
```
|
||||
[React Table v7 Sandbox](https://codesandbox.io/s/m5lxzzpz69)
|
||||
|
||||
## Columns
|
||||
`<ReactTable/>` requires a `columns` prop, which is an array of objects containing the following properties
|
||||
|
||||
```javascript
|
||||
[{
|
||||
// General
|
||||
accessor: 'propertyName' or Accessor eg. (row) => row.propertyName,
|
||||
id: 'myProperty', // Conditional - A unique ID is required if the accessor is not a string or if you would like to override the column name used in server-side calls
|
||||
sortable: true,
|
||||
sort: 'asc' or 'desc', // used to determine the column sorting on init
|
||||
show: true, // can be used to hide a column
|
||||
minWidth: 100 // A minimum width for this column. If there is room, columns will flex to fill available space
|
||||
|
||||
// Cell Options
|
||||
className: '', // Set the classname of the `td` element of the column
|
||||
style: {}, // Set the style of the `td` element of the column
|
||||
render: JSX eg. ({value, rowValues, row, index, viewIndex}) => <span>{value}</span>, // Provide a JSX element or stateless function to render whatever you want as the column's cell with access to the entire row
|
||||
// value == the accessed value of the column
|
||||
// rowValues == an object of all of the accessed values for the row
|
||||
// row == the original row of data supplied to the table
|
||||
// index == the original index of the data supplied to the table
|
||||
// viewIndex == the index of the row in the current page
|
||||
|
||||
// Header & HeaderGroup Options
|
||||
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
|
||||
headerClassName: '', // Set the classname of the `th` element of the column
|
||||
headerStyle: {}, // Set the style of the `th` element of the column
|
||||
|
||||
// Header Groups only
|
||||
columns: [...] // See Header Groups section below
|
||||
|
||||
}]
|
||||
```
|
||||
|
||||
## Styles
|
||||
React-table is built to be dropped into existing applications or styled from the ground up, but if you'd like a decent starting point, you can optionally include our default theme `react-table.css`. We think it looks great, honestly :)
|
||||
|
||||
## Header Groups
|
||||
To group columns with another header column, just nest your columns in a header column like so:
|
||||
```javascript
|
||||
const columns = [{
|
||||
header: 'Favorites',
|
||||
columns: [{
|
||||
header: 'Color',
|
||||
accessor: 'favorites.color'
|
||||
}, {
|
||||
header: 'Food',
|
||||
accessor: 'favorites.food'
|
||||
} {
|
||||
header: 'Actor',
|
||||
accessor: 'favorites.actor'
|
||||
}]
|
||||
}]
|
||||
```
|
||||
|
||||
## Pivoting & Aggregation
|
||||
Pivoting the table will group records together based on their accessed values and allow the rows in that group to be expanded underneath it.
|
||||
To pivot, pass an array of `columnID`'s to `pivotBy`. Remember, a column's `id` is either the one that you assign it (when using a custom accessors) or its `accessor` string.
|
||||
```javascript
|
||||
<ReactTable
|
||||
...
|
||||
pivotBy={['lastName', 'age']}
|
||||
/>
|
||||
```
|
||||
|
||||
Naturally when grouping rows together, you may want to aggregate the rows inside it into the grouped column. No aggregation is done by default, however, it is very simple to aggregate any pivoted columns:
|
||||
```javascript
|
||||
// In this example, we use lodash to sum and average the values, but you can use whatever you want to aggregate.
|
||||
const columns = [{
|
||||
header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: (values, rows) => _.round(_.mean(values)),
|
||||
render: row => {
|
||||
// You can even render the cell differently if it's an aggregated cell
|
||||
return <span>{row.aggregated ? `${row.value} (avg)` : row.value}</span>
|
||||
}
|
||||
}, {
|
||||
header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: (values, rows) => _.sum(values)
|
||||
}]
|
||||
```
|
||||
|
||||
Pivoted columns can be sorted just like regular columns, but not independently of each other. For instance, if you click to sort the pivot column in ascending order, it will sort by each pivot recursively in ascending order together.
|
||||
|
||||
## Sub Tables & Sub Components
|
||||
By adding a `SubComponent` props, you can easily add an expansion level to all root-level rows:
|
||||
```javascript
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div>
|
||||
You can put any component you want here, even another React Table! You even have access to the row-level data if you need! Spark-charts, drill-throughs, infographics... the possibilities are endless!
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
## Server-side Data
|
||||
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you.
|
||||
|
||||
1. Feed React Table `data` from somewhere dynamic. eg. `state`, a redux store, etc...
|
||||
1. Add `manual` as a prop. This informs React Table that you'll be handling sorting and pagination server-side
|
||||
1. Subscribe to the `onChange` prop. This function is called at `compomentDidMount` and any time sorting or pagination is changed by the user
|
||||
1. In the `onChange` callback, request your data using the provided information in the params of the function (state and instance)
|
||||
1. Update your data with the rows to be displayed
|
||||
1. Optionally set how many pages there are total
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
...
|
||||
data={this.state.data} // should default to []
|
||||
pages={this.state.pages} // should default to -1 (which means we don't know how many pages we have)
|
||||
loading={this.state.loading}
|
||||
manual // informs React Table that you'll be handling sorting and pagination server-side
|
||||
onChange={(state, instance) => {
|
||||
// show the loading overlay
|
||||
this.setState({loading: true})
|
||||
// fetch your data
|
||||
Axios.post('mysite.com/data', {
|
||||
page: state.page,
|
||||
pageSize: state.pageSize,
|
||||
sorting: state.sorting
|
||||
})
|
||||
.then((res) => {
|
||||
// Update react-table
|
||||
this.setState({
|
||||
data: res.data.rows,
|
||||
pages: res.data.pages,
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
For a detailed example, take a peek at our <a href="https://github.com/tannerlinsley/react-table/blob/master/stories/ServerSide.js" target="\_parent">async table mockup</a>
|
||||
|
||||
## Fully Controlled Component
|
||||
React Table by default works fantastically out of the box, but you can achieve even more control and customization if you choose to maintain the state yourself. It is very easy to do, even if you only want to manage *parts* of the state.
|
||||
|
||||
Here are the props and their corresponding callbacks that control the state of the a table:
|
||||
```javascript
|
||||
<ReactTable
|
||||
// Props
|
||||
page={0} // the index of the page you wish to display
|
||||
pageSize={20} // the number of rows per page to be displayed
|
||||
sorting={[{
|
||||
id: 'lastName',
|
||||
asc: true
|
||||
}, {
|
||||
id: 'firstName',
|
||||
asc: true
|
||||
}]} // the sorting model for the table
|
||||
expandedRows={{
|
||||
1: true,
|
||||
4: true,
|
||||
5: {
|
||||
2: true,
|
||||
3: true
|
||||
}
|
||||
}} // The nested row indexes on the current page that should appear expanded
|
||||
|
||||
// Callbacks
|
||||
onPageChange={(pageIndex) => {...}} // Called when the page index is changed by the user
|
||||
onPageSizeChange={(pageSize, pageIndex) => {...}} // Called when the pageSize is changed by the user. The resolve page is also sent to maintain approximate position in the data
|
||||
onSortingChange={(column, shiftKey) => {...}} // Called when a sortable column header is clicked with the column itself and if the shiftkey was held. If the column is a pivoted column, `column` will be an array of columns
|
||||
onExpandRow={(index, event) => {...}} // Called when an expander is clicked. Use this to manage `expandedRows`
|
||||
/>
|
||||
```
|
||||
|
||||
## Multi-Sort
|
||||
When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
|
||||
|
||||
## Component Overrides
|
||||
Though we confidently stand by the markup and architecture behind it, `react-table` does offer the ability to change the core componentry it uses to render everything. You can extend or override these internal components by passing a react component to it's corresponding prop on either the global props or on a one-off basis like so:
|
||||
```javascript
|
||||
// Change the global default
|
||||
import { ReactTableDefaults } from 'react-table'
|
||||
Object.assign(ReactTableDefaults, {
|
||||
TableComponent: Component,
|
||||
TheadComponent: Component,
|
||||
TbodyComponent: Component,
|
||||
TrGroupComponent: Component,
|
||||
TrComponent: Component,
|
||||
ThComponent: Component,
|
||||
TdComponent: Component,
|
||||
PaginationComponent: Component,
|
||||
PreviousComponent: Component,
|
||||
NextComponent: Component,
|
||||
LoadingComponent: Component,
|
||||
ExpanderComponent: Component
|
||||
})
|
||||
|
||||
// Or change per instance
|
||||
<ReactTable
|
||||
TableComponent={Component},
|
||||
TheadComponent={Component},
|
||||
// etc...
|
||||
/>
|
||||
```
|
||||
|
||||
If you choose to change the core components React-Table uses to render, you must make sure your replacement components consume and utilize all of the supplied and inherited props that are needed for that component to function properly. We would suggest investigating <a href="https://github.com/tannerlinsley/react-table/blob/master/src/index.js" target="\_parent">the source</a> for the component you wish to replace.
|
||||
# Documentation
|
||||
|
||||
Documentation for v7 is coming soon. If you're looking for the [v6 documentation, click here](https://github.com/tannerlinsley/react-table/tree/v6)
|
||||
|
||||
## Contributing
|
||||
|
||||
To suggest a feature, create an issue if it does not already exist.
|
||||
If you would like to help develop a suggested feature follow these steps:
|
||||
|
||||
- Fork this repo
|
||||
- `npm install`
|
||||
- `npm run watch`
|
||||
- Install dependencies with `$ yarn`
|
||||
- Auto-build files as you edit with `$ yarn run watch`
|
||||
- Implement your changes to files in the `src/` directory
|
||||
- Run the <a href="https://github.com/tannerlinsley/react-story">React Story</a> locally with `$ yarn run docs`
|
||||
- View changes as you edit `docs/src`
|
||||
- Submit PR for review
|
||||
|
||||
If you would like to preview your changes as you develop, you can link and utilize our <a href="https://github.com/storybooks/react-storybook" target="\_parent">React Storybook</a> like so:
|
||||
#### Package Utilities
|
||||
|
||||
- `npm install`
|
||||
- `run storybook` (or run them in separate tabs)
|
||||
- Make changes to stories in `/stories`, or create a new one if needed
|
||||
- View changes at `localhost:8000`
|
||||
|
||||
## Used By
|
||||
|
||||
<a href='https://nozzle.io' target="\_parent">
|
||||
<img src='https://nozzle.io/img/logo-blue.png' alt='Nozzle Logo' style='width:300px;'/>
|
||||
</a>
|
||||
- `$ yarn run watch` Watches files and builds via babel
|
||||
- `$ yarn run docs` Runs the storybook server
|
||||
- `$ yarn run test` Runs the test suite
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.1 KiB |
@@ -1,22 +0,0 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script>
|
||||
if (window.parent !== window) {
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
}
|
||||
</script>
|
||||
<title>React Storybook</title>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="error-display"></div>
|
||||
<script src="static/preview.30adfa2464622c0f1904.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="storybook-version" content="2.35.2">
|
||||
<title>React Storybook</title>
|
||||
<style>
|
||||
/*
|
||||
When resizing panels, the drag event breaks if the cursor
|
||||
moves over the iframe. Add the 'dragging' class to the body
|
||||
at drag start and remove it when the drag ends.
|
||||
*/
|
||||
.dragging iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Styling the fuzzy search box placeholders */
|
||||
.searchBox::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchBox::-moz-placeholder { /* Firefox 19+ */
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchBox:focus{
|
||||
border-color: #EEE !important;
|
||||
}
|
||||
|
||||
.btn:hover{
|
||||
background-color: #eee
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0;">
|
||||
<div id="root"></div>
|
||||
<script src="static/manager.32cbfa2b6d56d1f8fbac.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"static/manager.32cbfa2b6d56d1f8fbac.bundle.js","sources":["webpack:///static/manager.32cbfa2b6d56d1f8fbac.bundle.js"],"mappings":"AAAA;AA4zDA;AAg4DA;AAwgEA;AAqiEA;AAk0EA;AA67CA;AA0qDA;AAy4DA;AA8lDA;AA4hDA;AA62DA;AA+/DA;AA6mDA;AAsyCA;AA2gDA;AA+2DA;AA+jEA;AAixDA;AA6lDA;AA+nDA;AA0qEA;AAinDA;AAs3DA;AA80GA;AAhvCA;AAuqGA;AAuqEA;AA6wEA;AAleA;AA+gEA;AA+mCA","sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"static/preview.30adfa2464622c0f1904.bundle.js","sources":["webpack:///static/preview.30adfa2464622c0f1904.bundle.js"],"mappings":"AAAA;AAwxDA;AA45DA;AAo9DA;AA24DA;AAg3CA;AAkoIA;AAgwGA;AA+sPA;AAk6CA;AAq4DA;AA4gDA;AAmpDA;AA05DA;AAkoDA;AAu+CA;AA0rDA;AAiqEA;AAyoDA;AA8xCA;AAmnDA;AAqzDA;AAqhEA;AAqwDA;AAk7DA;AAw1CA;AAisFA;AAw3GA;AAo/CA;AAikCA;AA29BA;AA+vBA;AAy1CA","sourceRoot":""}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
Generated
+8348
File diff suppressed because it is too large
Load Diff
+40
-60
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "4.2.4",
|
||||
"version": "7.0.0-alpha.1",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
@@ -14,74 +14,54 @@
|
||||
"react-table",
|
||||
"datagrid"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"main": "dist/index.js",
|
||||
"_module": "dist/index.es.js",
|
||||
"_jsnext:main": "dist/index.es.js",
|
||||
"scripts": {
|
||||
"test": "cross-env CI=1 react-scripts test --env=jsdom",
|
||||
"test:watch": "react-scripts test --env=jsdom",
|
||||
"build": "rollup -c",
|
||||
"start": "rollup -c -w",
|
||||
"prepare": "yarn build",
|
||||
"release": "yarn publish",
|
||||
"releaseNext": "yarn publish --tag next"
|
||||
},
|
||||
"files": [
|
||||
"src/",
|
||||
"es/",
|
||||
"lib/",
|
||||
"react-table.js",
|
||||
"react-table.min.js",
|
||||
"react-table.css",
|
||||
"media/*.png"
|
||||
],
|
||||
"scripts": {
|
||||
"build:node": "babel src --out-dir lib --source-maps inline",
|
||||
"build:css": "rm -rf react-table.css && stylus src/index.styl --use ./node_modules/nib/lib/nib.js --compress -o react-table.css",
|
||||
"watch": "npm-run-all --parallel watch:*",
|
||||
"watch:node": "onchange 'src/**/*.js' -i -- npm run build:node",
|
||||
"watch:css": "onchange 'src/**/*.styl' -i -- npm run build:css",
|
||||
"test": "standard",
|
||||
"umd": "rm -rf react-table.js && browserify lib/index.js -s reactTable -x react -t babelify -g uglifyify -o react-table.js",
|
||||
"prepublish": "npm-run-all build:* && npm run umd",
|
||||
"storybook": "start-storybook -p 8000 -c .storybook",
|
||||
"docs": "build-storybook -o docs"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.x.x"
|
||||
"prop-types": "^15.5.0",
|
||||
"react": "^16.7.0-alpha.0 || ^16.8.x",
|
||||
"react-dom": "^16.7.0-alpha.0 || ^16.8.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kadira/storybook": "^2.35.1",
|
||||
"babel-cli": "6.14.0",
|
||||
"babel-eslint": "6.1.2",
|
||||
"babel-preset-es2015": "6.14.0",
|
||||
"babel-preset-react": "6.11.1",
|
||||
"babel-preset-stage-2": "6.13.0",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "13.1.0",
|
||||
"github-markdown-css": "^2.4.1",
|
||||
"html-loader": "^0.4.4",
|
||||
"markdown-loader": "^0.1.7",
|
||||
"namor": "^0.3.0",
|
||||
"nib": "^1.1.2",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"onchange": "^3.0.2",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"standard": "^8.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"uglifyify": "3.0.3"
|
||||
},
|
||||
"browserify-shim": {
|
||||
"react": "global:React"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"lib",
|
||||
"example",
|
||||
"react-table.js",
|
||||
"stories",
|
||||
"docs"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-2",
|
||||
"react"
|
||||
]
|
||||
"@babel/plugin-proposal-class-properties": "^7.2.1",
|
||||
"@babel/preset-env": "^7.2.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"@svgr/rollup": "^4.1.0",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"cross-env": "^5.1.4",
|
||||
"eslint": "^5.10.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-config-standard-react": "^7.0.2",
|
||||
"eslint-plugin-import": "^2.13.0",
|
||||
"eslint-plugin-node": "^8.0.0",
|
||||
"eslint-plugin-promise": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.10.0",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"rollup": "^0.68.0",
|
||||
"rollup-plugin-babel": "^4.1.0",
|
||||
"rollup-plugin-commonjs": "^9.1.3",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.0",
|
||||
"rollup-plugin-uglify": "^6.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
import babel from 'rollup-plugin-babel'
|
||||
import replace from 'rollup-plugin-replace'
|
||||
import commonjs from 'rollup-plugin-commonjs'
|
||||
import uglify from 'rollup-plugin-uglify'
|
||||
|
||||
const env = process.env.NODE_ENV
|
||||
|
||||
const config = {
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
file: env === 'production' ? 'react-table.min.js' : 'react-table.js',
|
||||
format: 'umd',
|
||||
globals: {
|
||||
react: 'React',
|
||||
},
|
||||
name: 'ReactTable',
|
||||
exports: 'named',
|
||||
},
|
||||
external: ['react'],
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
babel({
|
||||
exclude: '**/node_modules/**',
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify(env),
|
||||
}),
|
||||
commonjs(),
|
||||
],
|
||||
}
|
||||
|
||||
if (env === 'production') {
|
||||
config.plugins.push(uglify({
|
||||
compress: {
|
||||
dead_code: true,
|
||||
warnings: false,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export default config
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
export function sum (values, rows) {
|
||||
return values.reduce((sum, next) => sum + next, 0)
|
||||
}
|
||||
|
||||
export function average (values, rows) {
|
||||
return Math.round((sum(values, rows) / values.length) * 100) / 100
|
||||
}
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useAccessedRows ({
|
||||
debug, data, columns, subRowsKey,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getAccessedRows')
|
||||
|
||||
// Access the row's data
|
||||
const accessRow = (data, i, depth = 0) => {
|
||||
// Keep the original reference around
|
||||
const original = data
|
||||
|
||||
// Process any subRows
|
||||
const subRows = data[subRowsKey]
|
||||
? data[subRowsKey].map((d, i) => accessRow(d, i, depth + 1))
|
||||
: undefined
|
||||
|
||||
const row = {
|
||||
original,
|
||||
index: i,
|
||||
subRows,
|
||||
depth,
|
||||
}
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
columns.forEach(column => {
|
||||
row.values[column.id] = column.accessor ? column.accessor(data) : undefined
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// Use the resolved data
|
||||
return data.map((d, i) => accessRow(d, i))
|
||||
},
|
||||
[data, columns]
|
||||
)
|
||||
}
|
||||
Executable
+201
@@ -0,0 +1,201 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { getFirstDefined, getBy } from '../utils'
|
||||
|
||||
export default function useColumns ({
|
||||
debug,
|
||||
groupBy,
|
||||
userColumns,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getColumns')
|
||||
|
||||
// Decorate All the columns
|
||||
const columnTree = decorateColumnTree(userColumns)
|
||||
|
||||
// Get the flat list of all columns
|
||||
let columns = flattenBy(columnTree, 'columns')
|
||||
|
||||
columns = [
|
||||
...groupBy.map(g => columns.find(col => col.id === g)),
|
||||
...columns.filter(col => !groupBy.includes(col.id)),
|
||||
]
|
||||
|
||||
// Get headerGroups
|
||||
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
|
||||
const headers = flattenBy(headerGroups, 'headers')
|
||||
|
||||
return {
|
||||
columns,
|
||||
headerGroups,
|
||||
headers,
|
||||
}
|
||||
},
|
||||
[groupBy, userColumns]
|
||||
)
|
||||
|
||||
// Find the depth of the columns
|
||||
function findMaxDepth (columns, depth = 0) {
|
||||
return columns.reduce((prev, curr) => {
|
||||
if (curr.columns) {
|
||||
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
|
||||
}
|
||||
return depth
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function decorateColumn (column, parent) {
|
||||
// First check for string accessor
|
||||
let {
|
||||
id, accessor, Header, canSortBy, canGroupBy, canFilter,
|
||||
} = column
|
||||
|
||||
if (typeof accessor === 'string') {
|
||||
id = id || accessor
|
||||
const accessorString = accessor
|
||||
accessor = row => getBy(row, accessorString)
|
||||
}
|
||||
|
||||
const grouped = groupBy.includes(id)
|
||||
|
||||
canSortBy = accessor
|
||||
? getFirstDefined(canSortBy, disableSorting === true ? false : undefined, true)
|
||||
: false
|
||||
canGroupBy = accessor
|
||||
? getFirstDefined(canGroupBy, disableGrouping === true ? false : undefined, true)
|
||||
: false
|
||||
canFilter = accessor
|
||||
? getFirstDefined(canFilter, disableFilters === true ? false : undefined, true)
|
||||
: false
|
||||
|
||||
if (!id && typeof Header === 'string') {
|
||||
id = Header
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
// Accessor, but no column id? This is bad.
|
||||
console.error(column)
|
||||
throw new Error('A column id is required!')
|
||||
}
|
||||
|
||||
column = {
|
||||
Header: '',
|
||||
Cell: cell => cell.value,
|
||||
show: true,
|
||||
...column,
|
||||
grouped,
|
||||
canSortBy,
|
||||
canGroupBy,
|
||||
canFilter,
|
||||
id,
|
||||
accessor,
|
||||
parent,
|
||||
}
|
||||
|
||||
column.Aggregated = column.Aggregated || column.Cell
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
// Build the visible columns, headers and flat column list
|
||||
function decorateColumnTree (columns, parent, depth = 0) {
|
||||
return columns.map(column => {
|
||||
column = decorateColumn(column, parent)
|
||||
if (column.columns) {
|
||||
column.columns = decorateColumnTree(column.columns, column, depth + 1)
|
||||
}
|
||||
return column
|
||||
})
|
||||
}
|
||||
|
||||
function flattenBy (columns, childKey) {
|
||||
const flatColumns = []
|
||||
|
||||
const recurse = columns => {
|
||||
columns.forEach(d => {
|
||||
if (!d[childKey]) {
|
||||
flatColumns.push(d)
|
||||
} else {
|
||||
recurse(d[childKey])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
recurse(columns)
|
||||
|
||||
return flatColumns
|
||||
}
|
||||
|
||||
// Build the header groups from the bottom up
|
||||
function makeHeaderGroups (columns, maxDepth) {
|
||||
const headerGroups = []
|
||||
|
||||
const removeChildColumns = column => {
|
||||
delete column.columns
|
||||
if (column.parent) {
|
||||
removeChildColumns(column.parent)
|
||||
}
|
||||
}
|
||||
columns.forEach(removeChildColumns)
|
||||
|
||||
const buildGroup = (columns, depth = 0) => {
|
||||
const headerGroup = {
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const parentColumns = []
|
||||
|
||||
const hasParents = columns.some(col => col.parent)
|
||||
|
||||
columns.forEach(column => {
|
||||
const isFirst = !parentColumns.length
|
||||
let latestParentColumn = [...parentColumns].reverse()[0]
|
||||
|
||||
// If the column has a parent, add it if necessary
|
||||
if (column.parent) {
|
||||
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
|
||||
parentColumns.push({
|
||||
...column.parent,
|
||||
originalID: column.parent.id,
|
||||
id: [column.parent.id, parentColumns.length].join('_'),
|
||||
})
|
||||
}
|
||||
} else if (hasParents) {
|
||||
// If other columns have parents, add a place holder if necessary
|
||||
const placeholderColumn = decorateColumn({
|
||||
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
|
||||
id: [column.id, 'placeholder', maxDepth - depth, parentColumns.length].join('_'),
|
||||
})
|
||||
if (isFirst || latestParentColumn.originalID !== placeholderColumn.originalID) {
|
||||
parentColumns.push(placeholderColumn)
|
||||
}
|
||||
}
|
||||
|
||||
// Establish the new columns[] relationship on the parent
|
||||
if (column.parent || hasParents) {
|
||||
latestParentColumn = [...parentColumns].reverse()[0]
|
||||
latestParentColumn.columns = latestParentColumn.columns || []
|
||||
if (!latestParentColumn.columns.includes(column)) {
|
||||
latestParentColumn.columns.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
headerGroup.headers.push(column)
|
||||
})
|
||||
|
||||
headerGroups.push(headerGroup)
|
||||
|
||||
if (parentColumns.length) {
|
||||
buildGroup(parentColumns)
|
||||
}
|
||||
}
|
||||
|
||||
buildGroup(columns)
|
||||
|
||||
return headerGroups.reverse()
|
||||
}
|
||||
}
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
const mergeStateAndProps = (state, defaults, props) => {
|
||||
Object.keys(props).forEach(key => {
|
||||
if (typeof props[key] !== 'undefined') {
|
||||
state[key] = props[key]
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(defaults).forEach(key => {
|
||||
if (typeof state[key] === 'undefined') {
|
||||
state[key] = defaults[key]
|
||||
}
|
||||
})
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default function useControlledState ([userState, userSetState], defaults, props) {
|
||||
const state = mergeStateAndProps(userState, defaults, props)
|
||||
|
||||
const setState = (updater, type) =>
|
||||
userSetState(old => updater(mergeStateAndProps(old, defaults, props)), type)
|
||||
|
||||
return [state, setState]
|
||||
}
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
import { useMemo, useEffect } from 'react'
|
||||
|
||||
import { getBy } from '../utils'
|
||||
|
||||
export default function useExpandedRows ({
|
||||
debug,
|
||||
expanded,
|
||||
expandedKey,
|
||||
columns,
|
||||
rows,
|
||||
setState,
|
||||
actions,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getExpandedRows')
|
||||
|
||||
const expandedRows = []
|
||||
|
||||
// Here we do some mutation, but it's the last stage in the
|
||||
// immutable process so this is safe
|
||||
const handleRow = (row, index, depth = 0, parentPath = []) => {
|
||||
// Compute some final state for the row
|
||||
const path = [...parentPath, index]
|
||||
|
||||
row.path = path
|
||||
row.depth = depth
|
||||
|
||||
row.isExpanded = (row.original && row.original[expandedKey]) || getBy(expanded, path)
|
||||
|
||||
row.cells = columns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
state: null,
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
expandedRows.push(row)
|
||||
|
||||
if (row.isExpanded && row.subRows && row.subRows.length) {
|
||||
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
|
||||
}
|
||||
}
|
||||
|
||||
rows.forEach((row, i) => handleRow(row, i))
|
||||
|
||||
return expandedRows
|
||||
},
|
||||
[rows, expanded, columns]
|
||||
)
|
||||
}
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useFilteredRows ({
|
||||
debug,
|
||||
filters,
|
||||
rows,
|
||||
columns,
|
||||
filterFn,
|
||||
manualFilters,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualFilters || !Object.keys(filters).length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
if (debug) console.info('getFilteredRows')
|
||||
|
||||
// Filters top level and nested rows
|
||||
const filterRows = rows => {
|
||||
let filteredRows = rows
|
||||
|
||||
filteredRows = Object.entries(filters).reduce((filteredSoFar, [columnID, filterValue]) => {
|
||||
// Find the filters column
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
|
||||
// Don't filter hidden columns or columns that have had their filters disabled
|
||||
if (!column || column.filterable === false) {
|
||||
return filteredSoFar
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || filterFn
|
||||
|
||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||
if (column.filterAll) {
|
||||
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||
}
|
||||
return filteredSoFar.filter(row => filterMethod(row, columnID, filterValue, column))
|
||||
}, rows)
|
||||
|
||||
// Apply the filter to any subRows
|
||||
filteredRows = filteredRows.map(row => {
|
||||
if (!row.subRows) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
subRows: filterRows(row.subRows),
|
||||
}
|
||||
})
|
||||
|
||||
// then filter any rows without subcolumns because it would be strange to show
|
||||
filteredRows = filteredRows.filter(row => {
|
||||
if (!row.subRows) {
|
||||
return true
|
||||
}
|
||||
return row.subRows.length > 0
|
||||
})
|
||||
|
||||
return filteredRows
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
},
|
||||
[rows, filters, manualFilters]
|
||||
)
|
||||
}
|
||||
Executable
+77
@@ -0,0 +1,77 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import * as aggregations from '../aggregations'
|
||||
|
||||
export default function useGroupedRows ({
|
||||
debug,
|
||||
groupBy,
|
||||
rows,
|
||||
columns,
|
||||
groupByFn,
|
||||
manualGroupBy,
|
||||
userAggregations,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualGroupBy || !groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getGroupedRows')
|
||||
// Find the columns that can or are aggregating
|
||||
|
||||
// Uses each column to aggregate rows into a single value
|
||||
const aggregateRowsToValues = rows => {
|
||||
const values = {}
|
||||
columns.forEach(column => {
|
||||
const columnValues = rows.map(d => d.values[column.id])
|
||||
const aggregate =
|
||||
userAggregations[column.aggregate] || aggregations[column.aggregate] || column.aggregate
|
||||
if (typeof aggregate === 'function') {
|
||||
values[column.id] = aggregate(columnValues, rows)
|
||||
} else if (aggregate) {
|
||||
throw new Error(
|
||||
`Invalid aggregate "${aggregate}" passed to column with ID: "${column.id}"`
|
||||
)
|
||||
} else {
|
||||
values[column.id] = columnValues[0]
|
||||
}
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Recursively group the data
|
||||
const groupRecursively = (rows, groupBy, depth = 0) => {
|
||||
// This is the last level, just return the rows
|
||||
if (depth >= groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
// Group the rows together for this level
|
||||
const groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
|
||||
([groupByVal, subRows], index) => {
|
||||
// Recurse to sub rows before aggregation
|
||||
subRows = groupRecursively(subRows, groupBy, depth + 1)
|
||||
|
||||
const values = aggregateRowsToValues(subRows)
|
||||
|
||||
const row = {
|
||||
groupByID: groupBy[depth],
|
||||
groupByVal,
|
||||
values,
|
||||
subRows,
|
||||
depth,
|
||||
index,
|
||||
}
|
||||
return row
|
||||
}
|
||||
)
|
||||
|
||||
return groupedRows
|
||||
}
|
||||
|
||||
// Assign the new data
|
||||
return groupRecursively(rows, groupBy)
|
||||
},
|
||||
[rows, groupBy, columns, manualGroupBy]
|
||||
)
|
||||
}
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useSortedRows ({
|
||||
debug,
|
||||
sortBy,
|
||||
rows,
|
||||
columns,
|
||||
orderByFn,
|
||||
sortByFn,
|
||||
manualSorting,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualSorting || !sortBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getSortedRows')
|
||||
|
||||
const sortMethodsByColumnID = {}
|
||||
|
||||
columns
|
||||
.filter(col => col.sortMethod)
|
||||
.forEach(col => {
|
||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||
})
|
||||
|
||||
const sortData = rows => {
|
||||
// Use the orderByFn to compose multiple sortBy's together.
|
||||
// This will also perform a stable sorting using the row index
|
||||
// if needed.
|
||||
const sortedData = orderByFn(
|
||||
rows,
|
||||
sortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const columnSortBy = sortMethodsByColumnID[sort.id]
|
||||
|
||||
// Return the correct sortFn
|
||||
return (a, b) =>
|
||||
(columnSortBy || sortByFn)(a.values[sort.id], b.values[sort.id], sort.desc)
|
||||
}),
|
||||
// Map the directions
|
||||
sortBy.map(d => !d.desc)
|
||||
)
|
||||
|
||||
// TODO: this should be optimized. Not good to loop again
|
||||
sortedData.forEach(row => {
|
||||
if (!row.subRows) {
|
||||
return
|
||||
}
|
||||
row.subRows = sortData(row.subRows)
|
||||
})
|
||||
|
||||
return sortedData
|
||||
}
|
||||
|
||||
return sortData(rows)
|
||||
},
|
||||
[rows, columns, sortBy, manualSorting]
|
||||
)
|
||||
}
|
||||
Executable
+156
@@ -0,0 +1,156 @@
|
||||
import { getBy, setBy, getFirstDefined } from '../utils'
|
||||
|
||||
export default function useTableMethods ({
|
||||
debug,
|
||||
setState,
|
||||
actions,
|
||||
groupBy,
|
||||
columns,
|
||||
defaultSortDesc,
|
||||
filters,
|
||||
}) {
|
||||
const toggleExpandedByPath = (path, set) =>
|
||||
setState(old => {
|
||||
const { expanded } = old
|
||||
const existing = getBy(expanded, path)
|
||||
set = getFirstDefined(set, !existing)
|
||||
return {
|
||||
...old,
|
||||
expanded: setBy(expanded, path, set),
|
||||
}
|
||||
}, actions.toggleExpanded)
|
||||
|
||||
// Updates sorting based on a columnID, desc flag and multi flag
|
||||
const toggleSortByID = (columnID, desc, multi) =>
|
||||
setState(old => {
|
||||
const { sortBy } = old
|
||||
|
||||
// Find the column for this columnID
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const resolvedDefaultSortDesc = getFirstDefined(column.defaultSortDesc, defaultSortDesc)
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
const existingSortBy = sortBy.find(d => d.id === columnID)
|
||||
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
|
||||
|
||||
let newSortBy = []
|
||||
|
||||
// What should we do with this filter?
|
||||
let action
|
||||
|
||||
if (!multi) {
|
||||
if (sortBy.length <= 1 && existingSortBy) {
|
||||
if (existingSortBy.desc) {
|
||||
action = 'remove'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
} else {
|
||||
action = 'replace'
|
||||
}
|
||||
} else if (!existingSortBy) {
|
||||
action = 'add'
|
||||
} else if (hasDescDefined) {
|
||||
action = 'set'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
|
||||
if (action === 'replace') {
|
||||
newSortBy = [
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||
},
|
||||
]
|
||||
} else if (action === 'add') {
|
||||
newSortBy = [
|
||||
...sortBy,
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||
},
|
||||
]
|
||||
} else if (action === 'set') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc,
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'toggle') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc: !existingSortBy.desc,
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'remove') {
|
||||
newSortBy = []
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
sortBy: newSortBy,
|
||||
}
|
||||
}, actions.sortByChange)
|
||||
|
||||
const toggleGroupBy = (id, toggle) =>
|
||||
setState(old => {
|
||||
const resolvedToggle = typeof set !== 'undefined' ? toggle : !groupBy.includes(id)
|
||||
if (resolvedToggle) {
|
||||
return {
|
||||
...old,
|
||||
groupBy: [...groupBy, id],
|
||||
}
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
groupBy: groupBy.filter(d => d !== id),
|
||||
}
|
||||
}, actions.toggleGroupBy)
|
||||
|
||||
const setFilter = (id, val) =>
|
||||
setState(old => {
|
||||
if (typeof val === 'undefined') {
|
||||
const { [id]: prev, ...rest } = filters
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...rest,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...filters,
|
||||
[id]: val,
|
||||
},
|
||||
}
|
||||
}, actions.setFilter)
|
||||
|
||||
const setAllFilters = filters =>
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
filters,
|
||||
}),
|
||||
actions.setAllFilters
|
||||
)
|
||||
|
||||
return {
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
}
|
||||
}
|
||||
Regular → Executable
+8
-986
@@ -1,989 +1,11 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
import _ from './utils'
|
||||
import useReactTable, { actions as mainActions } from './useReactTable'
|
||||
import usePagination, { actions as paginationActions } from './usePagination'
|
||||
import useFlexLayout, { actions as flexLayoutActions } from './useFlexLayout'
|
||||
|
||||
import Pagination from './pagination'
|
||||
|
||||
export const ReactTableDefaults = {
|
||||
// General
|
||||
data: [],
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
pageSizeOptions: [5, 10, 20, 25, 50, 100],
|
||||
defaultPageSize: 20,
|
||||
showPageJump: true,
|
||||
expanderColumnWidth: 35,
|
||||
|
||||
// Controlled State Overrides
|
||||
// page
|
||||
// pageSize
|
||||
// sorting
|
||||
|
||||
// Controlled State Callbacks
|
||||
onExpandSubComponent: undefined,
|
||||
onPageChange: undefined,
|
||||
onPageSizeChange: undefined,
|
||||
onSortingChange: undefined,
|
||||
|
||||
// Pivoting
|
||||
pivotBy: [],
|
||||
pivotColumnWidth: 200,
|
||||
pivotValKey: '_pivotVal',
|
||||
pivotIDKey: '_pivotID',
|
||||
subRowsKey: '_subRows',
|
||||
|
||||
// Pivoting State Overrides
|
||||
// expandedRows: {},
|
||||
|
||||
// Pivoting State Callbacks
|
||||
onExpandRow: undefined,
|
||||
|
||||
// General Callbacks
|
||||
onChange: () => null,
|
||||
onTrClick: () => null,
|
||||
|
||||
// Classes
|
||||
className: '-striped -highlight',
|
||||
tableClassName: '',
|
||||
theadClassName: '',
|
||||
tbodyClassName: '',
|
||||
trClassName: '',
|
||||
trClassCallback: d => null,
|
||||
thClassName: '',
|
||||
thGroupClassName: '',
|
||||
tdClassName: '',
|
||||
paginationClassName: '',
|
||||
|
||||
// Styles
|
||||
style: {},
|
||||
tableStyle: {},
|
||||
theadStyle: {},
|
||||
tbodyStyle: {},
|
||||
trStyle: {},
|
||||
trStyleCallback: d => {},
|
||||
thStyle: {},
|
||||
tdStyle: {},
|
||||
paginationStyle: {},
|
||||
|
||||
// Global Column Defaults
|
||||
column: {
|
||||
sortable: true,
|
||||
show: true,
|
||||
className: '',
|
||||
style: {},
|
||||
headerClassName: '',
|
||||
headerStyle: {},
|
||||
headerInnerClassName: '',
|
||||
headerInnerStyle: {},
|
||||
minWidth: 100
|
||||
},
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingText: 'Loading...',
|
||||
pageText: 'Page',
|
||||
ofText: 'of',
|
||||
rowsText: 'rows',
|
||||
|
||||
// Components
|
||||
TableComponent: _.makeTemplateComponent('rt-table'),
|
||||
TheadComponent: _.makeTemplateComponent('rt-thead'),
|
||||
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
|
||||
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
|
||||
TrComponent: _.makeTemplateComponent('rt-tr'),
|
||||
ThComponent: ({toggleSort, className, children, ...rest}) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, 'rt-th')}
|
||||
onClick={e => {
|
||||
toggleSort && toggleSort(e)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
TdComponent: _.makeTemplateComponent('rt-td'),
|
||||
ExpanderComponent: ({isExpanded, ...rest}) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames('rt-expander', isExpanded && '-open')}
|
||||
{...rest}
|
||||
>•</div>
|
||||
)
|
||||
},
|
||||
PaginationComponent: Pagination,
|
||||
PreviousComponent: undefined,
|
||||
NextComponent: undefined,
|
||||
LoadingComponent: ({loading, loadingText}) => (
|
||||
<div className={classnames('-loading', {'-active': loading})}>
|
||||
<div className='-loading-inner'>
|
||||
{loadingText}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
const actions = {
|
||||
...mainActions,
|
||||
...paginationActions,
|
||||
...flexLayoutActions,
|
||||
}
|
||||
|
||||
export default React.createClass({
|
||||
getDefaultProps () {
|
||||
return ReactTableDefaults
|
||||
},
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
page: 0,
|
||||
pageSize: this.props.defaultPageSize || 10,
|
||||
sorting: [],
|
||||
expandedRows: {}
|
||||
}
|
||||
},
|
||||
|
||||
getResolvedState (props, state) {
|
||||
const resolvedProps = {
|
||||
...this.state,
|
||||
...state,
|
||||
...this.props,
|
||||
...props
|
||||
}
|
||||
return resolvedProps
|
||||
},
|
||||
|
||||
componentWillMount () {
|
||||
this.setStateWithData(this.getDataModel())
|
||||
},
|
||||
|
||||
componentDidMount () {
|
||||
this.fireOnChange()
|
||||
},
|
||||
|
||||
componentWillReceiveProps (nextProps, nextState) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newState = this.getResolvedState(nextProps, nextState)
|
||||
// Props that trigger a data update
|
||||
if (
|
||||
oldState.data !== newState.data ||
|
||||
oldState.columns !== newState.columns ||
|
||||
oldState.pivotBy !== newState.pivotBy ||
|
||||
oldState.sorting !== newState.sorting
|
||||
) {
|
||||
this.setStateWithData(this.getDataModel(nextProps, nextState))
|
||||
}
|
||||
},
|
||||
|
||||
setStateWithData (newState, cb) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newResolvedState = this.getResolvedState({}, newState)
|
||||
if (
|
||||
oldState.resolvedData !== newResolvedState.resolvedData ||
|
||||
oldState.sorting !== newResolvedState.sorting
|
||||
) {
|
||||
Object.assign(newState, this.getSortedData({}, newState))
|
||||
}
|
||||
// Calculate pageSize all the time
|
||||
if (newResolvedState.resolvedData) {
|
||||
newState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.resolvedData.length / newResolvedState.pageSize)
|
||||
}
|
||||
return this.setState(newState, cb)
|
||||
},
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newState = this.getResolvedState(nextProps, nextState)
|
||||
// State changes that trigger a render
|
||||
if (
|
||||
oldState.sortedData !== newState.sortedData ||
|
||||
oldState.page !== newState.page ||
|
||||
oldState.pageSize !== newState.pageSize ||
|
||||
oldState.expandedRows !== newState.expandedRows
|
||||
) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
render () {
|
||||
const resolvedProps = this.getResolvedState()
|
||||
const {
|
||||
className,
|
||||
style,
|
||||
tableClassName,
|
||||
tableStyle,
|
||||
theadGroupClassName,
|
||||
theadStyle,
|
||||
trClassName,
|
||||
trStyle,
|
||||
thClassname,
|
||||
thStyle,
|
||||
theadClassName,
|
||||
tbodyClassName,
|
||||
tbodyStyle,
|
||||
onTrClick,
|
||||
trClassCallback,
|
||||
trStyleCallback,
|
||||
tdStyle,
|
||||
showPagination,
|
||||
paginationClassName,
|
||||
expanderColumnWidth,
|
||||
manual,
|
||||
loadingText,
|
||||
// State
|
||||
loading,
|
||||
pageSize,
|
||||
page,
|
||||
sorting,
|
||||
pages,
|
||||
// Pivoting State
|
||||
pivotBy,
|
||||
pivotValKey,
|
||||
pivotIDKey,
|
||||
subRowsKey,
|
||||
expandedRows,
|
||||
onExpandRow,
|
||||
// Components
|
||||
TableComponent,
|
||||
TheadComponent,
|
||||
TbodyComponent,
|
||||
TrGroupComponent,
|
||||
TrComponent,
|
||||
ThComponent,
|
||||
TdComponent,
|
||||
ExpanderComponent,
|
||||
PaginationComponent,
|
||||
LoadingComponent,
|
||||
SubComponent,
|
||||
columnPercentage,
|
||||
// Data model
|
||||
pivotColumn,
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
headerGroups,
|
||||
standardColumns,
|
||||
allDecoratedColumns,
|
||||
hasHeaderGroups,
|
||||
// Sorted Data
|
||||
sortedData
|
||||
} = resolvedProps
|
||||
|
||||
// Pagination
|
||||
const startRow = pageSize * page
|
||||
const endRow = startRow + pageSize
|
||||
const pageRows = manual ? resolvedData : sortedData.slice(startRow, endRow)
|
||||
const minRows = this.getMinRows()
|
||||
const padRows = pages > 1 ? _.range(pageSize - pageRows.length)
|
||||
: minRows ? _.range(Math.max(minRows - pageRows.length, 0))
|
||||
: []
|
||||
|
||||
const recurseRowsViewIndex = (rows, path = [], index = -1) => {
|
||||
rows.forEach((row, i) => {
|
||||
index++
|
||||
row._viewIndex = index
|
||||
const newPath = path.concat([i])
|
||||
if (row[subRowsKey] && _.get(expandedRows, newPath)) {
|
||||
index = recurseRowsViewIndex(row[subRowsKey], newPath, index)
|
||||
}
|
||||
})
|
||||
return index
|
||||
}
|
||||
|
||||
recurseRowsViewIndex(pageRows)
|
||||
|
||||
const canPrevious = page > 0
|
||||
const canNext = page + 1 < pages
|
||||
|
||||
const rowWidth = (SubComponent ? expanderColumnWidth : 0) + _.sum(allVisibleColumns.map(d => d.minWidth))
|
||||
|
||||
let rowIndex = -1
|
||||
|
||||
const makeHeaderGroup = () => (
|
||||
<TheadComponent
|
||||
className={classnames(theadGroupClassName, '-headerGroups')}
|
||||
style={Object.assign({}, theadStyle, {
|
||||
minWidth: `${rowWidth}px`
|
||||
})}
|
||||
>
|
||||
<TrComponent
|
||||
className={trClassName}
|
||||
style={trStyle}
|
||||
>
|
||||
{pivotBy.length ? (
|
||||
<ThComponent
|
||||
className={classnames(thClassname, 'rt-pivot-header')}
|
||||
style={_.prefixAll({
|
||||
flex: `${columnPercentage} 0 auto`,
|
||||
width: `${pivotColumn.minWidth}px`
|
||||
})}
|
||||
/>
|
||||
) : SubComponent ? (
|
||||
<ThComponent
|
||||
className={classnames(thClassname, 'rt-expander-header')}
|
||||
style={_.prefixAll({
|
||||
flex: `0 0 auto`,
|
||||
width: `${expanderColumnWidth}px`
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
{headerGroups.map((column, i) => {
|
||||
return (
|
||||
<ThComponent
|
||||
key={i}
|
||||
className={classnames(thClassname, column.headerClassName)}
|
||||
style={Object.assign({}, thStyle, column.headerStyle, _.prefixAll({
|
||||
flex: `${column.columns.length * columnPercentage} 0 auto`,
|
||||
width: `${_.sum(column.columns.map(d => d.minWidth))}px`
|
||||
}))}
|
||||
>
|
||||
{typeof column.header === 'function' ? (
|
||||
<column.header
|
||||
data={sortedData}
|
||||
column={column}
|
||||
/>
|
||||
) : column.header}
|
||||
</ThComponent>
|
||||
)
|
||||
})}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
|
||||
const makeHeader = () => {
|
||||
const pivotSort = pivotColumn && sorting.find(d => d.id === pivotColumn.id)
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames(theadClassName, '-header')}
|
||||
style={Object.assign({}, theadStyle, {
|
||||
minWidth: `${rowWidth}px`
|
||||
})}
|
||||
>
|
||||
<TrComponent
|
||||
className={trClassName}
|
||||
style={trStyle}
|
||||
>
|
||||
{pivotBy.length ? (
|
||||
<ThComponent
|
||||
className={classnames(
|
||||
thClassname,
|
||||
'rt-pivot-header',
|
||||
pivotSort ? (pivotSort.asc ? '-sort-asc' : '-sort-desc') : '',
|
||||
pivotColumn.sortable && '-cursor-pointer'
|
||||
)}
|
||||
style={_.prefixAll({
|
||||
flex: `${columnPercentage} 0 auto`,
|
||||
width: `${pivotColumn.minWidth}px`
|
||||
})}
|
||||
toggleSort={(e) => {
|
||||
pivotColumn.sortable && this.sortColumn(pivotColumn.pivotColumns, e.shiftKey)
|
||||
}}
|
||||
>
|
||||
{pivotColumn.pivotColumns.map((column, i) => {
|
||||
return (
|
||||
<span key={column.id}>
|
||||
{typeof column.header === 'function' ? (
|
||||
<column.header
|
||||
data={sortedData}
|
||||
column={column}
|
||||
/>
|
||||
) : column.header}
|
||||
{i < pivotColumn.pivotColumns.length - 1 && (
|
||||
<ExpanderComponent />
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</ThComponent>
|
||||
) : SubComponent ? (
|
||||
<ThComponent
|
||||
className={classnames(thClassname, 'rt-expander-header')}
|
||||
style={_.prefixAll({
|
||||
flex: `0 0 auto`,
|
||||
width: `${expanderColumnWidth}px`
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
{standardColumns.map(makeHeaderGroupColumn)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
const makeHeaderGroupColumn = (column, i) => {
|
||||
const sort = sorting.find(d => d.id === column.id)
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<ThComponent
|
||||
key={i}
|
||||
className={classnames(
|
||||
thClassname,
|
||||
column.headerClassName,
|
||||
sort ? (sort.asc ? '-sort-asc' : '-sort-desc') : '',
|
||||
{
|
||||
'-cursor-pointer': column.sortable,
|
||||
'-hidden': !show
|
||||
}
|
||||
)}
|
||||
style={Object.assign({}, thStyle, column.headerStyle, _.prefixAll({
|
||||
flex: `${columnPercentage} 0 auto`,
|
||||
width: `${column.minWidth}px`
|
||||
}))}
|
||||
toggleSort={(e) => {
|
||||
column.sortable && this.sortColumn(column, e.shiftKey)
|
||||
}}
|
||||
>
|
||||
{typeof column.header === 'function' ? (
|
||||
<column.header
|
||||
data={sortedData}
|
||||
column={column}
|
||||
/>
|
||||
) : column.header}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePageRow = (row, i, path = []) => {
|
||||
const rowInfo = {
|
||||
row: row.__original,
|
||||
rowValues: row,
|
||||
index: row.__index,
|
||||
viewIndex: ++rowIndex,
|
||||
level: path.length,
|
||||
nestingPath: path.concat([i]),
|
||||
aggregated: !!row[subRowsKey],
|
||||
subRows: row[subRowsKey]
|
||||
}
|
||||
const isExpanded = _.get(expandedRows, rowInfo.nestingPath)
|
||||
const rowPivotColumn = allDecoratedColumns.find(d => d.id === row[pivotIDKey])
|
||||
const PivotCell = rowPivotColumn && rowPivotColumn.pivotRender
|
||||
return (
|
||||
<TrGroupComponent key={rowInfo.nestingPath.join('_')}>
|
||||
<TrComponent
|
||||
onClick={event => onTrClick(rowInfo.row, event)}
|
||||
className={classnames(trClassName, trClassCallback(rowInfo), row._viewIndex % 2 ? '-even' : '-odd')}
|
||||
style={Object.assign({}, trStyle, trStyleCallback(rowInfo))}
|
||||
>
|
||||
{(pivotBy.length || SubComponent) && (
|
||||
<TdComponent
|
||||
className={classnames(thClassname, 'rt-pivot')}
|
||||
style={_.prefixAll({
|
||||
paddingLeft: rowInfo.nestingPath.length === 1 ? undefined : `${30 * (rowInfo.nestingPath.length - 1)}px`,
|
||||
flex: `${pivotColumn ? columnPercentage : 0} 0 auto`,
|
||||
width: `${pivotColumn ? pivotColumn.minWidth : expanderColumnWidth}px`
|
||||
})}
|
||||
onClick={(e) => {
|
||||
if (onExpandRow) {
|
||||
return onExpandRow(rowInfo.nestingPath, e)
|
||||
}
|
||||
let newExpandedRows = _.clone(expandedRows)
|
||||
if (isExpanded) {
|
||||
return this.setStateWithData({
|
||||
expandedRows: _.set(newExpandedRows, rowInfo.nestingPath, false)
|
||||
})
|
||||
}
|
||||
return this.setStateWithData({
|
||||
expandedRows: _.set(newExpandedRows, rowInfo.nestingPath, {})
|
||||
})
|
||||
}}
|
||||
>
|
||||
{rowInfo.subRows ? (
|
||||
<span>
|
||||
<ExpanderComponent
|
||||
isExpanded={isExpanded}
|
||||
/>
|
||||
{rowPivotColumn && rowPivotColumn.pivotRender ? (
|
||||
<PivotCell
|
||||
{...rowInfo}
|
||||
value={rowInfo.rowValues[pivotValKey]}
|
||||
/>
|
||||
) : <span>{row[pivotValKey]} ({rowInfo.subRows.length})</span>}
|
||||
</span>
|
||||
) : SubComponent ? (
|
||||
<span>
|
||||
<ExpanderComponent
|
||||
isExpanded={isExpanded}
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
</TdComponent>
|
||||
)}
|
||||
{standardColumns.map((column, i2) => {
|
||||
const Cell = column.render
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<TdComponent
|
||||
key={i2}
|
||||
className={classnames(column.className, {hidden: !show})}
|
||||
style={Object.assign({}, tdStyle, column.style, _.prefixAll({
|
||||
flex: `${columnPercentage} 0 auto`,
|
||||
width: `${column.minWidth}px`
|
||||
}))}
|
||||
>
|
||||
{typeof Cell === 'function' ? (
|
||||
<Cell
|
||||
{...rowInfo}
|
||||
value={rowInfo.rowValues[column.id]}
|
||||
/>
|
||||
) : typeof Cell !== 'undefined' ? Cell
|
||||
: rowInfo.rowValues[column.id]}
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
</TrComponent>
|
||||
{(
|
||||
rowInfo.subRows &&
|
||||
isExpanded &&
|
||||
rowInfo.subRows.map((d, i) => makePageRow(d, i, rowInfo.nestingPath))
|
||||
)}
|
||||
{SubComponent && !rowInfo.subRows && isExpanded && SubComponent(rowInfo)}
|
||||
</TrGroupComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePadRow = (row, i) => {
|
||||
return (
|
||||
<TrGroupComponent
|
||||
key={i}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(trClassName, '-padRow')}
|
||||
style={trStyle}
|
||||
>
|
||||
{SubComponent && (
|
||||
<ThComponent
|
||||
className={classnames(thClassname, 'rt-expander-header')}
|
||||
style={_.prefixAll({
|
||||
flex: `0 0 auto`,
|
||||
width: `${expanderColumnWidth}px`
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{standardColumns.map((column, i2) => {
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
return (
|
||||
<TdComponent
|
||||
key={i2}
|
||||
className={classnames(column.className, {hidden: !show})}
|
||||
style={Object.assign({}, tdStyle, column.style, {
|
||||
flex: `${columnPercentage} 0 auto`,
|
||||
width: `${column.minWidth}px`
|
||||
})}
|
||||
>
|
||||
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
</TrComponent>
|
||||
</TrGroupComponent>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, 'ReactTable')}
|
||||
style={style}
|
||||
>
|
||||
<TableComponent
|
||||
className={classnames(tableClassName)}
|
||||
style={tableStyle}
|
||||
>
|
||||
{hasHeaderGroups && makeHeaderGroup()}
|
||||
{makeHeader()}
|
||||
<TbodyComponent
|
||||
className={classnames(tbodyClassName)}
|
||||
style={Object.assign({}, tbodyStyle, {
|
||||
minWidth: `${rowWidth}px`
|
||||
})}
|
||||
>
|
||||
{pageRows.map((d, i) => makePageRow(d, i))}
|
||||
{padRows.map(makePadRow)}
|
||||
</TbodyComponent>
|
||||
</TableComponent>
|
||||
{showPagination && (
|
||||
<PaginationComponent
|
||||
{...resolvedProps}
|
||||
pages={pages}
|
||||
canPrevious={canPrevious}
|
||||
canNext={canNext}
|
||||
onPageChange={this.onPageChange}
|
||||
onPageSizeChange={this.onPageSizeChange}
|
||||
className={paginationClassName}
|
||||
/>
|
||||
)}
|
||||
<LoadingComponent
|
||||
loading={loading}
|
||||
loadingText={loadingText}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
// Helpers
|
||||
getDataModel (nextProps, nextState) {
|
||||
const {
|
||||
columns,
|
||||
pivotBy,
|
||||
data,
|
||||
pivotIDKey,
|
||||
pivotValKey,
|
||||
subRowsKey
|
||||
} = this.getResolvedState(nextProps, nextState)
|
||||
|
||||
// Determine Header Groups
|
||||
let hasHeaderGroups = false
|
||||
columns.forEach(column => {
|
||||
if (column.columns) {
|
||||
hasHeaderGroups = true
|
||||
}
|
||||
})
|
||||
|
||||
// Build Header Groups
|
||||
const headerGroups = []
|
||||
let currentSpan = []
|
||||
|
||||
// A convenience function to add a header and reset the currentSpan
|
||||
const addHeader = (columns, column = {}) => {
|
||||
headerGroups.push(Object.assign({}, column, {
|
||||
columns: columns
|
||||
}))
|
||||
currentSpan = []
|
||||
}
|
||||
|
||||
// Decorate the columns
|
||||
const decorateAndAddToAll = (col) => {
|
||||
const decoratedColumn = this.makeDecoratedColumn(col)
|
||||
allDecoratedColumns.push(decoratedColumn)
|
||||
return decoratedColumn
|
||||
}
|
||||
let allDecoratedColumns = []
|
||||
const decoratedColumns = columns.map((column, i) => {
|
||||
if (column.columns) {
|
||||
return {
|
||||
...column,
|
||||
columns: column.columns.map(decorateAndAddToAll)
|
||||
}
|
||||
} else {
|
||||
return decorateAndAddToAll(column)
|
||||
}
|
||||
})
|
||||
|
||||
// Build the visible columns and headers and flat column list
|
||||
let visibleColumns = decoratedColumns.slice()
|
||||
let allVisibleColumns = []
|
||||
|
||||
visibleColumns = visibleColumns.map((column, i) => {
|
||||
if (column.columns) {
|
||||
const visibleSubColumns = column.columns.filter(d => pivotBy.indexOf(d.id) > -1 ? false : _.getFirstDefined(d.show, true))
|
||||
return {
|
||||
...column,
|
||||
columns: visibleSubColumns
|
||||
}
|
||||
}
|
||||
return column
|
||||
})
|
||||
|
||||
visibleColumns = visibleColumns.filter(column => {
|
||||
return column.columns ? column.columns.length : pivotBy.indexOf(column.id) > -1 ? false : _.getFirstDefined(column.show, true)
|
||||
})
|
||||
|
||||
// Build allVisible columns and HeaderGroups
|
||||
visibleColumns.forEach((column, i) => {
|
||||
if (column.columns) {
|
||||
allVisibleColumns = allVisibleColumns.concat(column.columns)
|
||||
if (currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
addHeader(column.columns, column)
|
||||
return
|
||||
}
|
||||
allVisibleColumns.push(column)
|
||||
currentSpan.push(column)
|
||||
})
|
||||
if (hasHeaderGroups && currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
|
||||
// Move the pivot columns into a single column if needed
|
||||
if (pivotBy.length) {
|
||||
const pivotColumns = []
|
||||
for (var i = 0; i < allDecoratedColumns.length; i++) {
|
||||
if (pivotBy.indexOf(allDecoratedColumns[i].id) > -1) {
|
||||
pivotColumns.push(allDecoratedColumns[i])
|
||||
}
|
||||
}
|
||||
allVisibleColumns.unshift({
|
||||
...pivotColumns[0],
|
||||
pivotColumns
|
||||
})
|
||||
}
|
||||
|
||||
// Determine the flex percentage for each column
|
||||
const columnPercentage = 100 / allVisibleColumns.length
|
||||
|
||||
// Access the data
|
||||
let resolvedData = data.map((d, i) => {
|
||||
const row = {
|
||||
__original: d,
|
||||
__index: i
|
||||
}
|
||||
allDecoratedColumns.forEach(column => {
|
||||
row[column.id] = column.accessor(d)
|
||||
})
|
||||
return row
|
||||
})
|
||||
|
||||
// If pivoting, recursively group the data
|
||||
const aggregate = (rows) => {
|
||||
const aggregationValues = {}
|
||||
aggregatingColumns.forEach(column => {
|
||||
const values = rows.map(d => d[column.id])
|
||||
aggregationValues[column.id] = column.aggregate(values, rows)
|
||||
})
|
||||
return aggregationValues
|
||||
}
|
||||
let standardColumns = pivotBy.length ? allVisibleColumns.slice(1) : allVisibleColumns
|
||||
const aggregatingColumns = standardColumns.filter(d => d.aggregate)
|
||||
let pivotColumn
|
||||
if (pivotBy.length) {
|
||||
pivotColumn = allVisibleColumns[0]
|
||||
const groupRecursively = (rows, keys, i = 0) => {
|
||||
// This is the last level, just return the rows
|
||||
if (i === keys.length) {
|
||||
return rows
|
||||
}
|
||||
// Group the rows together for this level
|
||||
let groupedRows = Object.entries(
|
||||
_.groupBy(rows, keys[i]))
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
[pivotIDKey]: keys[i],
|
||||
[pivotValKey]: key,
|
||||
[keys[i]]: key,
|
||||
[subRowsKey]: value
|
||||
}
|
||||
}
|
||||
)
|
||||
// Recurse into the subRows
|
||||
groupedRows = groupedRows.map(rowGroup => {
|
||||
let subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
|
||||
return {
|
||||
...rowGroup,
|
||||
[subRowsKey]: subRows,
|
||||
...aggregate(subRows)
|
||||
}
|
||||
})
|
||||
return groupedRows
|
||||
}
|
||||
resolvedData = groupRecursively(resolvedData, pivotBy)
|
||||
}
|
||||
|
||||
return {
|
||||
resolvedData,
|
||||
columnPercentage,
|
||||
pivotColumn,
|
||||
allVisibleColumns,
|
||||
headerGroups,
|
||||
standardColumns,
|
||||
allDecoratedColumns,
|
||||
hasHeaderGroups
|
||||
}
|
||||
},
|
||||
|
||||
getSortedData (nextProps, nextState) {
|
||||
const {
|
||||
manual,
|
||||
sorting,
|
||||
allDecoratedColumns,
|
||||
resolvedData
|
||||
} = this.getResolvedState(nextProps, nextState)
|
||||
|
||||
const resolvedSorting = sorting.length ? sorting : this.getInitSorting(allDecoratedColumns)
|
||||
|
||||
// Resolve the data from either manual data or sorted data
|
||||
return {
|
||||
sortedData: manual ? resolvedData : this.sortData(resolvedData, resolvedSorting)
|
||||
}
|
||||
},
|
||||
|
||||
fireOnChange () {
|
||||
this.props.onChange(this.getResolvedState(), this)
|
||||
},
|
||||
getPropOrState (key) {
|
||||
return _.getFirstDefined(this.props[key], this.state[key])
|
||||
},
|
||||
getStateOrProp (key) {
|
||||
return _.getFirstDefined(this.state[key], this.props[key])
|
||||
},
|
||||
getInitSorting (columns) {
|
||||
if (!columns) {
|
||||
return []
|
||||
}
|
||||
const initSorting = columns.filter(d => {
|
||||
return typeof d.sort !== 'undefined'
|
||||
}).map(d => {
|
||||
return {
|
||||
id: d.id,
|
||||
asc: d.sort === 'asc'
|
||||
}
|
||||
})
|
||||
|
||||
return initSorting.length ? initSorting : [{
|
||||
id: columns[0].id,
|
||||
asc: true
|
||||
}]
|
||||
},
|
||||
sortData (data, sorting) {
|
||||
const sorted = _.sortBy(data, sorting.map(sort => {
|
||||
return row => {
|
||||
if (row[sort.id] === null || row[sort.id] === undefined) {
|
||||
return -Infinity
|
||||
}
|
||||
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
|
||||
}
|
||||
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
|
||||
|
||||
return sorted.map(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
[this.props.subRowsKey]: this.sortData(row[this.props.subRowsKey], sorting)
|
||||
}
|
||||
})
|
||||
},
|
||||
makeDecoratedColumn (column) {
|
||||
const dcol = Object.assign({}, this.props.column, column)
|
||||
|
||||
if (typeof dcol.accessor === 'string') {
|
||||
dcol.id = dcol.id || dcol.accessor
|
||||
const accessorString = dcol.accessor
|
||||
dcol.accessor = row => _.get(row, accessorString)
|
||||
return dcol
|
||||
}
|
||||
|
||||
if (dcol.accessor && !dcol.id) {
|
||||
console.warn(dcol)
|
||||
throw new Error('A column id is required if using a non-string accessor for column above.')
|
||||
}
|
||||
|
||||
if (!dcol.accessor) {
|
||||
dcol.accessor = d => undefined
|
||||
}
|
||||
|
||||
return dcol
|
||||
},
|
||||
getMinRows () {
|
||||
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
|
||||
},
|
||||
|
||||
// User actions
|
||||
onPageChange (page) {
|
||||
const { onPageChange } = this.props
|
||||
if (onPageChange) {
|
||||
return onPageChange(page)
|
||||
}
|
||||
this.setStateWithData({
|
||||
expandedRows: {},
|
||||
page
|
||||
}, () => {
|
||||
this.fireOnChange()
|
||||
})
|
||||
},
|
||||
onPageSizeChange (newPageSize) {
|
||||
const { onPageSizeChange } = this.props
|
||||
const { pageSize, page } = this.getResolvedState()
|
||||
|
||||
// Normalize the page to display
|
||||
const currentRow = pageSize * page
|
||||
const newPage = Math.floor(currentRow / newPageSize)
|
||||
|
||||
if (onPageSizeChange) {
|
||||
return onPageSizeChange(newPageSize, newPage)
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
pageSize: newPageSize,
|
||||
page: newPage
|
||||
}, () => {
|
||||
this.fireOnChange()
|
||||
})
|
||||
},
|
||||
sortColumn (column, additive) {
|
||||
const { sorting } = this.getResolvedState()
|
||||
const { onSortingChange } = this.props
|
||||
if (onSortingChange) {
|
||||
return onSortingChange(column, additive)
|
||||
}
|
||||
let newSorting = _.clone(sorting || [])
|
||||
if (_.isArray(column)) {
|
||||
const existingIndex = newSorting.findIndex(d => d.id === column[0].id)
|
||||
if (existingIndex > -1) {
|
||||
const existing = newSorting[existingIndex]
|
||||
if (existing.asc) {
|
||||
column.forEach((d, i) => {
|
||||
newSorting[existingIndex + i].asc = false
|
||||
})
|
||||
} else {
|
||||
if (additive) {
|
||||
newSorting.splice(existingIndex, column.length)
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorting[existingIndex + i].asc = true
|
||||
})
|
||||
}
|
||||
}
|
||||
if (!additive) {
|
||||
newSorting = newSorting.slice(existingIndex, column.length)
|
||||
}
|
||||
} else {
|
||||
if (additive) {
|
||||
newSorting = newSorting.concat(column.map(d => ({
|
||||
id: d.id,
|
||||
asc: true
|
||||
})))
|
||||
} else {
|
||||
newSorting = column.map(d => ({
|
||||
id: d.id,
|
||||
asc: true
|
||||
}))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const existingIndex = newSorting.findIndex(d => d.id === column.id)
|
||||
if (existingIndex > -1) {
|
||||
const existing = newSorting[existingIndex]
|
||||
if (existing.asc) {
|
||||
existing.asc = false
|
||||
if (!additive) {
|
||||
newSorting = [existing]
|
||||
}
|
||||
} else {
|
||||
if (additive) {
|
||||
newSorting.splice(existingIndex, 1)
|
||||
} else {
|
||||
existing.asc = true
|
||||
newSorting = [existing]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (additive) {
|
||||
newSorting.push({
|
||||
id: column.id,
|
||||
asc: true
|
||||
})
|
||||
} else {
|
||||
newSorting = [{
|
||||
id: column.id,
|
||||
asc: true
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setStateWithData({
|
||||
page: ((!sorting.length && newSorting.length) || !additive) ? 0 : this.state.page,
|
||||
sorting: newSorting
|
||||
}, () => {
|
||||
this.fireOnChange()
|
||||
})
|
||||
}
|
||||
})
|
||||
export { useReactTable, usePagination, useFlexLayout, actions }
|
||||
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
@import 'nib'
|
||||
|
||||
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940)
|
||||
$easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
$expandSize = 7px
|
||||
|
||||
.ReactTable
|
||||
position:relative
|
||||
border: 1px solid alpha(black, .1)
|
||||
*
|
||||
box-sizing: border-box
|
||||
.rt-table
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
overflow: auto
|
||||
|
||||
.rt-thead
|
||||
display: flex
|
||||
flex-direction: column
|
||||
&.-headerGroups
|
||||
background: alpha(black, .03)
|
||||
border-bottom: 1px solid alpha(black, .05)
|
||||
|
||||
&.-header
|
||||
box-shadow: 0 2px 15px 0px alpha(black, .15)
|
||||
|
||||
.rt-tr
|
||||
text-align:center
|
||||
|
||||
.rt-th
|
||||
.rt-td
|
||||
color: black
|
||||
border-right: 1px solid alpha(black, .05)
|
||||
transition box-shadow .3s $easeOutBack
|
||||
box-shadow:inset 0 0 0 0 transparent
|
||||
&.-sort-asc
|
||||
box-shadow:inset 0 3px 0 0 alpha(black, .6)
|
||||
&.-sort-desc
|
||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
||||
&.-cursor-pointer
|
||||
cursor: pointer
|
||||
&:last-child
|
||||
border-right: 0
|
||||
.rt-tbody
|
||||
display: flex
|
||||
flex-direction: column
|
||||
// z-index:0
|
||||
.rt-tr-group
|
||||
border-bottom: solid 1px alpha(black, .05)
|
||||
&:last-child
|
||||
border-bottom: 0
|
||||
.rt-td
|
||||
border-right:1px solid alpha(black, .02)
|
||||
&:last-child
|
||||
border-right:0
|
||||
.rt-pivot
|
||||
cursor: pointer
|
||||
.rt-tr-group
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
.rt-tr
|
||||
display: inline-flex
|
||||
.rt-th
|
||||
.rt-td
|
||||
flex: 1 0 0px
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
padding: 7px 5px
|
||||
overflow: hidden
|
||||
transition: .3s ease
|
||||
transition-property: width, min-width, padding, opacity
|
||||
|
||||
&.-hidden
|
||||
width: 0 !important
|
||||
min-width: 0 !important
|
||||
padding: 0 !important
|
||||
border:0 !important
|
||||
opacity: 0 !important
|
||||
|
||||
.rt-expander
|
||||
display: inline-block
|
||||
position:relative
|
||||
margin: 0
|
||||
color: transparent
|
||||
margin: 0 10px
|
||||
&:after
|
||||
content: ''
|
||||
position: absolute
|
||||
width: 0
|
||||
height: 0
|
||||
top:50%
|
||||
left:50%
|
||||
transform: translate(-50%, -50%) rotate(-90deg)
|
||||
border-left: ($expandSize * .72) solid transparent
|
||||
border-right: ($expandSize * .72) solid transparent
|
||||
border-top: $expandSize solid alpha(black, .8)
|
||||
transition: all .3s $easeOutBack
|
||||
cursor: pointer
|
||||
&.-open:after
|
||||
transform: translate(-50%, -50%) rotate(0deg)
|
||||
|
||||
&.-striped
|
||||
.rt-tr.-odd
|
||||
background: alpha(black, .03)
|
||||
&.-highlight
|
||||
.rt-tbody
|
||||
.rt-tr:not(.-padRow):hover
|
||||
background: alpha(black, .05)
|
||||
|
||||
.-pagination
|
||||
z-index: 1
|
||||
display:flex
|
||||
justify-content: space-between
|
||||
align-items: stretch
|
||||
flex-wrap: wrap
|
||||
padding: 3px
|
||||
box-shadow: 0 0px 15px 0px alpha(black, .1)
|
||||
|
||||
.-btn
|
||||
appearance:none
|
||||
display:block
|
||||
width:100%
|
||||
height: 100%
|
||||
border: 0
|
||||
border-radius: 3px
|
||||
padding: 6px
|
||||
font-size: 1em
|
||||
color: alpha(black, .6)
|
||||
background: alpha(black, .1)
|
||||
transition: all .1s ease
|
||||
cursor: pointer
|
||||
outline:none
|
||||
|
||||
&[disabled]
|
||||
opacity: .5
|
||||
cursor: default
|
||||
|
||||
&:not([disabled]):hover
|
||||
background: alpha(black, .3)
|
||||
color: white
|
||||
|
||||
.-previous
|
||||
.-next
|
||||
flex: 1
|
||||
text-align: center
|
||||
|
||||
.-center
|
||||
flex: 1.5
|
||||
text-align:center
|
||||
margin-bottom:0
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
align-items: center
|
||||
justify-content: space-around
|
||||
|
||||
.-pageInfo
|
||||
display: inline-block
|
||||
margin: 3px 10px
|
||||
white-space: nowrap
|
||||
|
||||
.-pageJump
|
||||
display:inline-block
|
||||
input
|
||||
width: 70px
|
||||
text-align:center
|
||||
|
||||
.-pageSizeOptions
|
||||
margin: 3px 10px
|
||||
|
||||
|
||||
.-loading
|
||||
display:block
|
||||
position:absolute
|
||||
left:0
|
||||
right:0
|
||||
top:0
|
||||
bottom:0
|
||||
background: alpha(white, .8)
|
||||
transition: all .3s ease
|
||||
z-index: 2
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
> div
|
||||
position:absolute
|
||||
display: block
|
||||
text-align:center
|
||||
width:100%
|
||||
top:50%
|
||||
left: 0
|
||||
font-size: 15px
|
||||
color: alpha(black, .6)
|
||||
transform: translateY(-52%)
|
||||
transition: all .3s $easeOutQuad
|
||||
|
||||
&.-active
|
||||
opacity: 1
|
||||
pointer-events: all
|
||||
> div
|
||||
transform: translateY(50%)
|
||||
|
||||
input
|
||||
select
|
||||
appearance: none
|
||||
border: 1px solid rgba(0,0,0,0.1)
|
||||
background: white
|
||||
padding: 5px 7px
|
||||
font-size: inherit
|
||||
border-radius: 3px
|
||||
font-weight: normal
|
||||
outline:none
|
||||
|
||||
.select-wrap
|
||||
position:relative
|
||||
display:inline-block
|
||||
select
|
||||
padding: 5px 15px 5px 7px
|
||||
min-width:100px
|
||||
&:after
|
||||
content: ''
|
||||
position: absolute
|
||||
right: 8px
|
||||
top: 50%
|
||||
transform: translate(0, -50%)
|
||||
border-color: #999 transparent transparent
|
||||
border-style: solid
|
||||
border-width: 5px 5px 2.5px
|
||||
@@ -1,123 +0,0 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
// import _ from './utils'
|
||||
|
||||
const defaultButton = (props) => (
|
||||
<button {...props} className='-btn'>{props.children}</button>
|
||||
)
|
||||
|
||||
export default React.createClass({
|
||||
getInitialState () {
|
||||
return {
|
||||
page: this.props.page
|
||||
}
|
||||
},
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState({page: nextProps.page})
|
||||
},
|
||||
getSafePage (page) {
|
||||
return Math.min(Math.max(page, 0), this.props.pages - 1)
|
||||
},
|
||||
changePage (page) {
|
||||
page = this.getSafePage(page)
|
||||
this.setState({page})
|
||||
this.props.onPageChange(page)
|
||||
},
|
||||
applyPage (e) {
|
||||
e && e.preventDefault()
|
||||
const page = this.state.page
|
||||
this.changePage(page === '' ? this.props.page : page)
|
||||
},
|
||||
render () {
|
||||
const {
|
||||
// Computed
|
||||
pages,
|
||||
// Props
|
||||
page,
|
||||
showPageSizeOptions,
|
||||
pageSizeOptions,
|
||||
pageSize,
|
||||
showPageJump,
|
||||
canPrevious,
|
||||
canNext,
|
||||
onPageSizeChange,
|
||||
className,
|
||||
PreviousComponent = defaultButton,
|
||||
NextComponent = defaultButton
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, '-pagination')}
|
||||
style={this.props.paginationStyle}
|
||||
>
|
||||
<div className='-previous'>
|
||||
<PreviousComponent
|
||||
onClick={(e) => {
|
||||
if (!canPrevious) return
|
||||
this.changePage(page - 1)
|
||||
}}
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
{this.props.previousText}
|
||||
</PreviousComponent>
|
||||
</div>
|
||||
<div className='-center'>
|
||||
<span className='-pageInfo'>
|
||||
{this.props.pageText} {showPageJump ? (
|
||||
<form className='-pageJump'
|
||||
onSubmit={this.applyPage}
|
||||
>
|
||||
<input
|
||||
type={this.state.page === '' ? 'text' : 'number'}
|
||||
onChange={e => {
|
||||
const val = e.target.value
|
||||
const page = val - 1
|
||||
if (val === '') {
|
||||
return this.setState({page: val})
|
||||
}
|
||||
this.setState({page: this.getSafePage(page)})
|
||||
}}
|
||||
value={this.state.page === '' ? '' : this.state.page + 1}
|
||||
onBlur={this.applyPage}
|
||||
/>
|
||||
</form>
|
||||
) : (
|
||||
<span className='-currentPage'>{page + 1}</span>
|
||||
)} {this.props.ofText} <span className='-totalPages'>{pages}</span>
|
||||
</span>
|
||||
{showPageSizeOptions && (
|
||||
<span className='select-wrap -pageSizeOptions'>
|
||||
<select
|
||||
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
||||
value={pageSize}
|
||||
>
|
||||
{pageSizeOptions.map((option, i) => {
|
||||
return (
|
||||
<option
|
||||
key={i}
|
||||
value={option}>
|
||||
{option} {this.props.rowsText}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='-next'>
|
||||
<NextComponent
|
||||
onClick={(e) => {
|
||||
if (!canNext) return
|
||||
this.changePage(page + 1)
|
||||
}}
|
||||
disabled={!canNext}
|
||||
>
|
||||
{this.props.nextText}
|
||||
</NextComponent>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
Executable
+195
@@ -0,0 +1,195 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { getFirstDefined, sum } from './utils'
|
||||
|
||||
const propTypes = {
|
||||
defaultFlex: PropTypes.number,
|
||||
}
|
||||
|
||||
export const actions = {}
|
||||
|
||||
export default function useFlexLayout (api, props = {}) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useFlexLayout')
|
||||
|
||||
const {
|
||||
hooks: {
|
||||
getRowProps, getHeaderRowProps, getHeaderProps, getCellProps,
|
||||
},
|
||||
visibleColumns,
|
||||
} = api
|
||||
|
||||
const { defaultFlex = 1 } = props
|
||||
|
||||
const [columnMeasurements, setColumnMeasurements] = useState({})
|
||||
|
||||
const rowStyles = useMemo(
|
||||
() => {
|
||||
let sumWidth = 0
|
||||
visibleColumns.forEach(column => {
|
||||
const { width, minWidth } = getSizesForColumn(
|
||||
column,
|
||||
defaultFlex,
|
||||
undefined,
|
||||
undefined,
|
||||
api
|
||||
)
|
||||
if (width) {
|
||||
sumWidth += width
|
||||
} else if (minWidth) {
|
||||
sumWidth += minWidth
|
||||
} else {
|
||||
sumWidth += defaultFlex
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
style: {
|
||||
display: 'flex',
|
||||
minWidth: `${sumWidth}px`,
|
||||
},
|
||||
}
|
||||
},
|
||||
[visibleColumns]
|
||||
)
|
||||
|
||||
getRowProps.push(row => rowStyles)
|
||||
getHeaderRowProps.push(row => rowStyles)
|
||||
|
||||
getHeaderProps.push(column => ({
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(column, columnMeasurements, defaultFlex, api),
|
||||
},
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[key] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// },
|
||||
}))
|
||||
|
||||
getCellProps.push(cell => ({
|
||||
style: {
|
||||
display: 'block',
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(cell.column, columnMeasurements, defaultFlex, undefined, api),
|
||||
},
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[columnPathStr] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// }
|
||||
}))
|
||||
|
||||
return {
|
||||
rowStyles,
|
||||
}
|
||||
}
|
||||
|
||||
function getStylesForColumn (column, columnMeasurements, defaultFlex, api) {
|
||||
const { flex, width, maxWidth } = getSizesForColumn(column, columnMeasurements, defaultFlex, api)
|
||||
|
||||
return {
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`,
|
||||
}
|
||||
}
|
||||
|
||||
function getSizesForColumn (
|
||||
{
|
||||
columns, id, width, minWidth, maxWidth,
|
||||
},
|
||||
columnMeasurements,
|
||||
defaultFlex,
|
||||
api
|
||||
) {
|
||||
if (columns) {
|
||||
columns = columns
|
||||
.map(column => getSizesForColumn(column, columnMeasurements, defaultFlex, api))
|
||||
.filter(Boolean)
|
||||
|
||||
if (!columns.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const flex = sum(columns.map(col => col.flex))
|
||||
const width = sum(columns.map(col => col.width))
|
||||
const maxWidth = sum(columns.map(col => col.maxWidth))
|
||||
|
||||
return {
|
||||
flex,
|
||||
width,
|
||||
maxWidth,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
flex: width ? 0 : defaultFlex,
|
||||
width:
|
||||
width === 'auto'
|
||||
? columnMeasurements[id] || defaultFlex
|
||||
: getFirstDefined(width, minWidth, defaultFlex),
|
||||
maxWidth,
|
||||
}
|
||||
}
|
||||
|
||||
// const resetRefs = () => {
|
||||
// if (debug) console.info("resetRefs");
|
||||
// renderedCellInfoRef.current = {};
|
||||
// };
|
||||
|
||||
// const calculateAutoWidths = () => {
|
||||
// RAF(() => {
|
||||
// const newColumnMeasurements = {};
|
||||
// Object.values(renderedCellInfoRef.current).forEach(({ column, el }) => {
|
||||
// if (!el) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let measurement = 0;
|
||||
|
||||
// const measureChildren = children => {
|
||||
// if (children) {
|
||||
// [].slice.call(children).forEach(child => {
|
||||
// measurement = Math.max(
|
||||
// measurement,
|
||||
// Math.ceil(child.offsetWidth) || 0
|
||||
// );
|
||||
// measureChildren(child.children);
|
||||
// });
|
||||
// }
|
||||
// return measurement;
|
||||
// };
|
||||
|
||||
// const parentDims = getElementDimensions(el);
|
||||
// measureChildren(el.children);
|
||||
|
||||
// newColumnMeasurements[column.id] = Math.max(
|
||||
// newColumnMeasurements[column.id] || 0,
|
||||
// measurement + parentDims.paddingLeft + parentDims.paddingRight
|
||||
// );
|
||||
// });
|
||||
|
||||
// const oldKeys = Object.keys(columnMeasurements);
|
||||
// const newKeys = Object.keys(newColumnMeasurements);
|
||||
|
||||
// const needsUpdate =
|
||||
// oldKeys.length !== newKeys.length ||
|
||||
// oldKeys.some(key => {
|
||||
// return columnMeasurements[key] !== newColumnMeasurements[key];
|
||||
// });
|
||||
|
||||
// if (needsUpdate) {
|
||||
// setState(old => {
|
||||
// return {
|
||||
// ...old,
|
||||
// columnMeasurements: newColumnMeasurements
|
||||
// };
|
||||
// }, actions.updateAutoWidth);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
Executable
+155
@@ -0,0 +1,155 @@
|
||||
import { useMemo, useState, useLayoutEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { warnUnknownProps } from './utils'
|
||||
import useControlledState from './hooks/useControlledState'
|
||||
|
||||
const propTypes = {
|
||||
defaultPageSize: PropTypes.number,
|
||||
defaultPageIndex: PropTypes.number,
|
||||
pageSize: PropTypes.number,
|
||||
pages: PropTypes.number,
|
||||
pageIndex: PropTypes.number,
|
||||
onStateChange: PropTypes.func,
|
||||
stateReducer: PropTypes.func,
|
||||
subRowsKey: PropTypes.string,
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
pageChange: '__pageChange__',
|
||||
}
|
||||
|
||||
export default function usePagination (
|
||||
{
|
||||
debug: parentDebug, rows, prepareRows, groupedRows, sortedRows, filteredRows,
|
||||
},
|
||||
props = {}
|
||||
) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
|
||||
|
||||
// Destructure props
|
||||
const {
|
||||
defaultPageSize = 10,
|
||||
defaultPageIndex = 0,
|
||||
pageSize: userPageSize,
|
||||
pageIndex: userPageIndex,
|
||||
state: userState,
|
||||
manualPagination,
|
||||
pageCount: userPageCount,
|
||||
pageOptions: userPageOptions,
|
||||
debug = parentDebug,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
warnUnknownProps(rest)
|
||||
|
||||
const defaultState = useState({})
|
||||
const localState = userState || defaultState
|
||||
|
||||
// Build the controllable state
|
||||
const [{ pageSize, pageIndex }, setState] = useControlledState(
|
||||
localState,
|
||||
{
|
||||
pageSize: defaultPageSize,
|
||||
pageIndex: defaultPageIndex,
|
||||
},
|
||||
{
|
||||
pageSize: userPageSize,
|
||||
pageIndex: userPageIndex,
|
||||
}
|
||||
)
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (manualPagination) {
|
||||
return
|
||||
}
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
pageIndex: 0,
|
||||
}),
|
||||
actions.pageChange
|
||||
)
|
||||
},
|
||||
[groupedRows, sortedRows, filteredRows]
|
||||
)
|
||||
|
||||
const { pages, pageCount } = useMemo(
|
||||
() => {
|
||||
if (manualPagination) {
|
||||
return {
|
||||
pages: [rows],
|
||||
pageCount: userPageCount,
|
||||
}
|
||||
}
|
||||
if (debug) console.info('getPages')
|
||||
|
||||
// Create a new pages with the first page ready to go.
|
||||
const pages = rows.length ? [] : [[]]
|
||||
|
||||
// Start the pageIndex and currentPage cursors
|
||||
let cursor = 0
|
||||
while (cursor < rows.length) {
|
||||
const end = cursor + pageSize
|
||||
pages.push(rows.slice(cursor, end))
|
||||
cursor = end
|
||||
}
|
||||
|
||||
const pageCount = pages.length
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageCount,
|
||||
pageOptions,
|
||||
}
|
||||
},
|
||||
[rows, pageSize, userPageCount]
|
||||
)
|
||||
|
||||
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
|
||||
const page = manualPagination ? rows : pages[pageIndex] || []
|
||||
const canPreviousPage = pageIndex > 0
|
||||
const canNextPage = pageIndex < pageCount - 1
|
||||
|
||||
const gotoPage = pageIndex => {
|
||||
if (debug) console.info('gotoPage')
|
||||
return setState(old => {
|
||||
if (pageIndex < 0 || pageIndex > pageCount - 1) {
|
||||
return old
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
pageIndex,
|
||||
}
|
||||
}, actions.pageChange)
|
||||
}
|
||||
|
||||
const previousPage = () => gotoPage(pageIndex - 1)
|
||||
|
||||
const nextPage = () => gotoPage(pageIndex + 1)
|
||||
|
||||
const setPageSize = pageSize => {
|
||||
setState(old => ({
|
||||
...old,
|
||||
pageSize,
|
||||
}))
|
||||
}
|
||||
|
||||
prepareRows(page)
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageIndex,
|
||||
pageOptions,
|
||||
page,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize,
|
||||
}
|
||||
}
|
||||
Executable
+470
@@ -0,0 +1,470 @@
|
||||
import { useState, useMemo, useRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
//
|
||||
import {
|
||||
defaultOrderByFn,
|
||||
defaultSortByFn,
|
||||
defaultGroupByFn,
|
||||
defaultFilterFn,
|
||||
flexRender,
|
||||
findExpandedDepth,
|
||||
applyHooks,
|
||||
mergeProps,
|
||||
warnUnknownProps,
|
||||
} from './utils'
|
||||
|
||||
// Internal Hooks
|
||||
import useControlledState from './hooks/useControlledState'
|
||||
import useColumns from './hooks/useColumns'
|
||||
import useTableMethods from './hooks/useTableMethods'
|
||||
import useAccessedRows from './hooks/useAccessedRows'
|
||||
import useGroupedRows from './hooks/useGroupedRows'
|
||||
import useFilteredRows from './hooks/useFilteredRows'
|
||||
import useSortedRows from './hooks/useSortedRows'
|
||||
import useExpandedRows from './hooks/useExpandedRows'
|
||||
|
||||
const defaultFlex = 1
|
||||
const renderErr =
|
||||
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
|
||||
|
||||
export const actions = {
|
||||
sortByChange: '__sortByChange',
|
||||
updateAutoWidth: '__updateAutoWidth__',
|
||||
toggleGroupBy: '__toggleGroupBy__',
|
||||
toggleExpanded: '__toggleExpanded__',
|
||||
setExpanded: '__setExpanded__',
|
||||
setFilter: '__setFilter__',
|
||||
setAllFilters: '__setAllFilters__',
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
data: PropTypes.any,
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
aggregate: PropTypes.func,
|
||||
filterFn: PropTypes.func,
|
||||
filterAll: PropTypes.bool,
|
||||
sortByFn: PropTypes.func,
|
||||
resolvedDefaultSortDesc: PropTypes.bool,
|
||||
canSortBy: PropTypes.bool,
|
||||
canGroupBy: PropTypes.bool,
|
||||
Cell: PropTypes.any,
|
||||
Header: PropTypes.any,
|
||||
Filter: PropTypes.any,
|
||||
})
|
||||
),
|
||||
defaultFilters: PropTypes.array,
|
||||
defaultSortBy: PropTypes.array,
|
||||
defaultGroupBy: PropTypes.array,
|
||||
|
||||
groupBy: PropTypes.array,
|
||||
filters: PropTypes.array,
|
||||
sortBy: PropTypes.array,
|
||||
|
||||
filterFn: PropTypes.func,
|
||||
sortByFn: PropTypes.func,
|
||||
orderByFn: PropTypes.func,
|
||||
groupByFn: PropTypes.func,
|
||||
|
||||
manualGrouping: PropTypes.bool,
|
||||
manualFilters: PropTypes.bool,
|
||||
manualSorting: PropTypes.bool,
|
||||
|
||||
disableGrouping: PropTypes.bool,
|
||||
disableFilters: PropTypes.bool,
|
||||
disableSorting: PropTypes.bool,
|
||||
|
||||
defaultSortDesc: PropTypes.bool,
|
||||
disableMultiSort: PropTypes.bool,
|
||||
subRowsKey: PropTypes.string,
|
||||
expandedKey: PropTypes.string,
|
||||
userAggregations: PropTypes.object,
|
||||
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default function useReactTable (props) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useReactTable')
|
||||
|
||||
// Destructure props
|
||||
const {
|
||||
data = [],
|
||||
state: userState,
|
||||
columns: userColumns,
|
||||
sortBy: userSortBy,
|
||||
groupBy: userGroupBy,
|
||||
filters: userFilters,
|
||||
expanded: userExpanded,
|
||||
defaultSortBy = [],
|
||||
defaultGroupBy = [],
|
||||
defaultFilters = {},
|
||||
defaultExpanded = {},
|
||||
manualGrouping,
|
||||
manualFilters,
|
||||
manualSorting,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
defaultSortDesc,
|
||||
disableMultiSort,
|
||||
subRowsKey = 'subRows',
|
||||
expandedKey = 'expanded',
|
||||
filterFn = defaultFilterFn,
|
||||
sortByFn = defaultSortByFn,
|
||||
orderByFn = defaultOrderByFn,
|
||||
groupByFn = defaultGroupByFn,
|
||||
userAggregations = {},
|
||||
debug,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
warnUnknownProps(rest)
|
||||
|
||||
const defaultState = useState({})
|
||||
|
||||
const localState = userState || defaultState
|
||||
|
||||
// Build the controllable state
|
||||
const [{
|
||||
sortBy, groupBy, filters, expanded,
|
||||
}, setState] = useControlledState(
|
||||
localState,
|
||||
{
|
||||
sortBy: defaultSortBy,
|
||||
groupBy: defaultGroupBy,
|
||||
filters: defaultFilters,
|
||||
expanded: defaultExpanded,
|
||||
},
|
||||
{
|
||||
sortBy: userSortBy,
|
||||
groupBy: userGroupBy,
|
||||
filters: userFilters,
|
||||
expanded: userExpanded,
|
||||
}
|
||||
)
|
||||
|
||||
const expandedDepth = findExpandedDepth(expanded)
|
||||
const renderedCellInfoRef = useRef({})
|
||||
|
||||
// Build the base column
|
||||
let { columns, headerGroups, headers } = useColumns({
|
||||
debug,
|
||||
groupBy,
|
||||
userColumns,
|
||||
defaultFlex,
|
||||
renderedCellInfoRef,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
})
|
||||
|
||||
// Use data and columns as memoization
|
||||
const accessedRows = useAccessedRows({
|
||||
debug,
|
||||
data,
|
||||
columns,
|
||||
subRowsKey,
|
||||
})
|
||||
|
||||
// Use rows and groupBy as memoization for row grouping
|
||||
const groupedRows = useGroupedRows({
|
||||
debug,
|
||||
rows: accessedRows,
|
||||
groupBy,
|
||||
columns,
|
||||
groupByFn,
|
||||
manualGrouping,
|
||||
userAggregations,
|
||||
})
|
||||
|
||||
const filteredRows = useFilteredRows({
|
||||
debug,
|
||||
rows: groupedRows,
|
||||
filters,
|
||||
columns,
|
||||
filterFn,
|
||||
manualFilters,
|
||||
})
|
||||
|
||||
const sortedRows = useSortedRows({
|
||||
debug,
|
||||
rows: filteredRows,
|
||||
sortBy,
|
||||
columns,
|
||||
orderByFn,
|
||||
sortByFn,
|
||||
manualSorting,
|
||||
})
|
||||
|
||||
const rows = useExpandedRows({
|
||||
debug,
|
||||
rows: sortedRows,
|
||||
expanded,
|
||||
expandedKey,
|
||||
columns,
|
||||
groupBy,
|
||||
setState,
|
||||
actions,
|
||||
})
|
||||
|
||||
// Mutate columns to reflect sorting state
|
||||
columns.forEach(column => {
|
||||
const { id } = column
|
||||
column.sorted = sortBy.find(d => d.id === id)
|
||||
column.sortedIndex = sortBy.findIndex(d => d.id === id)
|
||||
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
|
||||
column.filterVal = filters[id]
|
||||
})
|
||||
|
||||
// Public API
|
||||
|
||||
const {
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
} = useTableMethods({
|
||||
debug,
|
||||
setState,
|
||||
actions,
|
||||
groupBy,
|
||||
columns,
|
||||
defaultSortDesc,
|
||||
filters,
|
||||
})
|
||||
|
||||
const state = {
|
||||
// State
|
||||
columns,
|
||||
expandedDepth,
|
||||
accessedRows,
|
||||
groupedRows,
|
||||
filteredRows,
|
||||
sortedRows,
|
||||
rows,
|
||||
headerGroups,
|
||||
renderedCellInfoRef,
|
||||
disableMultiSort,
|
||||
|
||||
// Controllable state
|
||||
groupBy,
|
||||
filters,
|
||||
sortBy,
|
||||
expanded,
|
||||
}
|
||||
|
||||
const api = {
|
||||
// State manager,
|
||||
...state,
|
||||
|
||||
// Methods
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
}
|
||||
|
||||
columns.forEach(column => attachApi(column, api))
|
||||
headers.forEach(header => attachApi(header, api))
|
||||
|
||||
const visibleColumns = columns.filter(column => {
|
||||
column.visible = typeof column.show === 'function' ? column.show(state) : !!column.show
|
||||
return column.visible
|
||||
})
|
||||
|
||||
state.visibleColumns = visibleColumns
|
||||
api.visibleColumns = visibleColumns
|
||||
|
||||
api.hooks = {
|
||||
getTableProps: [],
|
||||
getRowProps: [],
|
||||
getHeaderRowProps: [],
|
||||
getCellProps: [],
|
||||
getHeaderProps: [],
|
||||
getSortByToggleProps: [],
|
||||
getGroupByToggleProps: [],
|
||||
}
|
||||
|
||||
api.getTableProps = props => mergeProps(
|
||||
{
|
||||
style: {
|
||||
overflowX: 'auto',
|
||||
},
|
||||
},
|
||||
applyHooks(api.hooks.getTableProps),
|
||||
props
|
||||
)
|
||||
|
||||
api.getRowProps = props => mergeProps(applyHooks(api.hooks.getRowProps), props)
|
||||
|
||||
// Filter out hidden header groups or header groups that
|
||||
// have no visible columns, then add the getRowProps method
|
||||
headerGroups = useMemo(
|
||||
() =>
|
||||
headerGroups.filter((headerGroup, i) => {
|
||||
headerGroup.headers = headerGroup.headers.filter(header => {
|
||||
const recurse = columns => columns.filter(column => {
|
||||
if (column.columns) {
|
||||
return recurse(column.columns)
|
||||
}
|
||||
return column.visible
|
||||
}).length
|
||||
if (header.columns) {
|
||||
return recurse(header.columns)
|
||||
}
|
||||
return header.visible
|
||||
})
|
||||
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getRowProps = (props = {}) => mergeProps(
|
||||
{
|
||||
key: [`header${i}`].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getHeaderRowProps, headerGroup),
|
||||
props
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
[headerGroups]
|
||||
)
|
||||
|
||||
// A function that mutates and prepares page rows with methods
|
||||
api.prepareRows = rows => {
|
||||
rows.forEach((row, i) => {
|
||||
const { path } = row
|
||||
row.getRowProps = props => mergeProps(
|
||||
{ key: ['row', i].join('_') },
|
||||
applyHooks(api.hooks.getRowProps, row),
|
||||
props
|
||||
)
|
||||
|
||||
row.toggleExpanded = set => toggleExpandedByPath(path, set)
|
||||
|
||||
row.cells = row.cells.filter(cell => cell.column.visible);
|
||||
[row.groupByCell, ...row.cells].forEach(cell => {
|
||||
if (!cell) {
|
||||
return
|
||||
}
|
||||
|
||||
const { column } = cell
|
||||
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [i, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getCellProps, cell),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
cell.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(
|
||||
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
|
||||
)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const makeHook = api => {
|
||||
api.hook = (hook, ...args) => {
|
||||
const result = hook(api, ...args) || {}
|
||||
const newApi = {
|
||||
...api,
|
||||
...result,
|
||||
}
|
||||
makeHook(newApi)
|
||||
return newApi
|
||||
}
|
||||
}
|
||||
|
||||
makeHook(api)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
function attachApi (column, api) {
|
||||
const {
|
||||
id, canSortBy, canGroupBy, canFilter,
|
||||
} = column
|
||||
const { toggleSortByID, toggleGroupBy, setFilter } = api
|
||||
|
||||
column.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...column,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
|
||||
if (canSortBy) {
|
||||
column.toggleSortBy = (desc, multi) => toggleSortByID(id, desc, multi)
|
||||
}
|
||||
if (canGroupBy) {
|
||||
column.toggleGroupBy = () => toggleGroupBy(id)
|
||||
}
|
||||
if (canFilter) {
|
||||
column.setFilter = val => setFilter(id, val)
|
||||
}
|
||||
|
||||
column.getSortByToggleProps = props => mergeProps(
|
||||
{
|
||||
onClick: canSortBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleSortBy(undefined, !api.disableMultiSort && e.shiftKey)
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canSortBy ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle SortBy',
|
||||
},
|
||||
applyHooks(api.hooks.getSortByToggleProps, column),
|
||||
props
|
||||
)
|
||||
|
||||
column.getGroupByToggleProps = props => mergeProps(
|
||||
{
|
||||
onClick: canGroupBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleGroupBy()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canGroupBy ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle GroupBy',
|
||||
},
|
||||
applyHooks(api.hooks.getGroupByToggleProps, column),
|
||||
props
|
||||
)
|
||||
|
||||
column.getHeaderProps = props => mergeProps(
|
||||
{
|
||||
key: ['header', column.id].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getHeaderProps, column),
|
||||
props
|
||||
)
|
||||
}
|
||||
Regular → Executable
+134
-125
@@ -1,167 +1,176 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
export default {
|
||||
get,
|
||||
set,
|
||||
takeRight,
|
||||
last,
|
||||
sortBy,
|
||||
range,
|
||||
remove,
|
||||
clone,
|
||||
getFirstDefined,
|
||||
sum,
|
||||
makeTemplateComponent,
|
||||
prefixAll,
|
||||
groupBy,
|
||||
isArray
|
||||
}
|
||||
|
||||
function get (obj, path, def) {
|
||||
export function getBy (obj, path, def) {
|
||||
if (!path) {
|
||||
return obj
|
||||
}
|
||||
const pathObj = makePathArray(path)
|
||||
let val
|
||||
try {
|
||||
val = pathObj.reduce((current, pathPart) => current[pathPart], obj)
|
||||
} catch (e) {}
|
||||
val = pathObj.reduce((cursor, pathPart) => cursor[pathPart], obj)
|
||||
} catch (e) {
|
||||
// continue regardless of error
|
||||
}
|
||||
return typeof val !== 'undefined' ? val : def
|
||||
}
|
||||
|
||||
function set (obj = {}, path, value) {
|
||||
const keys = makePathArray(path)
|
||||
let keyPart
|
||||
let cursor = obj
|
||||
while ((keyPart = keys.shift()) && keys.length) {
|
||||
if (!cursor[keyPart]) {
|
||||
cursor[keyPart] = {}
|
||||
}
|
||||
cursor = cursor[keyPart]
|
||||
}
|
||||
cursor[keyPart] = value
|
||||
return obj
|
||||
}
|
||||
|
||||
function takeRight (arr, n) {
|
||||
const start = n > arr.length ? 0 : arr.length - n
|
||||
return arr.slice(start)
|
||||
}
|
||||
|
||||
function last (arr) {
|
||||
return arr[arr.length - 1]
|
||||
}
|
||||
|
||||
function range (n) {
|
||||
const arr = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
arr.push(n)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function sortBy (arr, funcs, dirs) {
|
||||
return arr.sort((a, b) => {
|
||||
for (let i = 0; i < funcs.length; i++) {
|
||||
const comp = funcs[i]
|
||||
const ca = comp(a)
|
||||
const cb = comp(b)
|
||||
export function defaultOrderByFn (arr, funcs, dirs) {
|
||||
return [...arr].sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i += 1) {
|
||||
const sortFn = funcs[i]
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
if (ca > cb) {
|
||||
return desc ? -1 : 1
|
||||
}
|
||||
if (ca < cb) {
|
||||
return desc ? 1 : -1
|
||||
const sortInt = sortFn(rowA, rowB)
|
||||
if (sortInt !== 0) {
|
||||
return desc ? -sortInt : sortInt
|
||||
}
|
||||
}
|
||||
return 0
|
||||
return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index
|
||||
})
|
||||
}
|
||||
|
||||
function remove (a, b) {
|
||||
return a.filter(function (o, i) {
|
||||
var r = b(o)
|
||||
if (r) {
|
||||
a.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function clone (a) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(a, (key, value) => {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
}))
|
||||
} catch (e) {
|
||||
return a
|
||||
export function defaultSortByFn (a, b, desc) {
|
||||
// force null and undefined to the bottom
|
||||
a = a === null || a === undefined ? '' : a
|
||||
b = b === null || b === undefined ? '' : b
|
||||
// force any string values to lowercase
|
||||
a = typeof a === 'string' ? a.toLowerCase() : a
|
||||
b = typeof b === 'string' ? b.toLowerCase() : b
|
||||
// Return either 1 or -1 to indicate a sort priority
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
// returning 0, undefined or any falsey value will defer to the next
|
||||
// sorting mechanism or eventually the columns index via the orderByFn
|
||||
return 0
|
||||
}
|
||||
|
||||
function getFirstDefined (...args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
export function getFirstDefined (...args) {
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
if (typeof args[i] !== 'undefined') {
|
||||
return args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sum (arr) {
|
||||
return arr.reduce((a, b) => {
|
||||
return a + b
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function makeTemplateComponent (compClass) {
|
||||
return ({children, className, ...rest}) => (
|
||||
<div
|
||||
className={classnames(compClass, className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function prefixAll (obj) {
|
||||
return obj
|
||||
}
|
||||
|
||||
function groupBy (xs, key) {
|
||||
return xs.reduce((rv, x, i) => {
|
||||
const resKey = typeof key === 'function' ? key(x, i) : x[key]
|
||||
rv[resKey] = rv[resKey] || []
|
||||
rv[resKey].push(x)
|
||||
return rv
|
||||
export function defaultGroupByFn (rows, grouper) {
|
||||
return rows.reduce((prev, row, i) => {
|
||||
const resKey = typeof grouper === 'function' ? grouper(row.values, i) : row.values[grouper]
|
||||
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
|
||||
prev[resKey].push(row)
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
export function defaultFilterFn (row, id, value, column) {
|
||||
return row.values[id] !== undefined
|
||||
? String(row.values[id])
|
||||
.toLowerCase()
|
||||
.includes(String(value).toLowerCase())
|
||||
: true
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Non-exported Helpers
|
||||
// ########################################################################
|
||||
export function setBy (obj = {}, path, value) {
|
||||
const recurse = (obj, depth = 0) => {
|
||||
const key = path[depth]
|
||||
const target = typeof obj[key] !== 'object' ? {} : obj[key]
|
||||
const subValue = depth === path.length - 1 ? value : recurse(target, depth + 1)
|
||||
return {
|
||||
...obj,
|
||||
[key]: subValue,
|
||||
}
|
||||
}
|
||||
|
||||
return recurse(obj)
|
||||
}
|
||||
|
||||
export function getElementDimensions (element) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const style = window.getComputedStyle(element)
|
||||
const margins = {
|
||||
left: parseInt(style.marginLeft),
|
||||
right: parseInt(style.marginRight),
|
||||
}
|
||||
const padding = {
|
||||
left: parseInt(style.paddingLeft),
|
||||
right: parseInt(style.paddingRight),
|
||||
}
|
||||
return {
|
||||
left: Math.ceil(rect.left),
|
||||
width: Math.ceil(rect.width),
|
||||
outerWidth: Math.ceil(rect.width + margins.left + margins.right + padding.left + padding.right),
|
||||
marginLeft: margins.left,
|
||||
marginRight: margins.right,
|
||||
paddingLeft: padding.left,
|
||||
paddingRight: padding.right,
|
||||
scrollWidth: element.scrollWidth,
|
||||
}
|
||||
}
|
||||
|
||||
export function flexRender (Comp, props) {
|
||||
if (typeof Comp === 'function') {
|
||||
return Object.getPrototypeOf(Comp).isReactComponent ? <Comp {...props} /> : Comp(props)
|
||||
}
|
||||
return Comp
|
||||
}
|
||||
|
||||
export function findExpandedDepth (obj, depth = 1) {
|
||||
return Object.values(obj).reduce((prev, curr) => {
|
||||
if (typeof curr === 'object') {
|
||||
return Math.max(prev, findExpandedDepth(curr, depth + 1))
|
||||
}
|
||||
return depth
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export const mergeProps = (...groups) => {
|
||||
let props = {}
|
||||
groups.forEach(({ style = {}, className, ...rest } = {}) => {
|
||||
props = {
|
||||
...props,
|
||||
...rest,
|
||||
style: {
|
||||
...(props.style || {}),
|
||||
...style,
|
||||
},
|
||||
className: [props.className, className].filter(Boolean).join(' '),
|
||||
}
|
||||
})
|
||||
return props
|
||||
}
|
||||
|
||||
export const applyHooks = (hooks, ...args) =>
|
||||
hooks.reduce((prev, next) => mergeProps(prev, next(...args)), {})
|
||||
|
||||
export const warnUnknownProps = props => {
|
||||
if (Object.keys(props).length) {
|
||||
throw new Error(
|
||||
`Unknown options passed to useReactTable:
|
||||
|
||||
${JSON.stringify(props, null, 2)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function sum (arr) {
|
||||
return arr.reduce((prev, curr) => prev + curr, 0)
|
||||
}
|
||||
|
||||
function makePathArray (obj) {
|
||||
return flattenDeep(obj)
|
||||
.join('.')
|
||||
.replace('[', '.')
|
||||
.replace(']', '')
|
||||
.split('.')
|
||||
.join('.')
|
||||
.replace(/\[/g, '.')
|
||||
.replace(/\]/g, '')
|
||||
.split('.')
|
||||
}
|
||||
|
||||
function flattenDeep (arr, newArr = []) {
|
||||
if (!isArray(arr)) {
|
||||
if (!Array.isArray(arr)) {
|
||||
newArr.push(arr)
|
||||
} else {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
for (let i = 0; i < arr.length; i += 1) {
|
||||
flattenDeep(arr[i], newArr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
export default () => {
|
||||
const data = _.map(_.range(100000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
render: row => {
|
||||
return <span>{row.aggregated ? `${row.value} (avg)` : row.value}</span>
|
||||
}
|
||||
}, {
|
||||
header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<CodeHighlight>{() => getCode()}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
export default (
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
`
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
export default () => {
|
||||
const data = _.map(_.range(10000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
render: row => {
|
||||
return <span>{row.aggregated ? `${row.value} (avg)` : row.value}</span>
|
||||
}
|
||||
}, {
|
||||
header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
// expandedRows={{
|
||||
// 2: true,
|
||||
// 3: {
|
||||
// 2: true
|
||||
// }
|
||||
// }}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<CodeHighlight>{() => getCode()}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
export default (
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
`
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
export default () => {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
render: row => {
|
||||
return <span>{row.aggregated ? `${row.value} (avg)` : row.value}</span>
|
||||
}
|
||||
}, {
|
||||
header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<CodeHighlight>{() => getCode()}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
export default (
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
`
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
const rawData = _.map(_.range(3424), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
// Now let's mock the server. It's job is simple: use the table model to sort and return the page data
|
||||
const requestData = (pageSize, page, sorting) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// On the server, you'll likely use SQL or noSQL or some other query language to do this.
|
||||
// For this mock, we'll just use lodash
|
||||
const sortedData = _.orderBy(rawData, sorting.map(sort => {
|
||||
return row => {
|
||||
if (row[sort.id] === null || row[sort.id] === undefined) {
|
||||
return -Infinity
|
||||
}
|
||||
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
|
||||
}
|
||||
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
|
||||
|
||||
// Be sure to send back the rows to be displayed and any other pertinent information, like how many pages there are total.
|
||||
const res = {
|
||||
rows: sortedData.slice(pageSize * page, (pageSize * page) + pageSize),
|
||||
pages: Math.ceil(rawData.length / pageSize)
|
||||
}
|
||||
|
||||
// Here we'll simulate a server response with 500ms of delay.
|
||||
setTimeout(() => resolve(res), 500)
|
||||
})
|
||||
}
|
||||
|
||||
const ServerSide = React.createClass({
|
||||
getInitialState () {
|
||||
// To handle our data server-side, we need a few things in the state to help us out:
|
||||
return {
|
||||
data: [],
|
||||
pages: null,
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
fetchData (state, instance) {
|
||||
console.log(state, instance)
|
||||
// Whenever the table model changes, or the user sorts or changes pages, this method gets called and passed the current table model.
|
||||
// You can set the `loading` prop of the table to true to use the built-in one or show you're own loading bar if you want.
|
||||
this.setState({loading: true})
|
||||
// Request the data however you want. Here, we'll use our mocked service we created earlier
|
||||
requestData(state.pageSize, state.page, state.sorting)
|
||||
.then((res) => {
|
||||
// Now just get the rows of data to your React Table (and update anything else like total pages or loading)
|
||||
this.setState({
|
||||
data: res.rows,
|
||||
pages: res.pages,
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
},
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
columns={[{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}, {
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]}
|
||||
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
|
||||
defaultPageSize={10}
|
||||
data={this.state.data} // Set the rows to be displayed
|
||||
pages={this.state.pages} // Display the total number of pages
|
||||
loading={this.state.loading} // Display the loading overlay when we need it
|
||||
onChange={this.fetchData} // Request new data when things change
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<CodeHighlight>{() => getCode()}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default () => (
|
||||
<ServerSide />
|
||||
)
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
import ReactTable from 'react-table'
|
||||
import Axios from 'axios'
|
||||
|
||||
export default React.createClass({
|
||||
getInitialState () {
|
||||
// To handle our data server-side, we need to keep track of our table state
|
||||
return {
|
||||
data: [],
|
||||
pages: null,
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
fetchData (state, instance) {
|
||||
// Whenever the table model changes (sorting, pagination, etc), this method gets called and passed the current table model.
|
||||
// You can set the 'loading' prop of the table to true to use the built-in loading notice, or show you're own loading bar if you want.
|
||||
this.setState({loading: true})
|
||||
// Request the data from a server however you want! Be sure to send the bits of the table model that it may neeed.
|
||||
Axios.post('mysite.com/data', {
|
||||
pageSize: state.pageSize,
|
||||
page: state.page,
|
||||
sorting: state.sorting
|
||||
})
|
||||
.then((res) => {
|
||||
// Now update your state!
|
||||
this.setState({
|
||||
data: res.rows,
|
||||
pages: res.pages,
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
},
|
||||
render () {
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<ReactTable
|
||||
columns={[{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]}
|
||||
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
|
||||
defaultPageSize={10}
|
||||
data={this.state.data} // Set the rows to be displayed
|
||||
pages={this.state.pages} // Display the total number of pages
|
||||
loading={this.state.loading} // Display the loading overlay when we need it
|
||||
onChange={this.fetchData} // Request new data when things change
|
||||
/>
|
||||
}
|
||||
})
|
||||
`
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
export default () => {
|
||||
const data = _.map(_.range(5553), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<CodeHighlight>{() => getCode()}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
// Create some column definitions
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
// Display your table!
|
||||
return (
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
)
|
||||
`
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
export default () => {
|
||||
const data = _.map(_.range(5553), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<CodeHighlight>{() => getCode()}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
const columns = [{
|
||||
header: 'Name',
|
||||
columns: [{
|
||||
header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
header: 'Info',
|
||||
columns: [{
|
||||
header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
export default (
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
`
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react'
|
||||
import '../utils/prism'
|
||||
|
||||
export default React.createClass({
|
||||
render () {
|
||||
const { language, children } = this.props
|
||||
return (
|
||||
<pre>
|
||||
<code className={'language-' + (language || 'jsx')}>
|
||||
{children()}
|
||||
</code>
|
||||
</pre>
|
||||
)
|
||||
},
|
||||
componentDidMount () {
|
||||
window.Prism.highlightAll()
|
||||
},
|
||||
componentDidUpdate () {
|
||||
window.Prism.highlightAll()
|
||||
}
|
||||
})
|
||||
@@ -1,138 +0,0 @@
|
||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #a67f59;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user