Init v7 src

This commit is contained in:
Tanner Linsley 2019-01-31 12:54:56 -07:00
parent a99ecbd567
commit 2a6dfb6ebd
61 changed files with 1640 additions and 20996 deletions

View File

@ -1,77 +0,0 @@
module.exports = {
parserOptions: {
ecmaVersion: 8,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
node: false,
classes: true
},
sourceType: 'module'
},
parser: 'babel-eslint',
extends: ['standard'],
plugins: ['react'],
rules: {
// Nozzle
'jsx-quotes': [2, 'prefer-single'],
'comma-dangle': [2, 'always-multiline'],
// // React
'react/jsx-boolean-value': 2,
'react/jsx-curly-spacing': [2, 'never'],
'react/jsx-equals-spacing': [2, 'never'],
// 'react/jsx-indent': 2,
'react/jsx-indent-props': [2, 2],
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-undef': 2,
'react/jsx-tag-spacing': [
2,
{
closingSlash: 'never',
beforeSelfClosing: 'always',
afterOpening: 'never'
}
],
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/self-closing-comp': 2,
'react/jsx-no-bind': [
2,
{
allowArrowFunctions: true,
allowBind: false,
ignoreRefs: true
}
],
'react/no-did-update-set-state': 2,
'react/no-unknown-property': 2,
'react/react-in-jsx-scope': 2,
'react/jsx-closing-bracket-location': [0, 'tag-aligned'],
'react/jsx-tag-spacing': [2, { beforeSelfClosing: 'always' }],
'react/jsx-wrap-multilines': 2,
'react/self-closing-comp': 2,
'react/jsx-key': 2,
'react/jsx-no-comment-textnodes': 2,
'react/jsx-no-duplicate-props': 2,
'react/jsx-no-target-blank': 2,
'react/jsx-no-undef': 2,
'react/jsx-uses-react': 2,
'react/jsx-uses-vars': 2,
'react/no-danger-with-children': 2,
'react/no-deprecated': 2,
'react/no-direct-mutation-state': 2,
'react/no-find-dom-node': 2,
'react/no-is-mounted': 2,
'react/no-render-return-value': 2,
'react/no-string-refs': 2,
'react/no-unknown-property': 2,
'react/react-in-jsx-scope': 2,
'react/require-render-return': 2
// 'react/jsx-max-props-per-line': [2, { maximum: 1 }]
}
}

View File

@ -1 +0,0 @@
react-table.js.org

File diff suppressed because it is too large Load Diff

View File

@ -1,53 +0,0 @@
{
"name": "new",
"version": "0.1.0",
"private": true,
"devDependencies": {
"autoprefixer": "^6.7.0",
"babel-cli": "6.14.0",
"babel-eslint": "6.1.2",
"babel-preset-es2015": "6.14.0",
"babel-preset-react": "6.11.1",
"babel-preset-stage-2": "6.13.0",
"eslint": "^4.1.1",
"eslint-config-standard": "^10.2.1",
"eslint-plugin-class-property": "^1.0.6",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.4.0",
"eslint-plugin-standard": "^3.0.1",
"html-element-attributes": "^1.3.0",
"match-sorter": "^1.8.0",
"npm-run-all": "^3.1.1",
"onchange": "^3.0.2",
"postcss-cli": "^2.6.0",
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-json-tree": "^0.10.9",
"rimraf": "^2.6.1",
"standard": "^10.0.2",
"stylus": "^0.54.5"
},
"dependencies": {
"eslint-config-react-tools": "^1.0.10",
"github-markdown-css": "^2.6.0",
"marked": "^0.3.6",
"namor": "^1.0.1",
"raw-loader": "^0.5.1",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-json-tree": "^0.10.9",
"react-script": "^2.0.5",
"react-scripts": "^0.9.5",
"react-story": "^0.0.10",
"shortid": "^2.2.8"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,31 +0,0 @@
<!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>

View File

@ -1,120 +0,0 @@
/* eslint-disable */
import React from 'react';
//
import ReactStory, { defaultProps } from 'react-story';
import CodeSandbox from './CodeSandbox.js';
import './stories/utils/prism.css';
import '../../react-table.css';
import Readme from './stories/Readme.js';
import HOCReadme from './stories/HOCReadme.js';
// import Test from './stories/test.js'
// import Tester from './examples/expander';
const stories = [
{ name: 'Readme', component: Readme },
{ name: 'HOC Readme', component: HOCReadme },
// { name: 'Tester', component: Test },
{ name: 'Simple Table', component: CodeSandbox('X6npLXPRW') },
{
name: 'Cell Renderers & Custom Components',
component: CodeSandbox('OyRL04Z4Y')
},
{ name: 'Default Sorting', component: CodeSandbox('gLwmmjzA3') },
{
name: 'Custom Sorting',
component: CodeSandbox('VGx67J35')
},
{ name: 'Custom Column Widths', component: CodeSandbox('o2OORXNXN') },
{ name: 'Custom Component Props', component: CodeSandbox('nZW3L0wp4') },
{ name: 'Server-side Data', component: CodeSandbox('wjrn8wy3R') },
{ name: 'Sub Components', component: CodeSandbox('n2gqAxl7') },
{ name: 'Pivoting & Aggregation', component: CodeSandbox('oNY9z8xN') },
{
name: 'Pivoting & Aggregation w/ Sub Components',
component: CodeSandbox('p0kEVBgQ')
},
{
name: '100k Rows w/ Pivoting & Sub Components',
component: CodeSandbox('DRmKj0XyK')
},
{ name: 'Pivoting Options', component: CodeSandbox('kZKmNBK6r') },
{ name: 'Functional Rendering', component: CodeSandbox('VPZ0Bzv8X') },
{
name: 'Custom Expander Position',
component: CodeSandbox('1jj2XrPEV')
},
{ name: 'Custom "No Data" Text', component: CodeSandbox('RgRpRDv80') },
{ name: 'Footers', component: CodeSandbox('KOqQXn3p8') },
{ name: 'Custom Filtering', component: CodeSandbox('5Eyxxxyx') },
{ name: 'Controlled Component', component: CodeSandbox('r7XEZRK2') },
{ name: 'Editable Table', component: CodeSandbox('n5r19gzQP') },
{
name: 'Fixed Header w/ Vertical Scroll',
component: CodeSandbox('7LY0gjA8O')
},
{
name: 'Multiple Pagers (Top and Bottom)',
component: CodeSandbox('VEZ8OgvX')
},
{ name: 'Tree Table (HOC)', component: CodeSandbox('lxmr4wynzq') },
{ name: 'Select Table (HOC)', component: CodeSandbox('7yq5ylw09j') },
{ name: 'Select Tree Table (HOC)', component: CodeSandbox('2p7jp4klwp') },
{ name: 'Foldable Table (HOC)', component: CodeSandbox('8pkrj5yorl') },
{ name: 'Advanced Expand Table (HOC)', component: CodeSandbox('y2m39jz8v1') }
];
export default class App extends React.Component {
render() {
return (
<ReactStory
style={{
display: 'block',
width: '100%',
height: '100%'
}}
pathPrefix="story/"
StoryWrapper={props => (
<defaultProps.StoryWrapper
css={{
padding: 0,
display: 'flex',
flexDirection: 'column'
}}
>
<a
href="//github.com/react-tools/react-table"
style={{
display: 'block',
textAlign: 'center',
borderBottom: 'solid 3px #cccccc'
}}
>
<img
src="https://github.com/react-tools/media/raw/master/logo-react-table.png"
alt="React Table Logo"
style={{
width: '150px',
padding: '10px'
}}
/>
</a>
<div
{...props}
style={{
flex: '1 0 auto',
position: 'relative'
}}
/>
</defaultProps.StoryWrapper>
)}
stories={stories}
/>
);
}
}

View File

@ -1,21 +0,0 @@
import React from "react";
export default id => () => {
return (
<iframe
src={`https://codesandbox.io/embed/${id}?autoresize=1&hidenavigation=1&view=${
global.innerWidth < 1000 ? "preview" : "split"
}`}
style={{
position: "absolute",
top: 0,
left: 0,
width: `100%`,
height: `100%`,
border: 0,
overflow: `hidden`
}}
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
/>
);
};

View File

@ -1,116 +0,0 @@
import React from 'react';
import { render } from 'react-dom';
import { makeData, Logo, Tips } from './Utils';
import { advancedExpandTableHOC } from '../../../../lib/hoc/advancedExpandTable';
// Import React Table
import ReactTable from 'react-table';
import 'react-table/react-table.css';
const columns = [
{
Header: 'First Name',
accessor: 'firstName',
Cell: props => {
const {
// react table props
columnProps: { rest: { showRowSubComponent } },
nestingPath
} = props;
return (
<div>
<button
onClick={e => showRowSubComponent({ nestingPath: nestingPath }, e)}
>
SHOW SUBCOMPONENT {props.value}
</button>
</div>
);
}
},
{
Header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
Cell: props => {
const {
// react table props
columnProps: { rest: { toggleRowSubComponent } },
nestingPath
} = props;
return (
<div>
<button
onClick={e =>
toggleRowSubComponent({ nestingPath: nestingPath }, e)
}
>
TOGGLE SUBCOMPONENT {props.value}
</button>
</div>
);
}
},
{
Header: 'Status',
accessor: 'status'
}
]
},
{
Header: 'Stats',
columns: [
{
Header: 'Visits',
accessor: 'visits'
}
]
}
];
const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
data: makeData()
};
}
render() {
const { data } = this.state;
return (
<div>
<AdvancedExpandReactTable
data={data}
columns={columns}
defaultPageSize={10}
className='-striped -highlight'
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => {
return (
<div style={{ padding: '20px' }}>
<button
onClick={e => toggleRowSubComponent({ nestingPath }, e)}
>
CLOSE SUBCOMPONENT {row.firstName} {row.lastName}
</button>
</div>
);
}}
/>
<br />
<Tips />
<Logo />
</div>
);
}
}
render(<App />, document.getElementById('root'));

View File

@ -1,94 +0,0 @@
import React from "react";
export default Component => {
const wrapper = class RTCheckboxTable extends React.Component {
// we only need a Component so we can get the 'ref' - pure components can't get a 'ref'
rowSelector = row => {
if (!row || !row.hasOwnProperty(this.props.keyField)) return null;
const checked = this.props.isSelected(row[this.props.keyField]);
return (
<input
type="checkbox"
checked={checked}
onClick={e => {
const { shiftKey } = e;
e.stopPropagation();
this.props.toggleSelection(row[this.props.keyField], shiftKey, row);
}}
onChange={() => {}}
value=""
/>
);
};
headSelector = row => {
const checked = this.props.selectAll;
return (
<input
type="checkbox"
checked={checked}
onClick={e => {
e.stopPropagation();
this.props.toggleAll();
}}
onChange={() => {}}
value=""
/>
);
};
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
getWrappedInstance = () => this.wrappedInstance;
render() {
const {
columns: originalCols,
isSelected,
toggleSelection,
toggleAll,
keyField,
selectAll,
...rest
} = this.props;
const { rowSelector, headSelector } = this;
const select = {
id: "_selector",
accessor: () => "x", // this value is not important
Header: headSelector,
Cell: ci => {
return rowSelector(ci.original);
},
width: 30,
filterable: false,
sortable: false,
resizable: false,
style: { textAlign: "center" }
};
const columns = [select, ...originalCols];
const extra = {
columns
};
return (
<Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
);
}
};
wrapper.displayName = "RTCheckboxTable";
wrapper.defaultProps = {
keyField: "_id",
isSelected: key => {
console.log("No isSelected handler provided:", { key });
},
selectAll: false,
toggleSelection: (key, shift, row) => {
console.log("No toggleSelection handler provided:", { key, shift, row });
},
toggleAll: () => {
console.log("No toggleAll handler provided.");
}
};
return wrapper;
};

View File

@ -1,148 +0,0 @@
import React from "react";
import shortid from "shortid";
import ReactTable from "../../../../lib/index";
import "../../../../react-table.css";
import checkboxTableHOC from "./checkboxHOC";
const CheckboxTable = checkboxTableHOC(ReactTable);
async function getData() {
const result = await (await fetch("/au_500_tree.json")).json();
// we are adding a unique ID to the data for tracking the selected records
return result.map(item => {
const _id = shortid.generate();
return {
_id,
...item
};
});
}
function getColumns(data) {
const columns = [];
const sample = data[0];
for (let key in sample) {
if (key === "_id") continue;
columns.push({
accessor: key,
Header: key
});
}
return columns;
}
export class ComponentTest extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
columns: null,
selection: [],
selectAll: false
};
}
componentDidMount() {
getData().then(data => {
const columns = getColumns(data);
this.setState({ data, columns });
});
}
toggleSelection = (key, shift, row) => {
/*
Implementation of how to manage the selection state is up to the developer.
This implementation uses an array stored in the component state.
Other implementations could use object keys, a Javascript Set, or Redux... etc.
*/
// start off with the existing state
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({ selection });
};
toggleAll = () => {
/*
'toggleAll' is a tricky concept with any filterable table
do you just select ALL the records that are in your data?
OR
do you only select ALL the records that are in the current filtered data?
The latter makes more sense because 'selection' is a visual thing for the user.
This is especially true if you are going to implement a set of external functions
that act on the selected information (you would not want to DELETE the wrong thing!).
So, to that end, access to the internals of ReactTable are required to get what is
currently visible in the table (either on the current page or any other page).
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
ReactTable and then get the internal state and the 'sortedData'.
That can then be iterrated to get all the currently visible records and set
the selection state.
*/
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = this.checkboxTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach(item => {
selection.push(item._original._id);
});
}
this.setState({ selectAll, selection });
};
isSelected = key => {
/*
Instead of passing our external selection state we provide an 'isSelected'
callback and detect the selection state ourselves. This allows any implementation
for selection (either an array, object keys, or even a Javascript Set object).
*/
return this.state.selection.includes(key);
};
logSelection = () => {
console.log("selection:", this.state.selection);
};
render() {
const { toggleSelection, toggleAll, isSelected, logSelection } = this;
const { data, columns, selectAll } = this.state;
const extraProps = {
selectAll,
isSelected,
toggleAll,
toggleSelection
};
return (
<div style={{ padding: "10px" }}>
<h1>react-table - Checkbox Table</h1>
<button onClick={logSelection}>Log Selection to Console</button>
{` (${this.state.selection.length}) selected`}
{data ? (
<CheckboxTable
data={data}
columns={columns}
ref={r => (this.checkboxTable = r)}
className="-striped -highlight"
{...extraProps}
/>
) : null}
</div>
);
}
}
// export default treeTableHOC(ComponentTest);
export default ComponentTest;

View File

@ -1,60 +0,0 @@
import React from "react";
export default class ComponentTest extends React.component {
render() {
return <div>Bozo</div>;
}
}
// import ReactTable from '../../../../lib/index'
// import '../../../../react-table.css'
//
// console.log('ReactTable:',ReactTable)
//
// const data = [
// {one:"1.1",two:"1.2"},
// {one:"2.1",two:"2.2"},
// {one:"3.1",two:"3.2"},
// {one:"4.1",two:"4.2"},
// ]
//
// const columns = [
// {accessor:'one', Header: 'One'},
// {accessor:'two', Header: 'Two'},
// ]
//
// class ExpanderComponent extends React.Component {
// render()
// {
// return (
// <div className={`rt-expander ${this.props.isExpanded ? '-open' : ''}`}>
// &bull;
// </div>
// )
// }
// }
//
// class SubComponent extends React.Component {
// render()
// {
// return <div>Nothing</div>
// }
// }
//
// export default class ComponentTest extends React.Component {
// render()
// {
// const rtProps = {
// data,
// columns,
// // ExpanderComponent: (props, closeExpanded)=><ExpanderComponent {...props, closeExpanded} />,
// // SubComponent: (props)=><SubComponent {...props} />,
// // multiSort: false,
// }
// return (
// <ReactTable
// {...rtProps}
// />
// )
// }
// }

View File

@ -1,110 +0,0 @@
import React from 'react'
import ReactTable from '../../../../lib/index'
import FoldableTableHOC from '../../../../lib/hoc/foldableTable'
import selectTableHOC from '../../../../lib/hoc/selectTable'
const FoldableTable = FoldableTableHOC(selectTableHOC(ReactTable))
export default class FoldableTableCustomState extends React.Component {
constructor(props, context) {
super(props, context)
this.state = { folded: {}, seleted: {}, selectedAll: false }
}
getData = () => [{
id: 1,
first_name: 'Jeanette',
'last_name': 'Penddreth',
email: 'jpenddreth0@census.gov',
'gender': 'Female',
ip_address: '26.58.193.2',
}, {
'id': 2,
'first_name': 'Giavani',
last_name: 'Frediani',
'email': 'gfrediani1@senate.gov',
'gender': 'Male',
'ip_address': '229.179.4.212',
}, {
'id': 3,
first_name: 'Noell',
last_name: 'Bea',
'email': 'nbea2@imageshack.us',
gender: 'Female',
ip_address: '180.66.162.255',
}, {
'id': 4,
'first_name': 'Willard',
'last_name': 'Valek',
email: 'wvalek3@vk.com',
'gender': 'Male',
ip_address: '67.76.188.26',
}];
toggleSelection = (key, shift, row) => {
const { selected } = this.state
let newSelected = Object.assign({}, selected)
newSelected[key] = !newSelected[key]
this.setState(p => ({ selected: newSelected }))
};
toggleAll = () => {
const { selectedAll } = this.state
if (selectedAll) { this.setState(p => { return { selectedAll: false, seleted: {} } }); }
else {
const data = this.getData()
let newSelected = {}
data.forEach(d => newSelected[d.id] = true)
this.setState(p => ({ selectedAll: true, seleted: newSelected }))
}
}
isSelected = key => this.state.seleted[key];
render() {
return (<FoldableTable
//SelectTable props
keyField='id'
toggleSelection={this.toggleSelection}
toggleAll={this.toggleAll}
isSelected={this.isSelected}
selectAll={this.state.selectedAll}
selectType='checkbox'
onFoldChange={newFolded => this.setState(p => { return { folded: newFolded } })}
folded={this.state.folded}
data={this.getData()}
columns={[{
Header: "Name",
foldable: true,
columns: [
{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name"
}
]
}, {
Header: "Info",
foldable: true,
columns: [
{
Header: "Email",
accessor: "email"
},
{
Header: "Gender",
accessor: "gender"
}
]
}]
}></FoldableTable>)
}
}

View File

