feat: Pagination component.

This commit is contained in:
Ahmed Bouhuolia
2020-06-21 19:21:27 +02:00
parent 3e15cd42c8
commit 15bcd55979
19 changed files with 668 additions and 167 deletions

View File

@@ -16,7 +16,7 @@ import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
import { ConditionalWrapper } from 'utils';
import { useUpdateEffect } from 'hooks';
import { If } from 'components';
import { If, Pagination } from 'components';
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
@@ -46,6 +46,11 @@ export default function DataTable({
expandToggleColumn = 2,
noInitialFetch = false,
spinnerProps = { size: 40 },
pagination = false,
pagesCount: controlledPageCount,
initialPageIndex,
initialPageSize,
}) {
const {
getTableProps,
@@ -59,18 +64,28 @@ export default function DataTable({
isAllRowsExpanded,
totalColumnsWidth,
// page,
pageCount,
canPreviousPage,
canNextPage,
gotoPage,
previousPage,
nextPage,
setPageSize,
// Get the state from the instance
state: { pageIndex, pageSize, sortBy, selectedRowIds },
} = useTable(
{
columns,
data: data,
initialState: { pageIndex: 0, expanded }, // Pass our hoisted table state
manualPagination: true, // Tell the usePagination
// hook that we'll handle our own data fetching
// This means we'll also have to provide our own
// pageCount.
// pageCount: controlledPageCount,
initialState: {
pageIndex: initialPageIndex,
pageSize: initialPageSize,
expanded,
}, // Pass our hoisted table state
manualPagination: true,
pageCount: controlledPageCount,
getSubRows: (row) => row.children,
manualSortBy,
expandSubRows,
@@ -80,10 +95,10 @@ export default function DataTable({
useSortBy,
useExpanded,
useRowSelect,
usePagination,
useResizeColumns,
useFlexLayout,
useSticky,
usePagination,
(hooks) => {
hooks.visibleColumns.push((columns) => [
// Let's make a column for selection
@@ -197,6 +212,7 @@ export default function DataTable({
{...cell.getCellProps({
className: classnames(cell.column.className || '', 'td'),
})}
onContextMenu={handleRowContextMenu(cell, row)}
>
{RenderCell({ cell, row, index })}
</div>
@@ -337,6 +353,21 @@ export default function DataTable({
</ScrollSyncPane>
</div>
</ScrollSync>
<If condition={pagination && pageCount}>
<Pagination
initialPage={pageIndex + 1}
total={pageSize * pageCount}
size={pageSize}
onPageChange={(currentPage) => {
gotoPage(currentPage - 1);
}}
onPageSizeChange={(pageSize, currentPage) => {
gotoPage(currentPage - 1);
setPageSize(pageSize);
}}
/>
</If>
</div>
);
}

View File

@@ -0,0 +1,218 @@
import React, { useReducer, useEffect } from 'react';
import classNames from 'classnames';
import { Button, ButtonGroup, Intent, HTMLSelect, } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
import PropTypes from 'prop-types';
import { range } from 'lodash';
import { Icon } from 'components';
const getState = ({ currentPage, size, total }) => {
const totalPages = Math.ceil(total / size);
const visibleItems = 5;
const halfVisibleItems = Math.ceil(visibleItems / 2);
// create an array of pages to ng-repeat in the pager control
let startPage, endPage;
if (totalPages <= visibleItems) {
// less than {visibleItems} total pages so show
startPage = 1;
endPage = totalPages;
} else {
// more than {visibleItems} total pages so calculate start and end pages
if (currentPage <= halfVisibleItems) {
startPage = 1;
endPage = visibleItems;
} else if (currentPage + (halfVisibleItems - 1) >= totalPages) {
startPage = totalPages - (visibleItems - 1);
endPage = totalPages;
} else {
startPage = currentPage - halfVisibleItems;
endPage = currentPage + halfVisibleItems - 1;
}
}
const pages = [...Array(endPage + 1 - startPage).keys()].map(
(i) => startPage + i,
);
// Too large or small currentPage
let correctCurrentpage = currentPage;
if (currentPage > totalPages) correctCurrentpage = totalPages;
if (currentPage <= 0) correctCurrentpage = 1;
return {
currentPage: correctCurrentpage,
size,
total,
pages,
totalPages,
};
};
const TYPE = {
PAGE_CHANGE: 'PAGE_CHANGE',
PAGE_SIZE_CHANGE: 'PAGE_SIZE_CHANGE',
INITIALIZE: 'INITIALIZE',
};
const reducer = (state, action) => {
switch (action.type) {
case TYPE.PAGE_CHANGE:
return getState({
currentPage: action.page,
size: state.size,
total: state.total,
});
case TYPE.PAGE_SIZE_CHANGE:
return getState({
currentPage: state.currentPage,
size: action.size,
total: state.total,
});
case TYPE.INITIALIZE:
return getState({
currentPage: state.currentPage,
size: action.size,
total: action.total,
});
default:
throw new Error();
}
};
const Pagination = ({
initialPage,
total,
size,
pageSizesOptions = [5, 12, 20, 30, 50, 75, 100, 150],
onPageChange,
onPageSizeChange,
}) => {
const [state, dispatch] = useReducer(
reducer,
{ currentPage: initialPage, total, size },
getState,
);
useEffect(() => {
dispatch({
type: 'INITIALIZE',
total,
size,
});
}, [total, size]);
return (
<div class="pagination">
<div class="pagination__buttons-group">
<ButtonGroup>
<Button
disabled={state.currentPage === 1}
onClick={() => {
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
onPageChange(state.currentPage - 1);
}}
minimal={true}
className={'pagination__item pagination__item--previous'}
icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
>
<T id='previous' />
</Button>
{state.pages.map((page) => (
<Button
key={page}
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
disabled={state.currentPage === page}
onClick={() => {
dispatch({ type: 'PAGE_CHANGE', page });
onPageChange(page);
}}
minimal={true}
className={classNames(
'pagination__item',
'pagination__item--page',
{
'is-active': state.currentPage === page,
}
)}
>
{ page }
</Button>
))}
<Button
disabled={state.currentPage === state.totalPages}
onClick={() => {
dispatch({
type: 'PAGE_CHANGE',
page: state.currentPage + 1
});
onPageChange(state.currentPage + 1);
}}
minimal={true}
className={'pagination__item pagination__item--next'}
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
>
<T id='next' />
</Button>
</ButtonGroup>
</div>
<div class="pagination__controls">
<div class="pagination__goto-control">
Go to
<HTMLSelect
minimal={true}
options={range(1, state.totalPages + 1)}
value={state.currentPage}
onChange={(event) => {
const page = parseInt(event.currentTarget.value, 10);
dispatch({ type: 'PAGE_CHANGE', page });
onPageChange(page);
}}
minimal={true}
/>
</div>
<div class="pagination__pagesize-control">
Page size
<HTMLSelect
minimal={true}
options={pageSizesOptions}
value={size}
onChange={(event) => {
const pageSize = parseInt(event.currentTarget.value, 10);
dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize });
dispatch({ type: 'PAGE_CHANGE', page: 1 });
onPageSizeChange(pageSize, 1);
}}
minimal={true}
/>
</div>
</div>
<div class="pagination__info">
<T id={'showing_current_page_to_total'} values={{
currentPage: state.currentPage,
totalPages: state.totalPages,
total: total,
}} />
</div>
</div>
);
};
Pagination.propTypes = {
initialPage: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
total: PropTypes.number.isRequired,
onPageChange: PropTypes.func,
onPageSizeChange: PropTypes.func,
};
Pagination.defaultProps = {
initialPage: 1,
size: 25,
};
export default Pagination;

View File

@@ -10,6 +10,7 @@ import ErrorMessage from './ErrorMessage';
import MODIFIER from './modifiers';
import FieldHint from './FieldHint';
import MenuItemLabel from './MenuItemLabel';
import Pagination from './Pagination';
const Hint = FieldHint;
@@ -27,5 +28,6 @@ export {
FieldHint,
Hint,
MenuItemLabel,
Pagination,
// For,
};