Files
2022-10-07 10:01:34 +08:00

143 lines
3.8 KiB
TypeScript

import clsx from 'clsx';
import type { ReactElement } from 'react';
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/20/solid';
type Props = Readonly<{
current: number;
end: number;
label: string;
onSelect: (page: number, event: React.MouseEvent<HTMLElement>) => void;
pagePadding?: number;
start: number;
}>;
function PaginationPage({
isCurrent = false,
label,
onClick,
}: Readonly<{
isCurrent?: boolean;
label: number;
onClick: (event: React.MouseEvent<HTMLElement>) => void;
}>) {
return (
<button
aria-current={isCurrent}
className={clsx(
'focus:ring-primary-500 focus:border-primary-500 relative inline-flex items-center border px-4 py-2 text-sm font-medium focus:z-20 focus:outline-none focus:ring-1',
isCurrent
? 'border-primary-500 bg-primary-50 text-primary-600 z-10'
: 'border-slate-300 bg-white text-slate-500 hover:bg-slate-50',
)}
disabled={isCurrent}
type="button"
onClick={onClick}>
{label}
</button>
);
}
function PaginationEllipsis() {
return (
<span className="relative inline-flex items-center border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700">
...
</span>
);
}
export default function Pagination({
current,
end,
label,
onSelect,
pagePadding = 1,
start = 1,
}: Props) {
const pageNumberSet = new Set();
const pageNumberList: Array<number | string> = [];
const elements: Array<ReactElement> = [];
let lastAddedPage = 0;
function addPage(page: number) {
if (page < start || page > end) {
return;
}
if (!pageNumberSet.has(page)) {
lastAddedPage = page;
pageNumberList.push(page);
pageNumberSet.add(page);
elements.push(
<PaginationPage
isCurrent={current === page}
label={page}
onClick={(event) => {
onSelect(page, event);
}}
/>,
);
}
}
for (let i = start; i <= start + pagePadding; i++) {
addPage(i);
}
if (lastAddedPage < current - pagePadding) {
elements.push(<PaginationEllipsis />);
}
for (let i = current - pagePadding; i <= current + pagePadding; i++) {
addPage(i);
}
if (lastAddedPage < end - pagePadding) {
elements.push(<PaginationEllipsis />);
}
for (let i = end - pagePadding; i <= end; i++) {
addPage(i);
}
const isPrevButtonDisabled = current === start;
const isNextButtonDisabled = current === end;
return (
<nav
aria-label={label}
className="isolate inline-flex -space-x-px rounded-md shadow-sm">
<button
aria-label="Previous"
className={clsx(
'relative inline-flex items-center rounded-l-md border border-slate-300 px-2 py-2 text-sm font-medium focus:z-20',
isPrevButtonDisabled
? 'text-slate-300'
: 'focus:ring-primary-500 focus:border-primary-500 bg-white text-slate-500 hover:bg-slate-50 focus:outline-none focus:ring-1',
)}
disabled={isPrevButtonDisabled}
type="button"
onClick={(event) => {
onSelect(current - 1, event);
}}>
<ChevronLeftIcon aria-hidden="true" className="h-5 w-5" />
</button>
{elements}
<button
aria-label="Next"
className={clsx(
'relative inline-flex items-center rounded-r-md border border-slate-300 px-2 py-2 text-sm font-medium focus:z-20',
isNextButtonDisabled
? 'text-slate-300'
: 'focus:ring-primary-500 focus:border-primary-500 bg-white text-slate-500 hover:bg-slate-50 focus:outline-none focus:ring-1',
)}
disabled={isNextButtonDisabled}
type="button"
onClick={(event) => {
onSelect(current + 1, event);
}}>
<ChevronRightIcon aria-hidden="true" className="h-5 w-5" />
</button>
</nav>
);
}