aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/organisms/widgets
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-11-13 18:46:31 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-13 18:53:15 +0100
commite331106e56d59a8b987230860b66214139c12ef6 (patch)
tree18b595ddd86089b405e9517cd3efc72e2f17a1ab /src/components/organisms/widgets
parent56878f647ea0f1066fa3e222d7aa0d83057f496d (diff)
refactor(components): rewrite ImageWidget component
* remove `imageClassName` prop * replace `image` prop with `img` and expect an image instead of an object * remove `alignment prop` * remove useless CSS
Diffstat (limited to 'src/components/organisms/widgets')
-rw-r--r--src/components/organisms/widgets/image-widget.module.scss48
-rw-r--r--src/components/organisms/widgets/image-widget.stories.tsx158
-rw-r--r--src/components/organisms/widgets/image-widget.test.tsx55
-rw-r--r--src/components/organisms/widgets/image-widget.tsx74
-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
9 files changed, 253 insertions, 335 deletions
diff --git a/src/components/organisms/widgets/image-widget.module.scss b/src/components/organisms/widgets/image-widget.module.scss
deleted file mode 100644
index 25de03e..0000000
--- a/src/components/organisms/widgets/image-widget.module.scss
+++ /dev/null
@@ -1,48 +0,0 @@
-@use "../../../styles/abstracts/functions" as fun;
-
-.figure {
- --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);
-}
-
-.txt {
- padding: var(--spacing-sm);
-}
-
-.widget {
- &--left {
- .figure {
- margin-right: auto;
- }
-
- .txt {
- text-align: left;
- }
- }
-
- &--center {
- .figure {
- margin-left: auto;
- margin-right: auto;
- }
-
- .txt {
- text-align: center;
- }
- }
-
- &--right {
- .figure {
- margin-left: auto;
- }
-
- .txt {
- text-align: right;
- }
- }
-}
diff --git a/src/components/organisms/widgets/image-widget.stories.tsx b/src/components/organisms/widgets/image-widget.stories.tsx
deleted file mode 100644
index e9857bf..0000000
--- a/src/components/organisms/widgets/image-widget.stories.tsx
+++ /dev/null
@@ -1,158 +0,0 @@
-import type { ComponentMeta, ComponentStory } from '@storybook/react';
-import { Heading } from '../../atoms';
-import { ImageWidget } from './image-widget';
-
-/**
- * ImageWidget - Storybook Meta
- */
-export default {
- title: 'Organisms/Widgets/Image',
- component: ImageWidget,
- args: {
- alignment: 'left',
- },
- argTypes: {
- alignment: {
- control: {
- type: 'select',
- },
- description: 'The content alignment.',
- options: ['left', 'center', 'right'],
- table: {
- category: 'Options',
- defaultValue: { summary: 'left' },
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- className: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the widget wrapper.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- description: {
- control: {
- type: 'text',
- },
- description: 'Add a caption image.',
- table: {
- category: 'Options',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- image: {
- description: 'An image object.',
- type: {
- name: 'object',
- required: true,
- value: {},
- },
- },
- imageClassName: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the image wrapper.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- 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: 'Et perferendis quaerat',
- height: 480,
- src: 'http://picsum.photos/640/480',
- width: 640,
-};
-
-/**
- * ImageWidget Stories - Align left
- */
-export const AlignLeft = Template.bind({});
-AlignLeft.args = {
- alignment: 'left',
- heading: (
- <Heading isFake level={3}>
- Quo et totam
- </Heading>
- ),
- image,
-};
-
-/**
- * ImageWidget Stories - Align center
- */
-export const AlignCenter = Template.bind({});
-AlignCenter.args = {
- alignment: 'center',
- heading: (
- <Heading isFake level={3}>
- Quo et totam
- </Heading>
- ),
- image,
-};
-
-/**
- * ImageWidget Stories - Align right
- */
-export const AlignRight = Template.bind({});
-AlignRight.args = {
- alignment: 'right',
- heading: (
- <Heading isFake level={3}>
- Quo et totam
- </Heading>
- ),
- image,
-};
-
-/**
- * ImageWidget Stories - With description
- */
-export const WithDescription = Template.bind({});
-WithDescription.args = {
- description: 'Sint enim harum',
- heading: (
- <Heading isFake level={3}>
- Quo et totam
- </Heading>
- ),
- image,
-};
diff --git a/src/components/organisms/widgets/image-widget.test.tsx b/src/components/organisms/widgets/image-widget.test.tsx
deleted file mode 100644
index 3d48947..0000000
--- a/src/components/organisms/widgets/image-widget.test.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import { render, screen as rtlScreen } from '@testing-library/react';
-import { Heading } from '../../atoms';
-import { ImageWidget } from './image-widget';
-
-const description = 'Ut vitae sit';
-
-const img = {
- alt: 'Et perferendis quaerat',
- height: 480,
- src: 'http://placeimg.com/640/480/nature',
- width: 640,
-};
-
-const title = 'Fugiat cumque et';
-const titleLevel = 2;
-
-const url = '/another-page';
-
-describe('ImageWidget', () => {
- it('renders an image', () => {
- render(
- <ImageWidget
- heading={<Heading level={titleLevel}>{title}</Heading>}
- image={img}
- />
- );
- expect(rtlScreen.getByRole('img', { name: img.alt })).toBeInTheDocument();
- });
-
- it('renders an image with a link', () => {
- render(
- <ImageWidget
- heading={<Heading level={titleLevel}>{title}</Heading>}
- image={img}
- url={url}
- />
- );
- expect(rtlScreen.getByRole('link', { name: img.alt })).toHaveAttribute(
- 'href',
- url
- );
- });
-
- it('renders a description', () => {
- render(
- <ImageWidget
- heading={<Heading level={titleLevel}>{title}</Heading>}
- image={img}
- description={description}
- />
- );
- expect(rtlScreen.getByText(description)).toBeInTheDocument();
- });
-});
diff --git a/src/components/organisms/widgets/image-widget.tsx b/src/components/organisms/widgets/image-widget.tsx
deleted file mode 100644
index 5de8dd8..0000000
--- a/src/components/organisms/widgets/image-widget.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import NextImage, { type ImageProps as NextImageProps } from 'next/image';
-import type { FC } from 'react';
-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<NextImageProps, 'alt' | 'height' | 'src' | 'width'>;
-
-export type ImageWidgetProps = Omit<
- CollapsibleProps,
- 'children' | 'onToggle'
-> & {
- /**
- * The content alignment.
- */
- alignment?: Alignment;
- /**
- * Add a caption to the image.
- */
- description?: FigureProps['caption'];
- /**
- * An object describing the image.
- */
- image: Image;
- /**
- * Set additional classnames to the image wrapper.
- */
- imageClassName?: string;
- /**
- * Add a link to the image.
- */
- url?: string;
-};
-
-/**
- * ImageWidget component
- *
- * Renders a widget that print an image and an optional text.
- */
-export const ImageWidget: FC<ImageWidgetProps> = ({
- alignment = 'left',
- className = '',
- description,
- image,
- imageClassName = '',
- isCollapsed,
- url,
- ...props
-}) => {
- const alignmentClass = `widget--${alignment}`;
-
- return (
- <Collapsible
- {...props}
- className={`${styles[alignmentClass]} ${className}`}
- >
- <Figure
- caption={description}
- className={`${styles.figure} ${imageClassName}`}
- hasBorders
- >
- {url ? (
- <Link href={url}>
- <NextImage {...image} />
- </Link>
- ) : (
- <NextImage {...image} />
- )}
- </Figure>
- </Collapsible>
- );
-};
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';