From c42f22b9bd4d39d48f3349031acb27d37db89bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Thu, 24 Mar 2022 19:23:44 +0100 Subject: [PATCH] refactor: extract MDX components (#6989) --- .../src/getSwizzleConfig.ts | 62 +++++++++++ .../src/theme-classic.d.ts | 91 ++++++++++++++-- .../src/theme/MDXComponents/A.tsx | 14 +++ .../src/theme/MDXComponents/Code.tsx | 37 +++++++ .../src/theme/MDXComponents/Details.tsx | 26 +++++ .../src/theme/MDXComponents/Head.tsx | 29 +++++ .../src/theme/MDXComponents/Heading.tsx | 14 +++ .../src/theme/MDXComponents/Img.module.css | 10 ++ .../src/theme/MDXComponents/Img.tsx | 20 ++++ .../src/theme/MDXComponents/Pre.tsx | 21 ++++ .../{styles.css => Ul.module.css} | 6 +- .../src/theme/MDXComponents/Ul.tsx | 28 +++++ .../src/theme/MDXComponents/index.tsx | 103 ++++-------------- project-words.txt | 1 + 14 files changed, 368 insertions(+), 94 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx rename packages/docusaurus-theme-classic/src/theme/MDXComponents/{styles.css => Ul.module.css} (82%) create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx diff --git a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts index 3bf2cac6f6..13b9fde846 100644 --- a/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts +++ b/packages/docusaurus-theme-classic/src/getSwizzleConfig.ts @@ -136,6 +136,68 @@ export default function getSwizzleConfig(): SwizzleConfig { description: 'The MDX components to use for rendering MDX files. Meant to be ejected.', }, + 'MDXComponents/A': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render tags and Markdown links in MDX', + }, + 'MDXComponents/Code': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render tags and Markdown code blocks in MDX', + }, + 'MDXComponents/Details': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The component used to render
tags in MDX', + }, + 'MDXComponents/Head': { + actions: { + eject: 'forbidden', + wrap: 'forbidden', + }, + description: + 'Technical component used to assign metadata (generally for SEO purpose) to the current MDX document', + }, + 'MDXComponents/Heading': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render heading tags (

,

...) and Markdown headings in MDX', + }, + 'MDXComponents/Img': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: + 'The component used to render tags and Markdown images in MDX', + }, + 'MDXComponents/Pre': { + actions: { + eject: 'safe', + wrap: 'safe', + }, + description: 'The component used to render
 tags in MDX',
