From bbc3efb2b39fdc2381251a3e092c497b7c257eb3 Mon Sep 17 00:00:00 2001 From: terrance456 Date: Sat, 20 Jul 2024 21:04:11 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20pagination=20compone?= =?UTF-8?q?nt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/react/src/index.ts | 1 + .../src/lib/pagination/pagination.stories.mdx | 59 +++++++ .../src/lib/pagination/pagination.test.tsx | 60 +++++++ libs/react/src/lib/pagination/pagination.tsx | 159 ++++++++++++++++++ 4 files changed, 279 insertions(+) create mode 100644 libs/react/src/lib/pagination/pagination.stories.mdx create mode 100644 libs/react/src/lib/pagination/pagination.test.tsx create mode 100644 libs/react/src/lib/pagination/pagination.tsx diff --git a/libs/react/src/index.ts b/libs/react/src/index.ts index 91b5686989..82aaafe8dc 100644 --- a/libs/react/src/index.ts +++ b/libs/react/src/index.ts @@ -23,6 +23,7 @@ export * from './lib/table/components' export * from './lib/icons' export * from './lib/breadcrumb/breadcrumb' export * from './lib/segmented-control/segmented-control' +export * from './lib/pagination/pagination' // Backwards compatibility export export { AlertRibbon as Alert } from './lib/alert-ribbon/alert-ribbon' diff --git a/libs/react/src/lib/pagination/pagination.stories.mdx b/libs/react/src/lib/pagination/pagination.stories.mdx new file mode 100644 index 0000000000..f84e2273cd --- /dev/null +++ b/libs/react/src/lib/pagination/pagination.stories.mdx @@ -0,0 +1,59 @@ +import { + Meta, + Story, + Canvas, + Markdown, + ArgsTable, + Preview, +} from '@storybook/addon-docs' +import { Pagination } from './pagination' +import { useState } from 'react' + +export const Template = (props) => { + const [pageIndex, setPageIndex] = useState(1) + return ( + setPageIndex(index)} + /> + ) +} + +# Pagination + +The `Pagination` component allows users to navigate through large sets of items by dividing them into pages. +It provides controls to switch between pages and displays the current page number. + + + + + + {Template.bind({})} + + + +### Small + + + + {Template.bind({})} + + + +Here are the props available for the `Pagination` component: + + diff --git a/libs/react/src/lib/pagination/pagination.test.tsx b/libs/react/src/lib/pagination/pagination.test.tsx new file mode 100644 index 0000000000..2e749adb77 --- /dev/null +++ b/libs/react/src/lib/pagination/pagination.test.tsx @@ -0,0 +1,60 @@ +import { render, screen } from '@testing-library/react' +import { Pagination } from './pagination' +import userEvent, { UserEvent } from '@testing-library/user-event' + +describe('Pagination', () => { + it('Should render', () => { + render() + expect(screen.getAllByRole('button')).toHaveLength(11) + }) + + it('Should display dots on right', () => { + render() + expect(screen.getAllByText('...')).toHaveLength(1) + }) + + it('Should display dots on left', () => { + render() + expect(screen.getAllByText('...')).toHaveLength(1) + }) + + it('Should display dots on both sides', () => { + render() + expect(screen.getAllByText('...')).toHaveLength(2) + }) + + it('Should fire onClickPage fn', async () => { + const user: UserEvent = userEvent.setup() + const mockOnClickPage: jest.Mock = jest.fn() + render( + , + ) + await user.click(screen.getByText('2')) + expect(mockOnClickPage).toBeCalledWith(2) + }) + + it('Should navigate to next page', async () => { + const user: UserEvent = userEvent.setup() + const mockOnClickPage: jest.Mock = jest.fn() + render( + , + ) + await user.click(screen.getByLabelText('Next Page')) + expect(mockOnClickPage).toBeCalledWith(2) + }) + + it('Should navigate to previous page', async () => { + const user: UserEvent = userEvent.setup() + const mockOnClickPage: jest.Mock = jest.fn() + render( + , + ) + await user.click(screen.getByLabelText('Previous Page')) + expect(mockOnClickPage).toBeCalledWith(1) + }) +}) diff --git a/libs/react/src/lib/pagination/pagination.tsx b/libs/react/src/lib/pagination/pagination.tsx new file mode 100644 index 0000000000..766f331fe8 --- /dev/null +++ b/libs/react/src/lib/pagination/pagination.tsx @@ -0,0 +1,159 @@ +import { MouseEvent, useMemo } from 'react' +import classNames from 'classnames' + +export interface PaginationProps { + /** Determines if the pagination buttons should be small */ + small?: boolean + /**The total number of data items to paginate */ + length: number + /**The number of items to display per page, default 10 */ + pageSize?: number + /** The current page index, start from 1 */ + pageIndex?: number + /** The number of sibling pages to display around the current page, default 1 */ + offset?: number + /**Callback function to handle page changes */ + onClickPage?: (pageIndex: number) => void +} + +const chevronArrowLeft = ( + + + +) + +const chevronArrowRight = ( + + + +) + +const DOTS = '...' + +const getPageRange = (start: number, end: number) => { + const length = end - start + 1 + return Array.from({ length }, (_, idx) => idx + start) +} + +export const Pagination = ({ + small, + length, + pageSize = 10, + pageIndex = 1, + offset = 1, + onClickPage, +}: PaginationProps) => { + const totalPageCount: number = useMemo( + () => Math.ceil(length / pageSize), + [length, pageSize], + ) + + const pageList: Array | undefined = useMemo(() => { + if (offset + 5 >= totalPageCount) { + return getPageRange(1, totalPageCount) + } + const offsetPageCount = pageIndex + offset + const leftSiblingIndex = Math.max(pageIndex - offset, 1) + const rightSiblingIndex = Math.min(offsetPageCount, totalPageCount) + const showLeftDots = leftSiblingIndex > 2 + const showRightDots = rightSiblingIndex < totalPageCount - 2 + const itemCount = 3 + 2 * offset + + if (!showLeftDots && showRightDots) { + return [...getPageRange(1, itemCount), DOTS, totalPageCount] + } + + if (showLeftDots && !showRightDots) { + return [ + 1, + DOTS, + ...getPageRange(totalPageCount - itemCount + 1, totalPageCount), + ] + } + + if (showLeftDots && showRightDots) { + return [ + 1, + DOTS, + ...getPageRange(leftSiblingIndex, rightSiblingIndex), + DOTS, + totalPageCount, + ] + } + }, [offset, pageIndex, totalPageCount]) + + const onPrev = (event: MouseEvent) => { + event.preventDefault() + onClickPage && onClickPage(Math.max(pageIndex - 1, 1)) + } + + const onNext = (event: MouseEvent) => { + event.preventDefault() + onClickPage && onClickPage(Math.min(pageIndex + 1, totalPageCount)) + } + + return ( + + ) +} From c4f2ae3442674cf3309fd4a591106afebeaa4e52 Mon Sep 17 00:00:00 2001 From: terrance456 Date: Sat, 20 Jul 2024 21:05:34 +0800 Subject: [PATCH 2/6] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20update=20storybo?= =?UTF-8?q?ok=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/react/src/lib/pagination/pagination.stories.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/react/src/lib/pagination/pagination.stories.mdx b/libs/react/src/lib/pagination/pagination.stories.mdx index f84e2273cd..5cb71190c7 100644 --- a/libs/react/src/lib/pagination/pagination.stories.mdx +++ b/libs/react/src/lib/pagination/pagination.stories.mdx @@ -45,7 +45,7 @@ It provides controls to switch between pages and displays the current page numbe Date: Mon, 22 Jul 2024 10:27:24 +0800 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20improve=20pagina?= =?UTF-8?q?tion=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/react/src/lib/pagination/pagination.tsx | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/libs/react/src/lib/pagination/pagination.tsx b/libs/react/src/lib/pagination/pagination.tsx index 766f331fe8..30c43f3ab9 100644 --- a/libs/react/src/lib/pagination/pagination.tsx +++ b/libs/react/src/lib/pagination/pagination.tsx @@ -28,7 +28,7 @@ const chevronArrowRight = ( ) -const DOTS = '...' +const ELLIPSIS = '...' const getPageRange = (start: number, end: number) => { const length = end - start + 1 @@ -52,31 +52,31 @@ export const Pagination = ({ if (offset + 5 >= totalPageCount) { return getPageRange(1, totalPageCount) } - const offsetPageCount = pageIndex + offset - const leftSiblingIndex = Math.max(pageIndex - offset, 1) - const rightSiblingIndex = Math.min(offsetPageCount, totalPageCount) - const showLeftDots = leftSiblingIndex > 2 - const showRightDots = rightSiblingIndex < totalPageCount - 2 - const itemCount = 3 + 2 * offset + const leftEllipsisIndex = Math.max(pageIndex - offset, 1) + const rightEllipsisIndex = Math.min(pageIndex + offset, totalPageCount) + const showLeftEllipsis = leftEllipsisIndex > 2 + const showRightEllipsis = rightEllipsisIndex < totalPageCount - 2 + const eachItemCount = 3 + 2 * offset - if (!showLeftDots && showRightDots) { - return [...getPageRange(1, itemCount), DOTS, totalPageCount] + // show right elipsis + if (!showLeftEllipsis && showRightEllipsis) { + return [...getPageRange(1, eachItemCount), ELLIPSIS, totalPageCount] } - - if (showLeftDots && !showRightDots) { + // show left elipsis + if (showLeftEllipsis && !showRightEllipsis) { return [ 1, - DOTS, - ...getPageRange(totalPageCount - itemCount + 1, totalPageCount), + ELLIPSIS, + ...getPageRange(totalPageCount - eachItemCount + 1, totalPageCount), ] } - - if (showLeftDots && showRightDots) { + // show both elipsis + if (showLeftEllipsis && showRightEllipsis) { return [ 1, - DOTS, - ...getPageRange(leftSiblingIndex, rightSiblingIndex), - DOTS, + ELLIPSIS, + ...getPageRange(leftEllipsisIndex, rightEllipsisIndex), + ELLIPSIS, totalPageCount, ] } @@ -115,7 +115,7 @@ export const Pagination = ({ )} {(pageList || []).map((page: string | number, index: number) => - page === DOTS ? ( + page === ELLIPSIS ? ( From e547683dedfc955a2004bcd0444758e5dc366cba Mon Sep 17 00:00:00 2001 From: terrance456 Date: Mon, 22 Jul 2024 11:36:51 +0800 Subject: [PATCH 4/6] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20fix=20linting=20?= =?UTF-8?q?issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/react/src/lib/pagination/pagination.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/react/src/lib/pagination/pagination.tsx b/libs/react/src/lib/pagination/pagination.tsx index 30c43f3ab9..7ed497cff4 100644 --- a/libs/react/src/lib/pagination/pagination.tsx +++ b/libs/react/src/lib/pagination/pagination.tsx @@ -80,6 +80,7 @@ export const Pagination = ({ totalPageCount, ] } + return [] }, [offset, pageIndex, totalPageCount]) const onPrev = (event: MouseEvent) => { From 69ec09a6dbed43678a02e17bd674e39665093cc5 Mon Sep 17 00:00:00 2001 From: terrance456 Date: Mon, 22 Jul 2024 11:43:16 +0800 Subject: [PATCH 5/6] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20redunda?= =?UTF-8?q?nt=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libs/react/src/lib/pagination/pagination.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/react/src/lib/pagination/pagination.tsx b/libs/react/src/lib/pagination/pagination.tsx index 7ed497cff4..76b5bfa218 100644 --- a/libs/react/src/lib/pagination/pagination.tsx +++ b/libs/react/src/lib/pagination/pagination.tsx @@ -115,7 +115,7 @@ export const Pagination = ({ } )} - {(pageList || []).map((page: string | number, index: number) => + {pageList.map((page: string | number, index: number) => page === ELLIPSIS ? (