@ -1,71 +0,0 @@
import React from 'react'
import ReactTable from '../../../../lib/index'
import FoldableTableHOC from '../../../../lib/hoc/foldableTable'
const FoldableTable = FoldableTableHOC(ReactTable)
export default class FoldableTableWithHeader extends React.Component {
getData = () => [{
id: 1,
first_name: 'Jeanette',
last_name: 'Penddreth',
'email': 'jpenddreth0@census.gov',
gender: 'Female',
'ip_address': '26.58.193.2',
}, {
'id': 2,
first_name: 'Giavani',
last_name: 'Frediani',
email: 'gfrediani1@senate.gov',
gender: 'Male',
'ip_address': '229.179.4.212',
}, {
'id': 3,
'first_name': 'Noell',
last_name: 'Bea',
'email': 'nbea2@imageshack.us',
'gender': 'Female',
ip_address: '180.66.162.255',
}, {
'id': 4,
'first_name': 'Willard',
'last_name': 'Valek',
email: 'wvalek3@vk.com',
'gender': 'Male',
ip_address: '67.76.188.26',
}];
render() {
return (<FoldableTable
data={this.getData()}
columns={[{
Header: "Name",
foldable: true,
columns: [
{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name"
}
]
}, {
Header: "Info",
foldable: true,
columns: [
{
Header: "Email",
accessor: "email"
},
{
Header: "Gender",
accessor: "gender"
}
]
}]
}></FoldableTable>)
}
}

View File

@ -1,64 +0,0 @@
import React from 'react';
import ReactTable from '../../../../lib/index';
import FoldableTableHOC from '../../../../lib/hoc/foldableTable';
const FoldableTable = FoldableTableHOC(ReactTable);
export default class FoldableTableWithoutHeader extends React.Component {
getData = () => [{
"id": 1,
"first_name": "Jeanette",
"last_name": "Penddreth",
"email": "jpenddreth0@census.gov",
"gender": "Female",
"ip_address": "26.58.193.2"
}, {
"id": 2,
"first_name": "Giavani",
"last_name": "Frediani",
"email": "gfrediani1@senate.gov",
"gender": "Male",
"ip_address": "229.179.4.212"
}, {
"id": 3,
"first_name": "Noell",
"last_name": "Bea",
"email": "nbea2@imageshack.us",
"gender": "Female",
"ip_address": "180.66.162.255"
}, {
"id": 4,
"first_name": "Willard",
"last_name": "Valek",
"email": "wvalek3@vk.com",
"gender": "Male",
"ip_address": "67.76.188.26"
}];
render() {
return <FoldableTable
data={this.getData()}
columns={[
{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name",
foldable: true,
},
{
Header: "Email",
accessor: "email",
foldable: true,
},
{
Header: "Gender",
accessor: "gender",
foldable: true,
}
]}></FoldableTable>
}
}

View File

@ -1,30 +0,0 @@
import React from 'react'
import '../../../../react-table.css'
import FoldableTableWithHeader from './FoldableTableWithHeader'
import FoldableTableWithoutHeader from './FoldableTableWithoutHeader'
import FoldableTableCustomState from './FoldableTableCustomState'
class FaldableComponentTest extends React.Component {
render() {
return (
<div>
<p>- Sample With Header Columns</p>
<br />
<FoldableTableWithHeader />
<br /> <br />
<p>- Sample With Normal Columns</p>
<br />
<FoldableTableWithoutHeader />
<br /> <br />
<p>- Custom State and selectedTable</p>
<br />
<FoldableTableCustomState />
</div >
)
}
}
export default FaldableComponentTest

View File

@ -1,13 +0,0 @@
/* eslint-disable */
import TreeTable from './treetable'
import SelectTable from './selecttable'
import SelectTreeTable from './selecttreetable'
import FoldableTable from './foldabletable';
export {
TreeTable,
SelectTable,
SelectTreeTable,
FoldableTable
}

View File

@ -1,173 +0,0 @@
import React from "react";
import shortid from "shortid";
import ReactTable from "../../../../lib/index";
import "../../../../react-table.css";
import selectTableHOC from "../../../../lib/hoc/selectTable";
const SelectTable = selectTableHOC(ReactTable);
async function getData() {
const result = await (await fetch("/au_500_tree.json")).json();
// we are adding a unique ID to the data for tracking the selected records
return result.map(item => {
const _id = shortid.generate();
return {
_id,
...item
};
});
}
function getColumns(data) {
const columns = [];
const sample = data[0];
for (let key in sample) {
if (key === "_id") continue;
columns.push({
accessor: key,
Header: key
});
}
return columns;
}
export class ComponentTest extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
columns: null,
selection: [],
selectAll: false,
selectType: "checkbox"
};
}
componentDidMount() {
getData().then(data => {
const columns = getColumns(data);
this.setState({ data, columns });
});
}
toggleSelection = (key, shift, row) => {
/*
Implementation of how to manage the selection state is up to the developer.
This implementation uses an array stored in the component state.
Other implementations could use object keys, a Javascript Set, or Redux... etc.
*/
// start off with the existing state
if (this.state.selectType === "radio") {
let selection = [];
if (selection.indexOf(key) < 0) selection.push(key);
this.setState({ selection });
} else {
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({ selection });
}
};
toggleAll = () => {
/*
'toggleAll' is a tricky concept with any filterable table
do you just select ALL the records that are in your data?
OR
do you only select ALL the records that are in the current filtered data?
The latter makes more sense because 'selection' is a visual thing for the user.
This is especially true if you are going to implement a set of external functions
that act on the selected information (you would not want to DELETE the wrong thing!).
So, to that end, access to the internals of ReactTable are required to get what is
currently visible in the table (either on the current page or any other page).
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
ReactTable and then get the internal state and the 'sortedData'.
That can then be iterrated to get all the currently visible records and set
the selection state.
*/
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = this.selectTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we just push all the IDs onto the selection array
currentRecords.forEach(item => {
if (item._original) {
selection.push(item._original._id);
}
});
}
this.setState({ selectAll, selection });
};
isSelected = key => {
/*
Instead of passing our external selection state we provide an 'isSelected'
callback and detect the selection state ourselves. This allows any implementation
for selection (either an array, object keys, or even a Javascript Set object).
*/
return this.state.selection.includes(key);
};
logSelection = () => {
console.log("selection:", this.state.selection);
};
toggleType = () => {
this.setState({
selectType: this.state.selectType === "radio" ? "checkbox" : "radio",
selection: [],
selectAll: false
});
};
render() {
const {
toggleSelection,
toggleAll,
isSelected,
logSelection,
toggleType
} = this;
const { data, columns, selectAll, selectType } = this.state;
const extraProps = {
selectAll,
isSelected,
toggleAll,
toggleSelection,
selectType
};
return (
<div style={{ padding: "10px" }}>
<h1>react-table - Select Table</h1>
<button onClick={toggleType}>
Select Type: <strong>{selectType}</strong>
</button>
<button onClick={logSelection}>Log Selection to Console</button>
{` (${this.state.selection.length}) selected`}
{data ? (
<SelectTable
data={data}
columns={columns}
ref={r => (this.selectTable = r)}
className="-striped -highlight"
{...extraProps}
/>
) : null}
</div>
);
}
}
export default ComponentTest;

View File

@ -1,238 +0,0 @@
import React from "react";
import shortid from "shortid";
import ReactTable from "../../../../lib/index";
import "../../../../react-table.css";
import selectTableHOC from "../../../../lib/hoc/selectTable";
import treeTableHOC from "../../../../lib/hoc/treeTable";
const SelectTreeTable = selectTableHOC(treeTableHOC(ReactTable));
async function getData() {
const result = await (await fetch("/au_500_tree.json")).json();
// we are adding a unique ID to the data for tracking the selected records
return result.map(item => {
const _id = shortid.generate();
return {
_id,
...item
};
});
}
function getColumns(data) {
const columns = [];
const sample = data[0];
for (let key in sample) {
if (key === "_id") continue;
columns.push({
accessor: key,
Header: key
});
}
return columns;
}
function getNodes(data, node = []) {
data.forEach(item => {
if (item.hasOwnProperty("_subRows") && item._subRows) {
node = getNodes(item._subRows, node);
} else {
node.push(item._original);
}
});
return node;
}
export class ComponentTest extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
columns: null,
selection: [],
selectAll: false,
selectType: "checkbox"
};
}
componentDidMount() {
getData().then(data => {
const columns = getColumns(data);
const pivotBy = ["state", "post"];
this.setState({ data, columns, pivotBy });
});
}
toggleSelection = (key, shift, row) => {
/*
Implementation of how to manage the selection state is up to the developer.
This implementation uses an array stored in the component state.
Other implementations could use object keys, a Javascript Set, or Redux... etc.
*/
// start off with the existing state
if (this.state.selectType === "radio") {
let selection = [];
if (selection.indexOf(key) < 0) selection.push(key);
this.setState({ selection });
} else {
let selection = [...this.state.selection];
const keyIndex = selection.indexOf(key);
// check to see if the key exists
if (keyIndex >= 0) {
// it does exist so we will remove it using destructing
selection = [
...selection.slice(0, keyIndex),
...selection.slice(keyIndex + 1)
];
} else {
// it does not exist so add it
selection.push(key);
}
// update the state
this.setState({ selection });
}
};
toggleAll = () => {
/*
'toggleAll' is a tricky concept with any filterable table
do you just select ALL the records that are in your data?
OR
do you only select ALL the records that are in the current filtered data?
The latter makes more sense because 'selection' is a visual thing for the user.
This is especially true if you are going to implement a set of external functions
that act on the selected information (you would not want to DELETE the wrong thing!).
So, to that end, access to the internals of ReactTable are required to get what is
currently visible in the table (either on the current page or any other page).
The HOC provides a method call 'getWrappedInstance' to get a ref to the wrapped
ReactTable and then get the internal state and the 'sortedData'.
That can then be iterrated to get all the currently visible records and set
the selection state.
*/
const selectAll = this.state.selectAll ? false : true;
const selection = [];
if (selectAll) {
// we need to get at the internals of ReactTable
const wrappedInstance = this.selectTable.getWrappedInstance();
// the 'sortedData' property contains the currently accessible records based on the filter and sort
const currentRecords = wrappedInstance.getResolvedState().sortedData;
// we need to get all the 'real' (original) records out to get at their IDs
const nodes = getNodes(currentRecords);
// we just push all the IDs onto the selection array
nodes.forEach(item => {
selection.push(item._id);
});
}
this.setState({ selectAll, selection });
};
isSelected = key => {
/*
Instead of passing our external selection state we provide an 'isSelected'
callback and detect the selection state ourselves. This allows any implementation
for selection (either an array, object keys, or even a Javascript Set object).
*/
return this.state.selection.includes(key);
};
logSelection = () => {
console.log("selection:", this.state.selection);
};
toggleType = () => {
this.setState({
selectType: this.state.selectType === "radio" ? "checkbox" : "radio",
selection: [],
selectAll: false
});
};
toggleTree = () => {
if (this.state.pivotBy.length) {
this.setState({ pivotBy: [], expanded: {} });
} else {
this.setState({ pivotBy: ["state", "post"], expanded: {} });
}
};
onExpandedChange = expanded => {
this.setState({ expanded });
};
render() {
const {
toggleSelection,
toggleAll,
isSelected,
logSelection,
toggleType,
toggleTree,
onExpandedChange
} = this;
const {
data,
columns,
selectAll,
selectType,
pivotBy,
expanded
} = this.state;
const extraProps = {
selectAll,
isSelected,
toggleAll,
toggleSelection,
selectType,
pivotBy,
expanded,
onExpandedChange,
pageSize: 5
};
return (
<div style={{ padding: "10px" }}>
<h1>react-table - Select Tree Table</h1>
<p>
This example combines two HOCs (the TreeTable and the SelectTable) to
make a composite component.
</p>
<p>We'll call it SelectTreeTable!</p>
<p>Here is what the buttons do:</p>
<ul>
<li>
<strong>Toggle Tree:</strong> enables or disabled the pivotBy on the
table.
</li>
<li>
<strong>Select Type:</strong> changes from 'checkbox' to 'radio' and
back again.
</li>
<li>
<strong>Log Selection to Console:</strong> open your console to see
what has been selected.
</li>
</ul>
<p>
<strong>NOTE:</strong> the selection is maintained when toggling the
tree on and off but is cleared when switching between select types
(radio, checkbox).
</p>
<button onClick={toggleTree}>
Toggle Tree [{pivotBy && pivotBy.length ? pivotBy.join(", ") : ""}]
</button>
<button onClick={toggleType}>
Select Type: <strong>{selectType}</strong>
</button>
<button onClick={logSelection}>Log Selection to Console</button>
{` (${this.state.selection.length}) selected`}
{data ? (
<SelectTreeTable
data={data}
columns={columns}
ref={r => (this.selectTable = r)}
className="-striped -highlight"
freezeWhenExpanded={true}
{...extraProps}
/>
) : null}
</div>
);
}
}
export default ComponentTest;

View File

@ -1,70 +0,0 @@
import React from "react";
import ReactTable from "../../../../lib/index";
import "../../../../react-table.css";
import treeTableHOC from "../../../../lib/hoc/treeTable";
async function getData() {
const result = await (await fetch("/au_500_tree.json")).json();
return result;
}
function getColumns(data, pivotBy) {
const columns = [];
const sample = data[0];
for (let key in sample) {
columns.push({
accessor: key,
Header: key
});
}
return columns;
}
export class ComponentTest extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
columns: null,
pivotBy: null // ["firstName", "lastName"],
};
}
componentDidMount() {
getData().then(data => {
// console.log('cwm data:',data);
const pivotBy = ["state", "post", "city"];
const columns = getColumns(data, pivotBy);
// console.log('cwm cols:',columns);
this.setState({ data, columns, pivotBy });
});
}
showState = () => {
console.log("state:", this.reactTable.getResolvedState());
};
render() {
const { data, columns, pivotBy } = this.state;
const extraProps = {
data,
columns,
pivotBy
};
const TreeTable = treeTableHOC(ReactTable);
return (
<div style={{ padding: "10px" }}>
<h1>react-table - Tree Table</h1>
{data ? (
<TreeTable
ref={r => (this.reactTable = r)}
className="-striped -highlight"
defaultPageSize={5}
{...extraProps}
/>
) : null}
</div>
);
}
}
export default ComponentTest;

View File

@ -1,53 +0,0 @@
import React from "react";
export default Component => {
const wrapper = componentProps => {
const TrComponent = props => {
const { ri, ...rest } = props;
if (ri && ri.groupedByPivot) {
const cell = props.children[ri.level];
cell.props.style.flex = "unset";
cell.props.style.width = "100%";
cell.props.style.maxWidth = "unset";
cell.props.style.paddingLeft = `${componentProps.treeTableIndent *
ri.level}px`;
cell.props.style.backgroundColor = "#DDD";
cell.props.style.borderBottom = "1px solid rgba(128,128,128,0.2)";
return <div {...rest}>{cell}</div>;
}
return <Component.defaultProps.TrComponent {...rest} />;
};
const getTrProps = (state, ri, ci, instance) => {
return { ri };
};
const { columns, ...rest } = componentProps;
const extra = {
columns: columns.map(col => {
let column = col;
if (rest.pivotBy && rest.pivotBy.includes(col.accessor)) {
column = {
accessor: col.accessor,
width: `${componentProps.treeTableIndent}px`,
show: false,
Header: ""
};
}
return column;
}),
TrComponent,
getTrProps
};
return <Component {...rest} {...extra} />;
};
wrapper.displayName = "RTTreeTable";
wrapper.defaultProps = {
treeTableRowBackground: "#EEE",
treeTableIndent: 10
};
return wrapper;
};

View File

@ -1,35 +0,0 @@
import namor from "namor";
const range = len => {
const arr = [];
for (let i = 0; i < len; i++) {
arr.push(i);
}
return arr;
};
const newPerson = () => {
const statusChance = Math.random();
return {
firstName: namor.generate({ words: 1, numbers: 0 }),
lastName: namor.generate({ words: 1, numbers: 0 }),
age: Math.floor(Math.random() * 30),
visits: Math.floor(Math.random() * 100),
progress: Math.floor(Math.random() * 100),
status:
statusChance > 0.66
? "relationship"
: statusChance > 0.33
? "complicated"
: "single"
};
};
export function makeData(len = 5553) {
return range(len).map(d => {
return {
...newPerson(),
children: range(10).map(newPerson)
};
});
}

View File

@ -1,10 +0,0 @@
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

View File

@ -1,7 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
//
import "./index.css";
import App from "./App.js";
ReactDOM.render(<App />, document.getElementById("root"));

View File

@ -1,23 +0,0 @@
/* eslint-disable */
import React from "react";
import marked from "marked";
//
import HOCReadme from "!raw!../../../src/hoc/README.md";
import "github-markdown-css/github-markdown.css";
import "./utils/prism.js";
export default class HOCStory extends React.Component {
render() {
return (
<div style={{ padding: "10px" }}>
<span
className="markdown-body"
dangerouslySetInnerHTML={{ __html: marked(HOCReadme) }}
/>
</div>
);
}
componentDidMount() {
global.Prism && global.Prism.highlightAll();
}
}

View File

@ -1,23 +0,0 @@
/* eslint-disable import/no-webpack-loader-syntax */
import React from "react";
import marked from "marked";
//
import Readme from "!raw!../../../README.md";
import "github-markdown-css/github-markdown.css";
import "./utils/prism.js";
export default class Story extends React.Component {
render() {
return (
<div style={{ padding: "10px" }}>
<span
className="markdown-body"
dangerouslySetInnerHTML={{ __html: marked(Readme) }}
/>
</div>
);
}
componentDidMount() {
global.Prism && global.Prism.highlightAll();
}
}

View File

@ -1,27 +0,0 @@
import React from "react";
import ReactTable from "../../../lib/index";
import "../../../react-table.css";
const data = [
{ one: "1.1", two: "1.2" },
{ one: "2.1", two: "2.2" },
{ one: "3.1", two: "3.2" },
{ one: "4.1", two: "4.2" }
];
const columns = [
{ accessor: "one", Header: "One" },
{ accessor: "two", Header: "Two" }
];
export default class Story extends React.Component {
render() {
return (
<div>
Test
<ReactTable data={data} columns={columns} />
</div>
);
}
}

View File

@ -1,138 +0,0 @@
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
code[class*="language-"]::selection, code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #a67f59;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

View File

