aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/templates
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-11-22 19:07:25 +0100
committerArmand Philippot <git@armandphilippot.com>2023-11-23 11:44:28 +0100
commit4f1181581e177dd80a76165a0f930ef4577f9c6a (patch)
tree6029f86d42af7700f5b59cd1477854190bab65c6 /src/components/templates
parent329e7c89bac50be9db2c6d2ec6751ab0ffad42ac (diff)
refactor(components): integrate sectioned page template into Page
* replace Section component by a generic one (other components should be able to use it) * add a PageSection component * add `hasSections` prop to Page component * remove sectioned-page template
Diffstat (limited to 'src/components/templates')
-rw-r--r--src/components/templates/index.ts1
-rw-r--r--src/components/templates/page/index.ts1
-rw-r--r--src/components/templates/page/page-comments.tsx12
-rw-r--r--src/components/templates/page/page-section.test.tsx43
-rw-r--r--src/components/templates/page/page-section.tsx43
-rw-r--r--src/components/templates/page/page.module.scss47
-rw-r--r--src/components/templates/page/page.stories.tsx40
-rw-r--r--src/components/templates/page/page.test.tsx31
-rw-r--r--src/components/templates/page/page.tsx23
-rw-r--r--src/components/templates/sectioned/index.ts1
-rw-r--r--src/components/templates/sectioned/sectioned-layout.fixtures.tsx59
-rw-r--r--src/components/templates/sectioned/sectioned-layout.stories.tsx58
-rw-r--r--src/components/templates/sectioned/sectioned-layout.test.tsx21
-rw-r--r--src/components/templates/sectioned/sectioned-layout.tsx51
14 files changed, 224 insertions, 207 deletions
diff --git a/src/components/templates/index.ts b/src/components/templates/index.ts
index ae34898..bd41ab2 100644
--- a/src/components/templates/index.ts
+++ b/src/components/templates/index.ts
@@ -1,3 +1,2 @@
export * from './layout';
export * from './page';
-export * from './sectioned';
diff --git a/src/components/templates/page/index.ts b/src/components/templates/page/index.ts
index 3b26326..f6d2d48 100644
--- a/src/components/templates/page/index.ts
+++ b/src/components/templates/page/index.ts
@@ -3,4 +3,5 @@ export * from './page-body';
export * from './page-comments';
export * from './page-footer';
export * from './page-header';
+export * from './page-section';
export * from './page-sidebar';
diff --git a/src/components/templates/page/page-comments.tsx b/src/components/templates/page/page-comments.tsx
index bc715e8..170d6b7 100644
--- a/src/components/templates/page/page-comments.tsx
+++ b/src/components/templates/page/page-comments.tsx
@@ -8,7 +8,7 @@ import {
import { useIntl } from 'react-intl';
import { sendComment } from '../../../services/graphql';
import type { SendCommentInput } from '../../../types';
-import { Heading, Link } from '../../atoms';
+import { Heading, Link, Section } from '../../atoms';
import { Card, CardBody } from '../../molecules';
import {
type CommentData,
@@ -138,7 +138,7 @@ const PageCommentsWithRef: ForwardRefRenderFunction<
return (
<div {...props} className={wrapperClass} ref={ref}>
- <section className={styles.section}>
+ <Section className={styles.comments__body}>
<Heading className={styles.heading} level={2}>
{commentsListTitle}
</Heading>
@@ -154,10 +154,10 @@ const PageCommentsWithRef: ForwardRefRenderFunction<
<CardBody>{noCommentsYet}</CardBody>
</Card>
)}
- </section>
+ </Section>
{areCommentsClosed ? null : (
- <section
- className={styles.section}
+ <Section
+ className={styles.comments__body}
// eslint-disable-next-line react/jsx-no-literals
id="comment-form-section"
>
@@ -169,7 +169,7 @@ const PageCommentsWithRef: ForwardRefRenderFunction<
className={styles.form}
onSubmit={saveComment}
/>
- </section>
+ </Section>
)}
</div>
);
diff --git a/src/components/templates/page/page-section.test.tsx b/src/components/templates/page/page-section.test.tsx
new file mode 100644
index 0000000..b372ab7
--- /dev/null
+++ b/src/components/templates/page/page-section.test.tsx
@@ -0,0 +1,43 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { PageSection } from './page-section';
+
+describe('PageSection', () => {
+ it('renders its children', () => {
+ const body = 'a voluptas iste';
+
+ render(<PageSection>{body}</PageSection>);
+
+ expect(rtlScreen.getByText(body)).toBeInTheDocument();
+ });
+
+ it('can use the light variant', () => {
+ const body = 'a voluptas iste';
+
+ render(<PageSection variant="light">{body}</PageSection>);
+
+ expect(rtlScreen.getByText(body).parentElement).toHaveClass(
+ 'section--light'
+ );
+ });
+
+ it('can use the dark variant', () => {
+ const body = 'a voluptas iste';
+
+ render(<PageSection variant="dark">{body}</PageSection>);
+
+ expect(rtlScreen.getByText(body).parentElement).toHaveClass(
+ 'section--dark'
+ );
+ });
+
+ it('can have a border at the bottom', () => {
+ const body = 'a voluptas iste';
+
+ render(<PageSection hasBorder>{body}</PageSection>);
+
+ expect(rtlScreen.getByText(body).parentElement).toHaveClass(
+ 'section--bordered'
+ );
+ });
+});
diff --git a/src/components/templates/page/page-section.tsx b/src/components/templates/page/page-section.tsx
new file mode 100644
index 0000000..24bc1a1
--- /dev/null
+++ b/src/components/templates/page/page-section.tsx
@@ -0,0 +1,43 @@
+import { type ForwardRefRenderFunction, forwardRef } from 'react';
+import { Section, type SectionProps } from '../../atoms';
+import styles from './page.module.scss';
+
+export type PageSectionVariant = 'dark' | 'light';
+
+export type PageSectionProps = SectionProps & {
+ /**
+ * Add a border at the bottom of the section.
+ *
+ * @default false
+ */
+ hasBorder?: boolean;
+ /**
+ * The section variant.
+ *
+ * @default 'light'
+ */
+ variant?: PageSectionVariant;
+};
+
+const PageSectionWithRef: ForwardRefRenderFunction<
+ HTMLElement,
+ PageSectionProps
+> = (
+ { children, className = '', hasBorder = false, variant = 'light', ...props },
+ ref
+) => {
+ const sectionClass = [
+ styles.section,
+ styles[hasBorder ? 'section--bordered' : ''],
+ styles[`section--${variant}`],
+ className,
+ ].join(' ');
+
+ return (
+ <Section {...props} className={sectionClass} ref={ref}>
+ <div className={styles.section__body}>{children}</div>
+ </Section>
+ );
+};
+
+export const PageSection = forwardRef(PageSectionWithRef);
diff --git a/src/components/templates/page/page.module.scss b/src/components/templates/page/page.module.scss
index b521438..d2752a1 100644
--- a/src/components/templates/page/page.module.scss
+++ b/src/components/templates/page/page.module.scss
@@ -14,7 +14,8 @@
}
.breadcrumbs,
-.page {
+.page--regular,
+.section {
--border-size: #{fun.convert-px(3)};
--col-gap: clamp(var(--spacing-md), 4vw, var(--spacing-2xl));
--left-col: 0;
@@ -25,6 +26,10 @@
grid-auto-flow: column dense;
align-items: baseline;
+}
+
+.breadcrumbs,
+.page--regular {
margin-top: var(--spacing-sm);
}
@@ -107,6 +112,36 @@
}
}
+.section {
+ --right-col: minmax(0, 1fr);
+ --left-col: minmax(0, 1fr);
+
+ @extend %grid;
+
+ row-gap: var(--spacing-sm);
+ padding: var(--spacing-md) 0;
+
+ &--bordered {
+ border-bottom: fun.convert-px(1) solid var(--color-border);
+ }
+
+ &--dark {
+ background: var(--color-bg-secondary);
+ }
+
+ &--light {
+ background: var(--color-bg);
+ }
+
+ &__body {
+ grid-column: 2;
+
+ > * + * {
+ margin-block: var(--spacing-sm);
+ }
+ }
+}
+
:where(.footer) {
.btn {
margin-inline-end: var(--spacing-2xs);
@@ -161,13 +196,13 @@
padding: 0 0 var(--spacing-lg);
background: var(--color-bg-secondary);
border-top: var(--border-size) solid var(--color-border-light);
-}
-:where(.comments) {
- .section {
+ &__body {
grid-column: 2;
}
+}
+:where(.comments) {
.heading {
width: fit-content;
margin: var(--spacing-md) auto;
@@ -181,7 +216,7 @@
@container page (width > #{var.get-breakpoint("md")}) {
.breadcrumbs,
- .page {
+ .page--regular {
--right-col: minmax(25ch, 1fr);
}
@@ -200,7 +235,7 @@
@container page (width > #{var.get-breakpoint("lg")}) {
.breadcrumbs,
- .page {
+ .page--regular {
--left-col: minmax(25ch, 1fr);
}
diff --git a/src/components/templates/page/page.stories.tsx b/src/components/templates/page/page.stories.tsx
index 6b1058e..8b1616b 100644
--- a/src/components/templates/page/page.stories.tsx
+++ b/src/components/templates/page/page.stories.tsx
@@ -7,6 +7,7 @@ import { PageBody } from './page-body';
import { PageComments } from './page-comments';
import { PageFooter } from './page-footer';
import { PageHeader } from './page-header';
+import { PageSection } from './page-section';
import { PageSidebar } from './page-sidebar';
/**
@@ -454,3 +455,42 @@ HeaderBodyComments.args = {
</>
),
};
+
+/**
+ * Page Stories - SectionedPage
+ */
+export const SectionedPage = Template.bind({});
+SectionedPage.args = {
+ children: (
+ <>
+ <PageSection>
+ <Heading level={2}>A section title</Heading>
+ <p>
+ Illo temporibus nihil maiores nesciunt. Veritatis distinctio aperiam
+ culpa eveniet incidunt eos harum porro labore. Soluta culpa unde
+ adipisci fugiat voluptas eos.
+ </p>
+ </PageSection>
+ <PageSection variant="dark">
+ <Heading level={2}>Another section title</Heading>
+ <p>
+ Sint consequatur animi eum beatae. Non corporis quos quia et magnam.
+ Cumque molestiae blanditiis aut. Et suscipit iusto laudantium iusto
+ dignissimos.
+ </p>
+ </PageSection>
+ <PageSection>
+ <Heading level={2}>A third section title</Heading>
+ <p>
+ Omnis corporis perferendis animi iste quidem placeat est minus. Enim
+ autem consequatur voluptatem provident qui culpa. Aliquid aliquam
+ consequatur non explicabo ut distinctio quis a non. Delectus unde odio
+ eveniet temporibus omnis. Reprehenderit consequatur minima in
+ consequatur saepe est sed. Accusantium quia quae magnam expedita nihil
+ rerum omnis temporibus perspiciatis.
+ </p>
+ </PageSection>
+ </>
+ ),
+ hasSections: true,
+};
diff --git a/src/components/templates/page/page.test.tsx b/src/components/templates/page/page.test.tsx
index 21c5a86..fb06cb1 100644
--- a/src/components/templates/page/page.test.tsx
+++ b/src/components/templates/page/page.test.tsx
@@ -3,6 +3,8 @@ import { render, screen as rtlScreen } from '../../../../tests/utils';
import type { BreadcrumbsItem } from '../../organisms';
import { Page } from './page';
import { PageBody } from './page-body';
+import { PageSection } from './page-section';
+import { Heading } from 'src/components/atoms';
describe('Page', () => {
it('renders its children', () => {
@@ -46,4 +48,33 @@ describe('Page', () => {
expect(rtlScreen.getByText(body)).toHaveClass('page--body-last');
});
+
+ it('can render a sectioned page', () => {
+ const sections = [
+ {
+ heading: 'excepturi ex dolorum',
+ contents:
+ 'Id eius voluptas rerum nemo ullam omnis provident deserunt. Expedita sit ut consequatur deleniti. Maiores nam. Necessitatibus pariatur et qui dolor quia labore.',
+ },
+ {
+ heading: 'rerum corporis et',
+ contents:
+ 'Vel maxime doloremque quo laborum debitis. Ab perferendis animi dolores et ut voluptatem. Tempore aut doloremque sunt enim aut sint. Quae iure saepe consectetur. Ex animi ut. Nobis aliquid iste accusantium nesciunt ab voluptas illum.',
+ },
+ ];
+
+ render(
+ <Page hasSections>
+ {sections.map((section) => (
+ <PageSection aria-label={section.heading} key={section.heading}>
+ <Heading level={2}>{section.heading}</Heading>
+ <p>{section.contents}</p>
+ </PageSection>
+ ))}
+ </Page>
+ );
+
+ expect(rtlScreen.getAllByRole('region')).toHaveLength(sections.length);
+ expect(rtlScreen.getByRole('article')).toHaveClass('page--full');
+ });
});
diff --git a/src/components/templates/page/page.tsx b/src/components/templates/page/page.tsx
index f5f3ea5..b40c2f9 100644
--- a/src/components/templates/page/page.tsx
+++ b/src/components/templates/page/page.tsx
@@ -14,6 +14,12 @@ export type PageProps = HTMLAttributes<HTMLDivElement> & {
*/
breadcrumbs?: BreadcrumbsItem[];
/**
+ * Is it a regular page or a sectioned one?
+ *
+ * @default false
+ */
+ hasSections?: boolean;
+ /**
* Add an extra padding to the body when there are no footer/comments.
*
* Note: this should be refactored when `:has()` pseudo-class will have a
@@ -25,13 +31,22 @@ export type PageProps = HTMLAttributes<HTMLDivElement> & {
};
const PageWithRef: ForwardRefRenderFunction<HTMLDivElement, PageProps> = (
- { breadcrumbs, children, className = '', isBodyLastChild = false, ...props },
+ {
+ breadcrumbs,
+ children,
+ className = '',
+ hasSections = false,
+ isBodyLastChild = false,
+ ...props
+ },
ref
) => {
const wrapperClass = `${styles.wrapper} ${className}`;
- const pageClass = `${styles.page} ${
- styles[isBodyLastChild ? 'page--body-last' : '']
- }`;
+ const pageClass = [
+ styles.page,
+ styles[hasSections ? 'page--full' : 'page--regular'],
+ styles[isBodyLastChild ? 'page--body-last' : ''],
+ ].join(' ');
const intl = useIntl();
const breadcrumbsLabel = intl.formatMessage({
defaultMessage: 'Breadcrumbs',
diff --git a/src/components/templates/sectioned/index.ts b/src/components/templates/sectioned/index.ts
deleted file mode 100644
index a8c6045..0000000
--- a/src/components/templates/sectioned/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './sectioned-layout';
diff --git a/src/components/templates/sectioned/sectioned-layout.fixtures.tsx b/src/components/templates/sectioned/sectioned-layout.fixtures.tsx
deleted file mode 100644
index 0da8e7d..0000000
--- a/src/components/templates/sectioned/sectioned-layout.fixtures.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-/* eslint-disable react/jsx-no-literals */
-import { Heading } from '../../atoms';
-import type { PageSection } from './sectioned-layout';
-
-export const sections: PageSection[] = [
- {
- id: 'section-1',
- children: (
- <>
- <Heading level={2}>Section 1</Heading>
- <div>
- Qui suscipit ea et aut dicta. Quia ut dignissimos. Sapiente beatae
- voluptatem quis et. Nemo vitae magni. Nihil iste officia est sed esse
- molestiae doloribus. Quia temporibus nobis ea fuga quis incidunt
- doloribus eaque.
- </div>
- </>
- ),
- },
- {
- id: 'section-2',
- children: (
- <>
- <Heading level={2}>Section 2</Heading>
- <div>
- Reprehenderit aut magnam ut quos. Voluptatibus beatae et. Earum non
- atque voluptatum illum rem distinctio repellat.
- </div>
- </>
- ),
- },
- {
- id: 'section-3',
- children: (
- <>
- <Heading level={2}>Section 3</Heading>
- <div>
- Placeat rem dolores dolore illum earum officia dolore. Ut est ducimus.
- Officia eveniet pariatur ut laboriosam voluptatibus aut doloremque
- natus quis.
- </div>
- </>
- ),
- },
- {
- id: 'section-4',
- children: (
- <>
- <Heading level={2}>Section 4</Heading>
- <div>
- Vitae facere ipsa eum sunt debitis veritatis dolorem labore qui.
- Dolores recusandae omnis aut. Repudiandae quia neque porro in
- blanditiis. A atque minima fugit. Totam quidem voluptas natus velit
- at.
- </div>
- </>
- ),
- },
-];
diff --git a/src/components/templates/sectioned/sectioned-layout.stories.tsx b/src/components/templates/sectioned/sectioned-layout.stories.tsx
deleted file mode 100644
index 0336b7a..0000000
--- a/src/components/templates/sectioned/sectioned-layout.stories.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { ComponentMeta, ComponentStory } from '@storybook/react';
-import { LayoutBase } from '../layout/layout.stories';
-import { SectionedLayout as SectionedLayoutComponent } from './sectioned-layout';
-import { sections } from './sectioned-layout.fixtures';
-
-/**
- * SectionedLayout - Storybook Meta
- */
-export default {
- title: 'Templates/Sectioned',
- component: SectionedLayoutComponent,
- args: {
- breadcrumbSchema: [],
- },
- argTypes: {
- breadcrumbSchema: {
- control: {
- type: null,
- },
- description: 'The JSON schema for breadcrumb items.',
- type: {
- name: 'object',
- required: true,
- value: {},
- },
- },
- sections: {
- description: 'The different sections.',
- type: {
- name: 'object',
- required: true,
- value: {},
- },
- },
- },
- decorators: [
- (Story) => (
- <LayoutBase {...LayoutBase.args}>
- <Story />
- </LayoutBase>
- ),
- ],
- parameters: {
- layout: 'fullscreen',
- },
-} as ComponentMeta<typeof SectionedLayoutComponent>;
-
-const Template: ComponentStory<typeof SectionedLayoutComponent> = (args) => (
- <SectionedLayoutComponent {...args} />
-);
-
-/**
- * Sectioned Layout Stories - Default
- */
-export const Sectioned = Template.bind({});
-Sectioned.args = {
- sections,
-};
diff --git a/src/components/templates/sectioned/sectioned-layout.test.tsx b/src/components/templates/sectioned/sectioned-layout.test.tsx
deleted file mode 100644
index 372b0fb..0000000
--- a/src/components/templates/sectioned/sectioned-layout.test.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { describe, expect, it } from '@jest/globals';
-import type { BreadcrumbList } from 'schema-dts';
-import { render, screen as rtlScreen } from '../../../../tests/utils';
-import { SectionedLayout } from './sectioned-layout';
-import { sections } from './sectioned-layout.fixtures';
-
-const breadcrumbSchema: BreadcrumbList['itemListElement'][] = [];
-
-describe('SectionedLayout', () => {
- it('renders the correct number of section', () => {
- render(
- <SectionedLayout
- breadcrumbSchema={breadcrumbSchema}
- sections={sections}
- />
- );
- expect(
- rtlScreen.getAllByRole('heading', { name: /^Section/ })
- ).toHaveLength(sections.length);
- });
-});
diff --git a/src/components/templates/sectioned/sectioned-layout.tsx b/src/components/templates/sectioned/sectioned-layout.tsx
deleted file mode 100644
index 6d58e83..0000000
--- a/src/components/templates/sectioned/sectioned-layout.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import Script from 'next/script';
-import type { FC } from 'react';
-import type { BreadcrumbList } from 'schema-dts';
-import { Section, type SectionProps, type SectionVariant } from '../../atoms';
-
-export type PageSection = Required<Pick<SectionProps, 'children' | 'id'>>;
-
-export type SectionedLayoutProps = {
- /**
- * The breadcrumb JSON schema.
- */
- breadcrumbSchema: BreadcrumbList['itemListElement'][];
- /**
- * An array of objects describing each section.
- */
- sections: PageSection[];
-};
-
-/**
- * SectionedLayout component
- *
- * Render a sectioned layout.
- */
-export const SectionedLayout: FC<SectionedLayoutProps> = ({
- breadcrumbSchema,
- sections,
-}) => {
- const getSections = (items: PageSection[]) =>
- items.map((section, index) => {
- const variant: SectionVariant = index % 2 ? 'light' : 'dark';
- const isLastSection = index === items.length - 1;
-
- return (
- <Section hasBorder={!isLastSection} key={section.id} variant={variant}>
- {section.children}
- </Section>
- );
- });
-
- return (
- <>
- <Script
- dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
- // eslint-disable-next-line react/jsx-no-literals -- Id allowed.
- id="schema-breadcrumb"
- type="application/ld+json"
- />
- {getSections(sections)}
- </>
- );
-};