diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-11-22 17:45:03 +0100 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-22 17:46:32 +0100 |
| commit | 0ac690339083f01a0b12a74ec117eeccd055e932 (patch) | |
| tree | 539ffee3b35b086a8c035e5abc916ab603fe0001 | |
| parent | d4045fbcbfa8208ec31539744417f315f1f6fad8 (diff) | |
refactor(components,pages): extract MDX components mapping from pages
Instead of repeating the overriding on each pages, we should define
it in one place and reuse it in pages.
By default it is not possible to override native HTML tags with MDX
so I added a plugin in next config to allow it.
| -rw-r--r-- | next.config.js | 11 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | src/components/atoms/figure/figure.tsx | 2 | ||||
| -rw-r--r-- | src/components/atoms/heading/heading.tsx | 2 | ||||
| -rw-r--r-- | src/components/atoms/lists/list/list.tsx | 12 | ||||
| -rw-r--r-- | src/components/mdx.tsx | 90 | ||||
| m--------- | src/content | 0 | ||||
| -rw-r--r-- | src/pages/cv.tsx | 108 | ||||
| -rw-r--r-- | src/pages/index.tsx | 74 | ||||
| -rw-r--r-- | src/pages/mentions-legales.tsx | 18 | ||||
| -rw-r--r-- | src/pages/projets/[slug].tsx | 117 | ||||
| -rw-r--r-- | src/pages/projets/index.tsx | 9 |
12 files changed, 123 insertions, 321 deletions
diff --git a/next.config.js b/next.config.js index 8227603..6e404a5 100644 --- a/next.config.js +++ b/next.config.js @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'; import bundleAnalyzer from '@next/bundle-analyzer'; import nextMDX from '@next/mdx'; import rehypeSlug from 'rehype-slug'; +import { visit } from 'unist-util-visit'; const currentDir = dirname(fileURLToPath(import.meta.url)); @@ -161,11 +162,19 @@ const withBundleAnalyzer = bundleAnalyzer({ enabled: process.env.ANALYZE === 'true', }); +const overrideHTMLTags = () => (tree) => { + visit(tree, 'mdxJsxTextElement', (node) => { + if (node.data) { + delete node.data._mdxExplicitJsx; + } + }); +}; + const withMDX = nextMDX({ extension: /\.mdx?$/, options: { remarkPlugins: [], - rehypePlugins: [rehypeSlug], + rehypePlugins: [rehypeSlug, overrideHTMLTags], }, }); diff --git a/package.json b/package.json index 3d98086..6f39487 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,7 @@ "stylelint-config-standard": "^34.0.0", "stylelint-config-standard-scss": "^11.1.0", "typescript": "^5.2.2", + "unist-util-visit": "^5.0.0", "webpack": "^5.89.0" } } diff --git a/src/components/atoms/figure/figure.tsx b/src/components/atoms/figure/figure.tsx index 4dd5b10..7d63259 100644 --- a/src/components/atoms/figure/figure.tsx +++ b/src/components/atoms/figure/figure.tsx @@ -11,7 +11,7 @@ export type FigureProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & { /** * The contents (ie. an image, illustration, diagram, code snippet, etc.). */ - children: ReactNode; + children?: ReactNode; /** * A figure caption. */ diff --git a/src/components/atoms/heading/heading.tsx b/src/components/atoms/heading/heading.tsx index 6cdb578..d8c58d1 100644 --- a/src/components/atoms/heading/heading.tsx +++ b/src/components/atoms/heading/heading.tsx @@ -14,7 +14,7 @@ export type HeadingProps = HTMLAttributes<HTMLHeadingElement> & { /** * The heading body. */ - children: ReactNode; + children?: ReactNode; /** * Use an heading element or only its styles. * diff --git a/src/components/atoms/lists/list/list.tsx b/src/components/atoms/lists/list/list.tsx index 6e58433..61d2216 100644 --- a/src/components/atoms/lists/list/list.tsx +++ b/src/components/atoms/lists/list/list.tsx @@ -16,14 +16,14 @@ type UnorderedListProps = Omit<HTMLAttributes<HTMLUListElement>, 'children'>; type BaseListProps<O extends boolean, H extends boolean> = O extends true ? OrderedListProps : H extends true - ? OrderedListProps - : UnorderedListProps; + ? OrderedListProps + : UnorderedListProps; type AdditionalProps<O extends boolean, H extends boolean> = { /** - * An array of list items. + * The list items. */ - children: ReactNode; + children?: ReactNode; /** * Should the items marker be hidden? * @@ -109,8 +109,8 @@ const ListWithRef = <O extends boolean, H extends boolean>( O extends true ? HTMLOListElement : H extends true - ? HTMLOListElement - : HTMLUListElement + ? HTMLOListElement + : HTMLUListElement > ) => { const itemSpacing = spacing === null ? 0 : `var(--spacing-${spacing})`; diff --git a/src/components/mdx.tsx b/src/components/mdx.tsx new file mode 100644 index 0000000..f11dda5 --- /dev/null +++ b/src/components/mdx.tsx @@ -0,0 +1,90 @@ +import type { MDXComponents } from 'mdx/types'; +import NextImage from 'next/image'; +import type { AnchorHTMLAttributes, ImgHTMLAttributes, ReactNode } from 'react'; +import { Figure, Heading, Link, List, ListItem } from './atoms'; +import { Code, Grid } from './molecules'; + +const Anchor = ({ + children = '', + href = '', + hrefLang, + rel, + ...props +}: AnchorHTMLAttributes<HTMLAnchorElement>) => ( + <Link + {...props} + isExternal={rel?.includes('external')} + href={href} + lang={hrefLang} + rel={rel} + > + {children} + </Link> +); + +const Img = ({ + alt, + src, + height, + placeholder, + width, + ...props +}: ImgHTMLAttributes<HTMLImageElement>) => { + if (src) + return ( + <NextImage + {...props} + alt={alt ?? ''} + height={typeof height === 'string' ? Number(height) : height} + src={src} + width={typeof width === 'string' ? Number(width) : width} + /> + ); + + // eslint-disable-next-line @next/next/no-img-element + return <img {...props} alt={alt} height={height} src={src} width={width} />; +}; + +const Gallery = ({ children }: { children: ReactNode[] }) => ( + <Grid + // eslint-disable-next-line react/jsx-no-literals + gap="sm" + items={children.map((child, index) => { + return { id: `${index}`, item: child }; + })} + // eslint-disable-next-line react/jsx-no-literals + sizeMin="250px" + /> +); + +export const mdxComponents: MDXComponents = { + a: Anchor, + Code, + figure: ({ ref, ...props }) => <Figure {...props} />, + Figure, + Gallery, + h1: ({ ref, ...props }) => <Heading {...props} level={1} />, + h2: ({ ref, ...props }) => <Heading {...props} level={2} />, + h3: ({ ref, ...props }) => <Heading {...props} level={3} />, + h4: ({ ref, ...props }) => <Heading {...props} level={4} />, + h5: ({ ref, ...props }) => <Heading {...props} level={5} />, + h6: ({ ref, ...props }) => <Heading {...props} level={6} />, + img: Img, + li: ({ ref, ...props }) => <ListItem {...props} />, + Link, + ol: ({ ref, ...props }) => ( + <List + // eslint-disable-next-line react/jsx-no-literals + spacing="2xs" + {...props} + isOrdered + /> + ), + ul: ({ ref, ...props }) => ( + <List + // eslint-disable-next-line react/jsx-no-literals + spacing="2xs" + {...props} + /> + ), +}; diff --git a/src/content b/src/content -Subproject 5099889918cd056394e5bae8d5864b7c25ecaef +Subproject 6c0f2250ea956f9511fed8d2620466cb43d18d3 diff --git a/src/pages/cv.tsx b/src/pages/cv.tsx index fd19a83..edf267d 100644 --- a/src/pages/cv.tsx +++ b/src/pages/cv.tsx @@ -1,30 +1,24 @@ /* eslint-disable max-statements */ -import type { MDXComponents } from 'mdx/types'; import type { GetStaticProps } from 'next'; import Head from 'next/head'; import NextImage from 'next/image'; import { useRouter } from 'next/router'; import Script from 'next/script'; -import React, { - type AnchorHTMLAttributes, - type HTMLAttributes, - type ReactNode, -} from 'react'; +import React, { type ReactNode } from 'react'; import { useIntl } from 'react-intl'; import { getLayout, Heading, ImageWidget, Link, - List, SocialMediaWidget, - ListItem, Page, PageHeader, PageSidebar, TocWidget, PageBody, } from '../components'; +import { mdxComponents } from '../components/mdx'; import CVContent, { data, meta } from '../content/pages/cv.mdx'; import type { NextPageWithLayout } from '../types'; import { CONFIG } from '../utils/config'; @@ -37,102 +31,6 @@ import { import { loadTranslation } from '../utils/helpers/server'; import { useBreadcrumb, useHeadingsTree } from '../utils/hooks'; -const ExternalLink = ({ - children = '', - href = '', - ...props -}: AnchorHTMLAttributes<HTMLAnchorElement>) => ( - <Link {...props} isExternal href={href}> - {children} - </Link> -); - -const H1 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={1}> - {children} - </Heading> -); - -const H2 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={2}> - {children} - </Heading> -); - -const H3 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={3}> - {children} - </Heading> -); - -const H4 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={4}> - {children} - </Heading> -); - -const H5 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={5}> - {children} - </Heading> -); - -const H6 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={6}> - {children} - </Heading> -); - -const OrderedList = ({ - children, - ...props -}: HTMLAttributes<HTMLUListElement>) => ( - <List {...props} isOrdered spacing="2xs"> - {children} - </List> -); - -const UnorderedList = ({ - children, - ...props -}: HTMLAttributes<HTMLUListElement>) => ( - <List {...props} spacing="2xs"> - {children} - </List> -); - -const components: MDXComponents = { - a: ExternalLink, - h1: H1, - h2: H2, - h3: H3, - h4: H4, - h5: H5, - h6: H6, - li: ({ ref, ...props }) => <ListItem {...props} />, - Link, - ol: OrderedList, - ul: UnorderedList, -}; - /** * CV page. */ @@ -258,7 +156,7 @@ const CVPage: NextPageWithLayout = () => { /> </PageSidebar> <PageBody ref={ref}> - <CVContent components={components} /> + <CVContent components={mdxComponents} /> </PageBody> <PageSidebar> <ImageWidget diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 81883fc..32c2e7f 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,8 +1,7 @@ -/* eslint-disable max-statements */ import type { MDXComponents } from 'mdx/types'; import type { GetStaticProps } from 'next'; import Head from 'next/head'; -import NextImage, { type ImageProps as NextImageProps } from 'next/image'; +import NextImage from 'next/image'; import Script from 'next/script'; import type { FC, HTMLAttributes, ReactNode } from 'react'; import { useIntl } from 'react-intl'; @@ -14,11 +13,9 @@ import { CardHeader, CardMeta, CardTitle, - Figure, getLayout, Grid, type GridItem, - Heading, Icon, List, ListItem, @@ -27,6 +24,7 @@ import { Time, MetaItem, } from '../components'; +import { mdxComponents } from '../components/mdx'; import HomePageContent from '../content/pages/homepage.mdx'; import { getArticlesCard } from '../services/graphql'; import styles from '../styles/pages/home.module.scss'; @@ -46,66 +44,6 @@ const Column = ({ children, ...props }: HTMLAttributes<HTMLDivElement>) => ( <div {...props}>{children}</div> ); -const H1 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={1}> - {children} - </Heading> -); - -const H2 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={2}> - {children} - </Heading> -); - -const H3 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={3}> - {children} - </Heading> -); - -const H4 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={4}> - {children} - </Heading> -); - -const H5 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={5}> - {children} - </Heading> -); - -const H6 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={6}> - {children} - </Heading> -); - -const ResponsiveImage = (props: NextImageProps) => ( - <Figure> - <NextImage {...props} /> - </Figure> -); - /** * Retrieve a list of coding links. * @@ -368,17 +306,11 @@ const HomePage: NextPageWithLayout<HomeProps> = ({ recentPosts }) => { }; const components: MDXComponents = { + ...mdxComponents, CodingLinks, ColdarkRepos, Column, Grid: StyledGrid, - h1: H1, - h2: H2, - h3: H3, - h4: H4, - h5: H5, - h6: H6, - Image: ResponsiveImage, LibreLinks, MoreLinks, RecentPosts: getRecentPosts, diff --git a/src/pages/mentions-legales.tsx b/src/pages/mentions-legales.tsx index e3aabc5..d5958a6 100644 --- a/src/pages/mentions-legales.tsx +++ b/src/pages/mentions-legales.tsx @@ -1,15 +1,11 @@ /* eslint-disable max-statements */ -import type { MDXComponents } from 'mdx/types'; import type { GetStaticProps } from 'next'; import Head from 'next/head'; -import NextImage, { type ImageProps as NextImageProps } from 'next/image'; import { useRouter } from 'next/router'; import Script from 'next/script'; import { useIntl } from 'react-intl'; import { getLayout, - Link, - Figure, Page, PageHeader, PageSidebar, @@ -17,6 +13,7 @@ import { Heading, PageBody, } from '../components'; +import { mdxComponents } from '../components/mdx'; import LegalNoticeContent, { meta } from '../content/pages/legal-notice.mdx'; import type { NextPageWithLayout } from '../types'; import { CONFIG } from '../utils/config'; @@ -29,17 +26,6 @@ import { import { loadTranslation } from '../utils/helpers/server'; import { useBreadcrumb, useHeadingsTree } from '../utils/hooks'; -const ResponsiveImage = (props: NextImageProps) => ( - <Figure> - <NextImage {...props} /> - </Figure> -); - -const components: MDXComponents = { - Image: ResponsiveImage, - Link, -}; - /** * Legal Notice page. */ @@ -119,7 +105,7 @@ const LegalNoticePage: NextPageWithLayout = () => { /> </PageSidebar> <PageBody ref={ref}> - <LegalNoticeContent components={components} /> + <LegalNoticeContent components={mdxComponents} /> </PageBody> </Page> ); diff --git a/src/pages/projets/[slug].tsx b/src/pages/projets/[slug].tsx index 82d9149..2911951 100644 --- a/src/pages/projets/[slug].tsx +++ b/src/pages/projets/[slug].tsx @@ -3,22 +3,16 @@ import type { MDXComponents } from 'mdx/types'; import type { GetStaticPaths, GetStaticProps } from 'next'; import dynamic from 'next/dynamic'; import Head from 'next/head'; -import NextImage, { type ImageProps as NextImageProps } from 'next/image'; +import NextImage from 'next/image'; import { useRouter } from 'next/router'; import Script from 'next/script'; -import type { ComponentType, HTMLAttributes, ReactNode } from 'react'; +import type { ComponentType } from 'react'; import { useIntl } from 'react-intl'; import { - Code, getLayout, - Link, SharingWidget, Spinner, Heading, - List, - ListItem, - Figure, - Grid, ProjectOverview, type ProjectMeta, type Repository, @@ -28,6 +22,7 @@ import { TocWidget, PageBody, } from '../../components'; +import { mdxComponents } from '../../components/mdx'; import styles from '../../styles/pages/project.module.scss'; import type { NextPageWithLayout, ProjectPreview, Repos } from '../../types'; import { CONFIG } from '../../utils/config'; @@ -49,110 +44,6 @@ import { useHeadingsTree, } from '../../utils/hooks'; -const BorderedImage = (props: NextImageProps) => ( - <Figure hasBorders> - <NextImage {...props} /> - </Figure> -); - -const H1 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={1}> - {children} - </Heading> -); - -const H2 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={2}> - {children} - </Heading> -); - -const H3 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={3}> - {children} - </Heading> -); - -const H4 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={4}> - {children} - </Heading> -); - -const H5 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={5}> - {children} - </Heading> -); - -const H6 = ({ - children = '', - ...props -}: HTMLAttributes<HTMLHeadingElement>) => ( - <Heading {...props} level={6}> - {children} - </Heading> -); - -const OrderedList = ({ - children, - ...props -}: HTMLAttributes<HTMLUListElement>) => ( - <List {...props} isOrdered spacing="2xs"> - {children} - </List> -); - -const UnorderedList = ({ - children, - ...props -}: HTMLAttributes<HTMLUListElement>) => ( - <List {...props} spacing="2xs"> - {children} - </List> -); - -const Gallery = ({ children }: { children: ReactNode[] }) => ( - <Grid - gap="sm" - items={children.map((child, index) => { - return { id: `${index}`, item: child }; - })} - sizeMin="250px" - /> -); - -const components: MDXComponents = { - Code, - Gallery, - h1: H1, - h2: H2, - h3: H3, - h4: H4, - h5: H5, - h6: H6, - Image: BorderedImage, - li: ({ ref, ...props }) => <ListItem {...props} />, - Link, - ol: OrderedList, - ul: UnorderedList, -}; - type ProjectPageProps = { project: ProjectPreview; translation: Messages; @@ -326,7 +217,7 @@ const ProjectPage: NextPageWithLayout<ProjectPageProps> = ({ project }) => { meta={overviewMeta} name={project.title} /> - <ProjectContent components={components} /> + <ProjectContent components={mdxComponents} /> </PageBody> <PageSidebar> <SharingWidget diff --git a/src/pages/projets/index.tsx b/src/pages/projets/index.tsx index 0b9a91c..fc6eb5f 100644 --- a/src/pages/projets/index.tsx +++ b/src/pages/projets/index.tsx @@ -1,5 +1,4 @@ /* eslint-disable max-statements */ -import type { MDXComponents } from 'mdx/types'; import type { GetStaticProps } from 'next'; import Head from 'next/head'; import NextImage from 'next/image'; @@ -16,13 +15,13 @@ import { getLayout, Grid, type GridItem, - Link, MetaList, MetaItem, Page, PageHeader, PageBody, } from '../../components'; +import { mdxComponents } from '../../components/mdx'; import PageContent, { meta } from '../../content/pages/projects.mdx'; import styles from '../../styles/pages/projects.module.scss'; import type { NextPageWithLayout, ProjectCard } from '../../types'; @@ -40,10 +39,6 @@ import { } from '../../utils/helpers/server'; import { useBreadcrumb } from '../../utils/hooks'; -const components: MDXComponents = { - Link, -}; - type ProjectsPageProps = { projects: ProjectCard[]; translation?: Messages; @@ -167,7 +162,7 @@ const ProjectsPage: NextPageWithLayout<ProjectsPageProps> = ({ projects }) => { /> <PageHeader heading={title} - intro={<PageContent components={components} />} + intro={<PageContent components={mdxComponents} />} /> <PageBody className={styles.body}> <Grid |
