diff options
Diffstat (limited to 'src')
22 files changed, 253 insertions, 116 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; diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 4f6e22d..4ab7784 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,11 +1,13 @@  import Link from '@components/atoms/links/link';  import LinksListWidget from '@components/organisms/widgets/links-list-widget'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout from '@components/templates/page/page-layout';  import {    getThematicsPreview,    getTotalThematics,  } from '@services/graphql/thematics';  import { getTopicsPreview, getTotalTopics } from '@services/graphql/topics'; +import { type NextPageWithLayout } from '@ts/types/app';  import {    type RawThematicPreview,    type RawTopicPreview, @@ -17,7 +19,7 @@ import {  } from '@utils/helpers/pages';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { ReactNode } from 'react';  import { useIntl } from 'react-intl'; @@ -31,7 +33,7 @@ type Error404PageProps = {  /**   * Error 404 page.   */ -const Error404Page: NextPage<Error404PageProps> = ({ +const Error404Page: NextPageWithLayout<Error404PageProps> = ({    thematicsList,    topicsList,  }) => { @@ -119,6 +121,9 @@ const Error404Page: NextPage<Error404PageProps> = ({    );  }; +Error404Page.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps<Error404PageProps> = async ({    locale,  }) => { diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 939b337..5bc9f85 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,15 +1,16 @@ +import { type AppPropsWithLayout } from '@ts/types/app';  import { settings } from '@utils/config';  import { AckeeProvider } from '@utils/providers/ackee';  import { PrismThemeProvider } from '@utils/providers/prism-theme';  import { ThemeProvider } from 'next-themes'; -import { AppProps } from 'next/app';  import { useRouter } from 'next/router';  import { IntlProvider } from 'react-intl';  import '../styles/globals.scss'; -const App = ({ Component, pageProps }: AppProps) => { +const App = ({ Component, pageProps }: AppPropsWithLayout) => {    const { locale, defaultLocale } = useRouter();    const appLocale: string = locale || settings.locales.defaultLocale; +  const getLayout = Component.getLayout ?? ((page) => page);    return (      <AckeeProvider domain={settings.ackee.url} siteId={settings.ackee.siteId}> @@ -24,7 +25,7 @@ const App = ({ Component, pageProps }: AppProps) => {            enableSystem={true}          >            <PrismThemeProvider> -            <Component {...pageProps} /> +            {getLayout(<Component {...pageProps} />, {})}            </PrismThemeProvider>          </ThemeProvider>        </IntlProvider> diff --git a/src/pages/article/[slug].tsx b/src/pages/article/[slug].tsx index a0fb7fc..a3df43b 100644 --- a/src/pages/article/[slug].tsx +++ b/src/pages/article/[slug].tsx @@ -2,6 +2,7 @@ import ButtonLink from '@components/atoms/buttons/button-link';  import Link from '@components/atoms/links/link';  import ResponsiveImage from '@components/molecules/images/responsive-image';  import Sharing from '@components/organisms/widgets/sharing'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout, {    type PageLayoutProps,  } from '@components/templates/page/page-layout'; @@ -11,7 +12,11 @@ import {  } from '@services/graphql/articles';  import { getPostComments } from '@services/graphql/comments';  import styles from '@styles/pages/article.module.scss'; -import { type Article, type Comment } from '@ts/types/app'; +import { +  type Article, +  type Comment, +  type NextPageWithLayout, +} from '@ts/types/app';  import { loadTranslation, type Messages } from '@utils/helpers/i18n';  import useAddPrismClassAttr from '@utils/hooks/use-add-prism-class-attr';  import useBreadcrumb from '@utils/hooks/use-breadcrumb'; @@ -20,7 +25,7 @@ import usePrismPlugins, {  } from '@utils/hooks/use-prism-plugins';  import useReadingTime from '@utils/hooks/use-reading-time';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; +import { GetStaticPaths, GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -39,7 +44,10 @@ type ArticlePageProps = {  /**   * Article page.   */ -const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => { +const ArticlePage: NextPageWithLayout<ArticlePageProps> = ({ +  comments, +  post, +}) => {    const { content, id, intro, meta, slug, title } = post;    const {      author, @@ -252,6 +260,8 @@ const ArticlePage: NextPage<ArticlePageProps> = ({ comments, post }) => {    );  }; +ArticlePage.getLayout = (page) => getLayout(page, { useGrid: true }); +  interface PostParams extends ParsedUrlQuery {    slug: string;  } diff --git a/src/pages/blog/index.tsx b/src/pages/blog/index.tsx index b6ce221..2676305 100644 --- a/src/pages/blog/index.tsx +++ b/src/pages/blog/index.tsx @@ -1,6 +1,7 @@  import Notice from '@components/atoms/layout/notice';  import PostsList, { type Post } from '@components/organisms/layout/posts-list';  import LinksListWidget from '@components/organisms/widgets/links-list-widget'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout from '@components/templates/page/page-layout';  import { type EdgesResponse } from '@services/graphql/api';  import { @@ -13,7 +14,11 @@ import {    getTotalThematics,  } from '@services/graphql/thematics';  import { getTopicsPreview, getTotalTopics } from '@services/graphql/topics'; -import { type Article, type Meta } from '@ts/types/app'; +import { +  type Article, +  type Meta, +  type NextPageWithLayout, +} from '@ts/types/app';  import {    RawThematicPreview,    RawTopicPreview, @@ -28,7 +33,7 @@ import {  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import usePagination from '@utils/hooks/use-pagination';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -46,7 +51,7 @@ type BlogPageProps = {  /**   * Blog index page.   */ -const BlogPage: NextPage<BlogPageProps> = ({ +const BlogPage: NextPageWithLayout<BlogPageProps> = ({    articles,    thematicsList,    topicsList, @@ -268,6 +273,9 @@ const BlogPage: NextPage<BlogPageProps> = ({    );  }; +BlogPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps<BlogPageProps> = async ({    locale,  }) => { diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx index 617117b..c0d6c79 100644 --- a/src/pages/contact.tsx +++ b/src/pages/contact.tsx @@ -3,22 +3,24 @@ import ContactForm, {    type ContactFormProps,  } from '@components/organisms/forms/contact-form';  import SocialMedia from '@components/organisms/widgets/social-media'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout from '@components/templates/page/page-layout';  import { meta } from '@content/pages/contact.mdx';  import styles from '@styles/pages/contact.module.scss';  import { sendMail } from '@services/graphql/contact'; +import { type NextPageWithLayout } from '@ts/types/app';  import { loadTranslation } from '@utils/helpers/i18n'; +import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script';  import { useState } from 'react';  import { useIntl } from 'react-intl';  import { ContactPage as ContactPageSchema, Graph, WebPage } from 'schema-dts'; -import useBreadcrumb from '@utils/hooks/use-breadcrumb'; -const ContactPage: NextPage = () => { +const ContactPage: NextPageWithLayout = () => {    const { dates, intro, seo, title } = meta;    const intl = useIntl();    const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({ @@ -164,6 +166,9 @@ const ContactPage: NextPage = () => {    );  }; +ContactPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps = async ({ locale }) => {    const translation = await loadTranslation(locale); diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index 7936c84..3f035d8 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -3,16 +3,18 @@ import Link from '@components/atoms/links/link';  import List from '@components/atoms/lists/list';  import ImageWidget from '@components/organisms/widgets/image-widget';  import SocialMedia from '@components/organisms/widgets/social-media'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout, {    type PageLayoutProps,  } from '@components/templates/page/page-layout';  import CVContent, { data, meta } from '@content/pages/cv.mdx';  import styles from '@styles/pages/cv.module.scss'; +import { type NextPageWithLayout } from '@ts/types/app';  import { loadTranslation } from '@utils/helpers/i18n';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings';  import { NestedMDXComponents } from 'mdx/types'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -23,7 +25,7 @@ import { AboutPage, Graph, WebPage } from 'schema-dts';  /**   * CV page.   */ -const CVPage: NextPage = () => { +const CVPage: NextPageWithLayout = () => {    const intl = useIntl();    const { file, image } = data;    const { dates, intro, seo, title } = meta; @@ -186,6 +188,9 @@ const CVPage: NextPage = () => {    );  }; +CVPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps = async ({ locale }) => {    const translation = await loadTranslation(locale); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 1143a33..a831ea3 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -13,16 +13,16 @@ import Columns, {  import CardsList, {    type CardsListItem,  } from '@components/organisms/layout/cards-list'; -import Layout from '@components/templates/layout/layout'; +import { getLayout } from '@components/templates/layout/layout';  import HomePageContent from '@content/pages/homepage.mdx';  import { getArticlesCard } from '@services/graphql/articles';  import styles from '@styles/pages/home.module.scss'; -import { ArticleCard } from '@ts/types/app'; +import { type ArticleCard, type NextPageWithLayout } from '@ts/types/app';  import { loadTranslation, type Messages } from '@utils/helpers/i18n';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings';  import { NestedMDXComponents } from 'mdx/types'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import Script from 'next/script';  import { ReactElement } from 'react'; @@ -37,7 +37,7 @@ type HomeProps = {  /**   * Home page.   */ -const HomePage: NextPage<HomeProps> = ({ recentPosts }) => { +const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => {    const intl = useIntl();    const { schema: breadcrumbSchema } = useBreadcrumb({      title: '', @@ -327,7 +327,7 @@ const HomePage: NextPage<HomeProps> = ({ recentPosts }) => {    };    return ( -    <Layout breadcrumbSchema={breadcrumbSchema} isHome={true}> +    <>        <Head>          <title>{pageTitle}</title>          <meta name="description" content={pageDescription} /> @@ -340,11 +340,19 @@ const HomePage: NextPage<HomeProps> = ({ recentPosts }) => {          type="application/ld+json"          dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaJsonLd) }}        /> +      <Script +        id="schema-breadcrumb" +        type="application/ld+json" +        dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbSchema) }} +      />        <HomePageContent components={components} /> -    </Layout> +    </>    );  }; +HomePage.getLayout = (page) => +  getLayout(page, { isHome: true, withExtraPadding: false }); +  export const getStaticProps: GetStaticProps<HomeProps> = async ({ locale }) => {    const translation = await loadTranslation(locale);    const recentPosts = await getArticlesCard({ first: 3 }); diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx index 41bc218..c8d1772 100644 --- a/src/pages/mentions-legales.tsx +++ b/src/pages/mentions-legales.tsx @@ -1,14 +1,16 @@  import Link from '@components/atoms/links/link';  import ResponsiveImage from '@components/molecules/images/responsive-image'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout, {    type PageLayoutProps,  } from '@components/templates/page/page-layout';  import LegalNoticeContent, { meta } from '@content/pages/legal-notice.mdx'; +import { type NextPageWithLayout } from '@ts/types/app';  import { loadTranslation } from '@utils/helpers/i18n';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings';  import { NestedMDXComponents } from 'mdx/types'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -17,7 +19,7 @@ import { Article, Graph, WebPage } from 'schema-dts';  /**   * Legal Notice page.   */ -const LegalNoticePage: NextPage = () => { +const LegalNoticePage: NextPageWithLayout = () => {    const { dates, intro, seo, title } = meta;    const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({      title, @@ -111,6 +113,9 @@ const LegalNoticePage: NextPage = () => {    );  }; +LegalNoticePage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps = async ({ locale }) => {    const translation = await loadTranslation(locale); diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx index 1a90e0f..cf7d0dc 100644 --- a/src/pages/projets/[slug].tsx +++ b/src/pages/projets/[slug].tsx @@ -6,10 +6,15 @@ import Code from '@components/molecules/layout/code';  import Gallery from '@components/organisms/images/gallery';  import Overview, { OverviewMeta } from '@components/organisms/layout/overview';  import Sharing from '@components/organisms/widgets/sharing'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout, {    PageLayoutProps,  } from '@components/templates/page/page-layout'; -import { ProjectPreview, Repos } from '@ts/types/app'; +import { +  type NextPageWithLayout, +  type ProjectPreview, +  type Repos, +} from '@ts/types/app';  import { loadTranslation, Messages } from '@utils/helpers/i18n';  import { getProjectData, getProjectFilenames } from '@utils/helpers/projects';  import { capitalize } from '@utils/helpers/strings'; @@ -17,7 +22,7 @@ import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useGithubApi, { RepoData } from '@utils/hooks/use-github-api';  import useSettings from '@utils/hooks/use-settings';  import { MDXComponents, NestedMDXComponents } from 'mdx/types'; -import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; +import { GetStaticPaths, GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -33,7 +38,7 @@ type ProjectPageProps = {  /**   * Project page.   */ -const ProjectPage: NextPage<ProjectPageProps> = ({ project }) => { +const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => {    const { id, intro, meta, title } = project;    const { cover, dates, license, repos, seo, technologies } = meta;    const intl = useIntl(); @@ -207,6 +212,9 @@ const ProjectPage: NextPage<ProjectPageProps> = ({ project }) => {    );  }; +ProjectPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps<ProjectPageProps> = async ({    locale,    params, diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx index 4a58269..d500b6b 100644 --- a/src/pages/projets/index.tsx +++ b/src/pages/projets/index.tsx @@ -2,16 +2,17 @@ import Link from '@components/atoms/links/link';  import CardsList, {    CardsListItem,  } from '@components/organisms/layout/cards-list'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout from '@components/templates/page/page-layout';  import PageContent, { meta } from '@content/pages/projects.mdx';  import styles from '@styles/pages/projects.module.scss'; -import { ProjectCard } from '@ts/types/app'; +import { type NextPageWithLayout, type ProjectCard } from '@ts/types/app';  import { loadTranslation, Messages } from '@utils/helpers/i18n';  import { getProjectsCard } from '@utils/helpers/projects';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings';  import { NestedMDXComponents } from 'mdx/types'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -25,7 +26,7 @@ type ProjectsPageProps = {  /**   * Projects page.   */ -const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => { +const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => {    const { dates, seo, title } = meta;    const { items: breadcrumbItems, schema: breadcrumbSchema } = useBreadcrumb({      title, @@ -122,6 +123,9 @@ const ProjectsPage: NextPage<ProjectsPageProps> = ({ projects }) => {    );  }; +ProjectsPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps<ProjectsPageProps> = async ({    locale,  }) => { diff --git a/src/pages/recherche/index.tsx b/src/pages/recherche/index.tsx index d88a293..09091c8 100644 --- a/src/pages/recherche/index.tsx +++ b/src/pages/recherche/index.tsx @@ -2,6 +2,7 @@ import Notice from '@components/atoms/layout/notice';  import Spinner from '@components/atoms/loaders/spinner';  import PostsList, { type Post } from '@components/organisms/layout/posts-list';  import LinksListWidget from '@components/organisms/widgets/links-list-widget'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout from '@components/templates/page/page-layout';  import { type EdgesResponse } from '@services/graphql/api';  import { @@ -14,7 +15,11 @@ import {    getTotalThematics,  } from '@services/graphql/thematics';  import { getTopicsPreview, getTotalTopics } from '@services/graphql/topics'; -import { type Article, type Meta } from '@ts/types/app'; +import { +  type Article, +  type Meta, +  type NextPageWithLayout, +} from '@ts/types/app';  import {    RawThematicPreview,    RawTopicPreview, @@ -29,7 +34,7 @@ import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useDataFromAPI from '@utils/hooks/use-data-from-api';  import usePagination from '@utils/hooks/use-pagination';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticProps, NextPage } from 'next'; +import { GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -45,7 +50,7 @@ type SearchPageProps = {  /**   * Search page.   */ -const SearchPage: NextPage<SearchPageProps> = ({ +const SearchPage: NextPageWithLayout<SearchPageProps> = ({    thematicsList,    topicsList,  }) => { @@ -283,6 +288,9 @@ const SearchPage: NextPage<SearchPageProps> = ({    );  }; +SearchPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  export const getStaticProps: GetStaticProps<SearchPageProps> = async ({    locale,  }) => { diff --git a/src/pages/sujet/[slug].tsx b/src/pages/sujet/[slug].tsx index 348fe05..6277293 100644 --- a/src/pages/sujet/[slug].tsx +++ b/src/pages/sujet/[slug].tsx @@ -2,6 +2,7 @@ import Heading from '@components/atoms/headings/heading';  import ResponsiveImage from '@components/molecules/images/responsive-image';  import PostsList, { type Post } from '@components/organisms/layout/posts-list';  import LinksListWidget from '@components/organisms/widgets/links-list-widget'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout, {    type PageLayoutProps,  } from '@components/templates/page/page-layout'; @@ -12,7 +13,12 @@ import {    getTotalTopics,  } from '@services/graphql/topics';  import styles from '@styles/pages/topic.module.scss'; -import { type Article, type PageLink, type Topic } from '@ts/types/app'; +import { +  type Article, +  type NextPageWithLayout, +  type PageLink, +  type Topic, +} from '@ts/types/app';  import { loadTranslation, type Messages } from '@utils/helpers/i18n';  import {    getLinksListItems, @@ -21,7 +27,7 @@ import {  } from '@utils/helpers/pages';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; +import { GetStaticPaths, GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -35,7 +41,10 @@ export type TopicPageProps = {    translation: Messages;  }; -const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => { +const TopicPage: NextPageWithLayout<TopicPageProps> = ({ +  currentTopic, +  topics, +}) => {    const { content, intro, meta, slug, title } = currentTopic;    const {      articles, @@ -208,6 +217,9 @@ const TopicPage: NextPage<TopicPageProps> = ({ currentTopic, topics }) => {    );  }; +TopicPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  interface TopicParams extends ParsedUrlQuery {    slug: string;  } diff --git a/src/pages/thematique/[slug].tsx b/src/pages/thematique/[slug].tsx index 13ef0da..23e6a8b 100644 --- a/src/pages/thematique/[slug].tsx +++ b/src/pages/thematique/[slug].tsx @@ -1,6 +1,7 @@  import Heading from '@components/atoms/headings/heading';  import PostsList, { type Post } from '@components/organisms/layout/posts-list';  import LinksListWidget from '@components/organisms/widgets/links-list-widget'; +import { getLayout } from '@components/templates/layout/layout';  import PageLayout, {    type PageLayoutProps,  } from '@components/templates/page/page-layout'; @@ -10,7 +11,12 @@ import {    getThematicsPreview,    getTotalThematics,  } from '@services/graphql/thematics'; -import { type Article, type PageLink, type Thematic } from '@ts/types/app'; +import { +  type Article, +  type NextPageWithLayout, +  type PageLink, +  type Thematic, +} from '@ts/types/app';  import { loadTranslation, type Messages } from '@utils/helpers/i18n';  import {    getLinksListItems, @@ -19,7 +25,7 @@ import {  } from '@utils/helpers/pages';  import useBreadcrumb from '@utils/hooks/use-breadcrumb';  import useSettings from '@utils/hooks/use-settings'; -import { GetStaticPaths, GetStaticProps, NextPage } from 'next'; +import { GetStaticPaths, GetStaticProps } from 'next';  import Head from 'next/head';  import { useRouter } from 'next/router';  import Script from 'next/script'; @@ -33,7 +39,7 @@ export type ThematicPageProps = {    translation: Messages;  }; -const ThematicPage: NextPage<ThematicPageProps> = ({ +const ThematicPage: NextPageWithLayout<ThematicPageProps> = ({    currentThematic,    thematics,  }) => { @@ -191,6 +197,9 @@ const ThematicPage: NextPage<ThematicPageProps> = ({    );  }; +ThematicPage.getLayout = (page) => +  getLayout(page, { useGrid: true, withExtraPadding: true }); +  interface ThematicParams extends ParsedUrlQuery {    slug: string;  } diff --git a/src/ts/types/app.ts b/src/ts/types/app.ts index a3b9889..feff5a5 100644 --- a/src/ts/types/app.ts +++ b/src/ts/types/app.ts @@ -1,3 +1,24 @@ +import { NextPage } from 'next'; +import { AppProps } from 'next/app'; +import { ReactElement, ReactNode } from 'react'; + +export type NextPageWithLayoutOptions = { +  withExtraPadding?: boolean; +  isHome?: boolean; +  useGrid?: boolean; +}; + +export type NextPageWithLayout<T = {}> = NextPage<T> & { +  getLayout?: ( +    page: ReactElement, +    options: NextPageWithLayoutOptions +  ) => ReactNode; +}; + +export type AppPropsWithLayout = AppProps & { +  Component: NextPageWithLayout; +}; +  export type ContentKind =    | 'article'    | 'comment' | 
