diff --git a/apps/storybook/stories/pagination.stories.tsx b/apps/storybook/stories/pagination.stories.tsx new file mode 100644 index 00000000..435bed7b --- /dev/null +++ b/apps/storybook/stories/pagination.stories.tsx @@ -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; + +// eslint-disable-next-line @typescript-eslint/no-empty-function +function emptyFunction() {} + +export function Basic({ + current, + end, + start, + pagePadding, +}: Pick< + React.ComponentProps, + 'current' | 'end' | 'pagePadding' | 'start' +>) { + return ( +
+ +
+ ); +} + +Basic.args = { + current: 3, + end: 10, + pagePadding: 1, + start: 1, +}; + +export function Interaction() { + const [currentPage, setCurrentPage] = useState(5); + + return ( +
+
+ setCurrentPage(page)} + /> +
+
+ ); +} + +export function PageRanges() { + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ); +} + +export function PagePadding() { + return ( +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ ); +} diff --git a/packages/ui/src/Pagination/Pagination.tsx b/packages/ui/src/Pagination/Pagination.tsx new file mode 100644 index 00000000..92a6cf18 --- /dev/null +++ b/packages/ui/src/Pagination/Pagination.tsx @@ -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) => void; + pagePadding?: number; + start: number; +}>; + +function PaginationPage({ + isCurrent = false, + label, + onClick, +}: Readonly<{ + isCurrent?: boolean; + label: number; + onClick: (event: React.MouseEvent) => void; +}>) { + return ( + + ); +} + +function PaginationEllipsis() { + return ( + + ... + + ); +} + +export default function Pagination({ + current, + end, + label, + onSelect, + pagePadding = 1, + start = 1, +}: Props) { + const pageNumberSet = new Set(); + const pageNumberList: Array = []; + const elements: Array = []; + 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( + { + onSelect(page, event); + }} + />, + ); + } + } + + for (let i = start; i <= start + pagePadding; i++) { + addPage(i); + } + + if (lastAddedPage < current - pagePadding) { + elements.push(); + } + + for (let i = current - pagePadding; i <= current + pagePadding; i++) { + addPage(i); + } + + if (lastAddedPage < end - pagePadding) { + elements.push(); + } + + for (let i = end - pagePadding; i <= end; i++) { + addPage(i); + } + + const isPrevButtonDisabled = current === start; + const isNextButtonDisabled = current === end; + + return ( + + ); +} diff --git a/packages/ui/src/index.tsx b/packages/ui/src/index.tsx index db26daba..9faff648 100644 --- a/packages/ui/src/index.tsx +++ b/packages/ui/src/index.tsx @@ -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';