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