aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-10-09 18:26:23 +0200
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:14:41 +0100
commit15522ec9146f6f1956620355c44dea2a6a75b67c (patch)
tree7be0c4ca96cb3e59d2ee989785a6b6a286e6169d
parent891441a76173c708c6604fa203b175aefa222333 (diff)
refactor(components): replace ResponsiveImage with Figure component
The styles applied to ResponsiveImage are related to the figure and figcaption elements. Those elements could be use with other contents than images. So I extracted them in a Figure component. The ResponsiveImage component is no longer useful: the consumer should use the Image component from `next` and wrap it in a link if needed.
-rw-r--r--src/components/atoms/figure/figure.module.scss30
-rw-r--r--src/components/atoms/figure/figure.stories.tsx74
-rw-r--r--src/components/atoms/figure/figure.test.tsx32
-rw-r--r--src/components/atoms/figure/figure.tsx72
-rw-r--r--src/components/atoms/figure/index.ts1
-rw-r--r--src/components/atoms/index.ts1
-rw-r--r--src/components/molecules/images/index.ts1
-rw-r--r--src/components/molecules/images/responsive-image.module.scss84
-rw-r--r--src/components/molecules/images/responsive-image.stories.tsx212
-rw-r--r--src/components/molecules/images/responsive-image.test.tsx19
-rw-r--r--src/components/molecules/images/responsive-image.tsx91
-rw-r--r--src/components/molecules/layout/card.fixture.ts (renamed from src/components/molecules/layout/card.fixture.tsx)2
-rw-r--r--src/components/molecules/layout/card.module.scss1
-rw-r--r--src/components/molecules/layout/card.tsx11
-rw-r--r--src/components/organisms/images/gallery.stories.tsx23
-rw-r--r--src/components/organisms/images/gallery.test.tsx30
-rw-r--r--src/components/organisms/images/gallery.tsx5
-rw-r--r--src/components/organisms/layout/overview.stories.tsx6
-rw-r--r--src/components/organisms/layout/overview.tsx17
-rw-r--r--src/components/organisms/layout/summary.fixture.ts (renamed from src/components/organisms/layout/summary.fixture.tsx)4
-rw-r--r--src/components/organisms/layout/summary.module.scss1
-rw-r--r--src/components/organisms/layout/summary.tsx20
-rw-r--r--src/components/organisms/widgets/image-widget.module.scss1
-rw-r--r--src/components/organisms/widgets/image-widget.tsx33
-rw-r--r--src/i18n/en.json16
-rw-r--r--src/i18n/fr.json16
-rw-r--r--src/pages/article/[slug].tsx4
-rw-r--r--src/pages/index.tsx9
-rw-r--r--src/pages/mentions-legales.tsx9
-rw-r--r--src/pages/projets/[slug].tsx10
-rw-r--r--src/pages/sujet/[slug].tsx4
31 files changed, 337 insertions, 502 deletions
diff --git a/src/components/atoms/figure/figure.module.scss b/src/components/atoms/figure/figure.module.scss
new file mode 100644
index 0000000..e7ba5c2
--- /dev/null
+++ b/src/components/atoms/figure/figure.module.scss
@@ -0,0 +1,30 @@
+@use "../../../styles/abstracts/functions" as fun;
+
+.caption {
+ margin: 0;
+ padding: fun.convert-px(4) var(--spacing-2xs);
+ background: var(--color-bg-secondary);
+ border: fun.convert-px(1) solid var(--color-border-light);
+ font-size: var(--font-size-sm);
+ font-weight: 500;
+}
+
+.wrapper {
+ display: flex;
+ flex-flow: column;
+ width: fit-content;
+ margin: 0 auto;
+ position: relative;
+ text-align: center;
+
+ &--has-borders {
+ padding: fun.convert-px(4);
+ border: fun.convert-px(1) solid var(--color-border);
+ box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) 0
+ var(--color-shadow);
+
+ .caption {
+ margin-top: fun.convert-px(4);
+ }
+ }
+}
diff --git a/src/components/atoms/figure/figure.stories.tsx b/src/components/atoms/figure/figure.stories.tsx
new file mode 100644
index 0000000..7763641
--- /dev/null
+++ b/src/components/atoms/figure/figure.stories.tsx
@@ -0,0 +1,74 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import NextImage from 'next/image';
+import { Figure } from './figure';
+
+/**
+ * Figure - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Figure',
+ component: Figure,
+ args: {},
+ argTypes: {
+ caption: {
+ control: {
+ type: 'text',
+ },
+ description: 'A figure caption.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ hasBorders: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Add borders around the figure.',
+ table: {
+ category: 'Styles',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta<typeof Figure>;
+
+const Template: ComponentStory<typeof Figure> = (args) => <Figure {...args} />;
+
+/**
+ * Figure Stories - Illustration
+ */
+export const Illustration = Template.bind({});
+Illustration.args = {
+ children: (
+ <NextImage
+ alt="An example"
+ height={480}
+ src="https://picsum.photos/640/480"
+ width={640}
+ />
+ ),
+};
+
+/**
+ * Figure Stories - BorderedIllustration
+ */
+export const BorderedIllustration = Template.bind({});
+BorderedIllustration.args = {
+ children: (
+ <NextImage
+ alt="An example"
+ height={480}
+ src="https://picsum.photos/640/480"
+ width={640}
+ />
+ ),
+ hasBorders: true,
+};
diff --git a/src/components/atoms/figure/figure.test.tsx b/src/components/atoms/figure/figure.test.tsx
new file mode 100644
index 0000000..90a07c7
--- /dev/null
+++ b/src/components/atoms/figure/figure.test.tsx
@@ -0,0 +1,32 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { Figure } from './figure';
+
+describe('Figure', () => {
+ it('renders the figure contents', () => {
+ const body = 'tempora et quis';
+
+ render(<Figure>{body}</Figure>);
+
+ expect(rtlScreen.getByRole('figure')).toHaveTextContent(body);
+ });
+
+ it('can render its contents with a caption', () => {
+ const body = 'tempora et quis';
+ const caption = 'velit dolores magnam';
+
+ render(<Figure caption={caption}>{body}</Figure>);
+
+ expect(rtlScreen.getByRole('figure', { name: caption })).toHaveTextContent(
+ body
+ );
+ });
+
+ it('can style the figure with borders', () => {
+ const body = 'tempora et quis';
+
+ render(<Figure hasBorders>{body}</Figure>);
+
+ expect(rtlScreen.getByRole('figure')).toHaveClass('wrapper--has-borders');
+ });
+});
diff --git a/src/components/atoms/figure/figure.tsx b/src/components/atoms/figure/figure.tsx
new file mode 100644
index 0000000..4dd5b10
--- /dev/null
+++ b/src/components/atoms/figure/figure.tsx
@@ -0,0 +1,72 @@
+import {
+ forwardRef,
+ type ReactNode,
+ type ForwardRefRenderFunction,
+ type HTMLAttributes,
+ useId,
+} from 'react';
+import styles from './figure.module.scss';
+
+export type FigureProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
+ /**
+ * The contents (ie. an image, illustration, diagram, code snippet, etc.).
+ */
+ children: ReactNode;
+ /**
+ * A figure caption.
+ */
+ caption?: ReactNode;
+ /**
+ * Should we wrap the contents with borders?
+ */
+ hasBorders?: boolean;
+};
+
+const FigureWithRef: ForwardRefRenderFunction<HTMLElement, FigureProps> = (
+ {
+ 'aria-labelledby': ariaLabelledBy,
+ caption,
+ children,
+ className = '',
+ hasBorders,
+ ...props
+ },
+ ref
+) => {
+ const captionId = useId();
+ const bordersModifier = hasBorders ? styles['wrapper--has-borders'] : '';
+ const figureClass = `${styles.wrapper} ${bordersModifier} ${className}`;
+
+ /**
+ * We need to ensure that the figcaption is used as an accessible name for the
+ * figure. In Testing Library, it is not automatically associated, it could
+ * also be the case in some browsers. However if the consumer provide its own
+ * `aria-labelled-by` attribute, it should be used instead of the caption (we
+ * could combine them but we cannot know which order is the more logical).
+ */
+ const figureLabelledBy =
+ caption && !ariaLabelledBy ? captionId : ariaLabelledBy;
+
+ return (
+ <figure
+ {...props}
+ aria-labelledby={figureLabelledBy}
+ className={figureClass}
+ ref={ref}
+ >
+ {children}
+ {caption ? (
+ <figcaption className={styles.caption} id={captionId}>
+ {caption}
+ </figcaption>
+ ) : null}
+ </figure>
+ );
+};
+
+/**
+ * Figure component
+ *
+ * Render a responsive image wrapped in a figure element.
+ */
+export const Figure = forwardRef(FigureWithRef);
diff --git a/src/components/atoms/figure/index.ts b/src/components/atoms/figure/index.ts
new file mode 100644
index 0000000..0f6ad20
--- /dev/null
+++ b/src/components/atoms/figure/index.ts
@@ -0,0 +1 @@
+export * from './figure';
diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts
index e9c41ed..31beda9 100644
--- a/src/components/atoms/index.ts
+++ b/src/components/atoms/index.ts
@@ -1,4 +1,5 @@
export * from './buttons';
+export * from './figure';
export * from './flip';
export * from './forms';
export * from './heading';
diff --git a/src/components/molecules/images/index.ts b/src/components/molecules/images/index.ts
index 33ec886..318a6af 100644
--- a/src/components/molecules/images/index.ts
+++ b/src/components/molecules/images/index.ts
@@ -1,2 +1 @@
export * from './flipping-logo';
-export * from './responsive-image';
diff --git a/src/components/molecules/images/responsive-image.module.scss b/src/components/molecules/images/responsive-image.module.scss
deleted file mode 100644
index e4ed4aa..0000000
--- a/src/components/molecules/images/responsive-image.module.scss
+++ /dev/null
@@ -1,84 +0,0 @@
-@use "../../../styles/abstracts/functions" as fun;
-
-.caption {
- margin: 0;
- padding: fun.convert-px(4) var(--spacing-2xs);
- background: var(--color-bg-secondary);
- border: fun.convert-px(1) solid var(--color-border-light);
- font-size: var(--font-size-sm);
- font-weight: 500;
-}
-
-.wrapper {
- display: flex;
- flex-flow: column;
- width: fit-content;
- margin: 0 auto;
- position: relative;
- text-align: center;
-
- &--has-borders {
- .caption {
- margin-top: fun.convert-px(4);
- }
- }
-
- &--has-borders#{&}--has-link {
- .link {
- padding: fun.convert-px(4);
- }
- }
-
- &--has-borders#{&}--no-link {
- padding: fun.convert-px(4);
- border: fun.convert-px(1) solid var(--color-border);
- box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) 0
- var(--color-shadow);
- }
-}
-
-.img {
- max-height: 100%;
- object-fit: cover;
-}
-
-.link {
- display: flex;
- flex-flow: column;
- background: none;
- border: fun.convert-px(1) solid var(--color-border);
- box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) 0
- var(--color-shadow);
- text-decoration: none;
-
- .caption {
- color: var(--color-primary-darker);
- }
-
- &:hover,
- &:focus {
- box-shadow: 0 0 fun.convert-px(2) 0 var(--color-shadow-light),
- fun.convert-px(2) fun.convert-px(2) fun.convert-px(4) fun.convert-px(1)
- var(--color-shadow-light),
- fun.convert-px(4) fun.convert-px(4) fun.convert-px(8) fun.convert-px(2)
- var(--color-shadow-light);
- transform: scale(var(--scale-up, 1.05));
- }
-
- &:focus {
- .caption {
- text-decoration: underline solid var(--color-primary-darker)
- fun.convert-px(3);
- }
- }
-
- &:active {
- box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1)
- fun.convert-px(1) var(--color-shadow-light);
- transform: scale(var(--scale-down, 0.95));
-
- .caption {
- text-decoration: none;
- }
- }
-}
diff --git a/src/components/molecules/images/responsive-image.stories.tsx b/src/components/molecules/images/responsive-image.stories.tsx
deleted file mode 100644
index cc6b088..0000000
--- a/src/components/molecules/images/responsive-image.stories.tsx
+++ /dev/null
@@ -1,212 +0,0 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { ResponsiveImage } from './responsive-image';
-
-/**
- * ResponsiveImage - Storybook Meta
- */
-export default {
- title: 'Molecules/Images/ResponsiveImage',
- component: ResponsiveImage,
- args: {
- withBorders: false,
- },
- argTypes: {
- alt: {
- control: {
- type: 'text',
- },
- description: 'An alternative text.',
- type: {
- name: 'string',
- required: true,
- },
- },
- caption: {
- control: {
- type: 'text',
- },
- description: 'A figure caption.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- className: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the image wrapper.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- height: {
- control: {
- type: 'number',
- },
- description: 'The image height.',
- type: {
- name: 'string',
- required: true,
- },
- },
- src: {
- control: {
- type: 'text',
- },
- description: 'The image source.',
- type: {
- name: 'string',
- required: true,
- },
- },
- target: {
- control: {
- type: 'text',
- },
- description: 'A link target.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- width: {
- control: {
- type: 'number',
- },
- description: 'The image width.',
- type: {
- name: 'string',
- required: true,
- },
- },
- withBorders: {
- control: {
- type: 'boolean',
- },
- description: 'Add borders around the image.',
- table: {
- category: 'Styles',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
- },
-} as ComponentMeta<typeof ResponsiveImage>;
-
-const Template: ComponentStory<typeof ResponsiveImage> = (args) => (
- <ResponsiveImage {...args} />
-);
-
-/**
- * Responsive Image Stories - Default
- */
-export const Default = Template.bind({});
-Default.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
-};
-
-/**
- * Responsive Image Stories - With borders
- */
-export const WithBorders = Template.bind({});
-WithBorders.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- withBorders: true,
-};
-
-/**
- * Responsive Image Stories - With link
- */
-export const WithLink = Template.bind({});
-WithLink.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- target: '#',
-};
-
-/**
- * Responsive Image Stories - With link and borders
- */
-export const WithLinkAndBorders = Template.bind({});
-WithLinkAndBorders.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- target: '#',
- withBorders: true,
-};
-
-/**
- * Responsive Image Stories - With caption
- */
-export const WithCaption = Template.bind({});
-WithCaption.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- caption: 'Omnis nulla labore',
-};
-
-/**
- * Responsive Image Stories - With caption and borders
- */
-export const WithCaptionAndBorders = Template.bind({});
-WithCaptionAndBorders.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- caption: 'Omnis nulla labore',
- withBorders: true,
-};
-
-/**
- * Responsive Image Stories - With caption and link
- */
-export const WithCaptionAndLink = Template.bind({});
-WithCaptionAndLink.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- caption: 'Omnis nulla labore',
- target: '#',
-};
-
-/**
- * Responsive Image Stories - With caption, link and borders
- */
-export const WithCaptionLinkAndBorders = Template.bind({});
-WithCaptionLinkAndBorders.args = {
- alt: 'An example',
- src: 'http://placeimg.com/640/480/transport',
- width: 640,
- height: 480,
- caption: 'Omnis nulla labore',
- target: '#',
- withBorders: true,
-};
diff --git a/src/components/molecules/images/responsive-image.test.tsx b/src/components/molecules/images/responsive-image.test.tsx
deleted file mode 100644
index dec36ea..0000000
--- a/src/components/molecules/images/responsive-image.test.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { ResponsiveImage } from './responsive-image';
-
-describe('ResponsiveImage', () => {
- it('renders a responsive image', () => {
- render(
- <ResponsiveImage
- src="http://placeimg.com/640/480"
- alt="An alternative text"
- width={640}
- height={480}
- />
- );
- expect(
- screen.getByRole('img', { name: 'An alternative text' })
- ).toBeInTheDocument();
- });
-});
diff --git a/src/components/molecules/images/responsive-image.tsx b/src/components/molecules/images/responsive-image.tsx
deleted file mode 100644
index 85c0c46..0000000
--- a/src/components/molecules/images/responsive-image.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import Image, { type ImageProps } from 'next/image';
-import { FC, ReactNode } from 'react';
-import { Link, type LinkProps } from '../../atoms';
-import styles from './responsive-image.module.scss';
-
-export type ResponsiveImageProps = Omit<
- ImageProps,
- 'alt' | 'width' | 'height'
-> & {
- /**
- * An alternative text.
- */
- alt: string;
- /**
- * A figure caption.
- */
- caption?: ReactNode;
- /**
- * Set additional classnames to the figure wrapper.
- */
- className?: string;
- /**
- * The image height.
- */
- height: number | `${number}`;
- /**
- * A link target.
- */
- target?: LinkProps['href'];
- /**
- * The image width.
- */
- width: number | `${number}`;
- /**
- * Wrap the image with borders.
- */
- withBorders?: boolean;
-};
-
-/**
- * ResponsiveImage component
- *
- * Render a responsive image wrapped in a figure element.
- */
-export const ResponsiveImage: FC<ResponsiveImageProps> = ({
- alt,
- caption,
- className = '',
- target,
- title,
- withBorders,
- ...props
-}) => {
- const bordersModifier = withBorders ? styles['wrapper--has-borders'] : '';
- const linkModifier = target
- ? styles['wrapper--has-link']
- : styles['wrapper--no-link'];
- const figureClass = `${styles.wrapper} ${bordersModifier} ${linkModifier} ${className}`;
-
- return (
- <figure aria-label={caption ? undefined : title} className={figureClass}>
- {target ? (
- <Link href={target} className={styles.link}>
- <Image
- {...props}
- alt={alt}
- className={styles.img}
- sizes="100vw"
- title={title}
- />
- {caption && (
- <figcaption className={styles.caption}>{caption}</figcaption>
- )}
- </Link>
- ) : (
- <>
- <Image
- {...props}
- alt={alt}
- className={styles.img}
- sizes="100vw"
- title={title}
- />
- {caption && (
- <figcaption className={styles.caption}>{caption}</figcaption>
- )}
- </>
- )}
- </figure>
- );
-};
diff --git a/src/components/molecules/layout/card.fixture.tsx b/src/components/molecules/layout/card.fixture.ts
index f96cc43..01fe2e9 100644
--- a/src/components/molecules/layout/card.fixture.tsx
+++ b/src/components/molecules/layout/card.fixture.ts
@@ -1,7 +1,7 @@
export const cover = {
alt: 'A picture',
height: 480,
- src: 'http://placeimg.com/640/480',
+ src: 'https://picsum.photos/640/480',
width: 640,
};
diff --git a/src/components/molecules/layout/card.module.scss b/src/components/molecules/layout/card.module.scss
index 31f6a4b..7a06508 100644
--- a/src/components/molecules/layout/card.module.scss
+++ b/src/components/molecules/layout/card.module.scss
@@ -20,6 +20,7 @@
.cover {
place-content: center;
height: fun.convert-px(150);
+ object-fit: scale-down;
margin: auto;
border-bottom: fun.convert-px(1) solid var(--color-border);
}
diff --git a/src/components/molecules/layout/card.tsx b/src/components/molecules/layout/card.tsx
index c9e7a90..c316100 100644
--- a/src/components/molecules/layout/card.tsx
+++ b/src/components/molecules/layout/card.tsx
@@ -1,7 +1,6 @@
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import type { FC } from 'react';
-import type { Image as Img } from '../../../types';
-import { ButtonLink, Heading, type HeadingLevel } from '../../atoms';
-import { ResponsiveImage } from '../images';
+import { ButtonLink, Figure, Heading, type HeadingLevel } from '../../atoms';
import styles from './card.module.scss';
import { Meta, type MetaData } from './meta';
@@ -13,7 +12,7 @@ export type CardProps = {
/**
* The card cover.
*/
- cover?: Img;
+ cover?: Pick<NextImageProps, 'alt' | 'src' | 'title' | 'width' | 'height'>;
/**
* The card id.
*/
@@ -63,7 +62,9 @@ export const Card: FC<CardProps> = ({
<article className={styles.article}>
<header className={styles.header}>
{cover ? (
- <ResponsiveImage {...cover} className={styles.cover} />
+ <Figure>
+ <NextImage {...cover} className={styles.cover} />
+ </Figure>
) : null}
<Heading className={styles.title} id={headingId} level={titleLevel}>
{title}
diff --git a/src/components/organisms/images/gallery.stories.tsx b/src/components/organisms/images/gallery.stories.tsx
index 5005ed8..016b18e 100644
--- a/src/components/organisms/images/gallery.stories.tsx
+++ b/src/components/organisms/images/gallery.stories.tsx
@@ -1,5 +1,6 @@
import type { ComponentMeta, ComponentStory } from '@storybook/react';
-import { ResponsiveImage } from '../../molecules';
+import NextImage from 'next/image';
+import { Figure } from '../../atoms';
import { Gallery } from './gallery';
/**
@@ -13,7 +14,7 @@ export default {
control: {
type: null,
},
- description: 'Two or more ResponsiveImage component.',
+ description: 'Two or more images.',
type: {
name: 'function',
required: true,
@@ -37,16 +38,24 @@ export default {
const image = {
alt: 'Modi provident omnis',
height: 480,
- src: 'http://picsum.photos/640/480',
+ src: 'https://picsum.photos/640/480',
width: 640,
};
const Template: ComponentStory<typeof Gallery> = (args) => (
<Gallery {...args}>
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
+ <Figure>
+ <NextImage {...image} />
+ </Figure>
+ <Figure>
+ <NextImage {...image} />
+ </Figure>
+ <Figure>
+ <NextImage {...image} />
+ </Figure>
+ <Figure>
+ <NextImage {...image} />
+ </Figure>
</Gallery>
);
diff --git a/src/components/organisms/images/gallery.test.tsx b/src/components/organisms/images/gallery.test.tsx
index ea39348..bffc3b2 100644
--- a/src/components/organisms/images/gallery.test.tsx
+++ b/src/components/organisms/images/gallery.test.tsx
@@ -1,6 +1,6 @@
import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { ResponsiveImage } from '../../molecules';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import NextImage from 'next/image';
import { Gallery } from './gallery';
const columns = 3;
@@ -8,7 +8,7 @@ const columns = 3;
const image = {
alt: 'Modi provident omnis',
height: 480,
- src: 'http://placeimg.com/640/480/fashion',
+ src: 'http://picsum.photos/640/480',
width: 640,
};
@@ -16,24 +16,28 @@ describe('Gallery', () => {
it('renders the correct number of items', () => {
render(
<Gallery columns={columns}>
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
+ <NextImage {...image} />
+ <NextImage {...image} />
+ <NextImage {...image} />
+ <NextImage {...image} />
</Gallery>
);
- expect(screen.getAllByRole('listitem')).toHaveLength(4);
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect(rtlScreen.getAllByRole('listitem')).toHaveLength(4);
});
it('renders the right number of columns', () => {
render(
<Gallery columns={columns}>
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
- <ResponsiveImage {...image} />
+ <NextImage {...image} />
+ <NextImage {...image} />
+ <NextImage {...image} />
+ <NextImage {...image} />
</Gallery>
);
- expect(screen.getByRole('list')).toHaveClass(`wrapper--${columns}-columns`);
+ expect(rtlScreen.getByRole('list')).toHaveClass(
+ `wrapper--${columns}-columns`
+ );
});
});
diff --git a/src/components/organisms/images/gallery.tsx b/src/components/organisms/images/gallery.tsx
index b35acfe..2f17130 100644
--- a/src/components/organisms/images/gallery.tsx
+++ b/src/components/organisms/images/gallery.tsx
@@ -1,6 +1,5 @@
import { Children, type FC, type ReactElement } from 'react';
import { List, ListItem } from '../../atoms';
-import type { ResponsiveImageProps } from '../../molecules';
import styles from './gallery.module.scss';
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
@@ -8,9 +7,9 @@ export type GalleryColumn = 2 | 3 | 4;
export type GalleryProps = {
/**
- * The images using ResponsiveImage component.
+ * The images.
*/
- children: ReactElement<ResponsiveImageProps>[];
+ children: ReactElement[];
/**
* The columns count.
*/
diff --git a/src/components/organisms/layout/overview.stories.tsx b/src/components/organisms/layout/overview.stories.tsx
index be6db72..8f56d3a 100644
--- a/src/components/organisms/layout/overview.stories.tsx
+++ b/src/components/organisms/layout/overview.stories.tsx
@@ -1,5 +1,5 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
-import { Overview, OverviewMeta } from './overview';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Overview, type OverviewMeta } from './overview';
/**
* Overview - Storybook Meta
@@ -50,7 +50,7 @@ const Template: ComponentStory<typeof Overview> = (args) => (
const cover = {
alt: 'picture',
height: 480,
- src: 'http://placeimg.com/640/480/cats',
+ src: 'https://picsum.photos/640/480',
width: 640,
};
diff --git a/src/components/organisms/layout/overview.tsx b/src/components/organisms/layout/overview.tsx
index bb319c4..8af58ec 100644
--- a/src/components/organisms/layout/overview.tsx
+++ b/src/components/organisms/layout/overview.tsx
@@ -1,10 +1,7 @@
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import type { FC } from 'react';
-import {
- Meta,
- type MetaData,
- ResponsiveImage,
- type ResponsiveImageProps,
-} from '../../molecules';
+import { Figure } from '../../atoms';
+import { Meta, type MetaData } from '../../molecules';
import styles from './overview.module.scss';
export type OverviewMeta = Pick<
@@ -25,7 +22,7 @@ export type OverviewProps = {
/**
* The overview cover.
*/
- cover?: Pick<ResponsiveImageProps, 'alt' | 'src' | 'width' | 'height'>;
+ cover?: Pick<NextImageProps, 'alt' | 'src' | 'width' | 'height'>;
/**
* The overview meta.
*/
@@ -47,7 +44,11 @@ export const Overview: FC<OverviewProps> = ({
return (
<div className={`${styles.wrapper} ${className}`}>
- {cover ? <ResponsiveImage className={styles.cover} {...cover} /> : null}
+ {cover ? (
+ <Figure>
+ <NextImage {...cover} className={styles.cover} />
+ </Figure>
+ ) : null}
<Meta
className={`${styles.meta} ${metaModifier}`}
data={{ ...remainingMeta, technologies }}
diff --git a/src/components/organisms/layout/summary.fixture.tsx b/src/components/organisms/layout/summary.fixture.ts
index bb3ebcb..6f90b4a 100644
--- a/src/components/organisms/layout/summary.fixture.tsx
+++ b/src/components/organisms/layout/summary.fixture.ts
@@ -1,9 +1,9 @@
-import { type SummaryMeta } from './summary';
+import type { SummaryMeta } from './summary';
export const cover = {
alt: 'A cover',
height: 480,
- src: 'http://placeimg.com/640/480',
+ src: 'https://picsum.photos/640/480',
width: 640,
};
diff --git a/src/components/organisms/layout/summary.module.scss b/src/components/organisms/layout/summary.module.scss
index b6cb4f4..9dc1a69 100644
--- a/src/components/organisms/layout/summary.module.scss
+++ b/src/components/organisms/layout/summary.module.scss
@@ -46,6 +46,7 @@
height: fun.convert-px(100);
max-width: 100%;
border: fun.convert-px(1) solid var(--color-border);
+ object-fit: scale-down;
@include mix.media("screen") {
@include mix.dimensions("sm") {
diff --git a/src/components/organisms/layout/summary.tsx b/src/components/organisms/layout/summary.tsx
index e21e4c7..fa3dfe5 100644
--- a/src/components/organisms/layout/summary.tsx
+++ b/src/components/organisms/layout/summary.tsx
@@ -1,3 +1,4 @@
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import type { FC, ReactNode } from 'react';
import { useIntl } from 'react-intl';
import type { Article, Meta as MetaType } from '../../../types';
@@ -8,19 +9,12 @@ import {
type HeadingLevel,
Icon,
Link,
+ Figure,
} from '../../atoms';
-import {
- Meta,
- type MetaData,
- ResponsiveImage,
- type ResponsiveImageProps,
-} from '../../molecules';
+import { Meta, type MetaData } from '../../molecules';
import styles from './summary.module.scss';
-export type Cover = Pick<
- ResponsiveImageProps,
- 'alt' | 'src' | 'width' | 'height'
->;
+export type Cover = Pick<NextImageProps, 'alt' | 'src' | 'width' | 'height'>;
export type SummaryMeta = Pick<
MetaType<'article'>,
@@ -108,7 +102,11 @@ export const Summary: FC<SummaryProps> = ({
return (
<article className={styles.wrapper}>
- {cover ? <ResponsiveImage className={styles.cover} {...cover} /> : null}
+ {cover ? (
+ <Figure>
+ <NextImage {...cover} className={styles.cover} />
+ </Figure>
+ ) : null}
<header className={styles.header}>
<Link href={url} className={styles.link}>
<Heading level={titleLevel} className={styles.title}>
diff --git a/src/components/organisms/widgets/image-widget.module.scss b/src/components/organisms/widgets/image-widget.module.scss
index 2174d5b..25de03e 100644
--- a/src/components/organisms/widgets/image-widget.module.scss
+++ b/src/components/organisms/widgets/image-widget.module.scss
@@ -4,6 +4,7 @@
--scale-up: 1.02;
--scale-down: 0.98;
+ width: fit-content;
margin: 0;
padding: fun.convert-px(5);
border: fun.convert-px(1) solid var(--color-border);
diff --git a/src/components/organisms/widgets/image-widget.tsx b/src/components/organisms/widgets/image-widget.tsx
index 07c4b11..5de8dd8 100644
--- a/src/components/organisms/widgets/image-widget.tsx
+++ b/src/components/organisms/widgets/image-widget.tsx
@@ -1,18 +1,12 @@
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import type { FC } from 'react';
-import {
- ResponsiveImage,
- type ResponsiveImageProps,
- Collapsible,
- type CollapsibleProps,
-} from '../../molecules';
+import { Figure, Link, type FigureProps } from '../../atoms';
+import { Collapsible, type CollapsibleProps } from '../../molecules';
import styles from './image-widget.module.scss';
export type Alignment = 'left' | 'center' | 'right';
-export type Image = Pick<
- ResponsiveImageProps,
- 'alt' | 'height' | 'src' | 'width'
->;
+export type Image = Pick<NextImageProps, 'alt' | 'height' | 'src' | 'width'>;
export type ImageWidgetProps = Omit<
CollapsibleProps,
@@ -25,7 +19,7 @@ export type ImageWidgetProps = Omit<
/**
* Add a caption to the image.
*/
- description?: ResponsiveImageProps['caption'];
+ description?: FigureProps['caption'];
/**
* An object describing the image.
*/
@@ -37,7 +31,7 @@ export type ImageWidgetProps = Omit<
/**
* Add a link to the image.
*/
- url?: ResponsiveImageProps['target'];
+ url?: string;
};
/**
@@ -62,12 +56,19 @@ export const ImageWidget: FC<ImageWidgetProps> = ({
{...props}
className={`${styles[alignmentClass]} ${className}`}
>
- <ResponsiveImage
- {...image}
+ <Figure
caption={description}
className={`${styles.figure} ${imageClassName}`}
- target={url}
- />
+ hasBorders
+ >
+ {url ? (
+ <Link href={url}>
+ <NextImage {...image} />
+ </Link>
+ ) : (
+ <NextImage {...image} />
+ )}
+ </Figure>
</Collapsible>
);
};
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 60744b7..9c33d2a 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -107,6 +107,10 @@
"defaultMessage": "Read more articles about:",
"description": "ArticlePage: footer topics list label"
},
+ "52H2HA": {
+ "defaultMessage": "{website} logo",
+ "description": "Layout: logo title"
+ },
"5O2vpy": {
"defaultMessage": "No results found.",
"description": "NoResults: no results"
@@ -143,6 +147,10 @@
"defaultMessage": "Full includes all information from partial as well as information about referrer, operating system, device, browser, screen size and language.",
"description": "AckeeToggle: tooltip message"
},
+ "8jjY1X": {
+ "defaultMessage": "{website} picture",
+ "description": "Layout: photo alternative text"
+ },
"92zgdp": {
"defaultMessage": "Total:",
"description": "Meta: total label"
@@ -463,10 +471,6 @@
"defaultMessage": "Sidebar",
"description": "PageLayout: accessible name for the sidebar"
},
- "dDK5oc": {
- "defaultMessage": "{website} picture",
- "description": "Branding: photo alternative text"
- },
"dz2kDV": {
"defaultMessage": "Comment form",
"description": "CommentForm: aria label"
@@ -663,10 +667,6 @@
"defaultMessage": "Free",
"description": "HomePage: link to free thematic"
},
- "x55qsD": {
- "defaultMessage": "{website} logo",
- "description": "Branding: logo title"
- },
"xYNeKX": {
"defaultMessage": "Settings form",
"description": "SettingsModal: an accessible form name"
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index 6c1ee26..997e0e0 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -107,6 +107,10 @@
"defaultMessage": "Lire plus d’articles à propos de :",
"description": "ArticlePage: footer topics list label"
},
+ "52H2HA": {
+ "defaultMessage": "Logo du site d’{website}",
+ "description": "Layout: logo title"
+ },
"5O2vpy": {
"defaultMessage": "Aucun résultat.",
"description": "NoResults: no results"
@@ -143,6 +147,10 @@
"defaultMessage": "Complet inclut toutes les informations de Partiel ainsi que des informations à propos du site référent, du système d’exploitation, de l’appareil, du navigateur, de la taille d’écran et de la langue.",
"description": "AckeeToggle: tooltip message"
},
+ "8jjY1X": {
+ "defaultMessage": "Photo d’{website}",
+ "description": "Layout: photo alternative text"
+ },
"92zgdp": {
"defaultMessage": "Total :",
"description": "Meta: total label"
@@ -463,10 +471,6 @@
"defaultMessage": "Barre latérale",
"description": "PageLayout: accessible name for the sidebar"
},
- "dDK5oc": {
- "defaultMessage": "Photo d’{website}",
- "description": "Branding: photo alternative text"
- },
"dz2kDV": {
"defaultMessage": "Formulaire des commentaires",
"description": "CommentForm: aria label"
@@ -663,10 +667,6 @@
"defaultMessage": "Libre",
"description": "HomePage: link to free thematic"
},
- "x55qsD": {
- "defaultMessage": "Logo d’{website}",
- "description": "Branding: logo title"
- },
"xYNeKX": {
"defaultMessage": "Formulaire des réglages",
"description": "SettingsModal: an accessible form name"
diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx
index 523e21d..acb80b2 100644
--- a/src/pages/article/[slug].tsx
+++ b/src/pages/article/[slug].tsx
@@ -2,6 +2,7 @@
import type { ParsedUrlQuery } from 'querystring';
import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
+import NextImage from 'next/image';
import { useRouter } from 'next/router';
import Script from 'next/script';
import type { HTMLAttributes } from 'react';
@@ -12,7 +13,6 @@ import {
Link,
PageLayout,
type PageLayoutProps,
- ResponsiveImage,
Sharing,
Spinner,
} from '../../components';
@@ -108,7 +108,7 @@ const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({
label: footerMetaLabel,
value: topics.map((topic) => (
<ButtonLink className={styles.btn} key={topic.id} to={topic.url}>
- {topic.logo ? <ResponsiveImage {...topic.logo} /> : null} {topic.name}
+ {topic.logo ? <NextImage {...topic.logo} /> : null} {topic.name}
</ButtonLink>
)),
},
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 1f1c9f3..d94160f 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,6 +1,7 @@
import type { MDXComponents } from 'mdx/types';
import type { GetStaticProps } from 'next';
import Head from 'next/head';
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import Script from 'next/script';
import type { FC, HTMLAttributes } from 'react';
import { useIntl } from 'react-intl';
@@ -15,10 +16,10 @@ import {
Icon,
List,
ListItem,
- ResponsiveImage,
Section,
type SectionProps,
Heading,
+ Figure,
} from '../components';
import HomePageContent from '../content/pages/homepage.mdx';
import { getArticlesCard } from '../services/graphql';
@@ -83,6 +84,12 @@ const H6 = ({
</Heading>
);
+const ResponsiveImage = (props: NextImageProps) => (
+ <Figure>
+ <NextImage {...props} />
+ </Figure>
+);
+
/**
* Retrieve a list of coding links.
*
diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx
index 9b0cc98..810d9ec 100644
--- a/src/pages/mentions-legales.tsx
+++ b/src/pages/mentions-legales.tsx
@@ -1,14 +1,15 @@
import type { MDXComponents } from 'mdx/types';
import type { GetStaticProps } from 'next';
import Head from 'next/head';
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import { useRouter } from 'next/router';
import Script from 'next/script';
import {
getLayout,
Link,
PageLayout,
- ResponsiveImage,
type MetaData,
+ Figure,
} from '../components';
import LegalNoticeContent, { meta } from '../content/pages/legal-notice.mdx';
import type { NextPageWithLayout } from '../types';
@@ -21,6 +22,12 @@ import {
import { loadTranslation } from '../utils/helpers/server';
import { useBreadcrumb, useSettings } from '../utils/hooks';
+const ResponsiveImage = (props: NextImageProps) => (
+ <Figure>
+ <NextImage {...props} />
+ </Figure>
+);
+
const components: MDXComponents = {
Image: ResponsiveImage,
Link,
diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx
index ee86c7b..0b94a4e 100644
--- a/src/pages/projets/[slug].tsx
+++ b/src/pages/projets/[slug].tsx
@@ -3,6 +3,7 @@ import type { MDXComponents } from 'mdx/types';
import type { GetStaticPaths, GetStaticProps } from 'next';
import dynamic from 'next/dynamic';
import Head from 'next/head';
+import NextImage, { type ImageProps as NextImageProps } from 'next/image';
import { useRouter } from 'next/router';
import Script from 'next/script';
import type { ComponentType, HTMLAttributes } from 'react';
@@ -15,8 +16,6 @@ import {
Overview,
type OverviewMeta,
PageLayout,
- ResponsiveImage,
- type ResponsiveImageProps,
Sharing,
SocialLink,
Spinner,
@@ -24,6 +23,7 @@ import {
Heading,
List,
ListItem,
+ Figure,
} from '../../components';
import styles from '../../styles/pages/project.module.scss';
import type { NextPageWithLayout, ProjectPreview, Repos } from '../../types';
@@ -41,8 +41,10 @@ import {
} from '../../utils/helpers/server';
import { useBreadcrumb, useGithubApi, useSettings } from '../../utils/hooks';
-const BorderedImage = (props: ResponsiveImageProps) => (
- <ResponsiveImage withBorders={true} {...props} />
+const BorderedImage = (props: NextImageProps) => (
+ <Figure hasBorders>
+ <NextImage {...props} />
+ </Figure>
);
const H1 = ({
diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx
index 8e3100f..899f9e1 100644
--- a/src/pages/sujet/[slug].tsx
+++ b/src/pages/sujet/[slug].tsx
@@ -2,6 +2,7 @@
import type { ParsedUrlQuery } from 'querystring';
import type { GetStaticPaths, GetStaticProps } from 'next';
import Head from 'next/head';
+import NextImage from 'next/image';
import { useRouter } from 'next/router';
import Script from 'next/script';
import { useIntl } from 'react-intl';
@@ -11,7 +12,6 @@ import {
LinksListWidget,
PageLayout,
PostsList,
- ResponsiveImage,
type MetaData,
} from '../../components';
import {
@@ -101,7 +101,7 @@ const TopicPage: NextPageWithLayout<TopicPageProps> = ({
const getPageHeading = () => (
<>
- {cover ? <ResponsiveImage className={styles.logo} {...cover} /> : null}
+ {cover ? <NextImage {...cover} className={styles.logo} /> : null}
{title}
</>
);