122 lines
4.4 KiB
TypeScript
122 lines
4.4 KiB
TypeScript
import { useCallback, useMemo } from 'react';
|
|
import { Button as AriaButton } from 'react-aria-components';
|
|
|
|
type PaginationProps = {
|
|
totalPages: number;
|
|
currentPage: number;
|
|
pageCount?: number;
|
|
onPageChange: (page: number) => void;
|
|
};
|
|
|
|
// https://design-system.w3.org/components/pagination.html
|
|
export const Pagination = ({ totalPages, currentPage, pageCount = 10, onPageChange }: PaginationProps) => {
|
|
const start = useMemo(() => {
|
|
return Math.floor(currentPage / pageCount) * pageCount;
|
|
}, [currentPage, pageCount]);
|
|
|
|
const handlePageClick = useCallback(
|
|
(page: number) => () => {
|
|
if (typeof onPageChange === 'function' && page >= 0 && page < totalPages) {
|
|
onPageChange(page);
|
|
}
|
|
},
|
|
[totalPages, onPageChange],
|
|
);
|
|
|
|
const pageArray = useMemo(() => {
|
|
const arr: number[] = [];
|
|
|
|
// totalPage가 0이면 아무것도 안나와서 최소 1이 되도록 수정
|
|
const _totalPages = totalPages > 0 ? totalPages : 1;
|
|
if (Number.isNaN(start)) {
|
|
return arr;
|
|
}
|
|
|
|
for (let i = 0; i < pageCount; i++) {
|
|
const pageNumber = start + i;
|
|
|
|
if (pageNumber >= _totalPages) {
|
|
continue;
|
|
}
|
|
|
|
arr.push(pageNumber);
|
|
}
|
|
|
|
return arr;
|
|
}, [start, pageCount, totalPages]);
|
|
|
|
const noPrev = start === 0;
|
|
const noNext = start + pageCount >= totalPages;
|
|
|
|
return (
|
|
<nav className="text-dabeeo-gray-99 text-sm" aria-label="pagination">
|
|
<ul className="flex items-center justify-center list-none">
|
|
<li className="flex">
|
|
<AriaButton
|
|
className="cursor-pointer data-[disabled=true]:cursor-default"
|
|
isDisabled={noPrev}
|
|
aria-label="첫 페이지"
|
|
onClick={handlePageClick(0)}
|
|
>
|
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="15.8571" y="15" width="1.5" height="10" fill="currentColor" />
|
|
<path d="M24.1429 15.3571L19.1429 19.8571L24.1429 24.3571" stroke="currentColor" strokeWidth="1.5" />
|
|
</svg>
|
|
</AriaButton>
|
|
</li>
|
|
<li className="mr-2.5 flex">
|
|
<AriaButton
|
|
className="cursor-pointer data-[disabled=true]:cursor-default"
|
|
isDisabled={noPrev}
|
|
aria-label="이전 페이지"
|
|
onClick={handlePageClick(start - 1)}
|
|
>
|
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M22.5 15.5L17.5 20L22.5 24.5" stroke="currentColor" strokeWidth="1.5" />
|
|
</svg>
|
|
</AriaButton>
|
|
</li>
|
|
{pageArray.map((pageNumber) => (
|
|
<li key={pageNumber}>
|
|
<AriaButton
|
|
isDisabled={currentPage === pageNumber}
|
|
className="w-10 h-10 flex items-center justify-center tabular-nums not-disabled:cursor-pointer aria-[current=page]:text-sm aria-[current=page]:text-primary aria-[current=page]:font-bold"
|
|
onClick={handlePageClick(pageNumber)}
|
|
aria-current={currentPage === pageNumber ? 'page' : undefined}
|
|
>
|
|
{pageNumber + 1}
|
|
</AriaButton>
|
|
</li>
|
|
))}
|
|
<li className="ml-2.5 flex">
|
|
<AriaButton
|
|
isDisabled={noNext}
|
|
className="cursor-pointer data-[disabled=true]:cursor-default"
|
|
// className="text-[#CCCCCC]"
|
|
aria-label="다음 페이지"
|
|
onClick={handlePageClick(start + pageCount)}
|
|
>
|
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M17.5 15.5L22.5 20L17.5 24.5" stroke="currentColor" strokeWidth="1.5" />
|
|
</svg>
|
|
</AriaButton>
|
|
</li>
|
|
<li className="flex">
|
|
<AriaButton
|
|
isDisabled={noNext}
|
|
className="cursor-pointer data-[disabled=true]:cursor-default"
|
|
// className="text-[#CCCCCC]"
|
|
aria-label="마지막 페이지"
|
|
onClick={handlePageClick(totalPages - 1)}
|
|
>
|
|
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M15.75 15.5L20.75 20L15.75 24.5" stroke="currentColor" strokeWidth="1.5" />
|
|
<path d="M24.25 15H22.75V25H24.25V15Z" fill="currentColor" />
|
|
</svg>
|
|
</AriaButton>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
);
|
|
};
|