@ -3,10 +3,10 @@
"version": "6.9.0",
"description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT",
"homepage": "https://github.com/react-tools/react-table#readme",
"homepage": "https://github.com/tannerlinsley/react-table#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/react-tools/react-table.git"
"url": "git+https://github.com/tannerlinsley/react-table.git"
},
"keywords": [
"react",

7
src/aggregations.js Executable file
View File

@ -0,0 +1,7 @@
export function sum (values, rows) {
return values.reduce((sum, next) => sum + next, 0)
}
export function average (values, rows) {
return Math.round((sum(values, rows) / values.length) * 100) / 100
}

View File

@ -1,270 +0,0 @@
import React from 'react'
import classnames from 'classnames'
//
import _ from './utils'
import Pagination from './pagination'
const emptyObj = () => ({})
export default {
// General
data: [],
resolveData: data => data,
loading: false,
showPagination: true,
showPaginationTop: false,
showPaginationBottom: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPage: 0,
defaultPageSize: 20,
showPageJump: true,
collapseOnSortingChange: true,
collapseOnPageChange: true,
collapseOnDataChange: true,
freezeWhenExpanded: false,
sortable: true,
multiSort: true,
resizable: true,
filterable: false,
defaultSortDesc: false,
defaultSorted: [],
defaultFiltered: [],
defaultResized: [],
defaultExpanded: {},
// eslint-disable-next-line no-unused-vars
defaultFilterMethod: (filter, row, column) => {
const id = filter.pivotId || filter.id
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
},
// eslint-disable-next-line no-unused-vars
defaultSortMethod: (a, b, desc) => {
// force null and undefined to the bottom
a = a === null || a === undefined ? '' : a
b = b === null || b === undefined ? '' : b
// force any string values to lowercase
a = typeof a === 'string' ? a.toLowerCase() : a
b = typeof b === 'string' ? b.toLowerCase() : b
// Return either 1 or -1 to indicate a sort priority
if (a > b) {
return 1
}
if (a < b) {
return -1
}
// returning 0, undefined or any falsey value will use subsequent sorts or
// the index as a tiebreaker
return 0
},
// Controlled State Props
// page: undefined,
// pageSize: undefined,
// sorted: [],
// filtered: [],
// resized: [],
// expanded: {},
// Controlled State Callbacks
onPageChange: undefined,
onPageSizeChange: undefined,
onSortedChange: undefined,
onFilteredChange: undefined,
onResizedChange: undefined,
onExpandedChange: undefined,
// Pivoting
pivotBy: undefined,
// Key Constants
pivotValKey: '_pivotVal',
pivotIDKey: '_pivotID',
subRowsKey: '_subRows',
aggregatedKey: '_aggregated',
nestingLevelKey: '_nestingLevel',
originalKey: '_original',
indexKey: '_index',
groupedByPivotKey: '_groupedByPivot',
// Server-side Callbacks
onFetchData: () => null,
// Classes
className: '',
style: {},
// Component decorators
getProps: emptyObj,
getTableProps: emptyObj,
getTheadGroupProps: emptyObj,
getTheadGroupTrProps: emptyObj,
getTheadGroupThProps: emptyObj,
getTheadProps: emptyObj,
getTheadTrProps: emptyObj,
getTheadThProps: emptyObj,
getTheadFilterProps: emptyObj,
getTheadFilterTrProps: emptyObj,
getTheadFilterThProps: emptyObj,
getTbodyProps: emptyObj,
getTrGroupProps: emptyObj,
getTrProps: emptyObj,
getTdProps: emptyObj,
getTfootProps: emptyObj,
getTfootTrProps: emptyObj,
getTfootTdProps: emptyObj,
getPaginationProps: emptyObj,
getLoadingProps: emptyObj,
getNoDataProps: emptyObj,
getResizerProps: emptyObj,
// Global Column Defaults
column: {
// Renderers
Cell: undefined,
Header: undefined,
Footer: undefined,
Aggregated: undefined,
Pivot: undefined,
PivotValue: undefined,
Expander: undefined,
Filter: undefined,
Placeholder: undefined,
// All Columns
sortable: undefined, // use table default
resizable: undefined, // use table default
filterable: undefined, // use table default
show: true,
minWidth: 100,
minResizeWidth: 11,
// Cells only
className: '',
style: {},
getProps: emptyObj,
// Pivot only
aggregate: undefined,
// Headers only
headerClassName: '',
headerStyle: {},
getHeaderProps: emptyObj,
// Footers only
footerClassName: '',
footerStyle: {},
getFooterProps: emptyObj,
filterMethod: undefined,
filterAll: false,
sortMethod: undefined,
},
// Global Expander Column Defaults
expanderDefaults: {
sortable: false,
resizable: false,
filterable: false,
width: 35,
},
pivotDefaults: {
// extend the defaults for pivoted columns here
},
// Text
previousText: 'Previous',
nextText: 'Next',
loadingText: 'Loading...',
noDataText: 'No rows found',
pageText: 'Page',
ofText: 'of',
rowsText: 'rows',
pageJumpText: 'jump to page',
rowsSelectorText: 'rows per page',
// Components
TableComponent: ({ children, className, ...rest }) => (
<div
className={classnames('rt-table', className)}
role="grid"
// tabIndex='0'
{...rest}
>
{children}
</div>
),
TheadComponent: _.makeTemplateComponent('rt-thead', 'Thead'),
TbodyComponent: _.makeTemplateComponent('rt-tbody', 'Tbody'),
TrGroupComponent: ({ children, className, ...rest }) => (
<div className={classnames('rt-tr-group', className)} role="rowgroup" {...rest}>
{children}
</div>
),
TrComponent: ({ children, className, ...rest }) => (
<div className={classnames('rt-tr', className)} role="row" {...rest}>
{children}
</div>
),
ThComponent: ({
toggleSort, className, children, ...rest
}) => (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
<div
className={classnames('rt-th', className)}
onClick={e => toggleSort && toggleSort(e)}
role="columnheader"
tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
{...rest}
>
{children}
</div>
),
TdComponent: ({
toggleSort, className, children, ...rest
}) => (
<div className={classnames('rt-td', className)} role="gridcell" {...rest}>
{children}
</div>
),
TfootComponent: _.makeTemplateComponent('rt-tfoot', 'Tfoot'),
FilterComponent: ({ filter, onChange, column }) => (
<input
type="text"
style={{
width: '100%',
}}
placeholder={column.Placeholder}
value={filter ? filter.value : ''}
onChange={event => onChange(event.target.value)}
/>
),
ExpanderComponent: ({ isExpanded }) => (
<div className={classnames('rt-expander', isExpanded && '-open')}>&bull;</div>
),
PivotValueComponent: ({ subRows, value }) => (
<span>
{value} {subRows && `(${subRows.length})`}
</span>
),
AggregatedComponent: ({ subRows, column }) => {
const previewValues = subRows.filter(d => typeof d[column.id] !== 'undefined').map((row, i) => (
// eslint-disable-next-line react/no-array-index-key
<span key={i}>
{row[column.id]}
{i < subRows.length - 1 ? ', ' : ''}
</span>
))
return <span>{previewValues}</span>
},
PivotComponent: undefined, // this is a computed default generated using
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
PaginationComponent: Pagination,
PreviousComponent: undefined,
NextComponent: undefined,
LoadingComponent: ({
className, loading, loadingText, ...rest
}) => (
<div className={classnames('-loading', { '-active': loading }, className)} {...rest}>
<div className="-loading-inner">{loadingText}</div>
</div>
),
NoDataComponent: _.makeTemplateComponent('rt-noData', 'NoData'),
ResizerComponent: _.makeTemplateComponent('rt-resizer', 'Resizer'),
PadRowComponent: () => <span>&nbsp;</span>,
}

View File

@ -1,316 +0,0 @@
<div style="text-align:center;">
<a href="https://github.com/react-tools/react-table" target="\_parent"><img src="https://github.com/react-tools/media/raw/master/logo-react-table.png" alt="React Table Logo" style="width:450px;"/></a>
</div>
# ReactTable - expanding with HOCs
This documentation is about expanding ReactTable using Higher Order Components/Functions.
## Covered in this README
* A Brief explanation of HOCs and why they are a good approach for ReactTable enhancements
* Documentation of the currently available HOCs
* TreeTable
* SelectTable
* Documentation of the standard for writing HOCs with ReactTable
## What are HOCs and why use them with ReactTable
HOCs (or Higher Order Components/Functions) are either a React Component (or a function that returns a React Component)
that are used to enhance the functionality of an existing component. How much you can enhance depends on the props that
the component exposes.
Fortunately, ReactTable exposes a LOT of functionality as props to the component. In some cases there are too many
props to keep track of and that is where HOCs come in.
You can write a HOC that just focusses on the additional functionality you want to enhance and keep those enhancements to
reuse over and over again when you need them. You don't have to edit the ReactSource code, just wrap ReactTable in one or
more HOCs (more on some issues related to chaining HOCs later) that provide the additional functionality you want to expose.
The most obvious HOC is one that can add `checkbox` or select functionality. The HOC included provides `select` functionality
that allows the developer to specify if they want a `checkbox` or `radio` style of select column. The implementation of the
selection is recorded (e.g. in component state, Redux, etc.) and how to manage multiple selections. The HOC really only handles
the rendering pieces.
But there is more documentation on the `select` HOC below.
## Currently Available HOCs
Any of the below HOCs can be imported from react-table like so:
```javascript
import ReactTable from "react-table";
import treeTableHOC from "react-table/lib/hoc/treeTable";
const TreeTable = treeTableHOC(ReactTable);
```
Swap `treeTable` and `TreeTable` with any of the other HOC names as necessary.
### TreeTable
TreeTable takes over the rendering of the generated pivot rows of ReactTable so that they appear more like an expandable Tree.
It accomplishes this by rendering a 100% wide div and then only rendering the cell that controls the pivot at that level.
Using it is as simple as doing the following:
```javascript
const TreeTable = treeTableHOC(ReactTable);
```
After you have done the above, you can then use `TreeTable` just as you would `ReactTable` but it will render pivots using
the Tree style described above.
### SelectTable
SelectTable is a little trickier. The HOCs attempt to avoid adding additional state and, as there is no internal ID for a row that
can be relied on to be static (ReactTable just reuses indexes when rendering) the developer has to maintain the state outside of even
the wrapped component. So it is largely based on callbacks.
You include the HOC in the same manner as you would for the treeTableHOC but then need to provide the following overrides:
* isSelected - returns `true` if the key passed is selected otherwise it should return `false`
* selectAll - a property that indicates if the selectAll is set (`true|false`)
* toggleAll - called when the user clicks the `selectAll` checkbox/radio
* toggleSelection - called when the use clicks a specific checkbox/radio in a row
* selectType - either `checkbox|radio` to indicate what type of selection is required
**Note:** The select field defaults to the accessor `_id` property in order to render the select field for that particular row. If your objects have different
unique ID fields, make sure to tell React Table that by passing it the `keyField` property.
```javascript
<ReactTable keyField='id' />
```
In the case of `radio` there is no `selectAll` displayed but the developer is responsible for only making one selection in
the controlling component's state. You could select multiple but it wouldn't make sense and you should use `checkbox` instead.
You also have to decide what `selectAll` means. Given ReactTable is a paged solution there are other records off-page. When someone
selects the `selectAll` checkbox, should it mark every possible record, only what might be visible to due a Filter or only those items
on the current page?
The example opts for the middle approach so it gets a `ref` to the ReactTable instance and pulls the `sortedData` out of the resolved
state (then walks through those records and pulls their ID into the `selection` state of the controlling component).
You can also replace the input component that is used to render the select box and select all box:
* SelectAllInputComponent - the checkbox in the top left corner
* SelectInputComponent - the checkbox used on a row
### SelectTreeTable
SelectTreeTable is a combination of TreeTable and SelectTable.
To function correctly the chain has to be in the correct order as follows (see the comments in the guid on HOCs below).
```javascript
const SelectTreeTable = selectTableHOC(treeTableHOC(ReactTable));
```
In this particular instance it is (probably) because the functions need access to the state on the wrapped component to manage
the selected items. Although that is not totally clearly the issue.
### FoldableTable
FoldableTable is a HOC that make the columns are foldable. The reason I developed this HOC because when working on the real project related to the financial which display so many columns for validation and comparison.
So foldable columns allow users to temporary hidden the unwanted to columns so that they can focus on the data that they want to see.
#### How it works
```javascript
const FoldableTable = FoldableTableHOC(ReactTable);
```
It will scan all the columns which `foldable` is `true` and apply the foldable column feature. This feature will work for both normal columns and header columns as samples below.
- With Header Columns
```javascript
render(){
return <FoldableTable
columns={[{
Header: "Name",
foldable: true,
columns: [{
Header: "First Name",
accessor: "first_name"
},{
Header: "Last Name",
accessor: "last_name"
}]
},{
Header: "Info",
foldable: true,
columns: [{
Header: "Email",
accessor: "email"
},{
Header: "Gender",
accessor: "gender"
}]
}]
}/>
}
```
![With Header Columns](https://raw.githubusercontent.com/baoduy/Images/master/Wordpress/JavaScripts/react-table%20foldableHOC/FoldableTable%20With%20Header.gif)
- With Nornal Columns
```javascript
render() {
return <FoldableTable
columns={[{
Header: "First Name",
accessor: "first_name"
},
{
Header: "Last Name",
accessor: "last_name",
foldable: true,
},
{
Header: "Email",
accessor: "email",
foldable: true,
},
{
Header: "Gender",
accessor: "gender",
foldable: true,
}]}></FoldableTable>
}
```
![With Normal Columns](https://raw.githubusercontent.com/baoduy/Images/master/Wordpress/JavaScripts/react-table%20foldableHOC/FoldableTable%20Without%20Header.gif)
- The `FoldableTable` also fully compatible with existing HOCs, below is with selectTableHOC.
![With Normal Columns](https://raw.githubusercontent.com/baoduy/Images/master/Wordpress/JavaScripts/react-table%20foldableHOC/FoldableTable%20With%20selectTable.gif)
#### State management
If you would like to manage the state of FoldableTable, then add the following codes.
```javascript
render() {
return <FoldableTable
onFoldChange={newFolded => this.setState(p => { return { folded: newFolded } })}
folded={this.state.folded}
/>
}
```
#### Custom Components
- FoldIconComponent: to render the Icon of buttons.
- FoldButtonComponent: to render the folding buttons for each Column.
With default rendering as below.
```javascript
const defaultFoldIconComponent = ({ collapsed }) => {
//Render your Icon here
}
const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => {
//Render your button here.
}
```
### AdvancedExpandTable
HOC which allows any Cell in the row to toggle the row's
SubComponent (expand/collapse). Also allows the SubComponent to toggle itself. Technically supports toggling any row's SubComponent.
These are the expand functions available to any SubComponent or Column Cell:
```
toggleRowSubComponent
showRowSubComponent
hideRowSubComponent
```
They are available through the `props.columnProps.rest` object.
On any change to the props, the table will reset the expanded state.
Accepts a onExpandedChange callback to be called whenever the table expanded state changes
Note: only supports 1 level of nesting.
Example usage in a Column Cell Renderer:
```javascript
Cell: props => {
const {
value
columnProps: { rest: { showRowSubComponent } },
nestingPath
} = props;
return (
<div>
<button
onClick={e => showRowSubComponent(nestingPath, e)}
>
{value}
</button>
</div>
);
}
```
Example usage in the ReactTable SubComponent (toggle itself):
```javascript
const AdvancedExpandReactTable = advancedExpandTableHOC(ReactTable);
<AdvancedExpandReactTable>
...
SubComponent={({ row, nestingPath, toggleRowSubComponent }) => {
return (
<div>
<button
onClick={e => toggleRowSubComponent({ nestingPath }, e)}
>
{row.value}
</button>
</div>
);
}}
/>
```
Each Column Renderer (E.g. Cell ) gets the expand functions in its props and each SubComponent gets the expand functions in its props
Expand functions takes the `nestingPath` or `rowInfo` given to each
Column Renderer and SubComponent already by ReactTable.
## HOC Guide for ReactTable
There are a few rules required when writing a HOC for ReactTable (other than meeting the normal lint standards - which are
still being developed).
Firstly, there are issues with `ref` when you write a HOC. Consider a deeply nested component wrapped in multiple HOCs...
A HOC in the middle of the chain requires access to the instance of the component it thinks it is wrapping but there is at
least one other wrapper in the way. The challenge is: How do I get to the actual wrapped component?
Each HOC is required to be a React Class so that a `ref` can be obtained against each component:
```Javascript
<Component ... ref={r => this.wrappedInstance = r} />
```
_NOTE:_ "Component" can also be the `<ReactTable />` instance.
Then the following method needs
to be placed on the class so that it exposes the correct instance of ReactTable:
```Javascript
getWrappedInstance() {
if (!this.wrappedInstance) console.warn('<component name here> - No wrapped instance')
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
else return this.wrappedInstance
}
```
Essentially this will walk down the chain (if there are chained HOCs) and stop when it gets to the end and return the wrapped instance.
Finally, sometimes the chains need to be in a specific order to function correctly. It is not clear if this is just an architectural
issue or if it would be better solved using a library like `recompose`. Anyone who is able to contribute a reliable solution to this
is welcome to submit a PR.

View File

@ -1,199 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import set from 'lodash.set'
import get from 'lodash.get'
/*
AvancedExpandTableHOC for ReactTable
HOC which allows any Cell in the row to toggle the row's
SubComponent. Also allows the SubComponent to toggle itself.
Expand functions available to any SubComponent or Column Cell:
toggleRowSubComponent
showRowSubComponent
hideRowSubComponent
Each Column Renderer (E.g. Cell ) gets the expand functions in its props
And Each SubComponent gets the expand functions in its props
Expand functions takes the `rowInfo` given to each
Column Renderer and SubComponent already by ReactTable.
*/
export const subComponentWithToggle = (SubComponent, expandFuncs) => props => (
<SubComponent {...props} {...expandFuncs} />
)
// each cell in the column gets passed the function to toggle a sub component
export const columnsWithToggle = (columns, expandFuncs) =>
columns.map(column => {
if (column.columns) {
return {
...column,
columns: columnsWithToggle(column.columns, expandFuncs),
}
}
return {
...column,
getProps () {
return {
...expandFuncs,
}
},
}
})
export const advancedExpandTableHOC = TableComponent =>
class AdvancedExpandTable extends Component {
constructor (props) {
super(props)
this.state = {
expanded: {},
}
this.toggleRowSubComponent = this.toggleRowSubComponent.bind(this)
this.showRowSubComponent = this.showRowSubComponent.bind(this)
this.hideRowSubComponent = this.hideRowSubComponent.bind(this)
this.getTdProps = this.getTdProps.bind(this)
this.fireOnExpandedChange = this.fireOnExpandedChange.bind(this)
this.expandFuncs = {
toggleRowSubComponent: this.toggleRowSubComponent,
showRowSubComponent: this.showRowSubComponent,
hideRowSubComponent: this.hideRowSubComponent,
}
}
// after initial render if we get new
// data, columns, page changes, etc.
// we reset expanded state.
componentWillReceiveProps () {
this.setState({
expanded: {},
})
}
static propTypes = {
columns: PropTypes.array.isRequired,
SubComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element])
.isRequired,
onExpandedChange: PropTypes.func,
};
static defaultProps = {
onExpandedChange: null,
};
static DisplayName = 'AdvancedExpandTable';
// since we pass the expand functions to each Cell,
// we need to filter it out from being passed as an
// actual DOM attribute. See getProps in columnsWithToggle above.
static TdComponent ({
toggleRowSubComponent,
showRowSubComponent,
hideRowSubComponent,
...rest
}) {
return <TableComponent.defaultProps.TdComponent {...rest} />
}
fireOnExpandedChange (rowInfo, e) {
// fire callback once state has changed.
if (this.props.onExpandedChange) {
this.props.onExpandedChange(rowInfo, e)
}
}
resolveNewTableState (rowInfoOrNestingPath, e, expandType) {
// derive nestingPath if only rowInfo is passed
let nestingPath = rowInfoOrNestingPath
if (rowInfoOrNestingPath.nestingPath) {
nestingPath = rowInfoOrNestingPath.nestingPath
}
this.setState(
prevState => {
const isExpanded = get(prevState.expanded, nestingPath)
// since we do not support nested rows, a shallow clone is okay.
const newExpanded = { ...prevState.expanded }
switch (expandType) {
case 'show':
set(newExpanded, nestingPath, {})
break
case 'hide':
set(newExpanded, nestingPath, false)
break
default:
// toggle
set(newExpanded, nestingPath, isExpanded ? false : {})
}
return {
...prevState,
expanded: newExpanded,
}
},
() => this.fireOnExpandedChange(rowInfoOrNestingPath, e)
)
}
toggleRowSubComponent (rowInfo, e) {
this.resolveNewTableState(rowInfo, e)
}
showRowSubComponent (rowInfo, e) {
this.resolveNewTableState(rowInfo, e, 'show')
}
hideRowSubComponent (rowInfo, e) {
this.resolveNewTableState(rowInfo, e, 'hide')
}
getTdProps (tableState, rowInfo, column) {
const { expander } = column
if (!expander) {
// no overrides
return {}
}
return {
// only override onClick for column Td
onClick: e => {
this.toggleRowSubComponent(rowInfo, e)
},
}
}
getWrappedInstance () {
if (!this.wrappedInstance) { console.warn('AdvancedExpandTable - No wrapped instance') }
if (this.wrappedInstance.getWrappedInstance) {
return this.wrappedInstance.getWrappedInstance()
}
return this.wrappedInstance
}
render () {
const {
columns, SubComponent, onExpandedChange, ...rest
} = this.props
const wrappedColumns = columnsWithToggle(columns, this.expandFuncs)
const WrappedSubComponent = subComponentWithToggle(
SubComponent,
this.expandFuncs
)
return (
<TableComponent
{...rest}
columns={wrappedColumns}
expanded={this.state.expanded}
getTdProps={this.getTdProps}
SubComponent={WrappedSubComponent}
TdComponent={AdvancedExpandTable.TdComponent}
/>
)
}
}

View File

@ -1,230 +0,0 @@
/* eslint-disable */
import React from 'react'
import left from './left.svg'
import right from './right.svg'
const defaultFoldIconComponent = ({ collapsed }) => {
const style = { width: 25 }
if (collapsed) return <img src={right} style={style} alt="right" />
return <img src={left} style={style} alt="left" />
}
const defaultFoldButtonComponent = ({ header, collapsed, icon, onClick }) => {
const style = {
marginLeft: '0px',
marginTop: '-5px',
marginBottom: '-8px',
float: 'left',
cursor: 'pointer',
}
return (
<div>
<div style={style} onClick={onClick}>
{icon}
</div>
{!collapsed && <div>{header}</div>}
</div>
)
}
export default ReactTable => {
const wrapper = class RTFoldableTable extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
folded: props.onFoldChange ? undefined : {},
resized: props.resized || [],
}
}
componentWillReceiveProps(newProps) {
if (this.state.resized !== newProps.resized) {
this.setState(p => ({ resized: newProps.resized }))
}
}
onResizedChange = resized => {
const { onResizedChange } = this.props
if (onResizedChange) onResizedChange(resized)
else {
this.setState(p => ({ resized }))
}
}
removeResized = column => {
const { id } = column
if (!id) return
const { resized } = this.state
if (!resized) return
const rs = resized.find(r => r.id === id)
if (!rs) return
const newResized = resized.filter(r => r !== rs)
this.onResizedChange(newResized)
}
// this is so we can expose the underlying ReactTable.
getWrappedInstance = () => {
if (!this.wrappedInstance) console.warn('RTFoldableTable - No wrapped instance')
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
return this.wrappedInstance
}
getCopiedKey = key => {
const { foldableOriginalKey } = this.props
return `${foldableOriginalKey}${key}`
}
copyOriginals = column => {
const { FoldedColumn } = this.props
// Stop copy if the column already copied
if (column.original_Header) return
Object.keys(FoldedColumn).forEach(k => {
const copiedKey = this.getCopiedKey(k)
if (k === 'Cell') column[copiedKey] = column[k] ? column[k] : c => c.value
else column[copiedKey] = column[k]
})
// Copy sub Columns
if (column.columns && !column.original_Columns) column.original_Columns = column.columns
// Copy Header
if (!column.original_Header) column.original_Header = column.Header
}
restoreToOriginal = column => {
const { FoldedColumn } = this.props
Object.keys(FoldedColumn).forEach(k => {
// ignore header as handling by foldableHeaderRender
if (k === 'Header') return
const copiedKey = this.getCopiedKey(k)
column[k] = column[copiedKey]
})
if (column.columns && column.original_Columns) column.columns = column.original_Columns
}
getState = () => (this.props.onFoldChange ? this.props.folded : this.state.folded)
isFolded = col => {
const folded = this.getState()
return folded[col.id] === true
}
foldingHandler = col => {
if (!col || !col.id) return
const { onFoldChange } = this.props
const folded = this.getState()
const { id } = col
const newFold = Object.assign({}, folded)
newFold[id] = !newFold[id]
// Remove the Resized if have
this.removeResized(col)
if (onFoldChange) onFoldChange(newFold)
else {
this.setState(previous => ({ folded: newFold }))
}
}
foldableHeaderRender = cell => {
const { FoldButtonComponent, FoldIconComponent } = this.props
const { column } = cell
const collapsed = this.isFolded(column)
const icon = React.createElement(FoldIconComponent, { collapsed })
const onClick = () => this.foldingHandler(column)
return React.createElement(FoldButtonComponent, {
header: column.original_Header,
collapsed,
icon,
onClick,
})
}
applyFoldableForColumn = column => {
const collapsed = this.isFolded(column)
const { FoldedColumn } = this.props
// Handle Column Header
if (column.columns) {
if (collapsed) {
column.columns = [FoldedColumn]
column.width = FoldedColumn.width
column.style = FoldedColumn.style
} else this.restoreToOriginal(column)
}
// Handle Normal Column.
else if (collapsed) column = Object.assign(column, FoldedColumn)
else {
this.restoreToOriginal(column)
}
}
applyFoldableForColumns = columns =>
columns.map((col, index) => {
if (!col.foldable) return col
// If col don't have id then generate id based on index
if (!col.id) col.id = `col_${index}`
this.copyOriginals(col)
// Replace current header with internal header render.
col.Header = c => this.foldableHeaderRender(c)
// apply foldable
this.applyFoldableForColumn(col)
// return the new column out
return col
})
render() {
const {
columns: originalCols,
FoldButtonComponent,
FoldIconComponent,
FoldedColumn,
...rest
} = this.props
const columns = this.applyFoldableForColumns([...originalCols])
const extra = {
columns,
onResizedChange: this.onResizedChange,
resized: this.state.resized,
}
return <ReactTable {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
}
}
wrapper.displayName = 'RTFoldableTable'
wrapper.defaultProps = {
FoldIconComponent: defaultFoldIconComponent,
FoldButtonComponent: defaultFoldButtonComponent,
foldableOriginalKey: 'original_',
FoldedColumn: {
Cell: c => '',
width: 30,
sortable: false,
resizable: false,
filterable: false,
},
}
return wrapper
}

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M327.3,98.9l-2.1,1.8l-156.5,136c-5.3,4.6-8.6,11.5-8.6,19.2c0,7.7,3.4,14.6,8.6,19.2L324.9,411l2.6,2.3 c2.5,1.7,5.5,2.7,8.7,2.7c8.7,0,15.8-7.4,15.8-16.6h0V112.6h0c0-9.2-7.1-16.6-15.8-16.6C332.9,96,329.8,97.1,327.3,98.9z"/></svg>

