diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-13 18:46:31 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-13 18:53:15 +0100 |
| commit | e331106e56d59a8b987230860b66214139c12ef6 (patch) | |
| tree | 18b595ddd86089b405e9517cd3efc72e2f17a1ab /src/components/organisms/widgets | |
| parent | 56878f647ea0f1066fa3e222d7aa0d83057f496d (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')
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'; |
