Skip to content

Commit

Permalink
[Table] Add a TableFooter for pagination (#8254)
Browse files Browse the repository at this point in the history
* [TableFooter] Add a TableFooter component

* [TableFooter] Add a footer for pagination to the enhanced table demo

This also adds more demo data, and yes, the nutrition information of Marshmallow, Nougat and Oreo is actually real.

* Lint the code

* [EnhancedTable] Fix checkbox in table header, small code changes according to the review

* [TableFooter] Add tests

* [EnhancedTable] Fix sorting

* [TablePagination] Add a TablePagination component, lint the code and fix the sorting order in the EnhancedTable demo

* increase size-limit

* some changes

* remove div

* [EnhancedTable] Fix table not being sorted initially

* [TablePagination] Add more tests, handle too high page number after changing the rows per page, lint the code

* [TablePagination] Add typings

* Update the list of supported components
  • Loading branch information
leMaik authored and oliviertassinari committed Sep 20, 2017
1 parent 58d2ff9 commit 35989a9
Show file tree
Hide file tree
Showing 20 changed files with 749 additions and 37 deletions.
75 changes: 55 additions & 20 deletions docs/src/pages/demos/tables/EnhancedTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import keycode from 'keycode';
import Table, {
TableBody,
TableCell,
TableFooter,
TableHead,
TablePagination,
TableRow,
TableSortLabel,
} from 'material-ui/Table';
Expand Down Expand Up @@ -42,22 +44,23 @@ class EnhancedTableHead extends React.Component {
onSelectAllClick: PropTypes.func.isRequired,
order: PropTypes.string.isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired,
};

createSortHandler = property => event => {
this.props.onRequestSort(event, property);
};

render() {
const { onSelectAllClick, order, orderBy, numSelected } = this.props;
const { onSelectAllClick, order, orderBy, numSelected, rowCount } = this.props;

return (
<TableHead>
<TableRow>
<TableCell checkbox>
<Checkbox
indeterminate={numSelected > 0 && numSelected < 5}
checked={numSelected === 5}
indeterminate={numSelected > 0 && numSelected < rowCount}
checked={numSelected === rowCount}
onChange={onSelectAllClick}
/>
</TableCell>
Expand Down Expand Up @@ -157,18 +160,31 @@ const styles = theme => ({
});

class EnhancedTable extends React.Component {
state = {
order: 'asc',
orderBy: 'calories',
selected: [],
data: [
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Gingerbread', 356, 16.0, 49, 3.9),
],
};
constructor(props) {
super(props);
this.state = {
order: 'asc',
orderBy: 'calories',
selected: [],
data: [
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Donut', 452, 25.0, 51, 4.9),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Gingerbread', 356, 16.0, 49, 3.9),
createData('Honeycomb', 408, 3.2, 87, 6.5),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Jelly Bean', 375, 0.0, 94, 0.0),
createData('KitKat', 518, 26.0, 65, 7.0),
createData('Lollipop', 392, 0.2, 98, 0.0),
createData('Marshmallow', 318, 0, 81, 2.0),
createData('Nougat', 360, 19.0, 9, 37.0),
createData('Oreo', 437, 18.0, 63, 4.0),
].sort((a, b) => (a.calories < b.calories ? -1 : 1)),
page: 0,
rowsPerPage: 5,
};
}

handleRequestSort = (event, property) => {
const orderBy = property;
Expand All @@ -178,9 +194,10 @@ class EnhancedTable extends React.Component {
order = 'asc';
}

const data = this.state.data.sort(
(a, b) => (order === 'desc' ? b[orderBy] > a[orderBy] : a[orderBy] > b[orderBy]),
);
const data =
order === 'desc'
? this.state.data.sort((a, b) => (b[orderBy] < a[orderBy] ? -1 : 1))
: this.state.data.sort((a, b) => (a[orderBy] < b[orderBy] ? -1 : 1));

this.setState({ data, order, orderBy });
};
Expand Down Expand Up @@ -220,11 +237,19 @@ class EnhancedTable extends React.Component {
this.setState({ selected: newSelected });
};

handleChangePage = (event, page) => {
this.setState({ page });
};

handleChangeRowsPerPage = event => {
this.setState({ rowsPerPage: event.target.value });
};

isSelected = id => this.state.selected.indexOf(id) !== -1;

render() {
const classes = this.props.classes;
const { data, order, orderBy, selected } = this.state;
const { data, order, orderBy, selected, rowsPerPage, page } = this.state;

return (
<Paper className={classes.paper}>
Expand All @@ -236,9 +261,10 @@ class EnhancedTable extends React.Component {
orderBy={orderBy}
onSelectAllClick={this.handleSelectAllClick}
onRequestSort={this.handleRequestSort}
rowCount={data.length}
/>
<TableBody>
{data.map(n => {
{data.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map(n => {
const isSelected = this.isSelected(n.id);
return (
<TableRow
Expand All @@ -263,6 +289,15 @@ class EnhancedTable extends React.Component {
);
})}
</TableBody>
<TableFooter>
<TablePagination
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
/>
</TableFooter>
</Table>
</Paper>
);
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/demos/tables/tables.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
components: Table, TableBody, TableCell, TableHead, TableRow, TableSortLabel
components: Table, TableBody, TableCell, TableFooter, TableHead, TablePagination, TableRow, TableSortLabel
---

# Tables
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/getting-started/supported-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ to discuss the approach before submitting a PR.
- **[Data tables](https://www.google.com/design/spec/components/data-tables.html)**
- **Sortable ✓**
- **Selectable ✓**
- Pagination
- **Pagination**
- **[Dialogs](https://www.google.com/design/spec/components/dialogs.html)**
- **[Alerts](https://www.google.com/design/spec/components/dialogs.html#dialogs-alerts)**
- **[Simple menus](https://www.google.com/design/spec/components/dialogs.html#dialogs-simple-menus) (Menu) ✓**
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
"size-limit": [
{
"path": "build/index.js",
"limit": "90 KB"
"limit": "91 KB"
}
],
"nyc": {
Expand Down
12 changes: 12 additions & 0 deletions pages/api/table-footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @flow

import React from 'react';
import withRoot from 'docs/src/modules/components/withRoot';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import markdown from './table-footer.md';

function Page() {
return <MarkdownDocs markdown={markdown} />;
}

export default withRoot(Page);
32 changes: 32 additions & 0 deletions pages/api/table-footer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!--- This documentation is automatically generated, do not try to edit it. -->

# TableFooter



## Props
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| children | Node | | The content of the component, normally `TableRow`. |
| classes | Object | | Useful to extend the style applied to components. |
| component | union:&nbsp;string<br>&nbsp;ComponentType<*><br> | 'tfoot' | The component used for the root node. Either a string to use a DOM element or a component. |

Any other properties supplied will be [spread to the root element](/customization/api#spread).

## CSS API

You can override all the class names injected by Material-UI thanks to the `classes` property.
This property accepts the following keys:
- `root`

Have a look at [overriding with classes](/customization/overrides#overriding-with-classes)
section for more detail.

If using the `overrides` key of the theme as documented
[here](/customization/themes#customizing-all-instances-of-a-component-type),
you need to use the following style sheet name: `MuiTableFooter`.

## Demos

- [Tables](/demos/tables)

12 changes: 12 additions & 0 deletions pages/api/table-pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @flow

import React from 'react';
import withRoot from 'docs/src/modules/components/withRoot';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import markdown from './table-pagination.md';

function Page() {
return <MarkdownDocs markdown={markdown} />;
}

export default withRoot(Page);
43 changes: 43 additions & 0 deletions pages/api/table-pagination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!--- This documentation is automatically generated, do not try to edit it. -->

# TablePagination

A `TableRow` based component for placing inside `TableFooter` for pagination.

## Props
| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| classes | Object | | Useful to extend the style applied to components. |
| <span style="color: #31a148">count *</span> | number | | The total number of rows. |
| labelDisplayedRows | signature | ({ from, to, count }) => `${from}-${to} of ${count}` | Useful to customize the displayed rows label. |
| labelRowsPerPage | Node | 'Rows per page:' | Useful to customize the rows per page label. Invoked with a `{ from, to, count, page }` object. |
| <span style="color: #31a148">onChangePage *</span> | signature | | Callback fired when the page is changed. Invoked with two arguments: the event and the page to show. |
| <span style="color: #31a148">onChangeRowsPerPage *</span> | signature | | Callback fired when the number of rows per page is changed. Invoked with two arguments: the event. |
| <span style="color: #31a148">page *</span> | number | | The zero-based index of the current page. |
| <span style="color: #31a148">rowsPerPage *</span> | number | | The number of rows per page. |
| rowsPerPageOptions | unknown | [5, 10, 25] | Customizes the options of the rows per page select field. |

Any other properties supplied will be [spread to the root element](/customization/api#spread).

## CSS API

You can override all the class names injected by Material-UI thanks to the `classes` property.
This property accepts the following keys:
- `cell`
- `toolbar`
- `spacer`
- `select`
- `selectRoot`
- `actions`

Have a look at [overriding with classes](/customization/overrides#overriding-with-classes)
section for more detail.

If using the `overrides` key of the theme as documented
[here](/customization/themes#customizing-all-instances-of-a-component-type),
you need to use the following style sheet name: `MuiTablePagination`.

## Demos

- [Tables](/demos/tables)

4 changes: 0 additions & 4 deletions src/Table/TableBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,6 @@ class TableBody extends React.Component<AllProps, void> {
}
}

TableBody.contextTypes = {
table: PropTypes.object,
};

TableBody.childContextTypes = {
table: PropTypes.object,
};
Expand Down
6 changes: 4 additions & 2 deletions src/Table/TableCell.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export const styles = (theme: Object) => ({
paddingLeft: 12,
paddingRight: 12,
},
footer: {},
footer: {
borderBottom: 0,
},
});

function TableCell(props, context) {
Expand Down Expand Up @@ -122,7 +124,7 @@ TableCell.defaultProps = {
};

TableCell.contextTypes = {
table: PropTypes.object,
table: PropTypes.object.isRequired,
};

export default withStyles(styles, { name: 'MuiTableCell' })(TableCell);
17 changes: 15 additions & 2 deletions src/Table/TableCell.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ describe('<TableCell />', () => {
let classes;

before(() => {
shallow = createShallow({ dive: true });
shallow = createShallow({
dive: true,
context: {
table: { footer: true },
},
});
classes = getClasses(<TableCell />);
});

Expand All @@ -22,7 +27,7 @@ describe('<TableCell />', () => {
it('should spread custom props on the root node', () => {
const wrapper = shallow(<TableCell data-my-prop="woofTableCell" />);
assert.strictEqual(
wrapper.prop('data-my-prop'),
wrapper.props()['data-my-prop'],
'woofTableCell',
'custom prop should be woofTableCell',
);
Expand Down Expand Up @@ -60,6 +65,14 @@ describe('<TableCell />', () => {
assert.strictEqual(wrapper.hasClass(classes.head), true, 'should have the head class');
});

it('should render a th with the footer class when in the context of a table footer', () => {
const wrapper = shallow(<TableCell />);
wrapper.setContext({ ...wrapper.options.context, table: { footer: true } });
assert.strictEqual(wrapper.name(), 'td');
assert.strictEqual(wrapper.hasClass(classes.root), true);
assert.strictEqual(wrapper.hasClass(classes.footer), true, 'should have the footer class');
});

it('should render a div when custom component prop is used', () => {
const wrapper = shallow(<TableCell component="div" />);
assert.strictEqual(wrapper.name(), 'div', 'should be a div element');
Expand Down
7 changes: 7 additions & 0 deletions src/Table/TableFooter.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as React from 'react';
import { StyledComponent } from '..';

export interface TableFooterProps
extends React.HTMLAttributes<HTMLTableSectionElement> {}

export default class TableFooter extends StyledComponent<TableFooterProps> {}

0 comments on commit 35989a9

Please sign in to comment.