mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-07-30 21:53:28 +08:00
[ui][pagination] implementation
This commit is contained in:
142
packages/ui/src/Pagination/Pagination.tsx
Normal file
142
packages/ui/src/Pagination/Pagination.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
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>
|
||||
);
|
||||
}
|
@ -19,6 +19,9 @@ export { default as DropdownMenu } from './DropdownMenu/DropdownMenu';
|
||||
// HorizontalDivider
|
||||
export * from './HorizontalDivider/HorizontalDivider';
|
||||
export { default as HorizontalDivider } from './HorizontalDivider/HorizontalDivider';
|
||||
// Pagination
|
||||
export * from './Pagination/Pagination';
|
||||
export { default as Pagination } from './Pagination/Pagination';
|
||||
// Select
|
||||
export * from './Select/Select';
|
||||
export { default as Select } from './Select/Select';
|
||||
|
Reference in New Issue
Block a user