From 3f8ae3f558446aba3870e90c899db25ad9321499 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Tue, 24 Oct 2023 18:48:57 +0200 Subject: refactor(components): rewrite Pagination component --- .../organisms/nav/pagination/pagination.tsx | 183 +++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 src/components/organisms/nav/pagination/pagination.tsx (limited to 'src/components/organisms/nav/pagination/pagination.tsx') diff --git a/src/components/organisms/nav/pagination/pagination.tsx b/src/components/organisms/nav/pagination/pagination.tsx new file mode 100644 index 0000000..8e95122 --- /dev/null +++ b/src/components/organisms/nav/pagination/pagination.tsx @@ -0,0 +1,183 @@ +import { type ForwardRefRenderFunction, forwardRef } from 'react'; +import { ButtonLink, Icon, Nav, type NavProps } from '../../../atoms'; +import { NavItem, NavList } from '../../../molecules'; +import styles from './pagination.module.scss'; + +export type PaginationItemKind = 'backward' | 'forward' | 'number'; + +type RenderPaginationItemAriaLabelProps = { + /** + * Does the item represent the current page? + */ + isCurrentPage?: boolean; + /** + * The item kind. + */ + kind: PaginationItemKind; + /** + * The linked page number. + */ + pageNumber: number; +}; + +export type RenderPaginationItemAriaLabel = ( + props: RenderPaginationItemAriaLabelProps +) => string; + +export type RenderPaginationLink = (page: number) => string; + +export type PaginationProps = Omit & { + /** + * The currently active page number. + */ + current: number; + /** + * Function used to provide an accessible label to pagination items. + */ + renderItemAriaLabel: RenderPaginationItemAriaLabel; + /** + * Function used to create the href provided for each page link. + */ + renderLink: RenderPaginationLink; + /** + * The number of pages to show on each side of the current page. + * + * @default 1 + */ + siblings?: number; + /** + * The total number of pages. + */ + total: number; +}; + +type GetPagesProps = Pick & { + displayRange: number; +}; + +const getPages = ({ current, displayRange, total }: GetPagesProps) => + Array.from({ length: total }, (_, index) => { + const page = index + 1; + const isFirstPage = page === 1; + const isLastPage = page === total; + const isOutOfRangeFromStart = page < current - displayRange && !isFirstPage; + const isOutOfRangeFromEnd = page > current + displayRange && !isLastPage; + const isOutOfRange = isOutOfRangeFromStart || isOutOfRangeFromEnd; + const ellipsisId = isOutOfRangeFromStart + ? 'start-ellipsis' + : 'end-ellipsis'; + + return { + id: isOutOfRange ? ellipsisId : `page-${page}`, + number: isOutOfRangeFromStart || isOutOfRangeFromEnd ? null : page, + }; + }).filter( + (page, index, allPages) => + index === 0 || page.number !== allPages[index - 1]?.number + ); + +const PaginationWithRef: ForwardRefRenderFunction< + HTMLElement, + PaginationProps +> = ( + { + className = '', + current, + renderItemAriaLabel, + renderLink, + siblings = 1, + total, + ...props + }, + ref +) => { + const paginationClass = `${styles.wrapper} ${className}`; + const displayRange = + current === 1 || current === total ? siblings + 1 : siblings; + const hasPreviousPage = current > 1; + const hasNextPage = current < total; + const pages = getPages({ current, displayRange, total }); + const ellipsis = '\u2026' as const; + + return ( + + ); +}; + +export const Pagination = forwardRef(PaginationWithRef); -- cgit v1.2.3