+      },
+      'MDXComponents/Ul': {
+        actions: {
+          eject: 'safe',
+          wrap: 'safe',
+        },
+        description:
+          'The component used to render 
    tags and Markdown unordered lists in MDX', + }, MDXContent: { actions: { eject: 'safe', diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index a508a0c4d0..b010f00385 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -417,24 +417,97 @@ declare module '@theme/SkipToContent' { export default function SkipToContent(): JSX.Element; } -declare module '@theme/MDXComponents' { +declare module '@theme/MDXComponents/A' { import type {ComponentProps} from 'react'; - import type CodeBlock from '@theme/CodeBlock'; - import type Head from '@docusaurus/Head'; + + export interface Props extends ComponentProps<'a'> {} + + export default function MDXA(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Code' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'code'> {} + + export default function MDXCode(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Details' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'details'> {} + + export default function MDXDetails(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Ul' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'ul'> {} + + export default function MDXUl(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Img' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'img'> {} + + export default function MDXImg(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Head' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'head'> {} + + export default function MDXHead(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Heading' { + import type {ComponentProps} from 'react'; + import type Heading from '@theme/Heading'; + + export interface Props extends ComponentProps {} + + export default function MDXHeading(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents/Pre' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'pre'> {} + + export default function MDXPre(props: Props): JSX.Element; +} + +declare module '@theme/MDXComponents' { + import type {ComponentType, ComponentProps} from 'react'; + + import type MDXHead from '@theme/MDXComponents/Head'; + import type MDXCode from '@theme/MDXComponents/Code'; + import type MDXA from '@theme/MDXComponents/A'; + import type MDXPre from '@theme/MDXComponents/Pre'; + import type MDXDetails from '@theme/MDXComponents/Details'; + import type MDXUl from '@theme/MDXComponents/Ul'; + import type MDXImg from '@theme/MDXComponents/Img'; export type MDXComponentsObject = { - readonly head: typeof Head; - readonly code: typeof CodeBlock; - readonly a: (props: ComponentProps<'a'>) => JSX.Element; - readonly pre: typeof CodeBlock; - readonly details: (props: ComponentProps<'details'>) => JSX.Element; + readonly head: typeof MDXHead; + readonly code: typeof MDXCode; + readonly a: typeof MDXA; + readonly pre: typeof MDXPre; + readonly details: typeof MDXDetails; + readonly ul: typeof MDXUl; + readonly img: typeof MDXImg; readonly h1: (props: ComponentProps<'h1'>) => JSX.Element; readonly h2: (props: ComponentProps<'h2'>) => JSX.Element; readonly h3: (props: ComponentProps<'h3'>) => JSX.Element; readonly h4: (props: ComponentProps<'h4'>) => JSX.Element; readonly h5: (props: ComponentProps<'h5'>) => JSX.Element; readonly h6: (props: ComponentProps<'h6'>) => JSX.Element; - }; + } & Record>; const MDXComponents: MDXComponentsObject; export default MDXComponents; diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx new file mode 100644 index 0000000000..9ee8333e6c --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Link from '@docusaurus/Link'; +import type {Props} from '@theme/MDXComponents/A'; + +export default function MDXA(props: Props): JSX.Element { + return ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx new file mode 100644 index 0000000000..4d5680ed0a --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx @@ -0,0 +1,37 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type {ComponentProps} from 'react'; +import React, {isValidElement} from 'react'; +import CodeBlock from '@theme/CodeBlock'; +import type {Props} from '@theme/MDXComponents/Code'; + +export default function MDXCode(props: Props): JSX.Element { + const inlineElements = [ + 'a', + 'b', + 'big', + 'i', + 'span', + 'em', + 'strong', + 'sup', + 'sub', + 'small', + ]; + const shouldBeInline = React.Children.toArray(props.children).every( + (el) => + (typeof el === 'string' && !el.includes('\n')) || + (isValidElement(el) && inlineElements.includes(el.props.mdxType)), + ); + + return shouldBeInline ? ( + + ) : ( + )} /> + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx new file mode 100644 index 0000000000..d09f2d923f --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Details.tsx @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ComponentProps, type ReactElement} from 'react'; +import Details from '@theme/Details'; +import type {Props} from '@theme/MDXComponents/Details'; + +export default function MDXDetails(props: Props): JSX.Element { + const items = React.Children.toArray(props.children) as ReactElement[]; + // Split summary item from the rest to pass it as a separate prop to the + // Details theme component + const summary: ReactElement> = items.find( + (item) => item?.props?.mdxType === 'summary', + )!; + const children = <>{items.filter((item) => item !== summary)}; + + return ( +
    + {children} +
    + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx new file mode 100644 index 0000000000..c21608e69e --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {type ReactElement, type ComponentProps} from 'react'; +import Head from '@docusaurus/Head'; +import type {Props} from '@theme/MDXComponents/Head'; + +// MDX elements are wrapped through the MDX pragma. In some cases (notably usage +// with Head/Helmet) we need to unwrap those elements. +function unwrapMDXElement(element: ReactElement) { + if (element?.props?.mdxType && element?.props?.originalType) { + const {mdxType, originalType, ...newProps} = element.props; + return React.createElement(element.props.originalType, newProps); + } + return element; +} + +export default function MDXHead(props: Props): JSX.Element { + const unwrappedChildren = React.Children.map(props.children, (child) => + unwrapMDXElement(child as ReactElement), + ); + return ( + )}>{unwrappedChildren} + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx new file mode 100644 index 0000000000..2b6c433bc5 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Heading.tsx @@ -0,0 +1,14 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import Heading from '@theme/Heading'; +import type {Props} from '@theme/MDXComponents/Heading'; + +export default function MDXHeading(props: Props): JSX.Element { + return ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css new file mode 100644 index 0000000000..76644d485e --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.module.css @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +.img { + height: auto; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx new file mode 100644 index 0000000000..a3b3643294 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import type {Props} from '@theme/MDXComponents/Img'; +import styles from './Img.module.css'; +import clsx from 'clsx'; + +function transformImgClassName(className?: string): string { + return clsx(className, styles.img); +} + +export default function MDXImg(props: Props): JSX.Element { + // eslint-disable-next-line jsx-a11y/alt-text + return ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx new file mode 100644 index 0000000000..36227964eb --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx @@ -0,0 +1,21 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, {isValidElement} from 'react'; +import CodeBlock from '@theme/CodeBlock'; + +export default function MDXPre(props: any) { + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/styles.css b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.module.css similarity index 82% rename from packages/docusaurus-theme-classic/src/theme/MDXComponents/styles.css rename to packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.module.css index 3c263a2334..af91b8ee53 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/styles.css +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.module.css @@ -5,11 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -ul.contains-task-list { +.contains-task-list { padding-left: 0; list-style: none; } - -img { - height: auto; -} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx new file mode 100644 index 0000000000..c8bf10e62b --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx @@ -0,0 +1,28 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import clsx from 'clsx'; +import type {Props} from '@theme/MDXComponents/Ul'; + +import styles from './Ul.module.css'; + +const containsClassListLocalClass = styles['contains-task-list']; + +function transformUlClassName(className?: string): string { + return clsx( + className, + // This class is set globally by GitHub/MDX + // We keep the global class, but apply scoped CSS + // See https://github.com/syntax-tree/mdast-util-to-hast/issues/28 + className?.includes('contains-task-list') && containsClassListLocalClass, + ); +} + +export default function MDXUl(props: Props): JSX.Element { + return
      ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx index 8f9605c2a5..82e7c5ac6b 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx @@ -5,89 +5,32 @@ * LICENSE file in the root directory of this source tree. */ -import React, { - isValidElement, - type ComponentProps, - type ReactElement, -} from 'react'; -import Head from '@docusaurus/Head'; -import Link from '@docusaurus/Link'; -import CodeBlock from '@theme/CodeBlock'; -import Heading from '@theme/Heading'; -import Details from '@theme/Details'; +import React from 'react'; +import MDXHead from '@theme/MDXComponents/Head'; +import MDXCode from '@theme/MDXComponents/Code'; +import MDXA from '@theme/MDXComponents/A'; +import MDXPre from '@theme/MDXComponents/Pre'; +import MDXDetails from '@theme/MDXComponents/Details'; +import MDXHeading from '@theme/MDXComponents/Heading'; +import MDXUl from '@theme/MDXComponents/Ul'; +import MDXImg from '@theme/MDXComponents/Img'; + import type {MDXComponentsObject} from '@theme/MDXComponents'; -import './styles.css'; - -// MDX elements are wrapped through the MDX pragma. In some cases (notably usage -// with Head/Helmet) we need to unwrap those elements. -function unwrapMDXElement(element: ReactElement) { - if (element?.props?.mdxType && element?.props?.originalType) { - const {mdxType, originalType, ...newProps} = element.props; - return React.createElement(element.props.originalType, newProps); - } - return element; -} - const MDXComponents: MDXComponentsObject = { - head: (props) => { - const unwrappedChildren = React.Children.map(props.children, (child) => - unwrapMDXElement(child as ReactElement), - ); - return {unwrappedChildren}; - }, - code: (props) => { - const inlineElements = [ - 'a', - 'b', - 'big', - 'i', - 'span', - 'em', - 'strong', - 'sup', - 'sub', - 'small', - ]; - const shouldBeInline = React.Children.toArray(props.children).every( - (el) => - (typeof el === 'string' && !el.includes('\n')) || - (isValidElement(el) && inlineElements.includes(el.props.mdxType)), - ); - - return shouldBeInline ? : ; - }, - a: (props) => , - pre: (props) => ( - - ), - details: (props): JSX.Element => { - const items = React.Children.toArray(props.children) as ReactElement[]; - // Split summary item from the rest to pass it as a separate prop to the - // Details theme component - const summary: ReactElement> = items.find( - (item) => item?.props?.mdxType === 'summary', - )!; - const children = <>{items.filter((item) => item !== summary)}; - - return ( -
      - {children} -
      - ); - }, - h1: (props) => , - h2: (props) => , - h3: (props) => , - h4: (props) => , - h5: (props) => , - h6: (props) => , + head: MDXHead, + code: MDXCode, + a: MDXA, + pre: MDXPre, + details: MDXDetails, + ul: MDXUl, + img: MDXImg, + h1: (props) => , + h2: (props) => , + h3: (props) => , + h4: (props) => , + h5: (props) => , + h6: (props) => , }; export default MDXComponents; diff --git a/project-words.txt b/project-words.txt index 90473e1366..ce93c4352c 100644 --- a/project-words.txt +++ b/project-words.txt @@ -152,6 +152,7 @@ mathjax mdast mdxast mdxhast +MDXA metadatum metastring middlewares