From 15522ec9146f6f1956620355c44dea2a6a75b67c Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 9 Oct 2023 18:26:23 +0200 Subject: 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. --- src/components/atoms/figure/figure.module.scss | 30 +++ src/components/atoms/figure/figure.stories.tsx | 74 +++++++ src/components/atoms/figure/figure.test.tsx | 32 ++++ src/components/atoms/figure/figure.tsx | 72 +++++++ src/components/atoms/figure/index.ts | 1 + src/components/atoms/index.ts | 1 + src/components/molecules/images/index.ts | 1 - .../molecules/images/responsive-image.module.scss | 84 -------- .../molecules/images/responsive-image.stories.tsx | 212 --------------------- .../molecules/images/responsive-image.test.tsx | 19 -- .../molecules/images/responsive-image.tsx | 91 --------- src/components/molecules/layout/card.fixture.ts | 19 ++ src/components/molecules/layout/card.fixture.tsx | 19 -- src/components/molecules/layout/card.module.scss | 1 + src/components/molecules/layout/card.tsx | 11 +- .../organisms/images/gallery.stories.tsx | 23 ++- src/components/organisms/images/gallery.test.tsx | 30 +-- src/components/organisms/images/gallery.tsx | 5 +- .../organisms/layout/overview.stories.tsx | 6 +- src/components/organisms/layout/overview.tsx | 17 +- src/components/organisms/layout/summary.fixture.ts | 25 +++ .../organisms/layout/summary.fixture.tsx | 25 --- .../organisms/layout/summary.module.scss | 1 + src/components/organisms/layout/summary.tsx | 20 +- .../organisms/widgets/image-widget.module.scss | 1 + src/components/organisms/widgets/image-widget.tsx | 33 ++-- src/i18n/en.json | 16 +- src/i18n/fr.json | 16 +- src/pages/article/[slug].tsx | 4 +- src/pages/index.tsx | 9 +- src/pages/mentions-legales.tsx | 9 +- src/pages/projets/[slug].tsx | 10 +- src/pages/sujet/[slug].tsx | 4 +- 33 files changed, 378 insertions(+), 543 deletions(-) create mode 100644 src/components/atoms/figure/figure.module.scss create mode 100644 src/components/atoms/figure/figure.stories.tsx create mode 100644 src/components/atoms/figure/figure.test.tsx create mode 100644 src/components/atoms/figure/figure.tsx create mode 100644 src/components/atoms/figure/index.ts delete mode 100644 src/components/molecules/images/responsive-image.module.scss delete mode 100644 src/components/molecules/images/responsive-image.stories.tsx delete mode 100644 src/components/molecules/images/responsive-image.test.tsx delete mode 100644 src/components/molecules/images/responsive-image.tsx create mode 100644 src/components/molecules/layout/card.fixture.ts delete mode 100644 src/components/molecules/layout/card.fixture.tsx create mode 100644 src/components/organisms/layout/summary.fixture.ts delete mode 100644 src/components/organisms/layout/summary.fixture.tsx 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; + +const Template: ComponentStory = (args) =>
; + +/** + * Figure Stories - Illustration + */ +export const Illustration = Template.bind({}); +Illustration.args = { + children: ( + + ), +}; + +/** + * Figure Stories - BorderedIllustration + */ +export const BorderedIllustration = Template.bind({}); +BorderedIllustration.args = { + children: ( + + ), + 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(
{body}
); + + 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(
{body}
); + + expect(rtlScreen.getByRole('figure', { name: caption })).toHaveTextContent( + body + ); + }); + + it('can style the figure with borders', () => { + const body = 'tempora et quis'; + + render(
{body}
); + + 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, '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 = ( + { + '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 ( +
+ {children} + {caption ? ( +
+ {caption} +
+ ) : null} +
+ ); +}; + +/** + * 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; - -const Template: ComponentStory = (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( - - ); - 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 = ({ - 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 ( -
- {target ? ( - - {alt} - {caption && ( -
{caption}
- )} - - ) : ( - <> - {alt} - {caption && ( -
{caption}
- )} - - )} -
- ); -}; diff --git a/src/components/molecules/layout/card.fixture.ts b/src/components/molecules/layout/card.fixture.ts new file mode 100644 index 0000000..01fe2e9 --- /dev/null +++ b/src/components/molecules/layout/card.fixture.ts @@ -0,0 +1,19 @@ +export const cover = { + alt: 'A picture', + height: 480, + src: 'https://picsum.photos/640/480', + width: 640, +}; + +export const id = 'nam'; + +export const meta = { + author: 'Possimus', + thematics: ['Autem', 'Eos'], +}; + +export const tagline = 'Ut rerum incidunt'; + +export const title = 'Alias qui porro'; + +export const url = '/an-existing-url'; diff --git a/src/components/molecules/layout/card.fixture.tsx b/src/components/molecules/layout/card.fixture.tsx deleted file mode 100644 index f96cc43..0000000 --- a/src/components/molecules/layout/card.fixture.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export const cover = { - alt: 'A picture', - height: 480, - src: 'http://placeimg.com/640/480', - width: 640, -}; - -export const id = 'nam'; - -export const meta = { - author: 'Possimus', - thematics: ['Autem', 'Eos'], -}; - -export const tagline = 'Ut rerum incidunt'; - -export const title = 'Alias qui porro'; - -export const url = '/an-existing-url'; 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; /** * The card id. */ @@ -63,7 +62,9 @@ export const Card: FC = ({
{cover ? ( - +
+ +
) : null} {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 = (args) => ( - - - - +
+ +
+
+ +
+
+ +
+
+ +
); 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( - - - - + + + + ); - 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( - - - - + + + + ); - 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[]; + 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 = (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; + cover?: Pick; /** * The overview meta. */ @@ -47,7 +44,11 @@ export const Overview: FC = ({ return (
- {cover ? : null} + {cover ? ( +
+ +
+ ) : null} ; +export type Cover = Pick; export type SummaryMeta = Pick< MetaType<'article'>, @@ -108,7 +102,11 @@ export const Summary: FC = ({ return (
- {cover ? : null} + {cover ? ( +
+ +
+ ) : null}
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; 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 = ({ {...props} className={`${styles[alignmentClass]} ${className}`} > - + hasBorders + > + {url ? ( + + + + ) : ( + + )} +
); }; 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 = ({ label: footerMetaLabel, value: topics.map((topic) => ( - {topic.logo ? : null} {topic.name} + {topic.logo ? : null} {topic.name} )), }, 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 = ({ ); +const ResponsiveImage = (props: NextImageProps) => ( +
+ +
+); + /** * 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) => ( +
+ +
+); + 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) => ( - +const BorderedImage = (props: NextImageProps) => ( +
+ +
); 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 = ({ const getPageHeading = () => ( <> - {cover ? : null} + {cover ? : null} {title} ); -- cgit v1.2.3