mirror of
https://github.com/facebook/docusaurus.git
synced 2025-06-19 11:12:36 +02:00
refactor: extract MDX components (#6989)
This commit is contained in:
parent
4c0914c035
commit
c42f22b9bd
14 changed files with 368 additions and 94 deletions
|
@ -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 <a> tags and Markdown links in MDX',
|
||||
},
|
||||
'MDXComponents/Code': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component used to render <code> tags and Markdown code blocks in MDX',
|
||||
},
|
||||
'MDXComponents/Details': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The component used to render <details> 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 (<h1>, <h2>...) and Markdown headings in MDX',
|
||||
},
|
||||
'MDXComponents/Img': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component used to render <img> tags and Markdown images in MDX',
|
||||
},
|
||||
'MDXComponents/Pre': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description: 'The component used to render <pre> tags in MDX',
|
||||
},
|
||||
'MDXComponents/Ul': {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
wrap: 'safe',
|
||||
},
|
||||
description:
|
||||
'The component used to render <ul> tags and Markdown unordered lists in MDX',
|
||||
},
|
||||
MDXContent: {
|
||||
actions: {
|
||||
eject: 'safe',
|
||||
|
|
|
@ -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<typeof Heading> {}
|
||||
|
||||
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<string, ComponentType<unknown>>;
|
||||
|
||||
const MDXComponents: MDXComponentsObject;
|
||||
export default MDXComponents;
|
||||
|
|
|
@ -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 <Link {...props} />;
|
||||
}
|
|
@ -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 ? (
|
||||
<code {...props} />
|
||||
) : (
|
||||
<CodeBlock {...(props as ComponentProps<typeof CodeBlock>)} />
|
||||
);
|
||||
}
|
|
@ -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<ComponentProps<'summary'>> = items.find(
|
||||
(item) => item?.props?.mdxType === 'summary',
|
||||
)!;
|
||||
const children = <>{items.filter((item) => item !== summary)}</>;
|
||||
|
||||
return (
|
||||
<Details {...props} summary={summary}>
|
||||
{children}
|
||||
</Details>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<Head {...(props as ComponentProps<typeof Head>)}>{unwrappedChildren}</Head>
|
||||
);
|
||||
}
|
|
@ -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 <Heading {...props} />;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 <img {...props} className={transformImgClassName(props.className)} />;
|
||||
}
|
|
@ -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 (
|
||||
<CodeBlock
|
||||
// If this pre is created by a ``` fenced codeblock, unwrap the children
|
||||
{...(isValidElement(props.children) &&
|
||||
props.children.props.originalType === 'code'
|
||||
? props.children?.props
|
||||
: {...props})}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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 <ul {...props} className={transformUlClassName(props.className)} />;
|
||||
}
|
|
@ -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 <Head {...props}>{unwrappedChildren}</Head>;
|
||||
},
|
||||
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 ? <code {...props} /> : <CodeBlock {...props} />;
|
||||
},
|
||||
a: (props) => <Link {...props} />,
|
||||
pre: (props) => (
|
||||
<CodeBlock
|
||||
// If this pre is created by a ``` fenced codeblock, unwrap the children
|
||||
{...(isValidElement(props.children) &&
|
||||
props.children.props.originalType === 'code'
|
||||
? props.children?.props
|
||||
: {...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<ComponentProps<'summary'>> = items.find(
|
||||
(item) => item?.props?.mdxType === 'summary',
|
||||
)!;
|
||||
const children = <>{items.filter((item) => item !== summary)}</>;
|
||||
|
||||
return (
|
||||
<Details {...props} summary={summary}>
|
||||
{children}
|
||||
</Details>
|
||||
);
|
||||
},
|
||||
h1: (props) => <Heading as="h1" {...props} />,
|
||||
h2: (props) => <Heading as="h2" {...props} />,
|
||||
h3: (props) => <Heading as="h3" {...props} />,
|
||||
h4: (props) => <Heading as="h4" {...props} />,
|
||||
h5: (props) => <Heading as="h5" {...props} />,
|
||||
h6: (props) => <Heading as="h6" {...props} />,
|
||||
head: MDXHead,
|
||||
code: MDXCode,
|
||||
a: MDXA,
|
||||
pre: MDXPre,
|
||||
details: MDXDetails,
|
||||
ul: MDXUl,
|
||||
img: MDXImg,
|
||||
h1: (props) => <MDXHeading as="h1" {...props} />,
|
||||
h2: (props) => <MDXHeading as="h2" {...props} />,
|
||||
h3: (props) => <MDXHeading as="h3" {...props} />,
|
||||
h4: (props) => <MDXHeading as="h4" {...props} />,
|
||||
h5: (props) => <MDXHeading as="h5" {...props} />,
|
||||
h6: (props) => <MDXHeading as="h6" {...props} />,
|
||||
};
|
||||
|
||||
export default MDXComponents;
|
||||
|
|
|
@ -152,6 +152,7 @@ mathjax
|
|||
mdast
|
||||
mdxast
|
||||
mdxhast
|
||||
MDXA
|
||||
metadatum
|
||||
metastring
|
||||
middlewares
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue