Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
4a871db71f | ||
|
|
505db8d43e | ||
|
|
387f22028a | ||
|
|
f2839dcc6d | ||
|
|
8a74b2b795 | ||
|
|
9bbdd762e6 | ||
|
|
37cf57e1d0 | ||
|
|
cd507d39a4 | ||
|
|
4873af8c99 | ||
|
|
6c05306d99 | ||
|
|
ac2f9a2467 | ||
|
|
69eccc8909 | ||
|
|
721d193883 | ||
|
|
21cae2416f | ||
|
|
0bf363aeaf | ||
|
|
517702922e | ||
|
|
ba1325336f | ||
|
|
7bd30b623c | ||
|
|
f72fec3cc2 | ||
|
|
a8a0d1bab8 | ||
|
|
f06caec053 | ||
|
|
8fa7975ca5 | ||
|
|
e04a2dee69 | ||
|
|
e059ceec75 | ||
|
|
09f425aeb3 | ||
|
|
ca038e9a69 | ||
|
|
75c31b9354 | ||
|
|
4b2fa4e47d | ||
|
|
76e92de670 | ||
|
|
3c5ba2843d | ||
|
|
1cb280942d | ||
|
|
c8c650c729 | ||
|
|
29eda4dc71 | ||
|
|
e5689bc9d4 | ||
|
|
dd433f7d9c | ||
|
|
0daffaf08d | ||
|
|
ecd660ef10 | ||
|
|
be951a3252 | ||
|
|
f195b3243d | ||
|
|
19a0187720 |
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
extends: 'react-tools',
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
* text=auto
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**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](codesandbox.io/s/X6npLXPRW) 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,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Questions, Support & Help
|
||||
about: Looking for help? Need a question answered?
|
||||
|
||||
---
|
||||
|
||||
STOP!!!
|
||||
|
||||
Github issues is no place for general support. We encourage you to 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!
|
||||
@@ -1,6 +1,11 @@
|
||||
node_modules/
|
||||
lib/
|
||||
es/
|
||||
docs/build
|
||||
react-table.js
|
||||
react-table.min.js
|
||||
react-table.css
|
||||
*.log
|
||||
example/dist/
|
||||
.idea
|
||||
.DS_Store
|
||||
.history
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,77 @@
|
||||
module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 8,
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
jsx: true,
|
||||
node: false,
|
||||
classes: true
|
||||
},
|
||||
sourceType: 'module'
|
||||
},
|
||||
|
||||
parser: 'babel-eslint',
|
||||
|
||||
extends: ['standard'],
|
||||
|
||||
plugins: ['react'],
|
||||
|
||||
rules: {
|
||||
// Nozzle
|
||||
'jsx-quotes': [2, 'prefer-single'],
|
||||
'comma-dangle': [2, 'always-multiline'],
|
||||
|
||||
// // React
|
||||
'react/jsx-boolean-value': 2,
|
||||
'react/jsx-curly-spacing': [2, 'never'],
|
||||
'react/jsx-equals-spacing': [2, 'never'],
|
||||
// 'react/jsx-indent': 2,
|
||||
'react/jsx-indent-props': [2, 2],
|
||||
'react/jsx-no-duplicate-props': 2,
|
||||
'react/jsx-no-undef': 2,
|
||||
'react/jsx-tag-spacing': [
|
||||
2,
|
||||
{
|
||||
closingSlash: 'never',
|
||||
beforeSelfClosing: 'always',
|
||||
afterOpening: 'never'
|
||||
}
|
||||
],
|
||||
'react/jsx-uses-react': 2,
|
||||
'react/jsx-uses-vars': 2,
|
||||
'react/self-closing-comp': 2,
|
||||
'react/jsx-no-bind': [
|
||||
2,
|
||||
{
|
||||
allowArrowFunctions: true,
|
||||
allowBind: false,
|
||||
ignoreRefs: true
|
||||
}
|
||||
],
|
||||
'react/no-did-update-set-state': 2,
|
||||
'react/no-unknown-property': 2,
|
||||
'react/react-in-jsx-scope': 2,
|
||||
'react/jsx-closing-bracket-location': [0, 'tag-aligned'],
|
||||
'react/jsx-tag-spacing': [2, { beforeSelfClosing: 'always' }],
|
||||
'react/jsx-wrap-multilines': 2,
|
||||
'react/self-closing-comp': 2,
|
||||
'react/jsx-key': 2,
|
||||
'react/jsx-no-comment-textnodes': 2,
|
||||
'react/jsx-no-duplicate-props': 2,
|
||||
'react/jsx-no-target-blank': 2,
|
||||
'react/jsx-no-undef': 2,
|
||||
'react/jsx-uses-react': 2,
|
||||
'react/jsx-uses-vars': 2,
|
||||
'react/no-danger-with-children': 2,
|
||||
'react/no-deprecated': 2,
|
||||
'react/no-direct-mutation-state': 2,
|
||||
'react/no-find-dom-node': 2,
|
||||
'react/no-is-mounted': 2,
|
||||
'react/no-render-return-value': 2,
|
||||
'react/no-string-refs': 2,
|
||||
'react/no-unknown-property': 2,
|
||||
'react/react-in-jsx-scope': 2,
|
||||
'react/require-render-return': 2
|
||||
// 'react/jsx-max-props-per-line': [2, { maximum: 1 }]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
react-table.js.org
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "new",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.0",
|
||||
"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",
|
||||
"eslint": "^4.1.1",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-class-property": "^1.0.6",
|
||||
"eslint-plugin-import": "^2.8.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||
"eslint-plugin-node": "^5.2.1",
|
||||
"eslint-plugin-promise": "^3.6.0",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"html-element-attributes": "^1.3.0",
|
||||
"match-sorter": "^1.8.0",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"onchange": "^3.0.2",
|
||||
"postcss-cli": "^2.6.0",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"react-json-tree": "^0.10.9",
|
||||
"rimraf": "^2.6.1",
|
||||
"standard": "^10.0.2",
|
||||
"stylus": "^0.54.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"eslint-config-react-tools": "^1.0.10",
|
||||
"github-markdown-css": "^2.6.0",
|
||||
"marked": "^0.3.6",
|
||||
"namor": "^1.0.1",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-json-tree": "^0.10.9",
|
||||
"react-script": "^2.0.5",
|
||||
"react-scripts": "^0.9.5",
|
||||
"react-story": "^0.0.10",
|
||||
"shortid": "^2.2.8"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 24 KiB |
@@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tag above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React Table - A lightweight, fast and extendable datagrid built for React</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start`.
|
||||
To create a production bundle, use `npm run build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,120 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import React from 'react';
|
||||
//
|
||||
import ReactStory, { defaultProps } from 'react-story';
|
||||
import CodeSandbox from './CodeSandbox.js';
|
||||
import './stories/utils/prism.css';
|
||||
import '../../react-table.css';
|
||||
|
||||
import Readme from './stories/Readme.js';
|
||||
import HOCReadme from './stories/HOCReadme.js';
|
||||
|
||||
// import Test from './stories/test.js'
|
||||
|
||||
// import Tester from './examples/expander';
|
||||
|
||||
const stories = [
|
||||
{ name: 'Readme', component: Readme },
|
||||
{ name: 'HOC Readme', component: HOCReadme },
|
||||
|
||||
// { name: 'Tester', component: Test },
|
||||
{ name: 'Simple Table', component: CodeSandbox('X6npLXPRW') },
|
||||
{
|
||||
name: 'Cell Renderers & Custom Components',
|
||||
component: CodeSandbox('OyRL04Z4Y')
|
||||
},
|
||||
{ name: 'Default Sorting', component: CodeSandbox('gLwmmjzA3') },
|
||||
{
|
||||
name: 'Custom Sorting',
|
||||
component: CodeSandbox('VGx67J35')
|
||||
},
|
||||
{ name: 'Custom Column Widths', component: CodeSandbox('o2OORXNXN') },
|
||||
{ name: 'Custom Component Props', component: CodeSandbox('nZW3L0wp4') },
|
||||
{ name: 'Server-side Data', component: CodeSandbox('wjrn8wy3R') },
|
||||
{ name: 'Sub Components', component: CodeSandbox('n2gqAxl7') },
|
||||
{ name: 'Pivoting & Aggregation', component: CodeSandbox('oNY9z8xN') },
|
||||
{
|
||||
name: 'Pivoting & Aggregation w/ Sub Components',
|
||||
component: CodeSandbox('p0kEVBgQ')
|
||||
},
|
||||
{
|
||||
name: '100k Rows w/ Pivoting & Sub Components',
|
||||
component: CodeSandbox('DRmKj0XyK')
|
||||
},
|
||||
{ name: 'Pivoting Options', component: CodeSandbox('kZKmNBK6r') },
|
||||
{ name: 'Functional Rendering', component: CodeSandbox('VPZ0Bzv8X') },
|
||||
{
|
||||
name: 'Custom Expander Position',
|
||||
component: CodeSandbox('1jj2XrPEV')
|
||||
},
|
||||
{ name: 'Custom "No Data" Text', component: CodeSandbox('RgRpRDv80') },
|
||||
{ name: 'Footers', component: CodeSandbox('KOqQXn3p8') },
|
||||
{ name: 'Custom Filtering', component: CodeSandbox('5Eyxxxyx') },
|
||||
{ name: 'Controlled Component', component: CodeSandbox('r7XEZRK2') },
|
||||
{ name: 'Editable Table', component: CodeSandbox('n5r19gzQP') },
|
||||
{
|
||||
name: 'Fixed Header w/ Vertical Scroll',
|
||||
component: CodeSandbox('7LY0gjA8O')
|
||||
},
|
||||
{
|
||||
name: 'Multiple Pagers (Top and Bottom)',
|
||||
component: CodeSandbox('VEZ8OgvX')
|
||||
},
|
||||
|
||||
{ name: 'Tree Table (HOC)', component: CodeSandbox('lxmr4wynzq') },
|
||||
{ name: 'Select Table (HOC)', component: CodeSandbox('7yq5ylw09j') },
|
||||
{ name: 'Select Tree Table (HOC)', component: CodeSandbox('2p7jp4klwp') },
|
||||
{ name: 'Foldable Table (HOC)', component: CodeSandbox('8pkrj5yorl') },
|
||||
{ name: 'Advanced Expand Table (HOC)', component: CodeSandbox('y2m39jz8v1') }
|
||||
];
|
||||
|
||||
export default class App extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<ReactStory
|
||||
style={{
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
pathPrefix="story/"
|
||||
StoryWrapper={props => (
|
||||
<defaultProps.StoryWrapper
|
||||
css={{
|
||||
padding: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href="//github.com/react-tools/react-table"
|
||||
style={{
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
borderBottom: 'solid 3px #cccccc'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="https://github.com/react-tools/media/raw/master/logo-react-table.png"
|
||||
alt="React Table Logo"
|
||||
style={{
|
||||
width: '150px',
|
||||
padding: '10px'
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
{...props}
|
||||
style={{
|
||||
flex: '1 0 auto',
|
||||
position: 'relative'
|
||||
}}
|
||||
/>
|
||||
</defaultProps.StoryWrapper>
|
||||
)}
|
||||
stories={stories}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from "react";
|
||||
|
||||
export default id => () => {
|
||||
return (
|
||||
<iframe
|
||||
src={`https://codesandbox.io/embed/${id}?autoresize=1&hidenavigation=1&view=${
|
||||
global.innerWidth < 1000 ? "preview" : "split"
|
||||
}`}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: `100%`,
|
||||
height: `100%`,
|
||||
border: 0,
|
||||
overflow: `hidden`
|
||||
}}
|
||||
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { makeData, Logo, Tips } from './Utils';
|
||||
import { advancedExpandTableHOC } from '../../../../lib/hoc/advancedExpandTable';
|
||||
|
||||
// Import React Table
|
||||
import ReactTable from 'react-table';
|
||||
import 'react-table/react-table.css';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
Cell: props => {
|
||||
const {
|
||||
// react table props
|
||||
columnProps: { rest: { showRowSubComponent } },
|
||||
nestingPath
|
||||
} = props;
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={e => showRowSubComponent({ nestingPath: nestingPath }, e)}
|
||||
>
|
||||
SHOW SUBCOMPONENT {props.value}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Cell: props => {
|
||||
const {
|
||||
// react table props
|
||||
columnProps: { rest: { toggleRowSubComponent } },
|
||||
nestingPath
|
||||
} = props;
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={e =>
|
||||
toggleRowSubComponent({ nestingPath: nestingPath }, e)
|
||||
}
|
||||
>
|
||||
TOGGLE SUBCOMPONENT {props.value}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
Header: 'Stats',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable);
|
||||
|
||||
class App extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: makeData()
|
||||
};
|
||||
}
|
||||
render() {
|
||||
const { data } = this.state;
|
||||
return (
|
||||
<div>
|
||||
<AdvancedExpandReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
className='-striped -highlight'
|
||||
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => {
|
||||
return (
|
||||
<div style={{ padding: '20px' }}>
|
||||
<button
|
||||
onClick={e => toggleRowSubComponent({ nestingPath }, e)}
|
||||
>
|
||||
CLOSE SUBCOMPONENT {row.firstName} {row.lastName}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<br />
|
||||
<Tips />
|
||||
<Logo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
render(<App />, document.getElementById('root'));
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from "react";
|
||||
|
||||
export default Component => {
|
||||
const wrapper = class RTCheckboxTable extends React.Component {
|
||||
// we only need a Component so we can get the 'ref' - pure components can't get a 'ref'
|
||||
|
||||
rowSelector = row => {
|
||||
if (!row || !row.hasOwnProperty(this.props.keyField)) return null;
|
||||
const checked = this.props.isSelected(row[this.props.keyField]);
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onClick={e => {
|
||||
const { shiftKey } = e;
|
||||
e.stopPropagation();
|
||||
this.props.toggleSelection(row[this.props.keyField], shiftKey, row);
|
||||
}}
|
||||
onChange={() => {}}
|
||||
value=""
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
headSelector = row => {
|
||||
const checked = this.props.selectAll;
|
||||
return (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
this.props.toggleAll();
|
||||
}}
|
||||
onChange={() => {}}
|
||||
value=""
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
|
||||
getWrappedInstance = () => this.wrappedInstance;
|
||||
|
||||
render() {
|
||||
const {
|
||||
columns: originalCols,
|
||||
isSelected,
|
||||
toggleSelection,
|
||||
toggleAll,
|
||||
keyField,
|
||||
selectAll,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { rowSelector, headSelector } = this;
|
||||
const select = {
|
||||
id: "_selector",
|
||||
accessor: () => "x", // this value is not important
|
||||
Header: headSelector,
|
||||
Cell: ci => {
|
||||
return rowSelector(ci.original);
|
||||
},
|
||||
width: 30,
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
style: { textAlign: "center" }
|
||||
};
|
||||
const columns = [select, ...originalCols];
|
||||
const extra = {
|
||||
columns
|
||||
};
|
||||
return (
|
||||
<Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
wrapper.displayName = "RTCheckboxTable";
|
||||
wrapper.defaultProps = {
|
||||
keyField: "_id",
|
||||
isSelected: key => {
|
||||
console.log("No isSelected handler provided:", { key });
|
||||
},
|
||||
selectAll: false,
|
||||
toggleSelection: (key, shift, row) => {
|
||||
console.log("No toggleSelection handler provided:", { key, shift, row });
|
||||
},
|
||||
toggleAll: () => {
|
||||
console.log("No toggleAll handler provided.");
|
||||
}
|
||||
};
|
||||
|
||||
return wrapper;
|
||||
};
|
||||
@@ -0,0 +1,148 @@
|
||||
import React from "react";
|
||||
import shortid from "shortid";
|
||||
|
||||
import ReactTable from "../../../../lib/index";
|
||||
import "../../../../react-table.css";
|
||||
|
||||
import checkboxTableHOC from "./checkboxHOC";
|
||||
|
||||
const CheckboxTable = checkboxTableHOC(ReactTable);
|
||||
|
||||
async function getData() {
|
||||
const result = await (await fetch("/au_500_tree.json")).json();
|
||||
// we are adding a unique ID to the data for tracking the selected records
|
||||
return result.map(item => {
|
||||
const _id = shortid.generate();
|
||||
return {
|
||||
_id,
|
||||
...item
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getColumns(data) {
|
||||
const columns = [];
|
||||
const sample = data[0];
|
||||
for (let key in sample) {
|
||||
if (key === "_id") continue;
|
||||
columns.push({
|
||||
accessor: key,
|
||||
Header: key
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
export class ComponentTest extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: null,
|
||||
columns: null,
|
||||
selection: [],
|
||||
selectAll: false
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
getData().then(data => {
|
||||
const columns = getColumns(data);
|
||||
this.setState({ data, columns });
|
||||
});
|
||||
}
|
||||
toggleSelection = (key, shift, row) => {
|
||||
/*
|
||||
Implementation of how to manage the selection state is up to the developer.
|
||||
This implementation uses an array stored in the component state.
|
||||
Other implementations could use object keys, a Javascript Set, or Redux... etc.
|
||||
*/
|
||||
// start off with the existing state
|
||||
let selection = [...this.state.selection];
|
||||
const keyIndex = selection.indexOf(key);
|
||||
// check to see if the key exists
|
||||
if (keyIndex >= 0) {
|
||||
// it does exist so we will remove it using destructing
|
||||
selection = [
|
||||
...selection.slice(0, keyIndex),
|
||||
...selection.slice(keyIndex + 1)
|
||||
];
|
||||
} else {
|
||||
// it does not exist so add it
|
||||
selection.push(key);
|
||||
}
|
||||
// update the state
|
||||
this.setState({ selection });
|
||||
};
|
||||
toggleAll = () => {
|
||||
/*
|
||||
'toggleAll' is a tricky concept with any filterable table
|
||||
do you just select ALL the records that are in your data?
|
||||
OR
|
||||
do you only select ALL the records that are in the current filtered data?
|
||||
|
||||
The latter makes more sense because 'selection' is a visual thing for the user.
|
||||
This is especially true if you are going to implement a set of external functions
|
||||
that act on the selected information (you would not want to DELETE the wrong thing!).
|
||||
|
||||
So, to that end, access to the internals of ReactTable are required to get what is
|
||||
currently visible in the table (either on the current page or any other page).
|
||||
|
||||
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
|
||||
ReactTable and then get the internal state and the 'sortedData'.
|
||||
That can then be iterrated to get all the currently visible records and set
|
||||
the selection state.
|
||||
*/
|
||||
const selectAll = this.state.selectAll ? false : true;
|
||||
const selection = [];
|
||||
if (selectAll) {
|
||||
// we need to get at the internals of ReactTable
|
||||
const wrappedInstance = this.checkboxTable.getWrappedInstance();
|
||||
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||
// we just push all the IDs onto the selection array
|
||||
currentRecords.forEach(item => {
|
||||
selection.push(item._original._id);
|
||||
});
|
||||
}
|
||||
this.setState({ selectAll, selection });
|
||||
};
|
||||
isSelected = key => {
|
||||
/*
|
||||
Instead of passing our external selection state we provide an 'isSelected'
|
||||
callback and detect the selection state ourselves. This allows any implementation
|
||||
for selection (either an array, object keys, or even a Javascript Set object).
|
||||
*/
|
||||
return this.state.selection.includes(key);
|
||||
};
|
||||
logSelection = () => {
|
||||
console.log("selection:", this.state.selection);
|
||||
};
|
||||
render() {
|
||||
const { toggleSelection, toggleAll, isSelected, logSelection } = this;
|
||||
const { data, columns, selectAll } = this.state;
|
||||
const extraProps = {
|
||||
selectAll,
|
||||
isSelected,
|
||||
toggleAll,
|
||||
toggleSelection
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: "10px" }}>
|
||||
<h1>react-table - Checkbox Table</h1>
|
||||
<button onClick={logSelection}>Log Selection to Console</button>
|
||||
{` (${this.state.selection.length}) selected`}
|
||||
{data ? (
|
||||
<CheckboxTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
ref={r => (this.checkboxTable = r)}
|
||||
className="-striped -highlight"
|
||||
{...extraProps}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// export default treeTableHOC(ComponentTest);
|
||||
export default ComponentTest;
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
|
||||
export default class ComponentTest extends React.component {
|
||||
render() {
|
||||
return <div>Bozo</div>;
|
||||
}
|
||||
}
|
||||
|
||||
// import ReactTable from '../../../../lib/index'
|
||||
// import '../../../../react-table.css'
|
||||
//
|
||||
// console.log('ReactTable:',ReactTable)
|
||||
//
|
||||
// const data = [
|
||||
// {one:"1.1",two:"1.2"},
|
||||
// {one:"2.1",two:"2.2"},
|
||||
// {one:"3.1",two:"3.2"},
|
||||
// {one:"4.1",two:"4.2"},
|
||||
// ]
|
||||
//
|
||||
// const columns = [
|
||||
// {accessor:'one', Header: 'One'},
|
||||
// {accessor:'two', Header: 'Two'},
|
||||
// ]
|
||||
//
|
||||
// class ExpanderComponent extends React.Component {
|
||||
// render()
|
||||
// {
|
||||
// return (
|
||||
// <div className={`rt-expander ${this.props.isExpanded ? '-open' : ''}`}>
|
||||
// •
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class SubComponent extends React.Component {
|
||||
// render()
|
||||
// {
|
||||
// return <div>Nothing</div>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export default class ComponentTest extends React.Component {
|
||||
// render()
|
||||
// {
|
||||
// const rtProps = {
|
||||
// data,
|
||||
// columns,
|
||||
// // ExpanderComponent: (props, closeExpanded)=><ExpanderComponent {...props, closeExpanded} />,
|
||||
// // SubComponent: (props)=><SubComponent {...props} />,
|
||||
// // multiSort: false,
|
||||
// }
|
||||
// return (
|
||||
// <ReactTable
|
||||
// {...rtProps}
|
||||
// />
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react'
|
||||
import ReactTable from '../../../../lib/index'
|
||||
import FoldableTableHOC from '../../../../lib/hoc/foldableTable'
|
||||
import selectTableHOC from '../../../../lib/hoc/selectTable'
|
||||
|
||||
const FoldableTable = FoldableTableHOC(selectTableHOC(ReactTable))
|
||||
|
||||
export default class FoldableTableCustomState extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = { folded: {}, seleted: {}, selectedAll: false }
|
||||
}
|
||||
|
||||
getData = () => [{
|
||||
id: 1,
|
||||
first_name: 'Jeanette',
|
||||
'last_name': 'Penddreth',
|
||||
email: 'jpenddreth0@census.gov',
|
||||
'gender': 'Female',
|
||||
ip_address: '26.58.193.2',
|
||||
}, {
|
||||
'id': 2,
|
||||
'first_name': 'Giavani',
|
||||
last_name: 'Frediani',
|
||||
'email': 'gfrediani1@senate.gov',
|
||||
'gender': 'Male',
|
||||
'ip_address': '229.179.4.212',
|
||||
}, {
|
||||
'id': 3,
|
||||
first_name: 'Noell',
|
||||
last_name: 'Bea',
|
||||
'email': 'nbea2@imageshack.us',
|
||||
gender: 'Female',
|
||||
ip_address: '180.66.162.255',
|
||||
}, {
|
||||
'id': 4,
|
||||
'first_name': 'Willard',
|
||||
'last_name': 'Valek',
|
||||
email: 'wvalek3@vk.com',
|
||||
'gender': 'Male',
|
||||
ip_address: '67.76.188.26',
|
||||
}];
|
||||
|
||||
toggleSelection = (key, shift, row) => {
|
||||
const { selected } = this.state
|
||||
let newSelected = Object.assign({}, selected)
|
||||
newSelected[key] = !newSelected[key]
|
||||
this.setState(p => ({ selected: newSelected }))
|
||||
};
|
||||
|
||||
toggleAll = () => {
|
||||
const { selectedAll } = this.state
|
||||
|
||||
if (selectedAll) { this.setState(p => { return { selectedAll: false, seleted: {} } }); }
|
||||
else {
|
||||
const data = this.getData()
|
||||
|
||||
let newSelected = {}
|
||||
data.forEach(d => newSelected[d.id] = true)
|
||||
this.setState(p => ({ selectedAll: true, seleted: newSelected }))
|
||||
}
|
||||
}
|
||||
|
||||
isSelected = key => this.state.seleted[key];
|
||||
|
||||
render() {
|
||||
return (<FoldableTable
|
||||
//SelectTable props
|
||||
keyField='id'
|
||||
toggleSelection={this.toggleSelection}
|
||||
toggleAll={this.toggleAll}
|
||||
isSelected={this.isSelected}
|
||||
selectAll={this.state.selectedAll}
|
||||
selectType='checkbox'
|
||||
|
||||
onFoldChange={newFolded => this.setState(p => { return { folded: newFolded } })}
|
||||
folded={this.state.folded}
|
||||
|
||||
data={this.getData()}
|
||||
columns={[{
|
||||
Header: "Name",
|
||||
foldable: true,
|
||||
columns: [
|
||||
{
|
||||
Header: "First Name",
|
||||
accessor: "first_name"
|
||||
},
|
||||
{
|
||||
Header: "Last Name",
|
||||
accessor: "last_name"
|
||||
}
|
||||
]
|
||||
}, {
|
||||
Header: "Info",
|
||||
foldable: true,
|
||||
columns: [
|
||||
{
|
||||
Header: "Email",
|
||||
accessor: "email"
|
||||
},
|
||||
{
|
||||
Header: "Gender",
|
||||
accessor: "gender"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}></FoldableTable>)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react'
|
||||
import ReactTable from '../../../../lib/index'
|
||||
import FoldableTableHOC from '../../../../lib/hoc/foldableTable'
|
||||
|
||||
const FoldableTable = FoldableTableHOC(ReactTable)
|
||||
|
||||
export default class FoldableTableWithHeader extends React.Component {
|
||||
|
||||
getData = () => [{
|
||||
id: 1,
|
||||
first_name: 'Jeanette',
|
||||
last_name: 'Penddreth',
|
||||
'email': 'jpenddreth0@census.gov',
|
||||
gender: 'Female',
|
||||
'ip_address': '26.58.193.2',
|
||||
}, {
|
||||
'id': 2,
|
||||
first_name: 'Giavani',
|
||||
last_name: 'Frediani',
|
||||
email: 'gfrediani1@senate.gov',
|
||||
gender: 'Male',
|
||||
'ip_address': '229.179.4.212',
|
||||
}, {
|
||||
'id': 3,
|
||||
'first_name': 'Noell',
|
||||
last_name: 'Bea',
|
||||
'email': 'nbea2@imageshack.us',
|
||||
'gender': 'Female',
|
||||
ip_address: '180.66.162.255',
|
||||
}, {
|
||||
'id': 4,
|
||||
'first_name': 'Willard',
|
||||
'last_name': 'Valek',
|
||||
email: 'wvalek3@vk.com',
|
||||
'gender': 'Male',
|
||||
ip_address: '67.76.188.26',
|
||||
}];
|
||||
|
||||
render() {
|
||||
return (<FoldableTable
|
||||
data={this.getData()}
|
||||
columns={[{
|
||||
Header: "Name",
|
||||
foldable: true,
|
||||
columns: [
|
||||
{
|
||||
Header: "First Name",
|
||||
accessor: "first_name"
|
||||
},
|
||||
{
|
||||
Header: "Last Name",
|
||||
accessor: "last_name"
|
||||
}
|
||||
]
|
||||
}, {
|
||||
Header: "Info",
|
||||
foldable: true,
|
||||
columns: [
|
||||
{
|
||||
Header: "Email",
|
||||
accessor: "email"
|
||||
},
|
||||
{
|
||||
Header: "Gender",
|
||||
accessor: "gender"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}></FoldableTable>)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
import ReactTable from '../../../../lib/index';
|
||||
import FoldableTableHOC from '../../../../lib/hoc/foldableTable';
|
||||
|
||||
const FoldableTable = FoldableTableHOC(ReactTable);
|
||||
|
||||
export default class FoldableTableWithoutHeader extends React.Component {
|
||||
|
||||
getData = () => [{
|
||||
"id": 1,
|
||||
"first_name": "Jeanette",
|
||||
"last_name": "Penddreth",
|
||||
"email": "jpenddreth0@census.gov",
|
||||
"gender": "Female",
|
||||
"ip_address": "26.58.193.2"
|
||||
}, {
|
||||
"id": 2,
|
||||
"first_name": "Giavani",
|
||||
"last_name": "Frediani",
|
||||
"email": "gfrediani1@senate.gov",
|
||||
"gender": "Male",
|
||||
"ip_address": "229.179.4.212"
|
||||
}, {
|
||||
"id": 3,
|
||||
"first_name": "Noell",
|
||||
"last_name": "Bea",
|
||||
"email": "nbea2@imageshack.us",
|
||||
"gender": "Female",
|
||||
"ip_address": "180.66.162.255"
|
||||
}, {
|
||||
"id": 4,
|
||||
"first_name": "Willard",
|
||||
"last_name": "Valek",
|
||||
"email": "wvalek3@vk.com",
|
||||
"gender": "Male",
|
||||
"ip_address": "67.76.188.26"
|
||||
}];
|
||||
|
||||
render() {
|
||||
return <FoldableTable
|
||||
data={this.getData()}
|
||||
columns={[
|
||||
{
|
||||
Header: "First Name",
|
||||
accessor: "first_name"
|
||||
},
|
||||
{
|
||||
Header: "Last Name",
|
||||
accessor: "last_name",
|
||||
foldable: true,
|
||||
},
|
||||
{
|
||||
Header: "Email",
|
||||
accessor: "email",
|
||||
foldable: true,
|
||||
},
|
||||
{
|
||||
Header: "Gender",
|
||||
accessor: "gender",
|
||||
foldable: true,
|
||||
}
|
||||
]}></FoldableTable>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
import React from 'react'
|
||||
import '../../../../react-table.css'
|
||||
|
||||
import FoldableTableWithHeader from './FoldableTableWithHeader'
|
||||
import FoldableTableWithoutHeader from './FoldableTableWithoutHeader'
|
||||
import FoldableTableCustomState from './FoldableTableCustomState'
|
||||
|
||||
class FaldableComponentTest extends React.Component {
|
||||
render() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>- Sample With Header Columns</p>
|
||||
<br />
|
||||
<FoldableTableWithHeader />
|
||||
<br /> <br />
|
||||
<p>- Sample With Normal Columns</p>
|
||||
<br />
|
||||
<FoldableTableWithoutHeader />
|
||||
<br /> <br />
|
||||
<p>- Custom State and selectedTable</p>
|
||||
<br />
|
||||
<FoldableTableCustomState />
|
||||
</div >
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default FaldableComponentTest
|
||||
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import TreeTable from './treetable'
|
||||
import SelectTable from './selecttable'
|
||||
import SelectTreeTable from './selecttreetable'
|
||||
import FoldableTable from './foldabletable';
|
||||
|
||||
export {
|
||||
TreeTable,
|
||||
SelectTable,
|
||||
SelectTreeTable,
|
||||
FoldableTable
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import React from "react";
|
||||
import shortid from "shortid";
|
||||
|
||||
import ReactTable from "../../../../lib/index";
|
||||
import "../../../../react-table.css";
|
||||
|
||||
import selectTableHOC from "../../../../lib/hoc/selectTable";
|
||||
|
||||
const SelectTable = selectTableHOC(ReactTable);
|
||||
|
||||
async function getData() {
|
||||
const result = await (await fetch("/au_500_tree.json")).json();
|
||||
// we are adding a unique ID to the data for tracking the selected records
|
||||
return result.map(item => {
|
||||
const _id = shortid.generate();
|
||||
return {
|
||||
_id,
|
||||
...item
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getColumns(data) {
|
||||
const columns = [];
|
||||
const sample = data[0];
|
||||
for (let key in sample) {
|
||||
if (key === "_id") continue;
|
||||
columns.push({
|
||||
accessor: key,
|
||||
Header: key
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
export class ComponentTest extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: null,
|
||||
columns: null,
|
||||
selection: [],
|
||||
selectAll: false,
|
||||
selectType: "checkbox"
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
getData().then(data => {
|
||||
const columns = getColumns(data);
|
||||
this.setState({ data, columns });
|
||||
});
|
||||
}
|
||||
toggleSelection = (key, shift, row) => {
|
||||
/*
|
||||
Implementation of how to manage the selection state is up to the developer.
|
||||
This implementation uses an array stored in the component state.
|
||||
Other implementations could use object keys, a Javascript Set, or Redux... etc.
|
||||
*/
|
||||
// start off with the existing state
|
||||
if (this.state.selectType === "radio") {
|
||||
let selection = [];
|
||||
if (selection.indexOf(key) < 0) selection.push(key);
|
||||
this.setState({ selection });
|
||||
} else {
|
||||
let selection = [...this.state.selection];
|
||||
const keyIndex = selection.indexOf(key);
|
||||
// check to see if the key exists
|
||||
if (keyIndex >= 0) {
|
||||
// it does exist so we will remove it using destructing
|
||||
selection = [
|
||||
...selection.slice(0, keyIndex),
|
||||
...selection.slice(keyIndex + 1)
|
||||
];
|
||||
} else {
|
||||
// it does not exist so add it
|
||||
selection.push(key);
|
||||
}
|
||||
// update the state
|
||||
this.setState({ selection });
|
||||
}
|
||||
};
|
||||
toggleAll = () => {
|
||||
/*
|
||||
'toggleAll' is a tricky concept with any filterable table
|
||||
do you just select ALL the records that are in your data?
|
||||
OR
|
||||
do you only select ALL the records that are in the current filtered data?
|
||||
|
||||
The latter makes more sense because 'selection' is a visual thing for the user.
|
||||
This is especially true if you are going to implement a set of external functions
|
||||
that act on the selected information (you would not want to DELETE the wrong thing!).
|
||||
|
||||
So, to that end, access to the internals of ReactTable are required to get what is
|
||||
currently visible in the table (either on the current page or any other page).
|
||||
|
||||
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
|
||||
ReactTable and then get the internal state and the 'sortedData'.
|
||||
That can then be iterrated to get all the currently visible records and set
|
||||
the selection state.
|
||||
*/
|
||||
const selectAll = this.state.selectAll ? false : true;
|
||||
const selection = [];
|
||||
if (selectAll) {
|
||||
// we need to get at the internals of ReactTable
|
||||
const wrappedInstance = this.selectTable.getWrappedInstance();
|
||||
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||
// we just push all the IDs onto the selection array
|
||||
currentRecords.forEach(item => {
|
||||
if (item._original) {
|
||||
selection.push(item._original._id);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.setState({ selectAll, selection });
|
||||
};
|
||||
isSelected = key => {
|
||||
/*
|
||||
Instead of passing our external selection state we provide an 'isSelected'
|
||||
callback and detect the selection state ourselves. This allows any implementation
|
||||
for selection (either an array, object keys, or even a Javascript Set object).
|
||||
*/
|
||||
return this.state.selection.includes(key);
|
||||
};
|
||||
logSelection = () => {
|
||||
console.log("selection:", this.state.selection);
|
||||
};
|
||||
toggleType = () => {
|
||||
this.setState({
|
||||
selectType: this.state.selectType === "radio" ? "checkbox" : "radio",
|
||||
selection: [],
|
||||
selectAll: false
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
toggleSelection,
|
||||
toggleAll,
|
||||
isSelected,
|
||||
logSelection,
|
||||
toggleType
|
||||
} = this;
|
||||
const { data, columns, selectAll, selectType } = this.state;
|
||||
const extraProps = {
|
||||
selectAll,
|
||||
isSelected,
|
||||
toggleAll,
|
||||
toggleSelection,
|
||||
selectType
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: "10px" }}>
|
||||
<h1>react-table - Select Table</h1>
|
||||
<button onClick={toggleType}>
|
||||
Select Type: <strong>{selectType}</strong>
|
||||
</button>
|
||||
<button onClick={logSelection}>Log Selection to Console</button>
|
||||
{` (${this.state.selection.length}) selected`}
|
||||
{data ? (
|
||||
<SelectTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
ref={r => (this.selectTable = r)}
|
||||
className="-striped -highlight"
|
||||
{...extraProps}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComponentTest;
|
||||
@@ -0,0 +1,238 @@
|
||||
import React from "react";
|
||||
import shortid from "shortid";
|
||||
|
||||
import ReactTable from "../../../../lib/index";
|
||||
import "../../../../react-table.css";
|
||||
|
||||
import selectTableHOC from "../../../../lib/hoc/selectTable";
|
||||
import treeTableHOC from "../../../../lib/hoc/treeTable";
|
||||
|
||||
const SelectTreeTable = selectTableHOC(treeTableHOC(ReactTable));
|
||||
|
||||
async function getData() {
|
||||
const result = await (await fetch("/au_500_tree.json")).json();
|
||||
// we are adding a unique ID to the data for tracking the selected records
|
||||
return result.map(item => {
|
||||
const _id = shortid.generate();
|
||||
return {
|
||||
_id,
|
||||
...item
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function getColumns(data) {
|
||||
const columns = [];
|
||||
const sample = data[0];
|
||||
for (let key in sample) {
|
||||
if (key === "_id") continue;
|
||||
columns.push({
|
||||
accessor: key,
|
||||
Header: key
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
function getNodes(data, node = []) {
|
||||
data.forEach(item => {
|
||||
if (item.hasOwnProperty("_subRows") && item._subRows) {
|
||||
node = getNodes(item._subRows, node);
|
||||
} else {
|
||||
node.push(item._original);
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
export class ComponentTest extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: null,
|
||||
columns: null,
|
||||
selection: [],
|
||||
selectAll: false,
|
||||
selectType: "checkbox"
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
getData().then(data => {
|
||||
const columns = getColumns(data);
|
||||
const pivotBy = ["state", "post"];
|
||||
this.setState({ data, columns, pivotBy });
|
||||
});
|
||||
}
|
||||
toggleSelection = (key, shift, row) => {
|
||||
/*
|
||||
Implementation of how to manage the selection state is up to the developer.
|
||||
This implementation uses an array stored in the component state.
|
||||
Other implementations could use object keys, a Javascript Set, or Redux... etc.
|
||||
*/
|
||||
// start off with the existing state
|
||||
if (this.state.selectType === "radio") {
|
||||
let selection = [];
|
||||
if (selection.indexOf(key) < 0) selection.push(key);
|
||||
this.setState({ selection });
|
||||
} else {
|
||||
let selection = [...this.state.selection];
|
||||
const keyIndex = selection.indexOf(key);
|
||||
// check to see if the key exists
|
||||
if (keyIndex >= 0) {
|
||||
// it does exist so we will remove it using destructing
|
||||
selection = [
|
||||
...selection.slice(0, keyIndex),
|
||||
...selection.slice(keyIndex + 1)
|
||||
];
|
||||
} else {
|
||||
// it does not exist so add it
|
||||
selection.push(key);
|
||||
}
|
||||
// update the state
|
||||
this.setState({ selection });
|
||||
}
|
||||
};
|
||||
toggleAll = () => {
|
||||
/*
|
||||
'toggleAll' is a tricky concept with any filterable table
|
||||
do you just select ALL the records that are in your data?
|
||||
OR
|
||||
do you only select ALL the records that are in the current filtered data?
|
||||
|
||||
The latter makes more sense because 'selection' is a visual thing for the user.
|
||||
This is especially true if you are going to implement a set of external functions
|
||||
that act on the selected information (you would not want to DELETE the wrong thing!).
|
||||
|
||||
So, to that end, access to the internals of ReactTable are required to get what is
|
||||
currently visible in the table (either on the current page or any other page).
|
||||
|
||||
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
|
||||
ReactTable and then get the internal state and the 'sortedData'.
|
||||
That can then be iterrated to get all the currently visible records and set
|
||||
the selection state.
|
||||
*/
|
||||
const selectAll = this.state.selectAll ? false : true;
|
||||
const selection = [];
|
||||
if (selectAll) {
|
||||
// we need to get at the internals of ReactTable
|
||||
const wrappedInstance = this.selectTable.getWrappedInstance();
|
||||
// the 'sortedData' property contains the currently accessible records based on the filter and sort
|
||||
const currentRecords = wrappedInstance.getResolvedState().sortedData;
|
||||
// we need to get all the 'real' (original) records out to get at their IDs
|
||||
const nodes = getNodes(currentRecords);
|
||||
// we just push all the IDs onto the selection array
|
||||
nodes.forEach(item => {
|
||||
selection.push(item._id);
|
||||
});
|
||||
}
|
||||
this.setState({ selectAll, selection });
|
||||
};
|
||||
isSelected = key => {
|
||||
/*
|
||||
Instead of passing our external selection state we provide an 'isSelected'
|
||||
callback and detect the selection state ourselves. This allows any implementation
|
||||
for selection (either an array, object keys, or even a Javascript Set object).
|
||||
*/
|
||||
return this.state.selection.includes(key);
|
||||
};
|
||||
logSelection = () => {
|
||||
console.log("selection:", this.state.selection);
|
||||
};
|
||||
toggleType = () => {
|
||||
this.setState({
|
||||
selectType: this.state.selectType === "radio" ? "checkbox" : "radio",
|
||||
selection: [],
|
||||
selectAll: false
|
||||
});
|
||||
};
|
||||
toggleTree = () => {
|
||||
if (this.state.pivotBy.length) {
|
||||
this.setState({ pivotBy: [], expanded: {} });
|
||||
} else {
|
||||
this.setState({ pivotBy: ["state", "post"], expanded: {} });
|
||||
}
|
||||
};
|
||||
onExpandedChange = expanded => {
|
||||
this.setState({ expanded });
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
toggleSelection,
|
||||
toggleAll,
|
||||
isSelected,
|
||||
logSelection,
|
||||
toggleType,
|
||||
toggleTree,
|
||||
onExpandedChange
|
||||
} = this;
|
||||
const {
|
||||
data,
|
||||
columns,
|
||||
selectAll,
|
||||
selectType,
|
||||
pivotBy,
|
||||
expanded
|
||||
} = this.state;
|
||||
const extraProps = {
|
||||
selectAll,
|
||||
isSelected,
|
||||
toggleAll,
|
||||
toggleSelection,
|
||||
selectType,
|
||||
pivotBy,
|
||||
expanded,
|
||||
onExpandedChange,
|
||||
pageSize: 5
|
||||
};
|
||||
return (
|
||||
<div style={{ padding: "10px" }}>
|
||||
<h1>react-table - Select Tree Table</h1>
|
||||
<p>
|
||||
This example combines two HOCs (the TreeTable and the SelectTable) to
|
||||
make a composite component.
|
||||
</p>
|
||||
<p>We'll call it SelectTreeTable!</p>
|
||||
<p>Here is what the buttons do:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Toggle Tree:</strong> enables or disabled the pivotBy on the
|
||||
table.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Select Type:</strong> changes from 'checkbox' to 'radio' and
|
||||
back again.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Log Selection to Console:</strong> open your console to see
|
||||
what has been selected.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
<strong>NOTE:</strong> the selection is maintained when toggling the
|
||||
tree on and off but is cleared when switching between select types
|
||||
(radio, checkbox).
|
||||
</p>
|
||||
<button onClick={toggleTree}>
|
||||
Toggle Tree [{pivotBy && pivotBy.length ? pivotBy.join(", ") : ""}]
|
||||
</button>
|
||||
<button onClick={toggleType}>
|
||||
Select Type: <strong>{selectType}</strong>
|
||||
</button>
|
||||
<button onClick={logSelection}>Log Selection to Console</button>
|
||||
{` (${this.state.selection.length}) selected`}
|
||||
{data ? (
|
||||
<SelectTreeTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
ref={r => (this.selectTable = r)}
|
||||
className="-striped -highlight"
|
||||
freezeWhenExpanded={true}
|
||||
{...extraProps}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComponentTest;
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
|
||||
import ReactTable from "../../../../lib/index";
|
||||
import "../../../../react-table.css";
|
||||
|
||||
import treeTableHOC from "../../../../lib/hoc/treeTable";
|
||||
|
||||
async function getData() {
|
||||
const result = await (await fetch("/au_500_tree.json")).json();
|
||||
return result;
|
||||
}
|
||||
|
||||
function getColumns(data, pivotBy) {
|
||||
const columns = [];
|
||||
const sample = data[0];
|
||||
for (let key in sample) {
|
||||
columns.push({
|
||||
accessor: key,
|
||||
Header: key
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
export class ComponentTest extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: null,
|
||||
columns: null,
|
||||
pivotBy: null // ["firstName", "lastName"],
|
||||
};
|
||||
}
|
||||
componentDidMount() {
|
||||
getData().then(data => {
|
||||
// console.log('cwm data:',data);
|
||||
const pivotBy = ["state", "post", "city"];
|
||||
const columns = getColumns(data, pivotBy);
|
||||
// console.log('cwm cols:',columns);
|
||||
this.setState({ data, columns, pivotBy });
|
||||
});
|
||||
}
|
||||
showState = () => {
|
||||
console.log("state:", this.reactTable.getResolvedState());
|
||||
};
|
||||
render() {
|
||||
const { data, columns, pivotBy } = this.state;
|
||||
const extraProps = {
|
||||
data,
|
||||
columns,
|
||||
pivotBy
|
||||
};
|
||||
const TreeTable = treeTableHOC(ReactTable);
|
||||
return (
|
||||
<div style={{ padding: "10px" }}>
|
||||
<h1>react-table - Tree Table</h1>
|
||||
{data ? (
|
||||
<TreeTable
|
||||
ref={r => (this.reactTable = r)}
|
||||
className="-striped -highlight"
|
||||
defaultPageSize={5}
|
||||
{...extraProps}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ComponentTest;
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
|
||||
export default Component => {
|
||||
const wrapper = componentProps => {
|
||||
const TrComponent = props => {
|
||||
const { ri, ...rest } = props;
|
||||
if (ri && ri.groupedByPivot) {
|
||||
const cell = props.children[ri.level];
|
||||
|
||||
cell.props.style.flex = "unset";
|
||||
cell.props.style.width = "100%";
|
||||
cell.props.style.maxWidth = "unset";
|
||||
cell.props.style.paddingLeft = `${componentProps.treeTableIndent *
|
||||
ri.level}px`;
|
||||
cell.props.style.backgroundColor = "#DDD";
|
||||
cell.props.style.borderBottom = "1px solid rgba(128,128,128,0.2)";
|
||||
|
||||
return <div {...rest}>{cell}</div>;
|
||||
}
|
||||
return <Component.defaultProps.TrComponent {...rest} />;
|
||||
};
|
||||
|
||||
const getTrProps = (state, ri, ci, instance) => {
|
||||
return { ri };
|
||||
};
|
||||
|
||||
const { columns, ...rest } = componentProps;
|
||||
const extra = {
|
||||
columns: columns.map(col => {
|
||||
let column = col;
|
||||
if (rest.pivotBy && rest.pivotBy.includes(col.accessor)) {
|
||||
column = {
|
||||
accessor: col.accessor,
|
||||
width: `${componentProps.treeTableIndent}px`,
|
||||
show: false,
|
||||
Header: ""
|
||||
};
|
||||
}
|
||||
return column;
|
||||
}),
|
||||
TrComponent,
|
||||
getTrProps
|
||||
};
|
||||
|
||||
return <Component {...rest} {...extra} />;
|
||||
};
|
||||
wrapper.displayName = "RTTreeTable";
|
||||
wrapper.defaultProps = {
|
||||
treeTableRowBackground: "#EEE",
|
||||
treeTableIndent: 10
|
||||
};
|
||||
return wrapper;
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import namor from "namor";
|
||||
|
||||
const range = len => {
|
||||
const arr = [];
|
||||
for (let i = 0; i < len; i++) {
|
||||
arr.push(i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
const newPerson = () => {
|
||||
const statusChance = Math.random();
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numbers: 0 }),
|
||||
lastName: namor.generate({ words: 1, numbers: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status:
|
||||
statusChance > 0.66
|
||||
? "relationship"
|
||||
: statusChance > 0.33
|
||||
? "complicated"
|
||||
: "single"
|
||||
};
|
||||
};
|
||||
|
||||
export function makeData(len = 5553) {
|
||||
return range(len).map(d => {
|
||||
return {
|
||||
...newPerson(),
|
||||
children: range(10).map(newPerson)
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
//
|
||||
import "./index.css";
|
||||
import App from "./App.js";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById("root"));
|
||||
@@ -0,0 +1,23 @@
|
||||
/* eslint-disable */
|
||||
import React from "react";
|
||||
import marked from "marked";
|
||||
//
|
||||
import HOCReadme from "!raw!../../../src/hoc/README.md";
|
||||
import "github-markdown-css/github-markdown.css";
|
||||
import "./utils/prism.js";
|
||||
|
||||
export default class HOCStory extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div style={{ padding: "10px" }}>
|
||||
<span
|
||||
className="markdown-body"
|
||||
dangerouslySetInnerHTML={{ __html: marked(HOCReadme) }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
global.Prism && global.Prism.highlightAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from "react";
|
||||
import marked from "marked";
|
||||
//
|
||||
import Readme from "!raw!../../../README.md";
|
||||
import "github-markdown-css/github-markdown.css";
|
||||
import "./utils/prism.js";
|
||||
|
||||
export default class Story extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div style={{ padding: "10px" }}>
|
||||
<span
|
||||
className="markdown-body"
|
||||
dangerouslySetInnerHTML={{ __html: marked(Readme) }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
componentDidMount() {
|
||||
global.Prism && global.Prism.highlightAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
|
||||
import ReactTable from "../../../lib/index";
|
||||
import "../../../react-table.css";
|
||||
|
||||
const data = [
|
||||
{ one: "1.1", two: "1.2" },
|
||||
{ one: "2.1", two: "2.2" },
|
||||
{ one: "3.1", two: "3.2" },
|
||||
{ one: "4.1", two: "4.2" }
|
||||
];
|
||||
|
||||
const columns = [
|
||||
{ accessor: "one", Header: "One" },
|
||||
{ accessor: "two", Header: "Two" }
|
||||
];
|
||||
|
||||
export default class Story extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
Test
|
||||
<ReactTable data={data} columns={columns} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
# Jumpsuit Getting Started
|
||||
@@ -1,28 +0,0 @@
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var stylus = require('stylus')
|
||||
var nib = require('nib')
|
||||
|
||||
var stylusEntry = path.resolve('src/app.styl')
|
||||
|
||||
module.exports = {
|
||||
styles: {
|
||||
extensions: ['.css', '.styl'],
|
||||
action: buildStyles
|
||||
}
|
||||
}
|
||||
|
||||
function buildStyles () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
stylus.render(fs.readFileSync(stylusEntry, 'utf8'), {
|
||||
'include css': true,
|
||||
'hoist atrules': true,
|
||||
compress: process.env.NODE_ENV === 'production',
|
||||
paths: [path.resolve('src')],
|
||||
use: nib()
|
||||
}, function (err, css) {
|
||||
if (err) reject(err)
|
||||
else resolve(css)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "react-table-example",
|
||||
"version": "0.0.1",
|
||||
"description": "Jumpsuit getting started example",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/jumpsuit/jumpsuit.git"
|
||||
},
|
||||
"scripts": {
|
||||
"watch": "jumpsuit watch",
|
||||
"build": "jumpsuit build",
|
||||
"deploy": "jumpsuit build && zab deploy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nib": "^1.1.0",
|
||||
"standard": "^8.4.0",
|
||||
"stylus": "^0.54.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"jumpsuit": "^1.3.3",
|
||||
"lodash": "^4.16.4",
|
||||
"namor": "^0.3.0",
|
||||
"react-syntax-highlighter": "^3.0.0",
|
||||
"react-table": "^3.1.2"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Render, Router, Route, IndexRoute } from 'jumpsuit'
|
||||
//
|
||||
import Layout from 'components/layout'
|
||||
import Simple from 'screens/simple'
|
||||
import ServerSide from 'screens/serverSide'
|
||||
|
||||
Render(null, (
|
||||
<Router>
|
||||
<Route path='/' component={Layout}>
|
||||
<IndexRoute component={Simple} />
|
||||
<Route path='simple' component={Simple} />
|
||||
<Route path='server-side' component={ServerSide} />
|
||||
</Route>
|
||||
</Router>
|
||||
), {
|
||||
useHash: false
|
||||
})
|
||||
@@ -1,112 +0,0 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// Globals
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,600')
|
||||
@import 'nib'
|
||||
|
||||
global-reset()
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// vendor styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
@import '../node_modules/react-table/src/index'
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// variables
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
$fnt-open-sans = 'Open Sans', sans-serif
|
||||
$react = #25d8fd
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// styles
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
*
|
||||
box-sizing border-box
|
||||
|
||||
html
|
||||
min-height 100vh
|
||||
|
||||
body
|
||||
background: white
|
||||
font-family: $fnt-open-sans
|
||||
font-weight: 300
|
||||
|
||||
h1
|
||||
font-size: 2.5em
|
||||
color: white
|
||||
|
||||
a
|
||||
color: darken($react, 20%)
|
||||
font-weight: bold
|
||||
text-decoration: none
|
||||
|
||||
strong
|
||||
font-weight: bold
|
||||
|
||||
.logo
|
||||
width: 400px
|
||||
max-width: 100%
|
||||
|
||||
.container
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
min-height: 100vh
|
||||
padding-top: 20px
|
||||
|
||||
.github-addon
|
||||
font-size: 20px
|
||||
padding: 10px
|
||||
|
||||
.viewport
|
||||
width:100%
|
||||
|
||||
.table-wrap
|
||||
width: 90%
|
||||
margin: auto
|
||||
padding: 10px
|
||||
border-radius: 5px
|
||||
box-shadow: 0 0 20px 0 alpha(black, .2)
|
||||
|
||||
.menu
|
||||
display:block
|
||||
margin: 0 10px 20px
|
||||
ul
|
||||
display:block
|
||||
li
|
||||
display:inline-block
|
||||
a
|
||||
display:block
|
||||
padding: 10px
|
||||
margin: 5px
|
||||
background: alpha(black, .4)
|
||||
color: white
|
||||
border-radius: 3px
|
||||
transition: all .2s ease-out
|
||||
&.active
|
||||
&:hover
|
||||
background: alpha(black, .9)
|
||||
|
||||
.ReactTable
|
||||
thead
|
||||
th
|
||||
td
|
||||
&.-sort-asc
|
||||
box-shadow:inset 0 3px 0 0 alpha(black, .6)
|
||||
&.-sort-desc
|
||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
||||
|
||||
pre
|
||||
display:block
|
||||
font-family: monospace
|
||||
font-size: 15px
|
||||
line-height: 20px
|
||||
border-radius: 5px
|
||||
margin: 20px auto
|
||||
max-width: 90%
|
||||
padding: 0 20px !important
|
||||
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>React-Table Demo</title>
|
||||
<link rel="stylesheet" href="/app.css" charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="/app.js"></script>
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Component } from 'jumpsuit'
|
||||
import SyntaxHighlighter from 'react-syntax-highlighter'
|
||||
import atomOneDark from '../../node_modules/react-syntax-highlighter/dist/styles/atom-one-dark'
|
||||
|
||||
export default Component({
|
||||
render () {
|
||||
return (
|
||||
<SyntaxHighlighter language='javascript' style={atomOneDark}>{this.props.children}</SyntaxHighlighter>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,59 +0,0 @@
|
||||
import { Component, Link } from 'jumpsuit'
|
||||
|
||||
export default Component({
|
||||
render () {
|
||||
return (
|
||||
<div className='container'>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<h1>
|
||||
<span style={{position: 'absolute', textIndent: '-9999em'}}>
|
||||
react-table
|
||||
</span>
|
||||
<img src='/Banner.png' className='logo' />
|
||||
</h1>
|
||||
<br />
|
||||
<div>
|
||||
<a
|
||||
className='github-button'
|
||||
href='https://github.com/tannerlinsley/react-table'
|
||||
target='_blank'
|
||||
data-style='mega'
|
||||
data-count-href='/tannerlinsley/react-table/stargazers'
|
||||
data-count-api='/repos/tannerlinsley/react-table#stargazers_count'
|
||||
data-count-aria-label='# stargazers on GitHub'
|
||||
aria-label='Star tannerlinsley/react-table on GitHub'>
|
||||
Star
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<div className='github-addon'>
|
||||
<a
|
||||
target='_blank'
|
||||
href='https://github.com/tannerlinsley/react-table'>
|
||||
View on Github
|
||||
</a>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
</div>
|
||||
<div className='menu'>
|
||||
<ul>
|
||||
<li>
|
||||
<Link to='/simple' activeClassName='active'>
|
||||
Simple
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to='/server-side' activeClassName='active'>
|
||||
Server-Side
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className='viewport'>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,171 +0,0 @@
|
||||
import { Component } from 'jumpsuit'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from 'components/codeHighlight'
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
// Let's mock some data to play around with
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
export default Component({
|
||||
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) {
|
||||
// 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: '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
|
||||
pageSize={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>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
function getCode () {
|
||||
return `
|
||||
import ReactTable from 'react-table'
|
||||
import Axios from 'axios'
|
||||
|
||||
export default React.creatClass({
|
||||
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={columns}
|
||||
manual // This forces table not to paginate or sort automatically, so we can handle things server-side
|
||||
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,103 +0,0 @@
|
||||
import { Component } from 'jumpsuit'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from 'components/codeHighlight'
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
export default Component({
|
||||
render () {
|
||||
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}
|
||||
pageSize={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'
|
||||
|
||||
// To help us mock some data
|
||||
import namor from 'namor'
|
||||
import _ from 'lodash'
|
||||
|
||||
export default () => {
|
||||
|
||||
// Mock some data
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
// 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}
|
||||
pageSize={10}
|
||||
/>
|
||||
)
|
||||
})
|
||||
`
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"root": "dist",
|
||||
"routes": [{
|
||||
"src": "/**",
|
||||
"dest": "index.html"
|
||||
}]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 152 KiB |
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "3.1.4",
|
||||
"version": "6.9.0",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
"homepage": "https://github.com/react-tools/react-table#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tannerlinsley/react-table.git"
|
||||
"url": "git+https://github.com/react-tools/react-table.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
@@ -15,61 +15,123 @@
|
||||
"datagrid"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
"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": "onchange 'src/**' -i -- npm-run-all build:*",
|
||||
"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",
|
||||
"postpublish": "git push --tags"
|
||||
"build:cjs": "babel src --out-dir lib --source-maps inline --copy-files",
|
||||
"build:es": "cross-env BABEL_ENV=es babel src --out-dir es --source-maps inline",
|
||||
"build:umd": "cross-env BABEL_ENV=umd rollup -c",
|
||||
"build:umd-min": "cross-env BABEL_ENV=umd NODE_ENV=production rollup -c",
|
||||
"build:css": "rimraf react-table.css && stylus src/index.styl --compress -o react-table.css && yarn css:autoprefix",
|
||||
"css:autoprefix": "postcss --use autoprefixer react-table.css -r",
|
||||
"watch": "npm-run-all --parallel watch:*",
|
||||
"watch:js": "onchange 'src/**/*.js' -i -- yarn build:cjs",
|
||||
"watch:css": "onchange 'src/**/*.styl' -i -- yarn build:css",
|
||||
"test": "eslint src",
|
||||
"build": "npm-run-all build:*",
|
||||
"prepublish": "yarn build",
|
||||
"postpublish": "git push --tags",
|
||||
"docs": "yarn watch & cd docs && yarn && yarn start",
|
||||
"docs:build": "yarn build && cd docs && yarn && yarn build"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.3.1"
|
||||
"prop-types": "^15.5.0",
|
||||
"react": "^15.x.x || ^16.x.x",
|
||||
"react-dom": "^15.x.x || ^16.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.0",
|
||||
"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",
|
||||
"nib": "^1.1.2",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"cross-env": "^5.1.4",
|
||||
"eslint": "^4.1.1",
|
||||
"eslint-config-react-tools": "^1.0.10",
|
||||
"eslint-plugin-class-property": "^1.0.6",
|
||||
"eslint-plugin-import": "^2.7.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"match-sorter": "^1.8.0",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"onchange": "^3.0.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"
|
||||
]
|
||||
"postcss-cli": "^2.6.0",
|
||||
"prop-types": "^15.6.0",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-json-tree": "^0.10.9",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "^0.55.3",
|
||||
"rollup-plugin-babel": "^3.0.3",
|
||||
"rollup-plugin-commonjs": "^8.3.0",
|
||||
"rollup-plugin-node-resolve": "^3.0.2",
|
||||
"rollup-plugin-replace": "^2.0.0",
|
||||
"rollup-plugin-uglify": "^3.0.0",
|
||||
"standard": "^10.0.2",
|
||||
"stylus": "^0.54.5"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-2",
|
||||
"react"
|
||||
]
|
||||
"env": {
|
||||
"development": {
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"modules": "commonjs"
|
||||
}
|
||||
],
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
]
|
||||
},
|
||||
"es": {
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
]
|
||||
},
|
||||
"umd": {
|
||||
"presets": [
|
||||
[
|
||||
"env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"react"
|
||||
],
|
||||
"plugins": [
|
||||
"transform-object-rest-spread",
|
||||
"transform-class-properties",
|
||||
"external-helpers"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,270 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
import _ from './utils'
|
||||
import Pagination from './pagination'
|
||||
|
||||
const emptyObj = () => ({})
|
||||
|
||||
export default {
|
||||
// General
|
||||
data: [],
|
||||
resolveData: data => data,
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPaginationTop: false,
|
||||
showPaginationBottom: true,
|
||||
showPageSizeOptions: true,
|
||||
pageSizeOptions: [5, 10, 20, 25, 50, 100],
|
||||
defaultPage: 0,
|
||||
defaultPageSize: 20,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
sortable: true,
|
||||
multiSort: true,
|
||||
resizable: true,
|
||||
filterable: false,
|
||||
defaultSortDesc: false,
|
||||
defaultSorted: [],
|
||||
defaultFiltered: [],
|
||||
defaultResized: [],
|
||||
defaultExpanded: {},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultFilterMethod: (filter, row, column) => {
|
||||
const id = filter.pivotId || filter.id
|
||||
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
defaultSortMethod: (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 use subsequent sorts or
|
||||
// the index as a tiebreaker
|
||||
return 0
|
||||
},
|
||||
|
||||
// Controlled State Props
|
||||
// page: undefined,
|
||||
// pageSize: undefined,
|
||||
// sorted: [],
|
||||
// filtered: [],
|
||||
// resized: [],
|
||||
// expanded: {},
|
||||
|
||||
// Controlled State Callbacks
|
||||
onPageChange: undefined,
|
||||
onPageSizeChange: undefined,
|
||||
onSortedChange: undefined,
|
||||
onFilteredChange: undefined,
|
||||
onResizedChange: undefined,
|
||||
onExpandedChange: undefined,
|
||||
|
||||
// Pivoting
|
||||
pivotBy: undefined,
|
||||
|
||||
// Key Constants
|
||||
pivotValKey: '_pivotVal',
|
||||
pivotIDKey: '_pivotID',
|
||||
subRowsKey: '_subRows',
|
||||
aggregatedKey: '_aggregated',
|
||||
nestingLevelKey: '_nestingLevel',
|
||||
originalKey: '_original',
|
||||
indexKey: '_index',
|
||||
groupedByPivotKey: '_groupedByPivot',
|
||||
|
||||
// Server-side Callbacks
|
||||
onFetchData: () => null,
|
||||
|
||||
// Classes
|
||||
className: '',
|
||||
style: {},
|
||||
|
||||
// Component decorators
|
||||
getProps: emptyObj,
|
||||
getTableProps: emptyObj,
|
||||
getTheadGroupProps: emptyObj,
|
||||
getTheadGroupTrProps: emptyObj,
|
||||
getTheadGroupThProps: emptyObj,
|
||||
getTheadProps: emptyObj,
|
||||
getTheadTrProps: emptyObj,
|
||||
getTheadThProps: emptyObj,
|
||||
getTheadFilterProps: emptyObj,
|
||||
getTheadFilterTrProps: emptyObj,
|
||||
getTheadFilterThProps: emptyObj,
|
||||
getTbodyProps: emptyObj,
|
||||
getTrGroupProps: emptyObj,
|
||||
getTrProps: emptyObj,
|
||||
getTdProps: emptyObj,
|
||||
getTfootProps: emptyObj,
|
||||
getTfootTrProps: emptyObj,
|
||||
getTfootTdProps: emptyObj,
|
||||
getPaginationProps: emptyObj,
|
||||
getLoadingProps: emptyObj,
|
||||
getNoDataProps: emptyObj,
|
||||
getResizerProps: emptyObj,
|
||||
|
||||
// Global Column Defaults
|
||||
column: {
|
||||
// Renderers
|
||||
Cell: undefined,
|
||||
Header: undefined,
|
||||
Footer: undefined,
|
||||
Aggregated: undefined,
|
||||
Pivot: undefined,
|
||||
PivotValue: undefined,
|
||||
Expander: undefined,
|
||||
Filter: undefined,
|
||||
Placeholder: undefined,
|
||||
// All Columns
|
||||
sortable: undefined, // use table default
|
||||
resizable: undefined, // use table default
|
||||
filterable: undefined, // use table default
|
||||
show: true,
|
||||
minWidth: 100,
|
||||
minResizeWidth: 11,
|
||||
// Cells only
|
||||
className: '',
|
||||
style: {},
|
||||
getProps: emptyObj,
|
||||
// Pivot only
|
||||
aggregate: undefined,
|
||||
// Headers only
|
||||
headerClassName: '',
|
||||
headerStyle: {},
|
||||
getHeaderProps: emptyObj,
|
||||
// Footers only
|
||||
footerClassName: '',
|
||||
footerStyle: {},
|
||||
getFooterProps: emptyObj,
|
||||
filterMethod: undefined,
|
||||
filterAll: false,
|
||||
sortMethod: undefined,
|
||||
},
|
||||
|
||||
// Global Expander Column Defaults
|
||||
expanderDefaults: {
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
filterable: false,
|
||||
width: 35,
|
||||
},
|
||||
|
||||
pivotDefaults: {
|
||||
// extend the defaults for pivoted columns here
|
||||
},
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingText: 'Loading...',
|
||||
noDataText: 'No rows found',
|
||||
pageText: 'Page',
|
||||
ofText: 'of',
|
||||
rowsText: 'rows',
|
||||
pageJumpText: 'jump to page',
|
||||
rowsSelectorText: 'rows per page',
|
||||
|
||||
// Components
|
||||
TableComponent: ({ children, className, ...rest }) => (
|
||||
<div
|
||||
className={classnames('rt-table', className)}
|
||||
role="grid"
|
||||
// tabIndex='0'
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
TheadComponent: _.makeTemplateComponent('rt-thead', 'Thead'),
|
||||
TbodyComponent: _.makeTemplateComponent('rt-tbody', 'Tbody'),
|
||||
TrGroupComponent: ({ children, className, ...rest }) => (
|
||||
<div className={classnames('rt-tr-group', className)} role="rowgroup" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
TrComponent: ({ children, className, ...rest }) => (
|
||||
<div className={classnames('rt-tr', className)} role="row" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
ThComponent: ({
|
||||
toggleSort, className, children, ...rest
|
||||
}) => (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
className={classnames('rt-th', className)}
|
||||
onClick={e => toggleSort && toggleSort(e)}
|
||||
role="columnheader"
|
||||
tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
TdComponent: ({
|
||||
toggleSort, className, children, ...rest
|
||||
}) => (
|
||||
<div className={classnames('rt-td', className)} role="gridcell" {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
TfootComponent: _.makeTemplateComponent('rt-tfoot', 'Tfoot'),
|
||||
FilterComponent: ({ filter, onChange, column }) => (
|
||||
<input
|
||||
type="text"
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
placeholder={column.Placeholder}
|
||||
value={filter ? filter.value : ''}
|
||||
onChange={event => onChange(event.target.value)}
|
||||
/>
|
||||
),
|
||||
ExpanderComponent: ({ isExpanded }) => (
|
||||
<div className={classnames('rt-expander', isExpanded && '-open')}>•</div>
|
||||
),
|
||||
PivotValueComponent: ({ subRows, value }) => (
|
||||
<span>
|
||||
{value} {subRows && `(${subRows.length})`}
|
||||
</span>
|
||||
),
|
||||
AggregatedComponent: ({ subRows, column }) => {
|
||||
const previewValues = subRows.filter(d => typeof d[column.id] !== 'undefined').map((row, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<span key={i}>
|
||||
{row[column.id]}
|
||||
{i < subRows.length - 1 ? ', ' : ''}
|
||||
</span>
|
||||
))
|
||||
return <span>{previewValues}</span>
|
||||
},
|
||||
PivotComponent: undefined, // this is a computed default generated using
|
||||
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
|
||||
PaginationComponent: Pagination,
|
||||
PreviousComponent: undefined,
|
||||
NextComponent: undefined,
|
||||
LoadingComponent: ({
|
||||
className, loading, loadingText, ...rest
|
||||
}) => (
|
||||
<div className={classnames('-loading', { '-active': loading }, className)} {...rest}>
|
||||
<div className="-loading-inner">{loadingText}</div>
|
||||
</div>
|
||||
),
|
||||
NoDataComponent: _.makeTemplateComponent('rt-noData', 'NoData'),
|
||||
ResizerComponent: _.makeTemplateComponent('rt-resizer', 'Resizer'),
|
||||
PadRowComponent: () => <span> </span>,
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
<div style="text-align:center;">
|
||||
<a href="https://github.com/react-tools/react-table" target="\_parent"><img src="https://github.com/react-tools/media/raw/master/logo-react-table.png" alt="React Table Logo" style="width:450px;"/></a>
|
||||
</div>
|
||||
|
||||
# ReactTable - expanding with HOCs
|
||||
|
||||
This documentation is about expanding ReactTable using Higher Order Components/Functions.
|
||||
|
||||
## Covered in this README
|
||||
|
||||
* A Brief explanation of HOCs and why they are a good approach for ReactTable enhancements
|
||||
* Documentation of the currently available HOCs
|
||||
* TreeTable
|
||||
* SelectTable
|
||||
* Documentation of the standard for writing HOCs with ReactTable
|
||||
|
||||
## What are HOCs and why use them with ReactTable
|
||||
|
||||
HOCs (or Higher Order Components/Functions) are either a React Component (or a function that returns a React Component)
|
||||
that are used to enhance the functionality of an existing component. How much you can enhance depends on the props that
|
||||
the component exposes.
|
||||
|
||||
Fortunately, ReactTable exposes a LOT of functionality as props to the component. In some cases there are too many
|
||||
props to keep track of and that is where HOCs come in.
|
||||
|
||||
You can write a HOC that just focusses on the additional functionality you want to enhance and keep those enhancements to
|
||||
reuse over and over again when you need them. You don't have to edit the ReactSource code, just wrap ReactTable in one or
|
||||
more HOCs (more on some issues related to chaining HOCs later) that provide the additional functionality you want to expose.
|
||||
|
||||
The most obvious HOC is one that can add `checkbox` or select functionality. The HOC included provides `select` functionality
|
||||
that allows the developer to specify if they want a `checkbox` or `radio` style of select column. The implementation of the
|
||||
selection is recorded (e.g. in component state, Redux, etc.) and how to manage multiple selections. The HOC really only handles
|
||||
the rendering pieces.
|
||||
|
||||
But there is more documentation on the `select` HOC below.
|
||||
|
||||
## Currently Available HOCs
|
||||
|
||||
Any of the below HOCs can be imported from react-table like so:
|
||||
|
||||
```javascript
|
||||
import ReactTable from "react-table";
|
||||
import treeTableHOC from "react-table/lib/hoc/treeTable";
|
||||
|
||||
const TreeTable = treeTableHOC(ReactTable);
|
||||
```
|
||||
|
||||
Swap `treeTable` and `TreeTable` with any of the other HOC names as necessary.
|
||||
|
||||
### TreeTable
|
||||
|
||||
TreeTable takes over the rendering of the generated pivot rows of ReactTable so that they appear more like an expandable Tree.
|
||||
|
||||
It accomplishes this by rendering a 100% wide div and then only rendering the cell that controls the pivot at that level.
|
||||
|
||||
Using it is as simple as doing the following:
|
||||
|
||||
```javascript
|
||||
const TreeTable = treeTableHOC(ReactTable);
|
||||
```
|
||||
|
||||
After you have done the above, you can then use `TreeTable` just as you would `ReactTable` but it will render pivots using
|
||||
the Tree style described above.
|
||||
|
||||
### SelectTable
|
||||
|
||||
SelectTable is a little trickier. The HOCs attempt to avoid adding additional state and, as there is no internal ID for a row that
|
||||
can be relied on to be static (ReactTable just reuses indexes when rendering) the developer has to maintain the state outside of even
|
||||
the wrapped component. So it is largely based on callbacks.
|
||||
|
||||
You include the HOC in the same manner as you would for the treeTableHOC but then need to provide the following overrides:
|
||||
|
||||
* isSelected - returns `true` if the key passed is selected otherwise it should return `false`
|
||||
* selectAll - a property that indicates if the selectAll is set (`true|false`)
|
||||
* toggleAll - called when the user clicks the `selectAll` checkbox/radio
|
||||
* toggleSelection - called when the use clicks a specific checkbox/radio in a row
|
||||
* selectType - either `checkbox|radio` to indicate what type of selection is required
|
||||
|
||||
**Note:** The select field defaults to the accessor `_id` property in order to render the select field for that particular row. If your objects have different
|
||||
unique ID fields, make sure to tell React Table that by passing it the `keyField` property.
|
||||
|
||||
```javascript
|
||||
<ReactTable keyField='id' />
|
||||
```
|
||||
|
||||
In the case of `radio` there is no `selectAll` displayed but the developer is responsible for only making one selection in
|
||||
the controlling component's state. You could select multiple but it wouldn't make sense and you should use `checkbox` instead.
|
||||
|
||||
You also have to decide what `selectAll` means. Given ReactTable is a paged solution there are other records off-page. When someone
|
||||
selects the `selectAll` checkbox, should it mark every possible record, only what might be visible to due a Filter or only those items
|
||||
on the current page?
|
||||
|
||||
The example opts for the middle approach so it gets a `ref` to the ReactTable instance and pulls the `sortedData` out of the resolved
|
||||
state (then walks through those records and pulls their ID into the `selection` state of the controlling component).
|
||||
|
||||
You can also replace the input component that is used to render the select box and select all box:
|
||||
|
||||
* SelectAllInputComponent - the checkbox in the top left corner
|
||||
* SelectInputComponent - the checkbox used on a row
|
||||
|
||||
### SelectTreeTable
|
||||
|
||||
SelectTreeTable is a combination of TreeTable and SelectTable.
|
||||
|
||||
To function correctly the chain has to be in the correct order as follows (see the comments in the guid on HOCs below).
|
||||
|
||||
```javascript
|
||||
const SelectTreeTable = selectTableHOC(treeTableHOC(ReactTable));
|
||||
```
|
||||
|
||||
In this particular instance it is (probably) because the functions need access to the state on the wrapped component to manage
|
||||
the selected items. Although that is not totally clearly the issue.
|
||||
|
||||
### FoldableTable
|
||||
FoldableTable is a HOC that make the columns are foldable. The reason I developed this HOC because when working on the real project related to the financial which display so many columns for validation and comparison.
|
||||
|
||||
So foldable columns allow users to temporary hidden the unwanted to columns so that they can focus on the data that they want to see.
|
||||
|
||||
#### How it works
|
||||
|
||||
```javascript
|
||||
const FoldableTable = FoldableTableHOC(ReactTable);
|
||||
```
|
||||
|
||||
It will scan all the columns which `foldable` is `true` and apply the foldable column feature. This feature will work for both normal columns and header columns as samples below.
|
||||
|
||||
- With Header Columns
|
||||
```javascript
|
||||
render(){
|
||||
return <FoldableTable
|
||||
columns={[{
|
||||
Header: "Name",
|
||||
foldable: true,
|
||||
columns: [{
|
||||
Header: "First Name",
|
||||
accessor: "first_name"
|
||||
},{
|
||||
Header: "Last Name",
|
||||
accessor: "last_name"
|
||||
}]
|
||||
},{
|
||||
Header: "Info",
|
||||
foldable: true,
|
||||
columns: [{
|
||||
Header: "Email",
|
||||
accessor: "email"
|
||||
},{
|
||||
Header: "Gender",
|
||||
accessor: "gender"
|
||||
}]
|
||||
}]
|
||||
}/>
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
- With Nornal Columns
|
||||
```javascript
|
||||
render() {
|
||||
return <FoldableTable
|
||||
columns={[{
|
||||
Header: "First Name",
|
||||
accessor: "first_name"
|
||||
},
|
||||
{
|
||||
Header: "Last Name",
|
||||
accessor: "last_name",
|
||||
foldable: true,
|
||||
},
|
||||
{
|
||||
Header: "Email",
|
||||
accessor: "email",
|
||||
foldable: true,
|
||||
},
|
||||
{
|
||||
Header: "Gender",
|
||||
accessor: "gender",
|
||||
foldable: true,
|
||||
}]}></FoldableTable>
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
- The `FoldableTable` also fully compatible with existing HOCs, below is with selectTableHOC.
|
||||
|
||||

|
||||
|
||||
#### State management
|
||||
If you would like to manage the state of FoldableTable, then add the following codes.
|
||||
```javascript
|
||||
render() {
|
||||
return <FoldableTable
|
||||
onFoldChange={newFolded => this.setState(p => { return { folded: newFolded } })}
|
||||
folded={this.state.folded}
|
||||
/>
|
||||
}
|
||||
```
|
||||
#### Custom Components
|
||||
- FoldIconComponent: to render the Icon of buttons.
|
||||
- FoldButtonComponent: to render the folding buttons for each Column.
|
||||
With default rendering as below.
|
||||
|
||||
```javascript
|
||||
const defaultFoldIconComponent = ({ collapsed }) => {
|
||||
//Render your Icon here
|
||||
}
|
||||
|
||||
const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => {
|
||||
//Render your button here.
|
||||
}
|
||||
```
|
||||
|
||||
### AdvancedExpandTable
|
||||
|
||||
HOC which allows any Cell in the row to toggle the row's
|
||||
SubComponent (expand/collapse). Also allows the SubComponent to toggle itself. Technically supports toggling any row's SubComponent.
|
||||
|
||||
These are the expand functions available to any SubComponent or Column Cell:
|
||||
|
||||
```
|
||||
toggleRowSubComponent
|
||||
showRowSubComponent
|
||||
hideRowSubComponent
|
||||
```
|
||||
|
||||
They are available through the `props.columnProps.rest` object.
|
||||
|
||||
On any change to the props, the table will reset the expanded state.
|
||||
|
||||
Accepts a onExpandedChange callback to be called whenever the table expanded state changes
|
||||
|
||||
Note: only supports 1 level of nesting.
|
||||
|
||||
Example usage in a Column Cell Renderer:
|
||||
|
||||
```javascript
|
||||
Cell: props => {
|
||||
const {
|
||||
value
|
||||
columnProps: { rest: { showRowSubComponent } },
|
||||
nestingPath
|
||||
} = props;
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={e => showRowSubComponent(nestingPath, e)}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Example usage in the ReactTable SubComponent (toggle itself):
|
||||
|
||||
```javascript
|
||||
const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable);
|
||||
|
||||
<AdvancedExpandReactTable>
|
||||
...
|
||||
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={e => toggleRowSubComponent({ nestingPath }, e)}
|
||||
>
|
||||
{row.value}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
Each Column Renderer (E.g. Cell ) gets the expand functions in its props and each SubComponent gets the expand functions in its props
|
||||
|
||||
Expand functions takes the `nestingPath` or `rowInfo` given to each
|
||||
Column Renderer and SubComponent already by ReactTable.
|
||||
|
||||
## HOC Guide for ReactTable
|
||||
|
||||
There are a few rules required when writing a HOC for ReactTable (other than meeting the normal lint standards - which are
|
||||
still being developed).
|
||||
|
||||
Firstly, there are issues with `ref` when you write a HOC. Consider a deeply nested component wrapped in multiple HOCs...
|
||||
|
||||
A HOC in the middle of the chain requires access to the instance of the component it thinks it is wrapping but there is at
|
||||
least one other wrapper in the way. The challenge is: How do I get to the actual wrapped component?
|
||||
|
||||
Each HOC is required to be a React Class so that a `ref` can be obtained against each component:
|
||||
|
||||
```Javascript
|
||||
<Component ... ref={r => this.wrappedInstance = r} />
|
||||
```
|
||||
|
||||
_NOTE:_ "Component" can also be the `<ReactTable />` instance.
|
||||
|
||||
Then the following method needs
|
||||
to be placed on the class so that it exposes the correct instance of ReactTable:
|
||||
|
||||
```Javascript
|
||||
getWrappedInstance() {
|
||||
if (!this.wrappedInstance) console.warn('<component name here> - No wrapped instance')
|
||||
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
|
||||
else return this.wrappedInstance
|
||||
}
|
||||
```
|
||||
|
||||
Essentially this will walk down the chain (if there are chained HOCs) and stop when it gets to the end and return the wrapped instance.
|
||||
|
||||
Finally, sometimes the chains need to be in a specific order to function correctly. It is not clear if this is just an architectural
|
||||
issue or if it would be better solved using a library like `recompose`. Anyone who is able to contribute a reliable solution to this
|
||||
is welcome to submit a PR.
|
||||
@@ -0,0 +1,199 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import set from 'lodash.set'
|
||||
import get from 'lodash.get'
|
||||
|
||||
/*
|
||||
AvancedExpandTableHOC for ReactTable
|
||||
|
||||
HOC which allows any Cell in the row to toggle the row's
|
||||
SubComponent. Also allows the SubComponent to toggle itself.
|
||||
|
||||
Expand functions available to any SubComponent or Column Cell:
|
||||
toggleRowSubComponent
|
||||
showRowSubComponent
|
||||
hideRowSubComponent
|
||||
|
||||
Each Column Renderer (E.g. Cell ) gets the expand functions in its props
|
||||
And Each SubComponent gets the expand functions in its props
|
||||
|
||||
Expand functions takes the `rowInfo` given to each
|
||||
Column Renderer and SubComponent already by ReactTable.
|
||||
*/
|
||||
|
||||
export const subComponentWithToggle = (SubComponent, expandFuncs) => props => (
|
||||
<SubComponent {...props} {...expandFuncs} />
|
||||
)
|
||||
|
||||
// each cell in the column gets passed the function to toggle a sub component
|
||||
export const columnsWithToggle = (columns, expandFuncs) =>
|
||||
columns.map(column => {
|
||||
if (column.columns) {
|
||||
return {
|
||||
...column,
|
||||
columns: columnsWithToggle(column.columns, expandFuncs),
|
||||
}
|
||||
}
|
||||
return {
|
||||
...column,
|
||||
getProps () {
|
||||
return {
|
||||
...expandFuncs,
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
export const advancedExpandTableHOC = TableComponent =>
|
||||
class AdvancedExpandTable extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
expanded: {},
|
||||
}
|
||||
this.toggleRowSubComponent = this.toggleRowSubComponent.bind(this)
|
||||
this.showRowSubComponent = this.showRowSubComponent.bind(this)
|
||||
this.hideRowSubComponent = this.hideRowSubComponent.bind(this)
|
||||
this.getTdProps = this.getTdProps.bind(this)
|
||||
this.fireOnExpandedChange = this.fireOnExpandedChange.bind(this)
|
||||
this.expandFuncs = {
|
||||
toggleRowSubComponent: this.toggleRowSubComponent,
|
||||
showRowSubComponent: this.showRowSubComponent,
|
||||
hideRowSubComponent: this.hideRowSubComponent,
|
||||
}
|
||||
}
|
||||
|
||||
// after initial render if we get new
|
||||
// data, columns, page changes, etc.
|
||||
// we reset expanded state.
|
||||
componentWillReceiveProps () {
|
||||
this.setState({
|
||||
expanded: {},
|
||||
})
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
columns: PropTypes.array.isRequired,
|
||||
SubComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element])
|
||||
.isRequired,
|
||||
onExpandedChange: PropTypes.func,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onExpandedChange: null,
|
||||
};
|
||||
|
||||
static DisplayName = 'AdvancedExpandTable';
|
||||
|
||||
// since we pass the expand functions to each Cell,
|
||||
// we need to filter it out from being passed as an
|
||||
// actual DOM attribute. See getProps in columnsWithToggle above.
|
||||
static TdComponent ({
|
||||
toggleRowSubComponent,
|
||||
showRowSubComponent,
|
||||
hideRowSubComponent,
|
||||
...rest
|
||||
}) {
|
||||
return <TableComponent.defaultProps.TdComponent {...rest} />
|
||||
}
|
||||
|
||||
fireOnExpandedChange (rowInfo, e) {
|
||||
// fire callback once state has changed.
|
||||
if (this.props.onExpandedChange) {
|
||||
this.props.onExpandedChange(rowInfo, e)
|
||||
}
|
||||
}
|
||||
|
||||
resolveNewTableState (rowInfoOrNestingPath, e, expandType) {
|
||||
// derive nestingPath if only rowInfo is passed
|
||||
let nestingPath = rowInfoOrNestingPath
|
||||
|
||||
if (rowInfoOrNestingPath.nestingPath) {
|
||||
nestingPath = rowInfoOrNestingPath.nestingPath
|
||||
}
|
||||
|
||||
this.setState(
|
||||
prevState => {
|
||||
const isExpanded = get(prevState.expanded, nestingPath)
|
||||
// since we do not support nested rows, a shallow clone is okay.
|
||||
const newExpanded = { ...prevState.expanded }
|
||||
|
||||
switch (expandType) {
|
||||
case 'show':
|
||||
set(newExpanded, nestingPath, {})
|
||||
break
|
||||
case 'hide':
|
||||
set(newExpanded, nestingPath, false)
|
||||
break
|
||||
default:
|
||||
// toggle
|
||||
set(newExpanded, nestingPath, isExpanded ? false : {})
|
||||
}
|
||||
return {
|
||||
...prevState,
|
||||
expanded: newExpanded,
|
||||
}
|
||||
},
|
||||
() => this.fireOnExpandedChange(rowInfoOrNestingPath, e)
|
||||
)
|
||||
}
|
||||
|
||||
toggleRowSubComponent (rowInfo, e) {
|
||||
this.resolveNewTableState(rowInfo, e)
|
||||
}
|
||||
|
||||
showRowSubComponent (rowInfo, e) {
|
||||
this.resolveNewTableState(rowInfo, e, 'show')
|
||||
}
|
||||
|
||||
hideRowSubComponent (rowInfo, e) {
|
||||
this.resolveNewTableState(rowInfo, e, 'hide')
|
||||
}
|
||||
|
||||
getTdProps (tableState, rowInfo, column) {
|
||||
const { expander } = column
|
||||
|
||||
if (!expander) {
|
||||
// no overrides
|
||||
return {}
|
||||
}
|
||||
|
||||
return {
|
||||
// only override onClick for column Td
|
||||
onClick: e => {
|
||||
this.toggleRowSubComponent(rowInfo, e)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
getWrappedInstance () {
|
||||
if (!this.wrappedInstance) { console.warn('AdvancedExpandTable - No wrapped instance') }
|
||||
if (this.wrappedInstance.getWrappedInstance) {
|
||||
return this.wrappedInstance.getWrappedInstance()
|
||||
}
|
||||
return this.wrappedInstance
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
columns, SubComponent, onExpandedChange, ...rest
|
||||
} = this.props
|
||||
|
||||
const wrappedColumns = columnsWithToggle(columns, this.expandFuncs)
|
||||
const WrappedSubComponent = subComponentWithToggle(
|
||||
SubComponent,
|
||||
this.expandFuncs
|
||||
)
|
||||
|
||||
return (
|
||||
<TableComponent
|
||||
{...rest}
|
||||
columns={wrappedColumns}
|
||||
expanded={this.state.expanded}
|
||||
getTdProps={this.getTdProps}
|
||||
SubComponent={WrappedSubComponent}
|
||||
TdComponent={AdvancedExpandTable.TdComponent}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import React from 'react'
|
||||
import left from './left.svg'
|
||||
import right from './right.svg'
|
||||
|
||||
const defaultFoldIconComponent = ({ collapsed }) => {
|
||||
const style = { width: 25 }
|
||||
|
||||
if (collapsed) return <img src={right} style={style} alt="right" />
|
||||
return <img src={left} style={style} alt="left" />
|
||||
}
|
||||
|
||||
const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => {
|
||||
const style = {
|
||||
marginLeft: '0px',
|
||||
marginTop: '-5px',
|
||||
marginBottom: '-8px',
|
||||
float: 'left',
|
||||
cursor: 'pointer',
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={style} onClick={onClick}>
|
||||
{icon}
|
||||
</div>
|
||||
{!collapsed && <div>{header}</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReactTable => {
|
||||
const wrapper = class RTFoldableTable extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
folded: props.onFoldChange ? undefined : {},
|
||||
resized: props.resized || [],
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
if (this.state.resized !== newProps.resized) {
|
||||
this.setState(p => ({ resized: newProps.resized }))
|
||||
}
|
||||
}
|
||||
|
||||
onResizedChange = resized => {
|
||||
const { onResizedChange } = this.props
|
||||
if (onResizedChange) onResizedChange(resized)
|
||||
else {
|
||||
this.setState(p => ({ resized }))
|
||||
}
|
||||
}
|
||||
|
||||
removeResized = column => {
|
||||
const { id } = column
|
||||
if (!id) return
|
||||
|
||||
const { resized } = this.state
|
||||
if (!resized) return
|
||||
|
||||
const rs = resized.find(r => r.id === id)
|
||||
if (!rs) return
|
||||
|
||||
const newResized = resized.filter(r => r !== rs)
|
||||
this.onResizedChange(newResized)
|
||||
}
|
||||
|
||||
// this is so we can expose the underlying ReactTable.
|
||||
getWrappedInstance = () => {
|
||||
if (!this.wrappedInstance) console.warn('RTFoldableTable - No wrapped instance')
|
||||
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
|
||||
return this.wrappedInstance
|
||||
}
|
||||
|
||||
getCopiedKey = key => {
|
||||
const { foldableOriginalKey } = this.props
|
||||
return `${foldableOriginalKey}${key}`
|
||||
}
|
||||
|
||||
copyOriginals = column => {
|
||||
const { FoldedColumn } = this.props
|
||||
|
||||
// Stop copy if the column already copied
|
||||
if (column.original_Header) return
|
||||
|
||||
Object.keys(FoldedColumn).forEach(k => {
|
||||
const copiedKey = this.getCopiedKey(k)
|
||||
|
||||
if (k === 'Cell') column[copiedKey] = column[k] ? column[k] : c => c.value
|
||||
else column[copiedKey] = column[k]
|
||||
})
|
||||
|
||||
// Copy sub Columns
|
||||
if (column.columns && !column.original_Columns) column.original_Columns = column.columns
|
||||
|
||||
// Copy Header
|
||||
if (!column.original_Header) column.original_Header = column.Header
|
||||
}
|
||||
|
||||
restoreToOriginal = column => {
|
||||
const { FoldedColumn } = this.props
|
||||
|
||||
Object.keys(FoldedColumn).forEach(k => {
|
||||
// ignore header as handling by foldableHeaderRender
|
||||
if (k === 'Header') return
|
||||
|
||||
const copiedKey = this.getCopiedKey(k)
|
||||
column[k] = column[copiedKey]
|
||||
})
|
||||
|
||||
if (column.columns && column.original_Columns) column.columns = column.original_Columns
|
||||
}
|
||||
|
||||
getState = () => (this.props.onFoldChange ? this.props.folded : this.state.folded)
|
||||
|
||||
isFolded = col => {
|
||||
const folded = this.getState()
|
||||
return folded[col.id] === true
|
||||
}
|
||||
|
||||
foldingHandler = col => {
|
||||
if (!col || !col.id) return
|
||||
|
||||
const { onFoldChange } = this.props
|
||||
const folded = this.getState()
|
||||
const { id } = col
|
||||
|
||||
const newFold = Object.assign({}, folded)
|
||||
newFold[id] = !newFold[id]
|
||||
|
||||
// Remove the Resized if have
|
||||
this.removeResized(col)
|
||||
|
||||
if (onFoldChange) onFoldChange(newFold)
|
||||
else {
|
||||
this.setState(previous => ({ folded: newFold }))
|
||||
}
|
||||
}
|
||||
|
||||
foldableHeaderRender = cell => {
|
||||
const { FoldButtonComponent, FoldIconComponent } = this.props
|
||||
const { column } = cell
|
||||
const collapsed = this.isFolded(column)
|
||||
const icon = React.createElement(FoldIconComponent, { collapsed })
|
||||
const onClick = () => this.foldingHandler(column)
|
||||
|
||||
return React.createElement(FoldButtonComponent, {
|
||||
header: column.original_Header,
|
||||
collapsed,
|
||||
icon,
|
||||
onClick,
|
||||
})
|
||||
}
|
||||
|
||||
applyFoldableForColumn = column => {
|
||||
const collapsed = this.isFolded(column)
|
||||
const { FoldedColumn } = this.props
|
||||
|
||||
// Handle Column Header
|
||||
if (column.columns) {
|
||||
if (collapsed) {
|
||||
column.columns = [FoldedColumn]
|
||||
column.width = FoldedColumn.width
|
||||
column.style = FoldedColumn.style
|
||||
} else this.restoreToOriginal(column)
|
||||
}
|
||||
// Handle Normal Column.
|
||||
else if (collapsed) column = Object.assign(column, FoldedColumn)
|
||||
else {
|
||||
this.restoreToOriginal(column)
|
||||
}
|
||||
}
|
||||
|
||||
applyFoldableForColumns = columns =>
|
||||
columns.map((col, index) => {
|
||||
if (!col.foldable) return col
|
||||
|
||||
// If col don't have id then generate id based on index
|
||||
if (!col.id) col.id = `col_${index}`
|
||||
|
||||
this.copyOriginals(col)
|
||||
// Replace current header with internal header render.
|
||||
col.Header = c => this.foldableHeaderRender(c)
|
||||
// apply foldable
|
||||
this.applyFoldableForColumn(col)
|
||||
|
||||
// return the new column out
|
||||
return col
|
||||
})
|
||||
|
||||
render() {
|
||||
const {
|
||||
columns: originalCols,
|
||||
FoldButtonComponent,
|
||||
FoldIconComponent,
|
||||
FoldedColumn,
|
||||
...rest
|
||||
} = this.props
|
||||
const columns = this.applyFoldableForColumns([...originalCols])
|
||||
|
||||
const extra = {
|
||||
columns,
|
||||
onResizedChange: this.onResizedChange,
|
||||
resized: this.state.resized,
|
||||
}
|
||||
|
||||
return <ReactTable {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.displayName = 'RTFoldableTable'
|
||||
wrapper.defaultProps = {
|
||||
FoldIconComponent: defaultFoldIconComponent,
|
||||
FoldButtonComponent: defaultFoldButtonComponent,
|
||||
foldableOriginalKey: 'original_',
|
||||
FoldedColumn: {
|
||||
Cell: c => '',
|
||||
width: 30,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
filterable: false,
|
||||
},
|
||||
}
|
||||
|
||||
return wrapper
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M327.3,98.9l-2.1,1.8l-156.5,136c-5.3,4.6-8.6,11.5-8.6,19.2c0,7.7,3.4,14.6,8.6,19.2L324.9,411l2.6,2.3 c2.5,1.7,5.5,2.7,8.7,2.7c8.7,0,15.8-7.4,15.8-16.6h0V112.6h0c0-9.2-7.1-16.6-15.8-16.6C332.9,96,329.8,97.1,327.3,98.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 583 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M184.7,413.1l2.1-1.8l156.5-136c5.3-4.6,8.6-11.5,8.6-19.2c0-7.7-3.4-14.6-8.6-19.2L187.1,101l-2.6-2.3 C182,97,179,96,175.8,96c-8.7,0-15.8,7.4-15.8,16.6h0v286.8h0c0,9.2,7.1,16.6,15.8,16.6C179.1,416,182.2,414.9,184.7,413.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 585 B |
@@ -0,0 +1,119 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import React from 'react'
|
||||
|
||||
const defaultSelectInputComponent = props => {
|
||||
return (
|
||||
<input
|
||||
type={props.selectType || 'checkbox'}
|
||||
aria-label={`${props.checked ? 'Un-select':'Select'} row with id:${props.id}` }
|
||||
checked={props.checked}
|
||||
id={props.id}
|
||||
onClick={e => {
|
||||
const { shiftKey } = e
|
||||
e.stopPropagation()
|
||||
props.onClick(props.id, shiftKey, props.row)
|
||||
}}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default (Component, options) => {
|
||||
const wrapper = class RTSelectTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
rowSelector(row) {
|
||||
if (!row || !row.hasOwnProperty(this.props.keyField)) return null
|
||||
const { toggleSelection, selectType, keyField } = this.props
|
||||
const checked = this.props.isSelected(row[this.props.keyField])
|
||||
const inputProps = {
|
||||
checked,
|
||||
onClick: toggleSelection,
|
||||
selectType,
|
||||
row,
|
||||
id: `select-${row[keyField]}`
|
||||
}
|
||||
return React.createElement(this.props.SelectInputComponent, inputProps)
|
||||
}
|
||||
|
||||
headSelector(row) {
|
||||
const { selectType } = this.props
|
||||
if (selectType === 'radio') return null
|
||||
|
||||
const { toggleAll, selectAll: checked, SelectAllInputComponent } = this.props
|
||||
const inputProps = {
|
||||
checked,
|
||||
onClick: toggleAll,
|
||||
selectType,
|
||||
id: 'select-all'
|
||||
}
|
||||
|
||||
return React.createElement(SelectAllInputComponent, inputProps)
|
||||
}
|
||||
|
||||
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
|
||||
getWrappedInstance() {
|
||||
if (!this.wrappedInstance) console.warn('RTSelectTable - No wrapped instance')
|
||||
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
|
||||
else return this.wrappedInstance
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
columns: originalCols,
|
||||
isSelected,
|
||||
toggleSelection,
|
||||
toggleAll,
|
||||
keyField,
|
||||
selectAll,
|
||||
selectType,
|
||||
selectWidth,
|
||||
SelectAllInputComponent,
|
||||
SelectInputComponent,
|
||||
...rest
|
||||
} = this.props
|
||||
const select = {
|
||||
id: '_selector',
|
||||
accessor: () => 'x', // this value is not important
|
||||
Header: this.headSelector.bind(this),
|
||||
Cell: ci => {
|
||||
return this.rowSelector.bind(this)(ci.original)
|
||||
},
|
||||
width: selectWidth || 30,
|
||||
filterable: false,
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
style: { textAlign: 'center' },
|
||||
}
|
||||
|
||||
const columns = (options !== undefined && options.floatingLeft === true) ? [...originalCols, select] : [select, ...originalCols]
|
||||
const extra = {
|
||||
columns,
|
||||
}
|
||||
return <Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.displayName = 'RTSelectTable'
|
||||
wrapper.defaultProps = {
|
||||
keyField: '_id',
|
||||
isSelected: key => {
|
||||
console.log('No isSelected handler provided:', { key })
|
||||
},
|
||||
selectAll: false,
|
||||
toggleSelection: (key, shift, row) => {
|
||||
console.log('No toggleSelection handler provided:', { key, shift, row })
|
||||
},
|
||||
toggleAll: () => {
|
||||
console.log('No toggleAll handler provided.')
|
||||
},
|
||||
selectType: 'checkbox',
|
||||
SelectInputComponent: defaultSelectInputComponent,
|
||||
SelectAllInputComponent: defaultSelectInputComponent,
|
||||
}
|
||||
|
||||
return wrapper
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import React from 'react'
|
||||
|
||||
export default Component => {
|
||||
const wrapper = class RTTreeTable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.getWrappedInstance.bind(this)
|
||||
this.TrComponent.bind(this)
|
||||
this.getTrProps.bind(this)
|
||||
}
|
||||
|
||||
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
|
||||
getWrappedInstance = () => {
|
||||
if (!this.wrappedInstance) console.warn('RTTreeTable - No wrapped instance')
|
||||
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
|
||||
else return this.wrappedInstance
|
||||
}
|
||||
|
||||
TrComponent = props => {
|
||||
const { ri, ...rest } = props
|
||||
if (ri && ri.groupedByPivot) {
|
||||
const cell = { ...props.children[ri.level] }
|
||||
|
||||
cell.props.style.flex = 'unset'
|
||||
cell.props.style.width = '100%'
|
||||
cell.props.style.maxWidth = 'unset'
|
||||
cell.props.style.paddingLeft = `${this.props.treeTableIndent * ri.level}px`
|
||||
// cell.props.style.backgroundColor = '#DDD';
|
||||
cell.props.style.borderBottom = '1px solid rgba(128,128,128,0.2)'
|
||||
|
||||
return (
|
||||
<div className={`rt-tr ${rest.className}`} role="row" style={rest.style}>
|
||||
{cell}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return <Component.defaultProps.TrComponent {...rest} />
|
||||
}
|
||||
|
||||
getTrProps = (state, ri, ci, instance) => {
|
||||
return { ri }
|
||||
}
|
||||
|
||||
render() {
|
||||
const { columns, treeTableIndent, ...rest } = this.props
|
||||
const { TrComponent, getTrProps } = this
|
||||
const extra = {
|
||||
columns: columns.map(col => {
|
||||
let column = col
|
||||
if (rest.pivotBy && (rest.pivotBy.includes(col.accessor) || rest.pivotBy.includes(col.id))) {
|
||||
column = {
|
||||
id: col.id,
|
||||
accessor: col.accessor,
|
||||
width: `${treeTableIndent}px`,
|
||||
show: false,
|
||||
Header: '',
|
||||
Expander: col.Expander,
|
||||
PivotValue: col.PivotValue,
|
||||
Pivot: col.Pivot,
|
||||
}
|
||||
}
|
||||
return column
|
||||
}),
|
||||
TrComponent,
|
||||
getTrProps,
|
||||
}
|
||||
|
||||
return <Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
|
||||
}
|
||||
}
|
||||
wrapper.displayName = 'RTTreeTable'
|
||||
wrapper.defaultProps = {
|
||||
treeTableIndent: 10,
|
||||
}
|
||||
|
||||
return wrapper
|
||||
}
|
||||
@@ -1,91 +1,66 @@
|
||||
@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
|
||||
|
||||
input-select-style()
|
||||
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
|
||||
|
||||
.ReactTable
|
||||
position:relative
|
||||
table
|
||||
display: flex
|
||||
flex-direction: column
|
||||
border: 1px solid alpha(black, .1)
|
||||
*
|
||||
box-sizing: border-box
|
||||
.rt-table
|
||||
flex: auto 1
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
border: 1px solid alpha(black, .1)
|
||||
display: block
|
||||
overflow-x: auto
|
||||
overflow: auto
|
||||
|
||||
.-td
|
||||
.-th
|
||||
&-inner
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
padding: 7px 5px
|
||||
overflow: hidden
|
||||
z-index:0
|
||||
transition: .3s ease
|
||||
transition-property: width, min-width, padding, opacity
|
||||
|
||||
th
|
||||
td
|
||||
&:hover
|
||||
.-td
|
||||
.-th
|
||||
&-inner
|
||||
position:relative
|
||||
overflow:visible
|
||||
z-index:1
|
||||
text-shadow:
|
||||
0 0 1px white,
|
||||
0 0 2px white,
|
||||
1px 0 3px white,
|
||||
1px 0 4px white,
|
||||
2px 0 5px white,
|
||||
2px 0 6px white,
|
||||
3px 0 7px white,
|
||||
3px 0 8px white,
|
||||
4px 0 9px white,
|
||||
4px 0 10px white,
|
||||
5px 0 11px white,
|
||||
5px 0 12px white,
|
||||
6px 0 13px white,
|
||||
6px 0 14px white,
|
||||
7px 0 15px white,
|
||||
7px 0 16px white,
|
||||
8px 0 17px white,
|
||||
8px 0 18px white,
|
||||
9px 0 19px white,
|
||||
9px 0 20px white
|
||||
|
||||
> *
|
||||
text-shadow: none
|
||||
|
||||
&.-hidden
|
||||
border:0 !important
|
||||
opacity: 0 !important
|
||||
.-th-inner
|
||||
.-td-inner
|
||||
width: 0 !important
|
||||
min-width: 0 !important
|
||||
padding: 0 !important
|
||||
|
||||
thead
|
||||
box-shadow: 0px 5px 20px 0 alpha(black, .1)
|
||||
z-index: 0
|
||||
.rt-thead
|
||||
flex: 1 0 auto
|
||||
display: flex
|
||||
flex-direction: column
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
&.-headerGroups
|
||||
background: alpha(black, .03)
|
||||
border-bottom: 1px solid alpha(black, .05)
|
||||
|
||||
th
|
||||
td
|
||||
background: alpha(black, .03)
|
||||
&.-filters
|
||||
border-bottom: 1px solid alpha(black, 0.05)
|
||||
|
||||
tr
|
||||
input
|
||||
select
|
||||
input-select-style();
|
||||
|
||||
.rt-th
|
||||
border-right: 1px solid alpha(black, 0.02)
|
||||
|
||||
&.-header
|
||||
box-shadow: 0 2px 15px 0px alpha(black, .15)
|
||||
|
||||
.rt-tr
|
||||
text-align:center
|
||||
|
||||
th
|
||||
td
|
||||
width: 1%
|
||||
background: white
|
||||
font-weight:500
|
||||
color: $darkColor
|
||||
.rt-th
|
||||
.rt-td
|
||||
padding: 5px 5px
|
||||
line-height: normal
|
||||
position: relative
|
||||
border-right: 1px solid alpha(black, .05)
|
||||
transition box-shadow .3s $easeOutBack
|
||||
box-shadow:inset 0 0 0 0 transparent
|
||||
@@ -95,40 +70,158 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
||||
&.-cursor-pointer
|
||||
cursor: pointer
|
||||
&:last-child
|
||||
border-right: 0
|
||||
|
||||
tbody
|
||||
z-index:0
|
||||
tr
|
||||
.rt-th:focus
|
||||
outline: none
|
||||
|
||||
.rt-resizable-header
|
||||
overflow: visible
|
||||
&:last-child
|
||||
overflow: hidden
|
||||
|
||||
.rt-resizable-header-content
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
.rt-header-pivot
|
||||
border-right-color: #f7f7f7
|
||||
|
||||
.rt-header-pivot:after, .rt-header-pivot:before
|
||||
left: 100%
|
||||
top: 50%
|
||||
border: solid transparent
|
||||
content: " "
|
||||
height: 0
|
||||
width: 0
|
||||
position: absolute
|
||||
pointer-events: none
|
||||
|
||||
.rt-header-pivot:after
|
||||
border-color: rgba(255, 255, 255, 0)
|
||||
border-left-color: #FFF
|
||||
border-width: 8px
|
||||
margin-top: -8px
|
||||
|
||||
.rt-header-pivot:before
|
||||
border-color: rgba(102, 102, 102, 0)
|
||||
border-left-color: #f7f7f7
|
||||
border-width: 10px
|
||||
margin-top: -10px
|
||||
|
||||
.rt-tbody
|
||||
flex: 99999 1 auto
|
||||
display: flex
|
||||
flex-direction: column
|
||||
overflow: auto
|
||||
// z-index:0
|
||||
.rt-tr-group
|
||||
border-bottom: solid 1px alpha(black, .05)
|
||||
&:last-child
|
||||
border-bottom: 0
|
||||
|
||||
&:first-child td
|
||||
box-shadow: inset 0 20px 20px -20px alpha(black, .2)
|
||||
td
|
||||
.rt-td
|
||||
border-right:1px solid alpha(black, .02)
|
||||
&:last-child
|
||||
border-right:0
|
||||
.rt-expandable
|
||||
cursor: pointer
|
||||
text-overflow: clip
|
||||
.rt-tr-group
|
||||
flex: 1 0 auto
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
.rt-tr
|
||||
flex: 1 0 auto
|
||||
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
|
||||
|
||||
&.striped
|
||||
tbody tr:nth-child(even)
|
||||
&.-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)
|
||||
|
||||
.rt-resizer
|
||||
display: inline-block
|
||||
position: absolute
|
||||
width: 36px
|
||||
top: 0
|
||||
bottom: 0
|
||||
right: -18px
|
||||
cursor: col-resize
|
||||
z-index: 10
|
||||
|
||||
.rt-tfoot
|
||||
flex: 1 0 auto
|
||||
display: flex
|
||||
flex-direction: column
|
||||
box-shadow: 0 0px 15px 0px alpha(black, .15)
|
||||
|
||||
.rt-td
|
||||
border-right:1px solid alpha(black, .05)
|
||||
&:last-child
|
||||
border-right:0
|
||||
|
||||
&.-striped
|
||||
.rt-tr.-odd
|
||||
background: alpha(black, .03)
|
||||
&.highlight
|
||||
tbody tr:hover
|
||||
background: alpha(black, .05)
|
||||
&.-highlight
|
||||
.rt-tbody
|
||||
.rt-tr:not(.-padRow):hover
|
||||
background: alpha(black, .05)
|
||||
|
||||
.-pagination
|
||||
width:100%
|
||||
z-index: 1
|
||||
display:flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
align-items: stretch
|
||||
flex-wrap: wrap
|
||||
padding-top:3px
|
||||
padding: 3px
|
||||
box-shadow: 0 0px 15px 0px alpha(black, .1)
|
||||
border-top: 2px solid alpha(black, .1)
|
||||
|
||||
input
|
||||
select
|
||||
input-select-style()
|
||||
|
||||
.-btn
|
||||
appearance:none
|
||||
display:block
|
||||
width:100%
|
||||
height:100%
|
||||
border: 0
|
||||
border-radius: 3px
|
||||
padding: 6px
|
||||
@@ -147,11 +240,6 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
background: alpha(black, .3)
|
||||
color: white
|
||||
|
||||
.-previous
|
||||
.-center
|
||||
.-next
|
||||
margin:3px 0
|
||||
|
||||
.-previous
|
||||
.-next
|
||||
flex: 1
|
||||
@@ -169,7 +257,7 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
|
||||
.-pageInfo
|
||||
display: inline-block
|
||||
margin: 0 10px 3px
|
||||
margin: 3px 10px
|
||||
white-space: nowrap
|
||||
|
||||
.-pageJump
|
||||
@@ -179,8 +267,20 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
text-align:center
|
||||
|
||||
.-pageSizeOptions
|
||||
margin:0 10px 3px
|
||||
margin: 3px 10px
|
||||
|
||||
.rt-noData
|
||||
display:block
|
||||
position:absolute
|
||||
left:50%
|
||||
top:50%
|
||||
transform: translate(-50%, -50%)
|
||||
background: alpha(white, .8)
|
||||
transition: all .3s ease
|
||||
z-index: 1
|
||||
pointer-events: none
|
||||
padding: 20px
|
||||
color: alpha(black, .5)
|
||||
|
||||
.-loading
|
||||
display:block
|
||||
@@ -191,7 +291,7 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
bottom:0
|
||||
background: alpha(white, .8)
|
||||
transition: all .3s ease
|
||||
z-index: 2
|
||||
z-index: -1
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
@@ -209,33 +309,14 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
|
||||
&.-active
|
||||
opacity: 1
|
||||
z-index: 2
|
||||
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
|
||||
.rt-resizing
|
||||
.rt-th
|
||||
.rt-td
|
||||
transition: none!important
|
||||
cursor: col-resize
|
||||
user-select none
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
export default Base =>
|
||||
class extends Base {
|
||||
componentWillMount () {
|
||||
this.setStateWithData(this.getDataModel(this.getResolvedState(), true))
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.fireFetchData()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps, nextState) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newState = this.getResolvedState(nextProps, nextState)
|
||||
|
||||
// Do a deep compare of new and old `defaultOption` and
|
||||
// if they are different reset `option = defaultOption`
|
||||
const defaultableOptions = ['sorted', 'filtered', 'resized', 'expanded']
|
||||
defaultableOptions.forEach(x => {
|
||||
const defaultName = `default${x.charAt(0).toUpperCase() + x.slice(1)}`
|
||||
if (JSON.stringify(oldState[defaultName]) !== JSON.stringify(newState[defaultName])) {
|
||||
newState[x] = newState[defaultName]
|
||||
}
|
||||
})
|
||||
|
||||
// If they change these table options, we need to reset defaults
|
||||
// or else we could get into a state where the user has changed the UI
|
||||
// and then disabled the ability to change it back.
|
||||
// e.g. If `filterable` has changed, set `filtered = defaultFiltered`
|
||||
const resettableOptions = ['sortable', 'filterable', 'resizable']
|
||||
resettableOptions.forEach(x => {
|
||||
if (oldState[x] !== newState[x]) {
|
||||
const baseName = x.replace('able', '')
|
||||
const optionName = `${baseName}ed`
|
||||
const defaultName = `default${optionName.charAt(0).toUpperCase() + optionName.slice(1)}`
|
||||
newState[optionName] = newState[defaultName]
|
||||
}
|
||||
})
|
||||
|
||||
// Props that trigger a data update
|
||||
if (
|
||||
oldState.data !== newState.data ||
|
||||
oldState.columns !== newState.columns ||
|
||||
oldState.pivotBy !== newState.pivotBy ||
|
||||
oldState.sorted !== newState.sorted ||
|
||||
oldState.filtered !== newState.filtered
|
||||
) {
|
||||
this.setStateWithData(this.getDataModel(newState, oldState.data !== newState.data))
|
||||
}
|
||||
}
|
||||
|
||||
setStateWithData (newState, cb) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newResolvedState = this.getResolvedState({}, newState)
|
||||
const { freezeWhenExpanded } = newResolvedState
|
||||
|
||||
// Default to unfrozen state
|
||||
newResolvedState.frozen = false
|
||||
|
||||
// If freezeWhenExpanded is set, check for frozen conditions
|
||||
if (freezeWhenExpanded) {
|
||||
// if any rows are expanded, freeze the existing data and sorting
|
||||
const keys = Object.keys(newResolvedState.expanded)
|
||||
for (let i = 0; i < keys.length; i += 1) {
|
||||
if (newResolvedState.expanded[keys[i]]) {
|
||||
newResolvedState.frozen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the data isn't frozen and either the data or
|
||||
// sorting model has changed, update the data
|
||||
if (
|
||||
(oldState.frozen && !newResolvedState.frozen) ||
|
||||
oldState.sorted !== newResolvedState.sorted ||
|
||||
oldState.filtered !== newResolvedState.filtered ||
|
||||
oldState.showFilters !== newResolvedState.showFilters ||
|
||||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
|
||||
) {
|
||||
// Handle collapseOnsortedChange & collapseOnDataChange
|
||||
if (
|
||||
(oldState.sorted !== newResolvedState.sorted && this.props.collapseOnSortingChange) ||
|
||||
oldState.filtered !== newResolvedState.filtered ||
|
||||
oldState.showFilters !== newResolvedState.showFilters ||
|
||||
(oldState.sortedData &&
|
||||
!newResolvedState.frozen &&
|
||||
oldState.resolvedData !== newResolvedState.resolvedData &&
|
||||
this.props.collapseOnDataChange)
|
||||
) {
|
||||
newResolvedState.expanded = {}
|
||||
}
|
||||
|
||||
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
|
||||
}
|
||||
|
||||
// Set page to 0 if filters change
|
||||
if (oldState.filtered !== newResolvedState.filtered) {
|
||||
newResolvedState.page = 0
|
||||
}
|
||||
|
||||
// Calculate pageSize all the time
|
||||
if (newResolvedState.sortedData) {
|
||||
newResolvedState.pages = newResolvedState.manual
|
||||
? newResolvedState.pages
|
||||
: Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
|
||||
newResolvedState.page = newResolvedState.manual ? newResolvedState.page : Math.max(
|
||||
newResolvedState.page >= newResolvedState.pages
|
||||
? newResolvedState.pages - 1
|
||||
: newResolvedState.page,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
return this.setState(newResolvedState, () => {
|
||||
if (cb) {
|
||||
cb()
|
||||
}
|
||||
if (
|
||||
oldState.page !== newResolvedState.page ||
|
||||
oldState.pageSize !== newResolvedState.pageSize ||
|
||||
oldState.sorted !== newResolvedState.sorted ||
|
||||
oldState.filtered !== newResolvedState.filtered
|
||||
) {
|
||||
this.fireFetchData()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
import React from 'react'
|
||||
import _ from './utils'
|
||||
|
||||
export default Base =>
|
||||
class extends Base {
|
||||
getResolvedState (props, state) {
|
||||
const resolvedState = {
|
||||
..._.compactObject(this.state),
|
||||
..._.compactObject(this.props),
|
||||
..._.compactObject(state),
|
||||
..._.compactObject(props),
|
||||
}
|
||||
return resolvedState
|
||||
}
|
||||
|
||||
getDataModel (newState, dataChanged) {
|
||||
const {
|
||||
columns,
|
||||
pivotBy = [],
|
||||
data,
|
||||
resolveData,
|
||||
pivotIDKey,
|
||||
pivotValKey,
|
||||
subRowsKey,
|
||||
aggregatedKey,
|
||||
nestingLevelKey,
|
||||
originalKey,
|
||||
indexKey,
|
||||
groupedByPivotKey,
|
||||
SubComponent,
|
||||
} = newState
|
||||
|
||||
// Determine if there are Header Groups
|
||||
const hasHeaderGroups = columns.some(column => column.columns)
|
||||
|
||||
// Find the expander column which could be deep in tree of columns
|
||||
const allColumns = _.iterTree(columns, 'columns')
|
||||
const expanderColumn = _.getFirstDefined(allColumns)
|
||||
|
||||
// If we have SubComponent's we need to make sure we have an expander column
|
||||
const hasSubComponentAndNoExpanderColumn = SubComponent && !expanderColumn
|
||||
const columnsWithExpander = hasSubComponentAndNoExpanderColumn
|
||||
? [{ expander: true }, ...columns]
|
||||
: [...columns]
|
||||
|
||||
const makeDecoratedColumn = (column, parentColumn) => {
|
||||
let dcol
|
||||
if (column.expander) {
|
||||
dcol = {
|
||||
...this.props.column,
|
||||
...this.props.expanderDefaults,
|
||||
...column,
|
||||
}
|
||||
} else {
|
||||
dcol = {
|
||||
...this.props.column,
|
||||
...this.props.column,
|
||||
...column,
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure minWidth is not greater than maxWidth if set
|
||||
if (dcol.maxWidth < dcol.minWidth) {
|
||||
dcol.minWidth = dcol.maxWidth
|
||||
}
|
||||
|
||||
if (parentColumn) {
|
||||
dcol.parentColumn = parentColumn
|
||||
}
|
||||
|
||||
// First check for string accessor
|
||||
if (typeof dcol.accessor === 'string') {
|
||||
dcol.id = dcol.id || dcol.accessor
|
||||
const accessorString = dcol.accessor
|
||||
dcol.accessor = row => _.get(row, accessorString)
|
||||
return dcol
|
||||
}
|
||||
|
||||
// Fall back to functional accessor (but require an ID)
|
||||
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.'
|
||||
)
|
||||
}
|
||||
|
||||
// Fall back to an undefined accessor
|
||||
if (!dcol.accessor) {
|
||||
dcol.accessor = () => undefined
|
||||
}
|
||||
|
||||
return dcol
|
||||
}
|
||||
|
||||
const allDecoratedColumns = []
|
||||
|
||||
// Decorate the columns
|
||||
const decorateAndAddToAll = (columns, parentColumn) => columns.map(column => {
|
||||
const decoratedColumn = makeDecoratedColumn(column, parentColumn)
|
||||
if (column.columns) {
|
||||
decoratedColumn.columns = decorateAndAddToAll(column.columns, column)
|
||||
}
|
||||
|
||||
allDecoratedColumns.push(decoratedColumn)
|
||||
return decoratedColumn
|
||||
})
|
||||
|
||||
const decoratedColumns = decorateAndAddToAll(columnsWithExpander)
|
||||
const mapVisibleColumns = columns => columns.map(column => {
|
||||
if (column.columns) {
|
||||
const visibleSubColumns = column.columns.filter(
|
||||
d => (pivotBy.indexOf(d.id) > -1 ? false : _.getFirstDefined(d.show, true))
|
||||
)
|
||||
return {
|
||||
...column,
|
||||
columns: mapVisibleColumns(visibleSubColumns),
|
||||
}
|
||||
}
|
||||
return column
|
||||
})
|
||||
|
||||
const filterVisibleColumns = columns => columns.filter(
|
||||
column =>
|
||||
column.columns
|
||||
? column.columns.length
|
||||
: pivotBy.indexOf(column.id) > -1
|
||||
? false
|
||||
: _.getFirstDefined(column.show, true)
|
||||
)
|
||||
|
||||
// Build the full array of visible columns - this is an array that contains all columns that
|
||||
// are not hidden via pivoting
|
||||
const allVisibleColumns = filterVisibleColumns(mapVisibleColumns(decoratedColumns.slice()))
|
||||
|
||||
// Find any custom pivot location
|
||||
const pivotIndex = allVisibleColumns.findIndex(col => col.pivot)
|
||||
|
||||
// Handle Pivot Columns
|
||||
if (pivotBy.length) {
|
||||
// Retrieve the pivot columns in the correct pivot order
|
||||
const pivotColumns = []
|
||||
pivotBy.forEach(pivotID => {
|
||||
const found = allDecoratedColumns.find(d => d.id === pivotID)
|
||||
if (found) {
|
||||
pivotColumns.push(found)
|
||||
}
|
||||
})
|
||||
|
||||
const PivotParentColumn = pivotColumns.reduce(
|
||||
(prev, current) => prev && prev === current.parentColumn && current.parentColumn,
|
||||
pivotColumns[0].parentColumn
|
||||
)
|
||||
|
||||
let PivotGroupHeader = hasHeaderGroups && PivotParentColumn.Header
|
||||
PivotGroupHeader = PivotGroupHeader || (() => <strong>Pivoted</strong>)
|
||||
|
||||
let pivotColumnGroup = {
|
||||
Header: PivotGroupHeader,
|
||||
columns: pivotColumns.map(col => ({
|
||||
...this.props.pivotDefaults,
|
||||
...col,
|
||||
pivoted: true,
|
||||
})),
|
||||
}
|
||||
|
||||
// Place the pivotColumns back into the visibleColumns
|
||||
if (pivotIndex >= 0) {
|
||||
pivotColumnGroup = {
|
||||
...allVisibleColumns[pivotIndex],
|
||||
...pivotColumnGroup,
|
||||
}
|
||||
allVisibleColumns.splice(pivotIndex, 1, pivotColumnGroup)
|
||||
} else {
|
||||
allVisibleColumns.unshift(pivotColumnGroup)
|
||||
}
|
||||
}
|
||||
|
||||
// Build Visible Columns and Header Groups
|
||||
const allColumnHeaders = []
|
||||
|
||||
const addHeader = column => {
|
||||
let level = 0
|
||||
|
||||
// If this column has children, push them first and add this column to the next level
|
||||
if (column.columns) {
|
||||
const childLevels = column.columns.map(addHeader)
|
||||
level = Math.max(...childLevels) + 1
|
||||
}
|
||||
|
||||
// Add spans above columns without parents (orphans) to fill the space above them
|
||||
if (allColumnHeaders.length <= level) allColumnHeaders.push([])
|
||||
if (level > 0) {
|
||||
// The spans need to contain the shifted headers as children. This finds all of the
|
||||
// columns in the lower level between the first child of this column and the last child
|
||||
// of the preceding column (if there is one)
|
||||
const lowerLevel = allColumnHeaders[level - 1]
|
||||
const precedingColumn = _.last(allColumnHeaders[level])
|
||||
|
||||
const indexOfFirstChildInLowerLevel = lowerLevel.indexOf(column.columns[0])
|
||||
const indexAfterLastChildInPrecedingColumn = precedingColumn
|
||||
? lowerLevel.indexOf(_.last(precedingColumn.columns)) + 1
|
||||
: 0
|
||||
|
||||
// If there are ophans, add a span above them
|
||||
const orphans = lowerLevel.slice(
|
||||
indexAfterLastChildInPrecedingColumn,
|
||||
indexOfFirstChildInLowerLevel
|
||||
)
|
||||
|
||||
if (orphans.length) {
|
||||
allColumnHeaders[level].push({
|
||||
...this.props.column,
|
||||
columns: orphans,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
allColumnHeaders[level].push(column)
|
||||
|
||||
return level
|
||||
}
|
||||
|
||||
allVisibleColumns.forEach(addHeader)
|
||||
|
||||
// visibleColumns is an array containing column definitions for the bottom row of TH elements
|
||||
const visibleColumns = allColumnHeaders.shift()
|
||||
const headerGroups = allColumnHeaders.reverse()
|
||||
|
||||
// Access the data
|
||||
const accessRow = (d, i, level = 0) => {
|
||||
const row = {
|
||||
[originalKey]: d,
|
||||
[indexKey]: i,
|
||||
[subRowsKey]: d[subRowsKey],
|
||||
[nestingLevelKey]: level,
|
||||
}
|
||||
allDecoratedColumns.forEach(column => {
|
||||
if (column.expander) return
|
||||
row[column.id] = column.accessor(d)
|
||||
})
|
||||
if (row[subRowsKey]) {
|
||||
row[subRowsKey] = row[subRowsKey].map((d, i) => accessRow(d, i, level + 1))
|
||||
}
|
||||
return row
|
||||
}
|
||||
|
||||
// // If the data hasn't changed, just use the cached data
|
||||
let resolvedData = this.resolvedData
|
||||
// If the data has changed, run the data resolver and cache the result
|
||||
if (!this.resolvedData || dataChanged) {
|
||||
resolvedData = resolveData(data)
|
||||
this.resolvedData = resolvedData
|
||||
}
|
||||
// Use the resolved data
|
||||
resolvedData = resolvedData.map((d, i) => accessRow(d, i))
|
||||
|
||||
// TODO: Make it possible to fabricate nested rows without pivoting
|
||||
const aggregatingColumns = visibleColumns.filter(d => !d.expander && d.aggregate)
|
||||
|
||||
// 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
|
||||
}
|
||||
if (pivotBy.length) {
|
||||
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]) => ({
|
||||
[pivotIDKey]: keys[i],
|
||||
[pivotValKey]: key,
|
||||
[keys[i]]: key,
|
||||
[subRowsKey]: value,
|
||||
[nestingLevelKey]: i,
|
||||
[groupedByPivotKey]: true,
|
||||
}))
|
||||
// Recurse into the subRows
|
||||
groupedRows = groupedRows.map(rowGroup => {
|
||||
const subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
|
||||
return {
|
||||
...rowGroup,
|
||||
[subRowsKey]: subRows,
|
||||
[aggregatedKey]: true,
|
||||
...aggregate(subRows),
|
||||
}
|
||||
})
|
||||
return groupedRows
|
||||
}
|
||||
resolvedData = groupRecursively(resolvedData, pivotBy)
|
||||
}
|
||||
|
||||
return {
|
||||
...newState,
|
||||
resolvedData,
|
||||
visibleColumns,
|
||||
headerGroups,
|
||||
allDecoratedColumns,
|
||||
hasHeaderGroups,
|
||||
}
|
||||
}
|
||||
|
||||
getSortedData (resolvedState) {
|
||||
const {
|
||||
manual,
|
||||
sorted,
|
||||
filtered,
|
||||
defaultFilterMethod,
|
||||
resolvedData,
|
||||
visibleColumns,
|
||||
allDecoratedColumns,
|
||||
} = resolvedState
|
||||
|
||||
const sortMethodsByColumnID = {}
|
||||
|
||||
allDecoratedColumns.filter(col => col.sortMethod).forEach(col => {
|
||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||
})
|
||||
|
||||
// Resolve the data from either manual data or sorted data
|
||||
return {
|
||||
sortedData: manual
|
||||
? resolvedData
|
||||
: this.sortData(
|
||||
this.filterData(resolvedData, filtered, defaultFilterMethod, allDecoratedColumns),
|
||||
sorted,
|
||||
sortMethodsByColumnID
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fireFetchData () {
|
||||
// determine the current state, preferring certain state values over props
|
||||
const currentState = {
|
||||
...this.getResolvedState(),
|
||||
page: this.getStateOrProp('page'),
|
||||
pageSize: this.getStateOrProp('pageSize'),
|
||||
filter: this.getStateOrProp('filter'),
|
||||
};
|
||||
|
||||
this.props.onFetchData(currentState, this)
|
||||
}
|
||||
|
||||
getPropOrState (key) {
|
||||
return _.getFirstDefined(this.props[key], this.state[key])
|
||||
}
|
||||
|
||||
getStateOrProp (key) {
|
||||
return _.getFirstDefined(this.state[key], this.props[key])
|
||||
}
|
||||
|
||||
filterData (data, filtered, defaultFilterMethod, visibleColumns) {
|
||||
let filteredData = data
|
||||
|
||||
if (filtered.length) {
|
||||
filteredData = filtered.reduce((filteredSoFar, nextFilter) => {
|
||||
const column = visibleColumns.find(x => x.id === nextFilter.id)
|
||||
|
||||
// Don't filter hidden columns or columns that have had their filters disabled
|
||||
if (!column || column.filterable === false) {
|
||||
return filteredSoFar
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || defaultFilterMethod
|
||||
|
||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||
if (column.filterAll) {
|
||||
return filterMethod(nextFilter, filteredSoFar, column)
|
||||
}
|
||||
return filteredSoFar.filter(row => filterMethod(nextFilter, row, column))
|
||||
}, filteredData)
|
||||
|
||||
// Apply the filter to the subrows if we are pivoting, and then
|
||||
// filter any rows without subcolumns because it would be strange to show
|
||||
filteredData = filteredData
|
||||
.map(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
[this.props.subRowsKey]: this.filterData(
|
||||
row[this.props.subRowsKey],
|
||||
filtered,
|
||||
defaultFilterMethod,
|
||||
visibleColumns
|
||||
),
|
||||
}
|
||||
})
|
||||
.filter(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return true
|
||||
}
|
||||
return row[this.props.subRowsKey].length > 0
|
||||
})
|
||||
}
|
||||
|
||||
return filteredData
|
||||
}
|
||||
|
||||
sortData (data, sorted, sortMethodsByColumnID = {}) {
|
||||
if (!sorted.length) {
|
||||
return data
|
||||
}
|
||||
|
||||
const sortedData = (this.props.orderByMethod || _.orderBy)(
|
||||
data,
|
||||
sorted.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
if (sortMethodsByColumnID[sort.id]) {
|
||||
return (a, b) => sortMethodsByColumnID[sort.id](a[sort.id], b[sort.id], sort.desc)
|
||||
}
|
||||
return (a, b) => this.props.defaultSortMethod(a[sort.id], b[sort.id], sort.desc)
|
||||
}),
|
||||
sorted.map(d => !d.desc),
|
||||
this.props.indexKey
|
||||
)
|
||||
|
||||
sortedData.forEach(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return
|
||||
}
|
||||
row[this.props.subRowsKey] = this.sortData(
|
||||
row[this.props.subRowsKey],
|
||||
sorted,
|
||||
sortMethodsByColumnID
|
||||
)
|
||||
})
|
||||
|
||||
return sortedData
|
||||
}
|
||||
|
||||
getMinRows () {
|
||||
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
|
||||
}
|
||||
|
||||
// User actions
|
||||
onPageChange (page) {
|
||||
const { onPageChange, collapseOnPageChange } = this.props
|
||||
|
||||
const newState = { page }
|
||||
if (collapseOnPageChange) {
|
||||
newState.expanded = {}
|
||||
}
|
||||
this.setStateWithData(newState, () => onPageChange && onPageChange(page))
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
this.setStateWithData(
|
||||
{
|
||||
pageSize: newPageSize,
|
||||
page: newPage,
|
||||
},
|
||||
() => onPageSizeChange && onPageSizeChange(newPageSize, newPage)
|
||||
)
|
||||
}
|
||||
|
||||
sortColumn (column, additive) {
|
||||
const { sorted, skipNextSort, defaultSortDesc } = this.getResolvedState()
|
||||
|
||||
const firstSortDirection = Object.prototype.hasOwnProperty.call(column, 'defaultSortDesc')
|
||||
? column.defaultSortDesc
|
||||
: defaultSortDesc
|
||||
const secondSortDirection = !firstSortDirection
|
||||
|
||||
// we can't stop event propagation from the column resize move handlers
|
||||
// attached to the document because of react's synthetic events
|
||||
// so we have to prevent the sort function from actually sorting
|
||||
// if we click on the column resize element within a header.
|
||||
if (skipNextSort) {
|
||||
this.setStateWithData({
|
||||
skipNextSort: false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const { onSortedChange } = this.props
|
||||
|
||||
let newSorted = _.clone(sorted || []).map(d => {
|
||||
d.desc = _.isSortingDesc(d)
|
||||
return d
|
||||
})
|
||||
if (!_.isArray(column)) {
|
||||
// Single-Sort
|
||||
const existingIndex = newSorted.findIndex(d => d.id === column.id)
|
||||
if (existingIndex > -1) {
|
||||
const existing = newSorted[existingIndex]
|
||||
if (existing.desc === secondSortDirection) {
|
||||
if (additive) {
|
||||
newSorted.splice(existingIndex, 1)
|
||||
} else {
|
||||
existing.desc = firstSortDirection
|
||||
newSorted = [existing]
|
||||
}
|
||||
} else {
|
||||
existing.desc = secondSortDirection
|
||||
if (!additive) {
|
||||
newSorted = [existing]
|
||||
}
|
||||
}
|
||||
} else if (additive) {
|
||||
newSorted.push({
|
||||
id: column.id,
|
||||
desc: firstSortDirection,
|
||||
})
|
||||
} else {
|
||||
newSorted = [
|
||||
{
|
||||
id: column.id,
|
||||
desc: firstSortDirection,
|
||||
},
|
||||
]
|
||||
}
|
||||
} else {
|
||||
// Multi-Sort
|
||||
const existingIndex = newSorted.findIndex(d => d.id === column[0].id)
|
||||
// Existing Sorted Column
|
||||
if (existingIndex > -1) {
|
||||
const existing = newSorted[existingIndex]
|
||||
if (existing.desc === secondSortDirection) {
|
||||
if (additive) {
|
||||
newSorted.splice(existingIndex, column.length)
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorted[existingIndex + i].desc = firstSortDirection
|
||||
})
|
||||
}
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorted[existingIndex + i].desc = secondSortDirection
|
||||
})
|
||||
}
|
||||
if (!additive) {
|
||||
newSorted = newSorted.slice(existingIndex, column.length)
|
||||
}
|
||||
// New Sort Column
|
||||
} else if (additive) {
|
||||
newSorted = newSorted.concat(
|
||||
column.map(d => ({
|
||||
id: d.id,
|
||||
desc: firstSortDirection,
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
newSorted = column.map(d => ({
|
||||
id: d.id,
|
||||
desc: firstSortDirection,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
this.setStateWithData(
|
||||
{
|
||||
page: (!sorted.length && newSorted.length) || !additive ? 0 : this.state.page,
|
||||
sorted: newSorted,
|
||||
},
|
||||
() => onSortedChange && onSortedChange(newSorted, column, additive)
|
||||
)
|
||||
}
|
||||
|
||||
filterColumn (column, value) {
|
||||
const { filtered } = this.getResolvedState()
|
||||
const { onFilteredChange } = this.props
|
||||
|
||||
// Remove old filter first if it exists
|
||||
const newFiltering = (filtered || []).filter(x => x.id !== column.id)
|
||||
|
||||
if (value !== '') {
|
||||
newFiltering.push({
|
||||
id: column.id,
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
this.setStateWithData(
|
||||
{
|
||||
filtered: newFiltering,
|
||||
},
|
||||
() => onFilteredChange && onFilteredChange(newFiltering, column, value)
|
||||
)
|
||||
}
|
||||
|
||||
resizeColumnStart (event, column, isTouch) {
|
||||
event.stopPropagation()
|
||||
const parentWidth = event.target.parentElement.getBoundingClientRect().width
|
||||
|
||||
let pageX
|
||||
if (isTouch) {
|
||||
pageX = event.changedTouches[0].pageX
|
||||
} else {
|
||||
pageX = event.pageX
|
||||
}
|
||||
|
||||
this.trapEvents = true
|
||||
this.setStateWithData(
|
||||
{
|
||||
currentlyResizing: {
|
||||
id: column.id,
|
||||
startX: pageX,
|
||||
parentWidth,
|
||||
},
|
||||
},
|
||||
() => {
|
||||
if (isTouch) {
|
||||
document.addEventListener('touchmove', this.resizeColumnMoving)
|
||||
document.addEventListener('touchcancel', this.resizeColumnEnd)
|
||||
document.addEventListener('touchend', this.resizeColumnEnd)
|
||||
} else {
|
||||
document.addEventListener('mousemove', this.resizeColumnMoving)
|
||||
document.addEventListener('mouseup', this.resizeColumnEnd)
|
||||
document.addEventListener('mouseleave', this.resizeColumnEnd)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
resizeColumnMoving (event) {
|
||||
event.stopPropagation()
|
||||
const { onResizedChange, column } = this.props
|
||||
const { resized, currentlyResizing, columns } = this.getResolvedState()
|
||||
const currentColumn = columns.find(c => c.accessor === currentlyResizing.id)
|
||||
const minResizeWidth = currentColumn ? currentColumn.minResizeWidth : column.minResizeWidth
|
||||
|
||||
// Delete old value
|
||||
const newResized = resized.filter(x => x.id !== currentlyResizing.id)
|
||||
|
||||
let pageX
|
||||
|
||||
if (event.type === 'touchmove') {
|
||||
pageX = event.changedTouches[0].pageX
|
||||
} else if (event.type === 'mousemove') {
|
||||
pageX = event.pageX
|
||||
}
|
||||
|
||||
const newWidth = Math.max(
|
||||
currentlyResizing.parentWidth + pageX - currentlyResizing.startX,
|
||||
minResizeWidth
|
||||
)
|
||||
|
||||
newResized.push({
|
||||
id: currentlyResizing.id,
|
||||
value: newWidth,
|
||||
})
|
||||
|
||||
this.setStateWithData(
|
||||
{
|
||||
resized: newResized,
|
||||
},
|
||||
() => onResizedChange && onResizedChange(newResized, event)
|
||||
)
|
||||
}
|
||||
|
||||
resizeColumnEnd (event) {
|
||||
event.stopPropagation()
|
||||
const isTouch = event.type === 'touchend' || event.type === 'touchcancel'
|
||||
|
||||
if (isTouch) {
|
||||
document.removeEventListener('touchmove', this.resizeColumnMoving)
|
||||
document.removeEventListener('touchcancel', this.resizeColumnEnd)
|
||||
document.removeEventListener('touchend', this.resizeColumnEnd)
|
||||
}
|
||||
|
||||
// If its a touch event clear the mouse one's as well because sometimes
|
||||
// the mouseDown event gets called as well, but the mouseUp event doesn't
|
||||
document.removeEventListener('mousemove', this.resizeColumnMoving)
|
||||
document.removeEventListener('mouseup', this.resizeColumnEnd)
|
||||
document.removeEventListener('mouseleave', this.resizeColumnEnd)
|
||||
|
||||
// The touch events don't propagate up to the sorting's onMouseDown event so
|
||||
// no need to prevent it from happening or else the first click after a touch
|
||||
// event resize will not sort the column.
|
||||
if (!isTouch) {
|
||||
this.setStateWithData({
|
||||
skipNextSort: true,
|
||||
currentlyResizing: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +1,174 @@
|
||||
import React from 'react'
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
// import _ from './utils'
|
||||
|
||||
const defaultButton = (props) => (
|
||||
<button {...props} className='-btn'>{props.children}</button>
|
||||
const defaultButton = props => (
|
||||
<button type="button" {...props} className="-btn">
|
||||
{props.children}
|
||||
</button>
|
||||
)
|
||||
|
||||
export default React.createClass({
|
||||
getInitialState () {
|
||||
return {
|
||||
page: this.props.currentPage
|
||||
export default class ReactTablePagination extends Component {
|
||||
static defaultProps = {
|
||||
PreviousComponent: defaultButton,
|
||||
NextComponent: defaultButton,
|
||||
renderPageJump: ({
|
||||
onChange, value, onBlur, onKeyPress, inputType, pageJumpText,
|
||||
}) => (
|
||||
<div className="-pageJump">
|
||||
<input
|
||||
aria-label={pageJumpText}
|
||||
type={inputType}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
onBlur={onBlur}
|
||||
onKeyPress={onKeyPress}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
renderCurrentPage: page => <span className="-currentPage">{page + 1}</span>,
|
||||
renderTotalPagesCount: pages => <span className="-totalPages">{pages || 1}</span>,
|
||||
renderPageSizeOptions: ({
|
||||
pageSize,
|
||||
pageSizeOptions,
|
||||
rowsSelectorText,
|
||||
onPageSizeChange,
|
||||
rowsText,
|
||||
}) => (
|
||||
<span className="select-wrap -pageSizeOptions">
|
||||
<select
|
||||
aria-label={rowsSelectorText}
|
||||
onChange={e => onPageSizeChange(Number(e.target.value))}
|
||||
value={pageSize}
|
||||
>
|
||||
{pageSizeOptions.map((option, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<option key={i} value={option}>
|
||||
{`${option} ${rowsText}`}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</span>
|
||||
),
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.getSafePage = this.getSafePage.bind(this)
|
||||
this.changePage = this.changePage.bind(this)
|
||||
this.applyPage = this.applyPage.bind(this)
|
||||
|
||||
this.state = {
|
||||
page: props.page,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState({page: nextProps.currentPage})
|
||||
},
|
||||
if (this.props.page !== nextProps.page) {
|
||||
this.setState({ page: nextProps.page })
|
||||
}
|
||||
}
|
||||
|
||||
getSafePage (page) {
|
||||
return Math.min(Math.max(page, 0), this.props.pagesLength - 1)
|
||||
},
|
||||
if (Number.isNaN(page)) {
|
||||
page = this.props.page
|
||||
}
|
||||
return Math.min(Math.max(page, 0), this.props.pages - 1)
|
||||
}
|
||||
|
||||
changePage (page) {
|
||||
page = this.getSafePage(page)
|
||||
this.setState({page})
|
||||
this.props.onChange(page)
|
||||
},
|
||||
this.setState({ page })
|
||||
if (this.props.page !== page) {
|
||||
this.props.onPageChange(page)
|
||||
}
|
||||
}
|
||||
|
||||
applyPage (e) {
|
||||
e && e.preventDefault()
|
||||
if (e) {
|
||||
e.preventDefault()
|
||||
}
|
||||
const page = this.state.page
|
||||
this.changePage(page === '' ? this.props.currentPage : page)
|
||||
},
|
||||
this.changePage(page === '' ? this.props.page : page)
|
||||
}
|
||||
|
||||
getPageJumpProperties () {
|
||||
return {
|
||||
onKeyPress: e => {
|
||||
if (e.which === 13 || e.keyCode === 13) {
|
||||
this.applyPage()
|
||||
}
|
||||
},
|
||||
onBlur: this.applyPage,
|
||||
value: this.state.page === '' ? '' : this.state.page + 1,
|
||||
onChange: e => {
|
||||
const val = e.target.value
|
||||
const page = val - 1
|
||||
if (val === '') {
|
||||
return this.setState({ page: val })
|
||||
}
|
||||
this.setState({ page: this.getSafePage(page) })
|
||||
},
|
||||
inputType: this.state.page === '' ? 'text' : 'number',
|
||||
pageJumpText: this.props.pageJumpText,
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
currentPage,
|
||||
pagesLength,
|
||||
// Computed
|
||||
pages,
|
||||
// Props
|
||||
page,
|
||||
showPageSizeOptions,
|
||||
pageSizeOptions,
|
||||
pageSize,
|
||||
showPageJump,
|
||||
canPrevious,
|
||||
canNext,
|
||||
onPageSizeChange
|
||||
onPageSizeChange,
|
||||
className,
|
||||
PreviousComponent,
|
||||
NextComponent,
|
||||
renderPageJump,
|
||||
renderCurrentPage,
|
||||
renderTotalPagesCount,
|
||||
renderPageSizeOptions,
|
||||
} = this.props
|
||||
|
||||
const PreviousComponent = this.props.previousComponent || defaultButton
|
||||
const NextComponent = this.props.nextComponent || defaultButton
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(this.props.paginationClassName, '-pagination')}
|
||||
style={this.props.paginationStyle}
|
||||
>
|
||||
<div className='-previous'>
|
||||
<div className={classnames(className, '-pagination')} style={this.props.style}>
|
||||
<div className="-previous">
|
||||
<PreviousComponent
|
||||
onClick={(e) => {
|
||||
onClick={() => {
|
||||
if (!canPrevious) return
|
||||
this.changePage(currentPage - 1)
|
||||
this.changePage(page - 1)
|
||||
}}
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
{this.props.previousText}
|
||||
</PreviousComponent>
|
||||
</div>
|
||||
<div className='-center'>
|
||||
<span className='-pageInfo'>
|
||||
Page {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'>{currentPage + 1}</span>
|
||||
)} of <span className='-totalPages'>{pagesLength}</span>
|
||||
<div className="-center">
|
||||
<span className="-pageInfo">
|
||||
{this.props.pageText}{' '}
|
||||
{showPageJump ? renderPageJump(this.getPageJumpProperties()) : renderCurrentPage(page)}{' '}
|
||||
{this.props.ofText} {renderTotalPagesCount(pages)}
|
||||
</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} rows
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</span>
|
||||
)}
|
||||
{showPageSizeOptions &&
|
||||
renderPageSizeOptions({
|
||||
pageSize,
|
||||
rowsSelectorText: this.props.rowsSelectorText,
|
||||
pageSizeOptions,
|
||||
onPageSizeChange,
|
||||
rowsText: this.props.rowsText,
|
||||
})}
|
||||
</div>
|
||||
<div className='-next'>
|
||||
<div className="-next">
|
||||
<NextComponent
|
||||
onClick={(e) => {
|
||||
onClick={() => {
|
||||
if (!canNext) return
|
||||
this.changePage(currentPage + 1)
|
||||
this.changePage(page + 1)
|
||||
}}
|
||||
disabled={!canNext}
|
||||
>
|
||||
@@ -118,4 +178,4 @@ export default React.createClass({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
export default {
|
||||
// General
|
||||
data: PropTypes.any,
|
||||
loading: PropTypes.bool,
|
||||
showPagination: PropTypes.bool,
|
||||
showPaginationTop: PropTypes.bool,
|
||||
showPaginationBottom: PropTypes.bool,
|
||||
showPageSizeOptions: PropTypes.bool,
|
||||
pageSizeOptions: PropTypes.array,
|
||||
defaultPageSize: PropTypes.number,
|
||||
showPageJump: PropTypes.bool,
|
||||
collapseOnSortingChange: PropTypes.bool,
|
||||
collapseOnPageChange: PropTypes.bool,
|
||||
collapseOnDataChange: PropTypes.bool,
|
||||
freezeWhenExpanded: PropTypes.bool,
|
||||
sortable: PropTypes.bool,
|
||||
resizable: PropTypes.bool,
|
||||
filterable: PropTypes.bool,
|
||||
defaultSortDesc: PropTypes.bool,
|
||||
defaultSorted: PropTypes.array,
|
||||
defaultFiltered: PropTypes.array,
|
||||
defaultResized: PropTypes.array,
|
||||
defaultExpanded: PropTypes.object,
|
||||
defaultFilterMethod: PropTypes.func,
|
||||
defaultSortMethod: PropTypes.func,
|
||||
|
||||
// Controlled State Callbacks
|
||||
onPageChange: PropTypes.func,
|
||||
onPageSizeChange: PropTypes.func,
|
||||
onSortedChange: PropTypes.func,
|
||||
onFilteredChange: PropTypes.func,
|
||||
onResizedChange: PropTypes.func,
|
||||
onExpandedChange: PropTypes.func,
|
||||
|
||||
// Pivoting
|
||||
pivotBy: PropTypes.array,
|
||||
|
||||
// Key Constants
|
||||
pivotValKey: PropTypes.string,
|
||||
pivotIDKey: PropTypes.string,
|
||||
subRowsKey: PropTypes.string,
|
||||
aggregatedKey: PropTypes.string,
|
||||
nestingLevelKey: PropTypes.string,
|
||||
originalKey: PropTypes.string,
|
||||
indexKey: PropTypes.string,
|
||||
groupedByPivotKey: PropTypes.string,
|
||||
|
||||
// Server-side Callbacks
|
||||
onFetchData: PropTypes.func,
|
||||
|
||||
// Classes
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
|
||||
// Component decorators
|
||||
getProps: PropTypes.func,
|
||||
getTableProps: PropTypes.func,
|
||||
getTheadGroupProps: PropTypes.func,
|
||||
getTheadGroupTrProps: PropTypes.func,
|
||||
getTheadGroupThProps: PropTypes.func,
|
||||
getTheadProps: PropTypes.func,
|
||||
getTheadTrProps: PropTypes.func,
|
||||
getTheadThProps: PropTypes.func,
|
||||
getTheadFilterProps: PropTypes.func,
|
||||
getTheadFilterTrProps: PropTypes.func,
|
||||
getTheadFilterThProps: PropTypes.func,
|
||||
getTbodyProps: PropTypes.func,
|
||||
getTrGroupProps: PropTypes.func,
|
||||
getTrProps: PropTypes.func,
|
||||
getTdProps: PropTypes.func,
|
||||
getTfootProps: PropTypes.func,
|
||||
getTfootTrProps: PropTypes.func,
|
||||
getTfootTdProps: PropTypes.func,
|
||||
getPaginationProps: PropTypes.func,
|
||||
getLoadingProps: PropTypes.func,
|
||||
getNoDataProps: PropTypes.func,
|
||||
getResizerProps: PropTypes.func,
|
||||
|
||||
// Global Column Defaults
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
// Renderers
|
||||
Cell: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
Header: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
Footer: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
Aggregated: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
Pivot: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
PivotValue: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
Expander: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
|
||||
Filter: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
|
||||
|
||||
// All Columns
|
||||
sortable: PropTypes.bool, // use table default
|
||||
resizable: PropTypes.bool, // use table default
|
||||
filterable: PropTypes.bool, // use table default
|
||||
show: PropTypes.bool,
|
||||
minWidth: PropTypes.number,
|
||||
minResizeWidth: PropTypes.number,
|
||||
|
||||
// Cells only
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
getProps: PropTypes.func,
|
||||
|
||||
// Pivot only
|
||||
aggregate: PropTypes.func,
|
||||
|
||||
// Headers only
|
||||
headerClassName: PropTypes.string,
|
||||
headerStyle: PropTypes.object,
|
||||
getHeaderProps: PropTypes.func,
|
||||
|
||||
// Footers only
|
||||
footerClassName: PropTypes.string,
|
||||
footerStyle: PropTypes.object,
|
||||
getFooterProps: PropTypes.func,
|
||||
filterMethod: PropTypes.func,
|
||||
filterAll: PropTypes.bool,
|
||||
sortMethod: PropTypes.func,
|
||||
})
|
||||
),
|
||||
|
||||
// Global Expander Column Defaults
|
||||
expanderDefaults: PropTypes.shape({
|
||||
sortable: PropTypes.bool,
|
||||
resizable: PropTypes.bool,
|
||||
filterable: PropTypes.bool,
|
||||
width: PropTypes.number,
|
||||
}),
|
||||
|
||||
pivotDefaults: PropTypes.object,
|
||||
|
||||
// Text
|
||||
previousText: PropTypes.node,
|
||||
nextText: PropTypes.node,
|
||||
loadingText: PropTypes.node,
|
||||
noDataText: PropTypes.node,
|
||||
pageText: PropTypes.node,
|
||||
ofText: PropTypes.node,
|
||||
rowsText: PropTypes.node,
|
||||
pageJumpText: PropTypes.node,
|
||||
rowsSelectorText: PropTypes.node,
|
||||
|
||||
// Components
|
||||
TableComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
TheadComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
TbodyComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
TrGroupComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
TrComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
ThComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
TdComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
TfootComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
FilterComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
ExpanderComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
PivotValueComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
AggregatedComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
// this is a computed default generated using
|
||||
PivotComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
|
||||
PaginationComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
PreviousComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
NextComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
LoadingComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
NoDataComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
ResizerComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
PadRowComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
|
||||
}
|
||||
@@ -1,37 +1,55 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
export default {
|
||||
get,
|
||||
set,
|
||||
takeRight,
|
||||
last,
|
||||
orderBy,
|
||||
range,
|
||||
clone,
|
||||
remove,
|
||||
getFirstDefined
|
||||
clone,
|
||||
leaves,
|
||||
iterTree,
|
||||
getFirstDefined,
|
||||
sum,
|
||||
makeTemplateComponent,
|
||||
groupBy,
|
||||
isArray,
|
||||
splitProps,
|
||||
compactObject,
|
||||
isSortingDesc,
|
||||
normalizeComponent,
|
||||
asPx,
|
||||
}
|
||||
|
||||
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 get (a, b) {
|
||||
if (isArray(b)) {
|
||||
b = b.join('.')
|
||||
function get (obj, path, def) {
|
||||
if (!path) {
|
||||
return obj
|
||||
}
|
||||
return b
|
||||
.replace('[', '.').replace(']', '')
|
||||
.split('.')
|
||||
.reduce(
|
||||
function (obj, property) {
|
||||
return obj[property]
|
||||
}, a
|
||||
)
|
||||
const pathObj = makePathArray(path)
|
||||
let val
|
||||
try {
|
||||
val = pathObj.reduce((current, pathPart) => current[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) {
|
||||
@@ -45,51 +63,170 @@ function last (arr) {
|
||||
|
||||
function range (n) {
|
||||
const arr = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
for (let i = 0; i < n; i += 1) {
|
||||
arr.push(n)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function orderBy (arr, funcs, dirs) {
|
||||
return arr.sort((a, b) => {
|
||||
for (let i = 0; i < funcs.length; i++) {
|
||||
function orderBy (arr, funcs, dirs, indexKey) {
|
||||
return arr.sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i += 1) {
|
||||
const comp = funcs[i]
|
||||
const ca = comp(a)
|
||||
const cb = comp(b)
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
if (ca > cb) {
|
||||
return desc ? -1 : 1
|
||||
}
|
||||
if (ca < cb) {
|
||||
return desc ? 1 : -1
|
||||
const sortInt = comp(rowA, rowB)
|
||||
if (sortInt) {
|
||||
return desc ? -sortInt : sortInt
|
||||
}
|
||||
}
|
||||
return 0
|
||||
// Use the row index for tie breakers
|
||||
return dirs[0] ? rowA[indexKey] - rowB[indexKey] : rowB[indexKey] - rowA[indexKey]
|
||||
})
|
||||
}
|
||||
|
||||
function remove (a, b) {
|
||||
return a.filter((o, i) => {
|
||||
const r = b(o)
|
||||
if (r) {
|
||||
a.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function clone (a) {
|
||||
return JSON.parse(JSON.stringify(a, function (key, value) {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
}))
|
||||
try {
|
||||
return JSON.parse(
|
||||
JSON.stringify(a, (key, value) => {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
})
|
||||
)
|
||||
} catch (e) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Helpers
|
||||
// ########################################################################
|
||||
function leaves (root, childProperty) {
|
||||
if (Object.prototype.hasOwnProperty.call(root, childProperty)) {
|
||||
const children = root[childProperty].map(child => leaves(child, childProperty))
|
||||
return [].concat(...children)
|
||||
}
|
||||
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
return [root]
|
||||
}
|
||||
|
||||
function iterTree (root, childProperty) {
|
||||
if (Object.prototype.hasOwnProperty.call(root, childProperty)) {
|
||||
const children = root[childProperty].map(child => leaves(child, childProperty))
|
||||
return [].concat(root, ...children)
|
||||
}
|
||||
|
||||
return [root]
|
||||
}
|
||||
|
||||
function getFirstDefined (...args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
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) => a + b, 0)
|
||||
}
|
||||
|
||||
function makeTemplateComponent (compClass, displayName) {
|
||||
if (!displayName) {
|
||||
throw new Error('No displayName found for template component:', compClass)
|
||||
}
|
||||
const cmp = ({ children, className, ...rest }) => (
|
||||
<div className={classnames(compClass, className)} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
cmp.displayName = displayName
|
||||
return cmp
|
||||
}
|
||||
|
||||
function groupBy (xs, key) {
|
||||
return xs.reduce((rv, x, i) => {
|
||||
const resKey = typeof key === 'function' ? key(x, i) : x[key]
|
||||
rv[resKey] = isArray(rv[resKey]) ? rv[resKey] : []
|
||||
rv[resKey].push(x)
|
||||
return rv
|
||||
}, {})
|
||||
}
|
||||
|
||||
function asPx (value) {
|
||||
value = Number(value)
|
||||
return Number.isNaN(value) ? null : `${value}px`
|
||||
}
|
||||
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Non-exported Helpers
|
||||
// ########################################################################
|
||||
|
||||
function makePathArray (obj) {
|
||||
return flattenDeep(obj)
|
||||
.join('.')
|
||||
.replace(/\[/g, '.')
|
||||
.replace(/\]/g, '')
|
||||
.split('.')
|
||||
}
|
||||
|
||||
function flattenDeep (arr, newArr = []) {
|
||||
if (!isArray(arr)) {
|
||||
newArr.push(arr)
|
||||
} else {
|
||||
for (let i = 0; i < arr.length; i += 1) {
|
||||
flattenDeep(arr[i], newArr)
|
||||
}
|
||||
}
|
||||
return newArr
|
||||
}
|
||||
|
||||
function splitProps ({ className, style, ...rest }) {
|
||||
return {
|
||||
className,
|
||||
style,
|
||||
rest: rest || {},
|
||||
}
|
||||
}
|
||||
|
||||
function compactObject (obj) {
|
||||
const newObj = {}
|
||||
if (obj) {
|
||||
Object.keys(obj).map(key => {
|
||||
if (
|
||||
Object.prototype.hasOwnProperty.call(obj, key) &&
|
||||
obj[key] !== undefined &&
|
||||
typeof obj[key] !== 'undefined'
|
||||
) {
|
||||
newObj[key] = obj[key]
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
function isSortingDesc (d) {
|
||||
return !!(d.sort === 'desc' || d.desc === true || d.asc === false)
|
||||
}
|
||||
|
||||
function normalizeComponent (Comp, params = {}, fallback = Comp) {
|
||||
return typeof Comp === 'function' ? (
|
||||
<Comp {...params} />
|
||||
) : (
|
||||
fallback
|
||||
)
|
||||
}
|
||||
|
||||