aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-09-27 17:38:23 +0200
committerArmand Philippot <git@armandphilippot.com>2023-10-24 12:25:00 +0200
commit7255d25f6834a208c0ed44636356cc260f6ab6ba (patch)
tree88016a958190f766a3ac0ab4b77f4732e17502e8 /src/components/atoms
parentba793e043e4d8515b1a9ea490ee2c5f92b1fd6c2 (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.scss27
-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.tsx80
-rw-r--r--src/components/atoms/heading/heading.tsx57
-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.scss69
-rw-r--r--src/components/atoms/headings/heading.test.tsx57
-rw-r--r--src/components/atoms/headings/heading.tsx93
-rw-r--r--src/components/atoms/index.ts2
-rw-r--r--src/components/atoms/layout/section/section.module.scss1
-rw-r--r--src/components/atoms/layout/section/section.stories.tsx2
-rw-r--r--src/components/atoms/modal/modal.stories.tsx4
-rw-r--r--src/components/atoms/modal/modal.test.tsx8
-rw-r--r--src/components/atoms/modal/modal.tsx10
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> & {