From ba793e043e4d8515b1a9ea490ee2c5f92b1fd6c2 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 27 Sep 2023 15:40:16 +0200 Subject: 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 --- src/components/atoms/layout/section/index.ts | 1 + .../atoms/layout/section/section.module.scss | 25 +++++++ .../atoms/layout/section/section.stories.tsx | 85 ++++++++++++++++++++++ .../atoms/layout/section/section.test.tsx | 27 +++++++ src/components/atoms/layout/section/section.tsx | 50 +++++++++++++ 5 files changed, 188 insertions(+) create mode 100644 src/components/atoms/layout/section/index.ts create mode 100644 src/components/atoms/layout/section/section.module.scss create mode 100644 src/components/atoms/layout/section/section.stories.tsx create mode 100644 src/components/atoms/layout/section/section.test.tsx create mode 100644 src/components/atoms/layout/section/section.tsx (limited to 'src/components/atoms/layout/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/section.module.scss b/src/components/atoms/layout/section/section.module.scss new file mode 100644 index 0000000..771b8e3 --- /dev/null +++ b/src/components/atoms/layout/section/section.module.scss @@ -0,0 +1,25 @@ +@use "../../../../styles/abstracts/functions" as fun; +@use "../../../../styles/abstracts/placeholders"; + +.wrapper { + @extend %grid; + + row-gap: var(--spacing-sm); + padding: var(--spacing-md) 0; + + &--borders { + border-bottom: fun.convert-px(1) solid var(--color-border); + } + + &--dark { + background: var(--color-bg-secondary); + } + + &--light { + background: var(--color-bg); + } + + > * { + grid-column: 2; + } +} diff --git a/src/components/atoms/layout/section/section.stories.tsx b/src/components/atoms/layout/section/section.stories.tsx new file mode 100644 index 0000000..0a3388b --- /dev/null +++ b/src/components/atoms/layout/section/section.stories.tsx @@ -0,0 +1,85 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Heading } from '../../headings'; +import { Section } from './section'; + +/** + * Section - Storybook Meta + */ +export default { + title: 'Atoms/Layout/Section', + component: Section, + args: { + hasBorder: true, + variant: 'light', + }, + argTypes: { + children: { + description: 'The section content.', + type: { + name: 'function', + required: true, + }, + }, + hasBorder: { + control: { + type: 'boolean', + }, + description: 'Add a border at the bottom of the section.', + table: { + category: 'Styles', + defaultValue: { summary: false }, + }, + type: { + name: 'boolean', + required: false, + }, + }, + variant: { + control: { + type: 'select', + }, + description: 'The section variant.', + options: ['light', 'dark'], + table: { + category: 'Styles', + defaultValue: { summary: 'dark' }, + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( +
+); + +/** + * Section Stories - Light + */ +export const Light = Template.bind({}); +Light.args = { + children: ( + <> + A section title +
The body
+ + ), + variant: 'light', +}; + +/** + * Section Stories - Dark + */ +export const Dark = Template.bind({}); +Dark.args = { + children: ( + <> + A section title +
The body
+ + ), + 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(
{content}
); + expect(rtlScreen.getByText(content)).toBeInTheDocument(); + }); + + it('renders a section with border', () => { + render(
{content}
); + expect(rtlScreen.getByText(content)).toHaveClass('wrapper--borders'); + }); + + it('renders a light section', () => { + render(
{content}
); + expect(rtlScreen.getByText(content)).toHaveClass('wrapper--light'); + }); + + it('renders a dark section', () => { + render(
{content}
); + 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, '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 = ( + { 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 ( +
+ {children} +
+ ); +}; + +/** + * Section component + * + * Render a section element with a heading and a body. + */ +export const Section = forwardRef(SectionWithRef); -- cgit v1.2.3