aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-10-24 18:48:57 +0200
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:15:24 +0100
commit3f8ae3f558446aba3870e90c899db25ad9321499 (patch)
tree30824d02705337309d9223f8c5a6bd8fc41d482c /src/components/molecules
parent98044be08600daf6bd7c7e1a4adada319dbcbbaf (diff)
refactor(components): rewrite Pagination component
Diffstat (limited to 'src/components/molecules')
-rw-r--r--src/components/molecules/nav/index.ts1
-rw-r--r--src/components/molecules/nav/pagination.module.scss43
-rw-r--r--src/components/molecules/nav/pagination.stories.tsx171
-rw-r--r--src/components/molecules/nav/pagination.test.tsx27
-rw-r--r--src/components/molecules/nav/pagination.tsx216
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>
- );
-};