Before

Width:  |  Height:  |  Size: 583 B

View File

@ -1 +0,0 @@
<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg height="512px" id="Layer_1" style="enable-background:new 0 0 512 512;" version="1.1" viewBox="0 0 512 512" width="512px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M184.7,413.1l2.1-1.8l156.5-136c5.3-4.6,8.6-11.5,8.6-19.2c0-7.7-3.4-14.6-8.6-19.2L187.1,101l-2.6-2.3 C182,97,179,96,175.8,96c-8.7,0-15.8,7.4-15.8,16.6h0v286.8h0c0,9.2,7.1,16.6,15.8,16.6C179.1,416,182.2,414.9,184.7,413.1z"/></svg>

Before

Width:  |  Height:  |  Size: 585 B

View File

@ -1,119 +0,0 @@
/* eslint-disable */
import React from 'react'
const defaultSelectInputComponent = props => {
return (
<input
type={props.selectType || 'checkbox'}
aria-label={`${props.checked ? 'Un-select':'Select'} row with id:${props.id}` }
checked={props.checked}
id={props.id}
onClick={e => {
const { shiftKey } = e
e.stopPropagation()
props.onClick(props.id, shiftKey, props.row)
}}
onChange={() => {}}
/>
)
}
export default (Component, options) => {
const wrapper = class RTSelectTable extends React.Component {
constructor(props) {
super(props)
}
rowSelector(row) {
if (!row || !row.hasOwnProperty(this.props.keyField)) return null
const { toggleSelection, selectType, keyField } = this.props
const checked = this.props.isSelected(row[this.props.keyField])
const inputProps = {
checked,
onClick: toggleSelection,
selectType,
row,
id: `select-${row[keyField]}`
}
return React.createElement(this.props.SelectInputComponent, inputProps)
}
headSelector(row) {
const { selectType } = this.props
if (selectType === 'radio') return null
const { toggleAll, selectAll: checked, SelectAllInputComponent } = this.props
const inputProps = {
checked,
onClick: toggleAll,
selectType,
id: 'select-all'
}
return React.createElement(SelectAllInputComponent, inputProps)
}
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
getWrappedInstance() {
if (!this.wrappedInstance) console.warn('RTSelectTable - No wrapped instance')
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
else return this.wrappedInstance
}
render() {
const {
columns: originalCols,
isSelected,
toggleSelection,
toggleAll,
keyField,
selectAll,
selectType,
selectWidth,
SelectAllInputComponent,
SelectInputComponent,
...rest
} = this.props
const select = {
id: '_selector',
accessor: () => 'x', // this value is not important
Header: this.headSelector.bind(this),
Cell: ci => {
return this.rowSelector.bind(this)(ci.original)
},
width: selectWidth || 30,
filterable: false,
sortable: false,
resizable: false,
style: { textAlign: 'center' },
}
const columns = (options !== undefined && options.floatingLeft === true) ? [...originalCols, select] : [select, ...originalCols]
const extra = {
columns,
}
return <Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
}
}
wrapper.displayName = 'RTSelectTable'
wrapper.defaultProps = {
keyField: '_id',
isSelected: key => {
console.log('No isSelected handler provided:', { key })
},
selectAll: false,
toggleSelection: (key, shift, row) => {
console.log('No toggleSelection handler provided:', { key, shift, row })
},
toggleAll: () => {
console.log('No toggleAll handler provided.')
},
selectType: 'checkbox',
SelectInputComponent: defaultSelectInputComponent,
SelectAllInputComponent: defaultSelectInputComponent,
}
return wrapper
}

View File

@ -1,79 +0,0 @@
/* eslint-disable */
import React from 'react'
export default Component => {
const wrapper = class RTTreeTable extends React.Component {
constructor(props) {
super(props)
this.getWrappedInstance.bind(this)
this.TrComponent.bind(this)
this.getTrProps.bind(this)
}
// this is so we can expose the underlying ReactTable to get at the sortedData for selectAll
getWrappedInstance = () => {
if (!this.wrappedInstance) console.warn('RTTreeTable - No wrapped instance')
if (this.wrappedInstance.getWrappedInstance) return this.wrappedInstance.getWrappedInstance()
else return this.wrappedInstance
}
TrComponent = props => {
const { ri, ...rest } = props
if (ri && ri.groupedByPivot) {
const cell = { ...props.children[ri.level] }
cell.props.style.flex = 'unset'
cell.props.style.width = '100%'
cell.props.style.maxWidth = 'unset'
cell.props.style.paddingLeft = `${this.props.treeTableIndent * ri.level}px`
// cell.props.style.backgroundColor = '#DDD';
cell.props.style.borderBottom = '1px solid rgba(128,128,128,0.2)'
return (
<div className={`rt-tr ${rest.className}`} role="row" style={rest.style}>
{cell}
</div>
)
}
return <Component.defaultProps.TrComponent {...rest} />
}
getTrProps = (state, ri, ci, instance) => {
return { ri }
}
render() {
const { columns, treeTableIndent, ...rest } = this.props
const { TrComponent, getTrProps } = this
const extra = {
columns: columns.map(col => {
let column = col
if (rest.pivotBy && (rest.pivotBy.includes(col.accessor) || rest.pivotBy.includes(col.id))) {
column = {
id: col.id,
accessor: col.accessor,
width: `${treeTableIndent}px`,
show: false,
Header: '',
Expander: col.Expander,
PivotValue: col.PivotValue,
Pivot: col.Pivot,
}
}
return column
}),
TrComponent,
getTrProps,
}
return <Component {...rest} {...extra} ref={r => (this.wrappedInstance = r)} />
}
}
wrapper.displayName = 'RTTreeTable'
wrapper.defaultProps = {
treeTableIndent: 10,
}
return wrapper
}

41
src/hooks/useAccessedRows.js Executable file
View File

@ -0,0 +1,41 @@
import { useMemo } from 'react'
export default function useAccessedRows ({
debug, data, columns, subRowsKey,
}) {
return useMemo(
() => {
if (debug) console.info('getAccessedRows')
// Access the row's data
const accessRow = (data, i, depth = 0) => {
// Keep the original reference around
const original = data
// Process any subRows
const subRows = data[subRowsKey]
? data[subRowsKey].map((d, i) => accessRow(d, i, depth + 1))
: undefined
const row = {
original,
index: i,
subRows,
depth,
}
// Create the cells and values
row.values = {}
columns.forEach(column => {
row.values[column.id] = column.accessor ? column.accessor(data) : undefined
})
return row
}
// Use the resolved data
return data.map((d, i) => accessRow(d, i))
},
[data, columns]
)
}

201
src/hooks/useColumns.js Executable file
View File

@ -0,0 +1,201 @@
import { useMemo } from 'react'
import { getFirstDefined, getBy } from '../utils'
export default function useColumns ({
debug,
groupBy,
userColumns,
disableSorting,
disableGrouping,
disableFilters,
}) {
return useMemo(
() => {
if (debug) console.info('getColumns')
// Decorate All the columns
const columnTree = decorateColumnTree(userColumns)
// Get the flat list of all columns
let columns = flattenBy(columnTree, 'columns')
columns = [
...groupBy.map(g => columns.find(col => col.id === g)),
...columns.filter(col => !groupBy.includes(col.id)),
]
// Get headerGroups
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
const headers = flattenBy(headerGroups, 'headers')
return {
columns,
headerGroups,
headers,
}
},
[groupBy, userColumns]
)
// Find the depth of the columns
function findMaxDepth (columns, depth = 0) {
return columns.reduce((prev, curr) => {
if (curr.columns) {
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
}
return depth
}, 0)
}
function decorateColumn (column, parent) {
// First check for string accessor
let {
id, accessor, Header, canSortBy, canGroupBy, canFilter,
} = column
if (typeof accessor === 'string') {
id = id || accessor
const accessorString = accessor
accessor = row => getBy(row, accessorString)
}
const grouped = groupBy.includes(id)
canSortBy = accessor
? getFirstDefined(canSortBy, disableSorting === true ? false : undefined, true)
: false
canGroupBy = accessor
? getFirstDefined(canGroupBy, disableGrouping === true ? false : undefined, true)
: false
canFilter = accessor
? getFirstDefined(canFilter, disableFilters === true ? false : undefined, true)
: false
if (!id && typeof Header === 'string') {
id = Header
}
if (!id) {
// Accessor, but no column id? This is bad.
console.error(column)
throw new Error('A column id is required!')
}
column = {
Header: '',
Cell: cell => cell.value,
show: true,
...column,
grouped,
canSortBy,
canGroupBy,
canFilter,
id,
accessor,
parent,
}
column.Aggregated = column.Aggregated || column.Cell
return column
}
// Build the visible columns, headers and flat column list
function decorateColumnTree (columns, parent, depth = 0) {
return columns.map(column => {
column = decorateColumn(column, parent)
if (column.columns) {
column.columns = decorateColumnTree(column.columns, column, depth + 1)
}
return column
})
}
function flattenBy (columns, childKey) {
const flatColumns = []
const recurse = columns => {
columns.forEach(d => {
if (!d[childKey]) {
flatColumns.push(d)
} else {
recurse(d[childKey])
}
})
}
recurse(columns)
return flatColumns
}
// Build the header groups from the bottom up
function makeHeaderGroups (columns, maxDepth) {
const headerGroups = []
const removeChildColumns = column => {
delete column.columns
if (column.parent) {
removeChildColumns(column.parent)
}
}
columns.forEach(removeChildColumns)
const buildGroup = (columns, depth = 0) => {
const headerGroup = {
headers: [],
}
const parentColumns = []
const hasParents = columns.some(col => col.parent)
columns.forEach(column => {
const isFirst = !parentColumns.length
let latestParentColumn = [...parentColumns].reverse()[0]
// If the column has a parent, add it if necessary
if (column.parent) {
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
parentColumns.push({
...column.parent,
originalID: column.parent.id,
id: [column.parent.id, parentColumns.length].join('_'),
})
}
} else if (hasParents) {
// If other columns have parents, add a place holder if necessary
const placeholderColumn = decorateColumn({
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
id: [column.id, 'placeholder', maxDepth - depth, parentColumns.length].join('_'),
})
if (isFirst || latestParentColumn.originalID !== placeholderColumn.originalID) {
parentColumns.push(placeholderColumn)
}
}
// Establish the new columns[] relationship on the parent
if (column.parent || hasParents) {
latestParentColumn = [...parentColumns].reverse()[0]
latestParentColumn.columns = latestParentColumn.columns || []
if (!latestParentColumn.columns.includes(column)) {
latestParentColumn.columns.push(column)
}
}
headerGroup.headers.push(column)
})
headerGroups.push(headerGroup)
if (parentColumns.length) {
buildGroup(parentColumns)
}
}
buildGroup(columns)
return headerGroups.reverse()
}
}

24
src/hooks/useControlledState.js Executable file
View File

@ -0,0 +1,24 @@
const mergeStateAndProps = (state, defaults, props) => {
Object.keys(props).forEach(key => {
if (typeof props[key] !== 'undefined') {
state[key] = props[key]
}
})
Object.keys(defaults).forEach(key => {
if (typeof state[key] === 'undefined') {
state[key] = defaults[key]
}
})
return state
}
export default function useControlledState ([userState, userSetState], defaults, props) {
const state = mergeStateAndProps(userState, defaults, props)
const setState = (updater, type) =>
userSetState(old => updater(mergeStateAndProps(old, defaults, props)), type)
return [state, setState]
}

