Files
cunningham/packages/react/src/components/Pagination/index.tsx
Nathan Vasse 30ec253ab0 (react) make Pagination goto input optionnal
Based on a recent request we needed to make it optionnal in order to
have simpler pagination in some cases.

Closes #183
2024-03-08 16:33:54 +01:00

180 lines
5.0 KiB
TypeScript

import React, { Fragment, useEffect, useState } from "react";
import { Button } from ":/components/Button";
import { Input } from ":/components/Forms/Input";
import { useCunningham } from ":/components/Provider";
export interface PaginationProps {
/** Current page */
page: number;
/** Called when page need to change */
onPageChange: (page: number) => void;
/** Total number of pages */
pagesCount?: number;
/** Total number of items per page */
// eslint-disable-next-line react/no-unused-prop-types
pageSize: number;
/** Display the goto input */
displayGoto?: boolean;
}
export const usePagination = ({
defaultPage = 1,
defaultPagesCount,
pageSize = 10,
}: {
/** Default current page */
defaultPage?: number;
/** Default total number of pages */
defaultPagesCount?: number;
/** Total number of items per page */
pageSize?: number;
}) => {
const [page, setPage] = useState(defaultPage);
const [pagesCount, setPagesCount] = useState(defaultPagesCount);
return {
page,
setPage,
onPageChange: setPage,
pagesCount,
setPagesCount,
pageSize,
};
};
export const Pagination = ({
page,
onPageChange,
pagesCount = 0,
displayGoto = true,
}: PaginationProps) => {
const { t } = useCunningham();
const [gotoValue, setGotoValue] = useState("");
useEffect(() => {
setGotoValue(page + "");
}, [page]);
if (pagesCount <= 1) {
return null;
}
// Create the default list of all the page numbers we intend to show
const pageList = [
1,
// If there is just one page between first page and currentPage - 2,
// we can display this page number instead of "..."
page - 2 === 3 ? page - 3 : -1,
page - 2,
page - 1,
page,
page + 1,
page + 2,
// If there is just one page between maxPage and currentPage + 2,
// we can display this page number instead of "..."
page + 3 === pagesCount - 1 ? page + 3 : -1,
pagesCount,
]
// Filter out page numbers below 1 (when currentPage is 1 or 2)
.filter((_page) => _page > 0)
// Filter out page numbers above the max (they do not have anything to display)
.filter((_page) => _page <= pagesCount)
// Drop duplicates (this is trivial as our pageList is sorted)
.filter((_page, index, list) => _page !== list[index - 1]);
const onPreviousClick = () => {
onPageChange(Math.max(page - 1, 0));
};
const onNextClick = () => {
onPageChange(Math.min(page + 1, pagesCount));
};
const gotoPage = () => {
let value = +gotoValue;
if (value < 0 || !value) {
value = 1;
}
if (value > pagesCount) {
value = pagesCount;
}
onPageChange(value);
};
const canPrevious = page > 1;
const canNext = page < pagesCount;
return (
<div className="c__pagination">
<div className="c__pagination__list">
<Button
color="tertiary-text"
aria-label={t("components.pagination.previous_aria")}
onClick={onPreviousClick}
disabled={!canPrevious}
icon={<span className="material-icons">navigate_before</span>}
size="small"
/>
{pageList.map((_page, index) => (
<Fragment key={_page}>
{/* Prepend a cell with "..." when the page number we're rendering does not follow the previous one */}
{_page > (pageList[index - 1] || 0) + 1 && <span>...</span>}
{_page === page ? (
<Button
color="tertiary-text"
active={true}
aria-label={t("components.pagination.current_page_aria", {
page: _page,
})}
size="small"
>
{_page}
</Button>
) : (
<Button
color="tertiary-text"
aria-label={t("components.pagination.goto_page_aria", {
page: _page,
})}
onClick={() => onPageChange(_page)}
size="small"
>
{_page}
</Button>
)}
</Fragment>
))}
<Button
color="tertiary-text"
aria-label={t("components.pagination.next_aria")}
onClick={onNextClick}
disabled={!canNext}
icon={<span className="material-icons">navigate_next</span>}
size="small"
/>
</div>
{displayGoto && (
<div className="c__pagination__goto">
<form
onSubmit={(e) => {
e.preventDefault();
gotoPage();
}}
>
<Input
type="number"
label={t("components.pagination.goto_label")}
aria-label={t("components.pagination.goto_label_aria")}
size={2}
value={gotoValue}
onChange={(e) => setGotoValue(e.target.value)}
min={1}
max={pagesCount}
onBlur={() => gotoPage()}
onFocus={(e) => e.target.select()}
/>
</form>
</div>
)}
</div>
);
};