mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 16:30:21 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b69363e1e |
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
extends: 'react-tools',
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
* text=auto
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
# Using v6?
|
||||
|
||||
v6 is now considered feature complete to its current abilities and limitations. We are not actively working to fix any issues for v6 any more. We will, however, merge any non-breaking pull requests submitted to fix anything in v6.
|
||||
|
||||
# Using v7?
|
||||
|
||||
Thanks for using the alpha version of React Table v7! We're very excited about it.
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Codesandbox!**
|
||||
Use a new [react-table codesandbox](https://codesandbox.io/s/m5lxzzpz69) to reproduce the issue.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
# STOP
|
||||
|
||||
We do not track feature requests through Github. Please use the [Spectrum React Table Forum](https://spectrum.chat/react-table) to talk about new ideas and features.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
name: Questions, Support & Help
|
||||
about: Looking for help? Need a question answered?
|
||||
|
||||
---
|
||||
|
||||
# STOP
|
||||
|
||||
We do not use Github to track general support. Please use our public support forum at https://spectrum.chat/react-table.
|
||||
|
||||
NOTE: If a support/help/question issue is opened, it will be closed immediately and be redirected to the forum. Thanks for helping us keep our issues clean and productive!
|
||||
@@ -1,11 +1,8 @@
|
||||
node_modules/
|
||||
lib/
|
||||
es/
|
||||
docs/build
|
||||
react-table.js
|
||||
react-table.min.js
|
||||
react-table.css
|
||||
*.log
|
||||
.idea
|
||||
.DS_Store
|
||||
.history
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
react-table.js.org
|
||||
@@ -0,0 +1,18 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,600");
|
||||
body {
|
||||
background: #fff;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
line-height: 22px;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
body{
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
/*ol, ul {
|
||||
list-style: none;
|
||||
}*/
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
const path = require('path')
|
||||
|
||||
// load the default config generator.
|
||||
const genDefaultConfig = require('@kadira/storybook/dist/server/config/defaults/webpack.config.js')
|
||||
|
||||
module.exports = (config, env) => {
|
||||
config = genDefaultConfig(config, env)
|
||||
|
||||
Object.assign(config, {
|
||||
module: Object.assign(config.module, {
|
||||
loaders: config.module.loaders.concat([{
|
||||
test: /\.md$/,
|
||||
loader: 'html!markdown',
|
||||
include: path.resolve(__dirname, '../')
|
||||
}, {
|
||||
test: /\.html$/,
|
||||
loader: 'html',
|
||||
query: {
|
||||
minimize: true
|
||||
},
|
||||
include: path.resolve(__dirname, '../')
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
||||
+38
-167
@@ -1,173 +1,44 @@
|
||||
## 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.
|
||||
|
||||
* 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.
|
||||
- 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,15 @@
|
||||
## Problem Description
|
||||
include a detailed explanation of the problem here...
|
||||
|
||||
## Code Snippet(s) or Codepen (http://codepen.io/tannerlinsley/pen/QpeZBa?editors=0010)
|
||||
|
||||
## Steps to Reproduce
|
||||
1. list the steps
|
||||
2. to reproduce the issue
|
||||
|
||||
## System Information
|
||||
* **Browser & Browser Version:**
|
||||
* **Node Version:**
|
||||
* **OS Version:**
|
||||
* **NPM Version:**
|
||||
* **Yarn Version:**
|
||||
+1623
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "new",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"react-scripts": "0.9.5",
|
||||
"standard": "^10.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"github-markdown-css": "^2.6.0",
|
||||
"marked": "^0.3.6",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-story": "^0.0.6"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
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,94 @@
|
||||
import React from 'react'
|
||||
//
|
||||
import ReactStory, { defaultProps } from 'react-story'
|
||||
|
||||
import './stories/utils/prism.css'
|
||||
import '../../react-table.css'
|
||||
|
||||
import Readme from './stories/Readme.js'
|
||||
import Simple from './stories/Simple.js'
|
||||
import CellRenderers from './stories/CellRenderers.js'
|
||||
import DefaultSorting from './stories/DefaultSorting.js'
|
||||
import CustomSorting from './stories/CustomSorting.js'
|
||||
import CustomWidths from './stories/CustomWidths.js'
|
||||
import CustomComponentProps from './stories/CustomComponentProps.js'
|
||||
import ServerSide from './stories/ServerSide.js'
|
||||
import SubComponents from './stories/SubComponents.js'
|
||||
import Pivoting from './stories/Pivoting.js'
|
||||
import PivotingSubComponents from './stories/PivotingSubComponents.js'
|
||||
import OneHundredKRows from './stories/OneHundredKRows.js'
|
||||
import FunctionalRendering from './stories/FunctionalRendering.js'
|
||||
import CustomExpanderPosition from './stories/CustomExpanderPosition.js'
|
||||
import NoDataText from './stories/NoDataText.js'
|
||||
import Footers from './stories/Footers.js'
|
||||
import Filtering from './stories/Filtering.js'
|
||||
import ControlledTable from './stories/ControlledTable.js'
|
||||
import PivotingOptions from './stories/PivotingOptions.js'
|
||||
import EditableTable from './stories/EditableTable.js'
|
||||
|
||||
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
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href='//github.com/tannerlinsley/react-table'
|
||||
style={{
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
borderBottom: 'solid 3px #cccccc'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src='//npmcdn.com/react-table/media/Banner.png'
|
||||
alt='React Table Logo'
|
||||
style={{
|
||||
width: '100px'
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
<div
|
||||
{...props}
|
||||
style={{
|
||||
padding: '10px'
|
||||
}}
|
||||
/>
|
||||
</defaultProps.StoryWrapper>
|
||||
)}
|
||||
stories={[
|
||||
{ name: 'Readme', component: Readme },
|
||||
{ name: 'Simple Table', component: Simple },
|
||||
{ name: 'Cell Renderers & Custom Components', component: CellRenderers },
|
||||
{ name: 'Default Sorting', component: DefaultSorting },
|
||||
{ name: 'Custom Sorting', component: CustomSorting },
|
||||
{ name: 'Custom Column Widths', component: CustomWidths },
|
||||
{ name: 'Custom Component Props', component: CustomComponentProps },
|
||||
{ name: 'Server-side Data', component: ServerSide },
|
||||
{ name: 'Sub Components', component: SubComponents },
|
||||
{ name: 'Pivoting & Aggregation', component: Pivoting },
|
||||
{ name: 'Pivoting & Aggregation w/ Sub Components', component: PivotingSubComponents },
|
||||
{ name: '100k Rows w/ Pivoting & Sub Components', component: OneHundredKRows },
|
||||
{ name: 'Pivoting Options', component: PivotingOptions },
|
||||
{ name: 'Functional Rendering', component: FunctionalRendering },
|
||||
{ name: 'Custom Expander Position', component: CustomExpanderPosition },
|
||||
{ name: 'Custom "No Data" Text', component: NoDataText },
|
||||
{ name: 'Footers', component: Footers },
|
||||
{ name: 'Custom Filtering', component: Filtering },
|
||||
{ name: 'Controlled Component', component: ControlledTable },
|
||||
{ name: 'Editable Table', component: EditableTable }
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
html, body, #root {
|
||||
height: 100%
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
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,109 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
render () {
|
||||
const data = _.map(_.range(5553), d => {
|
||||
const statusChance = Math.random()
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status: statusChance > 0.66 ? 'relationship'
|
||||
: statusChance > 0.33 ? 'complicated'
|
||||
: 'single'
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
Cell: row => (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: '#dadada',
|
||||
borderRadius: '2px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: `${row.value}%`,
|
||||
height: '100%',
|
||||
backgroundColor: row.value > 66 ? '#85cc00'
|
||||
: row.value > 33 ? '#ffbf00'
|
||||
: '#ff2e00',
|
||||
borderRadius: '2px',
|
||||
transition: 'all .2s ease-out'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}, {
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
Cell: row => (
|
||||
<span>
|
||||
<span style={{
|
||||
color: row.value === 'relationship' ? '#ff2e00'
|
||||
: row.value === 'complicated' ? '#ffbf00'
|
||||
: '#57d500',
|
||||
transition: 'all .3s ease'
|
||||
}}>
|
||||
●
|
||||
</span> {
|
||||
row.value === 'relationship' ? 'In a relationship'
|
||||
: row.value === 'complicated' ? `It's complicated`
|
||||
: 'Single'
|
||||
}
|
||||
</span>
|
||||
)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./CellRenderers')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
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'
|
||||
}]
|
||||
}]
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
constructor () {
|
||||
super()
|
||||
this.state = {
|
||||
sorted: [],
|
||||
page: 0,
|
||||
pageSize: 10,
|
||||
expanded: {},
|
||||
resized: [],
|
||||
filtered: []
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
pivotBy={['lastName']}
|
||||
filterable
|
||||
// Controlled Props
|
||||
sorted={this.state.sorted}
|
||||
page={this.state.page}
|
||||
pageSize={this.state.pageSize}
|
||||
expanded={this.state.expanded}
|
||||
resized={this.state.resized}
|
||||
filtered={this.state.filtered}
|
||||
// Callbacks
|
||||
onSortedChange={sorted => this.setState({sorted})}
|
||||
onPageChange={page => this.setState({page})}
|
||||
onPageSizeChange={(pageSize, page) => this.setState({page, pageSize})}
|
||||
onExpandedChange={expanded => this.setState({expanded})}
|
||||
onResizedChange={resized => this.setState({resized})}
|
||||
onFilteredChange={filtered => this.setState({filtered})}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<pre><code><strong>this.state ===</strong> {JSON.stringify(this.state, null, 2)}</code></pre>
|
||||
<br />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./ControlledTable')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,76 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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>
|
||||
<strong>Hey!</strong> Open your console! :)
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
getTdProps={(state, rowInfo, column, instance) => {
|
||||
return {
|
||||
onMouseEnter: e => console.log('Cell - onMouseEnter', {
|
||||
state,
|
||||
rowInfo,
|
||||
column,
|
||||
instance,
|
||||
event: e
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./CustomComponentProps')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,81 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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',
|
||||
Footer: () => <div style={{textAlign: 'center'}}>First Name</div>
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
Footer: () => <div style={{textAlign: 'center'}}>Last Name</div>
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Footer: () => <div style={{textAlign: 'center'}}>Age</div>
|
||||
}]
|
||||
}, {
|
||||
Header: 'Expand',
|
||||
columns: [{
|
||||
expander: true,
|
||||
Header: () => (<strong>More</strong>),
|
||||
width: 65,
|
||||
Expander: ({isExpanded, ...rest}) => (
|
||||
<div>
|
||||
{isExpanded ? <span>⊙</span> : <span>⊕</span>}
|
||||
</div>
|
||||
),
|
||||
style: {cursor: 'pointer', fontSize: 25, padding: '0', textAlign: 'center', userSelect: 'none'},
|
||||
Footer: () => <span>♥</span>
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={() => <span>Hello</span>}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./CustomExpanderPosition')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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 (Sorted by Length, A-Z)',
|
||||
accessor: 'firstName',
|
||||
sortMethod: (a, b) => {
|
||||
if (a.length === b.length) {
|
||||
return a > b ? 1 : -1
|
||||
}
|
||||
return a.length > b.length ? 1 : -1
|
||||
}
|
||||
}, {
|
||||
Header: 'Last Name (Sorted in reverse, A-Z)',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
sortMethod: (a, b) => {
|
||||
if (a === b) {
|
||||
return 0
|
||||
}
|
||||
const aReverse = a.split('').reverse().join('')
|
||||
const bReverse = b.split('').reverse().join('')
|
||||
return aReverse > bReverse ? 1 : -1
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./CustomSorting')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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',
|
||||
maxWidth: 200
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
width: 300
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
minWidth: 400
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./CustomWidths')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
defaultSorted={[{
|
||||
id: 'age',
|
||||
desc: true
|
||||
}]}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./DefaultSorting')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
constructor (props, context) {
|
||||
super(props, context)
|
||||
this.renderEditable = this.renderEditable.bind(this)
|
||||
|
||||
this.state = {
|
||||
data: [
|
||||
{ firstName: 'Lucy', lastName: 'Marks' },
|
||||
{ firstName: 'Bejamin', lastName: 'Pike' }
|
||||
]
|
||||
}
|
||||
|
||||
this.columns = [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
Cell: this.renderEditable
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
Cell: this.renderEditable
|
||||
},
|
||||
{
|
||||
Header: 'Full Name',
|
||||
id: 'full',
|
||||
accessor: d => d.firstName + ' ' + d.lastName
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
renderEditable (cellInfo) {
|
||||
return (<div style={{ backgroundColor: '#fafafa' }} contentEditable suppressContentEditableWarning onBlur={(e) => {
|
||||
const data = [...this.state.data]
|
||||
data[cellInfo.index][cellInfo.column.id] = e.target.textContent
|
||||
this.setState({data: data})
|
||||
}}>{this.state.data[cellInfo.index][cellInfo.column.id]}</div>)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='table-wrap' style={{marginBottom: '20px'}}>
|
||||
<p>First two columns are editable just by clicking into them using the <code>contentEditable</code> attribute. Last column (Full Name) is computed from the first two.</p>
|
||||
<ReactTable
|
||||
data={this.state.data}
|
||||
columns={this.columns}
|
||||
defaultPageSize={2}
|
||||
showPageSizeOptions={false}
|
||||
showPagination={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./EditableTable')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,194 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
this.state = {
|
||||
tableOptions: {
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
resizable: true
|
||||
},
|
||||
data: data
|
||||
}
|
||||
|
||||
this.setTableOption = this.setTableOption.bind(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
filterMethod: (filter, row) => (row[filter.id].startsWith(filter.value) && row[filter.id].endsWith(filter.value))
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
filterMethod: (filter, row) => (row[filter.id].includes(filter.value))
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}, {
|
||||
Header: 'Over 21',
|
||||
accessor: 'age',
|
||||
id: 'over',
|
||||
Cell: ({value}) => (value >= 21 ? 'Yes' : 'No'),
|
||||
filterMethod: (filter, row) => {
|
||||
if (filter.value === 'all') {
|
||||
return true
|
||||
}
|
||||
if (filter.value === 'true') {
|
||||
return row[filter.id] >= 21
|
||||
}
|
||||
return row[filter.id] < 21
|
||||
},
|
||||
Filter: ({filter, onChange}) => (
|
||||
<select
|
||||
onChange={event => onChange(event.target.value)}
|
||||
style={{width: '100%'}}
|
||||
value={filter ? filter.value : 'all'}
|
||||
>
|
||||
<option value='all' />
|
||||
<option value='true'>Can Drink</option>
|
||||
<option value='false'>Can't Drink</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<h1>Table Options</h1>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
Object.keys(this.state.tableOptions).map(optionKey => {
|
||||
const optionValue = this.state.tableOptions[optionKey]
|
||||
return (
|
||||
<tr key={optionKey}>
|
||||
<td>{optionKey}</td>
|
||||
<td style={{paddingLeft: 10, paddingTop: 5}}>
|
||||
<input type='checkbox'
|
||||
name={optionKey}
|
||||
checked={optionValue}
|
||||
onChange={this.setTableOption}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
defaultFilterMethod={(filter, row) => (String(row[filter.id]) === filter.value)}
|
||||
{...this.state.tableOptions}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Custom Filters In This Example</h1>
|
||||
<p>The default filter for all columns of a table if it is not specified in the configuration is set to match
|
||||
on values that start with the filter text. Example: age.startsWith("2").</p>
|
||||
<p>This example overrides the default filter behavior by setting
|
||||
the <strong>defaultFilterMethod</strong> table option to match on values that are exactly equal to the
|
||||
filter text. Example: age == "23")</p>
|
||||
<p>Each column can also be customized with the column <strong>filterMethod</strong> option:</p>
|
||||
<p>In this example the firstName column filters on the value starting with and ending with the filter
|
||||
value.</p>
|
||||
<p>In this example the lastName column filters on the value including the filter value anywhere in its
|
||||
text.</p>
|
||||
<p>To completely override the filter that is shown, you can set the <strong>Filter</strong> column
|
||||
option. Using this option you can specify the JSX that is shown. The option is passed
|
||||
an <strong>onChange</strong> method that must be called with the value that you wan't to
|
||||
pass to the <strong>filterMethod</strong> option whenever the filter has changed.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
setTableOption (event) {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
tableOptions: {
|
||||
...this.state.tableOptions,
|
||||
[name]: value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const source = require('!raw!./Filtering')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,92 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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',
|
||||
Footer: (
|
||||
<span><strong>Popular:</strong> {
|
||||
_.first(
|
||||
_.reduce(
|
||||
_.map(
|
||||
_.groupBy(
|
||||
data, d => d.firstName
|
||||
)
|
||||
),
|
||||
(a, b) => a.length > b.length ? a : b
|
||||
)
|
||||
).firstName}
|
||||
</span>
|
||||
)
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
Footer: (
|
||||
<span><strong>Longest:</strong> {
|
||||
_.reduce(
|
||||
_.map(
|
||||
_.groupBy(
|
||||
data, d => d.lastName
|
||||
),
|
||||
(d, key) => key
|
||||
),
|
||||
(a, b) => a.length > b.length ? a : b
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Footer: <span><strong>Average:</strong> {_.round(_.mean(_.map(data, d => d.age)))}</span>
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./Footers')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,137 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
import JSONTree from 'react-json-tree'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
const JSONtheme = {
|
||||
scheme: 'monokai',
|
||||
author: 'wimer hazenberg (http://www.monokai.nl)',
|
||||
base00: '#272822',
|
||||
base01: '#383830',
|
||||
base02: '#49483e',
|
||||
base03: '#75715e',
|
||||
base04: '#a59f85',
|
||||
base05: '#f8f8f2',
|
||||
base06: '#f5f4f1',
|
||||
base07: '#f9f8f5',
|
||||
base08: '#f92672',
|
||||
base09: '#fd971f',
|
||||
base0A: '#f4bf75',
|
||||
base0B: '#a6e22e',
|
||||
base0C: '#a1efe4',
|
||||
base0D: '#66d9ef',
|
||||
base0E: '#ae81ff',
|
||||
base0F: '#cc6633'
|
||||
}
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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',
|
||||
Footer: 'Footer'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
Footer: 'Footer'
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Footer: 'Footer'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<strong>Functional rendering</strong> simply means that you have all of the building blocks to render your own React Table however you'd like.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<strong>Decorating the standard table output</strong>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
>
|
||||
{(state, makeTable, instance) => {
|
||||
return (
|
||||
<div style={{
|
||||
background: '#ffcf00',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
padding: '5px'
|
||||
}}>
|
||||
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
|
||||
{makeTable()}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</ReactTable>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<strong>Need more control? This is the entire table state and component instance at your disposal!</strong>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
>
|
||||
{(state, StandardTable, instance) => {
|
||||
return (
|
||||
<div>
|
||||
<JSONTree
|
||||
data={Object.assign({}, state, {children: 'function () {...}'})}
|
||||
theme={JSONtheme}
|
||||
invertTheme
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</ReactTable>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./FunctionalRendering')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,57 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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 (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={[]}
|
||||
noDataText='Oh Noes!'
|
||||
// noDataText={() => 'Oh Noes!'} // Supports functions
|
||||
// noDataText={() => <span>Oh Noes!</span>} // Supports JSX / React Components
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./NoDataText')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,97 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
render () {
|
||||
const data = _.map(_.range(100000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
Aggregated: row => {
|
||||
return <span>{row.value} (avg)</span>
|
||||
}
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
className='-striped -highlight'
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./OneHundredKRows')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,74 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
render () {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
Aggregated: row => {
|
||||
return <span>{row.value} (avg)</span>
|
||||
}
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
className='-striped -highlight'
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./Pivoting')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,132 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
render () {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({words: 1, numLen: 0}),
|
||||
lastName: namor.generate({words: 1, numLen: 0}),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
PivotValue: ({value}) => <span style={{color: 'darkred'}}>{value}</span>
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
PivotValue: ({value}) => <span style={{color: 'darkblue'}}>{value}</span>,
|
||||
Footer: () => <div style={{textAlign: 'center'}}><strong>Pivot Column Footer</strong></div>
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => {
|
||||
return _.round(_.mean(vals))
|
||||
},
|
||||
Aggregated: row => <span>{row.value} (avg)</span>
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals),
|
||||
filterable: false
|
||||
}]
|
||||
}, {
|
||||
pivot: true,
|
||||
Header: () => <strong>Overridden Pivot Column Header Group</strong>
|
||||
}, {
|
||||
expander: true
|
||||
}]
|
||||
|
||||
const subtableColumns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName'
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
className='-striped -highlight'
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
defaultSorted={[{id: 'firstName', desc: false}, {id: 'lastName', desc: true}]}
|
||||
collapseOnSortingChange={false}
|
||||
filterable
|
||||
ExpanderComponent={({isExpanded, ...rest}) => (
|
||||
isExpanded ? <span> ➘ </span> : <span> ➙ </span>
|
||||
)}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={subtableColumns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./PivotingOptions')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
render () {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
Aggregated: row => {
|
||||
return <span>{row.value} (avg)</span>
|
||||
},
|
||||
filterMethod: (filter, row) => (filter.value === `${row[filter.id]} (avg)`)
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals),
|
||||
filterable: false
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
className='-striped -highlight'
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
filterable
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./PivotingSubComponents')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
/* 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'
|
||||
|
||||
export default class Story extends React.Component {
|
||||
render () {
|
||||
return <span className='markdown-body' dangerouslySetInnerHTML={{__html: marked(Readme)}} />
|
||||
}
|
||||
componentDidMount () {
|
||||
global.Prism.highlightAll()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
const rawData = _.map(_.range(3424), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
// Now let's mock the server. It's job is simple: use the table model to sort and return the page data
|
||||
const requestData = (pageSize, page, sorted, filtered) => {
|
||||
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
|
||||
let filteredData = rawData
|
||||
if (filtered.length) {
|
||||
filteredData = filtered.reduce(
|
||||
(filteredSoFar, nextFilter) => {
|
||||
return filteredSoFar.filter(
|
||||
(row) => {
|
||||
return (row[nextFilter.id] + '').includes(nextFilter.value)
|
||||
})
|
||||
}
|
||||
, filteredData)
|
||||
}
|
||||
const sortedData = _.orderBy(filteredData, sorted.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]
|
||||
}
|
||||
}), sorted.map(d => d.desc ? 'desc' : 'asc'))
|
||||
|
||||
// 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(filteredData.length / pageSize)
|
||||
}
|
||||
|
||||
// Here we'll simulate a server response with 500ms of delay.
|
||||
setTimeout(() => resolve(res), 500)
|
||||
})
|
||||
}
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
constructor () {
|
||||
super()
|
||||
this.state = {
|
||||
data: [],
|
||||
pages: null,
|
||||
loading: true
|
||||
}
|
||||
this.fetchData = this.fetchData.bind(this)
|
||||
}
|
||||
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.sorted, state.filtered)
|
||||
.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
|
||||
className='-striped -highlight'
|
||||
columns={[{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}, {
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]}
|
||||
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
|
||||
defaultPageSize={10}
|
||||
filterable
|
||||
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
|
||||
onFetchData={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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./ServerSide')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,70 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
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),
|
||||
children: _.map(_.range(10), 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
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./Simple')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,146 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
|
||||
this.state = {
|
||||
tableOptions: {
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
filterable: false,
|
||||
sortable: true,
|
||||
resizable: true
|
||||
},
|
||||
data: data
|
||||
}
|
||||
|
||||
this.setTableOption = this.setTableOption.bind(this)
|
||||
}
|
||||
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 (
|
||||
<div>
|
||||
<div>
|
||||
<h1>Table Options</h1>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
Object.keys(this.state.tableOptions).map(optionKey => {
|
||||
const optionValue = this.state.tableOptions[optionKey]
|
||||
return (
|
||||
<tr key={optionKey}>
|
||||
<td>{optionKey}</td>
|
||||
<td style={{paddingLeft: 10, paddingTop: 5}}>
|
||||
<input type='checkbox'
|
||||
name={optionKey}
|
||||
checked={optionValue}
|
||||
onChange={this.setTableOption}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
{...this.state.tableOptions}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
setTableOption (event) {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
tableOptions: {
|
||||
...this.state.tableOptions,
|
||||
[name]: value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./SubComponents')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
/* eslint-disable import/no-webpack-loader-syntax */
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../../../lib/index'
|
||||
|
||||
class Story extends React.PureComponent {
|
||||
render () {
|
||||
const data = _.map(_.range(5553), d => {
|
||||
const children = _.map(_.range(10), d => {
|
||||
const grandChildren = _.map(_.range(10), d => {
|
||||
return {
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
children: grandChildren
|
||||
}
|
||||
})
|
||||
return {
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
firstName: children.map(d => d.firstName),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
children
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
expander: true
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName'
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
subRowsKey='children'
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw!./SubRows')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import '../utils/prism'
|
||||
|
||||
export default class CodeHighlight extends React.Component {
|
||||
render () {
|
||||
const { language, children } = this.props
|
||||
return (
|
||||
<pre>
|
||||
<code className={'language-' + (language || 'jsx')}>
|
||||
{children()}
|
||||
</code>
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
componentDidMount () {
|
||||
window.Prism.highlightAll()
|
||||
}
|
||||
componentDidUpdate () {
|
||||
window.Prism.highlightAll()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
+5744
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 152 KiB |
Generated
-8348
File diff suppressed because it is too large
Load Diff
+62
-40
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "7.0.0-alpha.1",
|
||||
"version": "6.0.2",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
@@ -14,54 +14,76 @@
|
||||
"react-table",
|
||||
"datagrid"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"_module": "dist/index.es.js",
|
||||
"_jsnext:main": "dist/index.es.js",
|
||||
"scripts": {
|
||||
"test": "cross-env CI=1 react-scripts test --env=jsdom",
|
||||
"test:watch": "react-scripts test --env=jsdom",
|
||||
"build": "rollup -c",
|
||||
"start": "rollup -c -w",
|
||||
"prepare": "yarn build",
|
||||
"release": "yarn publish",
|
||||
"releaseNext": "yarn publish --tag next"
|
||||
},
|
||||
"main": "lib/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": "rimraf react-table.css && stylus src/index.styl --compress -o react-table.css && yarn run css:autoprefix",
|
||||
"css:autoprefix": "postcss --use autoprefixer react-table.css -r",
|
||||
"watch": "npm-run-all --parallel watch:*",
|
||||
"watch:node": "onchange 'src/**/*.js' -i -- npm run build:node",
|
||||
"watch:css": "onchange 'src/**/*.styl' -i -- npm run build:css",
|
||||
"test": "standard",
|
||||
"umd": "rimraf react-table.js && webpack --config umd.webpack.js",
|
||||
"build": "npm-run-all build:*",
|
||||
"prepublish": "npm run build && npm run umd",
|
||||
"postpublish": "git push --tags",
|
||||
"docs": "cd docs && yarn && yarn start",
|
||||
"docs:build": "cd docs && yarn && yarn run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"prop-types": "^15.5.0",
|
||||
"react": "^16.7.0-alpha.0 || ^16.8.x",
|
||||
"react-dom": "^16.7.0-alpha.0 || ^16.8.x"
|
||||
"react": "^15.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.2.1",
|
||||
"@babel/preset-env": "^7.2.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/runtime": "^7.2.0",
|
||||
"@svgr/rollup": "^4.1.0",
|
||||
"babel-core": "7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"cross-env": "^5.1.4",
|
||||
"eslint": "^5.10.0",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-config-standard-react": "^7.0.2",
|
||||
"eslint-plugin-import": "^2.13.0",
|
||||
"eslint-plugin-node": "^8.0.0",
|
||||
"eslint-plugin-promise": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.10.0",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"rollup": "^0.68.0",
|
||||
"rollup-plugin-babel": "^4.1.0",
|
||||
"rollup-plugin-commonjs": "^9.1.3",
|
||||
"rollup-plugin-node-resolve": "^4.0.0",
|
||||
"rollup-plugin-peer-deps-external": "^2.2.0",
|
||||
"rollup-plugin-uglify": "^6.0.2"
|
||||
"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",
|
||||
"github-markdown-css": "^2.4.1",
|
||||
"html-loader": "^0.4.4",
|
||||
"markdown-loader": "^0.1.7",
|
||||
"namor": "^0.3.0",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"onchange": "^3.0.2",
|
||||
"postcss-cli": "^2.6.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"react-json-tree": "^0.10.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"standard": "^10.0.2",
|
||||
"storybook": "^0.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"webpack": "^2.4.1"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"lib",
|
||||
"example",
|
||||
"react-table.js",
|
||||
"stories",
|
||||
"docs"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-2",
|
||||
"react"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
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
|
||||
@@ -1,7 +0,0 @@
|
||||
export function sum (values, rows) {
|
||||
return values.reduce((sum, next) => sum + next, 0)
|
||||
}
|
||||
|
||||
export function average (values, rows) {
|
||||
return Math.round((sum(values, rows) / values.length) * 100) / 100
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
import _ from './utils'
|
||||
import Pagination from './pagination'
|
||||
|
||||
const emptyObj = () => ({})
|
||||
|
||||
export default {
|
||||
// General
|
||||
data: [],
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
pageSizeOptions: [5, 10, 20, 25, 50, 100],
|
||||
defaultPageSize: 20,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
filterable: false,
|
||||
defaultSorted: [],
|
||||
defaultFiltered: [],
|
||||
defaultResized: [],
|
||||
defaultExpanded: {},
|
||||
defaultFilterMethod: (filter, row, column) => {
|
||||
const id = filter.pivotId || filter.id
|
||||
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
|
||||
},
|
||||
defaultSortMethod: (a, b) => {
|
||||
// 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,
|
||||
// All Columns
|
||||
sortable: undefined, // use table default
|
||||
resizable: undefined, // use table default
|
||||
filterable: undefined, // use table default
|
||||
show: true,
|
||||
minWidth: 100,
|
||||
// Cells only
|
||||
className: '',
|
||||
style: {},
|
||||
getProps: emptyObj,
|
||||
// Pivot only
|
||||
aggregate: undefined,
|
||||
// Headers only
|
||||
headerClassName: '',
|
||||
headerStyle: {},
|
||||
getHeaderProps: emptyObj,
|
||||
// Footers only
|
||||
footerClassName: '',
|
||||
footerStyle: {},
|
||||
getFooterProps: emptyObj,
|
||||
filterMethod: undefined,
|
||||
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',
|
||||
|
||||
// Components
|
||||
TableComponent: _.makeTemplateComponent('rt-table'),
|
||||
TheadComponent: _.makeTemplateComponent('rt-thead'),
|
||||
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
|
||||
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
|
||||
TrComponent: _.makeTemplateComponent('rt-tr'),
|
||||
ThComponent: ({toggleSort, className, children, ...rest}) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, 'rt-th')}
|
||||
onClick={e => {
|
||||
toggleSort && toggleSort(e)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
TdComponent: _.makeTemplateComponent('rt-td'),
|
||||
TfootComponent: _.makeTemplateComponent('rt-tfoot'),
|
||||
FilterComponent: ({filter, onChange}) => (
|
||||
<input type='text'
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
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) => (
|
||||
<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'),
|
||||
ResizerComponent: _.makeTemplateComponent('rt-resizer')
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useAccessedRows ({
|
||||
debug, data, columns, subRowsKey,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getAccessedRows')
|
||||
|
||||
// Access the row's data
|
||||
const accessRow = (data, i, depth = 0) => {
|
||||
// Keep the original reference around
|
||||
const original = data
|
||||
|
||||
// Process any subRows
|
||||
const subRows = data[subRowsKey]
|
||||
? data[subRowsKey].map((d, i) => accessRow(d, i, depth + 1))
|
||||
: undefined
|
||||
|
||||
const row = {
|
||||
original,
|
||||
index: i,
|
||||
subRows,
|
||||
depth,
|
||||
}
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
columns.forEach(column => {
|
||||
row.values[column.id] = column.accessor ? column.accessor(data) : undefined
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// Use the resolved data
|
||||
return data.map((d, i) => accessRow(d, i))
|
||||
},
|
||||
[data, columns]
|
||||
)
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { getFirstDefined, getBy } from '../utils'
|
||||
|
||||
export default function useColumns ({
|
||||
debug,
|
||||
groupBy,
|
||||
userColumns,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getColumns')
|
||||
|
||||
// Decorate All the columns
|
||||
const columnTree = decorateColumnTree(userColumns)
|
||||
|
||||
// Get the flat list of all columns
|
||||
let columns = flattenBy(columnTree, 'columns')
|
||||
|
||||
columns = [
|
||||
...groupBy.map(g => columns.find(col => col.id === g)),
|
||||
...columns.filter(col => !groupBy.includes(col.id)),
|
||||
]
|
||||
|
||||
// Get headerGroups
|
||||
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
|
||||
const headers = flattenBy(headerGroups, 'headers')
|
||||
|
||||
return {
|
||||
columns,
|
||||
headerGroups,
|
||||
headers,
|
||||
}
|
||||
},
|
||||
[groupBy, userColumns]
|
||||
)
|
||||
|
||||
// Find the depth of the columns
|
||||
function findMaxDepth (columns, depth = 0) {
|
||||
return columns.reduce((prev, curr) => {
|
||||
if (curr.columns) {
|
||||
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
|
||||
}
|
||||
return depth
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function decorateColumn (column, parent) {
|
||||
// First check for string accessor
|
||||
let {
|
||||
id, accessor, Header, canSortBy, canGroupBy, canFilter,
|
||||
} = column
|
||||
|
||||
if (typeof accessor === 'string') {
|
||||
id = id || accessor
|
||||
const accessorString = accessor
|
||||
accessor = row => getBy(row, accessorString)
|
||||
}
|
||||
|
||||
const grouped = groupBy.includes(id)
|
||||
|
||||
canSortBy = accessor
|
||||
? getFirstDefined(canSortBy, disableSorting === true ? false : undefined, true)
|
||||
: false
|
||||
canGroupBy = accessor
|
||||
? getFirstDefined(canGroupBy, disableGrouping === true ? false : undefined, true)
|
||||
: false
|
||||
canFilter = accessor
|
||||
? getFirstDefined(canFilter, disableFilters === true ? false : undefined, true)
|
||||
: false
|
||||
|
||||
if (!id && typeof Header === 'string') {
|
||||
id = Header
|
||||
}
|
||||
|
||||
if (!id) {
|
||||
// Accessor, but no column id? This is bad.
|
||||
console.error(column)
|
||||
throw new Error('A column id is required!')
|
||||
}
|
||||
|
||||
column = {
|
||||
Header: '',
|
||||
Cell: cell => cell.value,
|
||||
show: true,
|
||||
...column,
|
||||
grouped,
|
||||
canSortBy,
|
||||
canGroupBy,
|
||||
canFilter,
|
||||
id,
|
||||
accessor,
|
||||
parent,
|
||||
}
|
||||
|
||||
column.Aggregated = column.Aggregated || column.Cell
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
// Build the visible columns, headers and flat column list
|
||||
function decorateColumnTree (columns, parent, depth = 0) {
|
||||
return columns.map(column => {
|
||||
column = decorateColumn(column, parent)
|
||||
if (column.columns) {
|
||||
column.columns = decorateColumnTree(column.columns, column, depth + 1)
|
||||
}
|
||||
return column
|
||||
})
|
||||
}
|
||||
|
||||
function flattenBy (columns, childKey) {
|
||||
const flatColumns = []
|
||||
|
||||
const recurse = columns => {
|
||||
columns.forEach(d => {
|
||||
if (!d[childKey]) {
|
||||
flatColumns.push(d)
|
||||
} else {
|
||||
recurse(d[childKey])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
recurse(columns)
|
||||
|
||||
return flatColumns
|
||||
}
|
||||
|
||||
// Build the header groups from the bottom up
|
||||
function makeHeaderGroups (columns, maxDepth) {
|
||||
const headerGroups = []
|
||||
|
||||
const removeChildColumns = column => {
|
||||
delete column.columns
|
||||
if (column.parent) {
|
||||
removeChildColumns(column.parent)
|
||||
}
|
||||
}
|
||||
columns.forEach(removeChildColumns)
|
||||
|
||||
const buildGroup = (columns, depth = 0) => {
|
||||
const headerGroup = {
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const parentColumns = []
|
||||
|
||||
const hasParents = columns.some(col => col.parent)
|
||||
|
||||
columns.forEach(column => {
|
||||
const isFirst = !parentColumns.length
|
||||
let latestParentColumn = [...parentColumns].reverse()[0]
|
||||
|
||||
// If the column has a parent, add it if necessary
|
||||
if (column.parent) {
|
||||
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
|
||||
parentColumns.push({
|
||||
...column.parent,
|
||||
originalID: column.parent.id,
|
||||
id: [column.parent.id, parentColumns.length].join('_'),
|
||||
})
|
||||
}
|
||||
} else if (hasParents) {
|
||||
// If other columns have parents, add a place holder if necessary
|
||||
const placeholderColumn = decorateColumn({
|
||||
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
|
||||
id: [column.id, 'placeholder', maxDepth - depth, parentColumns.length].join('_'),
|
||||
})
|
||||
if (isFirst || latestParentColumn.originalID !== placeholderColumn.originalID) {
|
||||
parentColumns.push(placeholderColumn)
|
||||
}
|
||||
}
|
||||
|
||||
// Establish the new columns[] relationship on the parent
|
||||
if (column.parent || hasParents) {
|
||||
latestParentColumn = [...parentColumns].reverse()[0]
|
||||
latestParentColumn.columns = latestParentColumn.columns || []
|
||||
if (!latestParentColumn.columns.includes(column)) {
|
||||
latestParentColumn.columns.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
headerGroup.headers.push(column)
|
||||
})
|
||||
|
||||
headerGroups.push(headerGroup)
|
||||
|
||||
if (parentColumns.length) {
|
||||
buildGroup(parentColumns)
|
||||
}
|
||||
}
|
||||
|
||||
buildGroup(columns)
|
||||
|
||||
return headerGroups.reverse()
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
const mergeStateAndProps = (state, defaults, props) => {
|
||||
Object.keys(props).forEach(key => {
|
||||
if (typeof props[key] !== 'undefined') {
|
||||
state[key] = props[key]
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(defaults).forEach(key => {
|
||||
if (typeof state[key] === 'undefined') {
|
||||
state[key] = defaults[key]
|
||||
}
|
||||
})
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default function useControlledState ([userState, userSetState], defaults, props) {
|
||||
const state = mergeStateAndProps(userState, defaults, props)
|
||||
|
||||
const setState = (updater, type) =>
|
||||
userSetState(old => updater(mergeStateAndProps(old, defaults, props)), type)
|
||||
|
||||
return [state, setState]
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useMemo, useEffect } from 'react'
|
||||
|
||||
import { getBy } from '../utils'
|
||||
|
||||
export default function useExpandedRows ({
|
||||
debug,
|
||||
expanded,
|
||||
expandedKey,
|
||||
columns,
|
||||
rows,
|
||||
setState,
|
||||
actions,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getExpandedRows')
|
||||
|
||||
const expandedRows = []
|
||||
|
||||
// Here we do some mutation, but it's the last stage in the
|
||||
// immutable process so this is safe
|
||||
const handleRow = (row, index, depth = 0, parentPath = []) => {
|
||||
// Compute some final state for the row
|
||||
const path = [...parentPath, index]
|
||||
|
||||
row.path = path
|
||||
row.depth = depth
|
||||
|
||||
row.isExpanded = (row.original && row.original[expandedKey]) || getBy(expanded, path)
|
||||
|
||||
row.cells = columns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
state: null,
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
expandedRows.push(row)
|
||||
|
||||
if (row.isExpanded && row.subRows && row.subRows.length) {
|
||||
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
|
||||
}
|
||||
}
|
||||
|
||||
rows.forEach((row, i) => handleRow(row, i))
|
||||
|
||||
return expandedRows
|
||||
},
|
||||
[rows, expanded, columns]
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useFilteredRows ({
|
||||
debug,
|
||||
filters,
|
||||
rows,
|
||||
columns,
|
||||
filterFn,
|
||||
manualFilters,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualFilters || !Object.keys(filters).length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
if (debug) console.info('getFilteredRows')
|
||||
|
||||
// Filters top level and nested rows
|
||||
const filterRows = rows => {
|
||||
let filteredRows = rows
|
||||
|
||||
filteredRows = Object.entries(filters).reduce((filteredSoFar, [columnID, filterValue]) => {
|
||||
// Find the filters column
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
|
||||
// Don't filter hidden columns or columns that have had their filters disabled
|
||||
if (!column || column.filterable === false) {
|
||||
return filteredSoFar
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || filterFn
|
||||
|
||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||
if (column.filterAll) {
|
||||
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||
}
|
||||
return filteredSoFar.filter(row => filterMethod(row, columnID, filterValue, column))
|
||||
}, rows)
|
||||
|
||||
// Apply the filter to any subRows
|
||||
filteredRows = filteredRows.map(row => {
|
||||
if (!row.subRows) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
subRows: filterRows(row.subRows),
|
||||
}
|
||||
})
|
||||
|
||||
// then filter any rows without subcolumns because it would be strange to show
|
||||
filteredRows = filteredRows.filter(row => {
|
||||
if (!row.subRows) {
|
||||
return true
|
||||
}
|
||||
return row.subRows.length > 0
|
||||
})
|
||||
|
||||
return filteredRows
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
},
|
||||
[rows, filters, manualFilters]
|
||||
)
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import * as aggregations from '../aggregations'
|
||||
|
||||
export default function useGroupedRows ({
|
||||
debug,
|
||||
groupBy,
|
||||
rows,
|
||||
columns,
|
||||
groupByFn,
|
||||
manualGroupBy,
|
||||
userAggregations,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualGroupBy || !groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getGroupedRows')
|
||||
// Find the columns that can or are aggregating
|
||||
|
||||
// Uses each column to aggregate rows into a single value
|
||||
const aggregateRowsToValues = rows => {
|
||||
const values = {}
|
||||
columns.forEach(column => {
|
||||
const columnValues = rows.map(d => d.values[column.id])
|
||||
const aggregate =
|
||||
userAggregations[column.aggregate] || aggregations[column.aggregate] || column.aggregate
|
||||
if (typeof aggregate === 'function') {
|
||||
values[column.id] = aggregate(columnValues, rows)
|
||||
} else if (aggregate) {
|
||||
throw new Error(
|
||||
`Invalid aggregate "${aggregate}" passed to column with ID: "${column.id}"`
|
||||
)
|
||||
} else {
|
||||
values[column.id] = columnValues[0]
|
||||
}
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Recursively group the data
|
||||
const groupRecursively = (rows, groupBy, depth = 0) => {
|
||||
// This is the last level, just return the rows
|
||||
if (depth >= groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
// Group the rows together for this level
|
||||
const groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
|
||||
([groupByVal, subRows], index) => {
|
||||
// Recurse to sub rows before aggregation
|
||||
subRows = groupRecursively(subRows, groupBy, depth + 1)
|
||||
|
||||
const values = aggregateRowsToValues(subRows)
|
||||
|
||||
const row = {
|
||||
groupByID: groupBy[depth],
|
||||
groupByVal,
|
||||
values,
|
||||
subRows,
|
||||
depth,
|
||||
index,
|
||||
}
|
||||
return row
|
||||
}
|
||||
)
|
||||
|
||||
return groupedRows
|
||||
}
|
||||
|
||||
// Assign the new data
|
||||
return groupRecursively(rows, groupBy)
|
||||
},
|
||||
[rows, groupBy, columns, manualGroupBy]
|
||||
)
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useSortedRows ({
|
||||
debug,
|
||||
sortBy,
|
||||
rows,
|
||||
columns,
|
||||
orderByFn,
|
||||
sortByFn,
|
||||
manualSorting,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualSorting || !sortBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getSortedRows')
|
||||
|
||||
const sortMethodsByColumnID = {}
|
||||
|
||||
columns
|
||||
.filter(col => col.sortMethod)
|
||||
.forEach(col => {
|
||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||
})
|
||||
|
||||
const sortData = rows => {
|
||||
// Use the orderByFn to compose multiple sortBy's together.
|
||||
// This will also perform a stable sorting using the row index
|
||||
// if needed.
|
||||
const sortedData = orderByFn(
|
||||
rows,
|
||||
sortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const columnSortBy = sortMethodsByColumnID[sort.id]
|
||||
|
||||
// Return the correct sortFn
|
||||
return (a, b) =>
|
||||
(columnSortBy || sortByFn)(a.values[sort.id], b.values[sort.id], sort.desc)
|
||||
}),
|
||||
// Map the directions
|
||||
sortBy.map(d => !d.desc)
|
||||
)
|
||||
|
||||
// TODO: this should be optimized. Not good to loop again
|
||||
sortedData.forEach(row => {
|
||||
if (!row.subRows) {
|
||||
return
|
||||
}
|
||||
row.subRows = sortData(row.subRows)
|
||||
})
|
||||
|
||||
return sortedData
|
||||
}
|
||||
|
||||
return sortData(rows)
|
||||
},
|
||||
[rows, columns, sortBy, manualSorting]
|
||||
)
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import { getBy, setBy, getFirstDefined } from '../utils'
|
||||
|
||||
export default function useTableMethods ({
|
||||
debug,
|
||||
setState,
|
||||
actions,
|
||||
groupBy,
|
||||
columns,
|
||||
defaultSortDesc,
|
||||
filters,
|
||||
}) {
|
||||
const toggleExpandedByPath = (path, set) =>
|
||||
setState(old => {
|
||||
const { expanded } = old
|
||||
const existing = getBy(expanded, path)
|
||||
set = getFirstDefined(set, !existing)
|
||||
return {
|
||||
...old,
|
||||
expanded: setBy(expanded, path, set),
|
||||
}
|
||||
}, actions.toggleExpanded)
|
||||
|
||||
// Updates sorting based on a columnID, desc flag and multi flag
|
||||
const toggleSortByID = (columnID, desc, multi) =>
|
||||
setState(old => {
|
||||
const { sortBy } = old
|
||||
|
||||
// Find the column for this columnID
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const resolvedDefaultSortDesc = getFirstDefined(column.defaultSortDesc, defaultSortDesc)
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
const existingSortBy = sortBy.find(d => d.id === columnID)
|
||||
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
|
||||
|
||||
let newSortBy = []
|
||||
|
||||
// What should we do with this filter?
|
||||
let action
|
||||
|
||||
if (!multi) {
|
||||
if (sortBy.length <= 1 && existingSortBy) {
|
||||
if (existingSortBy.desc) {
|
||||
action = 'remove'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
} else {
|
||||
action = 'replace'
|
||||
}
|
||||
} else if (!existingSortBy) {
|
||||
action = 'add'
|
||||
} else if (hasDescDefined) {
|
||||
action = 'set'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
|
||||
if (action === 'replace') {
|
||||
newSortBy = [
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||
},
|
||||
]
|
||||
} else if (action === 'add') {
|
||||
newSortBy = [
|
||||
...sortBy,
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||
},
|
||||
]
|
||||
} else if (action === 'set') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc,
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'toggle') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc: !existingSortBy.desc,
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'remove') {
|
||||
newSortBy = []
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
sortBy: newSortBy,
|
||||
}
|
||||
}, actions.sortByChange)
|
||||
|
||||
const toggleGroupBy = (id, toggle) =>
|
||||
setState(old => {
|
||||
const resolvedToggle = typeof set !== 'undefined' ? toggle : !groupBy.includes(id)
|
||||
if (resolvedToggle) {
|
||||
return {
|
||||
...old,
|
||||
groupBy: [...groupBy, id],
|
||||
}
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
groupBy: groupBy.filter(d => d !== id),
|
||||
}
|
||||
}, actions.toggleGroupBy)
|
||||
|
||||
const setFilter = (id, val) =>
|
||||
setState(old => {
|
||||
if (typeof val === 'undefined') {
|
||||
const { [id]: prev, ...rest } = filters
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...rest,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...filters,
|
||||
[id]: val,
|
||||
},
|
||||
}
|
||||
}, actions.setFilter)
|
||||
|
||||
const setAllFilters = filters =>
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
filters,
|
||||
}),
|
||||
actions.setAllFilters
|
||||
)
|
||||
|
||||
return {
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
}
|
||||
}
|
||||
Executable → Regular
+835
-9
@@ -1,11 +1,837 @@
|
||||
import useReactTable, { actions as mainActions } from './useReactTable'
|
||||
import usePagination, { actions as paginationActions } from './usePagination'
|
||||
import useFlexLayout, { actions as flexLayoutActions } from './useFlexLayout'
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
import _ from './utils'
|
||||
import Lifecycle from './lifecycle'
|
||||
import Methods from './methods'
|
||||
import defaultProps from './defaultProps'
|
||||
|
||||
const actions = {
|
||||
...mainActions,
|
||||
...paginationActions,
|
||||
...flexLayoutActions,
|
||||
export const ReactTableDefaults = defaultProps
|
||||
|
||||
export default class ReactTable extends Methods(Lifecycle(Component)) {
|
||||
static defaultProps = defaultProps
|
||||
|
||||
constructor (props) {
|
||||
super()
|
||||
|
||||
this.getResolvedState = this.getResolvedState.bind(this)
|
||||
this.getDataModel = this.getDataModel.bind(this)
|
||||
this.getSortedData = this.getSortedData.bind(this)
|
||||
this.fireFetchData = this.fireFetchData.bind(this)
|
||||
this.getPropOrState = this.getPropOrState.bind(this)
|
||||
this.getStateOrProp = this.getStateOrProp.bind(this)
|
||||
this.filterData = this.filterData.bind(this)
|
||||
this.sortData = this.sortData.bind(this)
|
||||
this.getMinRows = this.getMinRows.bind(this)
|
||||
this.onPageChange = this.onPageChange.bind(this)
|
||||
this.onPageSizeChange = this.onPageSizeChange.bind(this)
|
||||
this.sortColumn = this.sortColumn.bind(this)
|
||||
this.filterColumn = this.filterColumn.bind(this)
|
||||
this.resizeColumnStart = this.resizeColumnStart.bind(this)
|
||||
this.resizeColumnEnd = this.resizeColumnEnd.bind(this)
|
||||
this.resizeColumnMoving = this.resizeColumnMoving.bind(this)
|
||||
|
||||
this.state = {
|
||||
page: 0,
|
||||
pageSize: props.defaultPageSize,
|
||||
sorted: props.defaultSorted,
|
||||
expanded: props.defaultExpanded,
|
||||
filtered: props.defaultFiltered,
|
||||
resized: props.defaultResized,
|
||||
currentlyResizing: false,
|
||||
skipNextSort: false
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const resolvedState = this.getResolvedState()
|
||||
const {
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
getProps,
|
||||
getTableProps,
|
||||
getTheadGroupProps,
|
||||
getTheadGroupTrProps,
|
||||
getTheadGroupThProps,
|
||||
getTheadProps,
|
||||
getTheadTrProps,
|
||||
getTheadThProps,
|
||||
getTheadFilterProps,
|
||||
getTheadFilterTrProps,
|
||||
getTheadFilterThProps,
|
||||
getTbodyProps,
|
||||
getTrGroupProps,
|
||||
getTrProps,
|
||||
getTdProps,
|
||||
getTfootProps,
|
||||
getTfootTrProps,
|
||||
getTfootTdProps,
|
||||
getPaginationProps,
|
||||
getLoadingProps,
|
||||
getNoDataProps,
|
||||
getResizerProps,
|
||||
showPagination,
|
||||
manual,
|
||||
loadingText,
|
||||
noDataText,
|
||||
sortable,
|
||||
resizable,
|
||||
filterable,
|
||||
// Pivoting State
|
||||
pivotIDKey,
|
||||
pivotValKey,
|
||||
pivotBy,
|
||||
subRowsKey,
|
||||
aggregatedKey,
|
||||
originalKey,
|
||||
indexKey,
|
||||
groupedByPivotKey,
|
||||
// State
|
||||
loading,
|
||||
pageSize,
|
||||
page,
|
||||
sorted,
|
||||
filtered,
|
||||
resized,
|
||||
expanded,
|
||||
pages,
|
||||
onExpandedChange,
|
||||
// Components
|
||||
TableComponent,
|
||||
TheadComponent,
|
||||
TbodyComponent,
|
||||
TrGroupComponent,
|
||||
TrComponent,
|
||||
ThComponent,
|
||||
TdComponent,
|
||||
TfootComponent,
|
||||
PaginationComponent,
|
||||
LoadingComponent,
|
||||
SubComponent,
|
||||
NoDataComponent,
|
||||
ResizerComponent,
|
||||
ExpanderComponent,
|
||||
PivotValueComponent,
|
||||
PivotComponent,
|
||||
AggregatedComponent,
|
||||
FilterComponent,
|
||||
// Data model
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
headerGroups,
|
||||
hasHeaderGroups,
|
||||
// Sorted Data
|
||||
sortedData,
|
||||
currentlyResizing
|
||||
} = resolvedState
|
||||
|
||||
// Pagination
|
||||
const startRow = pageSize * page
|
||||
const endRow = startRow + pageSize
|
||||
let pageRows = manual ? resolvedData : sortedData.slice(startRow, endRow)
|
||||
const minRows = this.getMinRows()
|
||||
const padRows = _.range(Math.max(minRows - pageRows.length, 0))
|
||||
|
||||
const hasColumnFooter = allVisibleColumns.some(d => d.Footer)
|
||||
const hasFilters = filterable || allVisibleColumns.some(d => d.filterable)
|
||||
|
||||
const recurseRowsViewIndex = (rows, path = [], index = -1) => {
|
||||
return [
|
||||
rows.map((row, i) => {
|
||||
index++
|
||||
const rowWithViewIndex = {
|
||||
...row,
|
||||
_viewIndex: index
|
||||
}
|
||||
const newPath = path.concat([i])
|
||||
if (rowWithViewIndex[subRowsKey] && _.get(expanded, newPath)) {
|
||||
[rowWithViewIndex[subRowsKey], index] = recurseRowsViewIndex(rowWithViewIndex[subRowsKey], newPath, index)
|
||||
}
|
||||
return rowWithViewIndex
|
||||
}),
|
||||
index
|
||||
]
|
||||
}
|
||||
|
||||
[pageRows] = recurseRowsViewIndex(pageRows)
|
||||
|
||||
const canPrevious = page > 0
|
||||
const canNext = page + 1 < pages
|
||||
|
||||
const rowMinWidth = _.sum(allVisibleColumns.map(d => {
|
||||
const resizedColumn = resized.find(x => x.id === d.id) || {}
|
||||
return _.getFirstDefined(resizedColumn.value, d.width, d.minWidth)
|
||||
}))
|
||||
|
||||
let rowIndex = -1
|
||||
|
||||
const finalState = {
|
||||
...resolvedState,
|
||||
startRow,
|
||||
endRow,
|
||||
pageRows,
|
||||
minRows,
|
||||
padRows,
|
||||
hasColumnFooter,
|
||||
canPrevious,
|
||||
canNext,
|
||||
rowMinWidth
|
||||
}
|
||||
|
||||
// Visual Components
|
||||
|
||||
const makeHeaderGroups = () => {
|
||||
const theadGroupProps = _.splitProps(getTheadGroupProps(finalState, undefined, undefined, this))
|
||||
const theadGroupTrProps = _.splitProps(getTheadGroupTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames('-headerGroups', theadGroupProps.className)}
|
||||
style={{
|
||||
...theadGroupProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...theadGroupProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={theadGroupTrProps.className}
|
||||
style={theadGroupTrProps.style}
|
||||
{...theadGroupTrProps.rest}
|
||||
>
|
||||
{headerGroups.map(makeHeaderGroup)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeHeaderGroup = (column, i) => {
|
||||
const resizedValue = col => (resized.find(x => x.id === col.id) || {}).value
|
||||
const flex = _.sum(column.columns.map(col => col.width || resizedValue(col) ? 0 : col.minWidth))
|
||||
const width = _.sum(column.columns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.minWidth)))
|
||||
const maxWidth = _.sum(column.columns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.maxWidth)))
|
||||
|
||||
const theadGroupThProps = _.splitProps(getTheadGroupThProps(finalState, undefined, column, this))
|
||||
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
column.headerClassName,
|
||||
theadGroupThProps.className,
|
||||
columnHeaderProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...column.headerStyle,
|
||||
...theadGroupThProps.style,
|
||||
...columnHeaderProps.style
|
||||
}
|
||||
|
||||
const rest = {
|
||||
...theadGroupThProps.rest,
|
||||
...columnHeaderProps.rest
|
||||
}
|
||||
|
||||
const flexStyles = {
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}
|
||||
|
||||
return (
|
||||
<ThComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
...flexStyles
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{_.normalizeComponent(column.Header, {
|
||||
data: sortedData,
|
||||
column: column
|
||||
})}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeHeaders = () => {
|
||||
const theadProps = _.splitProps(getTheadProps(finalState, undefined, undefined, this))
|
||||
const theadTrProps = _.splitProps(getTheadTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames('-header', theadProps.className)}
|
||||
style={{
|
||||
...theadProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...theadProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={theadTrProps.className}
|
||||
style={theadTrProps.style}
|
||||
{...theadTrProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map(makeHeader)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeHeader = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const sort = sorted.find(d => d.id === column.id)
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const theadThProps = _.splitProps(getTheadThProps(finalState, undefined, column, this))
|
||||
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
column.headerClassName,
|
||||
theadThProps.className,
|
||||
columnHeaderProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...column.headerStyle,
|
||||
...theadThProps.style,
|
||||
...columnHeaderProps.style
|
||||
}
|
||||
|
||||
const rest = {
|
||||
...theadThProps.rest,
|
||||
...columnHeaderProps.rest
|
||||
}
|
||||
|
||||
const isResizable = _.getFirstDefined(column.resizable, resizable, false)
|
||||
const resizer = isResizable ? (
|
||||
<ResizerComponent
|
||||
onMouseDown={e => this.resizeColumnStart(column, e, false)}
|
||||
onTouchStart={e => this.resizeColumnStart(column, e, true)}
|
||||
|
||||
{...resizerProps}
|
||||
/>
|
||||
) : null
|
||||
|
||||
const isSortable = _.getFirstDefined(column.sortable, sortable, false)
|
||||
|
||||
return (
|
||||
<ThComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
'rt-resizable-header',
|
||||
sort ? (sort.desc ? '-sort-desc' : '-sort-asc') : '',
|
||||
isSortable && '-cursor-pointer',
|
||||
!show && '-hidden',
|
||||
pivotBy && pivotBy.slice(0, -1).includes(column.id) && 'rt-header-pivot'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
toggleSort={(e) => {
|
||||
isSortable && this.sortColumn(column, e.shiftKey)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<div className='rt-resizable-header-content'>
|
||||
{_.normalizeComponent(column.Header, {
|
||||
data: sortedData,
|
||||
column: column
|
||||
})}
|
||||
</div>
|
||||
{resizer}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeFilters = () => {
|
||||
const theadFilterProps = _.splitProps(getTheadFilterProps(finalState, undefined, undefined, this))
|
||||
const theadFilterTrProps = _.splitProps(getTheadFilterTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames('-filters', theadFilterProps.className)}
|
||||
style={{
|
||||
...theadFilterProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...theadFilterProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={theadFilterTrProps.className}
|
||||
style={theadFilterTrProps.style}
|
||||
{...theadFilterTrProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map(makeFilter)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeFilter = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const theadFilterThProps = _.splitProps(getTheadFilterThProps(finalState, undefined, column, this))
|
||||
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
column.headerClassName,
|
||||
theadFilterThProps.className,
|
||||
columnHeaderProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...column.headerStyle,
|
||||
...theadFilterThProps.style,
|
||||
...columnHeaderProps.style
|
||||
}
|
||||
|
||||
const rest = {
|
||||
...theadFilterThProps.rest,
|
||||
...columnHeaderProps.rest
|
||||
}
|
||||
|
||||
const filter = filtered.find(filter => filter.id === column.id)
|
||||
|
||||
const ResolvedFilterComponent = column.Filter || FilterComponent
|
||||
|
||||
const isFilterable = _.getFirstDefined(column.filterable, filterable, false)
|
||||
|
||||
return (
|
||||
<ThComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{isFilterable ? (
|
||||
_.normalizeComponent(ResolvedFilterComponent,
|
||||
{
|
||||
column,
|
||||
filter,
|
||||
onChange: (value) => (this.filterColumn(column, value))
|
||||
},
|
||||
defaultProps.column.Filter
|
||||
)
|
||||
) : null}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePageRow = (row, i, path = []) => {
|
||||
const rowInfo = {
|
||||
original: row[originalKey],
|
||||
row: row,
|
||||
index: row[indexKey],
|
||||
viewIndex: ++rowIndex,
|
||||
level: path.length,
|
||||
nestingPath: path.concat([i]),
|
||||
aggregated: row[aggregatedKey],
|
||||
groupedByPivot: row[groupedByPivotKey],
|
||||
subRows: row[subRowsKey]
|
||||
}
|
||||
const isExpanded = _.get(expanded, rowInfo.nestingPath)
|
||||
const trGroupProps = getTrGroupProps(finalState, rowInfo, undefined, this)
|
||||
const trProps = _.splitProps(getTrProps(finalState, rowInfo, undefined, this))
|
||||
return (
|
||||
<TrGroupComponent
|
||||
key={rowInfo.nestingPath.join('_')}
|
||||
{...trGroupProps}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(
|
||||
trProps.className,
|
||||
row._viewIndex % 2 ? '-even' : '-odd'
|
||||
)}
|
||||
style={trProps.style}
|
||||
{...trProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map((column, i2) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const tdProps = _.splitProps(getTdProps(finalState, rowInfo, column, this))
|
||||
const columnProps = _.splitProps(column.getProps(finalState, rowInfo, column, this))
|
||||
|
||||
const classes = [
|
||||
tdProps.className,
|
||||
column.className,
|
||||
columnProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...tdProps.style,
|
||||
...column.style,
|
||||
...columnProps.style
|
||||
}
|
||||
|
||||
const cellInfo = {
|
||||
...rowInfo,
|
||||
isExpanded,
|
||||
column: {...column},
|
||||
value: rowInfo.row[column.id],
|
||||
pivoted: column.pivoted,
|
||||
expander: column.expander,
|
||||
resized,
|
||||
show,
|
||||
width,
|
||||
maxWidth,
|
||||
tdProps,
|
||||
columnProps,
|
||||
classes,
|
||||
styles
|
||||
}
|
||||
|
||||
const value = cellInfo.value
|
||||
|
||||
let interactionProps
|
||||
let isBranch
|
||||
let isPreview
|
||||
|
||||
const onExpanderClick = (e) => {
|
||||
let newExpanded = _.clone(expanded)
|
||||
if (isExpanded) {
|
||||
newExpanded = _.set(newExpanded, cellInfo.nestingPath, false)
|
||||
} else {
|
||||
newExpanded = _.set(newExpanded, cellInfo.nestingPath, {})
|
||||
}
|
||||
|
||||
return this.setStateWithData({
|
||||
expanded: newExpanded
|
||||
}, () => {
|
||||
onExpandedChange && onExpandedChange(newExpanded, cellInfo.nestingPath, e)
|
||||
})
|
||||
}
|
||||
|
||||
// Default to a standard cell
|
||||
let resolvedCell = _.normalizeComponent(column.Cell, cellInfo, value)
|
||||
|
||||
// Resolve Renderers
|
||||
const ResolvedAggregatedComponent = column.Aggregated || (!column.aggregate ? AggregatedComponent : column.Cell)
|
||||
const ResolvedExpanderComponent = column.Expander || ExpanderComponent
|
||||
const ResolvedPivotValueComponent = column.PivotValue || PivotValueComponent
|
||||
const DefaultResolvedPivotComponent = PivotComponent || (
|
||||
props => (
|
||||
<div>
|
||||
<ResolvedExpanderComponent {...props} />
|
||||
<ResolvedPivotValueComponent {...props} />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
const ResolvedPivotComponent = column.Pivot || DefaultResolvedPivotComponent
|
||||
|
||||
// Is this cell expandable?
|
||||
if (cellInfo.pivoted || cellInfo.expander) {
|
||||
// Make it expandable by defualt
|
||||
cellInfo.expandable = true
|
||||
interactionProps = {
|
||||
onClick: onExpanderClick
|
||||
}
|
||||
// If pivoted, has no subRows, and does not have a subComponent, do not make expandable
|
||||
if (cellInfo.pivoted) {
|
||||
if (!cellInfo.subRows) {
|
||||
if (!SubComponent) {
|
||||
cellInfo.expandable = false
|
||||
interactionProps = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellInfo.pivoted) {
|
||||
// Is this column a branch?
|
||||
isBranch = rowInfo.row[pivotIDKey] === column.id &&
|
||||
cellInfo.subRows
|
||||
// Should this column be blank?
|
||||
isPreview = pivotBy.indexOf(column.id) > pivotBy.indexOf(rowInfo.row[pivotIDKey]) &&
|
||||
cellInfo.subRows
|
||||
// Pivot Cell Render Override
|
||||
if (isBranch) {
|
||||
// isPivot
|
||||
resolvedCell = _.normalizeComponent(ResolvedPivotComponent, {
|
||||
...cellInfo,
|
||||
value: row[pivotValKey]
|
||||
}, row[pivotValKey])
|
||||
} else if (isPreview) {
|
||||
// Show the pivot preview
|
||||
resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value)
|
||||
} else {
|
||||
resolvedCell = null
|
||||
}
|
||||
} else if (cellInfo.aggregated) {
|
||||
resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value)
|
||||
}
|
||||
|
||||
if (cellInfo.expander) {
|
||||
resolvedCell = _.normalizeComponent(ResolvedExpanderComponent, cellInfo, row[pivotValKey])
|
||||
if (pivotBy) {
|
||||
if (cellInfo.groupedByPivot) {
|
||||
resolvedCell = null
|
||||
}
|
||||
if (!cellInfo.subRows && !SubComponent) {
|
||||
resolvedCell = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the cell
|
||||
return (
|
||||
<TdComponent
|
||||
key={i2 + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
!show && 'hidden',
|
||||
cellInfo.expandable && 'rt-expandable',
|
||||
(isBranch || isPreview) && 'rt-pivot'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...tdProps.rest}
|
||||
{...interactionProps}
|
||||
>
|
||||
{resolvedCell}
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
</TrComponent>
|
||||
{(
|
||||
rowInfo.subRows &&
|
||||
isExpanded &&
|
||||
rowInfo.subRows.map((d, i) => makePageRow(d, i, rowInfo.nestingPath))
|
||||
)}
|
||||
{SubComponent && !rowInfo.subRows && isExpanded && SubComponent(rowInfo)}
|
||||
</TrGroupComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePadRow = (row, i) => {
|
||||
const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this)
|
||||
const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TrGroupComponent
|
||||
key={i}
|
||||
{...trGroupProps}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(
|
||||
'-padRow',
|
||||
(pageRows.length + i) % 2 ? '-even' : '-odd',
|
||||
trProps.className
|
||||
)}
|
||||
style={trProps.style || {}}
|
||||
>
|
||||
{allVisibleColumns.map(makePadColumn)}
|
||||
</TrComponent>
|
||||
</TrGroupComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePadColumn = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
let width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
let flex = width
|
||||
let maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const tdProps = _.splitProps(getTdProps(finalState, undefined, column, this))
|
||||
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
tdProps.className,
|
||||
column.className,
|
||||
columnProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...tdProps.style,
|
||||
...column.style,
|
||||
...columnProps.style
|
||||
}
|
||||
|
||||
return (
|
||||
<TdComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
!show && 'hidden'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...tdProps.rest}
|
||||
>
|
||||
|
||||
</TdComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeColumnFooters = () => {
|
||||
const tFootProps = getTfootProps(finalState, undefined, undefined, this)
|
||||
const tFootTrProps = _.splitProps(getTfootTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TfootComponent
|
||||
className={tFootProps.className}
|
||||
style={{
|
||||
...tFootProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...tFootProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(
|
||||
tFootTrProps.className
|
||||
)}
|
||||
style={tFootTrProps.style}
|
||||
{...tFootTrProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map(makeColumnFooter)}
|
||||
</TrComponent>
|
||||
</TfootComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeColumnFooter = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const tFootTdProps = _.splitProps(getTfootTdProps(finalState, undefined, undefined, this))
|
||||
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
|
||||
const columnFooterProps = _.splitProps(column.getFooterProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
tFootTdProps.className,
|
||||
column.className,
|
||||
columnProps.className,
|
||||
columnFooterProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...tFootTdProps.style,
|
||||
...column.style,
|
||||
...columnProps.style,
|
||||
...columnFooterProps.style
|
||||
}
|
||||
|
||||
return (
|
||||
<TdComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
!show && 'hidden'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...columnProps.rest}
|
||||
{...tFootTdProps.rest}
|
||||
{...columnFooterProps.rest}
|
||||
>
|
||||
{_.normalizeComponent(column.Footer, {
|
||||
data: sortedData,
|
||||
column: column
|
||||
})}
|
||||
</TdComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const rootProps = _.splitProps(getProps(finalState, undefined, undefined, this))
|
||||
const tableProps = _.splitProps(getTableProps(finalState, undefined, undefined, this))
|
||||
const tBodyProps = _.splitProps(getTbodyProps(finalState, undefined, undefined, this))
|
||||
const paginationProps = _.splitProps(getPaginationProps(finalState, undefined, undefined, this))
|
||||
const loadingProps = getLoadingProps(finalState, undefined, undefined, this)
|
||||
const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
|
||||
const resizerProps = getResizerProps(finalState, undefined, undefined, this)
|
||||
|
||||
const makeTable = () => (
|
||||
<div
|
||||
className={classnames(
|
||||
'ReactTable',
|
||||
className,
|
||||
rootProps.className
|
||||
)}
|
||||
style={{
|
||||
...style,
|
||||
...rootProps.style
|
||||
}}
|
||||
{...rootProps.rest}
|
||||
>
|
||||
<TableComponent
|
||||
className={classnames(
|
||||
tableProps.className,
|
||||
currentlyResizing ? 'rt-resizing' : ''
|
||||
)}
|
||||
style={tableProps.style}
|
||||
{...tableProps.rest}
|
||||
>
|
||||
{hasHeaderGroups ? makeHeaderGroups() : null}
|
||||
{makeHeaders()}
|
||||
{hasFilters ? makeFilters() : null}
|
||||
<TbodyComponent
|
||||
className={classnames(tBodyProps.className)}
|
||||
style={{
|
||||
...tBodyProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...tBodyProps.rest}
|
||||
>
|
||||
{pageRows.map((d, i) => makePageRow(d, i))}
|
||||
{padRows.map(makePadRow)}
|
||||
</TbodyComponent>
|
||||
{hasColumnFooter ? makeColumnFooters() : null}
|
||||
</TableComponent>
|
||||
{showPagination ? (
|
||||
<PaginationComponent
|
||||
{...resolvedState}
|
||||
pages={pages}
|
||||
canPrevious={canPrevious}
|
||||
canNext={canNext}
|
||||
onPageChange={this.onPageChange}
|
||||
onPageSizeChange={this.onPageSizeChange}
|
||||
className={paginationProps.className}
|
||||
style={paginationProps.style}
|
||||
{...paginationProps.rest}
|
||||
/>
|
||||
) : null}
|
||||
{!pageRows.length && (
|
||||
<NoDataComponent
|
||||
{...noDataProps}
|
||||
>
|
||||
{_.normalizeComponent(noDataText)}
|
||||
</NoDataComponent>
|
||||
)}
|
||||
<LoadingComponent
|
||||
loading={loading}
|
||||
loadingText={loadingText}
|
||||
{...loadingProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
// childProps are optionally passed to a function-as-a-child
|
||||
return children ? children(finalState, makeTable, this) : makeTable()
|
||||
}
|
||||
}
|
||||
|
||||
export { useReactTable, usePagination, useFlexLayout, actions }
|
||||
|
||||
+321
@@ -0,0 +1,321 @@
|
||||
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940)
|
||||
$easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
$expandSize = 7px
|
||||
|
||||
.ReactTable
|
||||
position:relative
|
||||
border: 1px solid alpha(black, .1)
|
||||
*
|
||||
box-sizing: border-box
|
||||
.rt-table
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
overflow: auto
|
||||
|
||||
.rt-thead
|
||||
display: flex
|
||||
flex-direction: column
|
||||
-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)
|
||||
|
||||
&.-filters
|
||||
border-bottom: 1px solid alpha(black, 0.05)
|
||||
|
||||
.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
|
||||
|
||||
.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
|
||||
&.-sort-asc
|
||||
box-shadow:inset 0 3px 0 0 alpha(black, .6)
|
||||
&.-sort-desc
|
||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
||||
&.-cursor-pointer
|
||||
cursor: pointer
|
||||
&:last-child
|
||||
border-right: 0
|
||||
|
||||
.rt-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
|
||||
display: flex
|
||||
flex-direction: column
|
||||
// z-index:0
|
||||
.rt-tr-group
|
||||
border-bottom: solid 1px alpha(black, .05)
|
||||
&:last-child
|
||||
border-bottom: 0
|
||||
.rt-td
|
||||
border-right:1px solid alpha(black, .02)
|
||||
&:last-child
|
||||
border-right:0
|
||||
.rt-expandable
|
||||
cursor: pointer
|
||||
.rt-tr-group
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
.rt-tr
|
||||
display: inline-flex
|
||||
.rt-th
|
||||
.rt-td
|
||||
flex: 1 0 0px
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
padding: 7px 5px
|
||||
overflow: hidden
|
||||
transition: .3s ease
|
||||
transition-property: width, min-width, padding, opacity
|
||||
|
||||
&.-hidden
|
||||
width: 0 !important
|
||||
min-width: 0 !important
|
||||
padding: 0 !important
|
||||
border:0 !important
|
||||
opacity: 0 !important
|
||||
|
||||
.rt-expander
|
||||
display: inline-block
|
||||
position:relative
|
||||
margin: 0
|
||||
color: transparent
|
||||
margin: 0 10px
|
||||
&:after
|
||||
content: ''
|
||||
position: absolute
|
||||
width: 0
|
||||
height: 0
|
||||
top:50%
|
||||
left:50%
|
||||
transform: translate(-50%, -50%) rotate(-90deg)
|
||||
border-left: ($expandSize * .72) solid transparent
|
||||
border-right: ($expandSize * .72) solid transparent
|
||||
border-top: $expandSize solid alpha(black, .8)
|
||||
transition: all .3s $easeOutBack
|
||||
cursor: pointer
|
||||
&.-open:after
|
||||
transform: translate(-50%, -50%) rotate(0deg)
|
||||
|
||||
.rt-resizer
|
||||
display: inline-block
|
||||
position: absolute
|
||||
width: 36px
|
||||
top: 0
|
||||
bottom: 0
|
||||
right: -18px
|
||||
cursor: col-resize
|
||||
z-index: 10
|
||||
|
||||
.rt-tfoot
|
||||
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
|
||||
.rt-tbody
|
||||
.rt-tr:not(.-padRow):hover
|
||||
background: alpha(black, .05)
|
||||
|
||||
.-pagination
|
||||
z-index: 1
|
||||
display:flex
|
||||
justify-content: space-between
|
||||
align-items: stretch
|
||||
flex-wrap: wrap
|
||||
padding: 3px
|
||||
box-shadow: 0 0px 15px 0px alpha(black, .1)
|
||||
border-top: 2px solid alpha(black, .1)
|
||||
|
||||
.-btn
|
||||
appearance:none
|
||||
display:block
|
||||
width:100%
|
||||
height: 100%
|
||||
border: 0
|
||||
border-radius: 3px
|
||||
padding: 6px
|
||||
font-size: 1em
|
||||
color: alpha(black, .6)
|
||||
background: alpha(black, .1)
|
||||
transition: all .1s ease
|
||||
cursor: pointer
|
||||
outline:none
|
||||
|
||||
&[disabled]
|
||||
opacity: .5
|
||||
cursor: default
|
||||
|
||||
&:not([disabled]):hover
|
||||
background: alpha(black, .3)
|
||||
color: white
|
||||
|
||||
.-previous
|
||||
.-next
|
||||
flex: 1
|
||||
text-align: center
|
||||
|
||||
.-center
|
||||
flex: 1.5
|
||||
text-align:center
|
||||
margin-bottom:0
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
align-items: center
|
||||
justify-content: space-around
|
||||
|
||||
.-pageInfo
|
||||
display: inline-block
|
||||
margin: 3px 10px
|
||||
white-space: nowrap
|
||||
|
||||
.-pageJump
|
||||
display:inline-block
|
||||
input
|
||||
width: 70px
|
||||
text-align:center
|
||||
|
||||
.-pageSizeOptions
|
||||
margin: 3px 10px
|
||||
|
||||
.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
|
||||
position:absolute
|
||||
left:0
|
||||
right:0
|
||||
top:0
|
||||
bottom:0
|
||||
background: alpha(white, .8)
|
||||
transition: all .3s ease
|
||||
z-index: 2
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
> div
|
||||
position:absolute
|
||||
display: block
|
||||
text-align:center
|
||||
width:100%
|
||||
top:50%
|
||||
left: 0
|
||||
font-size: 15px
|
||||
color: alpha(black, .6)
|
||||
transform: translateY(-52%)
|
||||
transition: all .3s $easeOutQuad
|
||||
|
||||
&.-active
|
||||
opacity: 1
|
||||
pointer-events: all
|
||||
> div
|
||||
transform: translateY(50%)
|
||||
|
||||
input
|
||||
select
|
||||
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
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"])
|
||||
select
|
||||
appearance: 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,100 @@
|
||||
export default Base => class extends Base {
|
||||
componentWillMount () {
|
||||
this.setStateWithData(this.getDataModel(this.getResolvedState()))
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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 (var i = 0; i < keys.length; i++) {
|
||||
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) ||
|
||||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData && this.props.collapseOnDataChange)
|
||||
) {
|
||||
newResolvedState.expanded = {}
|
||||
}
|
||||
|
||||
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
|
||||
}
|
||||
|
||||
// Calculate pageSize all the time
|
||||
if (newResolvedState.sortedData) {
|
||||
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
|
||||
newResolvedState.page = Math.max(newResolvedState.page >= newResolvedState.pages ? newResolvedState.pages - 1 : newResolvedState.page, 0)
|
||||
}
|
||||
|
||||
return this.setState(newResolvedState, cb)
|
||||
}
|
||||
}
|
||||
+642
@@ -0,0 +1,642 @@
|
||||
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) {
|
||||
const {
|
||||
columns,
|
||||
pivotBy = [],
|
||||
data,
|
||||
pivotIDKey,
|
||||
pivotValKey,
|
||||
subRowsKey,
|
||||
aggregatedKey,
|
||||
nestingLevelKey,
|
||||
originalKey,
|
||||
indexKey,
|
||||
groupedByPivotKey,
|
||||
SubComponent
|
||||
} = newState
|
||||
|
||||
// Determine Header Groups
|
||||
let hasHeaderGroups = false
|
||||
columns.forEach(column => {
|
||||
if (column.columns) {
|
||||
hasHeaderGroups = true
|
||||
}
|
||||
})
|
||||
|
||||
let columnsWithExpander = [...columns]
|
||||
|
||||
let expanderColumn = columns.find(col => col.expander || (col.columns && col.columns.some(col2 => col2.expander)))
|
||||
// The actual expander might be in the columns field of a group column
|
||||
if (expanderColumn && !expanderColumn.expander) {
|
||||
expanderColumn = expanderColumn.columns.find(col => col.expander)
|
||||
}
|
||||
|
||||
// If we have SubComponent's we need to make sure we have an expander column
|
||||
if (SubComponent && !expanderColumn) {
|
||||
expanderColumn = {expander: true}
|
||||
columnsWithExpander = [expanderColumn, ...columnsWithExpander]
|
||||
}
|
||||
|
||||
const makeDecoratedColumn = (column) => {
|
||||
let dcol
|
||||
if (column.expander) {
|
||||
dcol = {
|
||||
...this.props.column,
|
||||
...this.props.expanderDefaults,
|
||||
...column
|
||||
}
|
||||
} else {
|
||||
dcol = {
|
||||
...this.props.column,
|
||||
...column
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof dcol.accessor === 'string') {
|
||||
dcol.id = dcol.id || dcol.accessor
|
||||
const accessorString = dcol.accessor
|
||||
dcol.accessor = row => _.get(row, accessorString)
|
||||
return dcol
|
||||
}
|
||||
|
||||
if (dcol.accessor && !dcol.id) {
|
||||
console.warn(dcol)
|
||||
throw new Error('A column id is required if using a non-string accessor for column above.')
|
||||
}
|
||||
|
||||
if (!dcol.accessor) {
|
||||
dcol.accessor = d => undefined
|
||||
}
|
||||
|
||||
// Ensure minWidth is not greater than maxWidth if set
|
||||
if (dcol.maxWidth < dcol.minWidth) {
|
||||
dcol.minWidth = dcol.maxWidth
|
||||
}
|
||||
|
||||
return dcol
|
||||
}
|
||||
|
||||
// Decorate the columns
|
||||
const decorateAndAddToAll = (col) => {
|
||||
const decoratedColumn = makeDecoratedColumn(col)
|
||||
allDecoratedColumns.push(decoratedColumn)
|
||||
return decoratedColumn
|
||||
}
|
||||
let allDecoratedColumns = []
|
||||
const decoratedColumns = columnsWithExpander.map((column, i) => {
|
||||
if (column.columns) {
|
||||
return {
|
||||
...column,
|
||||
columns: column.columns.map(decorateAndAddToAll)
|
||||
}
|
||||
} else {
|
||||
return decorateAndAddToAll(column)
|
||||
}
|
||||
})
|
||||
|
||||
// Build the visible columns, headers and flat column list
|
||||
let visibleColumns = decoratedColumns.slice()
|
||||
let allVisibleColumns = []
|
||||
|
||||
visibleColumns = visibleColumns.map((column, i) => {
|
||||
if (column.columns) {
|
||||
const visibleSubColumns = column.columns.filter(d => pivotBy.indexOf(d.id) > -1 ? false : _.getFirstDefined(d.show, true))
|
||||
return {
|
||||
...column,
|
||||
columns: visibleSubColumns
|
||||
}
|
||||
}
|
||||
return column
|
||||
})
|
||||
|
||||
visibleColumns = visibleColumns.filter(column => {
|
||||
return column.columns ? column.columns.length : pivotBy.indexOf(column.id) > -1 ? false : _.getFirstDefined(column.show, true)
|
||||
})
|
||||
|
||||
// Find any custom pivot location
|
||||
const pivotIndex = visibleColumns.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)
|
||||
}
|
||||
})
|
||||
|
||||
let pivotColumnGroup = {
|
||||
header: () => <strong>Group</strong>,
|
||||
columns: pivotColumns.map(col => ({
|
||||
...this.props.pivotDefaults,
|
||||
...col,
|
||||
pivoted: true
|
||||
}))
|
||||
}
|
||||
|
||||
// Place the pivotColumns back into the visibleColumns
|
||||
if (pivotIndex >= 0) {
|
||||
pivotColumnGroup = {
|
||||
...visibleColumns[pivotIndex],
|
||||
...pivotColumnGroup
|
||||
}
|
||||
visibleColumns.splice(pivotIndex, 1, pivotColumnGroup)
|
||||
} else {
|
||||
visibleColumns.unshift(pivotColumnGroup)
|
||||
}
|
||||
}
|
||||
|
||||
// Build Header Groups
|
||||
const headerGroups = []
|
||||
let currentSpan = []
|
||||
|
||||
// A convenience function to add a header and reset the currentSpan
|
||||
const addHeader = (columns, column) => {
|
||||
headerGroups.push({
|
||||
...this.props.column,
|
||||
...column,
|
||||
columns: columns
|
||||
})
|
||||
currentSpan = []
|
||||
}
|
||||
|
||||
// Build flast list of allVisibleColumns and HeaderGroups
|
||||
visibleColumns.forEach((column, i) => {
|
||||
if (column.columns) {
|
||||
allVisibleColumns = allVisibleColumns.concat(column.columns)
|
||||
if (currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
addHeader(column.columns, column)
|
||||
return
|
||||
}
|
||||
allVisibleColumns.push(column)
|
||||
currentSpan.push(column)
|
||||
})
|
||||
if (hasHeaderGroups && currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
let resolvedData = data.map((d, i) => accessRow(d, i))
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// TODO: Make it possible to fabricate nested rows without pivoting
|
||||
const aggregatingColumns = allVisibleColumns.filter(d => !d.expander && d.aggregate)
|
||||
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]) => {
|
||||
return {
|
||||
[pivotIDKey]: keys[i],
|
||||
[pivotValKey]: key,
|
||||
[keys[i]]: key,
|
||||
[subRowsKey]: value,
|
||||
[nestingLevelKey]: i,
|
||||
[groupedByPivotKey]: true
|
||||
}
|
||||
})
|
||||
// Recurse into the subRows
|
||||
groupedRows = groupedRows.map(rowGroup => {
|
||||
let subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
|
||||
return {
|
||||
...rowGroup,
|
||||
[subRowsKey]: subRows,
|
||||
[aggregatedKey]: true,
|
||||
...aggregate(subRows)
|
||||
}
|
||||
})
|
||||
return groupedRows
|
||||
}
|
||||
resolvedData = groupRecursively(resolvedData, pivotBy)
|
||||
}
|
||||
|
||||
return {
|
||||
...newState,
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
headerGroups,
|
||||
allDecoratedColumns,
|
||||
hasHeaderGroups
|
||||
}
|
||||
}
|
||||
|
||||
getSortedData (resolvedState) {
|
||||
const {
|
||||
manual,
|
||||
sorted,
|
||||
filtered,
|
||||
defaultFilterMethod,
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
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,
|
||||
allVisibleColumns
|
||||
),
|
||||
sorted,
|
||||
sortMethodsByColumnID
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fireFetchData () {
|
||||
this.props.onFetchData(this.getResolvedState(), 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, allVisibleColumns) {
|
||||
let filteredData = data
|
||||
|
||||
if (filtered.length) {
|
||||
filteredData = filtered.reduce(
|
||||
(filteredSoFar, nextFilter) => {
|
||||
return filteredSoFar.filter(
|
||||
(row) => {
|
||||
let column
|
||||
|
||||
column = allVisibleColumns.find(x => x.id === nextFilter.id || (x.pivotColumns && x.pivotColumns.some(y => y.id === nextFilter.id)))
|
||||
|
||||
// Could possibly be in pivotColumns
|
||||
if (column.id !== nextFilter.id && column.pivotColumns) {
|
||||
column = column.pivotColumns.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 true
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || defaultFilterMethod
|
||||
|
||||
return 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, allVisibleColumns)
|
||||
}
|
||||
}).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) => {
|
||||
return sortMethodsByColumnID[sort.id](a[sort.id], b[sort.id])
|
||||
}
|
||||
}
|
||||
return (a, b) => {
|
||||
return this.props.defaultSortMethod(a[sort.id], b[sort.id])
|
||||
}
|
||||
}),
|
||||
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)
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
sortColumn (column, additive) {
|
||||
const {sorted, skipNextSort} = this.getResolvedState()
|
||||
|
||||
// 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) {
|
||||
if (additive) {
|
||||
newSorted.splice(existingIndex, 1)
|
||||
} else {
|
||||
existing.desc = false
|
||||
newSorted = [existing]
|
||||
}
|
||||
} else {
|
||||
existing.desc = true
|
||||
if (!additive) {
|
||||
newSorted = [existing]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (additive) {
|
||||
newSorted.push({
|
||||
id: column.id,
|
||||
desc: false
|
||||
})
|
||||
} else {
|
||||
newSorted = [{
|
||||
id: column.id,
|
||||
desc: false
|
||||
}]
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
if (additive) {
|
||||
newSorted.splice(existingIndex, column.length)
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorted[existingIndex + i].desc = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorted[existingIndex + i].desc = true
|
||||
})
|
||||
}
|
||||
if (!additive) {
|
||||
newSorted = newSorted.slice(existingIndex, column.length)
|
||||
}
|
||||
} else {
|
||||
// New Sort Column
|
||||
if (additive) {
|
||||
newSorted = newSorted.concat(column.map(d => ({
|
||||
id: d.id,
|
||||
desc: false
|
||||
})))
|
||||
} else {
|
||||
newSorted = column.map(d => ({
|
||||
id: d.id,
|
||||
desc: false
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
page: ((!sorted.length && newSorted.length) || !additive) ? 0 : this.state.page,
|
||||
sorted: newSorted
|
||||
}, () => {
|
||||
onSortedChange && onSortedChange(newSorted, column, additive)
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
filterColumn (column, value) {
|
||||
const {filtered} = this.getResolvedState()
|
||||
const {onFilteredChange} = this.props
|
||||
|
||||
// Remove old filter first if it exists
|
||||
const newFiltering = (filtered || []).filter(x => {
|
||||
if (x.id !== column.id) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if (value !== '') {
|
||||
newFiltering.push({
|
||||
id: column.id,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
filtered: newFiltering
|
||||
}, () => {
|
||||
onFilteredChange && onFilteredChange(newFiltering, column, value)
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
resizeColumnStart (column, event, isTouch) {
|
||||
const parentWidth = event.target.parentElement.getBoundingClientRect().width
|
||||
|
||||
let pageX
|
||||
if (isTouch) {
|
||||
pageX = event.changedTouches[0].pageX
|
||||
} else {
|
||||
pageX = event.pageX
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
currentlyResizing: {
|
||||
id: column.id,
|
||||
startX: pageX,
|
||||
parentWidth: 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
resizeColumnEnd (event) {
|
||||
let 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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resizeColumnMoving (event) {
|
||||
const {onResizedChange} = this.props
|
||||
const {resized, currentlyResizing} = this.getResolvedState()
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Set the min size to 10 to account for margin and border or else the group headers don't line up correctly
|
||||
const newWidth = Math.max(currentlyResizing.parentWidth + pageX - currentlyResizing.startX, 11)
|
||||
|
||||
newResized.push({
|
||||
id: currentlyResizing.id,
|
||||
value: newWidth
|
||||
})
|
||||
|
||||
this.setStateWithData({
|
||||
resized: newResized
|
||||
}, () => {
|
||||
onResizedChange && onResizedChange(newResized, event)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
// import _ from './utils'
|
||||
|
||||
const defaultButton = (props) => (
|
||||
<button type='button' {...props} className='-btn'>{props.children}</button>
|
||||
)
|
||||
|
||||
export default class ReactTablePagination extends Component {
|
||||
constructor (props) {
|
||||
super()
|
||||
|
||||
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.page})
|
||||
}
|
||||
|
||||
getSafePage (page) {
|
||||
if (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})
|
||||
if (this.props.page !== page) {
|
||||
this.props.onPageChange(page)
|
||||
}
|
||||
}
|
||||
|
||||
applyPage (e) {
|
||||
e && e.preventDefault()
|
||||
const page = this.state.page
|
||||
this.changePage(page === '' ? this.props.page : page)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
// Computed
|
||||
pages,
|
||||
// Props
|
||||
page,
|
||||
showPageSizeOptions,
|
||||
pageSizeOptions,
|
||||
pageSize,
|
||||
showPageJump,
|
||||
canPrevious,
|
||||
canNext,
|
||||
onPageSizeChange,
|
||||
className,
|
||||
PreviousComponent = defaultButton,
|
||||
NextComponent = defaultButton
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, '-pagination')}
|
||||
style={this.props.paginationStyle}
|
||||
>
|
||||
<div className='-previous'>
|
||||
<PreviousComponent
|
||||
onClick={(e) => {
|
||||
if (!canPrevious) return
|
||||
this.changePage(page - 1)
|
||||
}}
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
{this.props.previousText}
|
||||
</PreviousComponent>
|
||||
</div>
|
||||
<div className='-center'>
|
||||
<span className='-pageInfo'>
|
||||
{this.props.pageText} {showPageJump ? (
|
||||
<div className='-pageJump'>
|
||||
<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}
|
||||
onKeyPress={e => {
|
||||
if (e.which === 13 || e.keyCode === 13) {
|
||||
this.applyPage()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className='-currentPage'>{page + 1}</span>
|
||||
)} {this.props.ofText} <span className='-totalPages'>{pages || 1}</span>
|
||||
</span>
|
||||
{showPageSizeOptions && (
|
||||
<span className='select-wrap -pageSizeOptions'>
|
||||
<select
|
||||
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
||||
value={pageSize}
|
||||
>
|
||||
{pageSizeOptions.map((option, i) => {
|
||||
return (
|
||||
<option
|
||||
key={i}
|
||||
value={option}>
|
||||
{option} {this.props.rowsText}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='-next'>
|
||||
<NextComponent
|
||||
onClick={(e) => {
|
||||
if (!canNext) return
|
||||
this.changePage(page + 1)
|
||||
}}
|
||||
disabled={!canNext}
|
||||
>
|
||||
{this.props.nextText}
|
||||
</NextComponent>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { getFirstDefined, sum } from './utils'
|
||||
|
||||
const propTypes = {
|
||||
defaultFlex: PropTypes.number,
|
||||
}
|
||||
|
||||
export const actions = {}
|
||||
|
||||
export default function useFlexLayout (api, props = {}) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useFlexLayout')
|
||||
|
||||
const {
|
||||
hooks: {
|
||||
getRowProps, getHeaderRowProps, getHeaderProps, getCellProps,
|
||||
},
|
||||
visibleColumns,
|
||||
} = api
|
||||
|
||||
const { defaultFlex = 1 } = props
|
||||
|
||||
const [columnMeasurements, setColumnMeasurements] = useState({})
|
||||
|
||||
const rowStyles = useMemo(
|
||||
() => {
|
||||
let sumWidth = 0
|
||||
visibleColumns.forEach(column => {
|
||||
const { width, minWidth } = getSizesForColumn(
|
||||
column,
|
||||
defaultFlex,
|
||||
undefined,
|
||||
undefined,
|
||||
api
|
||||
)
|
||||
if (width) {
|
||||
sumWidth += width
|
||||
} else if (minWidth) {
|
||||
sumWidth += minWidth
|
||||
} else {
|
||||
sumWidth += defaultFlex
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
style: {
|
||||
display: 'flex',
|
||||
minWidth: `${sumWidth}px`,
|
||||
},
|
||||
}
|
||||
},
|
||||
[visibleColumns]
|
||||
)
|
||||
|
||||
getRowProps.push(row => rowStyles)
|
||||
getHeaderRowProps.push(row => rowStyles)
|
||||
|
||||
getHeaderProps.push(column => ({
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(column, columnMeasurements, defaultFlex, api),
|
||||
},
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[key] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// },
|
||||
}))
|
||||
|
||||
getCellProps.push(cell => ({
|
||||
style: {
|
||||
display: 'block',
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(cell.column, columnMeasurements, defaultFlex, undefined, api),
|
||||
},
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[columnPathStr] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// }
|
||||
}))
|
||||
|
||||
return {
|
||||
rowStyles,
|
||||
}
|
||||
}
|
||||
|
||||
function getStylesForColumn (column, columnMeasurements, defaultFlex, api) {
|
||||
const { flex, width, maxWidth } = getSizesForColumn(column, columnMeasurements, defaultFlex, api)
|
||||
|
||||
return {
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`,
|
||||
}
|
||||
}
|
||||
|
||||
function getSizesForColumn (
|
||||
{
|
||||
columns, id, width, minWidth, maxWidth,
|
||||
},
|
||||
columnMeasurements,
|
||||
defaultFlex,
|
||||
api
|
||||
) {
|
||||
if (columns) {
|
||||
columns = columns
|
||||
.map(column => getSizesForColumn(column, columnMeasurements, defaultFlex, api))
|
||||
.filter(Boolean)
|
||||
|
||||
if (!columns.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
const flex = sum(columns.map(col => col.flex))
|
||||
const width = sum(columns.map(col => col.width))
|
||||
const maxWidth = sum(columns.map(col => col.maxWidth))
|
||||
|
||||
return {
|
||||
flex,
|
||||
width,
|
||||
maxWidth,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
flex: width ? 0 : defaultFlex,
|
||||
width:
|
||||
width === 'auto'
|
||||
? columnMeasurements[id] || defaultFlex
|
||||
: getFirstDefined(width, minWidth, defaultFlex),
|
||||
maxWidth,
|
||||
}
|
||||
}
|
||||
|
||||
// const resetRefs = () => {
|
||||
// if (debug) console.info("resetRefs");
|
||||
// renderedCellInfoRef.current = {};
|
||||
// };
|
||||
|
||||
// const calculateAutoWidths = () => {
|
||||
// RAF(() => {
|
||||
// const newColumnMeasurements = {};
|
||||
// Object.values(renderedCellInfoRef.current).forEach(({ column, el }) => {
|
||||
// if (!el) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// let measurement = 0;
|
||||
|
||||
// const measureChildren = children => {
|
||||
// if (children) {
|
||||
// [].slice.call(children).forEach(child => {
|
||||
// measurement = Math.max(
|
||||
// measurement,
|
||||
// Math.ceil(child.offsetWidth) || 0
|
||||
// );
|
||||
// measureChildren(child.children);
|
||||
// });
|
||||
// }
|
||||
// return measurement;
|
||||
// };
|
||||
|
||||
// const parentDims = getElementDimensions(el);
|
||||
// measureChildren(el.children);
|
||||
|
||||
// newColumnMeasurements[column.id] = Math.max(
|
||||
// newColumnMeasurements[column.id] || 0,
|
||||
// measurement + parentDims.paddingLeft + parentDims.paddingRight
|
||||
// );
|
||||
// });
|
||||
|
||||
// const oldKeys = Object.keys(columnMeasurements);
|
||||
// const newKeys = Object.keys(newColumnMeasurements);
|
||||
|
||||
// const needsUpdate =
|
||||
// oldKeys.length !== newKeys.length ||
|
||||
// oldKeys.some(key => {
|
||||
// return columnMeasurements[key] !== newColumnMeasurements[key];
|
||||
// });
|
||||
|
||||
// if (needsUpdate) {
|
||||
// setState(old => {
|
||||
// return {
|
||||
// ...old,
|
||||
// columnMeasurements: newColumnMeasurements
|
||||
// };
|
||||
// }, actions.updateAutoWidth);
|
||||
// }
|
||||
// });
|
||||
// };
|
||||
@@ -1,155 +0,0 @@
|
||||
import { useMemo, useState, useLayoutEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { warnUnknownProps } from './utils'
|
||||
import useControlledState from './hooks/useControlledState'
|
||||
|
||||
const propTypes = {
|
||||
defaultPageSize: PropTypes.number,
|
||||
defaultPageIndex: PropTypes.number,
|
||||
pageSize: PropTypes.number,
|
||||
pages: PropTypes.number,
|
||||
pageIndex: PropTypes.number,
|
||||
onStateChange: PropTypes.func,
|
||||
stateReducer: PropTypes.func,
|
||||
subRowsKey: PropTypes.string,
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
pageChange: '__pageChange__',
|
||||
}
|
||||
|
||||
export default function usePagination (
|
||||
{
|
||||
debug: parentDebug, rows, prepareRows, groupedRows, sortedRows, filteredRows,
|
||||
},
|
||||
props = {}
|
||||
) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
|
||||
|
||||
// Destructure props
|
||||
const {
|
||||
defaultPageSize = 10,
|
||||
defaultPageIndex = 0,
|
||||
pageSize: userPageSize,
|
||||
pageIndex: userPageIndex,
|
||||
state: userState,
|
||||
manualPagination,
|
||||
pageCount: userPageCount,
|
||||
pageOptions: userPageOptions,
|
||||
debug = parentDebug,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
warnUnknownProps(rest)
|
||||
|
||||
const defaultState = useState({})
|
||||
const localState = userState || defaultState
|
||||
|
||||
// Build the controllable state
|
||||
const [{ pageSize, pageIndex }, setState] = useControlledState(
|
||||
localState,
|
||||
{
|
||||
pageSize: defaultPageSize,
|
||||
pageIndex: defaultPageIndex,
|
||||
},
|
||||
{
|
||||
pageSize: userPageSize,
|
||||
pageIndex: userPageIndex,
|
||||
}
|
||||
)
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (manualPagination) {
|
||||
return
|
||||
}
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
pageIndex: 0,
|
||||
}),
|
||||
actions.pageChange
|
||||
)
|
||||
},
|
||||
[groupedRows, sortedRows, filteredRows]
|
||||
)
|
||||
|
||||
const { pages, pageCount } = useMemo(
|
||||
() => {
|
||||
if (manualPagination) {
|
||||
return {
|
||||
pages: [rows],
|
||||
pageCount: userPageCount,
|
||||
}
|
||||
}
|
||||
if (debug) console.info('getPages')
|
||||
|
||||
// Create a new pages with the first page ready to go.
|
||||
const pages = rows.length ? [] : [[]]
|
||||
|
||||
// Start the pageIndex and currentPage cursors
|
||||
let cursor = 0
|
||||
while (cursor < rows.length) {
|
||||
const end = cursor + pageSize
|
||||
pages.push(rows.slice(cursor, end))
|
||||
cursor = end
|
||||
}
|
||||
|
||||
const pageCount = pages.length
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageCount,
|
||||
pageOptions,
|
||||
}
|
||||
},
|
||||
[rows, pageSize, userPageCount]
|
||||
)
|
||||
|
||||
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
|
||||
const page = manualPagination ? rows : pages[pageIndex] || []
|
||||
const canPreviousPage = pageIndex > 0
|
||||
const canNextPage = pageIndex < pageCount - 1
|
||||
|
||||
const gotoPage = pageIndex => {
|
||||
if (debug) console.info('gotoPage')
|
||||
return setState(old => {
|
||||
if (pageIndex < 0 || pageIndex > pageCount - 1) {
|
||||
return old
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
pageIndex,
|
||||
}
|
||||
}, actions.pageChange)
|
||||
}
|
||||
|
||||
const previousPage = () => gotoPage(pageIndex - 1)
|
||||
|
||||
const nextPage = () => gotoPage(pageIndex + 1)
|
||||
|
||||
const setPageSize = pageSize => {
|
||||
setState(old => ({
|
||||
...old,
|
||||
pageSize,
|
||||
}))
|
||||
}
|
||||
|
||||
prepareRows(page)
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageIndex,
|
||||
pageOptions,
|
||||
page,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize,
|
||||
}
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
import { useState, useMemo, useRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
//
|
||||
import {
|
||||
defaultOrderByFn,
|
||||
defaultSortByFn,
|
||||
defaultGroupByFn,
|
||||
defaultFilterFn,
|
||||
flexRender,
|
||||
findExpandedDepth,
|
||||
applyHooks,
|
||||
mergeProps,
|
||||
warnUnknownProps,
|
||||
} from './utils'
|
||||
|
||||
// Internal Hooks
|
||||
import useControlledState from './hooks/useControlledState'
|
||||
import useColumns from './hooks/useColumns'
|
||||
import useTableMethods from './hooks/useTableMethods'
|
||||
import useAccessedRows from './hooks/useAccessedRows'
|
||||
import useGroupedRows from './hooks/useGroupedRows'
|
||||
import useFilteredRows from './hooks/useFilteredRows'
|
||||
import useSortedRows from './hooks/useSortedRows'
|
||||
import useExpandedRows from './hooks/useExpandedRows'
|
||||
|
||||
const defaultFlex = 1
|
||||
const renderErr =
|
||||
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
|
||||
|
||||
export const actions = {
|
||||
sortByChange: '__sortByChange',
|
||||
updateAutoWidth: '__updateAutoWidth__',
|
||||
toggleGroupBy: '__toggleGroupBy__',
|
||||
toggleExpanded: '__toggleExpanded__',
|
||||
setExpanded: '__setExpanded__',
|
||||
setFilter: '__setFilter__',
|
||||
setAllFilters: '__setAllFilters__',
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
data: PropTypes.any,
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
aggregate: PropTypes.func,
|
||||
filterFn: PropTypes.func,
|
||||
filterAll: PropTypes.bool,
|
||||
sortByFn: PropTypes.func,
|
||||
resolvedDefaultSortDesc: PropTypes.bool,
|
||||
canSortBy: PropTypes.bool,
|
||||
canGroupBy: PropTypes.bool,
|
||||
Cell: PropTypes.any,
|
||||
Header: PropTypes.any,
|
||||
Filter: PropTypes.any,
|
||||
})
|
||||
),
|
||||
defaultFilters: PropTypes.array,
|
||||
defaultSortBy: PropTypes.array,
|
||||
defaultGroupBy: PropTypes.array,
|
||||
|
||||
groupBy: PropTypes.array,
|
||||
filters: PropTypes.array,
|
||||
sortBy: PropTypes.array,
|
||||
|
||||
filterFn: PropTypes.func,
|
||||
sortByFn: PropTypes.func,
|
||||
orderByFn: PropTypes.func,
|
||||
groupByFn: PropTypes.func,
|
||||
|
||||
manualGrouping: PropTypes.bool,
|
||||
manualFilters: PropTypes.bool,
|
||||
manualSorting: PropTypes.bool,
|
||||
|
||||
disableGrouping: PropTypes.bool,
|
||||
disableFilters: PropTypes.bool,
|
||||
disableSorting: PropTypes.bool,
|
||||
|
||||
defaultSortDesc: PropTypes.bool,
|
||||
disableMultiSort: PropTypes.bool,
|
||||
subRowsKey: PropTypes.string,
|
||||
expandedKey: PropTypes.string,
|
||||
userAggregations: PropTypes.object,
|
||||
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default function useReactTable (props) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useReactTable')
|
||||
|
||||
// Destructure props
|
||||
const {
|
||||
data = [],
|
||||
state: userState,
|
||||
columns: userColumns,
|
||||
sortBy: userSortBy,
|
||||
groupBy: userGroupBy,
|
||||
filters: userFilters,
|
||||
expanded: userExpanded,
|
||||
defaultSortBy = [],
|
||||
defaultGroupBy = [],
|
||||
defaultFilters = {},
|
||||
defaultExpanded = {},
|
||||
manualGrouping,
|
||||
manualFilters,
|
||||
manualSorting,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
defaultSortDesc,
|
||||
disableMultiSort,
|
||||
subRowsKey = 'subRows',
|
||||
expandedKey = 'expanded',
|
||||
filterFn = defaultFilterFn,
|
||||
sortByFn = defaultSortByFn,
|
||||
orderByFn = defaultOrderByFn,
|
||||
groupByFn = defaultGroupByFn,
|
||||
userAggregations = {},
|
||||
debug,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
warnUnknownProps(rest)
|
||||
|
||||
const defaultState = useState({})
|
||||
|
||||
const localState = userState || defaultState
|
||||
|
||||
// Build the controllable state
|
||||
const [{
|
||||
sortBy, groupBy, filters, expanded,
|
||||
}, setState] = useControlledState(
|
||||
localState,
|
||||
{
|
||||
sortBy: defaultSortBy,
|
||||
groupBy: defaultGroupBy,
|
||||
filters: defaultFilters,
|
||||
expanded: defaultExpanded,
|
||||
},
|
||||
{
|
||||
sortBy: userSortBy,
|
||||
groupBy: userGroupBy,
|
||||
filters: userFilters,
|
||||
expanded: userExpanded,
|
||||
}
|
||||
)
|
||||
|
||||
const expandedDepth = findExpandedDepth(expanded)
|
||||
const renderedCellInfoRef = useRef({})
|
||||
|
||||
// Build the base column
|
||||
let { columns, headerGroups, headers } = useColumns({
|
||||
debug,
|
||||
groupBy,
|
||||
userColumns,
|
||||
defaultFlex,
|
||||
renderedCellInfoRef,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
})
|
||||
|
||||
// Use data and columns as memoization
|
||||
const accessedRows = useAccessedRows({
|
||||
debug,
|
||||
data,
|
||||
columns,
|
||||
subRowsKey,
|
||||
})
|
||||
|
||||
// Use rows and groupBy as memoization for row grouping
|
||||
const groupedRows = useGroupedRows({
|
||||
debug,
|
||||
rows: accessedRows,
|
||||
groupBy,
|
||||
columns,
|
||||
groupByFn,
|
||||
manualGrouping,
|
||||
userAggregations,
|
||||
})
|
||||
|
||||
const filteredRows = useFilteredRows({
|
||||
debug,
|
||||
rows: groupedRows,
|
||||
filters,
|
||||
columns,
|
||||
filterFn,
|
||||
manualFilters,
|
||||
})
|
||||
|
||||
const sortedRows = useSortedRows({
|
||||
debug,
|
||||
rows: filteredRows,
|
||||
sortBy,
|
||||
columns,
|
||||
orderByFn,
|
||||
sortByFn,
|
||||
manualSorting,
|
||||
})
|
||||
|
||||
const rows = useExpandedRows({
|
||||
debug,
|
||||
rows: sortedRows,
|
||||
expanded,
|
||||
expandedKey,
|
||||
columns,
|
||||
groupBy,
|
||||
setState,
|
||||
actions,
|
||||
})
|
||||
|
||||
// Mutate columns to reflect sorting state
|
||||
columns.forEach(column => {
|
||||
const { id } = column
|
||||
column.sorted = sortBy.find(d => d.id === id)
|
||||
column.sortedIndex = sortBy.findIndex(d => d.id === id)
|
||||
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
|
||||
column.filterVal = filters[id]
|
||||
})
|
||||
|
||||
// Public API
|
||||
|
||||
const {
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
} = useTableMethods({
|
||||
debug,
|
||||
setState,
|
||||
actions,
|
||||
groupBy,
|
||||
columns,
|
||||
defaultSortDesc,
|
||||
filters,
|
||||
})
|
||||
|
||||
const state = {
|
||||
// State
|
||||
columns,
|
||||
expandedDepth,
|
||||
accessedRows,
|
||||
groupedRows,
|
||||
filteredRows,
|
||||
sortedRows,
|
||||
rows,
|
||||
headerGroups,
|
||||
renderedCellInfoRef,
|
||||
disableMultiSort,
|
||||
|
||||
// Controllable state
|
||||
groupBy,
|
||||
filters,
|
||||
sortBy,
|
||||
expanded,
|
||||
}
|
||||
|
||||
const api = {
|
||||
// State manager,
|
||||
...state,
|
||||
|
||||
// Methods
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
}
|
||||
|
||||
columns.forEach(column => attachApi(column, api))
|
||||
headers.forEach(header => attachApi(header, api))
|
||||
|
||||
const visibleColumns = columns.filter(column => {
|
||||
column.visible = typeof column.show === 'function' ? column.show(state) : !!column.show
|
||||
return column.visible
|
||||
})
|
||||
|
||||
state.visibleColumns = visibleColumns
|
||||
api.visibleColumns = visibleColumns
|
||||
|
||||
api.hooks = {
|
||||
getTableProps: [],
|
||||
getRowProps: [],
|
||||
getHeaderRowProps: [],
|
||||
getCellProps: [],
|
||||
getHeaderProps: [],
|
||||
getSortByToggleProps: [],
|
||||
getGroupByToggleProps: [],
|
||||
}
|
||||
|
||||
api.getTableProps = props => mergeProps(
|
||||
{
|
||||
style: {
|
||||
overflowX: 'auto',
|
||||
},
|
||||
},
|
||||
applyHooks(api.hooks.getTableProps),
|
||||
props
|
||||
)
|
||||
|
||||
api.getRowProps = props => mergeProps(applyHooks(api.hooks.getRowProps), props)
|
||||
|
||||
// Filter out hidden header groups or header groups that
|
||||
// have no visible columns, then add the getRowProps method
|
||||
headerGroups = useMemo(
|
||||
() =>
|
||||
headerGroups.filter((headerGroup, i) => {
|
||||
headerGroup.headers = headerGroup.headers.filter(header => {
|
||||
const recurse = columns => columns.filter(column => {
|
||||
if (column.columns) {
|
||||
return recurse(column.columns)
|
||||
}
|
||||
return column.visible
|
||||
}).length
|
||||
if (header.columns) {
|
||||
return recurse(header.columns)
|
||||
}
|
||||
return header.visible
|
||||
})
|
||||
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getRowProps = (props = {}) => mergeProps(
|
||||
{
|
||||
key: [`header${i}`].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getHeaderRowProps, headerGroup),
|
||||
props
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
[headerGroups]
|
||||
)
|
||||
|
||||
// A function that mutates and prepares page rows with methods
|
||||
api.prepareRows = rows => {
|
||||
rows.forEach((row, i) => {
|
||||
const { path } = row
|
||||
row.getRowProps = props => mergeProps(
|
||||
{ key: ['row', i].join('_') },
|
||||
applyHooks(api.hooks.getRowProps, row),
|
||||
props
|
||||
)
|
||||
|
||||
row.toggleExpanded = set => toggleExpandedByPath(path, set)
|
||||
|
||||
row.cells = row.cells.filter(cell => cell.column.visible);
|
||||
[row.groupByCell, ...row.cells].forEach(cell => {
|
||||
if (!cell) {
|
||||
return
|
||||
}
|
||||
|
||||
const { column } = cell
|
||||
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [i, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getCellProps, cell),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
cell.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(
|
||||
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
|
||||
)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const makeHook = api => {
|
||||
api.hook = (hook, ...args) => {
|
||||
const result = hook(api, ...args) || {}
|
||||
const newApi = {
|
||||
...api,
|
||||
...result,
|
||||
}
|
||||
makeHook(newApi)
|
||||
return newApi
|
||||
}
|
||||
}
|
||||
|
||||
makeHook(api)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
function attachApi (column, api) {
|
||||
const {
|
||||
id, canSortBy, canGroupBy, canFilter,
|
||||
} = column
|
||||
const { toggleSortByID, toggleGroupBy, setFilter } = api
|
||||
|
||||
column.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...column,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
|
||||
if (canSortBy) {
|
||||
column.toggleSortBy = (desc, multi) => toggleSortByID(id, desc, multi)
|
||||
}
|
||||
if (canGroupBy) {
|
||||
column.toggleGroupBy = () => toggleGroupBy(id)
|
||||
}
|
||||
if (canFilter) {
|
||||
column.setFilter = val => setFilter(id, val)
|
||||
}
|
||||
|
||||
column.getSortByToggleProps = props => mergeProps(
|
||||
{
|
||||
onClick: canSortBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleSortBy(undefined, !api.disableMultiSort && e.shiftKey)
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canSortBy ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle SortBy',
|
||||
},
|
||||
applyHooks(api.hooks.getSortByToggleProps, column),
|
||||
props
|
||||
)
|
||||
|
||||
column.getGroupByToggleProps = props => mergeProps(
|
||||
{
|
||||
onClick: canGroupBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleGroupBy()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canGroupBy ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle GroupBy',
|
||||
},
|
||||
applyHooks(api.hooks.getGroupByToggleProps, column),
|
||||
props
|
||||
)
|
||||
|
||||
column.getHeaderProps = props => mergeProps(
|
||||
{
|
||||
key: ['header', column.id].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getHeaderProps, column),
|
||||
props
|
||||
)
|
||||
}
|
||||
Executable → Regular
+152
-131
@@ -1,178 +1,199 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
export default {
|
||||
get,
|
||||
set,
|
||||
takeRight,
|
||||
last,
|
||||
orderBy,
|
||||
range,
|
||||
remove,
|
||||
clone,
|
||||
getFirstDefined,
|
||||
sum,
|
||||
makeTemplateComponent,
|
||||
groupBy,
|
||||
isArray,
|
||||
splitProps,
|
||||
compactObject,
|
||||
isSortingDesc,
|
||||
normalizeComponent
|
||||
}
|
||||
|
||||
export function getBy (obj, path, def) {
|
||||
function get (obj, path, def) {
|
||||
if (!path) {
|
||||
return obj
|
||||
}
|
||||
const pathObj = makePathArray(path)
|
||||
let val
|
||||
try {
|
||||
val = pathObj.reduce((cursor, pathPart) => cursor[pathPart], obj)
|
||||
} catch (e) {
|
||||
// continue regardless of error
|
||||
}
|
||||
val = pathObj.reduce((current, pathPart) => current[pathPart], obj)
|
||||
} catch (e) {}
|
||||
return typeof val !== 'undefined' ? val : def
|
||||
}
|
||||
|
||||
export function defaultOrderByFn (arr, funcs, dirs) {
|
||||
return [...arr].sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i += 1) {
|
||||
const sortFn = funcs[i]
|
||||
function set (obj = {}, path, value) {
|
||||
const keys = makePathArray(path)
|
||||
let keyPart
|
||||
let cursor = obj
|
||||
while ((keyPart = keys.shift()) && keys.length) {
|
||||
if (!cursor[keyPart]) {
|
||||
cursor[keyPart] = {}
|
||||
}
|
||||
cursor = cursor[keyPart]
|
||||
}
|
||||
cursor[keyPart] = value
|
||||
return obj
|
||||
}
|
||||
|
||||
function takeRight (arr, n) {
|
||||
const start = n > arr.length ? 0 : arr.length - n
|
||||
return arr.slice(start)
|
||||
}
|
||||
|
||||
function last (arr) {
|
||||
return arr[arr.length - 1]
|
||||
}
|
||||
|
||||
function range (n) {
|
||||
const arr = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
arr.push(n)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function orderBy (arr, funcs, dirs, indexKey) {
|
||||
return arr.sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i++) {
|
||||
const comp = funcs[i]
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
const sortInt = sortFn(rowA, rowB)
|
||||
if (sortInt !== 0) {
|
||||
const sortInt = comp(rowA, rowB)
|
||||
if (sortInt) {
|
||||
return desc ? -sortInt : sortInt
|
||||
}
|
||||
}
|
||||
return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index
|
||||
// Use the row index for tie breakers
|
||||
return dirs[0]
|
||||
? rowA[indexKey] - rowB[indexKey]
|
||||
: rowB[indexKey] - rowA[indexKey]
|
||||
})
|
||||
}
|
||||
|
||||
export function defaultSortByFn (a, b, desc) {
|
||||
// force null and undefined to the bottom
|
||||
a = a === null || a === undefined ? '' : a
|
||||
b = b === null || b === undefined ? '' : b
|
||||
// force any string values to lowercase
|
||||
a = typeof a === 'string' ? a.toLowerCase() : a
|
||||
b = typeof b === 'string' ? b.toLowerCase() : b
|
||||
// Return either 1 or -1 to indicate a sort priority
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
// returning 0, undefined or any falsey value will defer to the next
|
||||
// sorting mechanism or eventually the columns index via the orderByFn
|
||||
return 0
|
||||
function remove (a, b) {
|
||||
return a.filter(function (o, i) {
|
||||
var r = b(o)
|
||||
if (r) {
|
||||
a.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
export function getFirstDefined (...args) {
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
function clone (a) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(a, (key, value) => {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
}))
|
||||
} catch (e) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
function getFirstDefined (...args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (typeof args[i] !== 'undefined') {
|
||||
return args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultGroupByFn (rows, grouper) {
|
||||
return rows.reduce((prev, row, i) => {
|
||||
const resKey = typeof grouper === 'function' ? grouper(row.values, i) : row.values[grouper]
|
||||
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
|
||||
prev[resKey].push(row)
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function defaultFilterFn (row, id, value, column) {
|
||||
return row.values[id] !== undefined
|
||||
? String(row.values[id])
|
||||
.toLowerCase()
|
||||
.includes(String(value).toLowerCase())
|
||||
: true
|
||||
}
|
||||
|
||||
export function setBy (obj = {}, path, value) {
|
||||
const recurse = (obj, depth = 0) => {
|
||||
const key = path[depth]
|
||||
const target = typeof obj[key] !== 'object' ? {} : obj[key]
|
||||
const subValue = depth === path.length - 1 ? value : recurse(target, depth + 1)
|
||||
return {
|
||||
...obj,
|
||||
[key]: subValue,
|
||||
}
|
||||
}
|
||||
|
||||
return recurse(obj)
|
||||
}
|
||||
|
||||
export function getElementDimensions (element) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const style = window.getComputedStyle(element)
|
||||
const margins = {
|
||||
left: parseInt(style.marginLeft),
|
||||
right: parseInt(style.marginRight),
|
||||
}
|
||||
const padding = {
|
||||
left: parseInt(style.paddingLeft),
|
||||
right: parseInt(style.paddingRight),
|
||||
}
|
||||
return {
|
||||
left: Math.ceil(rect.left),
|
||||
width: Math.ceil(rect.width),
|
||||
outerWidth: Math.ceil(rect.width + margins.left + margins.right + padding.left + padding.right),
|
||||
marginLeft: margins.left,
|
||||
marginRight: margins.right,
|
||||
paddingLeft: padding.left,
|
||||
paddingRight: padding.right,
|
||||
scrollWidth: element.scrollWidth,
|
||||
}
|
||||
}
|
||||
|
||||
export function flexRender (Comp, props) {
|
||||
if (typeof Comp === 'function') {
|
||||
return Object.getPrototypeOf(Comp).isReactComponent ? <Comp {...props} /> : Comp(props)
|
||||
}
|
||||
return Comp
|
||||
}
|
||||
|
||||
export function findExpandedDepth (obj, depth = 1) {
|
||||
return Object.values(obj).reduce((prev, curr) => {
|
||||
if (typeof curr === 'object') {
|
||||
return Math.max(prev, findExpandedDepth(curr, depth + 1))
|
||||
}
|
||||
return depth
|
||||
function sum (arr) {
|
||||
return arr.reduce((a, b) => {
|
||||
return a + b
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export const mergeProps = (...groups) => {
|
||||
let props = {}
|
||||
groups.forEach(({ style = {}, className, ...rest } = {}) => {
|
||||
props = {
|
||||
...props,
|
||||
...rest,
|
||||
style: {
|
||||
...(props.style || {}),
|
||||
...style,
|
||||
},
|
||||
className: [props.className, className].filter(Boolean).join(' '),
|
||||
}
|
||||
})
|
||||
return props
|
||||
function makeTemplateComponent (compClass) {
|
||||
return ({children, className, ...rest}) => (
|
||||
<div
|
||||
className={classnames(compClass, className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const applyHooks = (hooks, ...args) =>
|
||||
hooks.reduce((prev, next) => mergeProps(prev, next(...args)), {})
|
||||
|
||||
export const warnUnknownProps = props => {
|
||||
if (Object.keys(props).length) {
|
||||
throw new Error(
|
||||
`Unknown options passed to useReactTable:
|
||||
|
||||
${JSON.stringify(props, null, 2)}`
|
||||
)
|
||||
}
|
||||
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
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function sum (arr) {
|
||||
return arr.reduce((prev, curr) => prev + curr, 0)
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Non-exported Helpers
|
||||
// ########################################################################
|
||||
|
||||
function makePathArray (obj) {
|
||||
return flattenDeep(obj)
|
||||
.join('.')
|
||||
.replace(/\[/g, '.')
|
||||
.replace(/\]/g, '')
|
||||
.split('.')
|
||||
.join('.')
|
||||
.replace('[', '.')
|
||||
.replace(']', '')
|
||||
.split('.')
|
||||
}
|
||||
|
||||
function flattenDeep (arr, newArr = []) {
|
||||
if (!Array.isArray(arr)) {
|
||||
if (!isArray(arr)) {
|
||||
newArr.push(arr)
|
||||
} else {
|
||||
for (let i = 0; i < arr.length; i += 1) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
flattenDeep(arr[i], newArr)
|
||||
}
|
||||
}
|
||||
return newArr
|
||||
}
|
||||
|
||||
function splitProps ({className, style, ...rest}) {
|
||||
return {
|
||||
className,
|
||||
style,
|
||||
rest
|
||||
}
|
||||
}
|
||||
|
||||
function compactObject (obj) {
|
||||
const newObj = {}
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key) && obj[key] !== undefined && typeof obj[key] !== 'undefined') {
|
||||
newObj[key] = obj[key]
|
||||
}
|
||||
}
|
||||
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' ? (
|
||||
Object.getPrototypeOf(Comp).isReactComponent ? (
|
||||
<Comp
|
||||
{...params}
|
||||
/>
|
||||
) : Comp(params)
|
||||
) : fallback
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const webpack = require('webpack')
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
filename: './react-table.js',
|
||||
libraryTarget: 'umd',
|
||||
library: 'ReactTable'
|
||||
},
|
||||
externals: {
|
||||
react: {
|
||||
root: 'React',
|
||||
commonjs2: 'react',
|
||||
commonjs: 'react',
|
||||
amd: 'react'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin()
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user