diff --git a/packages/__docs__/globals.js b/packages/__docs__/globals.js
index 4f3de7bc12..b6f1c53769 100644
--- a/packages/__docs__/globals.js
+++ b/packages/__docs__/globals.js
@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React, { useEffect, useState, useRef } from 'react'
+import React, { useContext, useEffect, useMemo, useState, useRef } from 'react'
import ReactDOM from 'react-dom'
import { LoremIpsum } from 'lorem-ipsum'
@@ -82,7 +82,9 @@ const globals = {
placeholderImage,
React,
ReactDOM,
+ useContext,
useEffect,
+ useMemo,
useState,
useRef,
additionalPrimitives,
diff --git a/packages/ui-table/src/Table/README.md b/packages/ui-table/src/Table/README.md
index 8715f77180..0067ab536a 100644
--- a/packages/ui-table/src/Table/README.md
+++ b/packages/ui-table/src/Table/README.md
@@ -11,33 +11,112 @@ In stacked layout, column header is rendered in each cell, but not in row header
> exceed the bounds of the table cell, use `fixed` or `stacked`, together with the [Text](#Text) component:
> `[long string]`.
-```javascript
----
-type: example
----
-class Example extends React.Component {
- state = {
- layout: 'auto',
- hover: false,
- }
+- ```javascript
+ class Example extends React.Component {
+ state = {
+ layout: 'auto',
+ hover: false
+ }
- handleChange = (field, value) => {
- this.setState({
- [field]: value,
- })
+ handleChange = (field, value) => {
+ this.setState({
+ [field]: value
+ })
+ }
+
+ renderOptions() {
+ const { layout, hover } = this.state
+
+ return (
+
+
+ this.handleChange('layout', value)}
+ >
+
+
+
+
+
+
+ this.handleChange('hover', !hover)}
+ />
+
+
+ )
+ }
+
+ render() {
+ const { layout, hover } = this.state
+
+ return (
+
+ {this.renderOptions()}
+
+
+
+ Rank
+ Title
+ Year
+ Rating
+
+
+
+
+ 1
+ The Shawshank Redemption
+ 1994
+ 9.3
+
+
+ 2
+ The Godfather
+ 1972
+ 9.2
+
+
+ 3
+ The Godfather: Part II
+ 1974
+ 9.0
+
+
+
+
+ )
+ }
}
- renderOptions () {
- const { layout, hover } = this.state
+ render()
+ ```
- return (
+- ```javascript
+ const Example = () => {
+ const [layout, setLayout] = useState('auto')
+ const [hover, setHover] = useState(false)
+
+ const handleChange = (field, value) => {
+ if (field === 'layout') {
+ setLayout(value)
+ } else if (field === 'hover') {
+ setHover(value)
+ }
+ }
+
+ const renderOptions = () => (
this.handleChange('layout', value)}
+ onChange={(e, value) => handleChange('layout', value)}
>
@@ -48,24 +127,16 @@ class Example extends React.Component {
this.handleChange('hover', !hover)}
+ onChange={(e, value) => handleChange('hover', !hover)}
/>
)
- }
-
- render() {
- const { layout, hover } = this.state
return (
- {this.renderOptions()}
-
+ {renderOptions()}
+
Rank
@@ -98,41 +169,154 @@ class Example extends React.Component {
)
}
-}
-render()
-```
+ render()
+ ```
### Column width and alignment
Each column (`ColHeader`) can have a custom width, and each cell (`ColHeader`, `RowHeader` or `Cell`)
can be aligned differently.
-```javascript
----
-type: example
----
-class Example extends React.Component {
- render() {
- const { headers, rows } = this.props
+- ```javascript
+ class Example extends React.Component {
+ render() {
+ const { headers, rows } = this.props
+
+ return (
+
+ {({ layout }) => (
+
+
+
+
+ {(headers || []).map(({ id, text, width, textAlign }) => (
+
+ {text}
+
+ ))}
+
+
+
+ {rows.map((row) => (
+
+ {headers.map(({ id, renderCell, textAlign }) => (
+
+ {renderCell ? renderCell(row[id], layout) : row[id]}
+
+ ))}
+
+ ))}
+
+
+
+ )}
+
+ )
+ }
+ }
+ const renderSummary = (summary, layout) =>
+ layout === 'stacked' ? (
+ summary
+ ) : (
+
+ {summary}
+
+ )
+
+ render(
+
+ )
+ ```
+
+- ```javascript
+ const Example = ({ headers, rows }) => {
return (
{({ layout }) => (
-
+
{(headers || []).map(({ id, text, width, textAlign }) => (
@@ -144,7 +328,7 @@ class Example extends React.Component {
>
{text}
- ))}
+ ))}
@@ -167,74 +351,74 @@ class Example extends React.Component {
)
}
-}
-
-const renderSummary = (summary, layout) => (layout === 'stacked')
- ? summary
- : (
-
- {summary}
-
- )
-render(
-
-)
-```
+ const renderSummary = (summary, layout) =>
+ layout === 'stacked' ? (
+ summary
+ ) : (
+
+ {summary}
+
+ )
+
+ render(
+
+ )
+ ```
### A sortable table using our Responsive component
@@ -242,56 +426,293 @@ Resize the window to see how column headers transition into a `Select` for sorti
By default, the options in the `Select` for sorting in stacked layout are generated from the `id` property of the `Table.ColHeader` components. If you want to display custom strings, use the `stackedSortByLabel` property.
-```javascript
----
-type: example
----
-class SortableTable extends React.Component {
- constructor (props) {
- super(props)
- const { headers } = props
+- ```javascript
+ class SortableTable extends React.Component {
+ constructor(props) {
+ super(props)
+ const { headers } = props
+
+ const initialColWidth = {}
+ headers.forEach((header) => {
+ initialColWidth[header.id] = 'start'
+ })
+
+ this.state = {
+ sortBy: headers && headers[0] && headers[0].id,
+ ascending: true,
+ colTextAligns: initialColWidth
+ }
+ }
+
+ handleSort = (event, { id }) => {
+ const { sortBy, ascending } = this.state
+
+ if (id === sortBy) {
+ this.setState({
+ ascending: !ascending
+ })
+ } else {
+ this.setState({
+ sortBy: id,
+ ascending: true
+ })
+ }
+ }
+
+ handleColTextAlignChange(id, value) {
+ this.setState((state) => ({
+ colTextAligns: {
+ ...state.colTextAligns,
+ [id]: value
+ }
+ }))
+ }
+
+ renderHeaderRow(direction) {
+ const { headers } = this.props
+ const { colTextAligns, sortBy } = this.state
+
+ return (
+
+ {(headers || []).map(({ id, text, width }) => (
+
+ {text}
+
+ ))}
+
+ )
+ }
+ renderOptions() {
+ const { headers } = this.props
+ const { colTextAligns } = this.state
+
+ return (
+
+
+ {this.renderHeaderRow()}
+
+
+ {Object.entries(colTextAligns).map(([headerId, textAlign]) => {
+ return (
+
+
+ Set text-align for column: {headerId}
+
+ }
+ name={`columnTextAlign_${headerId}`}
+ value={textAlign}
+ margin="0 0 small"
+ size="small"
+ onChange={(e, value) =>
+ this.handleColTextAlignChange(headerId, value)
+ }
+ >
+
+
+
+
+
+ )
+ })}
+
+
+
+
+ )
+ }
+
+ render() {
+ const { caption, headers, rows } = this.props
+ const { sortBy, ascending, colTextAligns } = this.state
+ const direction = ascending ? 'ascending' : 'descending'
+ const sortedRows = [...(rows || [])].sort((a, b) => {
+ if (a[sortBy] < b[sortBy]) {
+ return -1
+ }
+ if (a[sortBy] > b[sortBy]) {
+ return 1
+ }
+ return 0
+ })
+
+ if (!ascending) {
+ sortedRows.reverse()
+ }
+ return (
+
+ {(props) => (
+
+ {props.layout !== 'stacked' && (
+
+ {this.renderOptions()}
+
+ )}
+
+
+
+ {this.renderHeaderRow(direction)}
+
+
+ {sortedRows.map((row) => (
+
+ {headers.map(({ id, renderCell }) => (
+
+ {renderCell ? renderCell(row[id]) : row[id]}
+
+ ))}
+
+ ))}
+
+
+
document.getElementById('flash-messages')}
+ liveRegionPoliteness="polite"
+ screenReaderOnly
+ >
+ {`Sorted by ${sortBy} in ${direction} order`}
+
+
+ )}
+
+ )
+ }
+ }
+
+ render(
+ rating.toFixed(1)
+ }
+ ]}
+ rows={[
+ {
+ id: '1',
+ rank: 1,
+ title: 'The Shawshank Redemption',
+ year: 1994,
+ rating: 9.3
+ },
+ {
+ id: '2',
+ rank: 2,
+ title: 'The Godfather',
+ year: 1972,
+ rating: 9.2
+ },
+ {
+ id: '3',
+ rank: 3,
+ title: 'The Godfather: Part II',
+ year: 1974,
+ rating: 9.0
+ },
+ {
+ id: '4',
+ rank: 4,
+ title: 'The Dark Knight',
+ year: 2008,
+ rating: 9.0
+ },
+ {
+ id: '5',
+ rank: 5,
+ title: '12 Angry Men',
+ year: 1957,
+ rating: 8.9
+ }
+ ]}
+ />
+ )
+ ```
+
+- ```javascript
+ const SortableTable = ({ caption, headers, rows }) => {
const initialColWidth = {}
headers.forEach((header) => {
initialColWidth[header.id] = 'start'
})
- this.state = {
- sortBy: headers && headers[0] && headers[0].id,
- ascending: true,
- colTextAligns: initialColWidth
- }
- }
+ const [sortBy, setSortBy] = useState(headers && headers[0] && headers[0].id)
+ const [ascending, setAscending] = useState(true)
+ const [colTextAligns, setColTextAligns] = useState(initialColWidth)
- handleSort = (event, { id }) => {
- const { sortBy, ascending } = this.state
+ const sortedRows = useMemo(() => {
+ if (!sortBy) return rows
- if (id === sortBy) {
- this.setState({
- ascending: !ascending,
- })
- } else {
- this.setState({
- sortBy: id,
- ascending: true,
+ const sorted = [...rows].sort((a, b) => {
+ return a[sortBy] > b[sortBy] ? 1 : a[sortBy] < b[sortBy] ? -1 : 0
})
- }
- }
- handleColTextAlignChange(id, value) {
- this.setState(state => ({
- colTextAligns: {
- ...state.colTextAligns,
- [id]: value
+ return ascending ? sorted : sorted.reverse()
+ }, [sortBy, ascending, rows])
+
+ const handleSort = (event, { id }) => {
+ if (id === sortBy) {
+ setAscending(!ascending)
+ } else {
+ setSortBy(id)
+ setAscending(true)
}
- }))
- }
+ }
- renderHeaderRow(direction) {
- const { headers } = this.props
- const { colTextAligns , sortBy } = this.state
+ const handleColTextAlignChange = (id, value) => {
+ setColTextAligns((prevState) => ({
+ ...prevState,
+ [id]: value
+ }))
+ }
- return (
+ const renderHeaderRow = (direction) => (
{(headers || []).map(({ id, text, width }) => (
@@ -310,126 +731,64 @@ class SortableTable extends React.Component {
))}
)
- }
-
- renderOptions () {
- const { headers } = this.props
- const { colTextAligns } = this.state
- return (
+ const renderOptions = () => (
-
-
- {this.renderHeaderRow()}
-
+
+ {renderHeaderRow()}
- {Object.entries(colTextAligns).map(([headerId, textAlign]) => {
- return (
- header.id === headerId).width}
+ {Object.entries(colTextAligns).map(([headerId, textAlign]) => (
+
+
+ Set text-align for column: {headerId}
+
+ }
+ name={`columnTextAlign_${headerId}`}
+ value={textAlign}
+ margin="0 0 small"
+ size="small"
+ onChange={(e, value) =>
+ handleColTextAlignChange(headerId, value)
+ }
>
-
- Set text-align for column: {headerId}
-
- }
- name={`columnTextAlign_${headerId}`}
- value={textAlign}
- margin="0 0 small"
- size="small"
- onChange={
- (e, value) => this.handleColTextAlignChange(headerId, value)
- }
- >
-
-
-
-
-
- )
- })}
+
+
+
+
+
+ ))}
)
- return (
-
-
- {Object.entries(colTextAligns).map(([headerId, textAlign]) => {
- return (
- header.id === headerId).width}
- >
-
- Column {headerId}textAlign
-
- }
- name={`Column "${headerId}" textAlign`}
- value={textAlign}
- margin="0 0 small"
- size="small"
- onChange={
- (e, value) => this.handleColTextAlignChange(headerId, value)
- }
- >
-
-
-
-
-
- )
- })}
-
-
- )
- }
-
- render() {
- const { caption, headers, rows } = this.props
- const { sortBy, ascending, colTextAligns } = this.state
const direction = ascending ? 'ascending' : 'descending'
- const sortedRows = [...(rows || [])].sort((a, b) => {
- if (a[sortBy] < b[sortBy]) {
- return -1
- }
- if (a[sortBy] > b[sortBy]) {
- return 1
- }
- return 0
- })
- if (!ascending) {
- sortedRows.reverse()
- }
return (
{(props) => (
{props.layout !== 'stacked' && (
- {this.renderOptions()}
+ {renderOptions()}
)}
@@ -438,7 +797,7 @@ class SortableTable extends React.Component {
{...props}
>
- {this.renderHeaderRow(direction)}
+ {renderHeaderRow(direction)}
{sortedRows.map((row) => (
@@ -464,118 +823,446 @@ class SortableTable extends React.Component {
)
}
-}
-
-render(
- rating.toFixed(1),
- },
- ]}
- rows={[
- {
- id: '1',
- rank: 1,
- title: 'The Shawshank Redemption',
- year: 1994,
- rating: 9.3,
- },
- {
- id: '2',
- rank: 2,
- title: 'The Godfather',
- year: 1972,
- rating: 9.2,
- },
- {
- id: '3',
- rank: 3,
- title: 'The Godfather: Part II',
- year: 1974,
- rating: 9.0,
- },
- {
- id: '4',
- rank: 4,
- title: 'The Dark Knight',
- year: 2008,
- rating: 9.0,
- },
- {
- id: '5',
- rank: 5,
- title: '12 Angry Men',
- year: 1957,
- rating: 8.9,
- },
- ]}
- />
-)
-```
+
+ render(
+ rating.toFixed(1)
+ }
+ ]}
+ rows={[
+ {
+ id: '1',
+ rank: 1,
+ title: 'The Shawshank Redemption',
+ year: 1994,
+ rating: 9.3
+ },
+ {
+ id: '2',
+ rank: 2,
+ title: 'The Godfather',
+ year: 1972,
+ rating: 9.2
+ },
+ {
+ id: '3',
+ rank: 3,
+ title: 'The Godfather: Part II',
+ year: 1974,
+ rating: 9.0
+ },
+ {
+ id: '4',
+ rank: 4,
+ title: 'The Dark Knight',
+ year: 2008,
+ rating: 9.0
+ },
+ {
+ id: '5',
+ rank: 5,
+ title: '12 Angry Men',
+ year: 1957,
+ rating: 8.9
+ }
+ ]}
+ />
+ )
+ ```
### A sortable table with selection and pagination
The composition order is important. `SelectableTable` -> `PaginatedTable` -> `SortableTable`, so
that selection does not re-paginate or re-sort the table, and pagination does not re-sort the table.
-```javascript
----
-type: example
----
-class SelectableTable extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- selected: new Set()
+- ```javascript
+ class SelectableTable extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = {
+ selected: new Set()
+ }
+ }
+
+ handleSelectAll = (allSelected) => {
+ const { rowIds } = this.props
+
+ this.setState({
+ selected: allSelected ? new Set() : new Set(rowIds)
+ })
+ }
+
+ handleSelectRow = (rowSelected, rowId) => {
+ const { selected } = this.state
+ const copy = new Set(selected)
+ if (rowSelected) {
+ copy.delete(rowId)
+ } else {
+ copy.add(rowId)
+ }
+
+ this.setState({
+ selected: copy
+ })
+ }
+
+ render() {
+ const { caption, headers, rows, onSort, sortBy, ascending, rowIds } =
+ this.props
+ const { selected } = this.state
+ const allSelected =
+ selected.size > 0 && rowIds.every((id) => selected.has(id))
+ const someSelected = selected.size > 0 && !allSelected
+ const direction = ascending ? 'ascending' : 'descending'
+
+ return (
+
+ {(props) => (
+
+
+ {`${selected.size} of ${rowIds.length} selected`}
+
+
+ Sort by
+ }
+ >
+
+
+ Select all
+ }
+ onChange={() => this.handleSelectAll(allSelected)}
+ checked={allSelected}
+ indeterminate={someSelected}
+ />
+
+ {(headers || []).map(({ id, text, width }) => (
+
+ {text}
+
+ ))}
+
+
+
+ {(rows || []).map((row) => {
+ const rowSelected = selected.has(row.id)
+
+ return (
+
+
+
+ Select row
+
+ }
+ onChange={() =>
+ this.handleSelectRow(rowSelected, row.id)
+ }
+ checked={rowSelected}
+ />
+
+ {(headers || []).map(({ id, renderCell }) => (
+
+ {renderCell ? renderCell(row[id]) : row[id]}
+
+ ))}
+
+ )
+ })}
+
+
+
document.getElementById('flash-messages')}
+ liveRegionPoliteness="polite"
+ screenReaderOnly
+ >
+ {`${selected.size} of ${rowIds.length} selected`}
+
+
+ )}
+
+ )
}
}
- handleSelectAll = (allSelected) => {
- const { rowIds } = this.props
+ class PaginatedTable extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = {
+ page: 0
+ }
+ }
- this.setState({
- selected: allSelected ? new Set() : new Set(rowIds),
- })
+ handleClick = (page) => {
+ this.setState({
+ page
+ })
+ }
+
+ handleSort = (event, options) => {
+ const { onSort } = this.props
+
+ this.setState({
+ page: 0
+ })
+ onSort(event, options)
+ }
+
+ render() {
+ const { caption, headers, rows, sortBy, ascending, perPage } = this.props
+ const { page } = this.state
+ const startIndex = page * perPage
+ const slicedRows = rows.slice(startIndex, startIndex + perPage)
+ const pageCount = perPage && Math.ceil(rows.length / perPage)
+
+ return (
+
+
row.id)}
+ />
+ {pageCount > 1 && (
+
+ {Array.from(Array(pageCount), (item, index) => (
+ this.handleClick(index)}
+ current={index === page}
+ >
+ {index + 1}
+
+ ))}
+
+ )}
+ document.getElementById('flash-messages')}
+ liveRegionPoliteness="polite"
+ screenReaderOnly
+ >
+ {`Table page ${page + 1} of ${pageCount}`}
+
+
+ )
+ }
}
- handleSelectRow = (rowSelected, rowId) => {
- const { selected } = this.state
- const copy = new Set(selected)
- if (rowSelected) {
- copy.delete(rowId)
- } else {
- copy.add(rowId)
+ class SortableTable extends React.Component {
+ constructor(props) {
+ super(props)
+ const { headers } = props
+
+ this.state = {
+ sortBy: headers && headers[0] && headers[0].id,
+ ascending: true
+ }
}
- this.setState({
- selected: copy,
- })
+ handleSort = (event, { id }) => {
+ const { sortBy, ascending } = this.state
+
+ if (id === sortBy) {
+ this.setState({
+ ascending: !ascending
+ })
+ } else {
+ this.setState({
+ sortBy: id,
+ ascending: true
+ })
+ }
+ }
+
+ render() {
+ const { caption, headers, rows, perPage } = this.props
+ const { sortBy, ascending } = this.state
+ const sortedRows = [...rows].sort((a, b) => {
+ if (a[sortBy] < b[sortBy]) {
+ return -1
+ }
+ if (a[sortBy] > b[sortBy]) {
+ return 1
+ }
+ return 0
+ })
+
+ if (!ascending) {
+ sortedRows.reverse()
+ }
+ return (
+
+
+
document.getElementById('flash-messages')}
+ liveRegionPoliteness="polite"
+ screenReaderOnly
+ >
+ {`Sorted by ${sortBy} in ${
+ ascending ? 'ascending' : 'descending'
+ } order`}
+
+
+ )
+ }
}
- render() {
- const { caption, headers, rows, onSort, sortBy, ascending, rowIds } = this.props
- const { selected } = this.state
- const allSelected = selected.size > 0 && rowIds.every((id) => selected.has(id))
+ const renderRating = (rating) => (
+
+ )
+
+ render(
+
+ )
+ ```
+
+- ```javascript
+ const SelectableTable = ({
+ caption,
+ headers,
+ rows,
+ onSort,
+ sortBy,
+ ascending,
+ rowIds
+ }) => {
+ const [selected, setSelected] = useState(new Set())
+
+ const handleSelectAll = (allSelected) => {
+ setSelected(allSelected ? new Set() : new Set(rowIds))
+ }
+
+ const handleSelectRow = (rowSelected, rowId) => {
+ const copy = new Set(selected)
+ if (rowSelected) {
+ copy.delete(rowId)
+ } else {
+ copy.add(rowId)
+ }
+ setSelected(copy)
+ }
+
+ const allSelected =
+ selected.size > 0 && rowIds.every((id) => selected.has(id))
const someSelected = selected.size > 0 && !allSelected
const direction = ascending ? 'ascending' : 'descending'
@@ -583,47 +1270,49 @@ class SelectableTable extends React.Component {
{(props) => (
-
+
{`${selected.size} of ${rowIds.length} selected`}
- Sort by}>
+ Sort by
+ }
+ >
Select all}
- onChange={() => this.handleSelectAll(allSelected)}
+ label={
+ Select all
+ }
+ onChange={() => handleSelectAll(allSelected)}
checked={allSelected}
indeterminate={someSelected}
/>
{(headers || []).map(({ id, text, width }) => (
-
- {text}
-
- ))}
+
+ {text}
+
+ ))}
@@ -634,8 +1323,12 @@ class SelectableTable extends React.Component {
Select row}
- onChange={() => this.handleSelectRow(rowSelected, row.id)}
+ label={
+
+ Select row
+
+ }
+ onChange={() => handleSelectRow(rowSelected, row.id)}
checked={rowSelected}
/>
@@ -661,34 +1354,27 @@ class SelectableTable extends React.Component {
)
}
-}
-class PaginatedTable extends React.Component {
- constructor(props) {
- super(props)
- this.state = {
- page: 0,
+ const PaginatedTable = ({
+ caption,
+ headers,
+ rows,
+ onSort,
+ sortBy,
+ ascending,
+ perPage
+ }) => {
+ const [page, setPage] = useState(0)
+
+ const handleClick = (page) => {
+ setPage(page)
}
- }
-
- handleClick = (page) => {
- this.setState({
- page,
- })
- }
-
- handleSort = (event, options) => {
- const { onSort } = this.props
- this.setState({
- page: 0,
- })
- onSort(event, options)
- }
+ const handleSort = (event, options) => {
+ setPage(0)
+ onSort(event, options)
+ }
- render() {
- const { caption, headers, rows, sortBy, ascending, perPage } = this.props
- const { page } = this.state
const startIndex = page * perPage
const slicedRows = rows.slice(startIndex, startIndex + perPage)
const pageCount = perPage && Math.ceil(rows.length / perPage)
@@ -699,22 +1385,22 @@ class PaginatedTable extends React.Component {
caption={caption}
headers={headers}
rows={slicedRows}
- onSort={this.handleSort}
+ onSort={handleSort}
sortBy={sortBy}
ascending={ascending}
rowIds={rows.map((row) => row.id)}
/>
{pageCount > 1 && (
{Array.from(Array(pageCount), (item, index) => (
this.handleClick(index)}
+ onClick={() => handleClick(index)}
current={index === page}
>
{index + 1}
@@ -732,57 +1418,37 @@ class PaginatedTable extends React.Component {
)
}
-}
-class SortableTable extends React.Component {
- constructor (props) {
- super(props)
- const { headers } = props
-
- this.state = {
- sortBy: headers && headers[0] && headers[0].id,
- ascending: true,
- }
- }
+ const SortableTable = ({ caption, headers, rows, perPage }) => {
+ const [sortBy, setSortBy] = useState(headers && headers[0] && headers[0].id)
+ const [ascending, setAscending] = useState(true)
- handleSort = (event, { id }) => {
- const { sortBy, ascending } = this.state
+ const sortedRows = useMemo(() => {
+ if (!sortBy) return rows
- if (id === sortBy) {
- this.setState({
- ascending: !ascending,
- })
- } else {
- this.setState({
- sortBy: id,
- ascending: true,
+ const sorted = [...rows].sort((a, b) => {
+ return a[sortBy] > b[sortBy] ? 1 : a[sortBy] < b[sortBy] ? -1 : 0
})
- }
- }
- render() {
- const { caption, headers, rows, perPage } = this.props
- const { sortBy, ascending } = this.state
- const sortedRows = [...rows].sort((a, b) => {
- if (a[sortBy] < b[sortBy]) {
- return -1
- }
- if (a[sortBy] > b[sortBy]) {
- return 1
- }
- return 0
- })
+ return ascending ? sorted : sorted.reverse()
+ }, [sortBy, ascending, rows])
- if (!ascending) {
- sortedRows.reverse()
+ const handleSort = (event, { id }) => {
+ if (id === sortBy) {
+ setAscending(!ascending)
+ } else {
+ setSortBy(id)
+ setAscending(true)
+ }
}
+
return (
- {`Sorted by ${sortBy} in ${ascending ? 'ascending' : 'descending'} order`}
+ {`Sorted by ${sortBy} in ${
+ ascending ? 'ascending' : 'descending'
+ } order`}
)
}
-}
-
-const renderRating = (rating) => (
-
-)
-
-render(
-
-)
-```
+
+ const renderRating = (rating) => (
+
+ )
+
+ render(
+
+ )
+ ```
### Using Custom Components as Children
@@ -881,55 +1543,138 @@ In some cases you might want to use custom components in a `Table`, e.g. a HOC f
Wrapper HOCs are simple, just return the original component:
-```javascript
----
-type: example
----
-
-class CustomTableCell extends React.Component {
- render () {
- return (
- {this.props.children}
- )
+- ```javascript
+ class CustomTableCell extends React.Component {
+ render() {
+ return {this.props.children}
+ }
}
-}
-class CustomTableRow extends React.Component {
- render () {
- return (
+ class CustomTableRow extends React.Component {
+ render() {
+ return (
1
The Shawshank Redemption
1994
9.3
- )
+ )
+ }
}
-}
-class Example extends React.Component {
- state = {
- layout: 'auto',
- hover: false,
- }
+ class Example extends React.Component {
+ state = {
+ layout: 'auto',
+ hover: false
+ }
- handleChange = (field, value) => {
- this.setState({
- [field]: value,
- })
+ handleChange = (field, value) => {
+ this.setState({
+ [field]: value
+ })
+ }
+
+ renderOptions() {
+ const { layout, hover } = this.state
+
+ return (
+
+
+ this.handleChange('layout', value)}
+ >
+
+
+
+
+
+
+ this.handleChange('hover', !hover)}
+ />
+
+
+ )
+ }
+
+ render() {
+ const { layout, hover } = this.state
+ return (
+
+ {this.renderOptions()}
+
+
+
+ Rank
+ Title
+ Year
+ Rating
+
+
+
+
+
+ 2
+ The Godfather
+ 1972
+ 9.2
+
+
+ 3
+ The Godfather: Part II
+ 1974
+ 9.0
+
+
+
+
+ )
+ }
}
- renderOptions () {
- const { layout, hover } = this.state
+ render()
+ ```
- return (
+- ```javascript
+ const CustomTableCell = ({ children, ...props }) => (
+ {children}
+ )
+
+ const CustomTableRow = ({ children, ...props }) => (
+
+ 1
+ The Shawshank Redemption
+ 1994
+ 9.3
+
+ )
+
+ const Example = () => {
+ const [layout, setLayout] = useState('auto')
+ const [hover, setHover] = useState(false)
+
+ const handleChange = (field, value) => {
+ if (field === 'layout') {
+ setLayout(value)
+ } else if (field === 'hover') {
+ setHover(!hover)
+ }
+ }
+
+ const renderOptions = () => (
this.handleChange('layout', value)}
+ onChange={(e, value) => handleChange('layout', value)}
>
@@ -940,23 +1685,16 @@ class Example extends React.Component {
this.handleChange('hover', !hover)}
+ onChange={(e, value) => handleChange('hover', !hover)}
/>
)
- }
- render() {
- const { layout, hover } = this.state
return (
- {this.renderOptions()}
-
+ {renderOptions()}
+
Rank
@@ -966,7 +1704,7 @@ class Example extends React.Component {
-
+
2
The Godfather
@@ -984,10 +1722,9 @@ class Example extends React.Component {
)
}
-}
-render()
-```
+ render()
+ ```
#### Fully custom components
@@ -1000,64 +1737,163 @@ If you want to use fully custom components you have to pay attention to the foll
Basic fully custom table:
-```javascript
----
-type: example
----
-
-class CustomTableCell extends React.Component {
- render() {
- return {this.props.children} |
+- ```javascript
+ class CustomTableCell extends React.Component {
+ render() {
+ return {this.props.children} |
+ }
}
-}
-class CustomTableRow extends React.Component {
- static contextType = TableContext
- state = { isHovered: false }
+ class CustomTableRow extends React.Component {
+ static contextType = TableContext
+ state = { isHovered: false }
- toggleHoverOff = () => {
- this.setState({isHovered: false})
- }
- toggleHoverOn = () => {
- this.setState({isHovered: true})
+ toggleHoverOff = () => {
+ this.setState({ isHovered: false })
+ }
+ toggleHoverOn = () => {
+ this.setState({ isHovered: true })
+ }
+
+ render() {
+ const rowStyle =
+ this.context.hover && this.state.isHovered
+ ? { backgroundColor: 'SeaGreen' }
+ : { backgroundColor: 'white' }
+ return (
+
+ {this.props.children}
+
+ )
+ }
}
- render() {
- // super ugly way to change CSS on hover
- let css = {backgroundColor: 'white'}
- if (this.context.hover && this.state.isHovered) {
- css = {backgroundColor: 'SeaGreen'}
+
+ class Example extends React.Component {
+ state = {
+ layout: 'auto',
+ hover: false
+ }
+
+ handleChange = (field, value) => {
+ this.setState({
+ [field]: value
+ })
}
+
+ renderOptions() {
+ const { layout, hover } = this.state
+
+ return (
+
+
+ this.handleChange('layout', value)}
+ >
+
+
+
+
+
+ this.handleChange('hover', !hover)}
+ />
+
+
+ )
+ }
+
+ render() {
+ const { layout, hover } = this.state
+
+ return (
+
+ {this.renderOptions()}
+
+
+
+ Rank
+ Title
+ Year
+ Rating
+
+
+
+
+ 1
+ The Godfather
+ 1972
+ 9.2
+
+
+ 2
+ The Godfather: Part II
+ 1974
+ 9.0
+
+
+
+
+ )
+ }
+ }
+
+ render()
+ ```
+
+- ```javascript
+ const CustomTableCell = ({ children, ...props }) => (
+ {children} |
+ )
+
+ const CustomTableRow = ({ children, ...props }) => {
+ const { hover } = useContext(TableContext)
+ const [isHovered, setIsHovered] = useState(false)
+
+ const rowStyle =
+ hover && isHovered
+ ? { backgroundColor: 'SeaGreen' }
+ : { backgroundColor: 'white' }
+
return (
-
- {this.props.children}
+
setIsHovered(true)}
+ onMouseOut={() => setIsHovered(false)}
+ >
+ {children}
)
}
-}
-class Example extends React.Component {
- state = {
- layout: 'auto',
- hover: false,
- }
+ const Example = () => {
+ const [layout, setLayout] = useState('auto')
+ const [hover, setHover] = useState(false)
- handleChange = (field, value) => {
- this.setState({
- [field]: value,
- })
- }
-
- renderOptions () {
- const { layout, hover } = this.state
+ const handleChange = (field, value) => {
+ if (field === 'layout') {
+ setLayout(value)
+ } else if (field === 'hover') {
+ setHover(!hover)
+ }
+ }
- return (
+ const renderOptions = () => (
this.handleChange('layout', value)}
+ onChange={(e, value) => handleChange('layout', value)}
>
@@ -1067,41 +1903,33 @@ class Example extends React.Component {
this.handleChange('hover', !hover)}
+ onChange={(e, value) => handleChange('hover', !hover)}
/>
)
- }
-
- render() {
- const { layout, hover } = this.state
return (
- {this.renderOptions()}
-
+ {renderOptions()}
+
- Rank
- Title
- Year
- Rating
+ Rank
+ Title
+ Year
+ Rating
- 1
+ 1
The Godfather
1972
9.2
- 2
+ 2
The Godfather: Part II
1974
9.0
@@ -1111,10 +1939,9 @@ class Example extends React.Component {
)
}
-}
-render()
-```
+ render()
+ ```
#### Fully custom components with `stacked` layout
@@ -1136,56 +1963,188 @@ Also you need the following props on the components:
Custom table with `stacked` layout support:
-```javascript
----
-type: example
----
+- ```javascript
+ class CustomTableCell extends React.Component {
+ static contextType = TableContext
+
+ render() {
+ const isStacked = this.context.isStacked
+ if (isStacked) {
+ let headerTxt
+ if (typeof this.props.header === 'function') {
+ headerTxt = React.createElement(this.props.header)
+ } else {
+ headerTxt = this.props.header
+ }
+ return (
+
+ {headerTxt && headerTxt}
+ {headerTxt && ': '}
+ {this.props.children}
+
+ )
+ }
+ return {this.props.children} |
+ }
+ }
+
+ class CustomTableRow extends React.Component {
+ static contextType = TableContext
+ state = { isHovered: false }
+
+ toggleHoverOff = () => {
+ this.setState({ isHovered: false })
+ }
+ toggleHoverOn = () => {
+ this.setState({ isHovered: true })
+ }
+
+ render() {
+ const { hover, headers, isStacked } = this.context
+ const Tag = isStacked ? 'div' : 'tr'
+ const rowStyle =
+ hover && this.state.isHovered
+ ? { backgroundColor: 'SeaGreen' }
+ : { backgroundColor: 'white' }
+
+ return (
+
+ {React.Children.toArray(this.props.children)
+ .filter(React.isValidElement)
+ .map((child, index) => {
+ return React.cloneElement(child, {
+ key: child.props.name,
+ // used by `CustomTableCell` to render its column title in `stacked` layout
+ header: headers && headers[index]
+ })
+ })}
+
+ )
+ }
+ }
+
+ class Example extends React.Component {
+ state = {
+ layout: 'auto',
+ hover: false
+ }
+
+ handleChange = (field, value) => {
+ this.setState({
+ [field]: value
+ })
+ }
-class CustomTableCell extends React.Component {
- static contextType = TableContext
+ renderOptions() {
+ const { layout, hover } = this.state
+
+ return (
+
+
+ this.handleChange('layout', value)}
+ >
+
+
+
+
+
+
+ this.handleChange('hover', !hover)}
+ />
+
+
+ )
+ }
- render() {
- const isStacked = this.context.isStacked
+ render() {
+ const { layout, hover } = this.state
+
+ return (
+
+ {this.renderOptions()}
+
+
+
+ Rank
+ Title
+ Year
+ Rating
+
+
+
+
+ 1
+ The Godfather
+ 1972
+ 9.2
+
+
+ 2
+ The Godfather: Part II
+ 1974
+ 9.0
+
+
+
+
+ )
+ }
+ }
+
+ render()
+ ```
+
+- ```javascript
+ const CustomTableCell = ({ children, header }) => {
+ const { isStacked } = useContext(TableContext)
if (isStacked) {
let headerTxt
- if (typeof this.props.header === 'function') {
- headerTxt = React.createElement(this.props.header)
+ if (typeof header === 'function') {
+ headerTxt = React.createElement(header)
} else {
- headerTxt = this.props.header
+ headerTxt = header
}
- return
- {headerTxt && headerTxt}
- {headerTxt && ': '}
- {this.props.children}
-
+ return (
+
+ {headerTxt && headerTxt}
+ {headerTxt && ': '}
+ {children}
+
+ )
}
- return {this.props.children} |
+ return {children} |
}
-}
-class CustomTableRow extends React.Component {
- static contextType = TableContext
- state = { isHovered: false }
+ const CustomTableRow = ({ children }) => {
+ const { hover, headers, isStacked } = useContext(TableContext)
+ const [isHovered, setIsHovered] = useState(false)
- toggleHoverOff = () => {
- this.setState({isHovered: false})
- }
- toggleHoverOn = () => {
- this.setState({isHovered: true})
- }
- render() {
- // super ugly way to change CSS on hover
- let css = {backgroundColor: 'white'}
- if (this.context.hover && this.state.isHovered) {
- css = {backgroundColor: 'SeaGreen'}
- }
- const headers = this.context.headers
- const isStacked = this.context.isStacked
const Tag = isStacked ? 'div' : 'tr'
+ const rowStyle =
+ hover && isHovered
+ ? { backgroundColor: 'SeaGreen' }
+ : { backgroundColor: 'white' }
+
return (
-
- {React.Children.toArray(this.props.children)
+ setIsHovered(true)}
+ onMouseOut={() => setIsHovered(false)}
+ >
+ {React.Children.toArray(children)
.filter(React.isValidElement)
.map((child, index) => {
return React.cloneElement(child, {
@@ -1193,36 +2152,31 @@ class CustomTableRow extends React.Component {
// used by `CustomTableCell` to render its column title in `stacked` layout
header: headers && headers[index]
})
- })
- }
+ })}
)
}
-}
-class Example extends React.Component {
- state = {
- layout: 'auto',
- hover: false,
- }
-
- handleChange = (field, value) => {
- this.setState({
- [field]: value,
- })
- }
+ const Example = () => {
+ const [layout, setLayout] = useState('auto')
+ const [hover, setHover] = useState(false)
- renderOptions () {
- const { layout, hover } = this.state
+ const handleChange = (field, value) => {
+ if (field === 'layout') {
+ setLayout(value)
+ } else if (field === 'hover') {
+ setHover(!hover)
+ }
+ }
- return (
+ const renderOptions = () => (
this.handleChange('layout', value)}
+ onChange={(e, value) => handleChange('layout', value)}
>
@@ -1233,41 +2187,33 @@ class Example extends React.Component {
this.handleChange('hover', !hover)}
+ onChange={(e, value) => handleChange('hover', !hover)}
/>
)
- }
-
- render() {
- const { layout, hover } = this.state
return (
- {this.renderOptions()}
-
+ {renderOptions()}
+
- Rank
- Title
- Year
- Rating
+ Rank
+ Title
+ Year
+ Rating
- 1
+ 1
The Godfather
1972
9.2
- 2
+ 2
The Godfather: Part II
1974
9.0
@@ -1277,10 +2223,9 @@ class Example extends React.Component {
)
}
-}
-render()
-```
+ render()
+ ```
### Guidelines