Compare commits

...
Author SHA1 Message Date
Tanner Linsley b629d3ebbb 5.3.0 2017-02-16 10:41:46 -07:00
Tanner Linsley 1ebea1cac8 Added more customization for functional rendering 2017-02-16 10:41:35 -07:00
Tanner Linsley b16eee1bba Added support for Column Footers 2017-02-16 09:29:12 -07:00
22 changed files with 903 additions and 461 deletions
+2
View File
@@ -23,6 +23,7 @@ import OneHundredKRows from '../stories/OneHundredKRows.js'
import FunctionalRendering from '../stories/FunctionalRendering.js' import FunctionalRendering from '../stories/FunctionalRendering.js'
import CustomExpanderPosition from '../stories/CustomExpanderPosition.js' import CustomExpanderPosition from '../stories/CustomExpanderPosition.js'
import NoDataText from '../stories/NoDataText.js' import NoDataText from '../stories/NoDataText.js'
import Footers from '../stories/Footers.js'
// //
configure(() => { configure(() => {
storiesOf('1. Docs') storiesOf('1. Docs')
@@ -51,4 +52,5 @@ configure(() => {
.add('Functional Rendering', FunctionalRendering) .add('Functional Rendering', FunctionalRendering)
.add('Custom Expander Position', CustomExpanderPosition) .add('Custom Expander Position', CustomExpanderPosition)
.add('Custom "No Data" Text', NoDataText) .add('Custom "No Data" Text', NoDataText)
.add('Footers', Footers)
}, module) }, module)
+4
View File
@@ -1,3 +1,7 @@
body{ body{
padding: 20px; padding: 20px;
} }
strong {
font-weight: bold
}
+81 -35
View File
@@ -24,6 +24,9 @@
<a href="https://twitter.com/tannerlinsley" target="\_parent"> <a href="https://twitter.com/tannerlinsley" target="\_parent">
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" /> <img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
</a> </a>
<a href="https://cash.me/$tannerlinsley" target="\_parent">
<img alt="" src="https://img.shields.io/badge/%24-Donate-brightgreen.svg" />
</a>
## Features ## Features
@@ -185,6 +188,9 @@ These are all of the available props (and their default values) for the main `<R
getTrProps: () => ({}), getTrProps: () => ({}),
getThProps: () => ({}), getThProps: () => ({}),
getTdProps: () => ({}), getTdProps: () => ({}),
getTfootProps: () => ({}),
getTfootTrProps: () => ({}),
getTfootThProps: () => ({}),
getPaginationProps: () => ({}), getPaginationProps: () => ({}),
getLoadingProps: () => ({}), getLoadingProps: () => ({}),
getNoDataProps: () => ({}), getNoDataProps: () => ({}),
@@ -204,6 +210,11 @@ These are all of the available props (and their default values) for the main `<R
headerClassName: '', headerClassName: '',
headerStyle: {}, headerStyle: {},
getHeaderProps: () => ({}) getHeaderProps: () => ({})
// Footers only
footer: undefined,
footerClassName: '',
footerStyle: {},
getFooterProps: () => ({})
}, },
// Text // Text
@@ -268,13 +279,20 @@ Or just define them as props
// viewIndex == the index of the row in the current page // viewIndex == the index of the row in the current page
// Header & HeaderGroup Options // Header & HeaderGroup Options
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>, header: 'Header Name', a function that returns a primitive, or JSX / React Component eg. ({data, column}) => <div>Header Name</div>,
headerClassName: '', // Set the classname of the `th` element of the column headerClassName: '', // Set the classname of the `th` element of the column
headerStyle: {}, // Set the style of the `th` element of the column headerStyle: {}, // Set the style of the `th` element of the column
getHeaderProps: (state, rowInfo, column, instance) => ({}) // a function that returns props to decorate the `th` element of the column
// Header Groups only // Header Groups only
columns: [...] // See Header Groups section below columns: [...] // See Header Groups section below
// Footer
footer: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
footerClassName: '', // Set the classname of the `td` element of the column's footer
footerStyle: {}, // Set the style of the `td` element of the column's footer
getFooterProps: (state, rowInfo, column, instance) => ({}) // a function that returns props to decorate the `td` element of the column's footer
}] }]
``` ```
@@ -297,8 +315,8 @@ const columns = [{
}] }]
``` ```
## Custom Cell and Header Rendering ## Custom Cell, Header and Footer Rendering
You can use any react component or JSX to display column headers or cells. Any component you use will be passed the following props: You can use any react component or JSX to display content in column headers, cells and footers. Any component you use will be passed the following props (if available):
- `row` - Original row from your data - `row` - Original row from your data
- `rowValues` - The post-accessed values from the original row - `rowValues` - The post-accessed values from the original row
- `index` - The index of the row - `index` - The index of the row
@@ -340,17 +358,19 @@ const columns = [{
``` ```
## Styles ## Styles
React-table ships with a minimal and clean stylesheet to get you on your feet quickly. It's located at `react-table/react-table.css`. - React-table ships with a minimal and clean stylesheet to get you on your feet quickly.
- The stylesheet is located at `react-table/react-table.css`.
- There are countless ways to import a stylesheet. If you have questions on how to do so, consult the documentation of your build system.
#### Built-in Styles #### Classes
- Adding a `-striped` className to ReactTable will slightly color odd numbered rows for legibility - Adding a `-striped` className to ReactTable will slightly color odd numbered rows for legibility
- Adding a `-highlight` className to ReactTable will highlight any row as you hover over it - Adding a `-highlight` className to ReactTable will highlight any row as you hover over it
#### CSS Styles #### CSS
We think the default styles looks great! But, if you prefer a more custom look, all of the included styles are easily overridable. Every single component contains a unique class that makes it super easy to customize. Just go for it! We think the default styles looks great! But, if you prefer a more custom look, all of the included styles are easily overridable. Every single component contains a unique class that makes it super easy to customize. Just go for it!
#### JS Styles #### JS Styles
Every single react-table element and `get[ComponentName]Props` callback support classes (powered by `classname` and js styles. Every single react-table element and `get[ComponentName]Props` callback supports `classname` and `style` props.
## Custom Props ## Custom Props
@@ -561,51 +581,77 @@ Here are the props and their corresponding callbacks that control the state of t
``` ```
## Functional Rendering ## Functional Rendering
Possibly one of the coolest features of React-Table is its ability to expose internal state for custom render logic. The easiest way to do this is to optionally pass a function as a child of `<ReactTable />`. Possibly one of the coolest features of React-Table is its ability to expose internal components and state for custom render logic. The easiest way to do this is to pass a function as the child of `<ReactTable />`.
The function you pass will be called with the following items: The function you pass will be called with the following items:
- Fully-resolved state of the table - Fully-resolved state of the table
- The standard table generator - The standard table component (including individual partials as properties of this component)
- The instance of the component - The instance of the component
You can then return any JSX or react you want! This turns out to be perfect for: You can then return any JSX or react you want! This turns out to be perfect for:
- Accessing the internal state of the table before rendering the table - Reordering the internal components of the table
- Decorating the table with more UI - Accessing the internal state of the table without a `ref`
- Building your own 100% custom display logic, while utilizing the state and methods of the table component - Decorating the table or extending it with your own UI
- Building your own custom display logic
Example: Custom pagination order:
```javascript ```javascript
<ReactTable <ReactTable
columns={columns}
data={data} data={data}
... columns={columns}
> >
{(state, makeTable, instance) => { {(state, {
// Now you have full access to the state of the table! Root,
state.decoratedColumns === [...] // all of the columns (with id's and meta) Table,
state.visibleColumns === [...] // all of the columns (with id's and meta) HeaderGroups,
state.visibleColumns === [...] // all of the columns (with id's and meta) Headers,
// etc. Rows,
Footers,
// `makeTable` is a function that returns the standard table markup Pagination,
return makeTable() NoData,
Loading
// So add some decoration! }, instance) => {
return ( return (
<div> <Root>
<customPivotBySelect /> <Pagination />
<customColumnHideShow /> <Table>
<customAnything /> <HeaderGroups />
{makeTable()} <Headers />
</div> <Rows />
<Footers />
</Table>
<NoData />
<Loading />
</Root>
) )
// The possibilities are endless!!!
}} }}
</ReactTable> </ReactTable>
``` ```
Accessing internal state and wrapping with more UI:
```javascript
<ReactTable
data={data}
columns={columns}
>
{(state, Table, instance) => {
return (
<div style={{
background: '#ffcf00',
borderRadius: '5px',
overflow: 'hidden',
padding: '5px'
}}>
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
<Table />
</div>
)
}}
</ReactTable>
```
The possibilities are endless!
## Multi-Sort ## Multi-Sort
When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy! When clicking on a column header, hold shift to multi-sort! You can toggle `ascending` `descending` and `none` for multi-sort columns. Clicking on a header without holding shift will clear the multi-sort and replace it with the single sort of that column. It's quite handy!
+1 -1
View File
@@ -16,7 +16,7 @@
<body> <body>
<div id="root"></div> <div id="root"></div>
<div id="error-display"></div> <div id="error-display"></div>
<script src="static/preview.1dfdb2ebd0085aa535c8.bundle.js"></script> <script src="static/preview.fe48f7ea9cf295bed07c.bundle.js"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -38,7 +38,7 @@
</head> </head>
<body style="margin: 0;"> <body style="margin: 0;">
<div id="root"></div> <div id="root"></div>
<script src="static/manager.d3baea6e6f08c3c0279c.bundle.js"></script> <script src="static/manager.3f690da6fe3fd9729ee0.bundle.js"></script>
</body> </body>
</html> </html>
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"version":3,"file":"static/manager.3f690da6fe3fd9729ee0.bundle.js","sources":["webpack:///static/manager.3f690da6fe3fd9729ee0.bundle.js"],"mappings":"AAAA;AAkuDA;AA84DA;AA28DA;AA00DA;AAsyEA;AA89CA;AA+rDA;AAsiDA;AAg6DA;AA2nDA;AA++CA;AAkvDA;AAsnEA;AA2oDA;AAivCA;AA+nDA;AAkpDA;AA6lEA;AAs4DA;AAquDA;AA+pDA;AAoxDA;AAsrDA;AA4yDA;AA88GA;AAj6CA;AAopGA;AAsuFA;AA+zEA;AAtMA;AAizEA","sourceRoot":""}
@@ -1 +0,0 @@
{"version":3,"file":"static/manager.d3baea6e6f08c3c0279c.bundle.js","sources":["webpack:///static/manager.d3baea6e6f08c3c0279c.bundle.js"],"mappings":"AAAA;AAkuDA;AA84DA;AA28DA;AA00DA;AAsyEA;AA89CA;AA8rDA;AAsiDA;AAg6DA;AA2nDA;AA++CA;AAkvDA;AAsnEA;AA2oDA;AAivCA;AA+nDA;AAkpDA;AA6lEA;AAs4DA;AAquDA;AA+pDA;AAqxDA;AAsrDA;AAyyDA;AA88GA;AAj6CA;AAopGA;AAsuFA;AA+zEA;AAtMA;AAizEA","sourceRoot":""}
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
{"version":3,"file":"static/preview.1dfdb2ebd0085aa535c8.bundle.js","sources":["webpack:///static/preview.1dfdb2ebd0085aa535c8.bundle.js"],"mappings":"AAAA;AAkuDA;AAu+CA;AA0+FA;AAmpFA;AAkxLA;AA7sBA;AAojOA;AAkjEA;AAivDA;AAy7DA;AAqlDA;AAmvDA;AAu8CA;AA6+DA;AAwlDA;AA2+CA;AAimDA;AA29DA;AA04DA;AAsxCA;AAynDA;AAurDA;AAgiEA;AA03DA;AAwwCA;AA8tDA;AA02FA;AA+tGA;AAy+CA;AAuzCA;AAkuCA;AAikDA;AA8dA;AA+jFA","sourceRoot":""}
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{"version":3,"file":"static/preview.fe48f7ea9cf295bed07c.bundle.js","sources":["webpack:///static/preview.fe48f7ea9cf295bed07c.bundle.js"],"mappings":"AAAA;AAkuDA;AA48CA;AA0sGA;AAigFA;AAyiNA;AA0rFA;AAk4FA;AAsiEA;AAwuDA;AAu9DA;AA6gDA;AAixDA;AA69CA;AAk+DA;AAqoDA;AAs9CA;AAgoDA;AAwsEA;AAuoDA;AAmvCA;AAioDA;AAkpDA;AA6lEA;AAs4DA;AAuyDA;AAokDA;AA0mFA;AAyiGA;AA2gDA;AAm2CA;AAy/BA;AA4uDA;AAy4BA;AA0XA;AA6zEA","sourceRoot":""}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "react-table", "name": "react-table",
"version": "5.2.0", "version": "5.3.0",
"description": "A fast, lightweight, opinionated table and datagrid built on React", "description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT", "license": "MIT",
"homepage": "https://github.com/tannerlinsley/react-table#readme", "homepage": "https://github.com/tannerlinsley/react-table#readme",
Binary file not shown.
+152
View File
@@ -0,0 +1,152 @@
import React from 'react'
import classnames from 'classnames'
//
import _ from './utils'
import Pagination from './pagination'
const emptyObj = () => ({})
export default {
// General
data: [],
loading: false,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPageSize: 20,
showPageJump: true,
expanderColumnWidth: 35,
collapseOnSortingChange: false,
collapseOnPageChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
// Controlled State Overrides
// page: undefined,
// pageSize: undefined,
// sorting: undefined,
// Controlled State Callbacks
onExpandSubComponent: undefined,
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
// Pivoting
pivotBy: undefined,
pivotColumnWidth: 200,
pivotValKey: '_pivotVal',
pivotIDKey: '_pivotID',
subRowsKey: '_subRows',
// Pivoting State Overrides
// expandedRows: {},
// Pivoting State Callbacks
onExpandRow: undefined,
// General Callbacks
onChange: () => null,
// Classes
className: '',
style: {},
// Component decorators
getProps: emptyObj,
getTableProps: emptyObj,
getTheadGroupProps: emptyObj,
getTheadGroupTrProps: emptyObj,
getTheadGroupThProps: emptyObj,
getTheadProps: emptyObj,
getTheadTrProps: emptyObj,
getTheadThProps: emptyObj,
getTbodyProps: emptyObj,
getTrGroupProps: emptyObj,
getTrProps: emptyObj,
getTdProps: emptyObj,
getTfootProps: emptyObj,
getTfootTrProps: emptyObj,
getTfootTdProps: emptyObj,
getPaginationProps: emptyObj,
getLoadingProps: emptyObj,
getNoDataProps: emptyObj,
// Global Column Defaults
column: {
sortable: true,
show: true,
minWidth: 100,
// Cells only
render: undefined,
className: '',
style: {},
getProps: emptyObj,
// Headers only
header: undefined,
headerClassName: '',
headerStyle: {},
getHeaderProps: emptyObj,
// Footers only
footer: undefined,
footerClassName: '',
footerStyle: {},
getFooterProps: emptyObj
},
// Text
previousText: 'Previous',
nextText: 'Next',
loadingText: 'Loading...',
noDataText: 'No rows found',
pageText: 'Page',
ofText: 'of',
rowsText: 'rows',
// Components
TableComponent: _.makeTemplateComponent('rt-table'),
TheadComponent: _.makeTemplateComponent('rt-thead'),
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
TrComponent: _.makeTemplateComponent('rt-tr'),
ThComponent: ({toggleSort, className, children, ...rest}) => {
return (
<div
className={classnames(className, 'rt-th')}
onClick={e => {
toggleSort && toggleSort(e)
}}
{...rest}
>
{children}
</div>
)
},
TdComponent: _.makeTemplateComponent('rt-td'),
TfootComponent: _.makeTemplateComponent('rt-tfoot'),
ExpanderComponent: ({isExpanded, ...rest}) => {
return (
<div
className={classnames('rt-expander', isExpanded && '-open')}
{...rest}
>&bull;</div>
)
},
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')
}
+178 -273
View File
@@ -2,257 +2,12 @@ import React from 'react'
import classnames from 'classnames' import classnames from 'classnames'
// //
import _ from './utils' import _ from './utils'
import lifecycle from './lifecycle'
import componentMethods from './componentMethods' import methods from './methods'
import Pagination from './pagination'
const emptyObj = () => ({})
export const ReactTableDefaults = {
// General
data: [],
loading: false,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
defaultPageSize: 20,
showPageJump: true,
expanderColumnWidth: 35,
collapseOnSortingChange: false,
collapseOnPageChange: true,
freezeWhenExpanded: false,
defaultSorting: [],
// Controlled State Overrides
// page: undefined,
// pageSize: undefined,
// sorting: undefined,
// Controlled State Callbacks
onExpandSubComponent: undefined,
onPageChange: undefined,
onPageSizeChange: undefined,
onSortingChange: undefined,
// Pivoting
pivotBy: undefined,
pivotColumnWidth: 200,
pivotValKey: '_pivotVal',
pivotIDKey: '_pivotID',
subRowsKey: '_subRows',
// Pivoting State Overrides
// expandedRows: {},
// Pivoting State Callbacks
onExpandRow: undefined,
// General Callbacks
onChange: () => null,
// Classes
className: '',
style: {},
// Component decorators
getProps: emptyObj,
getTableProps: emptyObj,
getTheadGroupProps: emptyObj,
getTheadGroupTrProps: emptyObj,
getTheadGroupThProps: emptyObj,
getTheadProps: emptyObj,
getTheadTrProps: emptyObj,
getTheadThProps: emptyObj,
getTbodyProps: emptyObj,
getTrGroupProps: emptyObj,
getTrProps: emptyObj,
getThProps: emptyObj,
getTdProps: emptyObj,
getPaginationProps: emptyObj,
getLoadingProps: emptyObj,
getNoDataProps: emptyObj,
// Global Column Defaults
column: {
sortable: true,
show: true,
minWidth: 100,
// Cells only
render: undefined,
className: '',
style: {},
getProps: () => ({}),
// Headers only
header: undefined,
headerClassName: '',
headerStyle: {},
getHeaderProps: () => ({})
},
// Text
previousText: 'Previous',
nextText: 'Next',
loadingText: 'Loading...',
noDataText: 'No rows found',
pageText: 'Page',
ofText: 'of',
rowsText: 'rows',
// Components
TableComponent: _.makeTemplateComponent('rt-table'),
TheadComponent: _.makeTemplateComponent('rt-thead'),
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
TrComponent: _.makeTemplateComponent('rt-tr'),
ThComponent: ({toggleSort, className, children, ...rest}) => {
return (
<div
className={classnames(className, 'rt-th')}
onClick={e => {
toggleSort && toggleSort(e)
}}
{...rest}
>
{children}
</div>
)
},
TdComponent: _.makeTemplateComponent('rt-td'),
ExpanderComponent: ({isExpanded, ...rest}) => {
return (
<div
className={classnames('rt-expander', isExpanded && '-open')}
{...rest}
>&bull;</div>
)
},
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')
}
export default React.createClass({ export default React.createClass({
getDefaultProps () { ...lifecycle,
return ReactTableDefaults ...methods,
},
getInitialState () {
return {
page: 0,
pageSize: this.props.defaultPageSize || 10,
sorting: this.props.defaultSorting,
expandedRows: {}
}
},
getResolvedState (props, state) {
const resolvedState = {
..._.compactObject(this.state),
..._.compactObject(state),
..._.compactObject(this.props),
..._.compactObject(props)
}
return resolvedState
},
componentWillMount () {
this.setStateWithData(this.getDataModel())
},
componentDidMount () {
this.fireOnChange()
},
componentWillReceiveProps (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
// Props that trigger a data update
if (
oldState.data !== newState.data ||
oldState.columns !== newState.columns ||
oldState.pivotBy !== newState.pivotBy ||
oldState.sorting !== newState.sorting
) {
this.setStateWithData(this.getDataModel(nextProps, nextState))
}
},
setStateWithData (newState, cb) {
const oldState = this.getResolvedState()
const newResolvedState = this.getResolvedState({}, newState)
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.expandedRows)
for (var i = 0; i < keys.length; i++) {
if (newResolvedState.expandedRows[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.sorting !== newResolvedState.sorting ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
) {
// Handle collapseOnSortingChange & collapseOnPageChange
if (
(oldState.sorting !== newResolvedState.sorting && this.props.collapseOnSortingChange) ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData && this.props.collapseOnPageChange)
) {
newResolvedState.expandedRows = {}
}
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
}
// Calculate pageSize all the time
if (newResolvedState.resolvedData) {
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.resolvedData.length / newResolvedState.pageSize)
}
return this.setState(newResolvedState, cb)
},
shouldComponentUpdate (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
// State changes that trigger a render
if (
oldState.sortedData !== newState.sortedData ||
oldState.page !== newState.page ||
oldState.pageSize !== newState.pageSize ||
oldState.expandedRows !== newState.expandedRows
) {
return true
}
return false
},
render () { render () {
const resolvedState = this.getResolvedState() const resolvedState = this.getResolvedState()
@@ -271,8 +26,10 @@ export default React.createClass({
getTbodyProps, getTbodyProps,
getTrGroupProps, getTrGroupProps,
getTrProps, getTrProps,
getThProps,
getTdProps, getTdProps,
getTfootProps,
getTfootTrProps,
getTfootTdProps,
getPaginationProps, getPaginationProps,
getLoadingProps, getLoadingProps,
getNoDataProps, getNoDataProps,
@@ -300,6 +57,7 @@ export default React.createClass({
TrComponent, TrComponent,
ThComponent, ThComponent,
TdComponent, TdComponent,
TfootComponent,
ExpanderComponent, ExpanderComponent,
PaginationComponent, PaginationComponent,
LoadingComponent, LoadingComponent,
@@ -314,9 +72,6 @@ export default React.createClass({
sortedData sortedData
} = resolvedState } = resolvedState
// Determine the flex percentage for each column
// const columnPercentage = 100 / allVisibleColumns.length
// Pagination // Pagination
const startRow = pageSize * page const startRow = pageSize * page
const endRow = startRow + pageSize const endRow = startRow + pageSize
@@ -326,6 +81,8 @@ export default React.createClass({
: minRows ? _.range(Math.max(minRows - pageRows.length, 0)) : minRows ? _.range(Math.max(minRows - pageRows.length, 0))
: [] : []
const hasColumnFooter = allVisibleColumns.some(d => d.footer)
const recurseRowsViewIndex = (rows, path = [], index = -1) => { const recurseRowsViewIndex = (rows, path = [], index = -1) => {
rows.forEach((row, i) => { rows.forEach((row, i) => {
index++ index++
@@ -354,6 +111,7 @@ export default React.createClass({
pageRows, pageRows,
minRows, minRows,
padRows, padRows,
hasColumnFooter,
canPrevious, canPrevious,
canNext, canNext,
rowMinWidth rowMinWidth
@@ -765,7 +523,7 @@ export default React.createClass({
const makePadRow = (row, i) => { const makePadRow = (row, i) => {
const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this) const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this)
const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this)) const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this))
const thProps = _.splitProps(getThProps(finalState, undefined, undefined, this)) const tdProps = _.splitProps(getTdProps(finalState, undefined, undefined, this))
return ( return (
<TrGroupComponent <TrGroupComponent
key={i} key={i}
@@ -782,14 +540,14 @@ export default React.createClass({
<ThComponent <ThComponent
className={classnames( className={classnames(
'rt-expander-header', 'rt-expander-header',
thProps.className tdProps.className
)} )}
style={{ style={{
...thProps.style, ...tdProps.style,
flex: `0 0 auto`, flex: `0 0 auto`,
width: `${expanderColumnWidth}px` width: `${expanderColumnWidth}px`
}} }}
{...thProps.rest} {...tdProps.rest}
/> />
)} )}
{allVisibleColumns.map((column, i2) => { {allVisibleColumns.map((column, i2) => {
@@ -835,13 +593,123 @@ export default React.createClass({
) )
} }
const makeTable = () => { const makeColumnFooters = () => {
const tFootProps = getTfootProps(finalState, undefined, undefined, this)
const tFootTrProps = _.splitProps(getTfootTrProps(finalState, undefined, undefined, this))
return (
<TfootComponent
className={tFootProps.className}
style={{
...tFootProps.style,
minWidth: `${rowMinWidth}px`
}}
{...tFootProps.rest}
>
<TrComponent
className={classnames(
tFootTrProps.className
)}
style={tFootTrProps.style}
{...tFootTrProps.rest}
>
{allVisibleColumns.map((column, i2) => {
const show = typeof column.show === 'function' ? column.show() : column.show
const width = _.getFirstDefined(column.width, column.minWidth)
const maxWidth = _.getFirstDefined(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
}
if (column.expander) {
if (column.pivotColumns) {
return (
<TdComponent
key={i2}
className={classnames(
'rt-pivot',
classes
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`
}}
{...columnProps.rest}
{...tFootTdProps.rest}
{...columnFooterProps.rest}
>
{_.normalizeComponent(column.footer)}
</TdComponent>
)
}
// Return the regular expander cell
return (
<TdComponent
key={i2}
className={classnames(
classes,
{hidden: !show}
)}
style={{
...styles,
flex: `0 0 auto`,
width: `${expanderColumnWidth}px`
}}
/>
)
}
// Return regular cell
return (
<TdComponent
key={i2}
className={classnames(
classes,
!show && 'hidden'
)}
style={{
...styles,
flex: `${width} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`
}}
{...columnProps.rest}
{...tFootTdProps.rest}
{...columnFooterProps.rest}
>
{_.normalizeComponent(column.footer)}
</TdComponent>
)
})}
</TrComponent>
</TfootComponent>
)
}
const rootProps = _.splitProps(getProps(finalState, undefined, undefined, this)) const rootProps = _.splitProps(getProps(finalState, undefined, undefined, this))
const tableProps = _.splitProps(getTableProps(finalState, undefined, undefined, this)) const tableProps = _.splitProps(getTableProps(finalState, undefined, undefined, this))
const tBodyProps = _.splitProps(getTbodyProps(finalState, undefined, undefined, this)) const tBodyProps = _.splitProps(getTbodyProps(finalState, undefined, undefined, this))
const paginationProps = _.splitProps(getPaginationProps(finalState, undefined, undefined, this)) const paginationProps = _.splitProps(getPaginationProps(finalState, undefined, undefined, this))
const loadingProps = getLoadingProps(finalState, undefined, undefined, this) const loadingProps = getLoadingProps(finalState, undefined, undefined, this)
const noDataProps = getNoDataProps(finalState, undefined, undefined, this) const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
const Root = ({children}) => {
return ( return (
<div <div
className={classnames( className={classnames(
@@ -855,13 +723,24 @@ export default React.createClass({
}} }}
{...rootProps.rest} {...rootProps.rest}
> >
{children}
</div>
)
}
const Table = ({children}) => (
<TableComponent <TableComponent
className={classnames(tableProps.className)} className={classnames(tableProps.className)}
style={tableProps.style} style={tableProps.style}
{...tableProps.rest} {...tableProps.rest}
> >
{hasHeaderGroups && makeHeaderGroups()} {children}
{makeHeaders()} </TableComponent>
)
const HeaderGroups = () => hasHeaderGroups ? makeHeaderGroups() : null
const Headers = () => makeHeaders()
const Rows = ({children}) => (
<TbodyComponent <TbodyComponent
className={classnames(tBodyProps.className)} className={classnames(tBodyProps.className)}
style={{ style={{
@@ -873,8 +752,11 @@ export default React.createClass({
{pageRows.map((d, i) => makePageRow(d, i))} {pageRows.map((d, i) => makePageRow(d, i))}
{padRows.map(makePadRow)} {padRows.map(makePadRow)}
</TbodyComponent> </TbodyComponent>
</TableComponent> )
{showPagination && (
const Footers = () => hasColumnFooter ? makeColumnFooters() : null
const Pagination = () => showPagination ? (
<PaginationComponent <PaginationComponent
{...resolvedState} {...resolvedState}
pages={pages} pages={pages}
@@ -886,28 +768,51 @@ export default React.createClass({
style={paginationProps.style} style={paginationProps.style}
{...paginationProps.rest} {...paginationProps.rest}
/> />
)} ) : null
{!pageRows.length && (
const NoData = () => !pageRows.length ? (
<NoDataComponent <NoDataComponent
{...noDataProps} {...noDataProps}
> >
{_.normalizeComponent(noDataText)} {_.normalizeComponent(noDataText)}
</NoDataComponent> </NoDataComponent>
)} ) : null
const Loading = () => (
<LoadingComponent <LoadingComponent
loading={loading} loading={loading}
loadingText={loadingText} loadingText={loadingText}
{...loadingProps} {...loadingProps}
/> />
</div>
) )
}
const StandardTable = () => (
<Root>
<Table>
<HeaderGroups />
<Headers />
<Rows />
<Footers />
</Table>
<Pagination />
<NoData />
<Loading />
</Root>
)
Object.assign(StandardTable, {
Root,
Table,
HeaderGroups,
Headers,
Rows,
Footers,
Pagination,
NoData,
Loading
})
// childProps are optionally passed to a function-as-a-child // childProps are optionally passed to a function-as-a-child
return children ? children(finalState, makeTable, this) : makeTable() return children ? children(finalState, StandardTable, this) : <StandardTable />
}, }
// Helpers
...componentMethods
}) })
+10 -1
View File
@@ -30,7 +30,6 @@ $expandSize = 7px
.rt-th .rt-th
.rt-td .rt-td
color: black
border-right: 1px solid alpha(black, .05) border-right: 1px solid alpha(black, .05)
transition box-shadow .3s $easeOutBack transition box-shadow .3s $easeOutBack
box-shadow:inset 0 0 0 0 transparent box-shadow:inset 0 0 0 0 transparent
@@ -100,6 +99,15 @@ $expandSize = 7px
cursor: pointer cursor: pointer
&.-open:after &.-open:after
transform: translate(-50%, -50%) rotate(0deg) transform: translate(-50%, -50%) rotate(0deg)
.rt-tfoot
display: flex
flex-direction: column
box-shadow: 0 0px 15px 0px alpha(black, .15)
.rt-td
border-right:1px solid alpha(black, .05)
&:last-child
border-right:0
&.-striped &.-striped
.rt-tr.-odd .rt-tr.-odd
@@ -117,6 +125,7 @@ $expandSize = 7px
flex-wrap: wrap flex-wrap: wrap
padding: 3px padding: 3px
box-shadow: 0 0px 15px 0px alpha(black, .1) box-shadow: 0 0px 15px 0px alpha(black, .1)
border-top: 2px solid alpha(black, .1)
.-btn .-btn
appearance:none appearance:none
+96
View File
@@ -0,0 +1,96 @@
import _ from './utils'
import defaultProps from './defaultProps'
export default {
getDefaultProps () {
return defaultProps
},
getInitialState () {
return {
page: 0,
pageSize: this.props.defaultPageSize || 10,
sorting: this.props.defaultSorting,
expandedRows: {}
}
},
getResolvedState (props, state) {
const resolvedState = {
..._.compactObject(this.state),
..._.compactObject(state),
..._.compactObject(this.props),
..._.compactObject(props)
}
return resolvedState
},
componentWillMount () {
this.setStateWithData(this.getDataModel())
},
componentDidMount () {
this.fireOnChange()
},
componentWillReceiveProps (nextProps, nextState) {
const oldState = this.getResolvedState()
const newState = this.getResolvedState(nextProps, nextState)
// Props that trigger a data update
if (
oldState.data !== newState.data ||
oldState.columns !== newState.columns ||
oldState.pivotBy !== newState.pivotBy ||
oldState.sorting !== newState.sorting
) {
this.setStateWithData(this.getDataModel(nextProps, nextState))
}
},
setStateWithData (newState, cb) {
const oldState = this.getResolvedState()
const newResolvedState = this.getResolvedState({}, newState)
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.expandedRows)
for (var i = 0; i < keys.length; i++) {
if (newResolvedState.expandedRows[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.sorting !== newResolvedState.sorting ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
) {
// Handle collapseOnSortingChange & collapseOnPageChange
if (
(oldState.sorting !== newResolvedState.sorting && this.props.collapseOnSortingChange) ||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData && this.props.collapseOnPageChange)
) {
newResolvedState.expandedRows = {}
}
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
}
// Calculate pageSize all the time
if (newResolvedState.resolvedData) {
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.resolvedData.length / newResolvedState.pageSize)
}
return this.setState(newResolvedState, cb)
}
}
+1 -1
View File
@@ -191,7 +191,7 @@ function isSortingDesc (d) {
return !!(d.sort === 'desc' || d.desc === true || d.asc === false) return !!(d.sort === 'desc' || d.desc === true || d.asc === false)
} }
function normalizeComponent (Comp, params, fallback = Comp) { function normalizeComponent (Comp, params = {}, fallback = Comp) {
return typeof Comp === 'function' ? ( return typeof Comp === 'function' ? (
Comp.prototype.isReactComponent ? ( Comp.prototype.isReactComponent ? (
<Comp <Comp
+114
View File
@@ -0,0 +1,114 @@
import React from 'react'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from './components/codeHighlight'
import ReactTable from '../src/index'
export default () => {
const data = _.map(_.range(5553), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName',
footer: (
<span><strong>Popular:</strong> {
_.first(
_.reduce(
_.map(
_.groupBy(
data, d => d.firstName
)
),
(a, b) => a.length > b.length ? a : b
)
).firstName}
</span>
)
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName,
footer: (
<span><strong>Longest:</strong> {
_.reduce(
_.map(
_.groupBy(
data, d => d.lastName
),
(d, key) => key
),
(a, b) => a.length > b.length ? a : b
)}
</span>
)
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age',
footer: <span><strong>Average:</strong> {_.round(_.mean(_.map(data, d => d.age)))}</span>
}]
}]
return (
<div>
<div className='table-wrap'>
<ReactTable
className='-striped -highlight'
data={data}
columns={columns}
defaultPageSize={10}
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
<CodeHighlight>{() => getCode()}</CodeHighlight>
</div>
)
}
function getCode () {
return `
import ReactTable from 'react-table'
// Create some column definitions
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
// Display your table!
return (
<ReactTable
data={data}
columns={columns}
defaultPageSize={10}
/>
)
`
}
+156 -43
View File
@@ -40,22 +40,169 @@ export default () => {
header: 'Name', header: 'Name',
columns: [{ columns: [{
header: 'First Name', header: 'First Name',
accessor: 'firstName' accessor: 'firstName',
footer: 'Footer'
}, { }, {
header: 'Last Name', header: 'Last Name',
id: 'lastName', id: 'lastName',
accessor: d => d.lastName accessor: d => d.lastName,
footer: 'Footer'
}] }]
}, { }, {
header: 'Info', header: 'Info',
columns: [{ columns: [{
header: 'Age', header: 'Age',
accessor: 'age' accessor: 'age',
footer: 'Footer'
}] }]
}] }]
return ( return (
<div> <div>
<strong>Functional rendering</strong> simply means that you have all of the building blocks to render your own React Table however you'd like.
<br />
<br />
Whether it's <strong>completely custom</strong>, or even just <strong>rearranging the order of the table's elements</strong>, this is how you can do it.
<br />
<br />
<br />
<br />
<strong>Pagination at the top using partials:</strong>
<br />
<br />
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
>
{(state, {
Root,
Table,
HeaderGroups,
Headers,
Rows,
Footers,
Pagination,
NoData,
Loading
}, instance) => {
return (
<Root>
<Pagination />
<Table>
<HeaderGroups />
<Headers />
<Rows />
<Footers />
</Table>
<NoData />
<Loading />
</Root>
)
}}
</ReactTable>
</div>
<CodeHighlight>{() => `
import ReactTable from 'react-table'
return (
<ReactTable
data={data}
columns={columns}
>
{(state, {
Root,
Table,
HeaderGroups,
Headers,
Rows,
Footers,
Pagination,
NoData,
Loading
}, instance) => {
return (
<Root>
<Pagination />
<Table>
<HeaderGroups />
<Headers />
<Rows />
<Footers />
</Table>
<NoData />
<Loading />
</Root>
)
}}
</ReactTable>
)
`}</CodeHighlight>
<br />
<br />
<strong>Wrapping the standard table output</strong>
<br />
<br />
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
>
{(state, Table, instance) => {
return (
<div style={{
background: '#ffcf00',
borderRadius: '5px',
overflow: 'hidden',
padding: '5px'
}}>
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
<Table />
</div>
)
}}
</ReactTable>
</div>
<CodeHighlight>{() => `
import ReactTable from 'react-table'
return (
<ReactTable
data={data}
columns={columns}
>
{(state, Table, instance) => {
return (
<div style={{
background: '#ffcf00',
borderRadius: '5px',
overflow: 'hidden',
padding: '5px'
}}>
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
<Table />
</div>
)
}}
</ReactTable>
)
`}</CodeHighlight>
<br />
<br />
<strong>Need more control? This is the entire table state and component instance at your disposal!</strong>
<br />
<br />
<div className='table-wrap'> <div className='table-wrap'>
<ReactTable <ReactTable
className='-striped -highlight' className='-striped -highlight'
@@ -63,54 +210,23 @@ export default () => {
columns={columns} columns={columns}
defaultPageSize={10} defaultPageSize={10}
> >
{(state, makeTable, instance) => { {(state, StandardTable, instance) => {
console.log(state)
return ( return (
<div> <div>
Look! This is the entire table state and component instance at your disposal!
<JSONTree <JSONTree
data={Object.assign({}, state, {children: 'function () {...}'})} data={Object.assign({}, state, {children: 'function () {...}'})}
theme={JSONtheme} theme={JSONtheme}
invertTheme invertTheme
/> />
<br />
<br />
{makeTable()}
</div> </div>
) )
}} }}
</ReactTable> </ReactTable>
</div> </div>
<br /> <br />
<CodeHighlight>{() => getCode()}</CodeHighlight> <CodeHighlight>{() => `
</div>
)
}
function getCode () {
return `
import ReactTable from 'react-table' import ReactTable from 'react-table'
// Create some column definitions
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
// Display your table!
return ( return (
<ReactTable <ReactTable
className='-striped -highlight' className='-striped -highlight'
@@ -118,23 +234,20 @@ return (
columns={columns} columns={columns}
defaultPageSize={10} defaultPageSize={10}
> >
{(state, makeTable, instance) => { {(state, StandardTable, instance) => {
console.log(state)
return ( return (
<div> <div>
Look! This is the entire table state and component instance at your disposal!
<JSONTree <JSONTree
data={Object.assign({}, state, {children: 'function () {...}'})} data={Object.assign({}, state, {children: 'function () {...}'})}
theme={JSONtheme} theme={JSONtheme}
invertTheme invertTheme
/> />
<br />
<br />
{makeTable()}
</div> </div>
) )
}} }}
</ReactTable> </ReactTable>
) )
` `}</CodeHighlight>
</div>
)
} }