diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-09-27 17:38:23 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-10-24 12:25:00 +0200 |
| commit | 7255d25f6834a208c0ed44636356cc260f6ab6ba (patch) | |
| tree | 88016a958190f766a3ac0ab4b77f4732e17502e8 /src/components/atoms | |
| parent | ba793e043e4d8515b1a9ea490ee2c5f92b1fd6c2 (diff) | |
refactor(components): rewrite Heading component
* remove `alignment` and `withMargin` props (consumer should handle
that)
* move styles to Sass placeholders to avoid repeats with headings
coming from WordPress
* refactor some other components that depend on Heading to avoid ESlint
errors
Diffstat (limited to 'src/components/atoms')
| -rw-r--r-- | src/components/atoms/heading/heading.module.scss | 27 | ||||
| -rw-r--r-- | src/components/atoms/heading/heading.stories.tsx (renamed from src/components/atoms/headings/heading.stories.tsx) | 58 | ||||
| -rw-r--r-- | src/components/atoms/heading/heading.test.tsx | 80 | ||||
| -rw-r--r-- | src/components/atoms/heading/heading.tsx | 57 | ||||
| -rw-r--r-- | src/components/atoms/heading/index.ts (renamed from src/components/atoms/headings/index.ts) | 0 | ||||
| -rw-r--r-- | src/components/atoms/headings/heading.module.scss | 69 | ||||
| -rw-r--r-- | src/components/atoms/headings/heading.test.tsx | 57 | ||||
| -rw-r--r-- | src/components/atoms/headings/heading.tsx | 93 | ||||
| -rw-r--r-- | src/components/atoms/index.ts | 2 | ||||
| -rw-r--r-- | src/components/atoms/layout/section/section.module.scss | 1 | ||||
| -rw-r--r-- | src/components/atoms/layout/section/section.stories.tsx | 2 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.stories.tsx | 4 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.test.tsx | 8 | ||||
| -rw-r--r-- | src/components/atoms/modal/modal.tsx | 10 |
14 files changed, 180 insertions, 288 deletions
diff --git a/src/components/atoms/heading/heading.module.scss b/src/components/atoms/heading/heading.module.scss new file mode 100644 index 0000000..a2e339a --- /dev/null +++ b/src/components/atoms/heading/heading.module.scss @@ -0,0 +1,27 @@ +@use "../../../styles/abstracts/placeholders"; + +.heading { + &--1 { + @extend %h1; + } + + &--2 { + @extend %h2; + } + + &--3 { + @extend %h3; + } + + &--4 { + @extend %h4; + } + + &--5 { + @extend %h5; + } + + &--6 { + @extend %h6; + } +} diff --git a/src/components/atoms/headings/heading.stories.tsx b/src/components/atoms/heading/heading.stories.tsx index 4aa79c2..c5ac4a0 100644 --- a/src/components/atoms/headings/heading.stories.tsx +++ b/src/components/atoms/heading/heading.stories.tsx @@ -1,46 +1,16 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; import { Heading } from './heading'; /** * Heading - Storybook Meta */ export default { - title: 'Atoms/Typography/Headings', + title: 'Atoms/Headings', component: Heading, args: { - alignment: 'left', isFake: false, - withMargin: true, }, argTypes: { - alignment: { - control: { - type: 'select', - }, - description: 'The title alignment.', - options: ['center', 'left'], - table: { - category: 'Options', - defaultValue: { summary: 'left' }, - }, - type: { - name: 'string', - required: false, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, children: { description: 'Heading body.', type: { @@ -48,16 +18,6 @@ export default { required: true, }, }, - id: { - control: { - type: 'text', - }, - description: 'An unique id.', - type: { - name: 'string', - required: false, - }, - }, isFake: { control: { type: 'boolean', @@ -84,20 +44,6 @@ export default { required: true, }, }, - withMargin: { - control: { - type: 'boolean', - }, - description: 'Adds margin.', - table: { - category: 'Options', - defaultValue: { summary: true }, - }, - type: { - name: 'boolean', - required: false, - }, - }, }, } as ComponentMeta<typeof Heading>; diff --git a/src/components/atoms/heading/heading.test.tsx b/src/components/atoms/heading/heading.test.tsx new file mode 100644 index 0000000..39b23ad --- /dev/null +++ b/src/components/atoms/heading/heading.test.tsx @@ -0,0 +1,80 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Heading } from './heading'; + +describe('Heading', () => { + it('renders a h1', () => { + const body = 'provident'; + + render(<Heading level={1}>{body}</Heading>); + + expect(rtlScreen.getByRole('heading', { level: 1 })).toHaveTextContent( + body + ); + }); + + it('renders a h2', () => { + const body = 'iure'; + + render(<Heading level={2}>{body}</Heading>); + + expect(rtlScreen.getByRole('heading', { level: 2 })).toHaveTextContent( + body + ); + }); + + it('renders a h3', () => { + const body = 'ut'; + + render(<Heading level={3}>{body}</Heading>); + + expect(rtlScreen.getByRole('heading', { level: 3 })).toHaveTextContent( + body + ); + }); + + it('renders a h4', () => { + const body = 'dolor'; + + render(<Heading level={4}>{body}</Heading>); + + expect(rtlScreen.getByRole('heading', { level: 4 })).toHaveTextContent( + body + ); + }); + + it('renders a h5', () => { + const body = 'temporibus'; + + render(<Heading level={5}>{body}</Heading>); + + expect(rtlScreen.getByRole('heading', { level: 5 })).toHaveTextContent( + body + ); + }); + + it('renders a h6', () => { + const body = 'at'; + + render(<Heading level={6}>{body}</Heading>); + + expect(rtlScreen.getByRole('heading', { level: 6 })).toHaveTextContent( + body + ); + }); + + it('renders a fake heading', () => { + const body = 'dignissimos'; + + render( + <Heading isFake level={2}> + {body} + </Heading> + ); + + expect( + rtlScreen.queryByRole('heading', { level: 2 }) + ).not.toBeInTheDocument(); + expect(rtlScreen.getByText(body)).toHaveClass('heading--2'); + }); +}); diff --git a/src/components/atoms/heading/heading.tsx b/src/components/atoms/heading/heading.tsx new file mode 100644 index 0000000..6cdb578 --- /dev/null +++ b/src/components/atoms/heading/heading.tsx @@ -0,0 +1,57 @@ +import { + type ForwardedRef, + forwardRef, + type ForwardRefRenderFunction, + type HTMLAttributes, + type ReactNode, +} from 'react'; +import styles from './heading.module.scss'; + +// eslint-disable-next-line @typescript-eslint/no-magic-numbers +export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; + +export type HeadingProps = HTMLAttributes<HTMLHeadingElement> & { + /** + * The heading body. + */ + children: ReactNode; + /** + * Use an heading element or only its styles. + * + * @default false + */ + isFake?: boolean; + /** + * HTML heading level. + */ + level: HeadingLevel; +}; + +const HeadingWithRef: ForwardRefRenderFunction< + HTMLHeadingElement | HTMLParagraphElement, + HeadingProps +> = ( + { children, className = '', isFake = false, level, ...props }, + ref: ForwardedRef<HTMLHeadingElement | HTMLParagraphElement> +) => { + const HeadingTag = `h${level}` as const; + const levelClass = styles[`heading--${level}`]; + const headingClass = `${levelClass} ${className}`; + + return isFake ? ( + <p {...props} className={headingClass} ref={ref}> + {children} + </p> + ) : ( + <HeadingTag {...props} className={headingClass} ref={ref}> + {children} + </HeadingTag> + ); +}; + +/** + * Heading component. + * + * Render an HTML heading element or a paragraph with heading styles. + */ +export const Heading = forwardRef(HeadingWithRef); diff --git a/src/components/atoms/headings/index.ts b/src/components/atoms/heading/index.ts index 3de265c..3de265c 100644 --- a/src/components/atoms/headings/index.ts +++ b/src/components/atoms/heading/index.ts diff --git a/src/components/atoms/headings/heading.module.scss b/src/components/atoms/headings/heading.module.scss deleted file mode 100644 index 1c898e6..0000000 --- a/src/components/atoms/headings/heading.module.scss +++ /dev/null @@ -1,69 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; - -.heading { - color: var(--color-primary-dark); - font-family: var(--font-family-secondary); - letter-spacing: 0.01ex; - - &--regular { - margin-bottom: 0; - margin-top: 0; - } - - &--left { - text-align: left; - } - - &--center { - width: fit-content; - margin-left: auto; - margin-right: auto; - } - - &--margin { - margin-top: 0; - margin-bottom: var(--spacing-sm); - - & + & { - margin-top: var(--spacing-md); - } - } - - &--1 { - font-size: var(--font-size-3xl); - font-weight: 500; - } - - &--2 { - padding-bottom: fun.convert-px(3); - background: linear-gradient( - to top, - var(--color-primary-dark) 0.3rem, - transparent 0.3rem - ) - 0 0 / 3rem 100% no-repeat; - font-size: var(--font-size-2xl); - font-weight: 500; - text-shadow: fun.convert-px(1) fun.convert-px(1) 0 var(--color-shadow-light); - } - - &--3 { - font-size: var(--font-size-xl); - font-weight: 500; - } - - &--4 { - font-size: var(--font-size-lg); - font-weight: 500; - } - - &--5 { - font-size: var(--font-size-md); - font-weight: 600; - } - - &--6 { - font-size: var(--font-size-md); - font-weight: 500; - } -} diff --git a/src/components/atoms/headings/heading.test.tsx b/src/components/atoms/headings/heading.test.tsx deleted file mode 100644 index 61d7f8e..0000000 --- a/src/components/atoms/headings/heading.test.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { Heading } from './heading'; - -describe('Heading', () => { - it('renders a h1', () => { - render(<Heading level={1}>Level 1</Heading>); - expect(screen.getByRole('heading', { level: 1 })).toHaveTextContent( - 'Level 1' - ); - }); - - it('renders a h2', () => { - render(<Heading level={2}>Level 2</Heading>); - expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent( - 'Level 2' - ); - }); - - it('renders a h3', () => { - render(<Heading level={3}>Level 3</Heading>); - expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent( - 'Level 3' - ); - }); - - it('renders a h4', () => { - render(<Heading level={4}>Level 4</Heading>); - expect(screen.getByRole('heading', { level: 4 })).toHaveTextContent( - 'Level 4' - ); - }); - - it('renders a h5', () => { - render(<Heading level={5}>Level 5</Heading>); - expect(screen.getByRole('heading', { level: 5 })).toHaveTextContent( - 'Level 5' - ); - }); - - it('renders a h6', () => { - render(<Heading level={6}>Level 6</Heading>); - expect(screen.getByRole('heading', { level: 6 })).toHaveTextContent( - 'Level 6' - ); - }); - - it('renders a text with heading styles', () => { - render( - <Heading isFake={true} level={2}> - Fake heading - </Heading> - ); - expect(screen.queryByRole('heading', { level: 2 })).not.toBeInTheDocument(); - expect(screen.getByText('Fake heading')).toHaveClass('heading'); - }); -}); diff --git a/src/components/atoms/headings/heading.tsx b/src/components/atoms/headings/heading.tsx deleted file mode 100644 index b1b6164..0000000 --- a/src/components/atoms/headings/heading.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { - createElement, - ForwardedRef, - forwardRef, - ForwardRefRenderFunction, - HTMLAttributes, - ReactNode, -} from 'react'; -import styles from './heading.module.scss'; - -export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6; - -export type HeadingProps = HTMLAttributes<HTMLHeadingElement> & { - /** - * The title alignment. Default: left; - */ - alignment?: 'center' | 'left'; - /** - * The heading body. - */ - children: ReactNode; - /** - * Use an heading element or only its styles. Default: false. - */ - isFake?: boolean; - /** - * HTML heading level. - */ - level: HeadingLevel; - /** - * Adds margin. Default: true. - */ - withMargin?: boolean; -}; - -type TitleTagProps = Pick<HeadingProps, 'children' | 'className' | 'id'> & { - tagName: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'; -}; - -const TitleTag = forwardRef< - HTMLHeadingElement | HTMLParagraphElement, - TitleTagProps ->( - ( - { children, tagName, ...props }, - ref: ForwardedRef<HTMLHeadingElement | HTMLParagraphElement> - ) => { - return createElement(tagName, { ...props, ref }, children); - } -); -TitleTag.displayName = 'TitleTag'; - -const HeadingWithRef: ForwardRefRenderFunction< - HTMLHeadingElement | HTMLParagraphElement, - HeadingProps -> = ( - { - alignment = 'left', - children, - className = '', - id, - isFake = false, - level, - withMargin = true, - ...props - }, - ref: ForwardedRef<HTMLHeadingElement | HTMLParagraphElement> -) => { - const tagName = isFake ? 'p' : (`h${level}` as TitleTagProps['tagName']); - const levelClass = `heading--${level}`; - const alignmentClass = `heading--${alignment}`; - const marginClass = withMargin ? 'heading--margin' : 'heading--regular'; - const headingClass = `${styles.heading} ${styles[levelClass]} ${styles[alignmentClass]} ${styles[marginClass]} ${className}`; - - return ( - <TitleTag - {...props} - className={headingClass} - id={id} - ref={ref} - tagName={tagName} - > - {children} - </TitleTag> - ); -}; - -/** - * Heading component. - * - * Render an HTML heading element or a paragraph with heading styles. - */ -export const Heading = forwardRef(HeadingWithRef); diff --git a/src/components/atoms/index.ts b/src/components/atoms/index.ts index d9cf865..672440c 100644 --- a/src/components/atoms/index.ts +++ b/src/components/atoms/index.ts @@ -1,6 +1,6 @@ export * from './buttons'; export * from './forms'; -export * from './headings'; +export * from './heading'; export * from './icons'; export * from './images'; export * from './layout'; diff --git a/src/components/atoms/layout/section/section.module.scss b/src/components/atoms/layout/section/section.module.scss index 771b8e3..3da74a2 100644 --- a/src/components/atoms/layout/section/section.module.scss +++ b/src/components/atoms/layout/section/section.module.scss @@ -21,5 +21,6 @@ > * { grid-column: 2; + margin-block: 0; } } diff --git a/src/components/atoms/layout/section/section.stories.tsx b/src/components/atoms/layout/section/section.stories.tsx index 0a3388b..e21209b 100644 --- a/src/components/atoms/layout/section/section.stories.tsx +++ b/src/components/atoms/layout/section/section.stories.tsx @@ -1,5 +1,5 @@ import type { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Heading } from '../../headings'; +import { Heading } from '../../heading'; import { Section } from './section'; /** diff --git a/src/components/atoms/modal/modal.stories.tsx b/src/components/atoms/modal/modal.stories.tsx index d0c2f0b..0490a8f 100644 --- a/src/components/atoms/modal/modal.stories.tsx +++ b/src/components/atoms/modal/modal.stories.tsx @@ -1,6 +1,6 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Heading } from '../heading'; import { Modal } from './modal'; -import { Heading } from '../headings'; /** * Switch - Storybook Meta diff --git a/src/components/atoms/modal/modal.test.tsx b/src/components/atoms/modal/modal.test.tsx index 6e7d29e..dfa4a88 100644 --- a/src/components/atoms/modal/modal.test.tsx +++ b/src/components/atoms/modal/modal.test.tsx @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { Heading } from '../headings'; +import { render, screen as rtlScreen } from '../../../../tests/utils'; +import { Heading } from '../heading'; import { Modal } from './modal'; const title = 'A custom title'; @@ -16,11 +16,11 @@ describe('Modal', () => { {children} </Modal> ); - expect(screen.getByRole('heading', { level })).toHaveTextContent(title); + expect(rtlScreen.getByRole('heading', { level })).toHaveTextContent(title); }); it('renders the modal body', () => { render(<Modal>{children}</Modal>); - expect(screen.getByText(children)).toBeInTheDocument(); + expect(rtlScreen.getByText(children)).toBeInTheDocument(); }); }); diff --git a/src/components/atoms/modal/modal.tsx b/src/components/atoms/modal/modal.tsx index 78b4f6e..6f5506f 100644 --- a/src/components/atoms/modal/modal.tsx +++ b/src/components/atoms/modal/modal.tsx @@ -1,11 +1,11 @@ import { - ForwardRefRenderFunction, - HTMLAttributes, - ReactElement, - ReactNode, + type ForwardRefRenderFunction, + type HTMLAttributes, + type ReactElement, + type ReactNode, forwardRef, } from 'react'; -import { HeadingProps } from '../headings'; +import type { HeadingProps } from '../heading'; import styles from './modal.module.scss'; export type ModalProps = HTMLAttributes<HTMLDivElement> & { |
