diff options
Diffstat (limited to 'src/components/templates/page')
| -rw-r--r-- | src/components/templates/page/index.ts | 1 | ||||
| -rw-r--r-- | src/components/templates/page/page-comments.tsx | 12 | ||||
| -rw-r--r-- | src/components/templates/page/page-section.test.tsx | 43 | ||||
| -rw-r--r-- | src/components/templates/page/page-section.tsx | 43 | ||||
| -rw-r--r-- | src/components/templates/page/page.module.scss | 47 | ||||
| -rw-r--r-- | src/components/templates/page/page.stories.tsx | 40 | ||||
| -rw-r--r-- | src/components/templates/page/page.test.tsx | 31 | ||||
| -rw-r--r-- | src/components/templates/page/page.tsx | 23 |
8 files changed, 224 insertions, 16 deletions
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', |
