aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-09-26 18:43:11 +0200
committerArmand Philippot <git@armandphilippot.com>2023-10-24 12:25:00 +0200
commit388e687857345c85ee550cd5da472675e05a6ff5 (patch)
tree0f035a3cad57a75959c028949a57227a83d480e2 /src/components/molecules
parent70efcfeaa0603415dd992cb662d8efb960e6e49a (diff)
refactor(components): rewrite Button and ButtonLink components
Both: * move styles to Sass placeholders Button: * add `isPressed` prop to Button * add `isLoading` prop to Button (to differentiate state from disabled) ButtonLink: * replace `external` prop with `isExternal` prop * replace `href` prop with `to` prop
Diffstat (limited to 'src/components/molecules')
-rw-r--r--src/components/molecules/buttons/back-to-top.module.scss5
-rw-r--r--src/components/molecules/buttons/back-to-top.stories.tsx6
-rw-r--r--src/components/molecules/buttons/back-to-top.test.tsx11
-rw-r--r--src/components/molecules/buttons/back-to-top.tsx28
-rw-r--r--src/components/molecules/buttons/help-button.tsx4
-rw-r--r--src/components/molecules/layout/card.tsx31
-rw-r--r--src/components/molecules/nav/pagination.tsx40
7 files changed, 69 insertions, 56 deletions
diff --git a/src/components/molecules/buttons/back-to-top.module.scss b/src/components/molecules/buttons/back-to-top.module.scss
index f5b3acd..7eae03b 100644
--- a/src/components/molecules/buttons/back-to-top.module.scss
+++ b/src/components/molecules/buttons/back-to-top.module.scss
@@ -4,6 +4,7 @@
.link {
width: clamp(#{fun.convert-px(48)}, 8vw, #{fun.convert-px(55)});
height: clamp(#{fun.convert-px(48)}, 8vw, #{fun.convert-px(55)});
+ padding: 0;
svg {
width: 100%;
@@ -18,7 +19,9 @@
.arrow-bar {
opacity: 0;
transform: translateY(30%) scaleY(0);
- transition: transform 0.45s ease-in-out 0s, opacity 0.1s linear 0.2s;
+ transition:
+ transform 0.45s ease-in-out 0s,
+ opacity 0.1s linear 0.2s;
}
}
diff --git a/src/components/molecules/buttons/back-to-top.stories.tsx b/src/components/molecules/buttons/back-to-top.stories.tsx
index 5de12d4..40acd33 100644
--- a/src/components/molecules/buttons/back-to-top.stories.tsx
+++ b/src/components/molecules/buttons/back-to-top.stories.tsx
@@ -1,4 +1,4 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { BackToTop as BackToTopComponent } from './back-to-top';
/**
@@ -21,7 +21,7 @@ export default {
required: false,
},
},
- target: {
+ to: {
control: {
type: 'text',
},
@@ -43,5 +43,5 @@ const Template: ComponentStory<typeof BackToTopComponent> = (args) => (
*/
export const BackToTop = Template.bind({});
BackToTop.args = {
- target: 'top',
+ to: 'top',
};
diff --git a/src/components/molecules/buttons/back-to-top.test.tsx b/src/components/molecules/buttons/back-to-top.test.tsx
index aaae3ef..a775841 100644
--- a/src/components/molecules/buttons/back-to-top.test.tsx
+++ b/src/components/molecules/buttons/back-to-top.test.tsx
@@ -1,11 +1,14 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
+import { render, screen as rtlScreen } from '../../../../tests/utils';
import { BackToTop } from './back-to-top';
describe('BackToTop', () => {
it('renders a BackToTop link', () => {
- render(<BackToTop target="top" />);
- expect(screen.getByRole('link')).toHaveAccessibleName('Back to top');
- expect(screen.getByRole('link')).toHaveAttribute('href', '#top');
+ const id = 'top';
+
+ render(<BackToTop to={id} />);
+
+ expect(rtlScreen.getByRole('link')).toHaveAccessibleName('Back to top');
+ expect(rtlScreen.getByRole('link')).toHaveAttribute('href', `#${id}`);
});
});
diff --git a/src/components/molecules/buttons/back-to-top.tsx b/src/components/molecules/buttons/back-to-top.tsx
index d28d6c1..6ca6f10 100644
--- a/src/components/molecules/buttons/back-to-top.tsx
+++ b/src/components/molecules/buttons/back-to-top.tsx
@@ -1,13 +1,13 @@
-import { FC } from 'react';
+import type { FC, HTMLAttributes } from 'react';
import { useIntl } from 'react-intl';
-import { Arrow, ButtonLink, type ButtonLinkProps } from '../../atoms';
+import { Arrow, ButtonLink } from '../../atoms';
import styles from './back-to-top.module.scss';
-export type BackToTopProps = Pick<ButtonLinkProps, 'target'> & {
+export type BackToTopProps = HTMLAttributes<HTMLDivElement> & {
/**
- * Set additional classnames to the button wrapper.
+ * Define the element id to us as anchor.
*/
- className?: string;
+ to: string;
};
/**
@@ -15,23 +15,31 @@ export type BackToTopProps = Pick<ButtonLinkProps, 'target'> & {
*
* Render a back to top link.
*/
-export const BackToTop: FC<BackToTopProps> = ({ className = '', target }) => {
+export const BackToTop: FC<BackToTopProps> = ({
+ className = '',
+ to,
+ ...props
+}) => {
const intl = useIntl();
const linkName = intl.formatMessage({
defaultMessage: 'Back to top',
description: 'BackToTop: link text',
id: 'm+SUSR',
});
+ const btnClass = `${styles.wrapper} ${className}`;
+ const anchor = `#${to}`;
return (
- <div className={`${styles.wrapper} ${className}`}>
+ <div {...props} className={btnClass}>
<ButtonLink
- shape="square"
- target={`#${target}`}
aria-label={linkName}
className={styles.link}
+ // eslint-disable-next-line react/jsx-no-literals -- Shape allowed
+ shape="square"
+ to={anchor}
>
- <Arrow aria-hidden={true} direction="top" />
+ {/* eslint-disable-next-line react/jsx-no-literals -- Direction allowed */}
+ <Arrow aria-hidden direction="top" />
</ButtonLink>
</div>
);
diff --git a/src/components/molecules/buttons/help-button.tsx b/src/components/molecules/buttons/help-button.tsx
index 1234835..7a01b14 100644
--- a/src/components/molecules/buttons/help-button.tsx
+++ b/src/components/molecules/buttons/help-button.tsx
@@ -1,11 +1,11 @@
-import { forwardRef, ForwardRefRenderFunction } from 'react';
+import { forwardRef, type ForwardRefRenderFunction } from 'react';
import { useIntl } from 'react-intl';
import { Button, type ButtonProps } from '../../atoms';
import styles from './help-button.module.scss';
export type HelpButtonProps = Pick<
ButtonProps,
- 'aria-pressed' | 'className' | 'onClick'
+ 'className' | 'isPressed' | 'onClick'
>;
const HelpButtonWithRef: ForwardRefRenderFunction<
diff --git a/src/components/molecules/layout/card.tsx b/src/components/molecules/layout/card.tsx
index c342d0e..f39a430 100644
--- a/src/components/molecules/layout/card.tsx
+++ b/src/components/molecules/layout/card.tsx
@@ -1,9 +1,9 @@
-import { FC } from 'react';
-import { type Image } from '../../../types';
+import type { FC } from 'react';
+import type { Image as Img } from '../../../types';
import { ButtonLink, Heading, type HeadingLevel } from '../../atoms';
import { ResponsiveImage } from '../images';
-import { Meta, type MetaData } from './meta';
import styles from './card.module.scss';
+import { Meta, type MetaData } from './meta';
export type CardProps = {
/**
@@ -13,7 +13,7 @@ export type CardProps = {
/**
* The card cover.
*/
- cover?: Image;
+ cover?: Img;
/**
* The card id.
*/
@@ -55,29 +55,32 @@ export const Card: FC<CardProps> = ({
titleLevel,
url,
}) => {
+ const cardClass = `${styles.wrapper} ${className}`;
+ const headingId = `${id}-heading`;
+
return (
- <ButtonLink
- aria-labelledby={`${id}-heading`}
- className={`${styles.wrapper} ${className}`}
- target={url}
- >
+ <ButtonLink aria-labelledby={headingId} className={cardClass} to={url}>
<article className={styles.article}>
<header className={styles.header}>
- {cover && <ResponsiveImage {...cover} className={styles.cover} />}
+ {cover ? (
+ <ResponsiveImage {...cover} className={styles.cover} />
+ ) : null}
<Heading
+ // eslint-disable-next-line react/jsx-no-literals -- Hardcoded config
alignment="center"
className={styles.title}
- id={`${id}-heading`}
+ id={headingId}
level={titleLevel}
>
{title}
</Heading>
</header>
- {tagline && <div className={styles.tagline}>{tagline}</div>}
- {meta && (
+ {tagline ? <div className={styles.tagline}>{tagline}</div> : null}
+ {meta ? (
<footer className={styles.footer}>
<Meta
data={meta}
+ // eslint-disable-next-line react/jsx-no-literals -- Hardcoded config
layout="inline"
className={styles.list}
groupClassName={styles.meta__item}
@@ -85,7 +88,7 @@ export const Card: FC<CardProps> = ({
valueClassName={styles.meta__value}
/>
</footer>
- )}
+ ) : null}
</article>
</ButtonLink>
);
diff --git a/src/components/molecules/nav/pagination.tsx b/src/components/molecules/nav/pagination.tsx
index 6fa69f0..27ef1ec 100644
--- a/src/components/molecules/nav/pagination.tsx
+++ b/src/components/molecules/nav/pagination.tsx
@@ -1,4 +1,5 @@
-import { FC, Fragment, ReactNode } from 'react';
+/* eslint-disable max-statements */
+import { type FC, Fragment, type ReactNode } from 'react';
import { useIntl } from 'react-intl';
import { ButtonLink } from '../../atoms';
import styles from './pagination.module.scss';
@@ -78,11 +79,8 @@ export const Pagination: FC<PaginationProps> = ({
* @param {number} end - The last value.
* @returns {number[]} An array from start value to end value.
*/
- const range = (start: number, end: number): number[] => {
- const length = end - start + 1;
-
- return Array.from({ length }, (_, index) => index + start);
- };
+ const range = (start: number, end: number): number[] =>
+ Array.from({ length: end - start + 1 }, (_, index) => index + start);
/**
* Get the pagination range.
@@ -138,21 +136,17 @@ export const Pagination: FC<PaginationProps> = ({
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 (
<li className={styles.item}>
{link ? (
- <ButtonLink
- kind={kind}
- target={link}
- className={`${styles.link} ${styles[linkModifier]}`}
- >
+ <ButtonLink className={linkClass} kind={kind} to={link}>
{body}
</ButtonLink>
) : (
- <span className={`${styles.link} ${styles['link--disabled']}`}>
- {body}
- </span>
+ <span className={disabledLinkClass}>{body}</span>
)}
</li>
);
@@ -187,6 +181,7 @@ export const Pagination: FC<PaginationProps> = ({
{
number: page,
a11y: (chunks: ReactNode) => (
+ // eslint-disable-next-line react/jsx-no-literals
<span className="screen-reader-text">
{page === currentPage && currentPagePrefix}
{chunks}
@@ -199,19 +194,20 @@ export const Pagination: FC<PaginationProps> = ({
? undefined
: `${baseUrl}${page}`;
- return <Fragment key={`item-${id}`}>{getItem(id, body, url)}</Fragment>;
+ 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={`${styles.wrapper} ${className}`}>
- <ul className={`${styles.list} ${styles['list--pages']}`}>
- {getPages(current, totalPages)}
- </ul>
+ <nav {...props} className={navClass}>
+ <ul className={listClass}>{getPages(current, totalPages)}</ul>
<ul className={styles.list}>
- {hasPreviousPage &&
- getItem('previous', previousPageName, previousPageUrl)}
- {hasNextPage && getItem('next', nextPageName, nextPageUrl)}
+ {hasPreviousPage
+ ? getItem('previous', previousPageName, previousPageUrl)
+ : null}
+ {hasNextPage ? getItem('next', nextPageName, nextPageUrl) : null}
</ul>
</nav>
);