summaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-05-17 22:48:41 +0200
committerArmand Philippot <git@armandphilippot.com>2022-05-17 22:48:41 +0200
commit271ef6debaca7ed9a01829dcef3a37e90a2dff05 (patch)
treeaa1513b40e7020a44cfcaaedc3a33d39ecfb8af7 /src/components
parent4e53a8654441481029746ff4e35a4a19c8d83709 (diff)
chore: use persistent layout
It prevents to rerender the common components between pages (header, footer...).
Diffstat (limited to 'src/components')
-rw-r--r--src/components/templates/layout/layout.module.scss14
-rw-r--r--src/components/templates/layout/layout.test.tsx11
-rw-r--r--src/components/templates/layout/layout.tsx54
-rw-r--r--src/components/templates/page/page-layout.module.scss11
-rw-r--r--src/components/templates/page/page-layout.stories.tsx24
-rw-r--r--src/components/templates/page/page-layout.tsx35
-rw-r--r--src/components/templates/sectioned/sectioned-layout.stories.tsx8
-rw-r--r--src/components/templates/sectioned/sectioned-layout.tsx25
8 files changed, 105 insertions, 77 deletions
diff --git a/src/components/templates/layout/layout.module.scss b/src/components/templates/layout/layout.module.scss
index 09904dc..1080732 100644
--- a/src/components/templates/layout/layout.module.scss
+++ b/src/components/templates/layout/layout.module.scss
@@ -1,5 +1,6 @@
@use "@styles/abstracts/functions" as fun;
@use "@styles/abstracts/mixins" as mix;
+@use "@styles/abstracts/placeholders";
.header {
border-bottom: fun.convert-px(3) solid var(--color-border-light);
@@ -9,6 +10,19 @@
flex: 1;
}
+.article {
+ &--grid {
+ @extend %grid;
+
+ grid-auto-flow: column dense;
+ align-items: baseline;
+ }
+
+ &--padding {
+ padding-bottom: var(--spacing-lg);
+ }
+}
+
.footer {
border-top: fun.convert-px(3) solid var(--color-border-light);
}
diff --git a/src/components/templates/layout/layout.test.tsx b/src/components/templates/layout/layout.test.tsx
index 94145ec..78547d4 100644
--- a/src/components/templates/layout/layout.test.tsx
+++ b/src/components/templates/layout/layout.test.tsx
@@ -4,33 +4,32 @@ import Layout from './layout';
const body =
'Sit dolorem eveniet. Sit sit odio nemo vitae corrupti modi sint est rerum. Pariatur quidem maiores distinctio. Quia et illum aspernatur est cum.';
-const breadcrumbSchema: BreadcrumbList['itemListElement'][] = [];
describe('Layout', () => {
it('renders the website header', () => {
- render(<Layout breadcrumbSchema={breadcrumbSchema}>{body}</Layout>);
+ render(<Layout>{body}</Layout>);
expect(screen.getByRole('banner')).toBeInTheDocument();
});
it('renders the website main content', () => {
- render(<Layout breadcrumbSchema={breadcrumbSchema}>{body}</Layout>);
+ render(<Layout>{body}</Layout>);
expect(screen.getByRole('main')).toBeInTheDocument();
});
it('renders the website footer', () => {
- render(<Layout breadcrumbSchema={breadcrumbSchema}>{body}</Layout>);
+ render(<Layout>{body}</Layout>);
expect(screen.getByRole('contentinfo')).toBeInTheDocument();
});
it('renders a skip to content link', () => {
- render(<Layout breadcrumbSchema={breadcrumbSchema}>{body}</Layout>);
+ render(<Layout>{body}</Layout>);
expect(
screen.getByRole('link', { name: 'Skip to content' })
).toBeInTheDocument();
});
it('renders an article', () => {
- render(<Layout breadcrumbSchema={breadcrumbSchema}>{body}</Layout>);
+ render(<Layout>{body}</Layout>);
expect(screen.getByRole('article')).toHaveTextContent(body);
});
});
diff --git a/src/components/templates/layout/layout.tsx b/src/components/templates/layout/layout.tsx
index 3eb02ee..559eaed 100644
--- a/src/components/templates/layout/layout.tsx
+++ b/src/components/templates/layout/layout.tsx
@@ -9,18 +9,13 @@ import Main from '@components/atoms/layout/main';
import NoScript from '@components/atoms/layout/no-script';
import Footer, { type FooterProps } from '@components/organisms/layout/footer';
import Header, { type HeaderProps } from '@components/organisms/layout/header';
+import { type NextPageWithLayoutOptions } from '@ts/types/app';
import useScrollPosition from '@utils/hooks/use-scroll-position';
import useSettings from '@utils/hooks/use-settings';
import Script from 'next/script';
-import { FC, ReactNode, useState } from 'react';
+import { FC, ReactElement, ReactNode, useState } from 'react';
import { useIntl } from 'react-intl';
-import {
- BreadcrumbList,
- Person,
- SearchAction,
- WebSite,
- WithContext,
-} from 'schema-dts';
+import { Person, SearchAction, WebSite, WithContext } from 'schema-dts';
import styles from './layout.module.scss';
export type QueryAction = SearchAction & {
@@ -29,17 +24,17 @@ export type QueryAction = SearchAction & {
export type LayoutProps = Pick<HeaderProps, 'isHome'> & {
/**
- * The breadcrumb JSON schema.
- */
- breadcrumbSchema: BreadcrumbList['itemListElement'][];
- /**
* The layout main content.
*/
children: ReactNode;
/**
- * Set additional classnames to the article element.
+ * Determine if article has a comments section.
+ */
+ withExtraPadding?: boolean;
+ /**
+ * Determine if article should use grid. Default: false.
*/
- className?: string;
+ useGrid?: boolean;
};
/**
@@ -48,14 +43,16 @@ export type LayoutProps = Pick<HeaderProps, 'isHome'> & {
* Render the base layout used by all pages.
*/
const Layout: FC<LayoutProps> = ({
- breadcrumbSchema,
children,
+ withExtraPadding = false,
isHome,
- ...props
+ useGrid = false,
}) => {
const intl = useIntl();
const { website } = useSettings();
const { baseline, copyright, locales, name, picture, url } = website;
+ const articleGridClass = useGrid ? 'article--grid' : '';
+ const articleCommentsClass = withExtraPadding ? 'article--padding' : '';
const skipToContent = intl.formatMessage({
defaultMessage: 'Skip to content',
@@ -188,11 +185,6 @@ const Layout: FC<LayoutProps> = ({
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(brandingSchema) }}
/>
- <Script
- id="schema-breadcrumb"
- type="application/ld+json"
- dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
- />
<noscript>
<div className={styles['noscript-spacing']}></div>
</noscript>
@@ -211,7 +203,11 @@ const Layout: FC<LayoutProps> = ({
className={styles.header}
/>
<Main id="main" className={styles.main}>
- <article {...props}>{children}</article>
+ <article
+ className={`${styles[articleGridClass]} ${styles[articleCommentsClass]}`}
+ >
+ {children}
+ </article>
</Main>
<Footer
copyright={copyrightData}
@@ -227,4 +223,18 @@ const Layout: FC<LayoutProps> = ({
);
};
+/**
+ * Get the global layout.
+ *
+ * @param {ReactElement} page - A page.
+ * @param {boolean} [isHome] - Determine if it is the homepage.
+ * @returns A page wrapped with the global layout.
+ */
+export const getLayout = (
+ page: ReactElement,
+ props: NextPageWithLayoutOptions
+) => {
+ return <Layout {...props}>{page}</Layout>;
+};
+
export default Layout;
diff --git a/src/components/templates/page/page-layout.module.scss b/src/components/templates/page/page-layout.module.scss
index c6b4e8d..c7674ae 100644
--- a/src/components/templates/page/page-layout.module.scss
+++ b/src/components/templates/page/page-layout.module.scss
@@ -2,17 +2,6 @@
@use "@styles/abstracts/mixins" as mix;
@use "@styles/abstracts/placeholders";
-.article {
- @extend %grid;
-
- grid-auto-flow: column dense;
- align-items: baseline;
-
- &--no-comments {
- padding-bottom: var(--spacing-lg);
- }
-}
-
.breadcrumb {
@extend %grid;
diff --git a/src/components/templates/page/page-layout.stories.tsx b/src/components/templates/page/page-layout.stories.tsx
index 8af5f98..bec1066 100644
--- a/src/components/templates/page/page-layout.stories.tsx
+++ b/src/components/templates/page/page-layout.stories.tsx
@@ -1,11 +1,11 @@
import ButtonLink from '@components/atoms/buttons/button-link';
import Heading from '@components/atoms/headings/heading';
import Link from '@components/atoms/links/link';
-import ProgressBar from '@components/atoms/loaders/progress-bar';
import PostsList from '@components/organisms/layout/posts-list';
import LinksListWidget from '@components/organisms/widgets/links-list-widget';
import Sharing from '@components/organisms/widgets/sharing';
import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { LayoutBase } from '../layout/layout.stories';
import PageLayoutComponent from './page-layout';
/**
@@ -118,20 +118,6 @@ export default {
required: false,
},
},
- isHome: {
- control: {
- type: 'boolean',
- },
- description: 'Determine if the current page is the homepage.',
- table: {
- category: 'Options',
- defaultValue: { summary: false },
- },
- type: {
- name: 'boolean',
- required: false,
- },
- },
title: {
control: {
type: 'text',
@@ -168,6 +154,13 @@ export default {
},
},
},
+ decorators: [
+ (Story) => (
+ <LayoutBase {...LayoutBase.args}>
+ <Story />
+ </LayoutBase>
+ ),
+ ],
parameters: {
layout: 'fullscreen',
},
@@ -477,7 +470,6 @@ Blog.args = {
children: (
<>
<PostsList posts={posts} byYear={true} total={posts.length} />
- <ProgressBar min={1} max={1} current={1} info="1/1 page loaded." />
</>
),
widgets: [
diff --git a/src/components/templates/page/page-layout.tsx b/src/components/templates/page/page-layout.tsx
index 2ff2084..d171944 100644
--- a/src/components/templates/page/page-layout.tsx
+++ b/src/components/templates/page/page-layout.tsx
@@ -21,19 +21,20 @@ import TableOfContents from '@components/organisms/widgets/table-of-contents';
import { type SendCommentVars } from '@services/graphql/api';
import { sendComment } from '@services/graphql/comments';
import useIsMounted from '@utils/hooks/use-is-mounted';
+import Script from 'next/script';
import { FC, HTMLAttributes, ReactNode, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
-import Layout, { type LayoutProps } from '../layout/layout';
+import { BreadcrumbList } from 'schema-dts';
import styles from './page-layout.module.scss';
-export type PageLayoutProps = Pick<
- LayoutProps,
- 'breadcrumbSchema' | 'isHome'
-> & {
+export type PageLayoutProps = {
/**
* True if the page accepts new comments. Default: false.
*/
allowComments?: boolean;
+ /**
+ * Set attributes to the page body.
+ */
bodyAttributes?: HTMLAttributes<HTMLDivElement>;
/**
* Set additional classnames to the body wrapper.
@@ -44,6 +45,10 @@ export type PageLayoutProps = Pick<
*/
breadcrumb: BreadcrumbItem[];
/**
+ * The breadcrumb JSON schema.
+ */
+ breadcrumbSchema: BreadcrumbList['itemListElement'][];
+ /**
* The main content of the page.
*/
children: ReactNode;
@@ -98,7 +103,6 @@ const PageLayout: FC<PageLayoutProps> = ({
headerMeta,
id,
intro,
- isHome = false,
title,
widgets,
withToC = false,
@@ -117,13 +121,7 @@ const PageLayout: FC<PageLayoutProps> = ({
const bodyRef = useRef<HTMLDivElement>(null);
const isMounted = useIsMounted(bodyRef);
-
const hasComments = Array.isArray(comments) && comments.length > 0;
- const articleModifier =
- hasComments || allowComments
- ? 'article--has-comments'
- : 'article--no-comments';
-
const [status, setStatus] = useState<NoticeKind>('info');
const [statusMessage, setStatusMessage] = useState<string>('');
const isReplyRef = useRef<boolean>(false);
@@ -186,11 +184,12 @@ const PageLayout: FC<PageLayoutProps> = ({
};
return (
- <Layout
- breadcrumbSchema={breadcrumbSchema}
- isHome={isHome}
- className={`${styles.article} ${styles[articleModifier]}`}
- >
+ <>
+ <Script
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ />
<Breadcrumb
items={breadcrumb}
className={styles.breadcrumb}
@@ -291,7 +290,7 @@ const PageLayout: FC<PageLayoutProps> = ({
</section>
</div>
)}
- </Layout>
+ </>
);
};
diff --git a/src/components/templates/sectioned/sectioned-layout.stories.tsx b/src/components/templates/sectioned/sectioned-layout.stories.tsx
index ce31a83..689f9a7 100644
--- a/src/components/templates/sectioned/sectioned-layout.stories.tsx
+++ b/src/components/templates/sectioned/sectioned-layout.stories.tsx
@@ -1,4 +1,5 @@
import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { LayoutBase } from '../layout/layout.stories';
import SectionedLayoutComponent from './sectioned-layout';
/**
@@ -31,6 +32,13 @@ export default {
},
},
},
+ decorators: [
+ (Story) => (
+ <LayoutBase {...LayoutBase.args}>
+ <Story />
+ </LayoutBase>
+ ),
+ ],
parameters: {
layout: 'fullscreen',
},
diff --git a/src/components/templates/sectioned/sectioned-layout.tsx b/src/components/templates/sectioned/sectioned-layout.tsx
index 58d5ad0..f91c354 100644
--- a/src/components/templates/sectioned/sectioned-layout.tsx
+++ b/src/components/templates/sectioned/sectioned-layout.tsx
@@ -2,12 +2,17 @@ import Section, {
type SectionProps,
type SectionVariant,
} from '@components/atoms/layout/section';
+import Script from 'next/script';
import { FC } from 'react';
-import Layout, { type LayoutProps } from '../layout/layout';
+import { BreadcrumbList } from 'schema-dts';
export type Section = Pick<SectionProps, 'content' | 'title'>;
-export type SectionedLayoutProps = Pick<LayoutProps, 'breadcrumbSchema'> & {
+export type SectionedLayoutProps = {
+ /**
+ * The breadcrumb JSON schema.
+ */
+ breadcrumbSchema: BreadcrumbList['itemListElement'][];
/**
* An array of objects describing each section.
*/
@@ -19,7 +24,10 @@ export type SectionedLayoutProps = Pick<LayoutProps, 'breadcrumbSchema'> & {
*
* Render a sectioned layout.
*/
-const SectionedLayout: FC<SectionedLayoutProps> = ({ sections, ...props }) => {
+const SectionedLayout: FC<SectionedLayoutProps> = ({
+ breadcrumbSchema,
+ sections,
+}) => {
const getSections = (items: SectionProps[]) => {
return items.map((section, index) => {
const variant: SectionVariant = index % 2 ? 'light' : 'dark';
@@ -37,7 +45,16 @@ const SectionedLayout: FC<SectionedLayoutProps> = ({ sections, ...props }) => {
});
};
- return <Layout {...props}>{getSections(sections)}</Layout>;
+ return (
+ <>
+ <Script
+ id="schema-breadcrumb"
+ type="application/ld+json"
+ dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }}
+ />
+ {getSections(sections)}
+ </>
+ );
};
export default SectionedLayout;