diff --git a/src/DataTable/DataTable.tsx b/src/DataTable/DataTable.tsx index e262a73c..b3a3ab9a 100644 --- a/src/DataTable/DataTable.tsx +++ b/src/DataTable/DataTable.tsx @@ -65,6 +65,7 @@ function DataTable(props: TableProps): JSX.Element { paginationResetDefaultPage = defaultProps.paginationResetDefaultPage, paginationPerPage = defaultProps.paginationPerPage, paginationRowsPerPageOptions = defaultProps.paginationRowsPerPageOptions, + paginationPage = defaultProps.paginationPage, paginationIconLastPage = defaultProps.paginationIconLastPage, paginationIconFirstPage = defaultProps.paginationIconFirstPage, paginationIconNext = defaultProps.paginationIconNext, @@ -148,7 +149,7 @@ function DataTable(props: TableProps): JSX.Element { selectedColumn: defaultSortColumn, toggleOnSelectedRowsChange: false, sortDirection: defaultSortDirection, - currentPage: paginationDefaultPage, + currentPage: paginationPage !== null ? paginationPage : paginationDefaultPage, rowsPerPage: paginationPerPage, selectedRowsFlag: false, contextMessage: defaultProps.contextMessage, @@ -296,6 +297,12 @@ function DataTable(props: TableProps): JSX.Element { handleChangePage(paginationDefaultPage); }, [paginationDefaultPage, paginationResetDefaultPage]); + useDidUpdateEffect(() => { + if (paginationPage !== null && paginationPage !== currentPage) { + handleChangePage(paginationPage); + } + }, [paginationPage]); + useDidUpdateEffect(() => { if (pagination && paginationServer && paginationTotalRows > 0) { const updatedPage = getNumberOfPages(paginationTotalRows, rowsPerPage); diff --git a/src/DataTable/__tests__/DataTable.test.tsx b/src/DataTable/__tests__/DataTable.test.tsx index 0628813e..37fe4f0f 100644 --- a/src/DataTable/__tests__/DataTable.test.tsx +++ b/src/DataTable/__tests__/DataTable.test.tsx @@ -1948,6 +1948,73 @@ describe('DataTable::Pagination', () => { expect(container.firstChild).toMatchSnapshot(); }); + + test('should call onChangePage when paginationPage is used to control the current page', () => { + const onChangePageMock = jest.fn(); + const mock = dataMock(); + const { container } = render( + , + ); + + fireEvent.click(container.querySelector('button#pagination-next-page') as HTMLButtonElement); + expect(onChangePageMock).toBeCalledWith(2, 2); + }); + + test('should render correctly when paginationPage is used to control the current page', () => { + const mock = dataMock(); + const { container, rerender } = render( + , + ); + + rerender( + , + ); + + // expect to be on page 2 + expect(container.firstChild).toMatchSnapshot(); + expect(container.querySelector('div[id="row-2"]')).not.toBeNull(); + }); + + test('should ignore paginationDefaultPage if paginationPage is set on first render', () => { + const mock = dataMock(); + const { container } = render( + , + ); + + // expect to be on page 2 + expect(container.firstChild).toMatchSnapshot(); + expect(container.querySelector('div[id="row-2"]')).not.toBeNull(); + }); }); describe('DataTable::subHeader', () => { diff --git a/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap b/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap index a109bdec..b9c2e77e 100644 --- a/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap +++ b/src/DataTable/__tests__/__snapshots__/DataTable.test.tsx.snap @@ -3131,6 +3131,260 @@ exports[`DataTable::Pagination should have the correct amount of rows when pagin `; +exports[`DataTable::Pagination should ignore paginationDefaultPage if paginationPage is set on first render 1`] = ` +.c2 { + position: relative; + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + height: 100%; + max-width: 100%; + color: rgba(0,0,0,0.87); + background-color: #FFFFFF; +} + +.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + width: 100%; + color: rgba(0,0,0,0.87); + font-size: 12px; + font-weight: 500; +} + +.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: stretch; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; + background-color: #FFFFFF; + min-height: 52px; + border-bottom-width: 1px; + border-bottom-color: rgba(0,0,0,.12); + border-bottom-style: solid; +} + +.c5 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + box-sizing: border-box; + line-height: normal; + padding-left: 16px; + padding-right: 16px; +} + +.c11 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + box-sizing: border-box; + line-height: normal; + padding-left: 16px; + padding-right: 16px; + word-break: break-word; +} + +.c6 { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + max-width: 100%; + min-width: 100px; +} + +.c12 div:first-child { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c10 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: stretch; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + -webkit-align-content: stretch; + -ms-flex-line-pack: stretch; + align-content: stretch; + width: 100%; + box-sizing: border-box; + font-size: 13px; + font-weight: 400; + color: rgba(0,0,0,0.87); + background-color: #FFFFFF; + min-height: 48px; +} + +.c10:not(:last-of-type) { + border-bottom-style: solid; + border-bottom-width: 1px; + border-bottom-color: rgba(0,0,0,.12); +} + +.c7 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: inherit; + -webkit-justify-content: inherit; + -ms-flex-pack: inherit; + justify-content: inherit; + height: 100%; + width: 100%; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + overflow: hidden; +} + +.c8 { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.c9 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.c0 { + position: relative; + width: 100%; + border-radius: inherit; + overflow-x: auto; + overflow-y: hidden; + min-height: 0; +} + +.c1 { + position: relative; + width: 100%; + display: table; +} + +@media screen and (max-width:599px) { + +} + +
+
+
+
+
+
+
+
+ Test +
+
+
+
+
+
+
+
+
+ Zuchinni +
+
+
+
+
+
+
+`; + exports[`DataTable::Pagination should navigate to page 1 if the table is sorted 1`] = ` .c2 { position: relative; @@ -5051,6 +5305,260 @@ exports[`DataTable::Pagination should render correctly when a paginationComponen `; +exports[`DataTable::Pagination should render correctly when paginationPage is used to control the current page 1`] = ` +.c2 { + position: relative; + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + height: 100%; + max-width: 100%; + color: rgba(0,0,0,0.87); + background-color: #FFFFFF; +} + +.c3 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + width: 100%; + color: rgba(0,0,0,0.87); + font-size: 12px; + font-weight: 500; +} + +.c4 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: stretch; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; + background-color: #FFFFFF; + min-height: 52px; + border-bottom-width: 1px; + border-bottom-color: rgba(0,0,0,.12); + border-bottom-style: solid; +} + +.c5 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + box-sizing: border-box; + line-height: normal; + padding-left: 16px; + padding-right: 16px; +} + +.c11 { + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + box-sizing: border-box; + line-height: normal; + padding-left: 16px; + padding-right: 16px; + word-break: break-word; +} + +.c6 { + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + -webkit-flex-basis: 0; + -ms-flex-preferred-size: 0; + flex-basis: 0; + max-width: 100%; + min-width: 100px; +} + +.c12 div:first-child { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.c10 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-align-items: stretch; + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch; + -webkit-align-content: stretch; + -ms-flex-line-pack: stretch; + align-content: stretch; + width: 100%; + box-sizing: border-box; + font-size: 13px; + font-weight: 400; + color: rgba(0,0,0,0.87); + background-color: #FFFFFF; + min-height: 48px; +} + +.c10:not(:last-of-type) { + border-bottom-style: solid; + border-bottom-width: 1px; + border-bottom-color: rgba(0,0,0,.12); +} + +.c7 { + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: inherit; + -webkit-justify-content: inherit; + -ms-flex-pack: inherit; + justify-content: inherit; + height: 100%; + width: 100%; + outline: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + overflow: hidden; +} + +.c8 { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.c9 { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; +} + +.c0 { + position: relative; + width: 100%; + border-radius: inherit; + overflow-x: auto; + overflow-y: hidden; + min-height: 0; +} + +.c1 { + position: relative; + width: 100%; + display: table; +} + +@media screen and (max-width:599px) { + +} + +
+
+
+
+
+
+
+
+ Test +
+
+
+
+
+
+
+
+
+ Zuchinni +
+
+
+
+
+
+
+`; + exports[`DataTable::Pagination should render correctly when paginationResetDefaultPage is toggled 1`] = ` .c2 { position: relative; diff --git a/src/DataTable/defaultProps.tsx b/src/DataTable/defaultProps.tsx index 3908f027..35a47ef5 100644 --- a/src/DataTable/defaultProps.tsx +++ b/src/DataTable/defaultProps.tsx @@ -79,6 +79,7 @@ export const defaultProps = { paginationDefaultPage: 1, paginationResetDefaultPage: false, paginationTotalRows: 0, + paginationPage: null, paginationPerPage: 10, paginationRowsPerPageOptions: [10, 15, 20, 25, 30], paginationComponent: null, diff --git a/src/DataTable/types.ts b/src/DataTable/types.ts index d8e7f89c..e8fe60e9 100644 --- a/src/DataTable/types.ts +++ b/src/DataTable/types.ts @@ -80,6 +80,7 @@ export type TableProps = { paginationIconLastPage?: React.ReactNode; paginationIconNext?: React.ReactNode; paginationIconPrevious?: React.ReactNode; + paginationPage?: number; paginationPerPage?: number; paginationResetDefaultPage?: boolean; paginationRowsPerPageOptions?: number[]; diff --git a/stories/DataTable/KitchenSink.stories.tsx b/stories/DataTable/KitchenSink.stories.tsx index d4f83d70..1c8e1f08 100644 --- a/stories/DataTable/KitchenSink.stories.tsx +++ b/stories/DataTable/KitchenSink.stories.tsx @@ -70,6 +70,7 @@ function KitchenSinkStory({ expandOnRowDoubleClicked, expandableRowsHideExpander, pagination, + paginationPage, highlightOnHover, striped, pointerOnHover, @@ -114,6 +115,7 @@ function KitchenSinkStory({ expandOnRowDoubleClicked={expandOnRowDoubleClicked} expandableRowsHideExpander={expandableRowsHideExpander} pagination={pagination} + paginationPage={paginationPage} highlightOnHover={highlightOnHover} striped={striped} pointerOnHover={pointerOnHover} @@ -151,6 +153,7 @@ KitchenSinkTS.args = { expandOnRowDoubleClicked: false, expandableRowsHideExpander: false, pagination: true, + paginationPage: 1, highlightOnHover: false, striped: false, pointerOnHover: false, diff --git a/stories/DataTable/pagination/controlled.mdx b/stories/DataTable/pagination/controlled.mdx new file mode 100644 index 00000000..47bf4440 --- /dev/null +++ b/stories/DataTable/pagination/controlled.mdx @@ -0,0 +1,11 @@ +import { Story, Canvas } from '@storybook/addon-docs'; + +# Controlled Pagination + +To enable controlled pagination you can add the `paginationPage` property in addition to `paginationServer` and or `pagination`. + +You'll also likely want to use the `onChangePage` callback to keep the `paginationPage` property in sync with the data table's internal state. + + + + diff --git a/stories/DataTable/pagination/controlled.stories.js b/stories/DataTable/pagination/controlled.stories.js new file mode 100644 index 00000000..b75d076b --- /dev/null +++ b/stories/DataTable/pagination/controlled.stories.js @@ -0,0 +1,56 @@ +import React, { useState } from 'react'; +import doc from './controlled.mdx'; +import data from '../../constants/sampleMovieData'; +import Button from '../../shared/Button'; +import DataTable from '../../../src/index'; + +const columns = [ + { + name: 'Title', + selector: row => row.title, + sortable: true, + }, + { + name: 'Director', + selector: row => row.director, + sortable: true, + }, + { + name: 'Year', + selector: row => row.year, + sortable: true, + }, +]; + +const Flex = ({ children }) => ( +
{children}
+); + +export const Controlled = () => { + const [page, setPage] = useState(1); + const reset = () => setPage(1); + + return ( + + + + + ); +}; + +export default { + title: 'Pagination/Controlled', + component: Controlled, + parameters: { + docs: { + page: doc, + }, + }, +}; diff --git a/stories/props.stories.mdx b/stories/props.stories.mdx index 6af7040b..d8a0ced2 100644 --- a/stories/props.stories.mdx +++ b/stories/props.stories.mdx @@ -83,6 +83,7 @@ import { Meta } from '@storybook/addon-docs'; | paginationServerOptions | object | no | `{ persistSelectedOnPageChange: false, persistSelectedOnSort: false }` | When using `selectableRows` is used to make selected rows persist on page change and on sort when using server side pagination.

**Note:** when using `persistSelectedOnPageChange` that select all checkbox will not be visible (i.e. you cannot select rows there you have to retrieved from the server) | | paginationDefaultPage | number | no | 1 | The default page to use when the table initially loads | | paginationResetDefaultPage | boolean | no | false | The prop can be "toggled" to reset the pagination back to `paginationDefaultPage`. For this to work make sure you are using some sort of state and toggling the prop. e.g. `setResetPaginationToggle(!resetPaginationToggle)` or for a class component `setState(resetPaginationToggle: !resetPaginationToggle)` | +| paginationPage | number | no | null | Allows you to specify which page to display when using either pagination or server side pagination. It's recommended to keep it in sync with the table's internal page state by using the `onChangePage` callback. | | paginationTotalRows | number | no | 0 | Allows you to provide the total row count for your table as represented by your API when performing server side pagination. if this property is not provided then react-data-table will use `data.length` | | paginationPerPage | number | no | 10 | The default rows per page to use when the table initially loads | | paginationRowsPerPageOptions | number | no | `[10, 15, 20, 25, 30]` | Row page dropdown selection options | | Callback when rows per page is changed returns `onChangeRowsPerPage(currentRowsPerPage, currentPage)` |