55
src/hooks/useExpandedRows.js Executable file
View File

@ -0,0 +1,55 @@
import { useMemo, useEffect } from 'react'
import { getBy } from '../utils'
export default function useExpandedRows ({
debug,
expanded,
expandedKey,
columns,
rows,
setState,
actions,
}) {
return useMemo(
() => {
if (debug) console.info('getExpandedRows')
const expandedRows = []
// Here we do some mutation, but it's the last stage in the
// immutable process so this is safe
const handleRow = (row, index, depth = 0, parentPath = []) => {
// Compute some final state for the row
const path = [...parentPath, index]
row.path = path
row.depth = depth
row.isExpanded = (row.original && row.original[expandedKey]) || getBy(expanded, path)
row.cells = columns.map(column => {
const cell = {
column,
row,
state: null,
value: row.values[column.id],
}
return cell
})
expandedRows.push(row)
if (row.isExpanded && row.subRows && row.subRows.length) {
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
}
}
rows.forEach((row, i) => handleRow(row, i))
return expandedRows
},
[rows, expanded, columns]
)
}

67
src/hooks/useFilteredRows.js Executable file
View File

@ -0,0 +1,67 @@
import { useMemo } from 'react'
export default function useFilteredRows ({
debug,
filters,
rows,
columns,
filterFn,
manualFilters,
}) {
return useMemo(
() => {
if (manualFilters || !Object.keys(filters).length) {
return rows
}
if (debug) console.info('getFilteredRows')
// Filters top level and nested rows
const filterRows = rows => {
let filteredRows = rows
filteredRows = Object.entries(filters).reduce((filteredSoFar, [columnID, filterValue]) => {
// Find the filters column
const column = columns.find(d => d.id === columnID)
// Don't filter hidden columns or columns that have had their filters disabled
if (!column || column.filterable === false) {
return filteredSoFar
}
const filterMethod = column.filterMethod || filterFn
// If 'filterAll' is set to true, pass the entire dataset to the filter method
if (column.filterAll) {
return filterMethod(filteredSoFar, columnID, filterValue, column)
}
return filteredSoFar.filter(row => filterMethod(row, columnID, filterValue, column))
}, rows)
// Apply the filter to any subRows
filteredRows = filteredRows.map(row => {
if (!row.subRows) {
return row
}
return {
...row,
subRows: filterRows(row.subRows),
}
})
// then filter any rows without subcolumns because it would be strange to show
filteredRows = filteredRows.filter(row => {
if (!row.subRows) {
return true
}
return row.subRows.length > 0
})
return filteredRows
}
return filterRows(rows)
},
[rows, filters, manualFilters]
)
}

77
src/hooks/useGroupedRows.js Executable file
View File

@ -0,0 +1,77 @@
import { useMemo } from 'react'
import * as aggregations from '../aggregations'
export default function useGroupedRows ({
debug,
groupBy,
rows,
columns,
groupByFn,
manualGroupBy,
userAggregations,
}) {
return useMemo(
() => {
if (manualGroupBy || !groupBy.length) {
return rows
}
if (debug) console.info('getGroupedRows')
// Find the columns that can or are aggregating
// Uses each column to aggregate rows into a single value
const aggregateRowsToValues = rows => {
const values = {}
columns.forEach(column => {
const columnValues = rows.map(d => d.values[column.id])
const aggregate =
userAggregations[column.aggregate] || aggregations[column.aggregate] || column.aggregate
if (typeof aggregate === 'function') {
values[column.id] = aggregate(columnValues, rows)
} else if (aggregate) {
throw new Error(
`Invalid aggregate "${aggregate}" passed to column with ID: "${column.id}"`
)
} else {
values[column.id] = columnValues[0]
}
})
return values
}
// Recursively group the data
const groupRecursively = (rows, groupBy, depth = 0) => {
// This is the last level, just return the rows
if (depth >= groupBy.length) {
return rows
}
// Group the rows together for this level
const groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
([groupByVal, subRows], index) => {
// Recurse to sub rows before aggregation
subRows = groupRecursively(subRows, groupBy, depth + 1)
const values = aggregateRowsToValues(subRows)
const row = {
groupByID: groupBy[depth],
groupByVal,
values,
subRows,
depth,
index,
}
return row
}
)
return groupedRows
}
// Assign the new data
return groupRecursively(rows, groupBy)
},
[rows, groupBy, columns, manualGroupBy]
)
}

60
src/hooks/useSortedRows.js Executable file
View File

@ -0,0 +1,60 @@
import { useMemo } from 'react'
export default function useSortedRows ({
debug,
sortBy,
rows,
columns,
orderByFn,
sortByFn,
manualSorting,
}) {
return useMemo(
() => {
if (manualSorting || !sortBy.length) {
return rows
}
if (debug) console.info('getSortedRows')
const sortMethodsByColumnID = {}
columns
.filter(col => col.sortMethod)
.forEach(col => {
sortMethodsByColumnID[col.id] = col.sortMethod
})
const sortData = rows => {
// Use the orderByFn to compose multiple sortBy's together.
// This will also perform a stable sorting using the row index
// if needed.
const sortedData = orderByFn(
rows,
sortBy.map(sort => {
// Support custom sorting methods for each column
const columnSortBy = sortMethodsByColumnID[sort.id]
// Return the correct sortFn
return (a, b) =>
(columnSortBy || sortByFn)(a.values[sort.id], b.values[sort.id], sort.desc)
}),
// Map the directions
sortBy.map(d => !d.desc)
)
// TODO: this should be optimized. Not good to loop again
sortedData.forEach(row => {
if (!row.subRows) {
return
}
row.subRows = sortData(row.subRows)
})
return sortedData
}
return sortData(rows)
},
[rows, columns, sortBy, manualSorting]
)
}

156
src/hooks/useTableMethods.js Executable file
View File

@ -0,0 +1,156 @@
import { getBy, setBy, getFirstDefined } from '../utils'
export default function useTableMethods ({
debug,
setState,
actions,
groupBy,
columns,
defaultSortDesc,
filters,
}) {
const toggleExpandedByPath = (path, set) =>
setState(old => {
const { expanded } = old
const existing = getBy(expanded, path)
set = getFirstDefined(set, !existing)
return {
...old,
expanded: setBy(expanded, path, set),
}
}, actions.toggleExpanded)
// Updates sorting based on a columnID, desc flag and multi flag
const toggleSortByID = (columnID, desc, multi) =>
setState(old => {
const { sortBy } = old
// Find the column for this columnID
const column = columns.find(d => d.id === columnID)
const resolvedDefaultSortDesc = getFirstDefined(column.defaultSortDesc, defaultSortDesc)
// Find any existing sortBy for this column
const existingSortBy = sortBy.find(d => d.id === columnID)
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
let newSortBy = []
// What should we do with this filter?
let action
if (!multi) {
if (sortBy.length <= 1 && existingSortBy) {
if (existingSortBy.desc) {
action = 'remove'
} else {
action = 'toggle'
}
} else {
action = 'replace'
}
} else if (!existingSortBy) {
action = 'add'
} else if (hasDescDefined) {
action = 'set'
} else {
action = 'toggle'
}
if (action === 'replace') {
newSortBy = [
{
id: columnID,
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
},
]
} else if (action === 'add') {
newSortBy = [
...sortBy,
{
id: columnID,
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
},
]
} else if (action === 'set') {
newSortBy = sortBy.map(d => {
if (d.id === columnID) {
return {
...d,
desc,
}
}
return d
})
} else if (action === 'toggle') {
newSortBy = sortBy.map(d => {
if (d.id === columnID) {
return {
...d,
desc: !existingSortBy.desc,
}
}
return d
})
} else if (action === 'remove') {
newSortBy = []
}
return {
...old,
sortBy: newSortBy,
}
}, actions.sortByChange)
const toggleGroupBy = (id, toggle) =>
setState(old => {
const resolvedToggle = typeof set !== 'undefined' ? toggle : !groupBy.includes(id)
if (resolvedToggle) {
return {
...old,
groupBy: [...groupBy, id],
}
}
return {
...old,
groupBy: groupBy.filter(d => d !== id),
}
}, actions.toggleGroupBy)
const setFilter = (id, val) =>
setState(old => {
if (typeof val === 'undefined') {
const { [id]: prev, ...rest } = filters
return {
...old,
filters: {
...rest,
},
}
}
return {
...old,
filters: {
...filters,
[id]: val,
},
}
}, actions.setFilter)
const setAllFilters = filters =>
setState(
old => ({
...old,
filters,
}),
actions.setAllFilters
)
return {
toggleExpandedByPath,
toggleSortByID,
toggleGroupBy,
setFilter,
setAllFilters,
}
}

877
src/index.js Normal file → Executable file
View File

@ -1,868 +1,11 @@
import React, { Component } from 'react'
import classnames from 'classnames'
//
import _ from './utils'
import Lifecycle from './lifecycle'
import Methods from './methods'
import defaultProps from './defaultProps'
import propTypes from './propTypes'
export const ReactTableDefaults = defaultProps
export default class ReactTable extends Methods(Lifecycle(Component)) {
static propTypes = propTypes
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: props.defaultPage,
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,
showPaginationTop,
showPaginationBottom,
manual,
loadingText,
noDataText,
sortable,
multiSort,
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,
PadRowComponent,
// Data model
resolvedData,
visibleColumns,
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 = visibleColumns.some(d => d.Footer)
const hasFilters = filterable || visibleColumns.some(d => d.filterable)
const recurseRowsViewIndex = (rows, path = [], index = -1) => [
rows.map((row, i) => {
index += 1
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(
visibleColumns.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,
}
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 loadingProps = getLoadingProps(finalState, undefined, undefined, this)
const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
// Visual Components
const makeHeaderGroup = (column, i) => {
const resizedValue = col => (resized.find(x => x.id === col.id) || {}).value
const leafColumns = _.leaves(column, 'columns')
const flex = _.sum(
leafColumns.map(col => (col.width || resizedValue(col) ? 0 : col.minWidth))
)
const width = _.sum(
leafColumns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.minWidth))
)
const maxWidth = _.sum(
leafColumns.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: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}
return (
<ThComponent
key={`${i}-${column.id}`}
className={classnames(classes)}
style={{
...styles,
...flexStyles,
}}
{...rest}
>
{_.normalizeComponent(column.Header, {
data: sortedData,
column,
})}
</ThComponent>
)
}
const makeHeaderGroups = (row, i) => {
const theadGroupProps = _.splitProps(getTheadGroupProps(finalState, undefined, row, this))
const theadGroupTrProps = _.splitProps(getTheadGroupTrProps(finalState, undefined, row, this))
return (
<TheadComponent
key={`${i}-${row.id}`}
className={classnames('-headerGroups', theadGroupProps.className)}
style={{
...theadGroupProps.style,
minWidth: `${rowMinWidth}px`,
}}
{...theadGroupProps.rest}
>
<TrComponent
className={theadGroupTrProps.className}
style={theadGroupTrProps.style}
{...theadGroupTrProps.rest}
>
{row.map(makeHeaderGroup)}
</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(e, column, false)}
onTouchStart={e => this.resizeColumnStart(e, column, true)}
{...getResizerProps('finalState', undefined, column, this)}
/>
) : null
const isSortable = _.getFirstDefined(column.sortable, sortable, false)
return (
<ThComponent
key={`${i}-${column.id}`}
className={classnames(
classes,
isResizable && '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: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}}
toggleSort={e => {
if (isSortable) this.sortColumn(column, multiSort ? e.shiftKey : false)
}}
{...rest}
>
<div className={classnames(isResizable && 'rt-resizable-header-content')}>
{_.normalizeComponent(column.Header, {
data: sortedData,
column,
})}
</div>
{resizer}
</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}
>
{visibleColumns.map(makeHeader)}
</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: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}}
{...rest}
>
{isFilterable
? _.normalizeComponent(
ResolvedFilterComponent,
{
column,
filter,
onChange: value => this.filterColumn(column, value),
},
defaultProps.column.Filter
)
: null}
</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}
>
{visibleColumns.map(makeFilter)}
</TrComponent>
</TheadComponent>
)
}
const makePageRow = (row, i, path = []) => {
const rowInfo = {
original: row[originalKey],
row,
index: row[indexKey],
viewIndex: (rowIndex += 1),
pageSize,
page,
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}
>
{visibleColumns.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 useOnExpanderClick
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
useOnExpanderClick = true
// If pivoted, has no subRows, and does not have a subComponent,
// do not make expandable
if (cellInfo.pivoted && !cellInfo.subRows && !SubComponent) {
cellInfo.expandable = false
}
}
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
}
}
}
const resolvedOnExpanderClick = useOnExpanderClick ? onExpanderClick : () => {}
// If there are multiple onClick events, make sure they don't
// override eachother. This should maybe be expanded to handle all
// function attributes
const interactionProps = {
onClick: resolvedOnExpanderClick,
}
if (tdProps.rest.onClick) {
interactionProps.onClick = e => {
tdProps.rest.onClick(e, () => resolvedOnExpanderClick(e))
}
}
if (columnProps.rest.onClick) {
interactionProps.onClick = e => {
columnProps.rest.onClick(e, () => resolvedOnExpanderClick(e))
}
}
// Return the cell
return (
<TdComponent
// eslint-disable-next-line react/no-array-index-key
key={`${i2}-${column.id}`}
className={classnames(
classes,
!cellInfo.expandable && !show && 'hidden',
cellInfo.expandable && 'rt-expandable',
(isBranch || isPreview) && 'rt-pivot'
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}}
{...tdProps.rest}
{...columnProps.rest}
{...interactionProps}
>
{resolvedCell}
</TdComponent>
)
})}
</TrComponent>
{rowInfo.subRows &&
isExpanded &&
rowInfo.subRows.map((d, i) => makePageRow(d, i, rowInfo.nestingPath))}
{SubComponent &&
!rowInfo.subRows &&
isExpanded &&
SubComponent(rowInfo, () => {
const newExpanded = _.clone(expanded)
_.set(newExpanded, rowInfo.nestingPath, false)
})}
</TrGroupComponent>
)
}
const makePadColumn = (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 flex = width
const 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: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}}
{...tdProps.rest}
>
{_.normalizeComponent(PadRowComponent)}
</TdComponent>
)
}
const makePadRow = (row, i) => {
const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this)
const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this))
return (
<TrGroupComponent key={`pad-${i}`} {...trGroupProps}>
<TrComponent
className={classnames(
'-padRow',
(pageRows.length + i) % 2 ? '-even' : '-odd',
trProps.className
)}
style={trProps.style || {}}
>
{visibleColumns.map(makePadColumn)}
</TrComponent>
</TrGroupComponent>
)
}
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: _.asPx(width),
maxWidth: _.asPx(maxWidth),
}}
{...columnProps.rest}
{...tFootTdProps.rest}
{...columnFooterProps.rest}
>
{_.normalizeComponent(column.Footer, {
data: sortedData,
column,
})}
</TdComponent>
)
}
const makeColumnFooters = () => {
const tFootProps = _.splitProps(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}
>
{visibleColumns.map(makeColumnFooter)}
</TrComponent>
</TfootComponent>
)
}
const makePagination = isTop => {
const paginationProps = _.splitProps(
getPaginationProps(finalState, undefined, undefined, this)
)
return (
<PaginationComponent
{...resolvedState}
pages={pages}
canPrevious={canPrevious}
canNext={canNext}
onPageChange={this.onPageChange}
onPageSizeChange={this.onPageSizeChange}
className={paginationProps.className}
style={paginationProps.style}
isTop={isTop}
{...paginationProps.rest}
/>
)
}
const makeTable = () => (
<div
className={classnames('ReactTable', className, rootProps.className)}
style={{
...style,
...rootProps.style,
}}
{...rootProps.rest}
>
{showPagination && showPaginationTop ? (
<div className="pagination-top">{makePagination(true)}</div>
) : null}
<TableComponent
className={classnames(tableProps.className, currentlyResizing ? 'rt-resizing' : '')}
style={tableProps.style}
{...tableProps.rest}
>
{hasHeaderGroups ? headerGroups.map(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 && showPaginationBottom ? (
<div className="pagination-bottom">{makePagination(false)}</div>
) : 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()
}
import useReactTable, { actions as mainActions } from './useReactTable'
import usePagination, { actions as paginationActions } from './usePagination'
import useFlexLayout, { actions as flexLayoutActions } from './useFlexLayout'
const actions = {
...mainActions,
...paginationActions,
...flexLayoutActions,
}
export { useReactTable, usePagination, useFlexLayout, actions }

View File

@ -1,322 +0,0 @@
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940)
$easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
$expandSize = 7px
input-select-style()
border: 1px solid rgba(0,0,0,0.1)
background: white
padding: 5px 7px
font-size: inherit
border-radius: 3px
font-weight: normal
outline:none
.ReactTable
position:relative
display: flex
flex-direction: column
border: 1px solid alpha(black, .1)
*
box-sizing: border-box
.rt-table
flex: auto 1
display: flex
flex-direction: column
align-items: stretch
width: 100%
border-collapse: collapse
overflow: auto
.rt-thead
flex: 1 0 auto
display: flex
flex-direction: column
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
&.-headerGroups
background: alpha(black, .03)
border-bottom: 1px solid alpha(black, .05)
&.-filters
border-bottom: 1px solid alpha(black, 0.05)
input
select
input-select-style();
.rt-th
border-right: 1px solid alpha(black, 0.02)
&.-header
box-shadow: 0 2px 15px 0px alpha(black, .15)
.rt-tr
text-align:center
.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-th:focus
outline: none
.rt-resizable-header
overflow: visible
&:last-child
overflow: hidden
.rt-resizable-header-content
overflow: hidden
text-overflow: ellipsis
.rt-header-pivot
border-right-color: #f7f7f7
.rt-header-pivot:after, .rt-header-pivot:before
left: 100%
top: 50%
border: solid transparent
content: " "
height: 0
width: 0
position: absolute
pointer-events: none
.rt-header-pivot:after
border-color: rgba(255, 255, 255, 0)
border-left-color: #FFF
border-width: 8px
margin-top: -8px
.rt-header-pivot:before
border-color: rgba(102, 102, 102, 0)
border-left-color: #f7f7f7
border-width: 10px
margin-top: -10px
.rt-tbody
flex: 99999 1 auto
display: flex
flex-direction: column
overflow: auto
// z-index:0
.rt-tr-group
border-bottom: solid 1px alpha(black, .05)
&:last-child
border-bottom: 0
.rt-td
border-right:1px solid alpha(black, .02)
&:last-child
border-right:0
.rt-expandable
cursor: pointer
text-overflow: clip
.rt-tr-group
flex: 1 0 auto
display: flex
flex-direction: column
align-items: stretch
.rt-tr
flex: 1 0 auto
display: inline-flex
.rt-th
.rt-td
flex: 1 0 0px
white-space: nowrap
text-overflow: ellipsis
padding: 7px 5px
overflow: hidden
transition: .3s ease
transition-property: width, min-width, padding, opacity
&.-hidden
width: 0 !important
min-width: 0 !important
padding: 0 !important
border:0 !important
opacity: 0 !important
.rt-expander
display: inline-block
position:relative
margin: 0
color: transparent
margin: 0 10px
&:after
content: ''
position: absolute
width: 0
height: 0
top:50%
left:50%
transform: translate(-50%, -50%) rotate(-90deg)
border-left: ($expandSize * .72) solid transparent
border-right: ($expandSize * .72) solid transparent
border-top: $expandSize solid alpha(black, .8)
transition: all .3s $easeOutBack
cursor: pointer
&.-open:after
transform: translate(-50%, -50%) rotate(0deg)
.rt-resizer
display: inline-block
position: absolute
width: 36px
top: 0
bottom: 0
right: -18px
cursor: col-resize
z-index: 10
.rt-tfoot
flex: 1 0 auto
display: flex
flex-direction: column
box-shadow: 0 0px 15px 0px alpha(black, .15)
.rt-td
border-right:1px solid alpha(black, .05)
&:last-child
border-right:0
&.-striped
.rt-tr.-odd
background: alpha(black, .03)
&.-highlight
.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)
input
select
input-select-style()
.-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: -1
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
z-index: 2
pointer-events: all
> div
transform: translateY(50%)
.rt-resizing
.rt-th
.rt-td
transition: none!important
cursor: col-resize
user-select none

