From dfd816d1891545aa8ead982b57891858f1c82bb4 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 6 Apr 2022 16:24:05 +0200 Subject: chore: add a ResponsiveImage component --- src/components/atoms/links/link.tsx | 18 ++++- .../molecules/images/responsive-image.module.scss | 52 +++++++++++++ .../molecules/images/responsive-image.stories.tsx | 87 ++++++++++++++++++++++ .../molecules/images/responsive-image.test.tsx | 18 +++++ .../molecules/images/responsive-image.tsx | 68 +++++++++++++++++ 5 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 src/components/molecules/images/responsive-image.module.scss create mode 100644 src/components/molecules/images/responsive-image.stories.tsx create mode 100644 src/components/molecules/images/responsive-image.test.tsx create mode 100644 src/components/molecules/images/responsive-image.tsx (limited to 'src') diff --git a/src/components/atoms/links/link.tsx b/src/components/atoms/links/link.tsx index 0a69c33..a61158f 100644 --- a/src/components/atoms/links/link.tsx +++ b/src/components/atoms/links/link.tsx @@ -3,6 +3,10 @@ import { FC } from 'react'; import styles from './link.module.scss'; type LinkProps = { + /** + * Set additional classes to the link. + */ + classes?: string; /** * True if it is an external link. Default: false. */ @@ -22,18 +26,26 @@ type LinkProps = { * * Render a link. */ -const Link: FC = ({ children, href, lang, external = false }) => { +const Link: FC = ({ + children, + classes, + href, + lang, + external = false, +}) => { + const additionalClasses = classes || ''; + return external ? ( {children} ) : ( - {children} + {children} ); }; diff --git a/src/components/molecules/images/responsive-image.module.scss b/src/components/molecules/images/responsive-image.module.scss new file mode 100644 index 0000000..83e8d10 --- /dev/null +++ b/src/components/molecules/images/responsive-image.module.scss @@ -0,0 +1,52 @@ +@use "@styles/abstracts/functions" as fun; + +.wrapper { + display: flex; + flex-flow: column; + width: 100%; + max-width: max-content; + margin: var(--spacing-sm) auto; + position: relative; + text-align: center; +} + +.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); + box-shadow: 0 fun.convert-px(-1) fun.convert-px(1) fun.convert-px(1) + var(--color-shadow-light); + font-weight: 500; +} + +.link { + display: flex; + flex-flow: column; + background: none; + text-decoration: none; + + .caption { + color: var(--color-primary-darker); + } + + &:hover, + &:focus { + transform: scale(1.1); + } + + &:focus { + .caption { + text-decoration: underline solid var(--color-primary-darker) + fun.convert-px(3); + } + } + + &:active { + transform: scale(0.9); + + .caption { + text-decoration: none; + } + } +} diff --git a/src/components/molecules/images/responsive-image.stories.tsx b/src/components/molecules/images/responsive-image.stories.tsx new file mode 100644 index 0000000..f9c1d2b --- /dev/null +++ b/src/components/molecules/images/responsive-image.stories.tsx @@ -0,0 +1,87 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import ResponsiveImageComponent from './responsive-image'; + +export default { + title: 'Molecules/Images', + component: ResponsiveImageComponent, + 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, + }, + }, + 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, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const ResponsiveImage = Template.bind({}); +ResponsiveImage.args = { + alt: 'An example', + src: 'http://placeimg.com/640/480/transport', + width: 640, + height: 480, +}; diff --git a/src/components/molecules/images/responsive-image.test.tsx b/src/components/molecules/images/responsive-image.test.tsx new file mode 100644 index 0000000..5452d28 --- /dev/null +++ b/src/components/molecules/images/responsive-image.test.tsx @@ -0,0 +1,18 @@ +import { render, screen } from '@test-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 new file mode 100644 index 0000000..db2f5ab --- /dev/null +++ b/src/components/molecules/images/responsive-image.tsx @@ -0,0 +1,68 @@ +import Link from '@components/atoms/links/link'; +import Image, { ImageProps } from 'next/image'; +import { FC } from 'react'; +import styles from './responsive-image.module.scss'; + +type ResponsiveImageProps = Omit & { + /** + * An alternative text. + */ + alt: string; + /** + * A figure caption. + */ + caption?: string; + /** + * The image height. + */ + height: number | string; + /** + * A link target. + */ + target?: string; + /** + * The image width. + */ + width: number | string; +}; + +/** + * ResponsiveImage component + * + * Render a responsive image wrapped in a figure element. + */ +const ResponsiveImage: FC = ({ + alt, + caption, + layout, + objectFit, + target, + ...props +}) => { + return ( +
+ {target ? ( + + {alt} + {caption && ( +
{caption}
+ )} + + ) : ( + <> + {alt} + {caption && ( +
{caption}
+ )} + + )} +
+ ); +}; + +export default ResponsiveImage; -- cgit v1.2.3