aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/layout
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-09-27 15:40:16 +0200
committerArmand Philippot <git@armandphilippot.com>2023-10-24 12:25:00 +0200
commitba793e043e4d8515b1a9ea490ee2c5f92b1fd6c2 (patch)
treef7240a681fb3ee8c886a0c9ec3944082ba2d89bd /src/components/atoms/layout
parent388e687857345c85ee550cd5da472675e05a6ff5 (diff)
refactor(components): rewrite Section component
* Make it compliant with ESlint rules * Remove mandatory heading, it now depends on the consumer * Change defaults for hasBorder and variant
Diffstat (limited to 'src/components/atoms/layout')
-rw-r--r--src/components/atoms/layout/section.test.tsx18
-rw-r--r--src/components/atoms/layout/section.tsx54
-rw-r--r--src/components/atoms/layout/section/index.ts1
-rw-r--r--src/components/atoms/layout/section/section.module.scss (renamed from src/components/atoms/layout/section.module.scss)12
-rw-r--r--src/components/atoms/layout/section/section.stories.tsx (renamed from src/components/atoms/layout/section.stories.tsx)71
-rw-r--r--src/components/atoms/layout/section/section.test.tsx27
-rw-r--r--src/components/atoms/layout/section/section.tsx50
7 files changed, 111 insertions, 122 deletions
diff --git a/src/components/atoms/layout/section.test.tsx b/src/components/atoms/layout/section.test.tsx
deleted file mode 100644
index 6a2805d..0000000
--- a/src/components/atoms/layout/section.test.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import { render, screen } from '../../../../tests/utils';
-import { Section } from './section';
-
-const title = 'Section title';
-const content = 'Section content.';
-
-describe('Section', () => {
- it('renders a title (h2)', () => {
- render(<Section title={title} content={content} />);
- expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent(title);
- });
-
- it('renders a content', () => {
- render(<Section title={title} content={content} />);
- expect(screen.getByText(content)).toBeInTheDocument();
- });
-});
diff --git a/src/components/atoms/layout/section.tsx b/src/components/atoms/layout/section.tsx
deleted file mode 100644
index 107e80a..0000000
--- a/src/components/atoms/layout/section.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { FC, HTMLAttributes, ReactNode } from 'react';
-import { Heading } from '../headings';
-import styles from './section.module.scss';
-
-export type SectionVariant = 'dark' | 'light';
-
-export type SectionProps = Omit<
- HTMLAttributes<HTMLElement>,
- 'children' | 'content'
-> & {
- /**
- * The section content.
- */
- content: ReactNode | ReactNode[];
- /**
- * The section title.
- */
- title: string;
- /**
- * The section variant.
- */
- variant?: SectionVariant;
- /**
- * Add a border at the bottom of the section. Default: true.
- */
- withBorder?: boolean;
-};
-
-/**
- * Section component
- *
- * Render a section element.
- */
-export const Section: FC<SectionProps> = ({
- className = '',
- content,
- title,
- variant = 'dark',
- withBorder = true,
- ...props
-}) => {
- const borderClass = withBorder ? styles[`wrapper--borders`] : '';
- const variantClass = styles[`wrapper--${variant}`];
- const sectionClass = `${styles.wrapper} ${borderClass} ${variantClass} ${className}`;
-
- return (
- <section {...props} className={sectionClass}>
- <Heading level={2} className={styles.title}>
- {title}
- </Heading>
- <div className={styles.body}>{content}</div>
- </section>
- );
-};
diff --git a/src/components/atoms/layout/section/index.ts b/src/components/atoms/layout/section/index.ts
new file mode 100644
index 0000000..2786cf0
--- /dev/null
+++ b/src/components/atoms/layout/section/index.ts
@@ -0,0 +1 @@
+export * from './section';
diff --git a/src/components/atoms/layout/section.module.scss b/src/components/atoms/layout/section/section.module.scss
index 8a33848..771b8e3 100644
--- a/src/components/atoms/layout/section.module.scss
+++ b/src/components/atoms/layout/section/section.module.scss
@@ -1,9 +1,10 @@
-@use "../../../styles/abstracts/functions" as fun;
-@use "../../../styles/abstracts/placeholders";
+@use "../../../../styles/abstracts/functions" as fun;
+@use "../../../../styles/abstracts/placeholders";
.wrapper {
@extend %grid;
+ row-gap: var(--spacing-sm);
padding: var(--spacing-md) 0;
&--borders {
@@ -17,9 +18,8 @@
&--light {
background: var(--color-bg);
}
-}
-.body,
-.title {
- grid-column: 2;
+ > * {
+ grid-column: 2;
+ }
}
diff --git a/src/components/atoms/layout/section.stories.tsx b/src/components/atoms/layout/section/section.stories.tsx
index 8ab2729..0a3388b 100644
--- a/src/components/atoms/layout/section.stories.tsx
+++ b/src/components/atoms/layout/section/section.stories.tsx
@@ -1,4 +1,5 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Heading } from '../../headings';
import { Section } from './section';
/**
@@ -8,41 +9,29 @@ export default {
title: 'Atoms/Layout/Section',
component: Section,
args: {
- variant: 'dark',
- withBorder: true,
+ hasBorder: true,
+ variant: 'light',
},
argTypes: {
- className: {
- control: {
- type: 'text',
- },
- description: 'Set additional classnames to the section element.',
- table: {
- category: 'Styles',
- },
- type: {
- name: 'string',
- required: false,
- },
- },
- content: {
- control: {
- type: 'text',
- },
+ children: {
description: 'The section content.',
type: {
- name: 'string',
+ name: 'function',
required: true,
},
},
- title: {
+ hasBorder: {
control: {
- type: 'text',
+ type: 'boolean',
+ },
+ description: 'Add a border at the bottom of the section.',
+ table: {
+ category: 'Styles',
+ defaultValue: { summary: false },
},
- description: 'The section title.',
type: {
- name: 'string',
- required: true,
+ name: 'boolean',
+ required: false,
},
},
variant: {
@@ -60,20 +49,6 @@ export default {
required: false,
},
},
- withBorder: {
- control: {
- type: 'boolean',
- },
- description: 'Add a border at the bottom of the section.',
- table: {
- category: 'Styles',
- defaultValue: { summary: true },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
},
} as ComponentMeta<typeof Section>;
@@ -86,8 +61,12 @@ const Template: ComponentStory<typeof Section> = (args) => (
*/
export const Light = Template.bind({});
Light.args = {
- title: 'A title',
- content: 'The content.',
+ children: (
+ <>
+ <Heading level={2}>A section title</Heading>
+ <div>The body</div>
+ </>
+ ),
variant: 'light',
};
@@ -96,7 +75,11 @@ Light.args = {
*/
export const Dark = Template.bind({});
Dark.args = {
- title: 'A title',
- content: 'The content.',
+ children: (
+ <>
+ <Heading level={2}>A section title</Heading>
+ <div>The body</div>
+ </>
+ ),
variant: 'dark',
};
diff --git a/src/components/atoms/layout/section/section.test.tsx b/src/components/atoms/layout/section/section.test.tsx
new file mode 100644
index 0000000..85305c0
--- /dev/null
+++ b/src/components/atoms/layout/section/section.test.tsx
@@ -0,0 +1,27 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { Section } from './section';
+
+const content = 'Section content.';
+
+describe('Section', () => {
+ it('renders its body', () => {
+ render(<Section>{content}</Section>);
+ expect(rtlScreen.getByText(content)).toBeInTheDocument();
+ });
+
+ it('renders a section with border', () => {
+ render(<Section hasBorder>{content}</Section>);
+ expect(rtlScreen.getByText(content)).toHaveClass('wrapper--borders');
+ });
+
+ it('renders a light section', () => {
+ render(<Section variant="light">{content}</Section>);
+ expect(rtlScreen.getByText(content)).toHaveClass('wrapper--light');
+ });
+
+ it('renders a dark section', () => {
+ render(<Section variant="dark">{content}</Section>);
+ expect(rtlScreen.getByText(content)).toHaveClass('wrapper--dark');
+ });
+});
diff --git a/src/components/atoms/layout/section/section.tsx b/src/components/atoms/layout/section/section.tsx
new file mode 100644
index 0000000..63c658a
--- /dev/null
+++ b/src/components/atoms/layout/section/section.tsx
@@ -0,0 +1,50 @@
+import {
+ forwardRef,
+ type ForwardRefRenderFunction,
+ type HTMLAttributes,
+ type ReactNode,
+} from 'react';
+import styles from './section.module.scss';
+
+export type SectionVariant = 'dark' | 'light';
+
+export type SectionProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
+ /**
+ * The section content.
+ */
+ children: ReactNode | ReactNode[];
+ /**
+ * Add a border at the bottom of the section.
+ *
+ * @default false
+ */
+ hasBorder?: boolean;
+ /**
+ * The section variant.
+ *
+ * @default 'light'
+ */
+ variant?: SectionVariant;
+};
+
+const SectionWithRef: ForwardRefRenderFunction<HTMLElement, SectionProps> = (
+ { children, className = '', hasBorder = false, variant = 'light', ...props },
+ ref
+) => {
+ const borderClass = hasBorder ? styles[`wrapper--borders`] : '';
+ const variantClass = styles[`wrapper--${variant}`];
+ const sectionClass = `${styles.wrapper} ${borderClass} ${variantClass} ${className}`;
+
+ return (
+ <section {...props} className={sectionClass} ref={ref}>
+ {children}
+ </section>
+ );
+};
+
+/**
+ * Section component
+ *
+ * Render a section element with a heading and a body.
+ */
+export const Section = forwardRef(SectionWithRef);