View File

@ -1,128 +0,0 @@
export default Base =>
class extends Base {
componentWillMount () {
this.setStateWithData(this.getDataModel(this.getResolvedState(), true))
}
componentDidMount () {
this.fireFetchData()
}
componentWillReceiveProps (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
// Do a deep compare of new and old `defaultOption` and
// if they are different reset `option = defaultOption`
const defaultableOptions = ['sorted', 'filtered', 'resized', 'expanded']
defaultableOptions.forEach(x => {
const defaultName = `default${x.charAt(0).toUpperCase() + x.slice(1)}`
if (JSON.stringify(oldState[defaultName]) !== JSON.stringify(newState[defaultName])) {
newState[x] = newState[defaultName]
}
})
// If they change these table options, we need to reset defaults
// or else we could get into a state where the user has changed the UI
// and then disabled the ability to change it back.
// e.g. If `filterable` has changed, set `filtered = defaultFiltered`
const resettableOptions = ['sortable', 'filterable', 'resizable']
resettableOptions.forEach(x => {
if (oldState[x] !== newState[x]) {
const baseName = x.replace('able', '')
const optionName = `${baseName}ed`
const defaultName = `default${optionName.charAt(0).toUpperCase() + optionName.slice(1)}`
newState[optionName] = newState[defaultName]
}
})
// Props that trigger a data update
if (
oldState.data !== newState.data ||
oldState.columns !== newState.columns ||
oldState.pivotBy !== newState.pivotBy ||
oldState.sorted !== newState.sorted ||
oldState.filtered !== newState.filtered
) {
this.setStateWithData(this.getDataModel(newState, oldState.data !== newState.data))
}
}
setStateWithData (newState, cb) {
const oldState = this.getResolvedState()
const newResolvedState = this.getResolvedState({}, newState)
const { freezeWhenExpanded } = newResolvedState
// Default to unfrozen state
newResolvedState.frozen = false
// If freezeWhenExpanded is set, check for frozen conditions
if (freezeWhenExpanded) {
// if any rows are expanded, freeze the existing data and sorting
const keys = Object.keys(newResolvedState.expanded)
for (let i = 0; i < keys.length; i += 1) {
if (newResolvedState.expanded[keys[i]]) {
newResolvedState.frozen = true
break
}
}
}
// If the data isn't frozen and either the data or
// sorting model has changed, update the data
if (
(oldState.frozen && !newResolvedState.frozen) ||
oldState.sorted !== newResolvedState.sorted ||
oldState.filtered !== newResolvedState.filtered ||
oldState.showFilters !== newResolvedState.showFilters ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
) {
// Handle collapseOnsortedChange & collapseOnDataChange
if (
(oldState.sorted !== newResolvedState.sorted && this.props.collapseOnSortingChange) ||
oldState.filtered !== newResolvedState.filtered ||
oldState.showFilters !== newResolvedState.showFilters ||
(oldState.sortedData &&
!newResolvedState.frozen &&
oldState.resolvedData !== newResolvedState.resolvedData &&
this.props.collapseOnDataChange)
) {
newResolvedState.expanded = {}
}
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
}
// Set page to 0 if filters change
if (oldState.filtered !== newResolvedState.filtered) {
newResolvedState.page = 0
}
// Calculate pageSize all the time
if (newResolvedState.sortedData) {
newResolvedState.pages = newResolvedState.manual
? newResolvedState.pages
: Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
newResolvedState.page = newResolvedState.manual ? newResolvedState.page : Math.max(
newResolvedState.page >= newResolvedState.pages
? newResolvedState.pages - 1
: newResolvedState.page,
0
)
}
return this.setState(newResolvedState, () => {
if (cb) {
cb()
}
if (
oldState.page !== newResolvedState.page ||
oldState.pageSize !== newResolvedState.pageSize ||
oldState.sorted !== newResolvedState.sorted ||
oldState.filtered !== newResolvedState.filtered
) {
this.fireFetchData()
}
})
}
}

View File

@ -1,691 +0,0 @@
import React from 'react'
import _ from './utils'
export default Base =>
class extends Base {
getResolvedState (props, state) {
const resolvedState = {
..._.compactObject(this.state),
..._.compactObject(this.props),
..._.compactObject(state),
..._.compactObject(props),
}
return resolvedState
}
getDataModel (newState, dataChanged) {
const {
columns,
pivotBy = [],
data,
resolveData,
pivotIDKey,
pivotValKey,
subRowsKey,
aggregatedKey,
nestingLevelKey,
originalKey,
indexKey,
groupedByPivotKey,
SubComponent,
} = newState
// Determine if there are Header Groups
const hasHeaderGroups = columns.some(column => column.columns)
// Find the expander column which could be deep in tree of columns
const allColumns = _.iterTree(columns, 'columns')
const expanderColumn = _.getFirstDefined(allColumns)
// If we have SubComponent's we need to make sure we have an expander column
const hasSubComponentAndNoExpanderColumn = SubComponent && !expanderColumn
const columnsWithExpander = hasSubComponentAndNoExpanderColumn
? [{ expander: true }, ...columns]
: [...columns]
const makeDecoratedColumn = (column, parentColumn) => {
let dcol
if (column.expander) {
dcol = {
...this.props.column,
...this.props.expanderDefaults,
...column,
}
} else {
dcol = {
...this.props.column,
...this.props.column,
...column,
}
}
// Ensure minWidth is not greater than maxWidth if set
if (dcol.maxWidth < dcol.minWidth) {
dcol.minWidth = dcol.maxWidth
}
if (parentColumn) {
dcol.parentColumn = parentColumn
}
// First check for string accessor
if (typeof dcol.accessor === 'string') {
dcol.id = dcol.id || dcol.accessor
const accessorString = dcol.accessor
dcol.accessor = row => _.get(row, accessorString)
return dcol
}
// Fall back to functional accessor (but require an ID)
if (dcol.accessor && !dcol.id) {
console.warn(dcol)
throw new Error(
'A column id is required if using a non-string accessor for column above.'
)
}
// Fall back to an undefined accessor
if (!dcol.accessor) {
dcol.accessor = () => undefined
}
return dcol
}
const allDecoratedColumns = []
// Decorate the columns
const decorateAndAddToAll = (columns, parentColumn) => columns.map(column => {
const decoratedColumn = makeDecoratedColumn(column, parentColumn)
if (column.columns) {
decoratedColumn.columns = decorateAndAddToAll(column.columns, column)
}
allDecoratedColumns.push(decoratedColumn)
return decoratedColumn
})
const decoratedColumns = decorateAndAddToAll(columnsWithExpander)
const mapVisibleColumns = columns => columns.map(column => {
if (column.columns) {
const visibleSubColumns = column.columns.filter(
d => (pivotBy.indexOf(d.id) > -1 ? false : _.getFirstDefined(d.show, true))
)
return {
...column,
columns: mapVisibleColumns(visibleSubColumns),
}
}
return column
})
const filterVisibleColumns = columns => columns.filter(
column =>
column.columns
? column.columns.length
: pivotBy.indexOf(column.id) > -1
? false
: _.getFirstDefined(column.show, true)
)
// Build the full array of visible columns - this is an array that contains all columns that
// are not hidden via pivoting
const allVisibleColumns = filterVisibleColumns(mapVisibleColumns(decoratedColumns.slice()))
// Find any custom pivot location
const pivotIndex = allVisibleColumns.findIndex(col => col.pivot)
// Handle Pivot Columns
if (pivotBy.length) {
// Retrieve the pivot columns in the correct pivot order
const pivotColumns = []
pivotBy.forEach(pivotID => {
const found = allDecoratedColumns.find(d => d.id === pivotID)
if (found) {
pivotColumns.push(found)
}
})
const PivotParentColumn = pivotColumns.reduce(
(prev, current) => prev && prev === current.parentColumn && current.parentColumn,
pivotColumns[0].parentColumn
)
let PivotGroupHeader = hasHeaderGroups && PivotParentColumn.Header
PivotGroupHeader = PivotGroupHeader || (() => <strong>Pivoted</strong>)
let pivotColumnGroup = {
Header: PivotGroupHeader,
columns: pivotColumns.map(col => ({
...this.props.pivotDefaults,
...col,
pivoted: true,
})),
}
// Place the pivotColumns back into the visibleColumns
if (pivotIndex >= 0) {
pivotColumnGroup = {
...allVisibleColumns[pivotIndex],
...pivotColumnGroup,
}
allVisibleColumns.splice(pivotIndex, 1, pivotColumnGroup)
} else {
allVisibleColumns.unshift(pivotColumnGroup)
}
}
// Build Visible Columns and Header Groups
const allColumnHeaders = []
const addHeader = column => {
let level = 0
// If this column has children, push them first and add this column to the next level
if (column.columns) {
const childLevels = column.columns.map(addHeader)
level = Math.max(...childLevels) + 1
}
// Add spans above columns without parents (orphans) to fill the space above them
if (allColumnHeaders.length <= level) allColumnHeaders.push([])
if (level > 0) {
// The spans need to contain the shifted headers as children. This finds all of the
// columns in the lower level between the first child of this column and the last child
// of the preceding column (if there is one)
const lowerLevel = allColumnHeaders[level - 1]
const precedingColumn = _.last(allColumnHeaders[level])
const indexOfFirstChildInLowerLevel = lowerLevel.indexOf(column.columns[0])
const indexAfterLastChildInPrecedingColumn = precedingColumn
? lowerLevel.indexOf(_.last(precedingColumn.columns)) + 1
: 0
// If there are ophans, add a span above them
const orphans = lowerLevel.slice(
indexAfterLastChildInPrecedingColumn,
indexOfFirstChildInLowerLevel
)
if (orphans.length) {
allColumnHeaders[level].push({
...this.props.column,
columns: orphans,
})
}
}
allColumnHeaders[level].push(column)
return level
}
allVisibleColumns.forEach(addHeader)
// visibleColumns is an array containing column definitions for the bottom row of TH elements
const visibleColumns = allColumnHeaders.shift()
const headerGroups = allColumnHeaders.reverse()
// Access the data
const accessRow = (d, i, level = 0) => {
const row = {
[originalKey]: d,
[indexKey]: i,
[subRowsKey]: d[subRowsKey],
[nestingLevelKey]: level,
}
allDecoratedColumns.forEach(column => {
if (column.expander) return
row[column.id] = column.accessor(d)
})
if (row[subRowsKey]) {
row[subRowsKey] = row[subRowsKey].map((d, i) => accessRow(d, i, level + 1))
}
return row
}
// // If the data hasn't changed, just use the cached data
let resolvedData = this.resolvedData
// If the data has changed, run the data resolver and cache the result
if (!this.resolvedData || dataChanged) {
resolvedData = resolveData(data)
this.resolvedData = resolvedData
}
// Use the resolved data
resolvedData = resolvedData.map((d, i) => accessRow(d, i))
// TODO: Make it possible to fabricate nested rows without pivoting
const aggregatingColumns = visibleColumns.filter(d => !d.expander && d.aggregate)
// If pivoting, recursively group the data
const aggregate = rows => {
const aggregationValues = {}
aggregatingColumns.forEach(column => {
const values = rows.map(d => d[column.id])
aggregationValues[column.id] = column.aggregate(values, rows)
})
return aggregationValues
}
if (pivotBy.length) {
const groupRecursively = (rows, keys, i = 0) => {
// This is the last level, just return the rows
if (i === keys.length) {
return rows
}
// Group the rows together for this level
let groupedRows = Object.entries(_.groupBy(rows, keys[i])).map(([key, value]) => ({
[pivotIDKey]: keys[i],
[pivotValKey]: key,
[keys[i]]: key,
[subRowsKey]: value,
[nestingLevelKey]: i,
[groupedByPivotKey]: true,
}))
// Recurse into the subRows
groupedRows = groupedRows.map(rowGroup => {
const subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
return {
...rowGroup,
[subRowsKey]: subRows,
[aggregatedKey]: true,
...aggregate(subRows),
}
})
return groupedRows
}
resolvedData = groupRecursively(resolvedData, pivotBy)
}
return {
...newState,
resolvedData,
visibleColumns,
headerGroups,
allDecoratedColumns,
hasHeaderGroups,
}
}
getSortedData (resolvedState) {
const {
manual,
sorted,
filtered,
defaultFilterMethod,
resolvedData,
allDecoratedColumns,
} = resolvedState
const sortMethodsByColumnID = {}
allDecoratedColumns.filter(col => col.sortMethod).forEach(col => {
sortMethodsByColumnID[col.id] = col.sortMethod
})
// Resolve the data from either manual data or sorted data
return {
sortedData: manual
? resolvedData
: this.sortData(
this.filterData(resolvedData, filtered, defaultFilterMethod, allDecoratedColumns),
sorted,
sortMethodsByColumnID
),
}
}
fireFetchData () {
// determine the current state, preferring certain state values over props
const currentState = {
...this.getResolvedState(),
page: this.getStateOrProp('page'),
pageSize: this.getStateOrProp('pageSize'),
filter: this.getStateOrProp('filter'),
}
this.props.onFetchData(currentState, this)
}
getPropOrState (key) {
return _.getFirstDefined(this.props[key], this.state[key])
}
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
}
filterData (data, filtered, defaultFilterMethod, visibleColumns) {
let filteredData = data
if (filtered.length) {
filteredData = filtered.reduce((filteredSoFar, nextFilter) => {
const column = visibleColumns.find(x => x.id === nextFilter.id)
// Don't filter hidden columns or columns that have had their filters disabled
if (!column || column.filterable === false) {
return filteredSoFar
}
const filterMethod = column.filterMethod || defaultFilterMethod
// If 'filterAll' is set to true, pass the entire dataset to the filter method
if (column.filterAll) {
return filterMethod(nextFilter, filteredSoFar, column)
}
return filteredSoFar.filter(row => filterMethod(nextFilter, row, column))
}, filteredData)
// Apply the filter to the subrows if we are pivoting, and then
// filter any rows without subcolumns because it would be strange to show
filteredData = filteredData
.map(row => {
if (!row[this.props.subRowsKey]) {
return row
}
return {
...row,
[this.props.subRowsKey]: this.filterData(
row[this.props.subRowsKey],
filtered,
defaultFilterMethod,
visibleColumns
),
}
})
.filter(row => {
if (!row[this.props.subRowsKey]) {
return true
}
return row[this.props.subRowsKey].length > 0
})
}
return filteredData
}
sortData (data, sorted, sortMethodsByColumnID = {}) {
if (!sorted.length) {
return data
}
const sortedData = (this.props.orderByMethod || _.orderBy)(
data,
sorted.map(sort => {
// Support custom sorting methods for each column
if (sortMethodsByColumnID[sort.id]) {
return (a, b) => sortMethodsByColumnID[sort.id](a[sort.id], b[sort.id], sort.desc)
}
return (a, b) => this.props.defaultSortMethod(a[sort.id], b[sort.id], sort.desc)
}),
sorted.map(d => !d.desc),
this.props.indexKey
)
sortedData.forEach(row => {
if (!row[this.props.subRowsKey]) {
return
}
row[this.props.subRowsKey] = this.sortData(
row[this.props.subRowsKey],
sorted,
sortMethodsByColumnID
)
})
return sortedData
}
getMinRows () {
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
}
// User actions
onPageChange (page) {
const { onPageChange, collapseOnPageChange } = this.props
const newState = { page }
if (collapseOnPageChange) {
newState.expanded = {}
}
this.setStateWithData(newState, () => onPageChange && onPageChange(page))
}
onPageSizeChange (newPageSize) {
const { onPageSizeChange } = this.props
const { pageSize, page } = this.getResolvedState()
// Normalize the page to display
const currentRow = pageSize * page
const newPage = Math.floor(currentRow / newPageSize)
this.setStateWithData(
{
pageSize: newPageSize,
page: newPage,
},
() => onPageSizeChange && onPageSizeChange(newPageSize, newPage)
)
}
sortColumn (column, additive) {
const { sorted, skipNextSort, defaultSortDesc } = this.getResolvedState()
const firstSortDirection = Object.prototype.hasOwnProperty.call(column, 'defaultSortDesc')
? column.defaultSortDesc
: defaultSortDesc
const secondSortDirection = !firstSortDirection
// we can't stop event propagation from the column resize move handlers
// attached to the document because of react's synthetic events
// so we have to prevent the sort function from actually sorting
// if we click on the column resize element within a header.
if (skipNextSort) {
this.setStateWithData({
skipNextSort: false,
})
return
}
const { onSortedChange } = this.props
let newSorted = _.clone(sorted || []).map(d => {
d.desc = _.isSortingDesc(d)
return d
})
if (!_.isArray(column)) {
// Single-Sort
const existingIndex = newSorted.findIndex(d => d.id === column.id)
if (existingIndex > -1) {
const existing = newSorted[existingIndex]
if (existing.desc === secondSortDirection) {
if (additive) {
newSorted.splice(existingIndex, 1)
} else {
existing.desc = firstSortDirection
newSorted = [existing]
}
} else {
existing.desc = secondSortDirection
if (!additive) {
newSorted = [existing]
}
}
} else if (additive) {
newSorted.push({
id: column.id,
desc: firstSortDirection,
})
} else {
newSorted = [
{
id: column.id,
desc: firstSortDirection,
},
]
}
} else {
// Multi-Sort
const existingIndex = newSorted.findIndex(d => d.id === column[0].id)
// Existing Sorted Column
if (existingIndex > -1) {
const existing = newSorted[existingIndex]
if (existing.desc === secondSortDirection) {
if (additive) {
newSorted.splice(existingIndex, column.length)
} else {
column.forEach((d, i) => {
newSorted[existingIndex + i].desc = firstSortDirection
})
}
} else {
column.forEach((d, i) => {
newSorted[existingIndex + i].desc = secondSortDirection
})
}
if (!additive) {
newSorted = newSorted.slice(existingIndex, column.length)
}
// New Sort Column
} else if (additive) {
newSorted = newSorted.concat(
column.map(d => ({
id: d.id,
desc: firstSortDirection,
}))
)
} else {
newSorted = column.map(d => ({
id: d.id,
desc: firstSortDirection,
}))
}
}
this.setStateWithData(
{
page: (!sorted.length && newSorted.length) || !additive ? 0 : this.state.page,
sorted: newSorted,
},
() => onSortedChange && onSortedChange(newSorted, column, additive)
)
}
filterColumn (column, value) {
const { filtered } = this.getResolvedState()
const { onFilteredChange } = this.props
// Remove old filter first if it exists
const newFiltering = (filtered || []).filter(x => x.id !== column.id)
if (value !== '') {
newFiltering.push({
id: column.id,
value,
})
}
this.setStateWithData(
{
filtered: newFiltering,
},
() => onFilteredChange && onFilteredChange(newFiltering, column, value)
)
}
resizeColumnStart (event, column, isTouch) {
event.stopPropagation()
const parentWidth = event.target.parentElement.getBoundingClientRect().width
let pageX
if (isTouch) {
pageX = event.changedTouches[0].pageX
} else {
pageX = event.pageX
}
this.trapEvents = true
this.setStateWithData(
{
currentlyResizing: {
id: column.id,
startX: pageX,
parentWidth,
},
},
() => {
if (isTouch) {
document.addEventListener('touchmove', this.resizeColumnMoving)
document.addEventListener('touchcancel', this.resizeColumnEnd)
document.addEventListener('touchend', this.resizeColumnEnd)
} else {
document.addEventListener('mousemove', this.resizeColumnMoving)
document.addEventListener('mouseup', this.resizeColumnEnd)
document.addEventListener('mouseleave', this.resizeColumnEnd)
}
}
)
}
resizeColumnMoving (event) {
event.stopPropagation()
const { onResizedChange, column } = this.props
const { resized, currentlyResizing, columns } = this.getResolvedState()
const currentColumn = columns.find(c => c.accessor === currentlyResizing.id)
const minResizeWidth = currentColumn ? currentColumn.minResizeWidth : column.minResizeWidth
// Delete old value
const newResized = resized.filter(x => x.id !== currentlyResizing.id)
let pageX
if (event.type === 'touchmove') {
pageX = event.changedTouches[0].pageX
} else if (event.type === 'mousemove') {
pageX = event.pageX
}
const newWidth = Math.max(
currentlyResizing.parentWidth + pageX - currentlyResizing.startX,
minResizeWidth
)
newResized.push({
id: currentlyResizing.id,
value: newWidth,
})
this.setStateWithData(
{
resized: newResized,
},
() => onResizedChange && onResizedChange(newResized, event)
)
}
resizeColumnEnd (event) {
event.stopPropagation()
const isTouch = event.type === 'touchend' || event.type === 'touchcancel'
if (isTouch) {
document.removeEventListener('touchmove', this.resizeColumnMoving)
document.removeEventListener('touchcancel', this.resizeColumnEnd)
document.removeEventListener('touchend', this.resizeColumnEnd)
}
// If its a touch event clear the mouse one's as well because sometimes
// the mouseDown event gets called as well, but the mouseUp event doesn't
document.removeEventListener('mousemove', this.resizeColumnMoving)
document.removeEventListener('mouseup', this.resizeColumnEnd)
document.removeEventListener('mouseleave', this.resizeColumnEnd)
// The touch events don't propagate up to the sorting's onMouseDown event so
// no need to prevent it from happening or else the first click after a touch
// event resize will not sort the column.
if (!isTouch) {
this.setStateWithData({
skipNextSort: true,
currentlyResizing: false,
})
}
}
}

