diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-09-27 15:40:16 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-10-24 12:25:00 +0200 |
| commit | ba793e043e4d8515b1a9ea490ee2c5f92b1fd6c2 (patch) | |
| tree | f7240a681fb3ee8c886a0c9ec3944082ba2d89bd /src/components/atoms/layout/section | |
| parent | 388e687857345c85ee550cd5da472675e05a6ff5 (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/section')
5 files changed, 188 insertions, 0 deletions
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<typeof Section>; + +const Template: ComponentStory<typeof Section> = (args) => ( + <Section {...args} /> +); + +/** + * Section Stories - Light + */ +export const Light = Template.bind({}); +Light.args = { + children: ( + <> + <Heading level={2}>A section title</Heading> + <div>The body</div> + </> + ), + variant: 'light', +}; + +/** + * Section Stories - Dark + */ +export const Dark = Template.bind({}); +Dark.args = { + 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); |
