Compare commits

..
50 Commits
Author SHA1 Message Date
Tanner Linsley d8e5188d94 3.1.4 2016-12-07 13:00:39 -07:00
Tanner Linsley 8c79bf95df Page Size fix 2016-12-07 13:00:27 -07:00
Tanner Linsley 6053c48781 3.1.3 2016-12-07 12:55:10 -07:00
Tanner Linsley 8f1c8774f2 Fixes and use Yarn 2016-12-07 12:55:03 -07:00
Tanner Linsley d5795fb5ac Readme Update 2016-12-06 23:19:00 -07:00
Tanner Linsley 8abbefa680 Readme updates 2016-12-06 13:17:38 -07:00
Tanner Linsley 4db644941a Badges 2016-12-06 13:15:55 -07:00
Pedro Tacla YamadaandTanner Linsley 6905ccae72 Fix typo when overriding pagination components (#17) 2016-11-15 08:56:05 -07:00
Tanner LinsleyandGitHub e832bf52b0 Update README.md 2016-11-07 13:44:31 -07:00
Tanner Linsley 3432fefce2 Example Update 2016-11-02 15:34:00 -06:00
Tanner Linsley 2abcc86954 3.1.2 2016-11-02 15:20:48 -06:00
Tanner Linsley 610972d99e Pagination Style fixes 2016-11-02 15:20:43 -06:00
Tanner Linsley c99796342b 3.1.1 2016-11-02 15:07:27 -06:00
Tanner Linsley 1b4d8bd45f Fix pagination not showing with 0 pagesLength 2016-11-02 15:07:20 -06:00
Tanner Linsley e922a43741 No example/dist 2016-11-02 13:08:20 -06:00
Tanner Linsley 75317c2251 Example Update 2016-11-02 13:07:05 -06:00
Tanner Linsley e531ef1f64 Readme Updates 2016-11-02 12:12:29 -06:00
Tanner Linsley 598d33410a 3.1.0 2016-11-02 12:09:57 -06:00
Tanner Linsley 57ac1f142d Pagination Features
- Page Jumping
- Page Size Changing
- Updated Examples
2016-11-02 12:09:52 -06:00
Tanner Linsley e181f8e3d1 Readme Update 2016-11-01 14:44:58 -06:00
Tanner Linsley 5e19459eba Readme updates 2016-10-31 12:20:03 -06:00
Tanner Linsley cad0ac49cb Readme updates 2016-10-31 12:17:40 -06:00
Tanner Linsley b5d17cdbce Readme Update 2016-10-31 11:12:48 -06:00
Tanner Linsley a5d9e15b5d Readme update 2016-10-31 11:11:01 -06:00
Tanner LinsleyandGitHub c9cf436576 Update async.js 2016-10-31 10:39:28 -06:00
Tanner Linsley ece0ddc8a4 Readme Updates 2016-10-31 02:00:41 -06:00
Tanner Linsley dbc90c2e63 Readme updates 2016-10-31 01:55:32 -06:00
Tanner Linsley 2b0e1eb8a7 Readme Updates 2016-10-31 01:53:54 -06:00
Tanner Linsley 3d0f38f2c5 3.0.0 2016-10-31 01:49:43 -06:00
Tanner Linsley 516b88468e Major changes for server-side data 2016-10-31 01:49:43 -06:00
Alex SimoesandTanner Linsley 07cd241ced Use curly brackets here instead of square brackets (#7) 2016-10-27 19:09:58 -06:00
Tanner Linsley 48e6d16d58 2.2.7 2016-10-27 16:39:04 -06:00
Tanner Linsley e4208e3b0f Allow column without accessor or id 2016-10-27 16:38:55 -06:00
Tanner Linsley e6d03673ff 2.2.6 2016-10-27 09:32:22 -06:00
Tanner Linsley c3fc84db01 Fixed Column Accessor bug 2016-10-27 09:32:15 -06:00
Tanner Linsley 3334796223 2.2.5 2016-10-27 09:20:21 -06:00
Tanner Linsley 92b70393fa Added rowValues as a prop & Readme Updates 2016-10-27 09:20:16 -06:00
Tanner Linsley efeabe3536 2.2.4 2016-10-27 08:59:34 -06:00
Tanner Linsley f018bac58f Fixed row value prop 2016-10-27 08:59:30 -06:00
Tanner Linsley a8f360a408 Remove Build Files 2016-10-26 19:39:18 -06:00
Tanner Linsley 82f3913034 2.2.3 2016-10-26 19:37:15 -06:00
Tanner Linsley cb3cc05a59 Fixed row references 2016-10-26 19:37:10 -06:00
Tanner Linsley e8146b5d14 2.2.2 2016-10-26 19:25:51 -06:00
Tanner Linsley eef59a559e Added stuff back to umd files array 2016-10-26 19:25:45 -06:00
Tanner Linsley 3b18e8c8c1 2.2.1 2016-10-26 19:18:51 -06:00
Tanner Linsley dd6b8af304 Ignore built files in git 2016-10-26 19:18:45 -06:00
Tanner Linsley f242d2f3f6 Fixed tbodystyle bug 2016-10-26 19:15:36 -06:00
Tanner Linsley 9dd0333571 Readme Update 2016-10-26 19:11:48 -06:00
Tanner Linsley 085616d858 2.2.0 2016-10-26 17:33:32 -06:00
Tanner Linsley 25ef0b2273 Styles, ClassNames & Row Style/ClassName callbacks 2016-10-26 17:33:26 -06:00
25 changed files with 7408 additions and 60365 deletions
+4 -1
View File
@@ -1,3 +1,6 @@
node_modules/
lib/
react-table.js
react-table.css
*.log
example/dist/
+110 -73
View File
@@ -1,19 +1,22 @@
![react-table](https://github.com/tannerlinsley/react-table/raw/master/media/Banner.png)
<div align="center">
<a href="https://github.com/tannerlinsley/react-table"><img src="https://github.com/tannerlinsley/react-table/raw/master/media/Banner.png" alt="React Table Logo" style="width:450px;"/></a>
<br />
<br />
</div>
# react-table
A fast, lightweight, opinionated table and datagrid built on React
[![Build Status](https://travis-ci.org/tannerlinsley/react-table.svg?branch=master)](https://travis-ci.org/tannerlinsley/react-table)
[![react-table on Slack](https://img.shields.io/badge/slack-react--table-blue.svg)](https://react-table-slack.herokuapp.com/)
[![Build Status](https://travis-ci.org/tannerlinsley/react-table.svg?branch=master)](https://travis-ci.org/tannerlinsley/react-table) [![npm](https://img.shields.io/npm/dm/react-table.svg)]() [![react-table on Slack](https://img.shields.io/badge/slack-react--table-blue.svg)](https://react-table-slack.herokuapp.com/) [![GitHub stars](https://img.shields.io/github/stars/tannerlinsley/react-table.svg?style=social&label=Star)]() [![Twitter Follow](https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow)](https://twitter.com/tannerlinsley)
## Features
- Lightweight at 3kb (and just 2kb more for styles)
- No composition needed
- Uses customizable JSX and callbacks for everything
- Client-side pagination and sorting
- Server-side data
- Lightweight at 4kb (and just 2kb more for styles)
- Fully customizable JSX and callbacks for everything
- Supports both Client-side & Server-side pagination and sorting
- Minimal design & easily themeable
- ["Why I wrote React Table and the problems it has solved for Nozzle.io"](https://medium.com/@tannerlinsley/why-i-wrote-react-table-and-the-problems-it-has-solved-for-nozzle-others-445c4e93d4a8#.axza4ixba) by Tanner Linsley
## [Demo](http://react-table.zabapps.com)
@@ -53,27 +56,28 @@ const data = [{
const columns = [{
header: 'Name',
accessor: 'name',
accessor: 'name' // String-based value accessors !
}, {
header: 'Age',
accessor: 'age'
accessor: 'age',
render: props => <span className='number'>props.value</span> // Custom cell components!
}, {
header: 'Friend Name',
accessor: d => d.friend.name
accessor: d => d.friend.name // Custom value accessors!
}, {
header: 'Friend Age',
header: props => <span>Friend Age</span>, // Custom header components!
accessor: 'friend.age'
}]
<ReactTable
data=[data]
data={data}
columns={columns}
/>
```
<a name="data"></a>
## Data
Every React-Table instance requires you to set the `data` prop. To use client-side data, simply pass the `data` prop anything that resembles an array or object. Client-side filtering and pagination is built in, and your table will update gracefully if you change any props. [Server-side data](#server-side-data) is also supported.
Simply pass the `data` prop anything that resembles an array or object. Client-side filtering and pagination is built in, and your table will update gracefully as you change any props. [Server-side data](#server-side-data) is also supported!
<a name="default-props"></a>
@@ -81,24 +85,40 @@ Every React-Table instance requires you to set the `data` prop. To use client-si
These are the default props for the main react component `<ReactTable />`
```javascript
{
// General
loading: false, // Whether to show the loading overlay or not
pageSize: 20, // The default page size (this can be changed by the user if `showPageSizeOptions` is enabled)
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
showPagination: true, // Shows or hides the pagination component
showPageJump: true, // Shows or hides the pagination number input
showPageSizeOptions: true, // Enables the user to change the page size
pageSizeOptions: [5, 10, 20, 25, 50, 100], // The available page size options
// Callbacks
onChange: (state, instance) => null, // Anytime the internal state of the table changes, this will fire
// Text
previousText: 'Previous',
nextText: 'Next'
// Classes
className: '-striped -highlight', // The most top level className for the component
tableClassName: '', // ClassName for the `table` element
theadClassName: '', // ClassName for the `thead` element
tbodyClassName: '', // ClassName for the `tbody` element
trClassName: '', // ClassName for all `tr` elements
trClassCallback: row => null, // A call back to dynamically add classes (via the classnames module) to a row element
paginationClassName: '' // ClassName for `pagination` element
//
pageSize: 20,
minRows: 0, // Ensure this many rows are always rendered, regardless of rows on page
// Global Column Defaults
column: { // default properties for every column's model
sortable: true,
show: true
},
// Text
previousText: 'Previous',
nextText: 'Next'
// Styles
style: {}, // Main style object for the component
tableStyle: {}, // style object for the `table` component
theadStyle: {}, // style object for the `thead` component
tbodyStyle: {}, // style object for the `tbody` component
trStyle: {}, // style object for the `tr` component
trStyleCallback: row => {}, // A call back to dynamically add styles to a row element
thStyle: {}, // style object for the `th` component
tdStyle: {}, // style object for the `td` component
paginationStyle: {}, // style object for the `paginination` component
}
```
@@ -126,27 +146,40 @@ Or just define them on the component per-instance
<a name="columns"></a>
## Columns
Every React-Table instance requires a `columns` prop, which is an array of objects containing the following properties
`<ReactTable/>` requires a `columns` prop, which is an array of objects with the following properties
```javascript
[{
// Required
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
// General
accessor: 'propertyName' or Accessor eg. (row) => row.propertyName,
// A unique ID is required if the accessor is not a string or if you would like to override the column name used in server-side calls
id: 'myProperty',
// Optional
className: '', // Set the classname of the `th/td` element of the column
innerClassName: '', // Set the classname of the `.th-inner/.td-inner` element of the column
columns: [...] // See Header Groups section below
render: JSX eg. ({row, value, index, viewIndex}) => <span>{value}</span>, // Provide a JSX element or stateless function to render whatever you want as the column's cell with access to the entire row
id: 'myProperty', // Conditional - A unique ID is required if the accessor is not a string or if you would like to override the column name used in server-side calls
sortable: true,
sort: 'asc' or 'desc',
show: true,
width: Number, // Locks the column width to this amount
sort: 'asc' or 'desc', // used to determine the column sorting on init
show: true, // can be used to hide a column
minWidth: Number // Allows the column to flex above this minimum amount
// Cell Options
className: '', // Set the classname of the `td` element of the column
style: {}, // Set the style of the `td` element of the column
innerClassName: '', // Set the classname of the `.-td-inner` element of the column
innerStyle: {}, // Set the style of the `.-td-inner` element of the column
render: JSX eg. ({value, rowValues, row, index, viewIndex}) => <span>{value}</span>, // Provide a JSX element or stateless function to render whatever you want as the column's cell with access to the entire row
// value == the accessed value of the column
// rowValues == an object of all of the accessed values for the row
// row == the original row of data supplied to the table
// index == the original index of the data supplied to the table
// viewIndex == the index of the row in the current page
// Header & HeaderGroup Options
header: 'Header Name' or JSX eg. ({data, column}) => <div>Header Name</div>,
headerClassName: '', // Set the classname of the `th` element of the column
headerStyle: {}, // Set the style of the `th` element of the column
headerInnerClassName: '', // Set the classname of the `.-th-inner` element of the column
headerInnerStyle: {}, // Set the style of the `.th-inner` element of the column
// Header Groups only
columns: [...] // See Header Groups section below
}]
```
@@ -175,47 +208,45 @@ const columns = [{
<a name="server-side-data"></a>
## Server-side Data
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you. Instead of passing the `data` prop an array, you provide a function instead.
If you want to handle pagination, and sorting on the server, `react-table` makes it easy on you.
This function will be called on mount, pagination events, and sorting events. It also provides you all of the parameters to help you query and format your data.
1. Feed React Table `data` from somewhere dynamic. eg. `state`, a redux store, etc...
1. Add `manual` as a prop. This informs React Table that you'll be handling sorting and pagination server-side
1. Subscribe to the `onChange` prop. This function is called at `compomentDidMount` and any time sorting or pagination is changed by the user
1. In the `onChange` callback, request your data using the provided information in the params of the function (state and instance)
1. Update your data with the rows to be displayed
1. Optionally set how many pages there are total
```javascript
<ReactTable
data={(params, callback) => {
// params will give you all the info you need to query and sort your data
params == {
page: 0, // The page index the user is requesting
pageSize: 20, // The current pageSize
pages: -1, // The amount of existing pages (-1 means there is no page data yet)
sorting: [ // An array of column sort models (yes, you can multi-sort!)
{
id: 'columnID', // The columnID (usually the accessor string, but can be overridden for server-side or required if the column accessor is a function)
ascending: true or false
}
]
}
// Query your data however you'd like, then structure your response like so:
const result = {
rows: [...], // Your data for the current page/sorting model
pages: 10 // optionally provide how many pages exist (this is only needed if you choose to display page numbers, and only the first time you make the call or if the page count changes)
}
// You can return a promise that resolve the result
return Axios.post('/myDataEnpoint', params) // resolves to `result`
// or use the manual callback whenever you please
setTimeout(() => {
callback(result)
}, 5000)
// That's it!
...
data={this.state.data} // should default to []
pages={this.state.pages} // should default to -1 (which means we don't know how many pages we have)
loading={this.state.loading}
manual // informs React Table that you'll be handling sorting and pagination server-side
onChange={(state, instance) => {
// show the loading overlay
this.setState({loading: true})
// fetch your data
Axios.post('mysite.com/data', {
page: state.page,
pageSize: state.pageSize,
sorting: state.sorting
})
.then((res) => {
// Update react-table
this.setState({
data: res.data.rows,
pages: res.data.pages,
loading: false
})
})
}}
/>
```
For a detailed example, take a peek at our [async table mockup](https://github.com/tannerlinsley/react-table/blob/master/example/src/screens/async.js)
<a name="multi-sort"></a>
## 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!
@@ -269,3 +300,9 @@ If you would like to preview your changes, you can link and utilize the example
- `npm watch`
- Make changes to the example in `src/screens/index.js` if needed
- View changes at `localhost:8000`
## Used By
<a href='https://nozzle.io'>
<img src='https://nozzle.io/img/logo-blue.png' alt='Nozzle Logo' style='width:300px;'/>
</a>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

-171
View File
File diff suppressed because one or more lines are too long
-59057
View File
File diff suppressed because one or more lines are too long
-13
View File
@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React-Table Demo</title>
<link rel="stylesheet" href="/app.css" charset="utf-8">
</head>
<body>
<div id="app"></div>
<script src="/app.js"></script>
<script async defer src="https://buttons.github.io/buttons.js"></script>
</body>
</html>
+5 -3
View File
@@ -10,7 +10,8 @@
},
"scripts": {
"watch": "jumpsuit watch",
"build": "jumpsuit build"
"build": "jumpsuit build",
"deploy": "jumpsuit build && zab deploy"
},
"devDependencies": {
"nib": "^1.1.0",
@@ -18,9 +19,10 @@
"stylus": "^0.54.5"
},
"dependencies": {
"jumpsuit": "^0.7.5",
"jumpsuit": "^1.3.3",
"lodash": "^4.16.4",
"namor": "^0.3.0",
"react-table": "^2.0.0"
"react-syntax-highlighter": "^3.0.0",
"react-table": "^3.1.2"
}
}
+16 -3
View File
@@ -1,4 +1,17 @@
import { Render } from 'jumpsuit'
import App from 'screens/index'
import { Render, Router, Route, IndexRoute } from 'jumpsuit'
//
import Layout from 'components/layout'
import Simple from 'screens/simple'
import ServerSide from 'screens/serverSide'
Render(null, <App />)
Render(null, (
<Router>
<Route path='/' component={Layout}>
<IndexRoute component={Simple} />
<Route path='simple' component={Simple} />
<Route path='server-side' component={ServerSide} />
</Route>
</Router>
), {
useHash: false
})
+39 -6
View File
@@ -11,7 +11,7 @@ global-reset()
// vendor styles
// -----------------------------------------------------------------------------
@import '../node_modules/react-table/react-table.css'
@import '../node_modules/react-table/src/index'
// -----------------------------------------------------------------------------
// variables
@@ -34,7 +34,6 @@ body
background: white
font-family: $fnt-open-sans
font-weight: 300
padding-bottom: 50px
h1
font-size: 2.5em
@@ -50,6 +49,7 @@ strong
.logo
width: 400px
max-width: 100%
.container
display: flex
@@ -63,11 +63,34 @@ strong
font-size: 20px
padding: 10px
.table-wrap
width: 700px
.viewport
width:100%
.table-wrap
width: 90%
margin: auto
padding: 10px
border-radius: 5px
box-shadow: 0 0 20px 0 alpha(black, .2)
.menu
display:block
margin: 0 10px 20px
ul
display:block
li
display:inline-block
a
display:block
padding: 10px
border-radius: 5px
box-shadow: 0 0 20px 0 alpha(black, .2)
margin: 5px
background: alpha(black, .4)
color: white
border-radius: 3px
transition: all .2s ease-out
&.active
&:hover
background: alpha(black, .9)
.ReactTable
thead
@@ -77,3 +100,13 @@ strong
box-shadow:inset 0 3px 0 0 alpha(black, .6)
&.-sort-desc
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
pre
display:block
font-family: monospace
font-size: 15px
line-height: 20px
border-radius: 5px
margin: 20px auto
max-width: 90%
padding: 0 20px !important
+1
View File
@@ -2,6 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>React-Table Demo</title>
<link rel="stylesheet" href="/app.css" charset="utf-8">
</head>
+11
View File
@@ -0,0 +1,11 @@
import { Component } from 'jumpsuit'
import SyntaxHighlighter from 'react-syntax-highlighter'
import atomOneDark from '../../node_modules/react-syntax-highlighter/dist/styles/atom-one-dark'
export default Component({
render () {
return (
<SyntaxHighlighter language='javascript' style={atomOneDark}>{this.props.children}</SyntaxHighlighter>
)
}
})
@@ -1,42 +1,13 @@
import { Component } from 'jumpsuit'
import _ from 'lodash'
import namor from 'namor'
import ReactTable from 'react-table'
import { Component, Link } from 'jumpsuit'
export default Component({
render () {
const data = _.map(_.range(5000), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
accessor: 'lastName'
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
return (
<div className='container'>
<div style={{textAlign: 'center'}}>
<h1>
<span style={{position: 'absolute', textIndent: '-9999em'}}>
react-table <strong>demo</strong>
react-table
</span>
<img src='/Banner.png' className='logo' />
</h1>
@@ -65,15 +36,22 @@ export default Component({
<br />
<br />
</div>
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
/>
<div className='menu'>
<ul>
<li>
<Link to='/simple' activeClassName='active'>
Simple
</Link>
</li>
<li>
<Link to='/server-side' activeClassName='active'>
Server-Side
</Link>
</li>
</ul>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
<div className='viewport'>
{this.props.children}
</div>
</div>
)
+171
View File
@@ -0,0 +1,171 @@
import { Component } from 'jumpsuit'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from 'components/codeHighlight'
import ReactTable from 'react-table'
// Let's mock some data to play around with
const rawData = _.map(_.range(3424), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
// Now let's mock the server. It's job is simple: use the table model to sort and return the page data
const requestData = (pageSize, page, sorting) => {
return new Promise((resolve, reject) => {
// On the server, you'll likely use SQL or noSQL or some other query language to do this.
// For this mock, we'll just use lodash
const sortedData = _.orderBy(rawData, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
}
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
}
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
// Be sure to send back the rows to be displayed and any other pertinent information, like how many pages there are total.
const res = {
rows: sortedData.slice(pageSize * page, (pageSize * page) + pageSize),
pages: Math.ceil(rawData.length / pageSize)
}
// Here we'll simulate a server response with 500ms of delay.
setTimeout(() => resolve(res), 500)
})
}
export default Component({
getInitialState () {
// To handle our data server-side, we need a few things in the state to help us out:
return {
data: [],
pages: null,
loading: true
}
},
fetchData (state, instance) {
// Whenever the table model changes, or the user sorts or changes pages, this method gets called and passed the current table model.
// You can set the `loading` prop of the table to true to use the built-in one or show you're own loading bar if you want.
this.setState({loading: true})
// Request the data however you want. Here, we'll use our mocked service we created earlier
requestData(state.pageSize, state.page, state.sorting)
.then((res) => {
// Now just get the rows of data to your React Table (and update anything else like total pages or loading)
this.setState({
data: res.rows,
pages: res.pages,
loading: false
})
})
},
render () {
return (
<div>
<div className='table-wrap'>
<ReactTable
columns={[{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
pageSize={10}
data={this.state.data} // Set the rows to be displayed
pages={this.state.pages} // Display the total number of pages
loading={this.state.loading} // Display the loading overlay when we need it
onChange={this.fetchData} // Request new data when things change
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
<CodeHighlight>{getCode()}</CodeHighlight>
</div>
)
}
})
function getCode () {
return `
import ReactTable from 'react-table'
import Axios from 'axios'
export default React.creatClass({
getInitialState () {
// To handle our data server-side, we need to keep track of our table state
return {
data: [],
pages: null,
loading: true
}
},
fetchData (state, instance) {
// Whenever the table model changes (sorting, pagination, etc), this method gets called and passed the current table model.
// You can set the 'loading' prop of the table to true to use the built-in loading notice, or show you're own loading bar if you want.
this.setState({loading: true})
// Request the data from a server however you want! Be sure to send the bits of the table model that it may neeed.
Axios.post('mysite.com/data', {
pageSize: state.pageSize,
page: state.page,
sorting: state.sorting
})
.then((res) => {
// Now update your state!
this.setState({
data: res.rows,
pages: res.pages,
loading: false
})
})
},
render () {
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
return (
<ReactTable
columns={columns}
manual // This forces table not to paginate or sort automatically, so we can handle things server-side
data={this.state.data} // Set the rows to be displayed
pages={this.state.pages} // Display the total number of pages
loading={this.state.loading} // Display the loading overlay when we need it
onChange={this.fetchData} // Request new data when things change
/>
)
}
})
`
}
+103
View File
@@ -0,0 +1,103 @@
import { Component } from 'jumpsuit'
import _ from 'lodash'
import namor from 'namor'
import CodeHighlight from 'components/codeHighlight'
import ReactTable from 'react-table'
export default Component({
render () {
const data = _.map(_.range(5553), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
return (
<div>
<div className='table-wrap'>
<ReactTable
data={data}
columns={columns}
pageSize={10}
/>
</div>
<div style={{textAlign: 'center'}}>
<br />
<em>Tip: Hold shift when sorting to multi-sort!</em>
</div>
<CodeHighlight>{getCode()}</CodeHighlight>
</div>
)
}
})
function getCode () {
return `
import ReactTable from 'react-table'
// To help us mock some data
import namor from 'namor'
import _ from 'lodash'
export default () => {
// Mock some data
const data = _.map(_.range(5553), d => {
return {
firstName: namor.generate({ words: 1, numLen: 0 }),
lastName: namor.generate({ words: 1, numLen: 0 }),
age: Math.floor(Math.random() * 30)
}
})
// Create some column definitions
const columns = [{
header: 'Name',
columns: [{
header: 'First Name',
accessor: 'firstName'
}, {
header: 'Last Name',
id: 'lastName',
accessor: d => d.lastName
}]
}, {
header: 'Info',
columns: [{
header: 'Age',
accessor: 'age'
}]
}]
// Display your table!
return (
<ReactTable
data={data}
columns={columns}
pageSize={10}
/>
)
})
`
}
+2741
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -1,4 +1,8 @@
{
"name": "react-table",
"root": "dist"
"root": "dist",
"routes": [{
"src": "/**",
"dest": "index.html"
}]
}
-643
View File
File diff suppressed because one or more lines are too long
+11 -3
View File
@@ -1,6 +1,6 @@
{
"name": "react-table",
"version": "2.1.3",
"version": "3.1.4",
"description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT",
"homepage": "https://github.com/tannerlinsley/react-table#readme",
@@ -15,13 +15,21 @@
"datagrid"
],
"main": "lib/index.js",
"files": [
"src/",
"lib/",
"react-table.js",
"react-table.css",
"media/*.png"
],
"scripts": {
"build:node": "babel src --out-dir lib --source-maps inline",
"build:css": "rm -rf react-table.css && stylus src/index.styl --use ./node_modules/nib/lib/nib.js --compress -o react-table.css",
"watch": "onchange 'src/**' -i -- npm-run-all build:*",
"test": "standard",
"umd": "rm -rf react-table.js && browserify lib/index.js -s reactTable -x react -t babelify -g uglifyify -o react-table.js",
"prepublish": "npm-run-all build:* && npm run umd"
"prepublish": "npm-run-all build:* && npm run umd",
"postpublish": "git push --tags"
},
"dependencies": {
"classnames": "^2.2.5"
@@ -40,7 +48,7 @@
"nib": "^1.1.2",
"npm-run-all": "^3.1.1",
"onchange": "^3.0.2",
"standard": "8.0.0",
"standard": "^8.0.0",
"stylus": "^0.54.5",
"uglifyify": "3.0.3"
},
-1
View File
File diff suppressed because one or more lines are too long
-7
View File
File diff suppressed because one or more lines are too long
+302 -334
View File
@@ -1,35 +1,54 @@
import React from 'react'
import classnames from 'classnames'
//
const _ = {
get,
takeRight,
last,
orderBy,
range,
clone,
remove
}
import _ from './utils'
const defaultButton = (props) => (
<button {...props} className='-btn'>{props.children}</button>
)
import Pagination from './pagination'
export const ReactTableDefaults = {
// General
data: [],
loading: false,
pageSize: 20,
showPagination: true,
showPageSizeOptions: true,
pageSizeOptions: [5, 10, 20, 25, 50, 100],
showPageJump: true,
// Callbacks
onChange: () => null,
// Classes
className: '-striped -highlight',
tableClassName: '',
theadClassName: '',
tbodyClassName: '',
trClassName: '',
trClassCallback: d => null,
thClassName: '',
thGroupClassName: '',
tdClassName: '',
paginationClassName: '',
//
pageSize: 20,
minRows: 0,
// Styles
style: {},
tableStyle: {},
theadStyle: {},
tbodyStyle: {},
trStyle: {},
trStyleCallback: d => {},
thStyle: {},
tdStyle: {},
paginationStyle: {},
// Global Column Defaults
column: {
sortable: true,
show: true
show: true,
className: '',
style: {},
innerClassName: '',
innerStyle: {},
headerClassName: '',
headerStyle: {},
headerInnerClassName: '',
headerInnerStyle: {}
},
// Text
previousText: 'Previous',
@@ -40,12 +59,24 @@ export const ReactTableDefaults = {
theadComponent: (props) => <thead {...props}>{props.children}</thead>,
tbodyComponent: (props) => <tbody {...props}>{props.children}</tbody>,
trComponent: (props) => <tr {...props}>{props.children}</tr>,
thComponent: (props) => <th {...props}>{props.children}</th>,
thComponent: (props) => {
const {toggleSort, ...rest} = props
return (
<th {...rest} onClick={e => {
toggleSort && toggleSort(e)
}}>{props.children}</th>
)
},
tdComponent: (props) => <td {...props}>{props.children}</td>,
previousComponent: null,
nextComponent: null,
// Unlisted
data: []
loadingComponent: props => (
<div className={classnames('-loading', {'-active': props.loading})}>
<div className='-loading-inner'>
{props.loadingText}
</div>
</div>
)
}
export default React.createClass({
@@ -54,88 +85,32 @@ export default React.createClass({
},
getInitialState () {
return {
page: 0,
sorting: false
}
},
componentWillMount () {
this.update(this.props)
componentDidMount () {
this.fireOnChange()
},
componentWillReceiveProps (nextProps) {
this.update(nextProps)
fireOnChange () {
this.props.onChange({
page: this.getPropOrState('page'),
pageSize: this.getStateOrProp('pageSize'),
pages: this.getPagesLength(),
sorting: this.getSorting()
}, this)
},
update (props) {
const resetState = {
loading: false,
page: 0,
pages: -1
// columns: {} for column hiding in the future
}
this.setState(resetState)
const newState = Object.assign({}, this.state, resetState)
this.isAsync = typeof props.data === 'function'
this.buildColumns(props, newState)
this.buildData(props, newState)
getPropOrState (key) {
return _.getFirstDefined(this.props[key], this.state[key])
},
buildColumns (props) {
this.hasHeaderGroups = false
props.columns.forEach(column => {
if (column.columns) {
this.hasHeaderGroups = true
}
})
this.headerGroups = []
this.decoratedColumns = []
let currentSpan = []
const addHeader = (columns, column = {}) => {
this.headerGroups.push(Object.assign({}, column, {
columns: columns
}))
currentSpan = []
}
const makeDecoratedColumn = (column) => {
const dcol = Object.assign({}, this.props.column, column)
if (typeof dcol.accessor === 'string') {
dcol.id = dcol.id || dcol.accessor
const accessorString = dcol.accessor
dcol.accessor = row => _.get(row, accessorString)
return dcol
}
if (dcol.accessor && !dcol.id) {
console.warn(dcol)
throw new Error('A column id is required if using a non-string accessor for column above.')
}
dcol.accessor = d => undefined
return dcol
}
props.columns.forEach((column, i) => {
if (column.columns) {
column.columns.forEach(nestedColumn => {
this.decoratedColumns.push(makeDecoratedColumn(nestedColumn))
})
if (this.hasHeaderGroups) {
if (currentSpan.length > 0) {
addHeader(currentSpan)
}
addHeader(_.takeRight(this.decoratedColumns, column.columns.length), column)
}
} else {
this.decoratedColumns.push(makeDecoratedColumn(column))
currentSpan.push(_.last(this.decoratedColumns))
}
})
if (this.hasHeaderGroups && currentSpan.length > 0) {
addHeader(currentSpan)
}
getStateOrProp (key) {
return _.getFirstDefined(this.state[key], this.props[key])
},
getInitSorting () {
const initSorting = this.decoratedColumns.filter(d => {
getInitSorting (columns) {
if (!columns) {
return []
}
const initSorting = columns.filter(d => {
return typeof d.sort !== 'undefined'
}).map(d => {
return {
@@ -145,104 +120,127 @@ export default React.createClass({
})
return initSorting.length ? initSorting : [{
id: this.decoratedColumns[0].id,
id: columns[0].id,
asc: true
}]
},
buildData (props, state) {
const sorting = state.sorting === false ? this.getInitSorting() : state.sorting
const setData = (data) => {
this.setState({
sorting,
data,
page: state.page,
loading: false
})
}
if (this.isAsync) {
this.setState({
loading: true
})
const cb = (res) => {
if (!res) {
return Promise.reject('Uh Oh! Nothing was returned in ReactTable\'s data callback!')
}
if (res.pages) {
this.setState({
pages: res.pages
})
}
// Only access the data. Sorting is done server side.
const accessedData = this.accessData(res.rows)
setData(accessedData)
}
// Fetch data with current state
const dataRes = props.data({
sorting,
page: state.page || 0,
pageSize: props.pageSize,
pages: state.pages
}, cb)
if (dataRes && dataRes.then) {
dataRes.then(cb)
}
} else {
// Return locally accessed, sorted data
const accessedData = this.accessData(props.data)
const sortedData = this.sortData(accessedData, sorting)
setData(sortedData)
}
},
accessData (data) {
return data.map((d, i) => {
const row = {
__original: d,
__index: i
}
this.decoratedColumns.forEach(column => {
row[column.id] = column.accessor(d)
})
return row
})
},
sortData (data, sorting) {
const resolvedSorting = sorting.length ? sorting : this.getInitSorting()
return _.orderBy(data, resolvedSorting.map(sort => {
return _.orderBy(data, sorting.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity
}
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
}
}), resolvedSorting.map(d => d.asc ? 'asc' : 'desc'))
}), sorting.map(d => d.asc ? 'asc' : 'desc'))
},
setPage (page) {
if (this.isAsync) {
return this.buildData(this.props, Object.assign({}, this.state, {page}))
makeDecoratedColumn (column) {
const dcol = Object.assign({}, this.props.column, column)
if (typeof dcol.accessor === 'string') {
dcol.id = dcol.id || dcol.accessor
const accessorString = dcol.accessor
dcol.accessor = row => _.get(row, accessorString)
return dcol
}
this.setState({
page
})
if (dcol.accessor && !dcol.id) {
console.warn(dcol)
throw new Error('A column id is required if using a non-string accessor for column above.')
}
if (!dcol.accessor) {
dcol.accessor = d => undefined
}
return dcol
},
getSorting (columns) {
return this.props.sorting || (this.state.sorting && this.state.sorting.length ? this.state.sorting : this.getInitSorting(columns))
},
getPagesLength () {
return this.props.manual ? this.props.pages
: Math.ceil(this.props.data.length / this.getStateOrProp('pageSize'))
},
getMinRows () {
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
},
render () {
const data = this.state.data ? this.state.data : []
// Build Columns
const decoratedColumns = []
const headerGroups = []
let currentSpan = []
const pagesLength = this.isAsync ? this.state.pages : Math.ceil(data.length / this.props.pageSize)
const startRow = this.props.pageSize * this.state.page
const endRow = startRow + this.props.pageSize
const pageRows = this.isAsync ? data.slice(0, this.props.pageSize) : data.slice(startRow, endRow)
const padRows = pagesLength > 1 ? _.range(this.props.pageSize - pageRows.length)
: this.props.minRows ? _.range(Math.max(this.props.minRows - pageRows.length, 0))
// Determine Header Groups
let hasHeaderGroups = false
this.props.columns
.forEach(column => {
if (column.columns) {
hasHeaderGroups = true
}
})
// A convenience function to add a header and reset the currentSpan
const addHeader = (columns, column = {}) => {
headerGroups.push(Object.assign({}, column, {
columns: columns
}))
currentSpan = []
}
// Build the columns and headers
const visibleColumns = this.props.columns.filter(d => _.getFirstDefined(d.show, true))
visibleColumns.forEach((column, i) => {
if (column.columns) {
const nestedColumns = column.columns.filter(d => _.getFirstDefined(d.show, true))
nestedColumns.forEach(nestedColumn => {
decoratedColumns.push(this.makeDecoratedColumn(nestedColumn))
})
if (hasHeaderGroups) {
if (currentSpan.length > 0) {
addHeader(currentSpan)
}
addHeader(_.takeRight(decoratedColumns, nestedColumns.length), column)
}
} else {
decoratedColumns.push(this.makeDecoratedColumn(column))
currentSpan.push(_.last(decoratedColumns))
}
})
if (hasHeaderGroups && currentSpan.length > 0) {
addHeader(currentSpan)
}
const sorting = this.getSorting(decoratedColumns)
const accessedData = this.props.data.map((d, i) => {
const row = {
__original: d,
__index: i
}
decoratedColumns.forEach(column => {
row[column.id] = column.accessor(d)
})
return row
})
const data = this.props.manual ? accessedData : this.sortData(accessedData, sorting)
// Normalize state
const currentPage = this.getPropOrState('page')
const pageSize = this.getStateOrProp('pageSize')
const pagesLength = this.getPagesLength()
// Pagination
const startRow = pageSize * currentPage
const endRow = startRow + pageSize
const pageRows = this.props.manual ? data : data.slice(startRow, endRow)
const minRows = this.getMinRows()
const padRows = pagesLength > 1 ? _.range(pageSize - pageRows.length)
: minRows ? _.range(Math.max(minRows - pageRows.length, 0))
: []
const canPrevious = this.state.page > 0
const canNext = this.state.page + 1 < pagesLength
const canPrevious = currentPage > 0
const canNext = currentPage + 1 < pagesLength
const TableComponent = this.props.tableComponent
const TheadComponent = this.props.theadComponent
@@ -250,24 +248,40 @@ export default React.createClass({
const TrComponent = this.props.trComponent
const ThComponent = this.props.thComponent
const TdComponent = this.props.tdComponent
const PreviousComponent = this.props.previousComponent || defaultButton
const NextComponent = this.props.nextComponent || defaultButton
const PreviousComponent = this.props.previousComponent
const NextComponent = this.props.nextComponent
const LoadingComponent = this.props.loadingComponent
return (
<div className={classnames(this.props.className, 'ReactTable')}>
<TableComponent className={classnames(this.props.tableClassName)}>
{this.hasHeaderGroups && (
<TheadComponent className={classnames(this.props.theadClassName, '-headerGroups')}>
<TrComponent className={this.props.trClassName}>
{this.headerGroups.map((column, i) => {
<div
className={classnames(this.props.className, 'ReactTable')}
style={this.props.style}
>
<TableComponent
className={classnames(this.props.tableClassName)}
style={this.props.tableStyle}
>
{hasHeaderGroups && (
<TheadComponent
className={classnames(this.props.theadGroupClassName, '-headerGroups')}
style={this.props.theadStyle}
>
<TrComponent
className={this.props.trClassName}
style={this.props.trStyle}
>
{headerGroups.map((column, i) => {
return (
<ThComponent
key={i}
className={classnames(column.className)}
colSpan={column.columns.length}>
colSpan={column.columns.length}
className={classnames(this.props.thClassname, column.headerClassName)}
style={Object.assign({}, this.props.thStyle, column.headerStyle)}
>
<div
className={classnames(column.innerClassName, '-th-inner')}>
className={classnames(column.headerInnerClassName, '-th-inner')}
style={Object.assign({}, this.props.thInnerStyle, column.headerInnerStyle)}
>
{typeof column.header === 'function' ? (
<column.header
data={this.props.data}
@@ -281,31 +295,40 @@ export default React.createClass({
</TrComponent>
</TheadComponent>
)}
<TheadComponent className={classnames(this.props.theadClassName)}>
<TrComponent className={this.props.trClassName}>
{this.decoratedColumns.map((column, i) => {
const sort = this.state.sorting.find(d => d.id === column.id)
<TheadComponent
className={classnames(this.props.theadClassName)}
style={this.props.theadStyle}
>
<TrComponent
className={this.props.trClassName}
style={this.props.trStyle}
>
{decoratedColumns.map((column, i) => {
const sort = sorting.find(d => d.id === column.id)
const show = typeof column.show === 'function' ? column.show() : column.show
return (
<ThComponent
key={i}
className={classnames(
column.className,
this.props.thClassname,
column.headerClassName,
sort ? (sort.asc ? '-sort-asc' : '-sort-desc') : '',
{
'-cursor-pointer': column.sortable,
'-hidden': !show
}
)}
onClick={(e) => {
style={Object.assign({}, this.props.thStyle, column.headerStyle)}
toggleSort={(e) => {
column.sortable && this.sortColumn(column, e.shiftKey)
}}>
}}
>
<div
className={classnames(column.innerClassName, '-th-inner')}
style={{
width: column.width + 'px',
className={classnames(column.headerInnerClassName, '-th-inner')}
style={Object.assign({}, column.headerInnerStyle, {
minWidth: column.minWidth + 'px'
}}>
})}
>
{typeof column.header === 'function' ? (
<column.header
data={this.props.data}
@@ -318,34 +341,45 @@ export default React.createClass({
})}
</TrComponent>
</TheadComponent>
<TbodyComponent className={classnames(this.props.tbodyClassName)}>
<TbodyComponent
className={classnames(this.props.tbodyClassName)}
style={this.props.tbodyStyle}
>
{pageRows.map((row, i) => {
const rowInfo = {
row: row.__original,
rowValues: row,
index: row.__index,
viewIndex: i
}
return (
<TrComponent
className={classnames(this.props.trClassName)}
key={i}>
{this.decoratedColumns.map((column, i2) => {
key={i}
className={classnames(this.props.trClassName, this.props.trClassCallback(rowInfo))}
style={Object.assign({}, this.props.trStyle, this.props.trStyleCallback(rowInfo))}
>
{decoratedColumns.map((column, i2) => {
const Cell = column.render
const show = typeof column.show === 'function' ? column.show() : column.show
return (
<TdComponent
key={i2}
className={classnames(column.className, {hidden: !show})}
key={i2}>
style={Object.assign({}, this.props.tdStyle, column.style)}
>
<div
className={classnames(column.innerClassName, '-td-inner')}
style={{
width: column.width + 'px',
style={Object.assign({}, column.innerStyle, {
minWidth: column.minWidth + 'px'
}}>
})}
>
{typeof Cell === 'function' ? (
<Cell
value={row[column.id]}
row={row.__original}
index={row.__index}
viewIndex={i}
{...rowInfo}
value={rowInfo.rowValues[column.id]}
/>
) : typeof Cell !== 'undefined' ? Cell
: row[column.id]}
) : typeof Cell !== 'undefined' ? Cell
: rowInfo.rowValues[column.id]}
</div>
</TdComponent>
)
@@ -356,20 +390,24 @@ export default React.createClass({
{padRows.map((row, i) => {
return (
<TrComponent
key={i}
className={classnames(this.props.trClassName, '-padRow')}
key={i}>
{this.decoratedColumns.map((column, i2) => {
style={this.props.trStyle}
>
{decoratedColumns.map((column, i2) => {
const show = typeof column.show === 'function' ? column.show() : column.show
return (
<TdComponent
key={i2}
className={classnames(column.className, {hidden: !show})}
key={i2}>
style={Object.assign({}, this.props.tdStyle, column.style)}
>
<div
className={classnames(column.innerClassName, '-td-inner')}
style={{
width: column.width + 'px',
style={Object.assign({}, column.innerStyle, {
minWidth: column.minWidth + 'px'
}}>&nbsp;</div>
})}
>&nbsp;</div>
</TdComponent>
)
})}
@@ -378,37 +416,51 @@ export default React.createClass({
})}
</TbodyComponent>
</TableComponent>
{pagesLength > 1 && (
<div className={classnames(this.props.paginationClassName, '-pagination')}>
<div className='-left'>
<PreviousComponent
onClick={canPrevious && ((e) => this.previousPage(e))}
disabled={!canPrevious}>
{this.props.previousText}
</PreviousComponent>
</div>
<div className='-center'>
Page {this.state.page + 1} of {pagesLength}
</div>
<div className='-right'>
<NextComponent
onClick={canNext && ((e) => this.nextPage(e))}
disabled={!canNext}>
{this.props.nextText}
</NextComponent>
</div>
</div>
{this.props.showPagination && (
<Pagination
currentPage={currentPage}
pagesLength={pagesLength}
pageSize={pageSize}
showPageSizeOptions={this.props.showPageSizeOptions}
pageSizeOptions={this.props.pageSizeOptions}
showPageJump={this.props.showPageJump}
canPrevious={canPrevious}
canNext={canNext}
previousText={this.props.previousText}
nextText={this.props.nextText}
previousComponent={PreviousComponent}
nextComponent={NextComponent}
//
onChange={this.setPage}
onPageSizeChange={this.setPageSize}
/>
)}
<div className={classnames('-loading', {'-active': this.state.loading})}>
<div className='-loading-inner'>
{this.props.loadingText}
</div>
</div>
<LoadingComponent {...this.props} />
</div>
)
},
// User actions
setPage (page) {
this.setState({
page
}, () => {
this.fireOnChange()
})
},
setPageSize (pageSize) {
const currentPageSize = this.getStateOrProp('pageSize')
const currentPage = this.getPropOrState('page')
const currentRow = currentPageSize * currentPage
const page = Math.floor(currentRow / pageSize)
this.setState({
pageSize,
page
}, () => {
this.fireOnChange()
})
},
sortColumn (column, additive) {
const existingSorting = this.state.sorting || []
const existingSorting = this.getSorting()
let sorting = _.clone(this.state.sorting || [])
const existingIndex = sorting.findIndex(d => d.id === column.id)
if (existingIndex > -1) {
@@ -440,95 +492,11 @@ export default React.createClass({
}
}
const page = (existingIndex === 0 || (!existingSorting.length && sorting.length) || !additive) ? 0 : this.state.page
this.buildData(this.props, Object.assign({}, this.state, {page, sorting}))
},
nextPage (e) {
e.preventDefault()
this.setPage(this.state.page + 1)
},
previousPage (e) {
e.preventDefault()
this.setPage(this.state.page - 1)
this.setState({
page,
sorting
}, () => {
this.fireOnChange()
})
}
})
// ########################################################################
// Utils
// ########################################################################
function remove (a, b) {
return a.filter(function (o, i) {
var r = b(o)
if (r) {
a.splice(i, 1)
return true
}
return false
})
}
function get (a, b) {
if (isArray(b)) {
b = b.join('.')
}
return b
.replace('[', '.').replace(']', '')
.split('.')
.reduce(
function (obj, property) {
return obj[property]
}, a
)
}
function takeRight (arr, n) {
const start = n > arr.length ? 0 : arr.length - n
return arr.slice(start)
}
function last (arr) {
return arr[arr.length - 1]
}
function range (n) {
const arr = []
for (let i = 0; i < n; i++) {
arr.push(n)
}
return arr
}
function orderBy (arr, funcs, dirs) {
return arr.sort((a, b) => {
for (let i = 0; i < funcs.length; i++) {
const comp = funcs[i]
const ca = comp(a)
const cb = comp(b)
const desc = dirs[i] === false || dirs[i] === 'desc'
if (ca > cb) {
return desc ? -1 : 1
}
if (ca < cb) {
return desc ? 1 : -1
}
}
return 0
})
}
function clone (a) {
return JSON.parse(JSON.stringify(a, function (key, value) {
if (typeof value === 'function') {
return value.toString()
}
return value
}))
}
// ########################################################################
// Helpers
// ########################################################################
function isArray (a) {
return Array.isArray(a)
}
+55 -10
View File
@@ -120,10 +120,10 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
.-pagination
width:100%
display:flex
margin-top:5px
justify-content: space-between
align-items: center
flex-wrap: wrap
padding-top:3px
.-btn
appearance:none
@@ -147,21 +147,39 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
background: alpha(black, .3)
color: white
.-left
.-previous
.-center
.-right
flex: 1
// min-width:150px
margin-bottom:5px
.-next
margin:3px 0
.-left
.-right
.-previous
.-next
flex: 1
text-align: center
.-center
flex: 1.5
text-align:center
padding: 0 5px
margin-bottom:0
display: flex
flex-direction: row
flex-wrap: wrap
align-items: center
justify-content: space-around
.-pageInfo
display: inline-block
margin: 0 10px 3px
white-space: nowrap
.-pageJump
display:inline-block
input
width: 70px
text-align:center
.-pageSizeOptions
margin:0 10px 3px
.-loading
@@ -194,3 +212,30 @@ $easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
pointer-events: all
> div
transform: translateY(50%)
input
select
appearance: none
border: 1px solid rgba(0,0,0,0.1)
background: white
padding: 5px 7px
font-size: inherit
border-radius: 3px
font-weight: normal
outline:none
.select-wrap
position:relative
display:inline-block
select
padding: 5px 15px 5px 7px
min-width:100px
&:after
content: ''
position: absolute
right: 8px
top: 50%
transform: translate(0, -50%)
border-color: #999 transparent transparent
border-style: solid
border-width: 5px 5px 2.5px
+121
View File
@@ -0,0 +1,121 @@
import React from 'react'
import classnames from 'classnames'
//
// import _ from './utils'
const defaultButton = (props) => (
<button {...props} className='-btn'>{props.children}</button>
)
export default React.createClass({
getInitialState () {
return {
page: this.props.currentPage
}
},
componentWillReceiveProps (nextProps) {
this.setState({page: nextProps.currentPage})
},
getSafePage (page) {
return Math.min(Math.max(page, 0), this.props.pagesLength - 1)
},
changePage (page) {
page = this.getSafePage(page)
this.setState({page})
this.props.onChange(page)
},
applyPage (e) {
e && e.preventDefault()
const page = this.state.page
this.changePage(page === '' ? this.props.currentPage : page)
},
render () {
const {
currentPage,
pagesLength,
showPageSizeOptions,
pageSizeOptions,
pageSize,
showPageJump,
canPrevious,
canNext,
onPageSizeChange
} = this.props
const PreviousComponent = this.props.previousComponent || defaultButton
const NextComponent = this.props.nextComponent || defaultButton
return (
<div
className={classnames(this.props.paginationClassName, '-pagination')}
style={this.props.paginationStyle}
>
<div className='-previous'>
<PreviousComponent
onClick={(e) => {
if (!canPrevious) return
this.changePage(currentPage - 1)
}}
disabled={!canPrevious}
>
{this.props.previousText}
</PreviousComponent>
</div>
<div className='-center'>
<span className='-pageInfo'>
Page {showPageJump ? (
<form className='-pageJump'
onSubmit={this.applyPage}
>
<input
type={this.state.page === '' ? 'text' : 'number'}
onChange={e => {
const val = e.target.value
const page = val - 1
if (val === '') {
return this.setState({page: val})
}
this.setState({page: this.getSafePage(page)})
}}
value={this.state.page === '' ? '' : this.state.page + 1}
onBlur={this.applyPage}
/>
</form>
) : (
<span className='-currentPage'>{currentPage + 1}</span>
)} of <span className='-totalPages'>{pagesLength}</span>
</span>
{showPageSizeOptions && (
<span className='select-wrap -pageSizeOptions'>
<select
onChange={(e) => onPageSizeChange(Number(e.target.value))}
value={pageSize}
>
{pageSizeOptions.map((option, i) => {
return (
<option
key={i}
value={option}>
{option} rows
</option>
)
})}
</select>
</span>
)}
</div>
<div className='-next'>
<NextComponent
onClick={(e) => {
if (!canNext) return
this.changePage(currentPage + 1)
}}
disabled={!canNext}
>
{this.props.nextText}
</NextComponent>
</div>
</div>
)
}
})
+95
View File
@@ -0,0 +1,95 @@
export default {
get,
takeRight,
last,
orderBy,
range,
clone,
remove,
getFirstDefined
}
function remove (a, b) {
return a.filter(function (o, i) {
var r = b(o)
if (r) {
a.splice(i, 1)
return true
}
return false
})
}
function get (a, b) {
if (isArray(b)) {
b = b.join('.')
}
return b
.replace('[', '.').replace(']', '')
.split('.')
.reduce(
function (obj, property) {
return obj[property]
}, a
)
}
function takeRight (arr, n) {
const start = n > arr.length ? 0 : arr.length - n
return arr.slice(start)
}
function last (arr) {
return arr[arr.length - 1]
}
function range (n) {
const arr = []
for (let i = 0; i < n; i++) {
arr.push(n)
}
return arr
}
function orderBy (arr, funcs, dirs) {
return arr.sort((a, b) => {
for (let i = 0; i < funcs.length; i++) {
const comp = funcs[i]
const ca = comp(a)
const cb = comp(b)
const desc = dirs[i] === false || dirs[i] === 'desc'
if (ca > cb) {
return desc ? -1 : 1
}
if (ca < cb) {
return desc ? 1 : -1
}
}
return 0
})
}
function clone (a) {
return JSON.parse(JSON.stringify(a, function (key, value) {
if (typeof value === 'function') {
return value.toString()
}
return value
}))
}
// ########################################################################
// Helpers
// ########################################################################
function isArray (a) {
return Array.isArray(a)
}
function getFirstDefined (...args) {
for (var i = 0; i < args.length; i++) {
if (typeof args[i] !== 'undefined') {
return args[i]
}
}
}
+3601
View File
File diff suppressed because it is too large Load Diff