View File

@ -1,181 +0,0 @@
import React, { Component } from 'react'
import classnames from 'classnames'
const defaultButton = props => (
<button type="button" {...props} className="-btn">
{props.children}
</button>
)
export default class ReactTablePagination extends Component {
static defaultProps = {
PreviousComponent: defaultButton,
NextComponent: defaultButton,
renderPageJump: ({
onChange, value, onBlur, onKeyPress, inputType, pageJumpText,
}) => (
<div className="-pageJump">
<input
aria-label={pageJumpText}
type={inputType}
onChange={onChange}
value={value}
onBlur={onBlur}
onKeyPress={onKeyPress}
/>
</div>
),
renderCurrentPage: page => <span className="-currentPage">{page + 1}</span>,
renderTotalPagesCount: pages => <span className="-totalPages">{pages || 1}</span>,
renderPageSizeOptions: ({
pageSize,
pageSizeOptions,
rowsSelectorText,
onPageSizeChange,
rowsText,
}) => (
<span className="select-wrap -pageSizeOptions">
<select
aria-label={rowsSelectorText}
onChange={e => onPageSizeChange(Number(e.target.value))}
value={pageSize}
>
{pageSizeOptions.map((option, i) => (
// eslint-disable-next-line react/no-array-index-key
<option key={i} value={option}>
{`${option} ${rowsText}`}
</option>
))}
</select>
</span>
),
}
constructor (props) {
super(props)
this.getSafePage = this.getSafePage.bind(this)
this.changePage = this.changePage.bind(this)
this.applyPage = this.applyPage.bind(this)
this.state = {
page: props.page,
}
}
componentWillReceiveProps (nextProps) {
if (this.props.page !== nextProps.page) {
this.setState({ page: nextProps.page })
}
}
getSafePage (page) {
if (Number.isNaN(page)) {
page = this.props.page
}
return Math.min(Math.max(page, 0), this.props.pages - 1)
}
changePage (page) {
page = this.getSafePage(page)
this.setState({ page })
if (this.props.page !== page) {
this.props.onPageChange(page)
}
}
applyPage (e) {
if (e) {
e.preventDefault()
}
const page = this.state.page
this.changePage(page === '' ? this.props.page : page)
}
getPageJumpProperties () {
return {
onKeyPress: e => {
if (e.which === 13 || e.keyCode === 13) {
this.applyPage()
}
},
onBlur: this.applyPage,
value: this.state.page === '' ? '' : this.state.page + 1,
onChange: e => {
const val = e.target.value
const page = val - 1
if (val === '') {
return this.setState({ page: val })
}
this.setState({ page: this.getSafePage(page) })
},
inputType: this.state.page === '' ? 'text' : 'number',
pageJumpText: this.props.pageJumpText,
}
}
render () {
const {
// Computed
pages,
// Props
page,
showPageSizeOptions,
pageSizeOptions,
pageSize,
showPageJump,
canPrevious,
canNext,
onPageSizeChange,
className,
PreviousComponent,
NextComponent,
renderPageJump,
renderCurrentPage,
renderTotalPagesCount,
renderPageSizeOptions,
} = this.props
return (
<div className={classnames(className, '-pagination')} style={this.props.style}>
<div className="-previous">
<PreviousComponent
onClick={() => {
if (!canPrevious) return
this.changePage(page - 1)
}}
disabled={!canPrevious}
>
{this.props.previousText}
</PreviousComponent>
</div>
<div className="-center">
<span className="-pageInfo">
{this.props.pageText}{' '}
{showPageJump ? renderPageJump(this.getPageJumpProperties()) : renderCurrentPage(page)}{' '}
{this.props.ofText} {renderTotalPagesCount(pages)}
</span>
{showPageSizeOptions &&
renderPageSizeOptions({
pageSize,
rowsSelectorText: this.props.rowsSelectorText,
pageSizeOptions,
onPageSizeChange,
rowsText: this.props.rowsText,
})}
</div>
<div className="-next">
<NextComponent
onClick={() => {
if (!canNext) return
this.changePage(page + 1)
}}
disabled={!canNext}
>
{this.props.nextText}
</NextComponent>
</div>
</div>
)
}
}

View File

@ -1,169 +0,0 @@
import PropTypes from 'prop-types'
export default {
// General
data: PropTypes.any,
loading: PropTypes.bool,
showPagination: PropTypes.bool,
showPaginationTop: PropTypes.bool,
showPaginationBottom: PropTypes.bool,
showPageSizeOptions: PropTypes.bool,
pageSizeOptions: PropTypes.array,
defaultPageSize: PropTypes.number,
showPageJump: PropTypes.bool,
collapseOnSortingChange: PropTypes.bool,
collapseOnPageChange: PropTypes.bool,
collapseOnDataChange: PropTypes.bool,
freezeWhenExpanded: PropTypes.bool,
sortable: PropTypes.bool,
resizable: PropTypes.bool,
filterable: PropTypes.bool,
defaultSortDesc: PropTypes.bool,
defaultSorted: PropTypes.array,
defaultFiltered: PropTypes.array,
defaultResized: PropTypes.array,
defaultExpanded: PropTypes.object,
defaultFilterMethod: PropTypes.func,
defaultSortMethod: PropTypes.func,
// Controlled State Callbacks
onPageChange: PropTypes.func,
onPageSizeChange: PropTypes.func,
onSortedChange: PropTypes.func,
onFilteredChange: PropTypes.func,
onResizedChange: PropTypes.func,
onExpandedChange: PropTypes.func,
// Pivoting
pivotBy: PropTypes.array,
// Key Constants
pivotValKey: PropTypes.string,
pivotIDKey: PropTypes.string,
subRowsKey: PropTypes.string,
aggregatedKey: PropTypes.string,
nestingLevelKey: PropTypes.string,
originalKey: PropTypes.string,
indexKey: PropTypes.string,
groupedByPivotKey: PropTypes.string,
// Server-side Callbacks
onFetchData: PropTypes.func,
// Classes
className: PropTypes.string,
style: PropTypes.object,
// Component decorators
getProps: PropTypes.func,
getTableProps: PropTypes.func,
getTheadGroupProps: PropTypes.func,
getTheadGroupTrProps: PropTypes.func,
getTheadGroupThProps: PropTypes.func,
getTheadProps: PropTypes.func,
getTheadTrProps: PropTypes.func,
getTheadThProps: PropTypes.func,
getTheadFilterProps: PropTypes.func,
getTheadFilterTrProps: PropTypes.func,
getTheadFilterThProps: PropTypes.func,
getTbodyProps: PropTypes.func,
getTrGroupProps: PropTypes.func,
getTrProps: PropTypes.func,
getTdProps: PropTypes.func,
getTfootProps: PropTypes.func,
getTfootTrProps: PropTypes.func,
getTfootTdProps: PropTypes.func,
getPaginationProps: PropTypes.func,
getLoadingProps: PropTypes.func,
getNoDataProps: PropTypes.func,
getResizerProps: PropTypes.func,
// Global Column Defaults
columns: PropTypes.arrayOf(
PropTypes.shape({
// Renderers
Cell: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
Header: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
Footer: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
Aggregated: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
Pivot: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
PivotValue: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
Expander: PropTypes.oneOfType([PropTypes.element, PropTypes.string, PropTypes.func]),
Filter: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
// All Columns
sortable: PropTypes.bool, // use table default
resizable: PropTypes.bool, // use table default
filterable: PropTypes.bool, // use table default
show: PropTypes.bool,
minWidth: PropTypes.number,
minResizeWidth: PropTypes.number,
// Cells only
className: PropTypes.string,
style: PropTypes.object,
getProps: PropTypes.func,
// Pivot only
aggregate: PropTypes.func,
// Headers only
headerClassName: PropTypes.string,
headerStyle: PropTypes.object,
getHeaderProps: PropTypes.func,
// Footers only
footerClassName: PropTypes.string,
footerStyle: PropTypes.object,
getFooterProps: PropTypes.func,
filterMethod: PropTypes.func,
filterAll: PropTypes.bool,
sortMethod: PropTypes.func,
})
),
// Global Expander Column Defaults
expanderDefaults: PropTypes.shape({
sortable: PropTypes.bool,
resizable: PropTypes.bool,
filterable: PropTypes.bool,
width: PropTypes.number,
}),
pivotDefaults: PropTypes.object,
// Text
previousText: PropTypes.node,
nextText: PropTypes.node,
loadingText: PropTypes.node,
noDataText: PropTypes.node,
pageText: PropTypes.node,
ofText: PropTypes.node,
rowsText: PropTypes.node,
pageJumpText: PropTypes.node,
rowsSelectorText: PropTypes.node,
// Components
TableComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
TheadComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
TbodyComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
TrGroupComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
TrComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
ThComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
TdComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
TfootComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
FilterComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
ExpanderComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
PivotValueComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
AggregatedComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
// this is a computed default generated using
PivotComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
PaginationComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
PreviousComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
NextComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
LoadingComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
NoDataComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
ResizerComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
PadRowComponent: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
}

195
src/useFlexLayout.js Executable file
View File

@ -0,0 +1,195 @@
import { useMemo, useState } from 'react'
import PropTypes from 'prop-types'
import { getFirstDefined, sum } from './utils'
const propTypes = {
defaultFlex: PropTypes.number,
}
export const actions = {}
export default function useFlexLayout (api, props = {}) {
// Validate props
PropTypes.checkPropTypes(propTypes, props, 'property', 'useFlexLayout')
const {
hooks: {
getRowProps, getHeaderRowProps, getHeaderProps, getCellProps,
},
visibleColumns,
} = api
const { defaultFlex = 1 } = props
const [columnMeasurements, setColumnMeasurements] = useState({})
const rowStyles = useMemo(
() => {
let sumWidth = 0
visibleColumns.forEach(column => {
const { width, minWidth } = getSizesForColumn(
column,
defaultFlex,
undefined,
undefined,
api
)
if (width) {
sumWidth += width
} else if (minWidth) {
sumWidth += minWidth
} else {
sumWidth += defaultFlex
}
})
return {
style: {
display: 'flex',
minWidth: `${sumWidth}px`,
},
}
},
[visibleColumns]
)
getRowProps.push(row => rowStyles)
getHeaderRowProps.push(row => rowStyles)
getHeaderProps.push(column => ({
style: {
boxSizing: 'border-box',
...getStylesForColumn(column, columnMeasurements, defaultFlex, api),
},
// [refKey]: el => {
// renderedCellInfoRef.current[key] = {
// column,
// el
// };
// },
}))
getCellProps.push(cell => ({
style: {
display: 'block',
boxSizing: 'border-box',
...getStylesForColumn(cell.column, columnMeasurements, defaultFlex, undefined, api),
},
// [refKey]: el => {
// renderedCellInfoRef.current[columnPathStr] = {
// column,
// el
// };
// }
}))
return {
rowStyles,
}
}
function getStylesForColumn (column, columnMeasurements, defaultFlex, api) {
const { flex, width, maxWidth } = getSizesForColumn(column, columnMeasurements, defaultFlex, api)
return {
flex: `${flex} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`,
}
}
function getSizesForColumn (
{
columns, id, width, minWidth, maxWidth,
},
columnMeasurements,
defaultFlex,
api
) {
if (columns) {
columns = columns
.map(column => getSizesForColumn(column, columnMeasurements, defaultFlex, api))
.filter(Boolean)
if (!columns.length) {
return false
}
const flex = sum(columns.map(col => col.flex))
const width = sum(columns.map(col => col.width))
const maxWidth = sum(columns.map(col => col.maxWidth))
return {
flex,
width,
maxWidth,
}
}
return {
flex: width ? 0 : defaultFlex,
width:
width === 'auto'
? columnMeasurements[id] || defaultFlex
: getFirstDefined(width, minWidth, defaultFlex),
maxWidth,
}
}
// const resetRefs = () => {
// if (debug) console.info("resetRefs");
// renderedCellInfoRef.current = {};
// };
// const calculateAutoWidths = () => {
// RAF(() => {
// const newColumnMeasurements = {};
// Object.values(renderedCellInfoRef.current).forEach(({ column, el }) => {
// if (!el) {
// return;
// }
// let measurement = 0;
// const measureChildren = children => {
// if (children) {
// [].slice.call(children).forEach(child => {
// measurement = Math.max(
// measurement,
// Math.ceil(child.offsetWidth) || 0
// );
// measureChildren(child.children);
// });
// }
// return measurement;
// };
// const parentDims = getElementDimensions(el);
// measureChildren(el.children);
// newColumnMeasurements[column.id] = Math.max(
// newColumnMeasurements[column.id] || 0,
// measurement + parentDims.paddingLeft + parentDims.paddingRight
// );
// });
// const oldKeys = Object.keys(columnMeasurements);
// const newKeys = Object.keys(newColumnMeasurements);
// const needsUpdate =
// oldKeys.length !== newKeys.length ||
// oldKeys.some(key => {
// return columnMeasurements[key] !== newColumnMeasurements[key];
// });
// if (needsUpdate) {
// setState(old => {
// return {
// ...old,
// columnMeasurements: newColumnMeasurements
// };
// }, actions.updateAutoWidth);
// }
// });
// };

155
src/usePagination.js Executable file
View File

@ -0,0 +1,155 @@
import { useMemo, useState, useLayoutEffect } from 'react'
import PropTypes from 'prop-types'
import { warnUnknownProps } from './utils'
import useControlledState from './hooks/useControlledState'
const propTypes = {
defaultPageSize: PropTypes.number,
defaultPageIndex: PropTypes.number,
pageSize: PropTypes.number,
pages: PropTypes.number,
pageIndex: PropTypes.number,
onStateChange: PropTypes.func,
stateReducer: PropTypes.func,
subRowsKey: PropTypes.string,
debug: PropTypes.bool,
}
export const actions = {
pageChange: '__pageChange__',
}
export default function usePagination (
{
debug: parentDebug, rows, prepareRows, groupedRows, sortedRows, filteredRows,
},
props = {}
) {
// Validate props
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
// Destructure props
const {
defaultPageSize = 10,
defaultPageIndex = 0,
pageSize: userPageSize,
pageIndex: userPageIndex,
state: userState,
manualPagination,
pageCount: userPageCount,
pageOptions: userPageOptions,
debug = parentDebug,
...rest
} = props
warnUnknownProps(rest)
const defaultState = useState({})
const localState = userState || defaultState
// Build the controllable state
const [{ pageSize, pageIndex }, setState] = useControlledState(
localState,
{
pageSize: defaultPageSize,
pageIndex: defaultPageIndex,
},
{
pageSize: userPageSize,
pageIndex: userPageIndex,
}
)
useLayoutEffect(
() => {
if (manualPagination) {
return
}
setState(
old => ({
...old,
pageIndex: 0,
}),
actions.pageChange
)
},
[groupedRows, sortedRows, filteredRows]
)
const { pages, pageCount } = useMemo(
() => {
if (manualPagination) {
return {
pages: [rows],
pageCount: userPageCount,
}
}
if (debug) console.info('getPages')
// Create a new pages with the first page ready to go.
const pages = rows.length ? [] : [[]]
// Start the pageIndex and currentPage cursors
let cursor = 0
while (cursor < rows.length) {
const end = cursor + pageSize
pages.push(rows.slice(cursor, end))
cursor = end
}
const pageCount = pages.length
return {
pages,
pageCount,
pageOptions,
}
},
[rows, pageSize, userPageCount]
)
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
const page = manualPagination ? rows : pages[pageIndex] || []
const canPreviousPage = pageIndex > 0
const canNextPage = pageIndex < pageCount - 1
const gotoPage = pageIndex => {
if (debug) console.info('gotoPage')
return setState(old => {
if (pageIndex < 0 || pageIndex > pageCount - 1) {
return old
}
return {
...old,
pageIndex,
}
}, actions.pageChange)
}
const previousPage = () => gotoPage(pageIndex - 1)
const nextPage = () => gotoPage(pageIndex + 1)
const setPageSize = pageSize => {
setState(old => ({
...old,
pageSize,
}))
}
prepareRows(page)
return {
pages,
pageIndex,
pageOptions,
page,
canPreviousPage,
canNextPage,
gotoPage,
previousPage,
nextPage,
setPageSize,
}
}

