parent
e0a3f4c15c
commit
641a565e5c
@ -0,0 +1,234 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import type { ComponentMeta } from '@storybook/react';
|
||||||
|
import { Pagination } from '@tih/ui';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
argTypes: {},
|
||||||
|
component: Pagination,
|
||||||
|
title: 'Pagination',
|
||||||
|
} as ComponentMeta<typeof Pagination>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
|
function emptyFunction() {}
|
||||||
|
|
||||||
|
export function Basic({
|
||||||
|
current,
|
||||||
|
end,
|
||||||
|
start,
|
||||||
|
pagePadding,
|
||||||
|
}: Pick<
|
||||||
|
React.ComponentProps<typeof Pagination>,
|
||||||
|
'current' | 'end' | 'pagePadding' | 'start'
|
||||||
|
>) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Pagination
|
||||||
|
current={current}
|
||||||
|
end={end}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={pagePadding}
|
||||||
|
start={start}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Basic.args = {
|
||||||
|
current: 3,
|
||||||
|
end: 10,
|
||||||
|
pagePadding: 1,
|
||||||
|
start: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Interaction() {
|
||||||
|
const [currentPage, setCurrentPage] = useState(5);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={currentPage}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={(page) => setCurrentPage(page)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PageRanges() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={5}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={1}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={2}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={9}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={10}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={1}
|
||||||
|
end={1}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={1}
|
||||||
|
end={1}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={2}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={1}
|
||||||
|
end={2}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={2}
|
||||||
|
end={2}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={1}
|
||||||
|
end={3}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={2}
|
||||||
|
end={3}
|
||||||
|
label="Pagination"
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PagePadding() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={5}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={2}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={5}
|
||||||
|
end={20}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={2}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={10}
|
||||||
|
end={20}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={2}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={10}
|
||||||
|
end={20}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={3}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={5}
|
||||||
|
end={10}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={3}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Pagination
|
||||||
|
current={1}
|
||||||
|
end={1}
|
||||||
|
label="Pagination"
|
||||||
|
pagePadding={2}
|
||||||
|
start={1}
|
||||||
|
onSelect={emptyFunction}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in new issue