import { useMemo } from "react";
import PropTypes from "prop-types";
import "./styles.scss";
/**
* A pagination component that can be used to navigate through a list of items.
*
* @category Components
* @component
* @returns {React.Component} - The pagination component.
*/
function Pagination({
totalCount,
pageSize,
siblingCount = 1,
currentPage,
onPageChange,
DOTS = "...",
DOTSClassName = "ellipsis",
previousButtonClassName = "paginate-button previous",
nextButtonClassName = "paginate-button next",
pageButtonClassName = "paginate-button",
currentPageClassName = "current-page",
disabledClassName = "disabled",
...props
}) {
/**
* Function to generate a range of pages to display
* @returns An array of numbers.
*/
const range = (start, end) => {
let length = end - start + 1;
return Array.from({ length }, (_, index) => index + start);
};
/** Function called when the component is rendered. It is used to generate a range of pages to display. */
const paginationRange = useMemo(() => {
// Calculating the total number of pages.
const totalPageCount = Math.ceil(totalCount / pageSize);
// Calculating the total number of pages to show in the pagination component.
const totalPageToShow = siblingCount + 5;
// If the number of pages to show is less than the total of pages, return the range from 1 to the total number of pages.
if (totalPageToShow >= totalPageCount) {
return range(1, totalPageCount);
}
/* Calculating the sibling index. */
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1);
const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPageCount);
// Don't show DOTS if there is only one position left after/before.
const showLeftDots = leftSiblingIndex > 2;
const showRightDots = rightSiblingIndex < totalPageCount - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPageCount;
// Check if there are more pages to show on the right side of the pagination component.
// If there are more pages to show on the right side of the pagination component,
// Then return the range from the first page to the right sibling index.
if (!showLeftDots && showRightDots) {
let leftItemCount = 3 + 2;
let leftRange = range(1, leftItemCount);
return [...leftRange, DOTS, totalPageCount];
}
// Check if there are more pages to show on the left side of the pagination component.
// If there are more pages to show on the left side of the pagination component,
// Then return the range from the left sibling index to the last page.
if (showLeftDots && !showRightDots) {
let rightItemCount = 3 + 2;
let rightRange = range(totalPageCount - rightItemCount + 1, totalPageCount);
return [firstPageIndex, DOTS, ...rightRange];
}
// Check if there are more pages to show on both sides of the pagination component.
// If there are more pages to show on both sides of the pagination component,
// Then return the range from the first page to the left sibling index,
if (showLeftDots && showRightDots) {
let middleRange = range(leftSiblingIndex, rightSiblingIndex);
return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
}
}, [totalCount, pageSize, siblingCount, currentPage, DOTS]);
// Check to see if the current page is 0 or if the pagination range is less than 2.
//If true, then the pagination component will not be rendered.
if (currentPage === 0 || paginationRange.length < 2) {
return null;
}
/** Go to the next page. */
const onNext = () => {
onPageChange(currentPage + 1);
};
/** Go to the previous page.*/
const onPrevious = () => {
onPageChange(currentPage - 1);
};
let lastPage = paginationRange[paginationRange.length - 1];
return (
<>
<button
// Check to see if the current page is 1. If true, add disabled class.
className={previousButtonClassName + (currentPage === 1 ? " " + disabledClassName : "")}
// If the current page is 1, then disable the previous button.
disabled={currentPage === 1}
tabIndex={currentPage === 1 ? -1 : 0}
onClick={onPrevious}
aria-label={"Navigate to previous page (" + (currentPage - 1) + ")"}
{...props}
>
Previous
</button>
<span>
{paginationRange.map((pageNumber, index) => {
if (pageNumber === DOTS) {
return (
<span key={index} className={DOTSClassName}>
{DOTS}
</span>
);
}
return (
<button
key={index}
// Check to see if the current page is the same as the page number. If true, add current class.
className={pageButtonClassName + (currentPage === pageNumber ? " " + currentPageClassName : "")}
onClick={() => onPageChange(pageNumber)}
{...props}
aria-label={`Navigate to page ${pageNumber}`}
>
{pageNumber}
</button>
);
})}
</span>
<button
// Check to see if the current page is the same as the last page. If true, add disabled class.
className={nextButtonClassName + (currentPage >= lastPage ? " " + disabledClassName : "")}
// If the current page is the same as the last page, then disable the next button.
disabled={currentPage === lastPage}
tabIndex={currentPage === lastPage ? -1 : 0}
onClick={onNext}
aria-label={"Navigate to next page (" + (currentPage + 1) + ")"}
{...props}
>
Next
</button>
</>
);
}
Pagination.propTypes = {
/** The total number of items in the list */
totalCount: PropTypes.number.isRequired,
/** The number of items to show per page */
pageSize: PropTypes.number.isRequired,
/** The number of pages to show before and after the current page */
siblingCount: PropTypes.number,
/** The current page number */
currentPage: PropTypes.number.isRequired,
/** The function to call when a page is clicked */
onPageChange: PropTypes.func.isRequired,
/** The string to show in the pagination component when there are more pages than can be shown */
DOTS: PropTypes.string,
/** The class name to add to the pagination component when there are more pages than can be shown */
DOTSClassName: PropTypes.string,
/** The class name to add to the page buttons */
pageButtonClassName: PropTypes.string,
/** The class name to add to the current page button */
currentPageClassName: PropTypes.string,
/** The class name to add to the previous button */
previousButtonClassName: PropTypes.string,
/** The class name to add to the next button */
nextButtonClassName: PropTypes.string,
/** The class name to add to the disabled buttons */
disabledClassName: PropTypes.string,
};
export default Pagination;
Source