470
src/useReactTable.js Executable file
View File

@ -0,0 +1,470 @@
import { useState, useMemo, useRef } from 'react'
import PropTypes from 'prop-types'
//
import {
defaultOrderByFn,
defaultSortByFn,
defaultGroupByFn,
defaultFilterFn,
flexRender,
findExpandedDepth,
applyHooks,
mergeProps,
warnUnknownProps,
} from './utils'
// Internal Hooks
import useControlledState from './hooks/useControlledState'
import useColumns from './hooks/useColumns'
import useTableMethods from './hooks/useTableMethods'
import useAccessedRows from './hooks/useAccessedRows'
import useGroupedRows from './hooks/useGroupedRows'
import useFilteredRows from './hooks/useFilteredRows'
import useSortedRows from './hooks/useSortedRows'
import useExpandedRows from './hooks/useExpandedRows'
const defaultFlex = 1
const renderErr =
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
export const actions = {
sortByChange: '__sortByChange',
updateAutoWidth: '__updateAutoWidth__',
toggleGroupBy: '__toggleGroupBy__',
toggleExpanded: '__toggleExpanded__',
setExpanded: '__setExpanded__',
setFilter: '__setFilter__',
setAllFilters: '__setAllFilters__',
}
const propTypes = {
// General
data: PropTypes.any,
columns: PropTypes.arrayOf(
PropTypes.shape({
aggregate: PropTypes.func,
filterFn: PropTypes.func,
filterAll: PropTypes.bool,
sortByFn: PropTypes.func,
resolvedDefaultSortDesc: PropTypes.bool,
canSortBy: PropTypes.bool,
canGroupBy: PropTypes.bool,
Cell: PropTypes.any,
Header: PropTypes.any,
Filter: PropTypes.any,
})
),
defaultFilters: PropTypes.array,
defaultSortBy: PropTypes.array,
defaultGroupBy: PropTypes.array,
groupBy: PropTypes.array,
filters: PropTypes.array,
sortBy: PropTypes.array,
filterFn: PropTypes.func,
sortByFn: PropTypes.func,
orderByFn: PropTypes.func,
groupByFn: PropTypes.func,
manualGrouping: PropTypes.bool,
manualFilters: PropTypes.bool,
manualSorting: PropTypes.bool,
disableGrouping: PropTypes.bool,
disableFilters: PropTypes.bool,
disableSorting: PropTypes.bool,
defaultSortDesc: PropTypes.bool,
disableMultiSort: PropTypes.bool,
subRowsKey: PropTypes.string,
expandedKey: PropTypes.string,
userAggregations: PropTypes.object,
debug: PropTypes.bool,
}
export default function useReactTable (props) {
// Validate props
PropTypes.checkPropTypes(propTypes, props, 'property', 'useReactTable')
// Destructure props
const {
data = [],
state: userState,
columns: userColumns,
sortBy: userSortBy,
groupBy: userGroupBy,
filters: userFilters,
expanded: userExpanded,
defaultSortBy = [],
defaultGroupBy = [],
defaultFilters = {},
defaultExpanded = {},
manualGrouping,
manualFilters,
manualSorting,
disableSorting,
disableGrouping,
disableFilters,
defaultSortDesc,
disableMultiSort,
subRowsKey = 'subRows',
expandedKey = 'expanded',
filterFn = defaultFilterFn,
sortByFn = defaultSortByFn,
orderByFn = defaultOrderByFn,
groupByFn = defaultGroupByFn,
userAggregations = {},
debug,
...rest
} = props
warnUnknownProps(rest)
const defaultState = useState({})
const localState = userState || defaultState
// Build the controllable state
const [{
sortBy, groupBy, filters, expanded,
}, setState] = useControlledState(
localState,
{
sortBy: defaultSortBy,
groupBy: defaultGroupBy,
filters: defaultFilters,
expanded: defaultExpanded,
},
{
sortBy: userSortBy,
groupBy: userGroupBy,
filters: userFilters,
expanded: userExpanded,
}
)
const expandedDepth = findExpandedDepth(expanded)
const renderedCellInfoRef = useRef({})
// Build the base column
let { columns, headerGroups, headers } = useColumns({
debug,
groupBy,
userColumns,
defaultFlex,
renderedCellInfoRef,
disableSorting,
disableGrouping,
disableFilters,
})
// Use data and columns as memoization
const accessedRows = useAccessedRows({
debug,
data,
columns,
subRowsKey,
})
// Use rows and groupBy as memoization for row grouping
const groupedRows = useGroupedRows({
debug,
rows: accessedRows,
groupBy,
columns,
groupByFn,
manualGrouping,
userAggregations,
})
const filteredRows = useFilteredRows({
debug,
rows: groupedRows,
filters,
columns,
filterFn,
manualFilters,
})
const sortedRows = useSortedRows({
debug,
rows: filteredRows,
sortBy,
columns,
orderByFn,
sortByFn,
manualSorting,
})
const rows = useExpandedRows({
debug,
rows: sortedRows,
expanded,
expandedKey,
columns,
groupBy,
setState,
actions,
})
// Mutate columns to reflect sorting state
columns.forEach(column => {
const { id } = column
column.sorted = sortBy.find(d => d.id === id)
column.sortedIndex = sortBy.findIndex(d => d.id === id)
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
column.filterVal = filters[id]
})
// Public API
const {
toggleExpandedByPath,
toggleSortByID,
toggleGroupBy,
setFilter,
setAllFilters,
} = useTableMethods({
debug,
setState,
actions,
groupBy,
columns,
defaultSortDesc,
filters,
})
const state = {
// State
columns,
expandedDepth,
accessedRows,
groupedRows,
filteredRows,
sortedRows,
rows,
headerGroups,
renderedCellInfoRef,
disableMultiSort,
// Controllable state
groupBy,
filters,
sortBy,
expanded,
}
const api = {
// State manager,
...state,
// Methods
toggleExpandedByPath,
toggleSortByID,
toggleGroupBy,
setFilter,
setAllFilters,
}
columns.forEach(column => attachApi(column, api))
headers.forEach(header => attachApi(header, api))
const visibleColumns = columns.filter(column => {
column.visible = typeof column.show === 'function' ? column.show(state) : !!column.show
return column.visible
})
state.visibleColumns = visibleColumns
api.visibleColumns = visibleColumns
api.hooks = {
getTableProps: [],
getRowProps: [],
getHeaderRowProps: [],
getCellProps: [],
getHeaderProps: [],
getSortByToggleProps: [],
getGroupByToggleProps: [],
}
api.getTableProps = props => mergeProps(
{
style: {
overflowX: 'auto',
},
},
applyHooks(api.hooks.getTableProps),
props
)
api.getRowProps = props => mergeProps(applyHooks(api.hooks.getRowProps), props)
// Filter out hidden header groups or header groups that
// have no visible columns, then add the getRowProps method
headerGroups = useMemo(
() =>
headerGroups.filter((headerGroup, i) => {
headerGroup.headers = headerGroup.headers.filter(header => {
const recurse = columns => columns.filter(column => {
if (column.columns) {
return recurse(column.columns)
}
return column.visible
}).length
if (header.columns) {
return recurse(header.columns)
}
return header.visible
})
if (headerGroup.headers.length) {
headerGroup.getRowProps = (props = {}) => mergeProps(
{
key: [`header${i}`].join('_'),
},
applyHooks(api.hooks.getHeaderRowProps, headerGroup),
props
)
return true
}
return false
}),
[headerGroups]
)
// A function that mutates and prepares page rows with methods
api.prepareRows = rows => {
rows.forEach((row, i) => {
const { path } = row
row.getRowProps = props => mergeProps(
{ key: ['row', i].join('_') },
applyHooks(api.hooks.getRowProps, row),
props
)
row.toggleExpanded = set => toggleExpandedByPath(path, set)
row.cells = row.cells.filter(cell => cell.column.visible);
[row.groupByCell, ...row.cells].forEach(cell => {
if (!cell) {
return
}
const { column } = cell
cell.getCellProps = props => {
const columnPathStr = [i, column.id].join('_')
return mergeProps(
{
key: ['cell', columnPathStr].join('_'),
},
applyHooks(api.hooks.getCellProps, cell),
props
)
}
cell.render = (type, userProps = {}) => {
if (!type) {
throw new Error(
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
)
}
return flexRender(column[type], {
...api,
...cell,
...userProps,
})
}
})
})
}
const makeHook = api => {
api.hook = (hook, ...args) => {
const result = hook(api, ...args) || {}
const newApi = {
...api,
...result,
}
makeHook(newApi)
return newApi
}
}
makeHook(api)
return api
}
function attachApi (column, api) {
const {
id, canSortBy, canGroupBy, canFilter,
} = column
const { toggleSortByID, toggleGroupBy, setFilter } = api
column.render = (type, userProps = {}) => {
if (!type) {
throw new Error(renderErr)
}
return flexRender(column[type], {
...api,
...column,
...userProps,
})
}
if (canSortBy) {
column.toggleSortBy = (desc, multi) => toggleSortByID(id, desc, multi)
}
if (canGroupBy) {
column.toggleGroupBy = () => toggleGroupBy(id)
}
if (canFilter) {
column.setFilter = val => setFilter(id, val)
}
column.getSortByToggleProps = props => mergeProps(
{
onClick: canSortBy
? e => {
e.persist()
column.toggleSortBy(undefined, !api.disableMultiSort && e.shiftKey)
}
: undefined,
style: {
cursor: canSortBy ? 'pointer' : undefined,
},
title: 'Toggle SortBy',
},
applyHooks(api.hooks.getSortByToggleProps, column),
props
)
column.getGroupByToggleProps = props => mergeProps(
{
onClick: canGroupBy
? e => {
e.persist()
column.toggleGroupBy()
}
: undefined,
style: {
cursor: canGroupBy ? 'pointer' : undefined,
},
title: 'Toggle GroupBy',
},
applyHooks(api.hooks.getGroupByToggleProps, column),
props
)
column.getHeaderProps = props => mergeProps(
{
key: ['header', column.id].join('_'),
},
applyHooks(api.hooks.getHeaderProps, column),
props
)
}

294
src/utils.js Normal file → Executable file
View File

@ -1,134 +1,53 @@
import React from 'react'
import classnames from 'classnames'
//
export default {
get,
set,
takeRight,
last,
orderBy,
range,
remove,
clone,
leaves,
iterTree,
getFirstDefined,
sum,
makeTemplateComponent,
groupBy,
isArray,
splitProps,
compactObject,
isSortingDesc,
normalizeComponent,
asPx,
}
function get (obj, path, def) {
export function getBy (obj, path, def) {
if (!path) {
return obj
}
const pathObj = makePathArray(path)
let val
try {
val = pathObj.reduce((current, pathPart) => current[pathPart], obj)
val = pathObj.reduce((cursor, pathPart) => cursor[pathPart], obj)
} catch (e) {
// continue regardless of error
}
return typeof val !== 'undefined' ? val : def
}
function set (obj = {}, path, value) {
const keys = makePathArray(path)
let keyPart
let cursor = obj
while ((keyPart = keys.shift()) && keys.length) {
if (!cursor[keyPart]) {
cursor[keyPart] = {}
}
cursor = cursor[keyPart]
}
cursor[keyPart] = value
return obj
}
function takeRight (arr, n) {
const start = n > arr.length ? 0 : arr.length - n
return arr.slice(start)
}
function last (arr) {
return arr[arr.length - 1]
}
function range (n) {
const arr = []
for (let i = 0; i < n; i += 1) {
arr.push(n)
}
return arr
}
function orderBy (arr, funcs, dirs, indexKey) {
return arr.sort((rowA, rowB) => {
export function defaultOrderByFn (arr, funcs, dirs) {
return [...arr].sort((rowA, rowB) => {
for (let i = 0; i < funcs.length; i += 1) {
const comp = funcs[i]
const sortFn = funcs[i]
const desc = dirs[i] === false || dirs[i] === 'desc'
const sortInt = comp(rowA, rowB)
if (sortInt) {
const sortInt = sortFn(rowA, rowB)
if (sortInt !== 0) {
return desc ? -sortInt : sortInt
}
}
// Use the row index for tie breakers
return dirs[0] ? rowA[indexKey] - rowB[indexKey] : rowB[indexKey] - rowA[indexKey]
return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index
})
}
function remove (a, b) {
return a.filter((o, i) => {
const r = b(o)
if (r) {
a.splice(i, 1)
return true
}
return false
})
}
function clone (a) {
try {
return JSON.parse(
JSON.stringify(a, (key, value) => {
if (typeof value === 'function') {
return value.toString()
}
return value
})
)
} catch (e) {
return a
export function defaultSortByFn (a, b, desc) {
// force null and undefined to the bottom
a = a === null || a === undefined ? '' : a
b = b === null || b === undefined ? '' : b
// force any string values to lowercase
a = typeof a === 'string' ? a.toLowerCase() : a
b = typeof b === 'string' ? b.toLowerCase() : b
// Return either 1 or -1 to indicate a sort priority
if (a > b) {
return 1
}
}
function leaves (root, childProperty) {
if (Object.prototype.hasOwnProperty.call(root, childProperty)) {
const children = root[childProperty].map(child => leaves(child, childProperty))
return [].concat(...children)
if (a < b) {
return -1
}
return [root]
// 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 iterTree (root, childProperty) {
if (Object.prototype.hasOwnProperty.call(root, childProperty)) {
const children = root[childProperty].map(child => leaves(child, childProperty))
return [].concat(root, ...children)
}
return [root]
}
function getFirstDefined (...args) {
export function getFirstDefined (...args) {
for (let i = 0; i < args.length; i += 1) {
if (typeof args[i] !== 'undefined') {
return args[i]
@ -136,44 +55,108 @@ function getFirstDefined (...args) {
}
}
function sum (arr) {
return arr.reduce((a, b) => a + b, 0)
}
function makeTemplateComponent (compClass, displayName) {
if (!displayName) {
throw new Error('No displayName found for template component:', compClass)
}
const cmp = ({ children, className, ...rest }) => (
<div className={classnames(compClass, className)} {...rest}>
{children}
</div>
)
cmp.displayName = displayName
return cmp
}
function groupBy (xs, key) {
return xs.reduce((rv, x, i) => {
const resKey = typeof key === 'function' ? key(x, i) : x[key]
rv[resKey] = isArray(rv[resKey]) ? rv[resKey] : []
rv[resKey].push(x)
return rv
export function defaultGroupByFn (rows, grouper) {
return rows.reduce((prev, row, i) => {
const resKey = typeof grouper === 'function' ? grouper(row.values, i) : row.values[grouper]
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
prev[resKey].push(row)
return prev
}, {})
}
function asPx (value) {
value = Number(value)
return Number.isNaN(value) ? null : `${value}px`
export function defaultFilterFn (row, id, value, column) {
return row.values[id] !== undefined
? String(row.values[id])
.toLowerCase()
.includes(String(value).toLowerCase())
: true
}
function isArray (a) {
return Array.isArray(a)
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)
}
// ########################################################################
// Non-exported Helpers
// ########################################################################
export function getElementDimensions (element) {
const rect = element.getBoundingClientRect()
const style = window.getComputedStyle(element)
const margins = {
left: parseInt(style.marginLeft),
right: parseInt(style.marginRight),
}
const padding = {
left: parseInt(style.paddingLeft),
right: parseInt(style.paddingRight),
}
return {
left: Math.ceil(rect.left),
width: Math.ceil(rect.width),
outerWidth: Math.ceil(rect.width + margins.left + margins.right + padding.left + padding.right),
marginLeft: margins.left,
marginRight: margins.right,
paddingLeft: padding.left,
paddingRight: padding.right,
scrollWidth: element.scrollWidth,
}
}
export function flexRender (Comp, props) {
if (typeof Comp === 'function') {
return Object.getPrototypeOf(Comp).isReactComponent ? <Comp {...props} /> : Comp(props)
}
return Comp
}
export function findExpandedDepth (obj, depth = 1) {
return Object.values(obj).reduce((prev, curr) => {
if (typeof curr === 'object') {
return Math.max(prev, findExpandedDepth(curr, depth + 1))
}
return depth
}, 0)
}
export const mergeProps = (...groups) => {
let props = {}
groups.forEach(({ style = {}, className, ...rest } = {}) => {
props = {
...props,
...rest,
style: {
...(props.style || {}),
...style,
},
className: [props.className, className].filter(Boolean).join(' '),
}
})
return props
}
export const applyHooks = (hooks, ...args) =>
hooks.reduce((prev, next) => mergeProps(prev, next(...args)), {})
export const warnUnknownProps = props => {
if (Object.keys(props).length) {
throw new Error(
`Unknown options passed to useReactTable:
${JSON.stringify(props, null, 2)}`
)
}
}
export function sum (arr) {
return arr.reduce((prev, curr) => prev + curr, 0)
}
function makePathArray (obj) {
return flattenDeep(obj)
@ -184,7 +167,7 @@ function makePathArray (obj) {
}
function flattenDeep (arr, newArr = []) {
if (!isArray(arr)) {
if (!Array.isArray(arr)) {
newArr.push(arr)
} else {
for (let i = 0; i < arr.length; i += 1) {
@ -193,40 +176,3 @@ function flattenDeep (arr, newArr = []) {
}
return newArr
}
function splitProps ({ className, style, ...rest }) {
return {
className,
style,
rest: rest || {},
}
}
function compactObject (obj) {
const newObj = {}
if (obj) {
Object.keys(obj).map(key => {
if (
Object.prototype.hasOwnProperty.call(obj, key) &&
obj[key] !== undefined &&
typeof obj[key] !== 'undefined'
) {
newObj[key] = obj[key]
}
return true
})
}
return newObj
}
function isSortingDesc (d) {
return !!(d.sort === 'desc' || d.desc === true || d.asc === false)
}
function normalizeComponent (Comp, params = {}, fallback = Comp) {
return typeof Comp === 'function' ? (
<Comp {...params} />
) : (
fallback
)
}