Skip to content

Commit

Permalink
refactor: pagination
Browse files Browse the repository at this point in the history
  • Loading branch information
renrizzolo committed Oct 25, 2022
1 parent d30f397 commit 5e46cac
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 206 deletions.
8 changes: 8 additions & 0 deletions src/components/Pagination/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ export interface PaginationProps {
* The max page count
*/
pageCount: number;
/**
* The number of pages to show on either side of the current page
*/
siblingCount?: number;
/**
* separator for truncated pages
*/
separator?: React.ReactNode;
/**
* A callback function for when clicking on previous, next and pagination items
*/
Expand Down
239 changes: 113 additions & 126 deletions src/components/Pagination/index.jsx
Original file line number Diff line number Diff line change
@@ -1,136 +1,115 @@
import React from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import Button from '../Button';
import './styles.css';

const Pagination = ({ className, activePage, pageCount, onSelect, prev, next }) => (
<div data-testid="pagination-wrapper" className={classnames('aui--pagination', className)}>
{activePage !== 1 && prev && (
<Button
variant="borderless"
onClick={() => onSelect(activePage - 1)}
className={classnames('aui--pagination-item', 'aui--pagination-sides')}
>
<div className="previous-icon" />
</Button>
)}

<Button
variant="borderless"
onClick={() => onSelect(1)}
className={classnames('aui--pagination-item', {
active: activePage === 1,
const inclusiveRange = (start, end) => _.range(start, end + 1);

const Pagination = ({
className,
activePage: activePageProp,
pageCount,
siblingCount: siblingCountProp = 1,
separator = '...',
onSelect,
prev,
next,
}) => {
const activePage = _.clamp(activePageProp, 1, pageCount);
const pages = React.useMemo(() => {
const siblingCount = _.clamp(siblingCountProp, 0, pageCount);
// [first page] [separator] [sibling(s)] [activePage] [sibling(s)] [separator] [last page]
const separatorCount = 2;
const endsCount = 2;
const centerCount = siblingCount * 2 + 1;
const totalPageItems = centerCount + separatorCount;
const totalItems = totalPageItems + endsCount;
const offset = Math.floor(centerCount / 2);
if (totalItems >= pageCount) {
return inclusiveRange(1, pageCount);
}
const showLeftSeparator = activePage > offset + 2 && activePage <= pageCount;
const showRightSeparator = activePage < pageCount - (offset + 2);
if (showLeftSeparator && !showRightSeparator) {
const rightRange = inclusiveRange(pageCount - totalPageItems + 1, pageCount);
return [1, separator, ...rightRange];
}
if (!showLeftSeparator && showRightSeparator) {
const leftRange = inclusiveRange(1, totalPageItems);
return [...leftRange, separator, pageCount];
}
if (showLeftSeparator && showRightSeparator) {
const centerRange = inclusiveRange(activePage - offset, activePage + offset);
return [1, separator, ...centerRange, separator, pageCount];
}
}, [activePage, pageCount, separator, siblingCountProp]);
return (
<div data-testid="pagination-wrapper" className={classnames('aui--pagination', className)}>
{prev && (
<Button
variant="borderless"
onClick={() => onSelect(activePage - 1)}
className={classnames('aui--pagination-item', 'aui--pagination-sides', {
'aui--pagination-hidden': activePage === 1,
})}
icon={<div className="previous-icon" />}
aria-label="Previous Page"
/>
)}

{pages.map((page, index) => {
const active = activePage === page;
if (page === separator)
return (
<div
key={index}
data-testid="pagination-ellipsis"
className="aui--pagination-item aui--pagination-separator"
>
{separator}
</div>
);
return (
<Button
key={index}
variant={active ? 'solid' : 'borderless'}
aria-current={active ? 'true' : undefined}
color="default"
onClick={() => onSelect(page)}
className={classnames('aui--pagination-item', {
active,
})}
aria-label={`Go to page ${page}`}
icon={page}
/>
);
})}
>
{1}
</Button>

{activePage > 3 && pageCount !== 4 && pageCount !== 5 && (
<div data-testid="pagination-left-ellipsis" className="aui--pagination-separator">
...
</div>
)}

{activePage === 5 && pageCount === 5 && (
<Button
variant="borderless"
className={classnames('aui--pagination-item')}
onClick={() => onSelect(activePage - 3)}
>
{activePage - 3}
</Button>
)}

{((activePage === pageCount && pageCount > 3) || (activePage === 4 && pageCount === 5)) && (
<Button
variant="borderless"
onClick={() => onSelect(activePage - 2)}
className={classnames('aui--pagination-item')}
>
{activePage - 2}
</Button>
)}

{activePage > 2 && (
<Button
variant="borderless"
className={classnames('aui--pagination-item')}
onClick={() => onSelect(activePage - 1)}
>
{activePage - 1}
</Button>
)}

{activePage !== 1 && activePage !== pageCount && (
<Button
variant="borderless"
onClick={() => onSelect(activePage)}
className={classnames('aui--pagination-item', 'active')}
>
{activePage}
</Button>
)}

{activePage < pageCount - 1 && (
<Button
variant="borderless"
className={classnames('aui--pagination-item')}
onClick={() => onSelect(activePage + 1)}
>
{activePage + 1}
</Button>
)}

{((activePage === 1 && pageCount > 3) || (activePage === 2 && pageCount === 5)) && (
<Button
variant="borderless"
className={classnames('aui--pagination-item')}
onClick={() => onSelect(activePage + 2)}
>
{activePage + 2}
</Button>
)}

{activePage === 1 && pageCount === 5 && (
<Button
variant="borderless"
className={classnames('aui--pagination-item')}
onClick={() => onSelect(activePage + 3)}
>
{activePage + 3}
</Button>
)}

{activePage < pageCount - 2 && pageCount !== 4 && pageCount !== 5 && (
<div data-testid="pagination-right-ellipsis" className="aui--pagination-separator">
...
</div>
)}

{pageCount !== 1 && (
<Button
variant="borderless"
onClick={() => onSelect(pageCount)}
className={classnames('aui--pagination-item', {
active: activePage === pageCount,
})}
>
{pageCount}
</Button>
)}

{activePage !== pageCount && next && (
<Button
variant="borderless"
onClick={() => onSelect(activePage + 1)}
className={classnames('aui--pagination-item', 'aui--pagination-sides')}
>
<div className="next-icon" />
</Button>
)}
</div>
);

{next && (
<Button
variant="borderless"
onClick={() => onSelect(activePage + 1)}
className={classnames('aui--pagination-item', 'aui--pagination-sides', {
'aui--pagination-hidden': activePage === pageCount,
})}
icon={<div className="next-icon" />}
aria-label="Next Page"
/>
)}
</div>
);
};

Pagination.propTypes = {
className: PropTypes.string,
Expand All @@ -142,6 +121,14 @@ Pagination.propTypes = {
* The max page count
*/
pageCount: PropTypes.number.isRequired,
/**
* The number of pages to show on either side of the current page
*/
siblingCount: PropTypes.number,
/**
* separator for truncated pages
*/
separator: PropTypes.node,
/**
* A callback function for when clicking on previous, next and pagination items
*/
Expand Down
Loading

0 comments on commit 5e46cac

Please sign in to comment.