diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-24 18:48:57 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:15:24 +0100 |
| commit | 3f8ae3f558446aba3870e90c899db25ad9321499 (patch) | |
| tree | 30824d02705337309d9223f8c5a6bd8fc41d482c /src/components/molecules | |
| parent | 98044be08600daf6bd7c7e1a4adada319dbcbbaf (diff) | |
refactor(components): rewrite Pagination component
Diffstat (limited to 'src/components/molecules')
| -rw-r--r-- | src/components/molecules/nav/index.ts | 1 | ||||
| -rw-r--r-- | src/components/molecules/nav/pagination.module.scss | 43 | ||||
| -rw-r--r-- | src/components/molecules/nav/pagination.stories.tsx | 171 | ||||
| -rw-r--r-- | src/components/molecules/nav/pagination.test.tsx | 27 | ||||
| -rw-r--r-- | src/components/molecules/nav/pagination.tsx | 216 |
5 files changed, 0 insertions, 458 deletions
diff --git a/src/components/molecules/nav/index.ts b/src/components/molecules/nav/index.ts index ca84088..2f9b8e3 100644 --- a/src/components/molecules/nav/index.ts +++ b/src/components/molecules/nav/index.ts @@ -2,4 +2,3 @@ export * from './breadcrumb'; export * from './nav-item'; export * from './nav-link'; export * from './nav-list'; -export * from './pagination'; diff --git a/src/components/molecules/nav/pagination.module.scss b/src/components/molecules/nav/pagination.module.scss deleted file mode 100644 index 8b06a95..0000000 --- a/src/components/molecules/nav/pagination.module.scss +++ /dev/null @@ -1,43 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; - -.wrapper { - .list { - justify-content: center; - - &--pages { - margin-bottom: var(--spacing-sm); - } - } - - .link { - height: 100%; - min-width: 5ch; - min-height: 6ex; - position: relative; - - &:not(&--disabled) { - &:hover, - &:focus { - z-index: 3; - } - } - - &--number { - padding: 0; - } - - &--disabled { - display: flex; - place-content: center; - align-items: center; - background: var(--color-bg); - border: fun.convert-px(3) solid var(--color-primary-darker); - border-radius: fun.convert-px(5); - color: var(--color-primary-darker); - font-size: var(--font-size-md); - font-weight: 600; - text-decoration: underline transparent 0; - transform: scale(var(--scale-down, 0.94)); - } - } -} diff --git a/src/components/molecules/nav/pagination.stories.tsx b/src/components/molecules/nav/pagination.stories.tsx deleted file mode 100644 index 678c574..0000000 --- a/src/components/molecules/nav/pagination.stories.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Pagination } from './pagination'; - -/** - * Pagination - Storybook Meta - */ -export default { - title: 'Molecules/Navigation/Pagination', - component: Pagination, - args: { - baseUrl: '/page/', - siblings: 1, - }, - argTypes: { - 'aria-label': { - control: { - type: 'text', - }, - description: 'An accessible name for the pagination.', - table: { - category: 'Accessibility', - }, - type: { - name: 'string', - required: false, - }, - }, - baseUrl: { - control: { - type: 'text', - }, - description: 'The url prefix.', - table: { - category: 'Options', - defaultValue: { summary: '/page/' }, - }, - type: { - name: 'string', - required: false, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the pagination wrapper.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - current: { - control: { - type: 'number', - }, - description: 'The current page number.', - type: { - name: 'number', - required: true, - }, - }, - perPage: { - control: { - type: 'number', - }, - description: 'The number of items per page.', - type: { - name: 'number', - required: true, - }, - }, - siblings: { - control: { - type: 'number', - }, - description: - 'The number of pages to show next to the current page for one side.', - table: { - category: 'Options', - defaultValue: { summary: 1 }, - }, - type: { - name: 'number', - required: false, - }, - }, - total: { - control: { - type: 'number', - }, - description: 'The total number of items.', - type: { - name: 'number', - required: true, - }, - }, - }, -} as ComponentMeta<typeof Pagination>; - -const Template: ComponentStory<typeof Pagination> = (args) => ( - <Pagination {...args} /> -); - -/** - * Pagination Stories - Less than 5 pages - */ -export const WithoutDots = Template.bind({}); -WithoutDots.args = { - current: 2, - perPage: 10, - siblings: 2, - total: 50, -}; - -/** - * Pagination Stories - Truncated to the right. - */ -export const RightDots = Template.bind({}); -RightDots.args = { - current: 2, - perPage: 10, - siblings: 2, - total: 80, -}; - -/** - * Pagination Stories - Truncated to the left. - */ -export const LeftDots = Template.bind({}); -LeftDots.args = { - current: 7, - perPage: 10, - siblings: 2, - total: 80, -}; - -/** - * Pagination Stories - Truncated both sides. - */ -export const LeftAndRightDots = Template.bind({}); -LeftAndRightDots.args = { - current: 6, - perPage: 10, - siblings: 2, - total: 150, -}; - -/** - * Pagination Stories - Without previous link - */ -export const WithoutPreviousLink = Template.bind({}); -WithoutPreviousLink.args = { - current: 1, - perPage: 10, - siblings: 2, - total: 50, -}; - -/** - * Pagination Stories - Without next link - */ -export const WithoutNextLink = Template.bind({}); -WithoutNextLink.args = { - current: 5, - perPage: 10, - siblings: 2, - total: 50, -}; diff --git a/src/components/molecules/nav/pagination.test.tsx b/src/components/molecules/nav/pagination.test.tsx deleted file mode 100644 index 7662d5f..0000000 --- a/src/components/molecules/nav/pagination.test.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { Pagination } from './pagination'; - -const total = 50; -const perPage = 10; - -describe('Pagination', () => { - it('renders previous and next page links', () => { - render(<Pagination current={2} total={total} perPage={perPage} />); - expect( - screen.getByRole('link', { name: /Previous page/i }) - ).toBeInTheDocument(); - expect( - screen.getByRole('link', { name: /Next page/i }) - ).toBeInTheDocument(); - }); - - it('renders the page links except for the current one', () => { - render( - <Pagination current={2} siblings={2} total={total} perPage={perPage} /> - ); - expect(screen.getAllByRole('link', { name: /Page / })).toHaveLength( - total / perPage - 1 - ); - }); -}); diff --git a/src/components/molecules/nav/pagination.tsx b/src/components/molecules/nav/pagination.tsx deleted file mode 100644 index 73517c3..0000000 --- a/src/components/molecules/nav/pagination.tsx +++ /dev/null @@ -1,216 +0,0 @@ -/* eslint-disable max-statements */ -import { type FC, Fragment, type ReactNode } from 'react'; -import { useIntl } from 'react-intl'; -import { ButtonLink, List, ListItem } from '../../atoms'; -import styles from './pagination.module.scss'; - -export type PaginationProps = { - /** - * An accessible name for the pagination. - */ - 'aria-label'?: string; - /** - * The url part before page number. Default: /page/ - */ - baseUrl?: string; - /** - * Set additional classnames to the pagination wrapper. - */ - className?: string; - /** - * The current page number. - */ - current: number; - /** - * The number of items per page. - */ - perPage: number; - /** - * The number of siblings on one side of the current page. Default: 1. - */ - siblings?: number; - /** - * The total number of items. - */ - total: number; -}; - -/** - * Pagination component - * - * Render a page-based navigation. - */ -export const Pagination: FC<PaginationProps> = ({ - baseUrl = '/page/', - className = '', - current, - perPage, - siblings = 2, - total, - ...props -}) => { - const intl = useIntl(); - const totalPages = Math.round(total / perPage); - const hasPreviousPage = current > 1; - const previousPageName = intl.formatMessage( - { - defaultMessage: '{icon} Previous page', - description: 'Pagination: previous page link', - id: 'aMFqPH', - }, - { icon: '←' } - ); - const previousPageUrl = `${baseUrl}${current - 1}`; - const hasNextPage = current < totalPages; - const nextPageName = intl.formatMessage( - { - defaultMessage: 'Next page {icon}', - description: 'Pagination: Next page link', - id: 'R4yaW6', - }, - { icon: '→' } - ); - const nextPageUrl = `${baseUrl}${current + 1}`; - - /** - * Create an array with a range of values from start value to end value. - * - * @param {number} start - The first value. - * @param {number} end - The last value. - * @returns {number[]} An array from start value to end value. - */ - const range = (start: number, end: number): number[] => - Array.from({ length: end - start + 1 }, (_, index) => index + start); - - /** - * Get the pagination range. - * - * @param currentPage - The current page number. - * @param maxPages - The total pages number. - * @returns {(number|string)[]} An array of page numbers with or without dots. - */ - const getPaginationRange = ( - currentPage: number, - maxPages: number - ): (number | string)[] => { - const dots = '\u2026'; - - /** - * Show left dots if current page less left siblings is greater than the - * first two pages. - */ - const hasLeftDots = currentPage - siblings > 2; - - /** - * Show right dots if current page plus right siblings is lower than the - * total of pages less the last page. - */ - const hasRightDots = currentPage + siblings < maxPages - 1; - - if (hasLeftDots && hasRightDots) { - const middleItems = range(currentPage - siblings, currentPage + siblings); - return [1, dots, ...middleItems, dots, maxPages]; - } - - if (hasLeftDots) { - const rightItems = range(currentPage - siblings, maxPages); - return [1, dots, ...rightItems]; - } - - if (hasRightDots) { - const leftItems = range(1, currentPage + siblings); - return [...leftItems, dots, maxPages]; - } - - return range(1, maxPages); - }; - - /** - * Get a link or a span wrapped in a list item. - * - * @param {string} id - The item id. - * @param {ReactNode} body - The link body. - * @param {string} [link] - An URL. - * @returns {JSX.Element} The list item. - */ - const getItem = (id: string, body: ReactNode, link?: string): JSX.Element => { - const linkModifier = id.startsWith('page') ? 'link--number' : ''; - const kind = id === 'previous' || id === 'next' ? 'tertiary' : 'secondary'; - const linkClass = `${styles.link} ${styles[linkModifier]}`; - const disabledLinkClass = `${styles.link} ${styles['link--disabled']}`; - - return ( - <ListItem className={styles.item}> - {link ? ( - <ButtonLink className={linkClass} kind={kind} to={link}> - {body} - </ButtonLink> - ) : ( - <span className={disabledLinkClass}>{body}</span> - )} - </ListItem> - ); - }; - - /** - * Get the list of pages. - * - * @param {number} currentPage - The current page number. - * @param {number} maxPages - The total of pages. - * @returns {JSX.Element[]} The list items. - */ - const getPages = (currentPage: number, maxPages: number): JSX.Element[] => { - const pagesRange = getPaginationRange(currentPage, maxPages); - - return pagesRange.map((page, index) => { - const id = typeof page === 'string' ? `dots-${index}` : `page-${page}`; - const currentPagePrefix = intl.formatMessage({ - defaultMessage: 'You are here:', - description: 'Pagination: current page indication', - id: 'yE/Jdz', - }); - const body = - typeof page === 'string' - ? page // dots - : intl.formatMessage( - { - defaultMessage: '<a11y>Page </a11y>{number}', - description: 'Pagination: page number', - id: 'TSXPzr', - }, - { - number: page, - a11y: (chunks: ReactNode) => ( - // eslint-disable-next-line react/jsx-no-literals - <span className="screen-reader-text"> - {page === currentPage && currentPagePrefix} - {chunks} - </span> - ), - } - ); - const url = - page === currentPage || typeof page === 'string' - ? undefined - : `${baseUrl}${page}`; - - return <Fragment key={id}>{getItem(id, body, url)}</Fragment>; - }); - }; - const navClass = `${styles.wrapper} ${className}`; - const listClass = `${styles.list} ${styles['list--pages']}`; - - return ( - <nav {...props} className={navClass}> - <List className={listClass} hideMarker isInline spacing="2xs"> - {getPages(current, totalPages)} - </List> - <List className={styles.list} hideMarker isInline spacing="xs"> - {hasPreviousPage - ? getItem('previous', previousPageName, previousPageUrl) - : null} - {hasNextPage ? getItem('next', nextPageName, nextPageUrl) : null} - </List> - </nav> - ); -}; |
