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 +- ```javascript + class CustomTableCell extends React.Component { + render() { + return + } } -} -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()} +
{this.props.children}{this.props.children}
+ + + 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
+ } + } + + 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()} +
{this.props.children}
+ + + 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