aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules/images
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/molecules/images')
-rw-r--r--src/components/molecules/images/responsive-image.module.scss52
-rw-r--r--src/components/molecules/images/responsive-image.stories.tsx87
-rw-r--r--src/components/molecules/images/responsive-image.test.tsx18
-rw-r--r--src/components/molecules/images/responsive-image.tsx81
4 files changed, 238 insertions, 0 deletions
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<typeof ResponsiveImageComponent>;
+
+const Template: ComponentStory<typeof ResponsiveImageComponent> = (args) => (
+ <ResponsiveImageComponent {...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(
+ <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
new file mode 100644
index 0000000..1d8787e
--- /dev/null
+++ b/src/components/molecules/images/responsive-image.tsx
@@ -0,0 +1,81 @@
+import Link from '@components/atoms/links/link';
+import Image, { ImageProps } from 'next/image';
+import { VFC } from 'react';
+import styles from './responsive-image.module.scss';
+
+export type ResponsiveImageProps = Omit<
+ ImageProps,
+ 'alt' | 'width' | 'height'
+> & {
+ /**
+ * An alternative text.
+ */
+ alt: string;
+ /**
+ * A figure caption.
+ */
+ caption?: string;
+ /**
+ * Set additional classnames to the figure wrapper.
+ */
+ className?: 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: VFC<ResponsiveImageProps> = ({
+ alt,
+ caption,
+ className = '',
+ layout,
+ objectFit,
+ target,
+ ...props
+}) => {
+ return (
+ <figure className={`${styles.wrapper} ${className}`}>
+ {target ? (
+ <Link href={target} className={styles.link}>
+ <Image
+ alt={alt}
+ layout={layout || 'intrinsic'}
+ objectFit={objectFit || 'contain'}
+ {...props}
+ />
+ {caption && (
+ <figcaption className={styles.caption}>{caption}</figcaption>
+ )}
+ </Link>
+ ) : (
+ <>
+ <Image
+ alt={alt}
+ layout={layout || 'intrinsic'}
+ objectFit={objectFit || 'contain'}
+ {...props}
+ />
+ {caption && (
+ <figcaption className={styles.caption}>{caption}</figcaption>
+ )}
+ </>
+ )}
+ </figure>
+ );
+};
+
+export default ResponsiveImage;