aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/organisms/widgets/image-widget
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/organisms/widgets/image-widget')
-rw-r--r--src/components/organisms/widgets/image-widget/image-widget.module.scss9
-rw-r--r--src/components/organisms/widgets/image-widget/image-widget.stories.tsx115
-rw-r--r--src/components/organisms/widgets/image-widget/image-widget.test.tsx81
-rw-r--r--src/components/organisms/widgets/image-widget/image-widget.tsx47
-rw-r--r--src/components/organisms/widgets/image-widget/index.ts1
5 files changed, 253 insertions, 0 deletions
diff --git a/src/components/organisms/widgets/image-widget/image-widget.module.scss b/src/components/organisms/widgets/image-widget/image-widget.module.scss
new file mode 100644
index 0000000..b3f18ec
--- /dev/null
+++ b/src/components/organisms/widgets/image-widget/image-widget.module.scss
@@ -0,0 +1,9 @@
+.link {
+ display: block;
+ transition: all 0.1s ease-in-out 0s;
+
+ &:focus {
+ outline: 3px ridge var(--color-primary);
+ outline-offset: 2px;
+ }
+}
diff --git a/src/components/organisms/widgets/image-widget/image-widget.stories.tsx b/src/components/organisms/widgets/image-widget/image-widget.stories.tsx
new file mode 100644
index 0000000..33f3e7b
--- /dev/null
+++ b/src/components/organisms/widgets/image-widget/image-widget.stories.tsx
@@ -0,0 +1,115 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import NextImage from 'next/image';
+import { Heading } from '../../../atoms';
+import { ImageWidget } from './image-widget';
+
+/**
+ * ImageWidget - Storybook Meta
+ */
+export default {
+ title: 'Organisms/Widgets/Image',
+ component: ImageWidget,
+ argTypes: {
+ description: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add a caption image.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ img: {
+ description: 'The image.',
+ type: {
+ name: 'object',
+ required: true,
+ value: {},
+ },
+ },
+ url: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add a link to the image.',
+ table: {
+ category: 'Options',
+ },
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta<typeof ImageWidget>;
+
+const Template: ComponentStory<typeof ImageWidget> = (args) => (
+ <ImageWidget {...args} />
+);
+
+const image = {
+ alt: '',
+ height: 480,
+ src: 'https://picsum.photos/640/480',
+ width: 640,
+};
+
+/**
+ * ImageWidget Stories - Default
+ */
+export const Default = Template.bind({});
+Default.args = {
+ heading: (
+ <Heading isFake level={3}>
+ Quo et totam
+ </Heading>
+ ),
+ img: <NextImage {...image} />,
+};
+
+/**
+ * ImageWidget Stories - WithDescription
+ */
+export const WithDescription = Template.bind({});
+WithDescription.args = {
+ description: 'Any image used as an example',
+ heading: (
+ <Heading isFake level={3}>
+ Quo et totam
+ </Heading>
+ ),
+ img: <NextImage {...image} />,
+};
+
+/**
+ * ImageWidget Stories - WithLink
+ */
+export const WithLink = Template.bind({});
+WithLink.args = {
+ heading: (
+ <Heading isFake level={3}>
+ Quo et totam
+ </Heading>
+ ),
+ img: <NextImage {...image} />,
+ url: 'https://www.armandphilippot.com/',
+};
+
+/**
+ * ImageWidget Stories - WithDescriptionAndLink
+ */
+export const WithDescriptionAndLink = Template.bind({});
+WithDescriptionAndLink.args = {
+ description: 'Any image used as an example',
+ heading: (
+ <Heading isFake level={3}>
+ Quo et totam
+ </Heading>
+ ),
+ img: <NextImage {...image} />,
+ url: 'https://www.armandphilippot.com/',
+};
diff --git a/src/components/organisms/widgets/image-widget/image-widget.test.tsx b/src/components/organisms/widgets/image-widget/image-widget.test.tsx
new file mode 100644
index 0000000..ecf8d77
--- /dev/null
+++ b/src/components/organisms/widgets/image-widget/image-widget.test.tsx
@@ -0,0 +1,81 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import NextImage from 'next/image';
+import { Heading } from '../../../atoms';
+import { ImageWidget } from './image-widget';
+
+describe('ImageWidget', () => {
+ it('render the widget heading and an image', () => {
+ const heading = 'quam tempore ea';
+ const headingLvl = 3;
+ const altTxt = 'enim';
+
+ render(
+ <ImageWidget
+ heading={<Heading level={headingLvl}>{heading}</Heading>}
+ img={
+ <NextImage
+ alt={altTxt}
+ height={480}
+ src="https://picsum.photos/640/480"
+ width={640}
+ />
+ }
+ />
+ );
+
+ expect(
+ rtlScreen.getByRole('heading', { level: headingLvl })
+ ).toHaveTextContent(heading);
+ expect(rtlScreen.getByRole('img')).toHaveAccessibleName(altTxt);
+ });
+
+ it('can render an image wrapped in a link', () => {
+ const heading = 'quam tempore ea';
+ const headingLvl = 3;
+ const altTxt = 'enim';
+ const url = 'https://example.test';
+
+ render(
+ <ImageWidget
+ heading={<Heading level={headingLvl}>{heading}</Heading>}
+ img={
+ <NextImage
+ alt={altTxt}
+ height={480}
+ src="https://picsum.photos/640/480"
+ width={640}
+ />
+ }
+ url={url}
+ />
+ );
+
+ expect(rtlScreen.getByRole('link')).toHaveAttribute('href', url);
+ expect(rtlScreen.getByRole('img')).toHaveAccessibleName(altTxt);
+ });
+
+ it('can render an image with a description', () => {
+ const heading = 'quam tempore ea';
+ const headingLvl = 3;
+ const altTxt = 'enim';
+ const desc = 'itaque laudantium ut';
+
+ render(
+ <ImageWidget
+ description={desc}
+ heading={<Heading level={headingLvl}>{heading}</Heading>}
+ img={
+ <NextImage
+ alt={altTxt}
+ height={480}
+ src="https://picsum.photos/640/480"
+ width={640}
+ />
+ }
+ />
+ );
+
+ expect(rtlScreen.getByRole('figure')).toHaveAccessibleName(desc);
+ });
+});
diff --git a/src/components/organisms/widgets/image-widget/image-widget.tsx b/src/components/organisms/widgets/image-widget/image-widget.tsx
new file mode 100644
index 0000000..23e6775
--- /dev/null
+++ b/src/components/organisms/widgets/image-widget/image-widget.tsx
@@ -0,0 +1,47 @@
+import {
+ forwardRef,
+ type ForwardRefRenderFunction,
+ type ReactNode,
+} from 'react';
+import { Figure, Link, type FigureProps } from '../../../atoms';
+import { Collapsible, type CollapsibleProps } from '../../../molecules';
+import styles from './image-widget.module.scss';
+
+export type ImageWidgetProps = Omit<CollapsibleProps, 'children'> & {
+ /**
+ * Add a caption to the image.
+ */
+ description?: FigureProps['caption'];
+ /**
+ * The image.
+ */
+ img: ReactNode;
+ /**
+ * Add a link to the image.
+ */
+ url?: string;
+};
+
+const ImageWidgetWithRef: ForwardRefRenderFunction<
+ HTMLDivElement,
+ ImageWidgetProps
+> = ({ description, img, isCollapsed, url, ...props }, ref) => (
+ <Collapsible {...props} ref={ref}>
+ <Figure caption={description} hasBorders>
+ {url ? (
+ <Link className={styles.link} href={url}>
+ {img}
+ </Link>
+ ) : (
+ img
+ )}
+ </Figure>
+ </Collapsible>
+);
+
+/**
+ * ImageWidget component
+ *
+ * Renders a widget that print an image and an optional text.
+ */
+export const ImageWidget = forwardRef(ImageWidgetWithRef);
diff --git a/src/components/organisms/widgets/image-widget/index.ts b/src/components/organisms/widgets/image-widget/index.ts
new file mode 100644
index 0000000..c271b68
--- /dev/null
+++ b/src/components/organisms/widgets/image-widget/index.ts
@@ -0,0 +1 @@
+export * from './image-widget';