mirror of
https://github.com/gosticks/react-table.git
synced 2025-10-16 11:55:36 +00:00
parent
f27ec1fb34
commit
49c5a3d5e1
44
CHANGELOG.md
44
CHANGELOG.md
@ -0,0 +1,44 @@
|
||||
## 6.0.0
|
||||
|
||||
##### New Features
|
||||
- New Renderers:
|
||||
- `Aggregated` - Custom renderer for aggregated cells
|
||||
- `Pivot` - Custom renderer for Pivoted Cells (utilizes `Expander` and `PivotValue`)
|
||||
- `PivotValue` - Custom renderer for Pivot cell values (deprecates the undocumented `pivotRender` option)
|
||||
- `Expander` - Custom renderer for Pivot cell Expander
|
||||
- Added custom sorting methods per table via `defaultSortMethod` and per column via `column.sortMethod`
|
||||
- Pivot columns are now visibly separate and sorted/filtered independently.
|
||||
- Added `column.resizable` to override global table `resizable` option for specific columns.
|
||||
- Added `column.sortable` to override global table `sortable` option for specific columns.
|
||||
- Added `column.filterable` to override global table `filterable` option for specific columns.
|
||||
- Added `defaultExpanded` table option.
|
||||
- All callbacks can now be utilized without needing to hoist and manage the piece of state they export. That is what their prop counterparts are for, so now the corresponding prop is used instead of the callback to detect a "fully controlled" state.
|
||||
- Prevent transitions while column resizing for a smoother resize effect.
|
||||
- Disable text selection while resizing columns.
|
||||
|
||||
|
||||
##### Breaking API Changes
|
||||
- New Renderers:
|
||||
- `Cell` - deprecates and replaces `render`
|
||||
- `Header` - deprecates and replaces `header`
|
||||
- `Footer` - deprecates and replaces `footer`
|
||||
- `Filter`- deprecates and replaces `filterRender`
|
||||
- Callbacks now provide the destination state as the primary parameter(s). This makes hoisting and controlling the state in redux or component state much easier. eg.
|
||||
- `onSorting` no longer requires you to build your own toggle logic
|
||||
- `onResize` no longer requires you to build your own resize logic
|
||||
- Renamed `onChange` callback -> `onFetchData` which will always fire when a new data model needs to be fetched (or if not using `manual`, when new data is materialized internally).
|
||||
- Renamed `filtering` -> `filtered`
|
||||
- Renamed `sorting` -> `sorted`
|
||||
- Renamed `expandedRows` -> `expanded`
|
||||
- Renamed `resizing` -> `resized`
|
||||
- Renamed `defaultResizing` -> `defaultResized`
|
||||
- Renamed `defaultFiltering` -> `defaultFiltered`
|
||||
- Renamed `defaultSorting` -> `defaultSorted`
|
||||
- Renamed `onSortingChange` -> `onSortedChange`
|
||||
- Renamed `onFilteringChange` -> `onFilteredChange`
|
||||
- Renamed `onResize` -> `onResizedChange`
|
||||
- Renamed `onExpandRow` -> `onExpandedChange`
|
||||
- Renamed `showFilters` -> `filterable`
|
||||
- Renamed `hideFilter` -> `filterable` (Column option. Note the true/false value is now flipped.)
|
||||
- `cellInfo.row` and `rowInfo.row` now reference the materialize data for the table. To reference the original row, use `cellInfo.original` and `rowInfo.original`
|
||||
- Removed `pivotRender` column option. You can now control how the value is displayed by overriding the `PivotValueComponent` or the individual column's `PivotValue` renderer. See [Pivoting Options Story](https://react-table.js.org/?selectedKind=2.%20Demos&selectedStory=Pivoting%20Options&full=0&down=1&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel) for a reference on how to customize pivot column rendering.
|
||||
@ -1,7 +1,7 @@
|
||||
## Problem Description
|
||||
include a detailed explanation of the problem here...
|
||||
|
||||
## Code Snippet(s) or Codepen (http://codepen.io/tannerlinsley/pen/QpeZBa?editors=0010) // TODO
|
||||
## Code Snippet(s) or Codepen (http://codepen.io/tannerlinsley/pen/QpeZBa?editors=0010)
|
||||
|
||||
## Steps to Reproduce
|
||||
1. list the steps
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Tanner Linsley
|
||||
Copyright (c) 2016 Tanner Linsley
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
860
README.md
860
README.md
@ -1,25 +1,25 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/tannerlinsley/react-story" target="\_parent"><img src="https://github.com/tannerlinsley/react-story/raw/master/media/Banner.png" alt="React Table Logo" style="width:450px;"/></a>
|
||||
<a href="https://github.com/tannerlinsley/react-table" target="\_parent"><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
|
||||
`react-story` is a **lightweight, fast and extendable datagrid** built for React
|
||||
`react-table` is a **lightweight, fast and extendable datagrid** built for React
|
||||
|
||||
|
||||
<a href="https://travis-ci.org/tannerlinsley/react-story" target="\_parent">
|
||||
<img alt="" src="https://travis-ci.org/tannerlinsley/react-story.svg?branch=master" />
|
||||
<a href="https://travis-ci.org/tannerlinsley/react-table" target="\_parent">
|
||||
<img alt="" src="https://travis-ci.org/tannerlinsley/react-table.svg?branch=master" />
|
||||
</a>
|
||||
<a href="https://npmjs.com/package/react-story" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/npm/dm/react-story.svg" />
|
||||
<a href="https://npmjs.com/package/react-table" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/npm/dm/react-table.svg" />
|
||||
</a>
|
||||
<a href="https://react-chat-signup.herokuapp.com/" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/badge/slack-react--chat-blue.svg" />
|
||||
</a>
|
||||
<a href="https://github.com/tannerlinsley/react-story" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/github/stars/tannerlinsley/react-story.svg?style=social&label=Star" />
|
||||
<a href="https://github.com/tannerlinsley/react-table" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/github/stars/tannerlinsley/react-table.svg?style=social&label=Star" />
|
||||
</a>
|
||||
<a href="https://twitter.com/tannerlinsley" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
|
||||
@ -28,15 +28,28 @@
|
||||
<img alt="" src="https://img.shields.io/badge/%24-Donate-brightgreen.svg" />
|
||||
</a>
|
||||
|
||||
## Versions
|
||||
This document refers to version 6.x.x of react-table.
|
||||
|
||||
Previous versions:
|
||||
|
||||
[5.x.x Readme](https://github.com/tannerlinsley/react-table/blob/ad7d31cd3978eb45da7c6194dbab93c1e9a8594d/README.md)
|
||||
|
||||
## Features
|
||||
- Lightweight at 1kb
|
||||
- Lightweight at 7kb (and just 2kb more for styles)
|
||||
- Fully customizable JSX templating
|
||||
- Supports both Client-side & Server-side pagination and multi-sorting
|
||||
- Column Pivoting & Aggregation
|
||||
- Minimal design & easily themeable
|
||||
- Fully controllable via optional props and callbacks
|
||||
- <a href="https://medium.com/@tannerlinsley/why-i-wrote-react-table-and-the-problems-it-has-solved-for-nozzle-others-445c4e93d4a8#.axza4ixba" target="\_parent">"Why I wrote React Table and the problems it has solved for Nozzle.io</a> by Tanner Linsley
|
||||
|
||||
## Demos and examples
|
||||
<!-- - <a href="http://codepen.io/tannerlinsley/pen/QpeZBa?editors=0010" target="\_blank">Codepen</a>
|
||||
- <a href="http://react-story.js.org/?selectedKind=2.%20Demos&selectedStory=Client-side%20Data&full=0&down=0&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel" target="\_parent">Storybook</a> -->
|
||||
- <a href="http://codepen.io/tannerlinsley/pen/QpeZBa?editors=0010" target="\_blank">Codepen</a>
|
||||
- <a href="http://react-table.js.org/?selectedKind=2.%20Demos&selectedStory=Client-side%20Data&full=0&down=0&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel" target="\_parent">Storybook</a>
|
||||
|
||||
## Table of Contents
|
||||
<!-- - [Installation](#installation)
|
||||
- [Installation](#installation)
|
||||
- [Example](#example)
|
||||
- [Data](#data)
|
||||
- [Props](#props)
|
||||
@ -46,7 +59,7 @@
|
||||
- [Styles](#styles)
|
||||
- [Custom Props](#custom-props)
|
||||
- [Pivoting and Aggregation](#pivoting-and-aggregation)
|
||||
- [Sub Tables and Sub Components](#sub-storys-and-sub-components)
|
||||
- [Sub Tables and Sub Components](#sub-tables-and-sub-components)
|
||||
- [Server-side Data](#server-side-data)
|
||||
- [Fully Controlled Component](#fully-controlled-component)
|
||||
- [Functional Rendering](#functional-rendering)
|
||||
@ -55,62 +68,823 @@
|
||||
- [Component Overrides](#component-overrides)
|
||||
- [Contributing](#contributing)
|
||||
- [Scripts](#scripts)
|
||||
- [Used By](#used-by) -->
|
||||
- [Used By](#used-by)
|
||||
|
||||
|
||||
## Installation
|
||||
1. Install React Table as a dependency
|
||||
```bash
|
||||
$ yarn add react-story
|
||||
$ yarn add react-table
|
||||
```
|
||||
2. Import the `react-story` module
|
||||
2. Import the `react-table` module
|
||||
```javascript
|
||||
// ES6
|
||||
import ReactStory from 'react-story'
|
||||
import ReactTable from 'react-table'
|
||||
// ES5
|
||||
var ReactStory = require('react-story').default
|
||||
var ReactTable = require('react-table').default
|
||||
```
|
||||
3. Import styles by including `react-table.css`
|
||||
```javascript
|
||||
// JS (Webpack)
|
||||
import 'react-table/react-table.css'
|
||||
// Old-school
|
||||
<link rel="stylesheet" href="node_modules/react-table/react-table.css">
|
||||
```
|
||||
##### CDN
|
||||
```html
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/react-table@latest/react-table.css">
|
||||
|
||||
<!-- JS -->
|
||||
<script src="https://unpkg.com/react-story@latest/react-story.js"></script>
|
||||
<script src="https://unpkg.com/react-table@latest/react-table.js"></script>
|
||||
|
||||
<script>
|
||||
var ReactStory = window.ReactStory.default
|
||||
var ReactTable = window.ReactTable.default
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## Example
|
||||
```javascript
|
||||
import ReactStory from 'react-story'
|
||||
import ReactTable from 'react-table'
|
||||
|
||||
render () {
|
||||
return (
|
||||
<ReactStory
|
||||
stories={[
|
||||
Story1Component,
|
||||
Story2Component,
|
||||
Story3Component
|
||||
]}
|
||||
</ReactStory>
|
||||
render() {
|
||||
const data = [{
|
||||
name: 'Tanner Linsley',
|
||||
age: 26,
|
||||
friend: {
|
||||
name: 'Jason Maurer',
|
||||
age: 23,
|
||||
}
|
||||
},{
|
||||
...
|
||||
}]
|
||||
|
||||
// or
|
||||
<ReactStory
|
||||
stories={[{
|
||||
name: 'Story 1',
|
||||
story: Story1Component
|
||||
}, {
|
||||
path: 'story2',
|
||||
story: Story2Component
|
||||
}, {
|
||||
story: Story3Component
|
||||
}]}
|
||||
/>
|
||||
)
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
accessor: 'name' // String-based value accessors!
|
||||
}, {
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Cell: props => <span className='number'>{props.value}</span> // Custom cell components!
|
||||
}, {
|
||||
id: 'friendName', // Required because our accessor is not a string
|
||||
Header: 'Friend Name',
|
||||
accessor: d => d.friend.name // Custom value accessors!
|
||||
}, {
|
||||
Header: props => <span>Friend Age</span>, // Custom header components!
|
||||
accessor: 'friend.age'
|
||||
}]
|
||||
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
/>
|
||||
}
|
||||
```
|
||||
|
||||
## Data
|
||||
Simply pass the `data` prop anything that resembles an array or object. Client-side sorting and pagination are built in, and your table will update gracefully as you change any props. [Server-side data](#server-side-data) is also supported!
|
||||
|
||||
|
||||
## Props
|
||||
These are all of the available props (and their default values) for the main `<ReactTable />` component.
|
||||
```javascript
|
||||
{
|
||||
// General
|
||||
data: [],
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
pageSizeOptions: [5, 10, 20, 25, 50, 100],
|
||||
defaultPageSize: 20,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
filterable: false,
|
||||
defaultSorted: [],
|
||||
defaultFiltered: [],
|
||||
defaultResized: [],
|
||||
defaultExpanded: {},
|
||||
defaultFilterMethod: (filter, row, column) => {
|
||||
const id = filter.pivotId || filter.id
|
||||
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
|
||||
},
|
||||
defaultSortMethod: (a, b) => {
|
||||
// force null and undefined to the bottom
|
||||
a = (a === null || a === undefined) ? -Infinity : a
|
||||
b = (b === null || b === undefined) ? -Infinity : b
|
||||
// force any string values to lowercase
|
||||
a = a === 'string' ? a.toLowerCase() : a
|
||||
b = b === 'string' ? b.toLowerCase() : b
|
||||
// Return either 1 or -1 to indicate a sort priority
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
// returning 0, undefined or any falsey value will use subsequent sorts or the index as a tiebreaker
|
||||
return 0
|
||||
},
|
||||
|
||||
// Controlled State Overrides (see Fully Controlled Component section)
|
||||
page: undefined,
|
||||
pageSize: undefined,
|
||||
sorted: [],
|
||||
filtered: [],
|
||||
resized: [],
|
||||
expanded: {},
|
||||
|
||||
// Controlled State Callbacks
|
||||
onPageChange: undefined,
|
||||
onPageSizeChange: undefined,
|
||||
onSortedChange: undefined,
|
||||
onFilteredChange: undefined,
|
||||
onResizedChange: undefined,
|
||||
onExpandedChange: undefined,
|
||||
|
||||
// Pivoting
|
||||
pivotBy: undefined,
|
||||
|
||||
// Key Constants
|
||||
pivotValKey: '_pivotVal',
|
||||
pivotIDKey: '_pivotID',
|
||||
subRowsKey: '_subRows',
|
||||
aggregatedKey: '_aggregated',
|
||||
nestingLevelKey: '_nestingLevel',
|
||||
originalKey: '_original',
|
||||
indexKey: '_index',
|
||||
groupedByPivotKey: '_groupedByPivot',
|
||||
|
||||
// Server-side callbacks
|
||||
onFetchData: () => null,
|
||||
|
||||
// Classes
|
||||
className: '',
|
||||
style: {},
|
||||
|
||||
// Component decorators
|
||||
getProps: () => ({}),
|
||||
getTableProps: () => ({}),
|
||||
getTheadGroupProps: () => ({}),
|
||||
getTheadGroupTrProps: () => ({}),
|
||||
getTheadGroupThProps: () => ({}),
|
||||
getTheadProps: () => ({}),
|
||||
getTheadTrProps: () => ({}),
|
||||
getTheadThProps: () => ({}),
|
||||
getTheadFilterProps: () => ({}),
|
||||
getTheadFilterTrProps: () => ({}),
|
||||
getTheadFilterThProps: () => ({}),
|
||||
getTbodyProps: () => ({}),
|
||||
getTrGroupProps: () => ({}),
|
||||
getTrProps: () => ({}),
|
||||
getThProps: () => ({}),
|
||||
getTdProps: () => ({}),
|
||||
getTfootProps: () => ({}),
|
||||
getTfootTrProps: () => ({}),
|
||||
getTfootThProps: () => ({}),
|
||||
getPaginationProps: () => ({}),
|
||||
getLoadingProps: () => ({}),
|
||||
getNoDataProps: () => ({}),
|
||||
getResizerProps: () => ({}),
|
||||
|
||||
// Global Column Defaults
|
||||
column: {
|
||||
// Renderers
|
||||
Cell: undefined,
|
||||
Header: undefined,
|
||||
Footer: undefined,
|
||||
Aggregated: undefined,
|
||||
Pivot: undefined,
|
||||
PivotValue: undefined,
|
||||
Expander: undefined,
|
||||
Filter: undefined,
|
||||
// Standard options
|
||||
sortable: undefined, // use table default
|
||||
resizable: undefined, // use table default
|
||||
filterable: undefined, // use table default
|
||||
show: true,
|
||||
minWidth: 100,
|
||||
// Cells only
|
||||
className: '',
|
||||
style: {},
|
||||
getProps: () => ({}),
|
||||
// Headers only
|
||||
headerClassName: '',
|
||||
headerStyle: {},
|
||||
getHeaderProps: () => ({})
|
||||
// Footers only
|
||||
footerClassName: '',
|
||||
footerStyle: {},
|
||||
getFooterProps: () => ({}),
|
||||
filterMethod: undefined,
|
||||
sortMethod: undefined
|
||||
},
|
||||
|
||||
// Global Expander Column Defaults
|
||||
expanderDefaults: {
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
filterable: false,
|
||||
width: 35
|
||||
},
|
||||
|
||||
// Global Pivot Column Defaults
|
||||
pivotDefaults: {},
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingText: 'Loading...',
|
||||
noDataText: 'No rows found',
|
||||
pageText: 'Page',
|
||||
ofText: 'of',
|
||||
rowsText: 'rows',
|
||||
}
|
||||
```
|
||||
|
||||
You can easily override the core defaults like so:
|
||||
|
||||
```javascript
|
||||
import { ReactTableDefaults } from 'react-table'
|
||||
|
||||
Object.assign(ReactTableDefaults, {
|
||||
defaultPageSize: 10,
|
||||
minRows: 3,
|
||||
// etc...
|
||||
})
|
||||
```
|
||||
|
||||
Or just define them as props
|
||||
|
||||
```javascript
|
||||
<ReactTable
|
||||
defaultPageSize={10}
|
||||
minRows={3}
|
||||
// etc...
|
||||
/>
|
||||
```
|
||||
|
||||
## Columns
|
||||
`<ReactTable/>` requires a `columns` prop, which is an array of objects containing the following properties
|
||||
|
||||
```javascript
|
||||
[{
|
||||
// Renderers - For more information, see "Renderers" section below
|
||||
Cell: JSX | String | Function // Used to render a standard cell, defaults to the accessed value
|
||||
Header: JSX | String | Function // Used to render the header of a column or column group
|
||||
Footer: JSX | String | Function // Used to render the footer of a column
|
||||
Filter: JSX | cellInfo => ( // Used to render the filter UI of a filter-enabled column
|
||||
<select onChange={event => onFiltersChange(event.target.value)} value={filter ? filter.value : ''}></select>
|
||||
// The value passed to onFiltersChange will be the value passed to filter.value of the filterMethod
|
||||
)
|
||||
Aggregated: JSX | String | Function // Used to render aggregated cells. Defaults to a comma separated list of values.
|
||||
Pivot: JSX | String | Function | cellInfo => ( // Used to render a pivoted cell
|
||||
<span>
|
||||
<Expander/><PivotValue /> // By default, will utilize the the PivotValue and Expander components at run time
|
||||
</span>
|
||||
),
|
||||
PivotValue: JSX | String | Function // Used to render the value inside of a Pivot cell
|
||||
Expander: JSX | String | Function // Used to render the expander in both Pivot and Expander cells
|
||||
|
||||
// General
|
||||
accessor: 'propertyName', // or Accessor eg. (row) => row.propertyName (see "Accessors" section for more details)
|
||||
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: boolean, // Overrides the table option
|
||||
resizable: boolean, // Overrides the table option
|
||||
filterable: boolean, // Overrides the table option
|
||||
show: true, // can be used to hide a column
|
||||
width: undefined, // A hardcoded width for the column. This overrides both min and max width options
|
||||
minWidth: 100, // A minimum width for this column. If there is extra room, column will flex to fill available space (up to the max-width, if set)
|
||||
maxWidth: undefined, // A maximum width for this column.
|
||||
|
||||
// Special
|
||||
pivot: false,
|
||||
// Turns this column into a special column for specifying pivot position in your column definitions.
|
||||
// The `pivotDefaults` options will be applied on top of this column's options.
|
||||
// It will also let you specify rendering of the header (and header group if this special column is placed in the `columns` option of another column)
|
||||
expander: false,
|
||||
// Turns this column into a special column for specifying expander position and options in your column definitions.
|
||||
// The `expanderDefaults` options will be applied on top of this column's options.
|
||||
// It will also let you specify rendering of the header (and header group if this special column is placed in the `columns` option of another column) and
|
||||
// the rendering of the expander itself via the `Expander` property
|
||||
|
||||
// Cell Options
|
||||
className: '', // Set the classname of the `td` element of the column
|
||||
style: {}, // Set the style of the `td` element of the column
|
||||
// Header & HeaderGroup Options
|
||||
headerClassName: '', // Set the classname 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
|
||||
columns: [...], // See Header Groups section below
|
||||
|
||||
// Footer
|
||||
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
|
||||
|
||||
// Filtering
|
||||
filterMethod: (filter, row, column) => {return true}, // A function returning a boolean that specifies the filtering logic for the column
|
||||
// filter == an object specifying which filter is being applied. Format: {id: [the filter column's id], value: [the value the user typed in the filter field], pivotId: [if filtering on a pivot column, the pivotId will be set to the pivot column's id and the `id` field will be set to the top level pivoting column]}
|
||||
// row == the row of data supplied to the table
|
||||
// column == the column that the filter is on
|
||||
}]
|
||||
```
|
||||
|
||||
## Renderers
|
||||
React Table supports very flexible renderers for just about everything:
|
||||
- `Cell` - Renders a standard cell
|
||||
- `Header` - Renders a column header or column group header
|
||||
- `Footer` - Renders a column footer
|
||||
- `Filter` - Renders a column's filter UI
|
||||
- `Aggregated` - Renders an aggregated cell
|
||||
- `Pivot` - Renders a pivoted cell (by default, will utilize `Expander` and `PivotValue` renderers)
|
||||
- `PivotValue` - Renders the value inside a `Pivot` renderer
|
||||
- `Expander` - Renders the Expander used in both the default `Pivot` renderer and any expander-designated column
|
||||
|
||||
Any of these renderers can be one of the following:
|
||||
- A React Class
|
||||
- JSX or any rendered react component
|
||||
- Stateless functional component
|
||||
- Function that returns any primitive
|
||||
|
||||
All of these formats receive the following props:
|
||||
```javascript
|
||||
{
|
||||
// Row-level props
|
||||
row: Object, // the materialized row of data
|
||||
original: , // the original row of data
|
||||
index: '', // the index of the row in the original arra
|
||||
viewIndex: '', // the index of the row relative to the current view
|
||||
level: '', // the nesting level of this row
|
||||
nestingPath: '', // the nesting path of this row
|
||||
aggregated: '', // true if this row's values were aggregated
|
||||
groupedByPivot: '', // true if this row was produced by a pivot
|
||||
subRows: '', // any sub rows defined by the `subRowKey` prop
|
||||
|
||||
// Cells-level props
|
||||
isExpanded: '', // true if this row is expanded
|
||||
value: '', // the materialized value of this cell
|
||||
resized: '', // the resize information for this cell's column
|
||||
show: '', // true if the column is visible
|
||||
width: '', // the resolved width of this cell
|
||||
maxWidth: '', // the resolved maxWidth of this cell
|
||||
tdProps: '', // the resolved tdProps from `getTdProps` for this cell
|
||||
columnProps: '', // the resolved column props from 'getProps' for this cell's column
|
||||
classes: '', // the resolved array of classes for this cell
|
||||
styles: '' // the resolved styles for this cell
|
||||
}
|
||||
```
|
||||
|
||||
## Accessors
|
||||
Accessors are functions that return the value to populate the row's value for the column.
|
||||
This lets the render function not have to worry about accessing the correct data, the value is automatically populated in it's props.
|
||||
|
||||
If a `string` or `array` is passed the default accessor is used.
|
||||
The default accessor will parse the input into an array and recursively flatten it.
|
||||
Any values that contain a dot (`.`) will be split.
|
||||
Any values that contain bracket (`[]`) will be split.
|
||||
This array is then used as the path to the value to return.
|
||||
|
||||
("$" is the placeholder value that would be returned by the default accessor)
|
||||
|
||||
| value | path | data |
|
||||
|--------------|-----------------|------------------------|
|
||||
| "a" | ["a"] | {"a": $} |
|
||||
| "a.b" | ["a", "b"] | {"a": {"b": $}} |
|
||||
| "a[0]" | ["a", "0"] | {"a": [$]} |
|
||||
| ["a.b", "c"] | ["a", "b", "c"] | {"a": {"b": {"c": $}}} |
|
||||
|
||||
*NOTE*
|
||||
If your data has a field/key with a dot (`.`) you will need to supply a custom accessor.
|
||||
|
||||
|
||||
## Column Header Groups
|
||||
To group columns with another header column, just nest your columns in a header column. Header columns utilize the same header properties as regular columns.
|
||||
```javascript
|
||||
const columns = [{
|
||||
Header: 'Favorites',
|
||||
headerClassName: 'my-favorites-column-header-group'
|
||||
columns: [{
|
||||
Header: 'Color',
|
||||
accessor: 'favorites.color'
|
||||
}, {
|
||||
Header: 'Food',
|
||||
accessor: 'favorites.food'
|
||||
} {
|
||||
Header: 'Actor',
|
||||
accessor: 'favorites.actor'
|
||||
}]
|
||||
}]
|
||||
```
|
||||
|
||||
## Custom Cell, Header and Footer Rendering
|
||||
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` - The post-accessed values from the original row
|
||||
- `index` - The index of the row
|
||||
- `viewIndex` - the index of the row relative to the current page
|
||||
- `level` - The nesting depth (zero-indexed)
|
||||
- `nestingPath` - The nesting path of the row
|
||||
- `aggregated` - A boolean stating if the row is an aggregation row
|
||||
- `subRows` - An array of any expandable sub-rows contained in this row
|
||||
|
||||
```javascript
|
||||
// This column uses a stateless component to produce a different colored bar depending on the value
|
||||
// You can also use stateful components or any other function that returns JSX
|
||||
const columns = [{
|
||||
Header: () => <span><i className='fa-tasks' /> Progress</span>,
|
||||
accessor: 'progress',
|
||||
Cell: row => (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: '#dadada',
|
||||
borderRadius: '2px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: `${row.value}%`,
|
||||
height: '100%',
|
||||
backgroundColor: row.value > 66 ? '#85cc00'
|
||||
: row.value > 33 ? '#ffbf00'
|
||||
: '#ff2e00',
|
||||
borderRadius: '2px',
|
||||
transition: 'all .2s ease-out'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}]
|
||||
```
|
||||
|
||||
## Styles
|
||||
- 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.
|
||||
|
||||
#### Classes
|
||||
- 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
|
||||
|
||||
#### 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!
|
||||
|
||||
#### JS Styles
|
||||
Every single react-table element and `get[ComponentName]Props` callback supports `classname` and `style` props.
|
||||
|
||||
## Custom Props
|
||||
|
||||
#### Built-in Components
|
||||
Every single built-in component's props can be dynamically extended using any one of these prop-callbacks:
|
||||
```javascript
|
||||
<ReactTable
|
||||
getProps={fn}
|
||||
getTableProps={fn}
|
||||
getTheadGroupProps={fn}
|
||||
getTheadGroupTrProps={fn}
|
||||
getTheadGroupThProps={fn}
|
||||
getTheadProps={fn}
|
||||
getTheadTrProps={fn}
|
||||
getTheadThProps={fn}
|
||||
getTbodyProps={fn}
|
||||
getTrGroupProps={fn}
|
||||
getTrProps={fn}
|
||||
getThProps={fn}
|
||||
getTdProps={fn}
|
||||
getPaginationProps={fn}
|
||||
getLoadingProps={fn}
|
||||
getNoDataProps={fn}
|
||||
getResizerProps={fn}
|
||||
/>
|
||||
```
|
||||
|
||||
These callbacks are executed with each render of the element with four parameters:
|
||||
1. Table State
|
||||
2. RowInfo (undefined if not applicable)
|
||||
3. Column (undefined if not applicable)
|
||||
4. React Table Instance
|
||||
|
||||
This makes it extremely easy to add, say... a row click callback!
|
||||
```javascript
|
||||
// When any Td element is clicked, we'll log out some information
|
||||
<ReactTable
|
||||
getTdProps={(state, rowInfo, column, instance) => {
|
||||
return {
|
||||
onClick: e => {
|
||||
console.log('A Td Element was clicked!')
|
||||
console.log('it produced this event:', e)
|
||||
console.log('It was in this column:', column)
|
||||
console.log('It was in this row:', rowInfo)
|
||||
console.log('It was in this table instance:', instance)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
You can use these callbacks for dynamic styling as well!
|
||||
```javascript
|
||||
// Any Tr element will be green if its (row.age > 20)
|
||||
<ReactTable
|
||||
getTrProps={(state, rowInfo, column) => {
|
||||
return {
|
||||
style: {
|
||||
background: rowInfo.row.age > 20 ? 'green' : 'red'
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Column Components
|
||||
Just as core components can have dynamic props, columns and column headers can too!
|
||||
|
||||
You can utilize either of these prop callbacks on columns:
|
||||
```javascript
|
||||
const columns = [{
|
||||
getHeaderProps: () => (...),
|
||||
getProps: () => (...)
|
||||
}]
|
||||
```
|
||||
|
||||
In a similar fashion these can be used to dynamically style just about anything!
|
||||
```javascript
|
||||
// This columns cells will be red if (row.name === Santa Clause)
|
||||
const columns = [{
|
||||
getProps: (state, rowInfo, column) => {
|
||||
return {
|
||||
style: {
|
||||
background: rowInfo.row.name === 'Santa Clause' ? 'red' : null
|
||||
}
|
||||
}
|
||||
}
|
||||
}]
|
||||
```
|
||||
|
||||
## Pivoting and Aggregation
|
||||
Pivoting the table will group records together based on their accessed values and allow the rows in that group to be expanded underneath it.
|
||||
To pivot, pass an array of `columnID`'s to `pivotBy`. Remember, a column's `id` is either the one that you assign it (when using a custom accessors) or its `accessor` string.
|
||||
```javascript
|
||||
<ReactTable
|
||||
...
|
||||
pivotBy={['lastName', 'age']}
|
||||
/>
|
||||
```
|
||||
|
||||
Naturally when grouping rows together, you may want to aggregate the rows inside it into the grouped column. No aggregation is done by default, however, it is very simple to aggregate any pivoted columns:
|
||||
```javascript
|
||||
// In this example, we use lodash to sum and average the values, but you can use whatever you want to aggregate.
|
||||
const columns = [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: (values, rows) => _.round(_.mean(values)),
|
||||
Aggregated: row => {
|
||||
// You can even render the cell differently if it's an aggregated cell
|
||||
return <span>row.value (avg)</span>
|
||||
}
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: (values, rows) => _.sum(values)
|
||||
}]
|
||||
```
|
||||
|
||||
Pivoted columns can be sorted just like regular columns including holding down the `<shift>` button to multi-sort.
|
||||
|
||||
## Sub Tables and Sub Components
|
||||
By adding a `SubComponent` props, you can easily add an expansion level to all root-level rows:
|
||||
```javascript
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div>
|
||||
You can put any component you want here, even another React Table! You even have access to the row-level data if you need! Spark-charts, drill-throughs, infographics... the possibilities are endless!
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
|
||||
## Server-side Data
|
||||
If you want to handle pagination, sorting, and filtering on the server, `react-table` makes it easy on you.
|
||||
|
||||
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 `onFetchData` prop. This function is called at `compomentDidMount` and any time sorting, pagination or filterting is changed in the table
|
||||
1. In the `onFetchData` callback, request your data using the provided information in the params of the function (current 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={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
|
||||
onFetchData={(state, instance) => {
|
||||
// show the loading overlay
|
||||
this.setState({loading: true})
|
||||
// fetch your data
|
||||
Axios.post('mysite.com/data', {
|
||||
page: state.page,
|
||||
pageSize: state.pageSize,
|
||||
sorted: state.sorted,
|
||||
filtered: state.filtered
|
||||
})
|
||||
.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 <a href="https://github.com/tannerlinsley/react-table/blob/master/stories/ServerSide.js" target="\_parent">async table mockup</a>
|
||||
|
||||
## Fully Controlled Component
|
||||
React Table by default works fantastically out of the box, but you can achieve even more control and customization if you choose to maintain the state yourself. It is very easy to do, even if you only want to manage *parts* of the state.
|
||||
|
||||
Here are the props and their corresponding callbacks that control the state of the a table:
|
||||
```javascript
|
||||
<ReactTable
|
||||
// Props
|
||||
page={0} // the index of the page you wish to display
|
||||
pageSize={20} // the number of rows per page to be displayed
|
||||
sorting={[{ // the sorting model for the table
|
||||
id: 'lastName',
|
||||
desc: true
|
||||
}, {
|
||||
id: 'firstName',
|
||||
desc: true
|
||||
}]}
|
||||
expandedRows={{ // The nested row indexes on the current page that should appear expanded
|
||||
1: true,
|
||||
4: true,
|
||||
5: {
|
||||
2: true,
|
||||
3: true
|
||||
}
|
||||
}}
|
||||
filters={[{ // the current filters model
|
||||
id: 'lastName',
|
||||
value: 'linsley'
|
||||
}]}
|
||||
resizing={[{ // the current resized column model
|
||||
"id": "lastName",
|
||||
"value": 446.25
|
||||
}]}
|
||||
|
||||
// Callbacks
|
||||
onPageChange={(pageIndex) => {...}} // Called when the page index is changed by the user
|
||||
onPageSizeChange={(pageSize, pageIndex) => {...}} // Called when the pageSize is changed by the user. The resolve page is also sent to maintain approximate position in the data
|
||||
onSortedChange={(newSorted, column, shiftKey) => {...}} // Called when a sortable column header is clicked with the column itself and if the shiftkey was held. If the column is a pivoted column, `column` will be an array of columns
|
||||
onExpandedChange={(newExpanded, index, event) => {...}} // Called when an expander is clicked. Use this to manage `expandedRows`
|
||||
onFilteredChange={(column, value) => {...}} // Called when a user enters a value into a filter input field or the value passed to the onFiltersChange handler by the filterRender option.
|
||||
onResizedChange={(newResized, event) => {...}} // Called when a user clicks on a resizing component (the right edge of a column header)
|
||||
/>
|
||||
```
|
||||
|
||||
## Functional Rendering
|
||||
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:
|
||||
- Fully-resolved state of the table
|
||||
- A function that returns the standard table component
|
||||
- The instance of the component
|
||||
|
||||
You can then return any JSX or react you want! This turns out to be perfect for:
|
||||
- Accessing the internal state of the table without a `ref`
|
||||
- Decorating the table or extending it with your own UI
|
||||
- Building your own custom display logic
|
||||
|
||||
Accessing internal state and wrapping with more UI:
|
||||
```javascript
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
>
|
||||
{(state, makeTable, instance) => {
|
||||
return (
|
||||
<div style={{
|
||||
background: '#ffcf00',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
padding: '5px'
|
||||
}}>
|
||||
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
|
||||
{makeTable()}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</ReactTable>
|
||||
```
|
||||
|
||||
The possibilities are endless!
|
||||
|
||||
## Sorting
|
||||
Sorting comes built in with React-Table. Click column header to sort by its column. Click it again to reverse the 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!
|
||||
|
||||
## Custom Sorting Algorithm
|
||||
To override the default sorting algorithm for the whole table use the `defaultSortMethod` prop.
|
||||
|
||||
To override the sorting algorithm for a single column, use the `sortMethod` column property.
|
||||
|
||||
Supply a function that implements the native javascript [`Array.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) interface. This is React Table's default sorting algorithm:
|
||||
- `a` the first value to compare
|
||||
- `b` the second value to compare
|
||||
- `dir` the
|
||||
```javascript
|
||||
defaultSortMethod = (a, b) => {
|
||||
// force null and undefined to the bottom
|
||||
a = (a === null || a === undefined) ? -Infinity : a
|
||||
b = (b === null || b === undefined) ? -Infinity : b
|
||||
// force any string values to lowercase
|
||||
a = a === 'string' ? a.toLowerCase() : a
|
||||
b = b === 'string' ? b.toLowerCase() : b
|
||||
// Return either 1 or -1 to indicate a sort priority
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
// returning 0 or undefined will use any subsequent column sorting methods or the row index as a tiebreaker
|
||||
return 0
|
||||
}
|
||||
```
|
||||
|
||||
## Filtering
|
||||
Filtering can be enabled by setting the `filterable` option on the table.
|
||||
|
||||
If you don't want particular column to be filtered you can set the `filterable={false}` option on the column.
|
||||
|
||||
By default the table tries to filter by checking if the row's value starts with the filter text. The default method for filtering the table can be set with the table's `defaultFilterMethod` option.
|
||||
|
||||
If you want to override a particular column's filtering method, you can set the `filterMethod` option on a column.
|
||||
|
||||
To completely override the filter that is shown, you can set the `Filter` column option. Using this option you can specify the JSX that is shown. The option is passed an `onChange` method which must be called with the the value that you wan't to pass to the `filterMethod` option whenever the filter has changed.
|
||||
|
||||
See <a href="http://react-table.js.org/?selectedKind=2.%20Demos&selectedStory=Custom%20Filtering&full=0&down=1&left=1&panelRight=0&downPanel=kadirahq%2Fstorybook-addon-actions%2Factions-panel" target="\_parent">Custom Filtering</a> demo for examples.
|
||||
|
||||
## Component Overrides
|
||||
Though we confidently stand by the markup and architecture behind it, `react-table` does offer the ability to change the core componentry it uses to render everything. You can extend or override these internal components by passing a react component to it's corresponding prop on either the global props or on a one-off basis like so:
|
||||
```javascript
|
||||
// Change the global default
|
||||
import { ReactTableDefaults } from 'react-table'
|
||||
Object.assign(ReactTableDefaults, {
|
||||
TableComponent: component,
|
||||
TheadComponent: component,
|
||||
TbodyComponent: component,
|
||||
TrGroupComponent: component,
|
||||
TrComponent: component,
|
||||
ThComponent: component
|
||||
TdComponent: component,
|
||||
TfootComponent: component,
|
||||
ExpanderComponent: component,
|
||||
AggregatedComponent: component,
|
||||
PivotValueComponent: component,
|
||||
PivotComponent: component,
|
||||
FilterComponent: component,
|
||||
PaginationComponent: component,
|
||||
PreviousComponent: undefined,
|
||||
NextComponent: undefined,
|
||||
LoadingComponent: component,
|
||||
NoDataComponent: component,
|
||||
ResizerComponent: component
|
||||
})
|
||||
|
||||
// Or change per instance
|
||||
<ReactTable
|
||||
TableComponent={Component},
|
||||
TheadComponent={Component},
|
||||
// etc...
|
||||
/>
|
||||
```
|
||||
|
||||
If you choose to change the core components React-Table uses to render, you must make sure your replacement components consume and utilize all of the supplied and inherited props that are needed for that component to function properly. We would suggest investigating <a href="https://github.com/tannerlinsley/react-table/blob/master/src/index.js" target="\_parent">the source</a> for the component you wish to replace.
|
||||
|
||||
|
||||
## Contributing
|
||||
To suggest a feature, create an issue if it does not already exist.
|
||||
|
||||
1
docs/CNAME
Normal file
1
docs/CNAME
Normal file
@ -0,0 +1 @@
|
||||
react-table.js.org
|
||||
1623
docs/README.md
1623
docs/README.md
File diff suppressed because it is too large
Load Diff
BIN
docs/favicon.ico
Normal file
BIN
docs/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
22
docs/iframe.html
Normal file
22
docs/iframe.html
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<script>
|
||||
if (window.parent !== window) {
|
||||
window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
||||
}
|
||||
</script>
|
||||
<title>React Storybook</title>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="error-display"></div>
|
||||
<script src="static/preview.b8deff9f16f0debf5b04.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
44
docs/index.html
Normal file
44
docs/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="storybook-version" content="2.35.3">
|
||||
<title>React Storybook</title>
|
||||
<style>
|
||||
/*
|
||||
When resizing panels, the drag event breaks if the cursor
|
||||
moves over the iframe. Add the 'dragging' class to the body
|
||||
at drag start and remove it when the drag ends.
|
||||
*/
|
||||
.dragging iframe {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Styling the fuzzy search box placeholders */
|
||||
.searchBox::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchBox::-moz-placeholder { /* Firefox 19+ */
|
||||
color: #ddd;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.searchBox:focus{
|
||||
border-color: #EEE !important;
|
||||
}
|
||||
|
||||
.btn:hover{
|
||||
background-color: #eee
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0;">
|
||||
<div id="root"></div>
|
||||
<script src="static/manager.4dc6928017acc8856e42.bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "new",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"react-scripts": "0.9.5",
|
||||
"standard": "^10.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@ -1,31 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tag above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start`.
|
||||
To create a production bundle, use `npm run build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
@ -1,40 +0,0 @@
|
||||
import React from 'react'
|
||||
//
|
||||
import ReactStory from '../../lib'
|
||||
|
||||
export default class App extends React.Component {
|
||||
render () {
|
||||
return (
|
||||
<ReactStory
|
||||
stories={[{
|
||||
name: 'Story 1',
|
||||
component: props => (
|
||||
<div>
|
||||
This is my first react-story!
|
||||
<br />
|
||||
<pre><code>{JSON.stringify(props, null, 2)}</code></pre>
|
||||
</div>
|
||||
)
|
||||
}, {
|
||||
name: 'Story 2',
|
||||
component: props => (
|
||||
<div>
|
||||
Hey! This is my second react-story!
|
||||
<br />
|
||||
<pre><code>{JSON.stringify(props, null, 2)}</code></pre>
|
||||
</div>
|
||||
)
|
||||
}, {
|
||||
name: 'Story 3',
|
||||
component: props => (
|
||||
<div>
|
||||
This is another one!
|
||||
<br />
|
||||
<pre><code>{JSON.stringify(props, null, 2)}</code></pre>
|
||||
</div>
|
||||
)
|
||||
}]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
//
|
||||
import './index.css'
|
||||
import App from './App.js'
|
||||
|
||||
ReactDOM.render(
|
||||
<App />,
|
||||
document.getElementById('root')
|
||||
)
|
||||
33
docs/static/manager.4dc6928017acc8856e42.bundle.js
vendored
Normal file
33
docs/static/manager.4dc6928017acc8856e42.bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
docs/static/manager.4dc6928017acc8856e42.bundle.js.map
vendored
Normal file
1
docs/static/manager.4dc6928017acc8856e42.bundle.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/manager.4dc6928017acc8856e42.bundle.js","sources":["webpack:///static/manager.4dc6928017acc8856e42.bundle.js"],"mappings":"AAAA;AAmuDA;AA64DA;AA28DA;AA00DA;AAsvEA;AAuhDA;AAsrDA;AAsiDA;AAg6DA;AA2nDA;AA++CA;AAkvDA;AAsnEA;AA2oDA;AAivCA;AA+nDA;AAkpDA;AA6lEA;AAs4DA;AAquDA;AA+pDA;AAoxDA;AAsrDA;AAkzDA;AA88GA;AAj6CA;AAopGA;AAsuFA;AA+zEA;AAtMA;AAo0EA","sourceRoot":""}
|
||||
41
docs/static/preview.b8deff9f16f0debf5b04.bundle.js
vendored
Normal file
41
docs/static/preview.b8deff9f16f0debf5b04.bundle.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
docs/static/preview.b8deff9f16f0debf5b04.bundle.js.map
vendored
Normal file
1
docs/static/preview.b8deff9f16f0debf5b04.bundle.js.map
vendored
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/preview.b8deff9f16f0debf5b04.bundle.js","sources":["webpack:///static/preview.b8deff9f16f0debf5b04.bundle.js"],"mappings":"AAAA;AAsnDA;AAg2EA;AA48EA;AA2lFA;AAmhQA;AA2/DA;AAguDA;AA8kEA;AA4xDA;AAo2DA;AAkwDA;AAunDA;AAsiDA;AA84DA;AA+nDA;AA69CA;AAunDA;AAqlEA;AA48DA;AAqgCA;AA+uDA;AAwmDA;AAiqEA;AA80DA;AAg2DA;AAwxCA;AAwqFA;AAmjGA;AA+mDA;AAk9CA;AA0xCA;AA60CA;AA8tCA;AAmmDA;AAiiBA;AAWA;AAmsEA;AAwEA;AAq9BA","sourceRoot":""}
|
||||
5608
docs/yarn.lock
5608
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@ -1,57 +1,71 @@
|
||||
{
|
||||
"name": "react-story",
|
||||
"name": "react-table",
|
||||
"version": "6.0.0",
|
||||
"description": "A story component for displaying, developing, and testing React components",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-story#readme",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tannerlinsley/react-story.git"
|
||||
"url": "git+https://github.com/tannerlinsley/react-table.git"
|
||||
},
|
||||
"keywords": [
|
||||
"react",
|
||||
"story",
|
||||
"react-story",
|
||||
"table",
|
||||
"react-table",
|
||||
"datagrid"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"files": [
|
||||
"src/",
|
||||
"lib/",
|
||||
"react-story.js",
|
||||
"react-table.js",
|
||||
"react-table.css",
|
||||
"media/*.png"
|
||||
],
|
||||
"scripts": {
|
||||
"build:node": "babel src --out-dir lib --source-maps inline --presets=es2015,stage-2,react",
|
||||
"build:node": "babel src --out-dir lib --source-maps inline",
|
||||
"build:css": "rimraf react-table.css && stylus src/index.styl --compress -o react-table.css && yarn run css:autoprefix",
|
||||
"css:autoprefix": "postcss --use autoprefixer react-table.css -r",
|
||||
"watch": "npm-run-all --parallel watch:*",
|
||||
"watch:node": "onchange 'src/**/*.js' -i -- npm run build:node",
|
||||
"watch:css": "onchange 'src/**/*.styl' -i -- npm run build:css",
|
||||
"test": "standard",
|
||||
"umd": "rimraf react-story.js && webpack --config umd.webpack.js",
|
||||
"umd": "rimraf react-table.js && webpack --config umd.webpack.js",
|
||||
"build": "npm-run-all build:*",
|
||||
"prepublish": "npm run build && npm run umd",
|
||||
"postpublish": "git push --tags"
|
||||
"postpublish": "git push --tags",
|
||||
"storybook": "start-storybook -p 8000 -c .storybook",
|
||||
"docs": "build-storybook -o docs && cp .storybook/CNAME docs/CNAME"
|
||||
},
|
||||
"dependencies": {
|
||||
"classnames": "^2.2.5",
|
||||
"glamorous": "^3.14.0",
|
||||
"react-router-dom": "next"
|
||||
"raw-loader": "^0.5.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^15.x.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kadira/storybook": "^2.35.1",
|
||||
"autoprefixer": "^6.7.0",
|
||||
"babel-cli": "6.14.0",
|
||||
"babel-eslint": "6.1.2",
|
||||
"babel-preset-es2015": "6.14.0",
|
||||
"babel-preset-react": "6.11.1",
|
||||
"babel-preset-stage-2": "6.13.0",
|
||||
"github-markdown-css": "^2.4.1",
|
||||
"html-loader": "^0.4.4",
|
||||
"markdown-loader": "^0.1.7",
|
||||
"namor": "^0.3.0",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"onchange": "^3.0.2",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"postcss-cli": "^2.6.0",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"react-json-tree": "^0.10.1",
|
||||
"rimraf": "^2.6.1",
|
||||
"standard": "^8.0.0",
|
||||
"storybook": "^0.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"webpack": "^2.4.1"
|
||||
},
|
||||
"standard": {
|
||||
@ -61,9 +75,16 @@
|
||||
"dist",
|
||||
"lib",
|
||||
"example",
|
||||
"react-story.js",
|
||||
"react-table.js",
|
||||
"stories",
|
||||
"docs"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"es2015",
|
||||
"stage-2",
|
||||
"react"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
1
react-story.js
vendored
1
react-story.js
vendored
@ -1 +0,0 @@
|
||||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.ReactStory=t(require("react")):e.ReactStory=t(e.React)}(this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=1)}([function(t,n){t.exports=e},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var c=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),l=n(0),i=function(e){return e&&e.__esModule?e:{default:e}}(l),a=function(e){function t(){r(this,t);var e=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return e.state={storyProps:{},currentStoryID:null},e}return u(t,e),c(t,[{key:"componentWillRecieveProps",value:function(e){}},{key:"render",value:function(){var e=this.state,t=e.stories,n=e.storyPropsByID,r=e.storyComponentsByID,o=e.currentStoryID,u=r[o],c=n[o];return i.default.createElement("div",null,i.default.createElement("div",null,i.default.createElement("ul",null,t.map(function(e,t){return i.default.createElement("li",{key:t},i.default.createElement(Link,null,e.name))}))),i.default.createElement("div",null,i.default.createElement(u,c)))}}]),t}(i.default.Component);t.default=a}])});
|
||||
Binary file not shown.
235
src/defaultProps.js
Normal file
235
src/defaultProps.js
Normal file
@ -0,0 +1,235 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
import _ from './utils'
|
||||
import Pagination from './pagination'
|
||||
|
||||
const emptyObj = () => ({})
|
||||
|
||||
export default {
|
||||
// General
|
||||
data: [],
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
pageSizeOptions: [5, 10, 20, 25, 50, 100],
|
||||
defaultPageSize: 20,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
sortable: true,
|
||||
resizable: true,
|
||||
filterable: false,
|
||||
defaultSorted: [],
|
||||
defaultFiltered: [],
|
||||
defaultResized: [],
|
||||
defaultExpanded: {},
|
||||
defaultFilterMethod: (filter, row, column) => {
|
||||
const id = filter.pivotId || filter.id
|
||||
return row[id] !== undefined ? String(row[id]).startsWith(filter.value) : true
|
||||
},
|
||||
defaultSortMethod: (a, b) => {
|
||||
// force null and undefined to the bottom
|
||||
a = (a === null || a === undefined) ? -Infinity : a
|
||||
b = (b === null || b === undefined) ? -Infinity : b
|
||||
// force any string values to lowercase
|
||||
a = a === 'string' ? a.toLowerCase() : a
|
||||
b = b === 'string' ? b.toLowerCase() : b
|
||||
// Return either 1 or -1 to indicate a sort priority
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
// returning 0, undefined or any falsey value will use subsequent sorts or the index as a tiebreaker
|
||||
return 0
|
||||
},
|
||||
|
||||
// Controlled State Props
|
||||
// page: undefined,
|
||||
// pageSize: undefined,
|
||||
// sorted: [],
|
||||
// filtered: [],
|
||||
// resized: [],
|
||||
// expanded: {},
|
||||
|
||||
// Controlled State Callbacks
|
||||
onPageChange: undefined,
|
||||
onPageSizeChange: undefined,
|
||||
onSortedChange: undefined,
|
||||
onFilteredChange: undefined,
|
||||
onResizedChange: undefined,
|
||||
onExpandedChange: undefined,
|
||||
|
||||
// Pivoting
|
||||
pivotBy: undefined,
|
||||
|
||||
// Key Constants
|
||||
pivotValKey: '_pivotVal',
|
||||
pivotIDKey: '_pivotID',
|
||||
subRowsKey: '_subRows',
|
||||
aggregatedKey: '_aggregated',
|
||||
nestingLevelKey: '_nestingLevel',
|
||||
originalKey: '_original',
|
||||
indexKey: '_index',
|
||||
groupedByPivotKey: '_groupedByPivot',
|
||||
|
||||
// Server-side Callbacks
|
||||
onFetchData: () => null,
|
||||
|
||||
// Classes
|
||||
className: '',
|
||||
style: {},
|
||||
|
||||
// Component decorators
|
||||
getProps: emptyObj,
|
||||
getTableProps: emptyObj,
|
||||
getTheadGroupProps: emptyObj,
|
||||
getTheadGroupTrProps: emptyObj,
|
||||
getTheadGroupThProps: emptyObj,
|
||||
getTheadProps: emptyObj,
|
||||
getTheadTrProps: emptyObj,
|
||||
getTheadThProps: emptyObj,
|
||||
getTheadFilterProps: emptyObj,
|
||||
getTheadFilterTrProps: emptyObj,
|
||||
getTheadFilterThProps: emptyObj,
|
||||
getTbodyProps: emptyObj,
|
||||
getTrGroupProps: emptyObj,
|
||||
getTrProps: emptyObj,
|
||||
getTdProps: emptyObj,
|
||||
getTfootProps: emptyObj,
|
||||
getTfootTrProps: emptyObj,
|
||||
getTfootTdProps: emptyObj,
|
||||
getPaginationProps: emptyObj,
|
||||
getLoadingProps: emptyObj,
|
||||
getNoDataProps: emptyObj,
|
||||
getResizerProps: emptyObj,
|
||||
|
||||
// Global Column Defaults
|
||||
column: {
|
||||
// Renderers
|
||||
Cell: undefined,
|
||||
Header: undefined,
|
||||
Footer: undefined,
|
||||
Aggregated: undefined,
|
||||
Pivot: undefined,
|
||||
PivotValue: undefined,
|
||||
Expander: undefined,
|
||||
Filter: undefined,
|
||||
// All Columns
|
||||
sortable: undefined, // use table default
|
||||
resizable: undefined, // use table default
|
||||
filterable: undefined, // use table default
|
||||
show: true,
|
||||
minWidth: 100,
|
||||
// Cells only
|
||||
className: '',
|
||||
style: {},
|
||||
getProps: emptyObj,
|
||||
// Pivot only
|
||||
aggregate: undefined,
|
||||
// Headers only
|
||||
headerClassName: '',
|
||||
headerStyle: {},
|
||||
getHeaderProps: emptyObj,
|
||||
// Footers only
|
||||
footerClassName: '',
|
||||
footerStyle: {},
|
||||
getFooterProps: emptyObj,
|
||||
filterMethod: undefined,
|
||||
sortMethod: undefined
|
||||
},
|
||||
|
||||
// Global Expander Column Defaults
|
||||
expanderDefaults: {
|
||||
sortable: false,
|
||||
resizable: false,
|
||||
filterable: false,
|
||||
width: 35
|
||||
},
|
||||
|
||||
pivotDefaults: {
|
||||
// extend the defaults for pivoted columns here
|
||||
},
|
||||
|
||||
// Text
|
||||
previousText: 'Previous',
|
||||
nextText: 'Next',
|
||||
loadingText: 'Loading...',
|
||||
noDataText: 'No rows found',
|
||||
pageText: 'Page',
|
||||
ofText: 'of',
|
||||
rowsText: 'rows',
|
||||
|
||||
// Components
|
||||
TableComponent: _.makeTemplateComponent('rt-table'),
|
||||
TheadComponent: _.makeTemplateComponent('rt-thead'),
|
||||
TbodyComponent: _.makeTemplateComponent('rt-tbody'),
|
||||
TrGroupComponent: _.makeTemplateComponent('rt-tr-group'),
|
||||
TrComponent: _.makeTemplateComponent('rt-tr'),
|
||||
ThComponent: ({toggleSort, className, children, ...rest}) => {
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, 'rt-th')}
|
||||
onClick={e => {
|
||||
toggleSort && toggleSort(e)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
TdComponent: _.makeTemplateComponent('rt-td'),
|
||||
TfootComponent: _.makeTemplateComponent('rt-tfoot'),
|
||||
FilterComponent: ({filter, onChange}) => (
|
||||
<input type='text'
|
||||
style={{
|
||||
width: '100%'
|
||||
}}
|
||||
value={filter ? filter.value : ''}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
/>
|
||||
),
|
||||
ExpanderComponent: ({isExpanded}) => (
|
||||
<div className={classnames('rt-expander', isExpanded && '-open')}>
|
||||
•
|
||||
</div>
|
||||
),
|
||||
PivotValueComponent: ({subRows, value}) => (
|
||||
<span>{value} {subRows && `(${subRows.length})`}</span>
|
||||
),
|
||||
AggregatedComponent: ({subRows, column}) => {
|
||||
const previewValues = subRows
|
||||
.filter(d => typeof d[column.id] !== 'undefined')
|
||||
.map((row, i) => (
|
||||
<span key={i}>{row[column.id]}{i < subRows.length - 1 ? ', ' : ''}</span>
|
||||
))
|
||||
return (
|
||||
<span>{previewValues}</span>
|
||||
)
|
||||
},
|
||||
PivotComponent: undefined, // this is a computed default generated using
|
||||
// the ExpanderComponent and PivotValueComponent at run-time in methods.js
|
||||
PaginationComponent: Pagination,
|
||||
PreviousComponent: undefined,
|
||||
NextComponent: undefined,
|
||||
LoadingComponent: ({className, loading, loadingText, ...rest}) => (
|
||||
<div className={classnames(
|
||||
'-loading',
|
||||
{'-active': loading},
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
<div className='-loading-inner'>
|
||||
{loadingText}
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
NoDataComponent: _.makeTemplateComponent('rt-noData'),
|
||||
ResizerComponent: _.makeTemplateComponent('rt-resizer')
|
||||
}
|
||||
909
src/index.js
909
src/index.js
@ -1,104 +1,841 @@
|
||||
import React from 'react'
|
||||
import {
|
||||
BrowserRouter as Router,
|
||||
Switch,
|
||||
Route,
|
||||
Link,
|
||||
Redirect
|
||||
} from 'react-router-dom'
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
import Utils from './utils'
|
||||
import _ from './utils'
|
||||
import Lifecycle from './lifecycle'
|
||||
import Methods from './methods'
|
||||
import defaultProps from './defaultProps'
|
||||
|
||||
let uid = 0
|
||||
export const ReactTableDefaults = defaultProps
|
||||
|
||||
const defaultProps = {
|
||||
stories: [],
|
||||
defaultComponent: () => <span>No story component found!</span>
|
||||
}
|
||||
|
||||
export default class ReactStory extends React.Component {
|
||||
export default class ReactTable extends Methods(Lifecycle(Component)) {
|
||||
static defaultProps = defaultProps
|
||||
constructor () {
|
||||
|
||||
constructor (props) {
|
||||
super()
|
||||
|
||||
this.getResolvedState = this.getResolvedState.bind(this)
|
||||
this.getDataModel = this.getDataModel.bind(this)
|
||||
this.getSortedData = this.getSortedData.bind(this)
|
||||
this.fireFetchData = this.fireFetchData.bind(this)
|
||||
this.getPropOrState = this.getPropOrState.bind(this)
|
||||
this.getStateOrProp = this.getStateOrProp.bind(this)
|
||||
this.filterData = this.filterData.bind(this)
|
||||
this.sortData = this.sortData.bind(this)
|
||||
this.getMinRows = this.getMinRows.bind(this)
|
||||
this.onPageChange = this.onPageChange.bind(this)
|
||||
this.onPageSizeChange = this.onPageSizeChange.bind(this)
|
||||
this.sortColumn = this.sortColumn.bind(this)
|
||||
this.filterColumn = this.filterColumn.bind(this)
|
||||
this.resizeColumnStart = this.resizeColumnStart.bind(this)
|
||||
this.resizeColumnEnd = this.resizeColumnEnd.bind(this)
|
||||
this.resizeColumnMoving = this.resizeColumnMoving.bind(this)
|
||||
|
||||
this.state = {
|
||||
stories: []
|
||||
}
|
||||
this.rebuild = this.rebuild.bind(this)
|
||||
}
|
||||
componentWillMount () {
|
||||
this.rebuild()
|
||||
}
|
||||
componentWillReceiveProps (newProps) {
|
||||
const oldProps = this.props
|
||||
|
||||
if (oldProps.stories !== newProps.stories) {
|
||||
this.rebuild()
|
||||
page: 0,
|
||||
pageSize: props.defaultPageSize,
|
||||
sorted: props.defaultSorted,
|
||||
expanded: props.defaultExpanded,
|
||||
filtered: props.defaultFiltered,
|
||||
resized: props.defaultResized,
|
||||
currentlyResizing: false,
|
||||
skipNextSort: false
|
||||
}
|
||||
}
|
||||
rebuild (props = this.props) {
|
||||
const {
|
||||
defaultComponent
|
||||
} = this.props
|
||||
|
||||
const stories = props.stories.map(story => {
|
||||
const name = story.name || `Story ${uid++}`
|
||||
const path = story.path || Utils.makePath(name)
|
||||
const component = story.component || defaultComponent
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
component
|
||||
}
|
||||
})
|
||||
this.setState({
|
||||
stories
|
||||
})
|
||||
}
|
||||
render () {
|
||||
const resolvedState = this.getResolvedState()
|
||||
const {
|
||||
stories
|
||||
} = this.state
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
getProps,
|
||||
getTableProps,
|
||||
getTheadGroupProps,
|
||||
getTheadGroupTrProps,
|
||||
getTheadGroupThProps,
|
||||
getTheadProps,
|
||||
getTheadTrProps,
|
||||
getTheadThProps,
|
||||
getTheadFilterProps,
|
||||
getTheadFilterTrProps,
|
||||
getTheadFilterThProps,
|
||||
getTbodyProps,
|
||||
getTrGroupProps,
|
||||
getTrProps,
|
||||
getTdProps,
|
||||
getTfootProps,
|
||||
getTfootTrProps,
|
||||
getTfootTdProps,
|
||||
getPaginationProps,
|
||||
getLoadingProps,
|
||||
getNoDataProps,
|
||||
getResizerProps,
|
||||
showPagination,
|
||||
manual,
|
||||
loadingText,
|
||||
noDataText,
|
||||
sortable,
|
||||
resizable,
|
||||
filterable,
|
||||
// Pivoting State
|
||||
pivotIDKey,
|
||||
pivotValKey,
|
||||
pivotBy,
|
||||
subRowsKey,
|
||||
aggregatedKey,
|
||||
originalKey,
|
||||
indexKey,
|
||||
groupedByPivotKey,
|
||||
// State
|
||||
loading,
|
||||
pageSize,
|
||||
page,
|
||||
sorted,
|
||||
filtered,
|
||||
resized,
|
||||
expanded,
|
||||
pages,
|
||||
onExpandedChange,
|
||||
// Components
|
||||
TableComponent,
|
||||
TheadComponent,
|
||||
TbodyComponent,
|
||||
TrGroupComponent,
|
||||
TrComponent,
|
||||
ThComponent,
|
||||
TdComponent,
|
||||
TfootComponent,
|
||||
PaginationComponent,
|
||||
LoadingComponent,
|
||||
SubComponent,
|
||||
NoDataComponent,
|
||||
ResizerComponent,
|
||||
ExpanderComponent,
|
||||
PivotValueComponent,
|
||||
PivotComponent,
|
||||
AggregatedComponent,
|
||||
FilterComponent,
|
||||
// Data model
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
headerGroups,
|
||||
hasHeaderGroups,
|
||||
// Sorted Data
|
||||
sortedData,
|
||||
currentlyResizing
|
||||
} = resolvedState
|
||||
|
||||
console.log(stories)
|
||||
// Pagination
|
||||
const startRow = pageSize * page
|
||||
const endRow = startRow + pageSize
|
||||
let pageRows = manual ? resolvedData : sortedData.slice(startRow, endRow)
|
||||
const minRows = this.getMinRows()
|
||||
const padRows = _.range(Math.max(minRows - pageRows.length, 0))
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<div>
|
||||
<div>
|
||||
<ul>
|
||||
{stories.map(story => (
|
||||
<li
|
||||
key={story.path}
|
||||
>
|
||||
<Link to={story.path}>
|
||||
{story.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
const hasColumnFooter = allVisibleColumns.some(d => d.Footer)
|
||||
const hasFilters = filterable || allVisibleColumns.some(d => d.filterable)
|
||||
|
||||
const recurseRowsViewIndex = (rows, path = [], index = -1) => {
|
||||
return [
|
||||
rows.map((row, i) => {
|
||||
index++
|
||||
const rowWithViewIndex = {
|
||||
...row,
|
||||
_viewIndex: index
|
||||
}
|
||||
const newPath = path.concat([i])
|
||||
if (rowWithViewIndex[subRowsKey] && _.get(expanded, newPath)) {
|
||||
[rowWithViewIndex[subRowsKey], index] = recurseRowsViewIndex(rowWithViewIndex[subRowsKey], newPath, index)
|
||||
}
|
||||
return rowWithViewIndex
|
||||
}),
|
||||
index
|
||||
]
|
||||
}
|
||||
|
||||
[pageRows] = recurseRowsViewIndex(pageRows)
|
||||
|
||||
const canPrevious = page > 0
|
||||
const canNext = page + 1 < pages
|
||||
|
||||
const rowMinWidth = _.sum(allVisibleColumns.map(d => {
|
||||
const resizedColumn = resized.find(x => x.id === d.id) || {}
|
||||
return _.getFirstDefined(resizedColumn.value, d.width, d.minWidth)
|
||||
}))
|
||||
|
||||
let rowIndex = -1
|
||||
|
||||
const finalState = {
|
||||
...resolvedState,
|
||||
startRow,
|
||||
endRow,
|
||||
pageRows,
|
||||
minRows,
|
||||
padRows,
|
||||
hasColumnFooter,
|
||||
canPrevious,
|
||||
canNext,
|
||||
rowMinWidth
|
||||
}
|
||||
|
||||
// Visual Components
|
||||
|
||||
const makeHeaderGroups = () => {
|
||||
const theadGroupProps = _.splitProps(getTheadGroupProps(finalState, undefined, undefined, this))
|
||||
const theadGroupTrProps = _.splitProps(getTheadGroupTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames('-headerGroups', theadGroupProps.className)}
|
||||
style={{
|
||||
...theadGroupProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...theadGroupProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={theadGroupTrProps.className}
|
||||
style={theadGroupTrProps.style}
|
||||
{...theadGroupTrProps.rest}
|
||||
>
|
||||
{headerGroups.map(makeHeaderGroup)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeHeaderGroup = (column, i) => {
|
||||
const resizedValue = col => (resized.find(x => x.id === col.id) || {}).value
|
||||
const flex = _.sum(column.columns.map(col => col.width || resizedValue(col) ? 0 : col.minWidth))
|
||||
const width = _.sum(column.columns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.minWidth)))
|
||||
const maxWidth = _.sum(column.columns.map(col => _.getFirstDefined(resizedValue(col), col.width, col.maxWidth)))
|
||||
|
||||
const theadGroupThProps = _.splitProps(getTheadGroupThProps(finalState, undefined, column, this))
|
||||
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
column.headerClassName,
|
||||
theadGroupThProps.className,
|
||||
columnHeaderProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...column.headerStyle,
|
||||
...theadGroupThProps.style,
|
||||
...columnHeaderProps.style
|
||||
}
|
||||
|
||||
const rest = {
|
||||
...theadGroupThProps.rest,
|
||||
...columnHeaderProps.rest
|
||||
}
|
||||
|
||||
const flexStyles = {
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}
|
||||
|
||||
return (
|
||||
<ThComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
...flexStyles
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{_.normalizeComponent(column.Header, {
|
||||
data: sortedData,
|
||||
column: column
|
||||
})}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeHeaders = () => {
|
||||
const theadProps = _.splitProps(getTheadProps(finalState, undefined, undefined, this))
|
||||
const theadTrProps = _.splitProps(getTheadTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames('-header', theadProps.className)}
|
||||
style={{
|
||||
...theadProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...theadProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={theadTrProps.className}
|
||||
style={theadTrProps.style}
|
||||
{...theadTrProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map(makeHeader)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeHeader = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const sort = sorted.find(d => d.id === column.id)
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const theadThProps = _.splitProps(getTheadThProps(finalState, undefined, column, this))
|
||||
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
column.headerClassName,
|
||||
theadThProps.className,
|
||||
columnHeaderProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...column.headerStyle,
|
||||
...theadThProps.style,
|
||||
...columnHeaderProps.style
|
||||
}
|
||||
|
||||
const rest = {
|
||||
...theadThProps.rest,
|
||||
...columnHeaderProps.rest
|
||||
}
|
||||
|
||||
const isResizable = _.getFirstDefined(column.resizable, resizable, false)
|
||||
const resizer = isResizable ? (
|
||||
<ResizerComponent
|
||||
onMouseDown={e => this.resizeColumnStart(column, e, false)}
|
||||
onTouchStart={e => this.resizeColumnStart(column, e, true)}
|
||||
|
||||
{...resizerProps}
|
||||
/>
|
||||
) : null
|
||||
|
||||
const isSortable = _.getFirstDefined(column.sortable, sortable, false)
|
||||
|
||||
return (
|
||||
<ThComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
'rt-resizable-header',
|
||||
sort ? (sort.desc ? '-sort-desc' : '-sort-asc') : '',
|
||||
isSortable && '-cursor-pointer',
|
||||
!show && '-hidden',
|
||||
pivotBy && pivotBy.slice(0, -1).includes(column.id) && 'rt-header-pivot'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
toggleSort={(e) => {
|
||||
isSortable && this.sortColumn(column, e.shiftKey)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<div className='rt-resizable-header-content'>
|
||||
{_.normalizeComponent(column.Header, {
|
||||
data: sortedData,
|
||||
column: column
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<Switch>
|
||||
{stories.map(story => (
|
||||
<Route
|
||||
key={story.path}
|
||||
exact
|
||||
path={'/' + story.path}
|
||||
render={props => (
|
||||
<story.component
|
||||
story={story}
|
||||
{...props}
|
||||
/>
|
||||
{resizer}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeFilters = () => {
|
||||
const theadFilterProps = _.splitProps(getTheadFilterProps(finalState, undefined, undefined, this))
|
||||
const theadFilterTrProps = _.splitProps(getTheadFilterTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TheadComponent
|
||||
className={classnames('-filters', theadFilterProps.className)}
|
||||
style={{
|
||||
...theadFilterProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...theadFilterProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={theadFilterTrProps.className}
|
||||
style={theadFilterTrProps.style}
|
||||
{...theadFilterTrProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map(makeFilter)}
|
||||
</TrComponent>
|
||||
</TheadComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeFilter = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const theadFilterThProps = _.splitProps(getTheadFilterThProps(finalState, undefined, column, this))
|
||||
const columnHeaderProps = _.splitProps(column.getHeaderProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
column.headerClassName,
|
||||
theadFilterThProps.className,
|
||||
columnHeaderProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...column.headerStyle,
|
||||
...theadFilterThProps.style,
|
||||
...columnHeaderProps.style
|
||||
}
|
||||
|
||||
const rest = {
|
||||
...theadFilterThProps.rest,
|
||||
...columnHeaderProps.rest
|
||||
}
|
||||
|
||||
const filter = filtered.find(filter => filter.id === column.id)
|
||||
|
||||
const ResolvedFilterComponent = column.Filter || FilterComponent
|
||||
|
||||
const isFilterable = _.getFirstDefined(column.filterable, filterable, false)
|
||||
|
||||
return (
|
||||
<ThComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{isFilterable ? (
|
||||
_.normalizeComponent(ResolvedFilterComponent,
|
||||
{
|
||||
column,
|
||||
filter,
|
||||
onChange: (value) => (this.filterColumn(column, value))
|
||||
},
|
||||
defaultProps.column.Filter
|
||||
)
|
||||
) : null}
|
||||
</ThComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePageRow = (row, i, path = []) => {
|
||||
const rowInfo = {
|
||||
original: row[originalKey],
|
||||
row: row,
|
||||
index: row[indexKey],
|
||||
viewIndex: ++rowIndex,
|
||||
level: path.length,
|
||||
nestingPath: path.concat([i]),
|
||||
aggregated: row[aggregatedKey],
|
||||
groupedByPivot: row[groupedByPivotKey],
|
||||
subRows: row[subRowsKey]
|
||||
}
|
||||
const isExpanded = _.get(expanded, rowInfo.nestingPath)
|
||||
const trGroupProps = getTrGroupProps(finalState, rowInfo, undefined, this)
|
||||
const trProps = _.splitProps(getTrProps(finalState, rowInfo, undefined, this))
|
||||
return (
|
||||
<TrGroupComponent
|
||||
key={rowInfo.nestingPath.join('_')}
|
||||
{...trGroupProps}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(
|
||||
trProps.className,
|
||||
row._viewIndex % 2 ? '-even' : '-odd'
|
||||
)}
|
||||
style={trProps.style}
|
||||
{...trProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map((column, i2) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const tdProps = _.splitProps(getTdProps(finalState, rowInfo, column, this))
|
||||
const columnProps = _.splitProps(column.getProps(finalState, rowInfo, column, this))
|
||||
|
||||
const classes = [
|
||||
tdProps.className,
|
||||
column.className,
|
||||
columnProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...tdProps.style,
|
||||
...column.style,
|
||||
...columnProps.style
|
||||
}
|
||||
|
||||
const cellInfo = {
|
||||
...rowInfo,
|
||||
isExpanded,
|
||||
column: {...column},
|
||||
value: rowInfo.row[column.id],
|
||||
pivoted: column.pivoted,
|
||||
expander: column.expander,
|
||||
resized,
|
||||
show,
|
||||
width,
|
||||
maxWidth,
|
||||
tdProps,
|
||||
columnProps,
|
||||
classes,
|
||||
styles
|
||||
}
|
||||
|
||||
const value = cellInfo.value
|
||||
|
||||
let interactionProps
|
||||
let isBranch
|
||||
let isPreview
|
||||
|
||||
const onExpanderClick = (e) => {
|
||||
let newExpanded = _.clone(expanded)
|
||||
if (isExpanded) {
|
||||
newExpanded = _.set(newExpanded, cellInfo.nestingPath, false)
|
||||
} else {
|
||||
newExpanded = _.set(newExpanded, cellInfo.nestingPath, {})
|
||||
}
|
||||
if (onExpandedChange) {
|
||||
onExpandedChange(newExpanded, cellInfo.nestingPath, e)
|
||||
}
|
||||
// If expanded is being controlled, don't manage internal state
|
||||
if (this.props.expanded) {
|
||||
return
|
||||
}
|
||||
return this.setStateWithData({
|
||||
expanded: newExpanded
|
||||
})
|
||||
}
|
||||
|
||||
// Default to a standard cell
|
||||
let resolvedCell = _.normalizeComponent(column.Cell, cellInfo, value)
|
||||
|
||||
// Resolve Renderers
|
||||
const ResolvedAggregatedComponent = column.Aggregated || (!column.aggregate ? AggregatedComponent : column.Cell)
|
||||
const ResolvedExpanderComponent = column.Expander || ExpanderComponent
|
||||
const ResolvedPivotValueComponent = column.PivotValue || PivotValueComponent
|
||||
const DefaultResolvedPivotComponent = PivotComponent || (
|
||||
props => (
|
||||
<div>
|
||||
<ResolvedExpanderComponent {...props} />
|
||||
<ResolvedPivotValueComponent {...props} />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
const ResolvedPivotComponent = column.Pivot || DefaultResolvedPivotComponent
|
||||
|
||||
// Is this cell expandable?
|
||||
if (cellInfo.pivoted || cellInfo.expander) {
|
||||
// Make it expandable by defualt
|
||||
cellInfo.expandable = true
|
||||
interactionProps = {
|
||||
onClick: onExpanderClick
|
||||
}
|
||||
// If pivoted, has no subRows, and does not have a subComponent, do not make expandable
|
||||
if (cellInfo.pivoted) {
|
||||
if (!cellInfo.subRows) {
|
||||
if (!SubComponent) {
|
||||
cellInfo.expandable = false
|
||||
interactionProps = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cellInfo.pivoted) {
|
||||
// Is this column a branch?
|
||||
isBranch = rowInfo.row[pivotIDKey] === column.id &&
|
||||
cellInfo.subRows
|
||||
// Should this column be blank?
|
||||
isPreview = pivotBy.indexOf(column.id) > pivotBy.indexOf(rowInfo.row[pivotIDKey]) &&
|
||||
cellInfo.subRows
|
||||
// Pivot Cell Render Override
|
||||
if (isBranch) {
|
||||
// isPivot
|
||||
resolvedCell = _.normalizeComponent(ResolvedPivotComponent, {
|
||||
...cellInfo,
|
||||
value: row[pivotValKey]
|
||||
}, row[pivotValKey])
|
||||
} else if (isPreview) {
|
||||
// Show the pivot preview
|
||||
resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value)
|
||||
} else {
|
||||
resolvedCell = null
|
||||
}
|
||||
} else if (cellInfo.aggregated) {
|
||||
resolvedCell = _.normalizeComponent(ResolvedAggregatedComponent, cellInfo, value)
|
||||
}
|
||||
|
||||
if (cellInfo.expander) {
|
||||
resolvedCell = _.normalizeComponent(ResolvedExpanderComponent, cellInfo, row[pivotValKey])
|
||||
if (pivotBy) {
|
||||
if (cellInfo.groupedByPivot) {
|
||||
resolvedCell = null
|
||||
}
|
||||
if (!cellInfo.subRows && !SubComponent) {
|
||||
resolvedCell = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the cell
|
||||
return (
|
||||
<TdComponent
|
||||
key={i2 + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
!show && 'hidden',
|
||||
cellInfo.expandable && 'rt-expandable',
|
||||
(isBranch || isPreview) && 'rt-pivot'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
<Redirect
|
||||
to={stories[0].path}
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</Router>
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...tdProps.rest}
|
||||
{...interactionProps}
|
||||
>
|
||||
{resolvedCell}
|
||||
</TdComponent>
|
||||
)
|
||||
})}
|
||||
</TrComponent>
|
||||
{(
|
||||
rowInfo.subRows &&
|
||||
isExpanded &&
|
||||
rowInfo.subRows.map((d, i) => makePageRow(d, i, rowInfo.nestingPath))
|
||||
)}
|
||||
{SubComponent && !rowInfo.subRows && isExpanded && SubComponent(rowInfo)}
|
||||
</TrGroupComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePadRow = (row, i) => {
|
||||
const trGroupProps = getTrGroupProps(finalState, undefined, undefined, this)
|
||||
const trProps = _.splitProps(getTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TrGroupComponent
|
||||
key={i}
|
||||
{...trGroupProps}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(
|
||||
'-padRow',
|
||||
(pageRows.length + i) % 2 ? '-even' : '-odd',
|
||||
trProps.className,
|
||||
)}
|
||||
style={trProps.style || {}}
|
||||
>
|
||||
{allVisibleColumns.map(makePadColumn)}
|
||||
</TrComponent>
|
||||
</TrGroupComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makePadColumn = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
let width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
let flex = width
|
||||
let maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const tdProps = _.splitProps(getTdProps(finalState, undefined, column, this))
|
||||
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
tdProps.className,
|
||||
column.className,
|
||||
columnProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...tdProps.style,
|
||||
...column.style,
|
||||
...columnProps.style
|
||||
}
|
||||
|
||||
return (
|
||||
<TdComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
!show && 'hidden'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...tdProps.rest}
|
||||
>
|
||||
|
||||
</TdComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeColumnFooters = () => {
|
||||
const tFootProps = getTfootProps(finalState, undefined, undefined, this)
|
||||
const tFootTrProps = _.splitProps(getTfootTrProps(finalState, undefined, undefined, this))
|
||||
return (
|
||||
<TfootComponent
|
||||
className={tFootProps.className}
|
||||
style={{
|
||||
...tFootProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...tFootProps.rest}
|
||||
>
|
||||
<TrComponent
|
||||
className={classnames(
|
||||
tFootTrProps.className
|
||||
)}
|
||||
style={tFootTrProps.style}
|
||||
{...tFootTrProps.rest}
|
||||
>
|
||||
{allVisibleColumns.map(makeColumnFooter)}
|
||||
</TrComponent>
|
||||
</TfootComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const makeColumnFooter = (column, i) => {
|
||||
const resizedCol = resized.find(x => x.id === column.id) || {}
|
||||
const show = typeof column.show === 'function' ? column.show() : column.show
|
||||
const width = _.getFirstDefined(resizedCol.value, column.width, column.minWidth)
|
||||
const maxWidth = _.getFirstDefined(resizedCol.value, column.width, column.maxWidth)
|
||||
const tFootTdProps = _.splitProps(getTfootTdProps(finalState, undefined, undefined, this))
|
||||
const columnProps = _.splitProps(column.getProps(finalState, undefined, column, this))
|
||||
const columnFooterProps = _.splitProps(column.getFooterProps(finalState, undefined, column, this))
|
||||
|
||||
const classes = [
|
||||
tFootTdProps.className,
|
||||
column.className,
|
||||
columnProps.className,
|
||||
columnFooterProps.className
|
||||
]
|
||||
|
||||
const styles = {
|
||||
...tFootTdProps.style,
|
||||
...column.style,
|
||||
...columnProps.style,
|
||||
...columnFooterProps.style
|
||||
}
|
||||
|
||||
return (
|
||||
<TdComponent
|
||||
key={i + '-' + column.id}
|
||||
className={classnames(
|
||||
classes,
|
||||
!show && 'hidden'
|
||||
)}
|
||||
style={{
|
||||
...styles,
|
||||
flex: `${width} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}}
|
||||
{...columnProps.rest}
|
||||
{...tFootTdProps.rest}
|
||||
{...columnFooterProps.rest}
|
||||
>
|
||||
{_.normalizeComponent(column.Footer, {
|
||||
data: sortedData,
|
||||
column: column
|
||||
})}
|
||||
</TdComponent>
|
||||
)
|
||||
}
|
||||
|
||||
const rootProps = _.splitProps(getProps(finalState, undefined, undefined, this))
|
||||
const tableProps = _.splitProps(getTableProps(finalState, undefined, undefined, this))
|
||||
const tBodyProps = _.splitProps(getTbodyProps(finalState, undefined, undefined, this))
|
||||
const paginationProps = _.splitProps(getPaginationProps(finalState, undefined, undefined, this))
|
||||
const loadingProps = getLoadingProps(finalState, undefined, undefined, this)
|
||||
const noDataProps = getNoDataProps(finalState, undefined, undefined, this)
|
||||
const resizerProps = getResizerProps(finalState, undefined, undefined, this)
|
||||
|
||||
const makeTable = () => (
|
||||
<div
|
||||
className={classnames(
|
||||
'ReactTable',
|
||||
className,
|
||||
rootProps.className
|
||||
)}
|
||||
style={{
|
||||
...style,
|
||||
...rootProps.style
|
||||
}}
|
||||
{...rootProps.rest}
|
||||
>
|
||||
<TableComponent
|
||||
className={classnames(
|
||||
tableProps.className,
|
||||
currentlyResizing ? 'rt-resizing' : ''
|
||||
)}
|
||||
style={tableProps.style}
|
||||
{...tableProps.rest}
|
||||
>
|
||||
{hasHeaderGroups ? makeHeaderGroups() : null}
|
||||
{makeHeaders()}
|
||||
{hasFilters ? makeFilters() : null}
|
||||
<TbodyComponent
|
||||
className={classnames(tBodyProps.className)}
|
||||
style={{
|
||||
...tBodyProps.style,
|
||||
minWidth: `${rowMinWidth}px`
|
||||
}}
|
||||
{...tBodyProps.rest}
|
||||
>
|
||||
{pageRows.map((d, i) => makePageRow(d, i))}
|
||||
{padRows.map(makePadRow)}
|
||||
</TbodyComponent>
|
||||
{hasColumnFooter ? makeColumnFooters() : null}
|
||||
</TableComponent>
|
||||
{showPagination ? (
|
||||
<PaginationComponent
|
||||
{...resolvedState}
|
||||
pages={pages}
|
||||
canPrevious={canPrevious}
|
||||
canNext={canNext}
|
||||
onPageChange={this.onPageChange}
|
||||
onPageSizeChange={this.onPageSizeChange}
|
||||
className={paginationProps.className}
|
||||
style={paginationProps.style}
|
||||
{...paginationProps.rest}
|
||||
/>
|
||||
) : null}
|
||||
{!pageRows.length && (
|
||||
<NoDataComponent
|
||||
{...noDataProps}
|
||||
>
|
||||
{_.normalizeComponent(noDataText)}
|
||||
</NoDataComponent>
|
||||
)}
|
||||
<LoadingComponent
|
||||
loading={loading}
|
||||
loadingText={loadingText}
|
||||
{...loadingProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
// childProps are optionally passed to a function-as-a-child
|
||||
return children ? children(finalState, makeTable, this) : makeTable()
|
||||
}
|
||||
}
|
||||
|
||||
321
src/index.styl
Normal file
321
src/index.styl
Normal file
@ -0,0 +1,321 @@
|
||||
$easeOutQuad = cubic-bezier(0.250, 0.460, 0.450, 0.940)
|
||||
$easeOutBack = cubic-bezier(0.175, 0.885, 0.320, 1.275)
|
||||
$expandSize = 7px
|
||||
|
||||
.ReactTable
|
||||
position:relative
|
||||
border: 1px solid alpha(black, .1)
|
||||
*
|
||||
box-sizing: border-box
|
||||
.rt-table
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
width: 100%
|
||||
border-collapse: collapse
|
||||
overflow: auto
|
||||
|
||||
.rt-thead
|
||||
display: flex
|
||||
flex-direction: column
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
&.-headerGroups
|
||||
background: alpha(black, .03)
|
||||
border-bottom: 1px solid alpha(black, .05)
|
||||
|
||||
&.-filters
|
||||
border-bottom: 1px solid alpha(black, 0.05)
|
||||
|
||||
.rt-th
|
||||
border-right: 1px solid alpha(black, 0.02)
|
||||
|
||||
&.-header
|
||||
box-shadow: 0 2px 15px 0px alpha(black, .15)
|
||||
|
||||
.rt-tr
|
||||
text-align:center
|
||||
|
||||
.rt-th
|
||||
.rt-td
|
||||
padding: 5px 5px
|
||||
line-height: normal
|
||||
position: relative
|
||||
border-right: 1px solid alpha(black, .05)
|
||||
transition box-shadow .3s $easeOutBack
|
||||
box-shadow:inset 0 0 0 0 transparent
|
||||
&.-sort-asc
|
||||
box-shadow:inset 0 3px 0 0 alpha(black, .6)
|
||||
&.-sort-desc
|
||||
box-shadow:inset 0 -3px 0 0 alpha(black, .6)
|
||||
&.-cursor-pointer
|
||||
cursor: pointer
|
||||
&:last-child
|
||||
border-right: 0
|
||||
|
||||
.rt-resizable-header
|
||||
overflow: visible
|
||||
&:last-child
|
||||
overflow: hidden
|
||||
|
||||
.rt-resizable-header-content
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
|
||||
.rt-header-pivot
|
||||
border-right-color: #f7f7f7
|
||||
|
||||
.rt-header-pivot:after, .rt-header-pivot:before
|
||||
left: 100%
|
||||
top: 50%
|
||||
border: solid transparent
|
||||
content: " "
|
||||
height: 0
|
||||
width: 0
|
||||
position: absolute
|
||||
pointer-events: none
|
||||
|
||||
.rt-header-pivot:after
|
||||
border-color: rgba(255, 255, 255, 0)
|
||||
border-left-color: #FFF
|
||||
border-width: 8px
|
||||
margin-top: -8px
|
||||
|
||||
.rt-header-pivot:before
|
||||
border-color: rgba(102, 102, 102, 0)
|
||||
border-left-color: #f7f7f7
|
||||
border-width: 10px
|
||||
margin-top: -10px
|
||||
|
||||
.rt-tbody
|
||||
display: flex
|
||||
flex-direction: column
|
||||
// z-index:0
|
||||
.rt-tr-group
|
||||
border-bottom: solid 1px alpha(black, .05)
|
||||
&:last-child
|
||||
border-bottom: 0
|
||||
.rt-td
|
||||
border-right:1px solid alpha(black, .02)
|
||||
&:last-child
|
||||
border-right:0
|
||||
.rt-expandable
|
||||
cursor: pointer
|
||||
.rt-tr-group
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: stretch
|
||||
.rt-tr
|
||||
display: inline-flex
|
||||
.rt-th
|
||||
.rt-td
|
||||
flex: 1 0 0px
|
||||
white-space: nowrap
|
||||
text-overflow: ellipsis
|
||||
padding: 7px 5px
|
||||
overflow: hidden
|
||||
transition: .3s ease
|
||||
transition-property: width, min-width, padding, opacity
|
||||
|
||||
&.-hidden
|
||||
width: 0 !important
|
||||
min-width: 0 !important
|
||||
padding: 0 !important
|
||||
border:0 !important
|
||||
opacity: 0 !important
|
||||
|
||||
.rt-expander
|
||||
display: inline-block
|
||||
position:relative
|
||||
margin: 0
|
||||
color: transparent
|
||||
margin: 0 10px
|
||||
&:after
|
||||
content: ''
|
||||
position: absolute
|
||||
width: 0
|
||||
height: 0
|
||||
top:50%
|
||||
left:50%
|
||||
transform: translate(-50%, -50%) rotate(-90deg)
|
||||
border-left: ($expandSize * .72) solid transparent
|
||||
border-right: ($expandSize * .72) solid transparent
|
||||
border-top: $expandSize solid alpha(black, .8)
|
||||
transition: all .3s $easeOutBack
|
||||
cursor: pointer
|
||||
&.-open:after
|
||||
transform: translate(-50%, -50%) rotate(0deg)
|
||||
|
||||
.rt-resizer
|
||||
display: inline-block
|
||||
position: absolute
|
||||
width: 36px
|
||||
top: 0
|
||||
bottom: 0
|
||||
right: -18px
|
||||
cursor: col-resize
|
||||
z-index: 10
|
||||
|
||||
.rt-tfoot
|
||||
display: flex
|
||||
flex-direction: column
|
||||
box-shadow: 0 0px 15px 0px alpha(black, .15)
|
||||
|
||||
.rt-td
|
||||
border-right:1px solid alpha(black, .05)
|
||||
&:last-child
|
||||
border-right:0
|
||||
|
||||
&.-striped
|
||||
.rt-tr.-odd
|
||||
background: alpha(black, .03)
|
||||
&.-highlight
|
||||
.rt-tbody
|
||||
.rt-tr:not(.-padRow):hover
|
||||
background: alpha(black, .05)
|
||||
|
||||
.-pagination
|
||||
z-index: 1
|
||||
display:flex
|
||||
justify-content: space-between
|
||||
align-items: stretch
|
||||
flex-wrap: wrap
|
||||
padding: 3px
|
||||
box-shadow: 0 0px 15px 0px alpha(black, .1)
|
||||
border-top: 2px solid alpha(black, .1)
|
||||
|
||||
.-btn
|
||||
appearance:none
|
||||
display:block
|
||||
width:100%
|
||||
height: 100%
|
||||
border: 0
|
||||
border-radius: 3px
|
||||
padding: 6px
|
||||
font-size: 1em
|
||||
color: alpha(black, .6)
|
||||
background: alpha(black, .1)
|
||||
transition: all .1s ease
|
||||
cursor: pointer
|
||||
outline:none
|
||||
|
||||
&[disabled]
|
||||
opacity: .5
|
||||
cursor: default
|
||||
|
||||
&:not([disabled]):hover
|
||||
background: alpha(black, .3)
|
||||
color: white
|
||||
|
||||
.-previous
|
||||
.-next
|
||||
flex: 1
|
||||
text-align: center
|
||||
|
||||
.-center
|
||||
flex: 1.5
|
||||
text-align:center
|
||||
margin-bottom:0
|
||||
display: flex
|
||||
flex-direction: row
|
||||
flex-wrap: wrap
|
||||
align-items: center
|
||||
justify-content: space-around
|
||||
|
||||
.-pageInfo
|
||||
display: inline-block
|
||||
margin: 3px 10px
|
||||
white-space: nowrap
|
||||
|
||||
.-pageJump
|
||||
display:inline-block
|
||||
input
|
||||
width: 70px
|
||||
text-align:center
|
||||
|
||||
.-pageSizeOptions
|
||||
margin: 3px 10px
|
||||
|
||||
.rt-noData
|
||||
display:block
|
||||
position:absolute
|
||||
left:50%
|
||||
top:50%
|
||||
transform: translate(-50%, -50%)
|
||||
background: alpha(white, .8)
|
||||
transition: all .3s ease
|
||||
z-index: 1
|
||||
pointer-events: none
|
||||
padding: 20px
|
||||
color: alpha(black, .5)
|
||||
|
||||
.-loading
|
||||
display:block
|
||||
position:absolute
|
||||
left:0
|
||||
right:0
|
||||
top:0
|
||||
bottom:0
|
||||
background: alpha(white, .8)
|
||||
transition: all .3s ease
|
||||
z-index: 2
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
> div
|
||||
position:absolute
|
||||
display: block
|
||||
text-align:center
|
||||
width:100%
|
||||
top:50%
|
||||
left: 0
|
||||
font-size: 15px
|
||||
color: alpha(black, .6)
|
||||
transform: translateY(-52%)
|
||||
transition: all .3s $easeOutQuad
|
||||
|
||||
&.-active
|
||||
opacity: 1
|
||||
pointer-events: all
|
||||
> div
|
||||
transform: translateY(50%)
|
||||
|
||||
input
|
||||
select
|
||||
border: 1px solid rgba(0,0,0,0.1)
|
||||
background: white
|
||||
padding: 5px 7px
|
||||
font-size: inherit
|
||||
border-radius: 3px
|
||||
font-weight: normal
|
||||
outline:none
|
||||
|
||||
input:not([type="checkbox"]):not([type="radio"])
|
||||
select
|
||||
appearance: none
|
||||
|
||||
.select-wrap
|
||||
position:relative
|
||||
display:inline-block
|
||||
select
|
||||
padding: 5px 15px 5px 7px
|
||||
min-width:100px
|
||||
&:after
|
||||
content: ''
|
||||
position: absolute
|
||||
right: 8px
|
||||
top: 50%
|
||||
transform: translate(0, -50%)
|
||||
border-color: #999 transparent transparent
|
||||
border-style: solid
|
||||
border-width: 5px 5px 2.5px
|
||||
|
||||
.rt-resizing
|
||||
.rt-th
|
||||
.rt-td
|
||||
transition: none!important
|
||||
cursor: col-resize
|
||||
user-select none
|
||||
101
src/lifecycle.js
Normal file
101
src/lifecycle.js
Normal file
@ -0,0 +1,101 @@
|
||||
export default Base => class extends Base {
|
||||
|
||||
componentWillMount () {
|
||||
this.setStateWithData(this.getDataModel(this.getResolvedState()))
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.fireFetchData()
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps, nextState) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newState = this.getResolvedState(nextProps, nextState)
|
||||
|
||||
// Do a deep compare of new and old `defaultOption` and
|
||||
// if they are different reset `option = defaultOption`
|
||||
const defaultableOptions = ['sorted', 'filtered', 'resized', 'expanded']
|
||||
defaultableOptions.forEach(x => {
|
||||
const defaultName = `default${x.charAt(0).toUpperCase() + x.slice(1)}`
|
||||
if (JSON.stringify(oldState[defaultName]) !== JSON.stringify(newState[defaultName])) {
|
||||
newState[x] = newState[defaultName]
|
||||
}
|
||||
})
|
||||
|
||||
// If they change these table options, we need to reset defaults
|
||||
// or else we could get into a state where the user has changed the UI
|
||||
// and then disabled the ability to change it back.
|
||||
// e.g. If `filterable` has changed, set `filtered = defaultFiltered`
|
||||
const resettableOptions = ['sortable', 'filterable', 'resizable']
|
||||
resettableOptions.forEach(x => {
|
||||
if (oldState[x] !== newState[x]) {
|
||||
const baseName = x.replace('able', '')
|
||||
const optionName = `${baseName}ed`
|
||||
const defaultName = `default${optionName.charAt(0).toUpperCase() + optionName.slice(1)}`
|
||||
newState[optionName] = newState[defaultName]
|
||||
}
|
||||
})
|
||||
|
||||
// Props that trigger a data update
|
||||
if (
|
||||
oldState.data !== newState.data ||
|
||||
oldState.columns !== newState.columns ||
|
||||
oldState.pivotBy !== newState.pivotBy ||
|
||||
oldState.sorted !== newState.sorted ||
|
||||
oldState.filtered !== newState.filtered
|
||||
) {
|
||||
this.setStateWithData(this.getDataModel(newState))
|
||||
}
|
||||
}
|
||||
|
||||
setStateWithData (newState, cb) {
|
||||
const oldState = this.getResolvedState()
|
||||
const newResolvedState = this.getResolvedState({}, newState)
|
||||
const {freezeWhenExpanded} = newResolvedState
|
||||
|
||||
// Default to unfrozen state
|
||||
newResolvedState.frozen = false
|
||||
|
||||
// If freezeWhenExpanded is set, check for frozen conditions
|
||||
if (freezeWhenExpanded) {
|
||||
// if any rows are expanded, freeze the existing data and sorting
|
||||
const keys = Object.keys(newResolvedState.expanded)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
if (newResolvedState.expanded[keys[i]]) {
|
||||
newResolvedState.frozen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the data isn't frozen and either the data or
|
||||
// sorting model has changed, update the data
|
||||
if (
|
||||
(oldState.frozen && !newResolvedState.frozen) ||
|
||||
oldState.sorted !== newResolvedState.sorted ||
|
||||
oldState.filtered !== newResolvedState.filtered ||
|
||||
oldState.showFilters !== newResolvedState.showFilters ||
|
||||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData)
|
||||
) {
|
||||
// Handle collapseOnsortedChange & collapseOnDataChange
|
||||
if (
|
||||
(oldState.sorted !== newResolvedState.sorted && this.props.collapseOnSortingChange) ||
|
||||
(oldState.filtered !== newResolvedState.filtered) ||
|
||||
(oldState.showFilters !== newResolvedState.showFilters) ||
|
||||
(!newResolvedState.frozen && oldState.resolvedData !== newResolvedState.resolvedData && this.props.collapseOnDataChange)
|
||||
) {
|
||||
newResolvedState.expanded = {}
|
||||
}
|
||||
|
||||
Object.assign(newResolvedState, this.getSortedData(newResolvedState))
|
||||
}
|
||||
|
||||
// Calculate pageSize all the time
|
||||
if (newResolvedState.sortedData) {
|
||||
newResolvedState.pages = newResolvedState.manual ? newResolvedState.pages : Math.ceil(newResolvedState.sortedData.length / newResolvedState.pageSize)
|
||||
newResolvedState.page = Math.max(newResolvedState.page >= newResolvedState.pages ? newResolvedState.pages - 1 : newResolvedState.page, 0)
|
||||
}
|
||||
|
||||
return this.setState(newResolvedState, cb)
|
||||
}
|
||||
}
|
||||
666
src/methods.js
Normal file
666
src/methods.js
Normal file
@ -0,0 +1,666 @@
|
||||
import React from 'react'
|
||||
import _ from './utils'
|
||||
|
||||
export default Base => class extends Base {
|
||||
getResolvedState (props, state) {
|
||||
const resolvedState = {
|
||||
..._.compactObject(this.state),
|
||||
..._.compactObject(this.props),
|
||||
..._.compactObject(state),
|
||||
..._.compactObject(props)
|
||||
}
|
||||
return resolvedState
|
||||
}
|
||||
|
||||
getDataModel (newState) {
|
||||
const {
|
||||
columns,
|
||||
pivotBy = [],
|
||||
data,
|
||||
pivotIDKey,
|
||||
pivotValKey,
|
||||
subRowsKey,
|
||||
aggregatedKey,
|
||||
nestingLevelKey,
|
||||
originalKey,
|
||||
indexKey,
|
||||
groupedByPivotKey,
|
||||
SubComponent
|
||||
} = newState
|
||||
|
||||
// Determine Header Groups
|
||||
let hasHeaderGroups = false
|
||||
columns.forEach(column => {
|
||||
if (column.columns) {
|
||||
hasHeaderGroups = true
|
||||
}
|
||||
})
|
||||
|
||||
let columnsWithExpander = [...columns]
|
||||
|
||||
let expanderColumn = columns.find(col => col.expander || (col.columns && col.columns.some(col2 => col2.expander)))
|
||||
// The actual expander might be in the columns field of a group column
|
||||
if (expanderColumn && !expanderColumn.expander) {
|
||||
expanderColumn = expanderColumn.columns.find(col => col.expander)
|
||||
}
|
||||
|
||||
// If we have SubComponent's we need to make sure we have an expander column
|
||||
if (SubComponent && !expanderColumn) {
|
||||
expanderColumn = {expander: true}
|
||||
columnsWithExpander = [expanderColumn, ...columnsWithExpander]
|
||||
}
|
||||
|
||||
const makeDecoratedColumn = (column) => {
|
||||
let dcol
|
||||
if (column.expander) {
|
||||
dcol = {
|
||||
...this.props.column,
|
||||
...this.props.expanderDefaults,
|
||||
...column
|
||||
}
|
||||
} else {
|
||||
dcol = {
|
||||
...this.props.column,
|
||||
...column
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof dcol.accessor === 'string') {
|
||||
dcol.id = dcol.id || dcol.accessor
|
||||
const accessorString = dcol.accessor
|
||||
dcol.accessor = row => _.get(row, accessorString)
|
||||
return dcol
|
||||
}
|
||||
|
||||
if (dcol.accessor && !dcol.id) {
|
||||
console.warn(dcol)
|
||||
throw new Error('A column id is required if using a non-string accessor for column above.')
|
||||
}
|
||||
|
||||
if (!dcol.accessor) {
|
||||
dcol.accessor = d => undefined
|
||||
}
|
||||
|
||||
// Ensure minWidth is not greater than maxWidth if set
|
||||
if (dcol.maxWidth < dcol.minWidth) {
|
||||
dcol.minWidth = dcol.maxWidth
|
||||
}
|
||||
|
||||
return dcol
|
||||
}
|
||||
|
||||
// Decorate the columns
|
||||
const decorateAndAddToAll = (col) => {
|
||||
const decoratedColumn = makeDecoratedColumn(col)
|
||||
allDecoratedColumns.push(decoratedColumn)
|
||||
return decoratedColumn
|
||||
}
|
||||
let allDecoratedColumns = []
|
||||
const decoratedColumns = columnsWithExpander.map((column, i) => {
|
||||
if (column.columns) {
|
||||
return {
|
||||
...column,
|
||||
columns: column.columns.map(decorateAndAddToAll)
|
||||
}
|
||||
} else {
|
||||
return decorateAndAddToAll(column)
|
||||
}
|
||||
})
|
||||
|
||||
// Build the visible columns, headers and flat column list
|
||||
let visibleColumns = decoratedColumns.slice()
|
||||
let allVisibleColumns = []
|
||||
|
||||
visibleColumns = visibleColumns.map((column, i) => {
|
||||
if (column.columns) {
|
||||
const visibleSubColumns = column.columns.filter(d => pivotBy.indexOf(d.id) > -1 ? false : _.getFirstDefined(d.show, true))
|
||||
return {
|
||||
...column,
|
||||
columns: visibleSubColumns
|
||||
}
|
||||
}
|
||||
return column
|
||||
})
|
||||
|
||||
visibleColumns = visibleColumns.filter(column => {
|
||||
return column.columns ? column.columns.length : pivotBy.indexOf(column.id) > -1 ? false : _.getFirstDefined(column.show, true)
|
||||
})
|
||||
|
||||
// Find any custom pivot location
|
||||
const pivotIndex = visibleColumns.findIndex(col => col.pivot)
|
||||
|
||||
// Handle Pivot Columns
|
||||
if (pivotBy.length) {
|
||||
// Retrieve the pivot columns in the correct pivot order
|
||||
const pivotColumns = []
|
||||
pivotBy.forEach(pivotID => {
|
||||
const found = allDecoratedColumns.find(d => d.id === pivotID)
|
||||
if (found) {
|
||||
pivotColumns.push(found)
|
||||
}
|
||||
})
|
||||
|
||||
let pivotColumnGroup = {
|
||||
header: () => <strong>Group</strong>,
|
||||
columns: pivotColumns.map(col => ({
|
||||
...this.props.pivotDefaults,
|
||||
...col,
|
||||
pivoted: true
|
||||
}))
|
||||
}
|
||||
|
||||
// Place the pivotColumns back into the visibleColumns
|
||||
if (pivotIndex >= 0) {
|
||||
pivotColumnGroup = {
|
||||
...visibleColumns[pivotIndex],
|
||||
...pivotColumnGroup
|
||||
}
|
||||
visibleColumns.splice(pivotIndex, 1, pivotColumnGroup)
|
||||
} else {
|
||||
visibleColumns.unshift(pivotColumnGroup)
|
||||
}
|
||||
}
|
||||
|
||||
// Build Header Groups
|
||||
const headerGroups = []
|
||||
let currentSpan = []
|
||||
|
||||
// A convenience function to add a header and reset the currentSpan
|
||||
const addHeader = (columns, column) => {
|
||||
headerGroups.push({
|
||||
...this.props.column,
|
||||
...column,
|
||||
columns: columns
|
||||
})
|
||||
currentSpan = []
|
||||
}
|
||||
|
||||
// Build flast list of allVisibleColumns and HeaderGroups
|
||||
visibleColumns.forEach((column, i) => {
|
||||
if (column.columns) {
|
||||
allVisibleColumns = allVisibleColumns.concat(column.columns)
|
||||
if (currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
addHeader(column.columns, column)
|
||||
return
|
||||
}
|
||||
allVisibleColumns.push(column)
|
||||
currentSpan.push(column)
|
||||
})
|
||||
if (hasHeaderGroups && currentSpan.length > 0) {
|
||||
addHeader(currentSpan)
|
||||
}
|
||||
|
||||
// Access the data
|
||||
const accessRow = (d, i, level = 0) => {
|
||||
const row = {
|
||||
[originalKey]: d,
|
||||
[indexKey]: i,
|
||||
[subRowsKey]: d[subRowsKey],
|
||||
[nestingLevelKey]: level
|
||||
}
|
||||
allDecoratedColumns.forEach(column => {
|
||||
if (column.expander) return
|
||||
row[column.id] = column.accessor(d)
|
||||
})
|
||||
if (row[subRowsKey]) {
|
||||
row[subRowsKey] = row[subRowsKey].map((d, i) => accessRow(d, i, level + 1))
|
||||
}
|
||||
return row
|
||||
}
|
||||
let resolvedData = data.map((d, i) => accessRow(d, i))
|
||||
|
||||
// If pivoting, recursively group the data
|
||||
const aggregate = (rows) => {
|
||||
const aggregationValues = {}
|
||||
aggregatingColumns.forEach(column => {
|
||||
const values = rows.map(d => d[column.id])
|
||||
aggregationValues[column.id] = column.aggregate(values, rows)
|
||||
})
|
||||
return aggregationValues
|
||||
}
|
||||
|
||||
// TODO: Make it possible to fabricate nested rows without pivoting
|
||||
const aggregatingColumns = allVisibleColumns.filter(d => !d.expander && d.aggregate)
|
||||
if (pivotBy.length) {
|
||||
const groupRecursively = (rows, keys, i = 0) => {
|
||||
// This is the last level, just return the rows
|
||||
if (i === keys.length) {
|
||||
return rows
|
||||
}
|
||||
// Group the rows together for this level
|
||||
let groupedRows = Object.entries(
|
||||
_.groupBy(rows, keys[i]))
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
[pivotIDKey]: keys[i],
|
||||
[pivotValKey]: key,
|
||||
[keys[i]]: key,
|
||||
[subRowsKey]: value,
|
||||
[nestingLevelKey]: i,
|
||||
[groupedByPivotKey]: true
|
||||
}
|
||||
})
|
||||
// Recurse into the subRows
|
||||
groupedRows = groupedRows.map(rowGroup => {
|
||||
let subRows = groupRecursively(rowGroup[subRowsKey], keys, i + 1)
|
||||
return {
|
||||
...rowGroup,
|
||||
[subRowsKey]: subRows,
|
||||
[aggregatedKey]: true,
|
||||
...aggregate(subRows)
|
||||
}
|
||||
})
|
||||
return groupedRows
|
||||
}
|
||||
resolvedData = groupRecursively(resolvedData, pivotBy)
|
||||
}
|
||||
|
||||
return {
|
||||
...newState,
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
headerGroups,
|
||||
allDecoratedColumns,
|
||||
hasHeaderGroups
|
||||
}
|
||||
}
|
||||
|
||||
getSortedData (resolvedState) {
|
||||
const {
|
||||
manual,
|
||||
sorted,
|
||||
filtered,
|
||||
defaultFilterMethod,
|
||||
resolvedData,
|
||||
allVisibleColumns,
|
||||
allDecoratedColumns
|
||||
} = resolvedState
|
||||
|
||||
const sortMethodsByColumnID = {}
|
||||
|
||||
allDecoratedColumns
|
||||
.filter(col => col.sortMethod)
|
||||
.forEach(col => {
|
||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||
})
|
||||
|
||||
// Resolve the data from either manual data or sorted data
|
||||
return {
|
||||
sortedData: manual ? resolvedData : this.sortData(
|
||||
this.filterData(
|
||||
resolvedData,
|
||||
filtered,
|
||||
defaultFilterMethod,
|
||||
allVisibleColumns
|
||||
),
|
||||
sorted,
|
||||
sortMethodsByColumnID
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fireFetchData () {
|
||||
this.props.onFetchData(this.getResolvedState(), this)
|
||||
}
|
||||
|
||||
getPropOrState (key) {
|
||||
return _.getFirstDefined(this.props[key], this.state[key])
|
||||
}
|
||||
|
||||
getStateOrProp (key) {
|
||||
return _.getFirstDefined(this.state[key], this.props[key])
|
||||
}
|
||||
|
||||
filterData (data, filtered, defaultFilterMethod, allVisibleColumns) {
|
||||
let filteredData = data
|
||||
|
||||
if (filtered.length) {
|
||||
filteredData = filtered.reduce(
|
||||
(filteredSoFar, nextFilter) => {
|
||||
return filteredSoFar.filter(
|
||||
(row) => {
|
||||
let column
|
||||
|
||||
column = allVisibleColumns.find(x => x.id === nextFilter.id || (x.pivotColumns && x.pivotColumns.some(y => y.id === nextFilter.id)))
|
||||
|
||||
// Could possibly be in pivotColumns
|
||||
if (column.id !== nextFilter.id && column.pivotColumns) {
|
||||
column = column.pivotColumns.find(x => x.id === nextFilter.id)
|
||||
}
|
||||
|
||||
// Don't filter hidden columns or columns that have had their filters disabled
|
||||
if (!column || column.filterable === false) {
|
||||
return true
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || defaultFilterMethod
|
||||
|
||||
return filterMethod(nextFilter, row, column)
|
||||
})
|
||||
}
|
||||
, filteredData
|
||||
)
|
||||
|
||||
// Apply the filter to the subrows if we are pivoting, and then
|
||||
// filter any rows without subcolumns because it would be strange to show
|
||||
filteredData = filteredData.map(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
[this.props.subRowsKey]: this.filterData(row[this.props.subRowsKey], filtered, defaultFilterMethod, allVisibleColumns)
|
||||
}
|
||||
}).filter(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return true
|
||||
}
|
||||
return row[this.props.subRowsKey].length > 0
|
||||
})
|
||||
}
|
||||
|
||||
return filteredData
|
||||
}
|
||||
|
||||
sortData (data, sorted, sortMethodsByColumnID = {}) {
|
||||
if (!sorted.length) {
|
||||
return data
|
||||
}
|
||||
|
||||
const sortedData = (this.props.orderByMethod || _.orderBy)(
|
||||
data,
|
||||
sorted.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
if (sortMethodsByColumnID[sort.id]) {
|
||||
return (a, b) => {
|
||||
return sortMethodsByColumnID[sort.id](a[sort.id], b[sort.id])
|
||||
}
|
||||
}
|
||||
return (a, b) => {
|
||||
return this.props.defaultSortMethod(a[sort.id], b[sort.id])
|
||||
}
|
||||
}),
|
||||
sorted.map(d => !d.desc),
|
||||
this.props.indexKey
|
||||
)
|
||||
|
||||
sortedData.forEach(row => {
|
||||
if (!row[this.props.subRowsKey]) {
|
||||
return
|
||||
}
|
||||
row[this.props.subRowsKey] = this.sortData(row[this.props.subRowsKey], sorted, sortMethodsByColumnID)
|
||||
})
|
||||
|
||||
return sortedData
|
||||
}
|
||||
|
||||
getMinRows () {
|
||||
return _.getFirstDefined(this.props.minRows, this.getStateOrProp('pageSize'))
|
||||
}
|
||||
|
||||
// User actions
|
||||
onPageChange (page) {
|
||||
const {onPageChange, collapseOnPageChange} = this.props
|
||||
onPageChange && onPageChange(page)
|
||||
// If controlled, do not keep track of state
|
||||
if (typeof this.props.page !== 'undefined') {
|
||||
this.fireFetchData()
|
||||
return
|
||||
}
|
||||
const newState = {page}
|
||||
if (collapseOnPageChange) {
|
||||
newState.expanded = {}
|
||||
}
|
||||
this.setStateWithData(newState, () => {
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
onPageSizeChange (newPageSize) {
|
||||
const {onPageSizeChange} = this.props
|
||||
const {pageSize, page} = this.getResolvedState()
|
||||
|
||||
// Normalize the page to display
|
||||
const currentRow = pageSize * page
|
||||
const newPage = Math.floor(currentRow / newPageSize)
|
||||
|
||||
onPageSizeChange && onPageSizeChange(newPageSize, newPage)
|
||||
if (typeof this.props.page !== 'undefined') {
|
||||
this.fireFetchData()
|
||||
return
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
pageSize: newPageSize,
|
||||
page: newPage
|
||||
}, () => {
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
sortColumn (column, additive) {
|
||||
const {sorted, skipNextSort} = this.getResolvedState()
|
||||
|
||||
// we can't stop event propagation from the column resize move handlers
|
||||
// attached to the document because of react's synthetic events
|
||||
// so we have to prevent the sort function from actually sorting
|
||||
// if we click on the column resize element within a header.
|
||||
if (skipNextSort) {
|
||||
this.setStateWithData({
|
||||
skipNextSort: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const {onSortedChange} = this.props
|
||||
|
||||
let newSorted = _.clone(sorted || []).map(d => {
|
||||
d.desc = _.isSortingDesc(d)
|
||||
return d
|
||||
})
|
||||
if (!_.isArray(column)) {
|
||||
// Single-Sort
|
||||
const existingIndex = newSorted.findIndex(d => d.id === column.id)
|
||||
if (existingIndex > -1) {
|
||||
const existing = newSorted[existingIndex]
|
||||
if (existing.desc) {
|
||||
if (additive) {
|
||||
newSorted.splice(existingIndex, 1)
|
||||
} else {
|
||||
existing.desc = false
|
||||
newSorted = [existing]
|
||||
}
|
||||
} else {
|
||||
existing.desc = true
|
||||
if (!additive) {
|
||||
newSorted = [existing]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (additive) {
|
||||
newSorted.push({
|
||||
id: column.id,
|
||||
desc: false
|
||||
})
|
||||
} else {
|
||||
newSorted = [{
|
||||
id: column.id,
|
||||
desc: false
|
||||
}]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multi-Sort
|
||||
const existingIndex = newSorted.findIndex(d => d.id === column[0].id)
|
||||
// Existing Sorted Column
|
||||
if (existingIndex > -1) {
|
||||
const existing = newSorted[existingIndex]
|
||||
if (existing.desc) {
|
||||
if (additive) {
|
||||
newSorted.splice(existingIndex, column.length)
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorted[existingIndex + i].desc = false
|
||||
})
|
||||
}
|
||||
} else {
|
||||
column.forEach((d, i) => {
|
||||
newSorted[existingIndex + i].desc = true
|
||||
})
|
||||
}
|
||||
if (!additive) {
|
||||
newSorted = newSorted.slice(existingIndex, column.length)
|
||||
}
|
||||
} else {
|
||||
// New Sort Column
|
||||
if (additive) {
|
||||
newSorted = newSorted.concat(column.map(d => ({
|
||||
id: d.id,
|
||||
desc: false
|
||||
})))
|
||||
} else {
|
||||
newSorted = column.map(d => ({
|
||||
id: d.id,
|
||||
desc: false
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
// If controlled, do not keep track of state
|
||||
onSortedChange && onSortedChange(newSorted, column, additive)
|
||||
if (typeof this.props.sorted !== 'undefined') {
|
||||
this.fireFetchData()
|
||||
return
|
||||
}
|
||||
this.setStateWithData({
|
||||
page: ((!sorted.length && newSorted.length) || !additive) ? 0 : this.state.page,
|
||||
sorted: newSorted
|
||||
}, () => {
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
filterColumn (column, value) {
|
||||
const {filtered} = this.getResolvedState()
|
||||
const {onFilteredChange} = this.props
|
||||
|
||||
// Remove old filter first if it exists
|
||||
const newFiltering = (filtered || []).filter(x => {
|
||||
if (x.id !== column.id) {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
if (value !== '') {
|
||||
newFiltering.push({
|
||||
id: column.id,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
|
||||
onFilteredChange && onFilteredChange(newFiltering, column, value)
|
||||
|
||||
// If filters is being controlled, do not manage state internally
|
||||
if (this.props.filtered) {
|
||||
this.fireFetchData()
|
||||
return
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
filtered: newFiltering
|
||||
}, () => {
|
||||
this.fireFetchData()
|
||||
})
|
||||
}
|
||||
|
||||
resizeColumnStart (column, event, isTouch) {
|
||||
const parentWidth = event.target.parentElement.getBoundingClientRect().width
|
||||
|
||||
let pageX
|
||||
if (isTouch) {
|
||||
pageX = event.changedTouches[0].pageX
|
||||
} else {
|
||||
pageX = event.pageX
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
currentlyResizing: {
|
||||
id: column.id,
|
||||
startX: pageX,
|
||||
parentWidth: parentWidth
|
||||
}
|
||||
}, () => {
|
||||
if (isTouch) {
|
||||
document.addEventListener('touchmove', this.resizeColumnMoving)
|
||||
document.addEventListener('touchcancel', this.resizeColumnEnd)
|
||||
document.addEventListener('touchend', this.resizeColumnEnd)
|
||||
} else {
|
||||
document.addEventListener('mousemove', this.resizeColumnMoving)
|
||||
document.addEventListener('mouseup', this.resizeColumnEnd)
|
||||
document.addEventListener('mouseleave', this.resizeColumnEnd)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
resizeColumnEnd (event) {
|
||||
let isTouch = event.type === 'touchend' || event.type === 'touchcancel'
|
||||
|
||||
if (isTouch) {
|
||||
document.removeEventListener('touchmove', this.resizeColumnMoving)
|
||||
document.removeEventListener('touchcancel', this.resizeColumnEnd)
|
||||
document.removeEventListener('touchend', this.resizeColumnEnd)
|
||||
}
|
||||
|
||||
// If its a touch event clear the mouse one's as well because sometimes
|
||||
// the mouseDown event gets called as well, but the mouseUp event doesn't
|
||||
document.removeEventListener('mousemove', this.resizeColumnMoving)
|
||||
document.removeEventListener('mouseup', this.resizeColumnEnd)
|
||||
document.removeEventListener('mouseleave', this.resizeColumnEnd)
|
||||
|
||||
// The touch events don't propagate up to the sorting's onMouseDown event so
|
||||
// no need to prevent it from happening or else the first click after a touch
|
||||
// event resize will not sort the column.
|
||||
if (!isTouch) {
|
||||
this.setStateWithData({
|
||||
skipNextSort: true,
|
||||
currentlyResizing: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resizeColumnMoving (event) {
|
||||
const {onResizedChange} = this.props
|
||||
const {resized, currentlyResizing} = this.getResolvedState()
|
||||
|
||||
// Delete old value
|
||||
const newResized = resized.filter(x => x.id !== currentlyResizing.id)
|
||||
|
||||
let pageX
|
||||
|
||||
if (event.type === 'touchmove') {
|
||||
pageX = event.changedTouches[0].pageX
|
||||
} else if (event.type === 'mousemove') {
|
||||
pageX = event.pageX
|
||||
}
|
||||
|
||||
// Set the min size to 10 to account for margin and border or else the group headers don't line up correctly
|
||||
const newWidth = Math.max(currentlyResizing.parentWidth + pageX - currentlyResizing.startX, 11)
|
||||
|
||||
newResized.push({
|
||||
id: currentlyResizing.id,
|
||||
value: newWidth
|
||||
})
|
||||
|
||||
onResizedChange && onResizedChange(newResized, event)
|
||||
|
||||
if (this.props.resized) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setStateWithData({
|
||||
resized: newResized
|
||||
})
|
||||
}
|
||||
}
|
||||
142
src/pagination.js
Normal file
142
src/pagination.js
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { Component } from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
// import _ from './utils'
|
||||
|
||||
const defaultButton = (props) => (
|
||||
<button type='button' {...props} className='-btn'>{props.children}</button>
|
||||
)
|
||||
|
||||
export default class ReactTablePagination extends Component {
|
||||
constructor (props) {
|
||||
super()
|
||||
|
||||
this.getSafePage = this.getSafePage.bind(this)
|
||||
this.changePage = this.changePage.bind(this)
|
||||
this.applyPage = this.applyPage.bind(this)
|
||||
|
||||
this.state = {
|
||||
page: props.page
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState({page: nextProps.page})
|
||||
}
|
||||
|
||||
getSafePage (page) {
|
||||
if (isNaN(page)) {
|
||||
page = this.props.page
|
||||
}
|
||||
return Math.min(Math.max(page, 0), this.props.pages - 1)
|
||||
}
|
||||
|
||||
changePage (page) {
|
||||
page = this.getSafePage(page)
|
||||
this.setState({page})
|
||||
if (this.props.page !== page) {
|
||||
this.props.onPageChange(page)
|
||||
}
|
||||
}
|
||||
|
||||
applyPage (e) {
|
||||
e && e.preventDefault()
|
||||
const page = this.state.page
|
||||
this.changePage(page === '' ? this.props.page : page)
|
||||
}
|
||||
|
||||
render () {
|
||||
const {
|
||||
// Computed
|
||||
pages,
|
||||
// Props
|
||||
page,
|
||||
showPageSizeOptions,
|
||||
pageSizeOptions,
|
||||
pageSize,
|
||||
showPageJump,
|
||||
canPrevious,
|
||||
canNext,
|
||||
onPageSizeChange,
|
||||
className,
|
||||
PreviousComponent = defaultButton,
|
||||
NextComponent = defaultButton
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames(className, '-pagination')}
|
||||
style={this.props.paginationStyle}
|
||||
>
|
||||
<div className='-previous'>
|
||||
<PreviousComponent
|
||||
onClick={(e) => {
|
||||
if (!canPrevious) return
|
||||
this.changePage(page - 1)
|
||||
}}
|
||||
disabled={!canPrevious}
|
||||
>
|
||||
{this.props.previousText}
|
||||
</PreviousComponent>
|
||||
</div>
|
||||
<div className='-center'>
|
||||
<span className='-pageInfo'>
|
||||
{this.props.pageText} {showPageJump ? (
|
||||
<div className='-pageJump'>
|
||||
<input
|
||||
type={this.state.page === '' ? 'text' : 'number'}
|
||||
onChange={e => {
|
||||
const val = e.target.value
|
||||
const page = val - 1
|
||||
if (val === '') {
|
||||
return this.setState({page: val})
|
||||
}
|
||||
this.setState({page: this.getSafePage(page)})
|
||||
}}
|
||||
value={this.state.page === '' ? '' : this.state.page + 1}
|
||||
onBlur={this.applyPage}
|
||||
onKeyPress={e => {
|
||||
if (e.which === 13 || e.keyCode === 13) {
|
||||
this.applyPage()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className='-currentPage'>{page + 1}</span>
|
||||
)} {this.props.ofText} <span className='-totalPages'>{pages || 1}</span>
|
||||
</span>
|
||||
{showPageSizeOptions && (
|
||||
<span className='select-wrap -pageSizeOptions'>
|
||||
<select
|
||||
onChange={(e) => onPageSizeChange(Number(e.target.value))}
|
||||
value={pageSize}
|
||||
>
|
||||
{pageSizeOptions.map((option, i) => {
|
||||
return (
|
||||
<option
|
||||
key={i}
|
||||
value={option}>
|
||||
{option} {this.props.rowsText}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</select>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className='-next'>
|
||||
<NextComponent
|
||||
onClick={(e) => {
|
||||
if (!canNext) return
|
||||
this.changePage(page + 1)
|
||||
}}
|
||||
disabled={!canNext}
|
||||
>
|
||||
{this.props.nextText}
|
||||
</NextComponent>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
203
src/utils.js
203
src/utils.js
@ -1,12 +1,199 @@
|
||||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
//
|
||||
export default {
|
||||
makePath
|
||||
get,
|
||||
set,
|
||||
takeRight,
|
||||
last,
|
||||
orderBy,
|
||||
range,
|
||||
remove,
|
||||
clone,
|
||||
getFirstDefined,
|
||||
sum,
|
||||
makeTemplateComponent,
|
||||
groupBy,
|
||||
isArray,
|
||||
splitProps,
|
||||
compactObject,
|
||||
isSortingDesc,
|
||||
normalizeComponent
|
||||
}
|
||||
|
||||
function makePath (text) {
|
||||
return text.toString().toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace spaces with -
|
||||
.replace(/[^\w-]+/g, '') // Remove all non-word chars
|
||||
.replace(/--+/g, '-') // Replace multiple - with single -
|
||||
.replace(/^-+/, '') // Trim - from start of text
|
||||
.replace(/-+$/, '') // Trim - from end of text
|
||||
function get (obj, path, def) {
|
||||
if (!path) {
|
||||
return obj
|
||||
}
|
||||
const pathObj = makePathArray(path)
|
||||
let val
|
||||
try {
|
||||
val = pathObj.reduce((current, pathPart) => current[pathPart], obj)
|
||||
} catch (e) {}
|
||||
return typeof val !== 'undefined' ? val : def
|
||||
}
|
||||
|
||||
function set (obj = {}, path, value) {
|
||||
const keys = makePathArray(path)
|
||||
let keyPart
|
||||
let cursor = obj
|
||||
while ((keyPart = keys.shift()) && keys.length) {
|
||||
if (!cursor[keyPart]) {
|
||||
cursor[keyPart] = {}
|
||||
}
|
||||
cursor = cursor[keyPart]
|
||||
}
|
||||
cursor[keyPart] = value
|
||||
return obj
|
||||
}
|
||||
|
||||
function takeRight (arr, n) {
|
||||
const start = n > arr.length ? 0 : arr.length - n
|
||||
return arr.slice(start)
|
||||
}
|
||||
|
||||
function last (arr) {
|
||||
return arr[arr.length - 1]
|
||||
}
|
||||
|
||||
function range (n) {
|
||||
const arr = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
arr.push(n)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
function orderBy (arr, funcs, dirs, indexKey) {
|
||||
return arr.sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i++) {
|
||||
const comp = funcs[i]
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
const sortInt = comp(rowA, rowB)
|
||||
if (sortInt) {
|
||||
return desc ? -sortInt : sortInt
|
||||
}
|
||||
}
|
||||
// Use the row index for tie breakers
|
||||
return dirs[0]
|
||||
? rowA[indexKey] - rowB[indexKey]
|
||||
: rowB[indexKey] - rowA[indexKey]
|
||||
})
|
||||
}
|
||||
|
||||
function remove (a, b) {
|
||||
return a.filter(function (o, i) {
|
||||
var r = b(o)
|
||||
if (r) {
|
||||
a.splice(i, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function clone (a) {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(a, (key, value) => {
|
||||
if (typeof value === 'function') {
|
||||
return value.toString()
|
||||
}
|
||||
return value
|
||||
}))
|
||||
} catch (e) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
function getFirstDefined (...args) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
if (typeof args[i] !== 'undefined') {
|
||||
return args[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sum (arr) {
|
||||
return arr.reduce((a, b) => {
|
||||
return a + b
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function makeTemplateComponent (compClass) {
|
||||
return ({children, className, ...rest}) => (
|
||||
<div
|
||||
className={classnames(compClass, className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function groupBy (xs, key) {
|
||||
return xs.reduce((rv, x, i) => {
|
||||
const resKey = typeof key === 'function' ? key(x, i) : x[key]
|
||||
rv[resKey] = isArray(rv[resKey]) ? rv[resKey] : []
|
||||
rv[resKey].push(x)
|
||||
return rv
|
||||
}, {})
|
||||
}
|
||||
|
||||
function isArray (a) {
|
||||
return Array.isArray(a)
|
||||
}
|
||||
|
||||
// ########################################################################
|
||||
// Non-exported Helpers
|
||||
// ########################################################################
|
||||
|
||||
function makePathArray (obj) {
|
||||
return flattenDeep(obj)
|
||||
.join('.')
|
||||
.replace('[', '.')
|
||||
.replace(']', '')
|
||||
.split('.')
|
||||
}
|
||||
|
||||
function flattenDeep (arr, newArr = []) {
|
||||
if (!isArray(arr)) {
|
||||
newArr.push(arr)
|
||||
} else {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
flattenDeep(arr[i], newArr)
|
||||
}
|
||||
}
|
||||
return newArr
|
||||
}
|
||||
|
||||
function splitProps ({className, style, ...rest}) {
|
||||
return {
|
||||
className,
|
||||
style,
|
||||
rest
|
||||
}
|
||||
}
|
||||
|
||||
function compactObject (obj) {
|
||||
const newObj = {}
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key) && obj[key] !== undefined && typeof obj[key] !== 'undefined') {
|
||||
newObj[key] = obj[key]
|
||||
}
|
||||
}
|
||||
return newObj
|
||||
}
|
||||
|
||||
function isSortingDesc (d) {
|
||||
return !!(d.sort === 'desc' || d.desc === true || d.asc === false)
|
||||
}
|
||||
|
||||
function normalizeComponent (Comp, params = {}, fallback = Comp) {
|
||||
return typeof Comp === 'function' ? (
|
||||
Object.getPrototypeOf(Comp).isReactComponent ? (
|
||||
<Comp
|
||||
{...params}
|
||||
/>
|
||||
) : Comp(params)
|
||||
) : fallback
|
||||
}
|
||||
|
||||
108
stories/CellRenderers.js
Normal file
108
stories/CellRenderers.js
Normal file
@ -0,0 +1,108 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const data = _.map(_.range(5553), d => {
|
||||
const statusChance = Math.random()
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
progress: Math.floor(Math.random() * 100),
|
||||
status: statusChance > 0.66 ? 'relationship'
|
||||
: statusChance > 0.33 ? 'complicated'
|
||||
: 'single'
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
Cell: row => (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: '#dadada',
|
||||
borderRadius: '2px'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: `${row.value}%`,
|
||||
height: '100%',
|
||||
backgroundColor: row.value > 66 ? '#85cc00'
|
||||
: row.value > 33 ? '#ffbf00'
|
||||
: '#ff2e00',
|
||||
borderRadius: '2px',
|
||||
transition: 'all .2s ease-out'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}, {
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
Cell: row => (
|
||||
<span>
|
||||
<span style={{
|
||||
color: row.value === 'relationship' ? '#ff2e00'
|
||||
: row.value === 'complicated' ? '#ffbf00'
|
||||
: '#57d500',
|
||||
transition: 'all .3s ease'
|
||||
}}>
|
||||
●
|
||||
</span> {
|
||||
row.value === 'relationship' ? 'In a relationship'
|
||||
: row.value === 'complicated' ? `It's complicated`
|
||||
: 'Single'
|
||||
}
|
||||
</span>
|
||||
)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./CellRenderers')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
88
stories/ControlledTable.js
Normal file
88
stories/ControlledTable.js
Normal file
@ -0,0 +1,88 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
const data = _.map(_.range(5553), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
class Story extends React.Component {
|
||||
constructor () {
|
||||
super()
|
||||
this.state = {
|
||||
sorted: [],
|
||||
page: 0,
|
||||
pageSize: 10,
|
||||
expanded: {},
|
||||
resized: [],
|
||||
filtered: []
|
||||
}
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
pivotBy={['lastName']}
|
||||
filterable
|
||||
// Controlled Props
|
||||
sorted={this.state.sorted}
|
||||
page={this.state.page}
|
||||
pageSize={this.state.pageSize}
|
||||
expanded={this.state.expanded}
|
||||
resized={this.state.resized}
|
||||
filtered={this.state.filtered}
|
||||
// Callbacks
|
||||
onSortedChange={sorted => this.setState({sorted})}
|
||||
onPageChange={page => this.setState({page})}
|
||||
onPageSizeChange={(pageSize, page) => this.setState({page, pageSize})}
|
||||
onExpandedChange={expanded => this.setState({expanded})}
|
||||
onResizedChange={resized => this.setState({resized})}
|
||||
onFilteredChange={filtered => this.setState({filtered})}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<pre><code><strong>this.state ===</strong> {JSON.stringify(this.state, null, 2)}</code></pre>
|
||||
<br />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./ControlledTable')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
75
stories/CustomComponentProps.js
Normal file
75
stories/CustomComponentProps.js
Normal file
@ -0,0 +1,75 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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>
|
||||
<strong>Hey!</strong> Open your console! :)
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
getTdProps={(state, rowInfo, column, instance) => {
|
||||
return {
|
||||
onMouseEnter: e => console.log('Cell - onMouseEnter', {
|
||||
state,
|
||||
rowInfo,
|
||||
column,
|
||||
instance,
|
||||
event: e
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./CustomComponentProps')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
80
stories/CustomExpanderPosition.js
Normal file
80
stories/CustomExpanderPosition.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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',
|
||||
Footer: () => <div style={{textAlign: 'center'}}>First Name</div>
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
Footer: () => <div style={{textAlign: 'center'}}>Last Name</div>
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Footer: () => <div style={{textAlign: 'center'}}>Age</div>
|
||||
}]
|
||||
}, {
|
||||
Header: 'Expand',
|
||||
columns: [{
|
||||
expander: true,
|
||||
Header: () => (<strong>More</strong>),
|
||||
width: 65,
|
||||
Expander: ({isExpanded, ...rest}) => (
|
||||
<div>
|
||||
{isExpanded ? <span>⊙</span> : <span>⊕</span>}
|
||||
</div>
|
||||
),
|
||||
style: {cursor: 'pointer', fontSize: 25, padding: '0', textAlign: 'center', userSelect: 'none'},
|
||||
Footer: () => <span>♥</span>
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
SubComponent={() => <span>Hello</span>}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./CustomExpanderPosition')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
77
stories/CustomSorting.js
Normal file
77
stories/CustomSorting.js
Normal file
@ -0,0 +1,77 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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 (Sorted by Length, A-Z)',
|
||||
accessor: 'firstName',
|
||||
sortMethod: (a, b) => {
|
||||
if (a.length === b.length) {
|
||||
return a > b ? 1 : -1
|
||||
}
|
||||
return a.length > b.length ? 1 : -1
|
||||
}
|
||||
}, {
|
||||
Header: 'Last Name (Sorted in reverse, A-Z)',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
sortMethod: (a, b) => {
|
||||
if (a === b) {
|
||||
return 0
|
||||
}
|
||||
const aReverse = a.split('').reverse().join('')
|
||||
const bReverse = b.split('').reverse().join('')
|
||||
return aReverse > bReverse ? 1 : -1
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./CustomSorting')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
66
stories/CustomWidths.js
Normal file
66
stories/CustomWidths.js
Normal file
@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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',
|
||||
maxWidth: 200
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
width: 300
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
minWidth: 400
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./CustomWidths')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
67
stories/DefaultSorting.js
Normal file
67
stories/DefaultSorting.js
Normal file
@ -0,0 +1,67 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
defaultSorted={[{
|
||||
id: 'age',
|
||||
desc: true
|
||||
}]}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./DefaultSorting')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
69
stories/EditableTable.js
Normal file
69
stories/EditableTable.js
Normal file
@ -0,0 +1,69 @@
|
||||
import React from 'react'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
constructor (props, context) {
|
||||
super(props, context)
|
||||
this.renderEditable = this.renderEditable.bind(this)
|
||||
|
||||
this.state = {
|
||||
data: [
|
||||
{ firstName: 'Lucy', lastName: 'Marks' },
|
||||
{ firstName: 'Bejamin', lastName: 'Pike' }
|
||||
]
|
||||
}
|
||||
|
||||
this.columns = [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
Cell: this.renderEditable
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
Cell: this.renderEditable
|
||||
},
|
||||
{
|
||||
Header: 'Full Name',
|
||||
id: 'full',
|
||||
accessor: d => d.firstName + ' ' + d.lastName
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
renderEditable (cellInfo) {
|
||||
return (<div style={{ backgroundColor: '#fafafa' }} contentEditable suppressContentEditableWarning onBlur={(e) => {
|
||||
const data = [...this.state.data]
|
||||
data[cellInfo.index][cellInfo.column.id] = e.target.textContent
|
||||
this.setState({data: data})
|
||||
}}>{this.state.data[cellInfo.index][cellInfo.column.id]}</div>)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='table-wrap' style={{marginBottom: '20px'}}>
|
||||
<p>First two columns are editable just by clicking into them using the <code>contentEditable</code> attribute. Last column (Full Name) is computed from the first two.</p>
|
||||
<ReactTable
|
||||
data={this.state.data}
|
||||
columns={this.columns}
|
||||
defaultPageSize={2}
|
||||
showPageSizeOptions={false}
|
||||
showPagination={false}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./EditableTable')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
193
stories/Filtering.js
Normal file
193
stories/Filtering.js
Normal file
@ -0,0 +1,193 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import CodeHighlight from './components/codeHighlight'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
const data = _.map(_.range(5553), d => {
|
||||
return {
|
||||
firstName: namor.generate({words: 1, numLen: 0}),
|
||||
lastName: namor.generate({words: 1, numLen: 0}),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
this.state = {
|
||||
tableOptions: {
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
filterable: true,
|
||||
sortable: true,
|
||||
resizable: true
|
||||
},
|
||||
data: data
|
||||
}
|
||||
|
||||
this.setTableOption = this.setTableOption.bind(this)
|
||||
}
|
||||
|
||||
render () {
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
filterMethod: (filter, row) => (row[filter.id].startsWith(filter.value) && row[filter.id].endsWith(filter.value))
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
filterMethod: (filter, row) => (row[filter.id].includes(filter.value))
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}, {
|
||||
Header: 'Over 21',
|
||||
accessor: 'age',
|
||||
id: 'over',
|
||||
Cell: ({value}) => (value >= 21 ? 'Yes' : 'No'),
|
||||
filterMethod: (filter, row) => {
|
||||
if (filter.value === 'all') {
|
||||
return true
|
||||
}
|
||||
if (filter.value === 'true') {
|
||||
return row[filter.id] >= 21
|
||||
}
|
||||
return row[filter.id] < 21
|
||||
},
|
||||
Filter: ({filter, onChange}) => (
|
||||
<select
|
||||
onChange={event => onChange(event.target.value)}
|
||||
style={{width: '100%'}}
|
||||
value={filter ? filter.value : 'all'}
|
||||
>
|
||||
<option value='all' />
|
||||
<option value='true'>Can Drink</option>
|
||||
<option value='false'>Can't Drink</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{float: 'left'}}>
|
||||
<h1>Table Options</h1>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
Object.keys(this.state.tableOptions).map(optionKey => {
|
||||
const optionValue = this.state.tableOptions[optionKey]
|
||||
return (
|
||||
<tr key={optionKey}>
|
||||
<td>{optionKey}</td>
|
||||
<td style={{paddingLeft: 10, paddingTop: 5}}>
|
||||
<input type='checkbox'
|
||||
name={optionKey}
|
||||
checked={optionValue}
|
||||
onChange={this.setTableOption}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='table-wrap' style={{paddingLeft: 240}}>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
defaultFilterMethod={(filter, row) => (String(row[filter.id]) === filter.value)}
|
||||
{...this.state.tableOptions}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Custom Filters In This Example</h1>
|
||||
<p>The default filter for all columns of a table if it is not specified in the configuration is set to match
|
||||
on values that start with the filter text. Example: age.startsWith("2").</p>
|
||||
<p>This example overrides the default filter behavior by setting
|
||||
the <strong>defaultFilterMethod</strong> table option to match on values that are exactly equal to the
|
||||
filter text. Example: age == "23")</p>
|
||||
<p>Each column can also be customized with the column <strong>filterMethod</strong> option:</p>
|
||||
<p>In this example the firstName column filters on the value starting with and ending with the filter
|
||||
value.</p>
|
||||
<p>In this example the lastName column filters on the value including the filter value anywhere in its
|
||||
text.</p>
|
||||
<p>To completely override the filter that is shown, you can set the <strong>Filter</strong> column
|
||||
option. Using this option you can specify the JSX that is shown. The option is passed
|
||||
an <strong>onChange</strong> method that must be called with the value that you wan't to
|
||||
pass to the <strong>filterMethod</strong> option whenever the filter has changed.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
setTableOption (event) {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
tableOptions: {
|
||||
...this.state.tableOptions,
|
||||
[name]: value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const source = require('!raw-loader!./Filtering')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
91
stories/Footers.js
Normal file
91
stories/Footers.js
Normal file
@ -0,0 +1,91 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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',
|
||||
Footer: (
|
||||
<span><strong>Popular:</strong> {
|
||||
_.first(
|
||||
_.reduce(
|
||||
_.map(
|
||||
_.groupBy(
|
||||
data, d => d.firstName
|
||||
)
|
||||
),
|
||||
(a, b) => a.length > b.length ? a : b
|
||||
)
|
||||
).firstName}
|
||||
</span>
|
||||
)
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
Footer: (
|
||||
<span><strong>Longest:</strong> {
|
||||
_.reduce(
|
||||
_.map(
|
||||
_.groupBy(
|
||||
data, d => d.lastName
|
||||
),
|
||||
(d, key) => key
|
||||
),
|
||||
(a, b) => a.length > b.length ? a : b
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Footer: <span><strong>Average:</strong> {_.round(_.mean(_.map(data, d => d.age)))}</span>
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./Footers')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
136
stories/FunctionalRendering.js
Normal file
136
stories/FunctionalRendering.js
Normal file
@ -0,0 +1,136 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
import JSONTree from 'react-json-tree'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
const JSONtheme = {
|
||||
scheme: 'monokai',
|
||||
author: 'wimer hazenberg (http://www.monokai.nl)',
|
||||
base00: '#272822',
|
||||
base01: '#383830',
|
||||
base02: '#49483e',
|
||||
base03: '#75715e',
|
||||
base04: '#a59f85',
|
||||
base05: '#f8f8f2',
|
||||
base06: '#f5f4f1',
|
||||
base07: '#f9f8f5',
|
||||
base08: '#f92672',
|
||||
base09: '#fd971f',
|
||||
base0A: '#f4bf75',
|
||||
base0B: '#a6e22e',
|
||||
base0C: '#a1efe4',
|
||||
base0D: '#66d9ef',
|
||||
base0E: '#ae81ff',
|
||||
base0F: '#cc6633'
|
||||
}
|
||||
|
||||
class Story extends React.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',
|
||||
Footer: 'Footer'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
Footer: 'Footer'
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
Footer: 'Footer'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<strong>Functional rendering</strong> simply means that you have all of the building blocks to render your own React Table however you'd like.
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<strong>Decorating the standard table output</strong>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
>
|
||||
{(state, makeTable, instance) => {
|
||||
return (
|
||||
<div style={{
|
||||
background: '#ffcf00',
|
||||
borderRadius: '5px',
|
||||
overflow: 'hidden',
|
||||
padding: '5px'
|
||||
}}>
|
||||
<pre><code>state.allVisibleColumns === {JSON.stringify(state.allVisibleColumns, null, 4)}</code></pre>
|
||||
{makeTable()}
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</ReactTable>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<strong>Need more control? This is the entire table state and component instance at your disposal!</strong>
|
||||
<br />
|
||||
<br />
|
||||
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
>
|
||||
{(state, StandardTable, instance) => {
|
||||
return (
|
||||
<div>
|
||||
<JSONTree
|
||||
data={Object.assign({}, state, {children: 'function () {...}'})}
|
||||
theme={JSONtheme}
|
||||
invertTheme
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</ReactTable>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./FunctionalRendering')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
56
stories/NoDataText.js
Normal file
56
stories/NoDataText.js
Normal file
@ -0,0 +1,56 @@
|
||||
import React from 'react'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={[]}
|
||||
noDataText='Oh Noes!'
|
||||
// noDataText={() => 'Oh Noes!'} // Supports functions
|
||||
// noDataText={() => <span>Oh Noes!</span>} // Supports JSX / React Components
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./NoDataText')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
96
stories/OneHundredKRows.js
Normal file
96
stories/OneHundredKRows.js
Normal file
@ -0,0 +1,96 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const data = _.map(_.range(100000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
Aggregated: row => {
|
||||
return <span>{row.value} (avg)</span>
|
||||
}
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
className='-striped -highlight'
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./OneHundredKRows')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
73
stories/Pivoting.js
Normal file
73
stories/Pivoting.js
Normal file
@ -0,0 +1,73 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
Aggregated: row => {
|
||||
return <span>{row.value} (avg)</span>
|
||||
}
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals)
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
className='-striped -highlight'
|
||||
defaultPageSize={10}
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./Pivoting')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
131
stories/PivotingOptions.js
Normal file
131
stories/PivotingOptions.js
Normal file
@ -0,0 +1,131 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({words: 1, numLen: 0}),
|
||||
lastName: namor.generate({words: 1, numLen: 0}),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
PivotValue: ({value}) => <span style={{color: 'darkred'}}>{value}</span>
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName,
|
||||
PivotValue: ({value}) => <span style={{color: 'darkblue'}}>{value}</span>,
|
||||
Footer: () => <div style={{textAlign: 'center'}}><strong>Pivot Column Footer</strong></div>
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => {
|
||||
return _.round(_.mean(vals))
|
||||
},
|
||||
Aggregated: row => <span>{row.value} (avg)</span>
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals),
|
||||
filterable: false
|
||||
}]
|
||||
}, {
|
||||
pivot: true,
|
||||
Header: () => <strong>Overridden Pivot Column Header Group</strong>
|
||||
}, {
|
||||
expander: true
|
||||
}]
|
||||
|
||||
const subtableColumns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName'
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
className='-striped -highlight'
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
defaultSorted={[{id: 'firstName', desc: false}, {id: 'lastName', desc: true}]}
|
||||
collapseOnSortingChange={false}
|
||||
filterable
|
||||
ExpanderComponent={({isExpanded, ...rest}) => (
|
||||
isExpanded ? <span> ➘ </span> : <span> ➙ </span>
|
||||
)}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={subtableColumns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./PivotingOptions')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
99
stories/PivotingSubComponents.js
Normal file
99
stories/PivotingSubComponents.js
Normal file
@ -0,0 +1,99 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const data = _.map(_.range(1000), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
visits: Math.floor(Math.random() * 100)
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
aggregate: vals => _.round(_.mean(vals)),
|
||||
Aggregated: row => {
|
||||
return <span>{row.value} (avg)</span>
|
||||
},
|
||||
filterMethod: (filter, row) => (filter.value === `${row[filter.id]} (avg)`)
|
||||
}, {
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
aggregate: vals => _.sum(vals),
|
||||
filterable: false
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
className='-striped -highlight'
|
||||
pivotBy={['firstName', 'lastName']}
|
||||
filterable
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./PivotingSubComponents')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
120
stories/ServerSide.js
Normal file
120
stories/ServerSide.js
Normal file
@ -0,0 +1,120 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
const rawData = _.map(_.range(3424), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
// Now let's mock the server. It's job is simple: use the table model to sort and return the page data
|
||||
const requestData = (pageSize, page, sorted, filtered) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// On the server, you'll likely use SQL or noSQL or some other query language to do this.
|
||||
// For this mock, we'll just use lodash
|
||||
let filteredData = rawData
|
||||
if (filtered.length) {
|
||||
filteredData = filtered.reduce(
|
||||
(filteredSoFar, nextFilter) => {
|
||||
return filteredSoFar.filter(
|
||||
(row) => {
|
||||
return (row[nextFilter.id] + '').includes(nextFilter.value)
|
||||
})
|
||||
}
|
||||
, filteredData)
|
||||
}
|
||||
const sortedData = _.orderBy(filteredData, sorted.map(sort => {
|
||||
return row => {
|
||||
if (row[sort.id] === null || row[sort.id] === undefined) {
|
||||
return -Infinity
|
||||
}
|
||||
return typeof row[sort.id] === 'string' ? row[sort.id].toLowerCase() : row[sort.id]
|
||||
}
|
||||
}), sorted.map(d => d.desc ? 'desc' : 'asc'))
|
||||
|
||||
// Be sure to send back the rows to be displayed and any other pertinent information, like how many pages there are total.
|
||||
const res = {
|
||||
rows: sortedData.slice(pageSize * page, (pageSize * page) + pageSize),
|
||||
pages: Math.ceil(filteredData.length / pageSize)
|
||||
}
|
||||
|
||||
// Here we'll simulate a server response with 500ms of delay.
|
||||
setTimeout(() => resolve(res), 500)
|
||||
})
|
||||
}
|
||||
|
||||
class Story extends React.Component {
|
||||
constructor () {
|
||||
super()
|
||||
this.state = {
|
||||
data: [],
|
||||
pages: null,
|
||||
loading: true
|
||||
}
|
||||
this.fetchData = this.fetchData.bind(this)
|
||||
}
|
||||
fetchData (state, instance) {
|
||||
// Whenever the table model changes, or the user sorts or changes pages, this method gets called and passed the current table model.
|
||||
// You can set the `loading` prop of the table to true to use the built-in one or show you're own loading bar if you want.
|
||||
this.setState({loading: true})
|
||||
// Request the data however you want. Here, we'll use our mocked service we created earlier
|
||||
requestData(state.pageSize, state.page, state.sorted, state.filtered)
|
||||
.then((res) => {
|
||||
// Now just get the rows of data to your React Table (and update anything else like total pages or loading)
|
||||
this.setState({
|
||||
data: res.rows,
|
||||
pages: res.pages,
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
columns={[{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}, {
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]}
|
||||
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
|
||||
defaultPageSize={10}
|
||||
filterable
|
||||
data={this.state.data} // Set the rows to be displayed
|
||||
pages={this.state.pages} // Display the total number of pages
|
||||
loading={this.state.loading} // Display the loading overlay when we need it
|
||||
onFetchData={this.fetchData} // Request new data when things change
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./ServerSide')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
69
stories/Simple.js
Normal file
69
stories/Simple.js
Normal file
@ -0,0 +1,69 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.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),
|
||||
children: _.map(_.range(10), d => {
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./Simple')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
145
stories/SubComponents.js
Normal file
145
stories/SubComponents.js
Normal file
@ -0,0 +1,145 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
const data = _.map(_.range(5553), d => {
|
||||
return {
|
||||
firstName: namor.generate({words: 1, numLen: 0}),
|
||||
lastName: namor.generate({words: 1, numLen: 0}),
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
|
||||
this.state = {
|
||||
tableOptions: {
|
||||
loading: false,
|
||||
showPagination: true,
|
||||
showPageSizeOptions: true,
|
||||
showPageJump: true,
|
||||
collapseOnSortingChange: true,
|
||||
collapseOnPageChange: true,
|
||||
collapseOnDataChange: true,
|
||||
freezeWhenExpanded: false,
|
||||
filterable: false,
|
||||
sortable: true,
|
||||
resizable: true
|
||||
},
|
||||
data: data
|
||||
}
|
||||
|
||||
this.setTableOption = this.setTableOption.bind(this)
|
||||
}
|
||||
render () {
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName'
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
id: 'lastName',
|
||||
accessor: d => d.lastName
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{float: 'left'}}>
|
||||
<h1>Table Options</h1>
|
||||
<table>
|
||||
<tbody>
|
||||
{
|
||||
Object.keys(this.state.tableOptions).map(optionKey => {
|
||||
const optionValue = this.state.tableOptions[optionKey]
|
||||
return (
|
||||
<tr key={optionKey}>
|
||||
<td>{optionKey}</td>
|
||||
<td style={{paddingLeft: 10, paddingTop: 5}}>
|
||||
<input type='checkbox'
|
||||
name={optionKey}
|
||||
checked={optionValue}
|
||||
onChange={this.setTableOption}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='table-wrap' style={{paddingLeft: 240}}>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
{...this.state.tableOptions}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>You can put any component you want here, even another React Table!</em>
|
||||
<br />
|
||||
<br />
|
||||
<ReactTable
|
||||
data={this.state.data}
|
||||
columns={columns}
|
||||
defaultPageSize={3}
|
||||
showPagination={false}
|
||||
SubComponent={(row) => {
|
||||
return (
|
||||
<div style={{padding: '20px'}}>
|
||||
<em>It even has access to the row data: </em>
|
||||
<CodeHighlight>{() => JSON.stringify(row, null, 2)}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
setTableOption (event) {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
tableOptions: {
|
||||
...this.state.tableOptions,
|
||||
[name]: value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./SubComponents')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
77
stories/SubRows.js
Normal file
77
stories/SubRows.js
Normal file
@ -0,0 +1,77 @@
|
||||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import namor from 'namor'
|
||||
|
||||
import ReactTable from '../src/index'
|
||||
|
||||
class Story extends React.Component {
|
||||
render () {
|
||||
const data = _.map(_.range(5553), d => {
|
||||
const children = _.map(_.range(10), d => {
|
||||
const grandChildren = _.map(_.range(10), d => {
|
||||
return {
|
||||
age: Math.floor(Math.random() * 30)
|
||||
}
|
||||
})
|
||||
return {
|
||||
firstName: namor.generate({ words: 1, numLen: 0 }),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
children: grandChildren
|
||||
}
|
||||
})
|
||||
return {
|
||||
lastName: namor.generate({ words: 1, numLen: 0 }),
|
||||
firstName: children.map(d => d.firstName),
|
||||
age: Math.floor(Math.random() * 30),
|
||||
children
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [{
|
||||
Header: 'Name',
|
||||
columns: [{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
expander: true
|
||||
}, {
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName'
|
||||
}]
|
||||
}, {
|
||||
Header: 'Info',
|
||||
columns: [{
|
||||
Header: 'Age',
|
||||
accessor: 'age'
|
||||
}]
|
||||
}]
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='table-wrap'>
|
||||
<ReactTable
|
||||
className='-striped -highlight'
|
||||
data={data}
|
||||
columns={columns}
|
||||
defaultPageSize={10}
|
||||
subRowsKey='children'
|
||||
/>
|
||||
</div>
|
||||
<div style={{textAlign: 'center'}}>
|
||||
<br />
|
||||
<em>Tip: Hold shift when sorting to multi-sort!</em>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Source Code
|
||||
const CodeHighlight = require('./components/codeHighlight').default
|
||||
const source = require('!raw-loader!./SubRows')
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
<Story />
|
||||
<CodeHighlight>{() => source}</CodeHighlight>
|
||||
</div>
|
||||
)
|
||||
21
stories/components/codeHighlight.js
Normal file
21
stories/components/codeHighlight.js
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react'
|
||||
import '../utils/prism'
|
||||
|
||||
export default React.createClass({
|
||||
render () {
|
||||
const { language, children } = this.props
|
||||
return (
|
||||
<pre>
|
||||
<code className={'language-' + (language || 'jsx')}>
|
||||
{children()}
|
||||
</code>
|
||||
</pre>
|
||||
)
|
||||
},
|
||||
componentDidMount () {
|
||||
window.Prism.highlightAll()
|
||||
},
|
||||
componentDidUpdate () {
|
||||
window.Prism.highlightAll()
|
||||
}
|
||||
})
|
||||
138
stories/utils/prism.css
Normal file
138
stories/utils/prism.css
Normal file
@ -0,0 +1,138 @@
|
||||
/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #a67f59;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
9
stories/utils/prism.js
Normal file
9
stories/utils/prism.js
Normal file
File diff suppressed because one or more lines are too long
@ -2,9 +2,9 @@ const webpack = require('webpack')
|
||||
module.exports = {
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
filename: './react-story.js',
|
||||
filename: './react-table.js',
|
||||
libraryTarget: 'umd',
|
||||
library: 'ReactStory'
|
||||
library: 'ReactTable'
|
||||
},
|
||||
externals: {
|
||||
react: {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user