Skip to content

Commit

Permalink
refactor: extract MDX components (#6989)
Browse files Browse the repository at this point in the history
  • Loading branch information
slorber authored Mar 24, 2022
1 parent 4c0914c commit c42f22b
Show file tree
Hide file tree
Showing 14 changed files with 368 additions and 94 deletions.
62 changes: 62 additions & 0 deletions packages/docusaurus-theme-classic/src/getSwizzleConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
91 changes: 82 additions & 9 deletions packages/docusaurus-theme-classic/src/theme-classic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

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';
import type CodeBlock from '@theme/CodeBlock';
import type Head from '@docusaurus/Head';

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;
Expand Down
14 changes: 14 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXComponents/A.tsx
Original file line number Diff line number Diff line change
@@ -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} />;
}
37 changes: 37 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXComponents/Code.tsx
Original file line number Diff line number Diff line change
@@ -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>)} />
);
}
Original file line number Diff line number Diff line change
@@ -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>
);
}
29 changes: 29 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXComponents/Head.tsx
Original file line number Diff line number Diff line change
@@ -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>
);
}
Original file line number Diff line number Diff line change
@@ -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} />;
}
Original file line number Diff line number Diff line change
@@ -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;
}
20 changes: 20 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXComponents/Img.tsx
Original file line number Diff line number Diff line change
@@ -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)} />;
}
21 changes: 21 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXComponents/Pre.tsx
Original file line number Diff line number Diff line change
@@ -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})}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
28 changes: 28 additions & 0 deletions packages/docusaurus-theme-classic/src/theme/MDXComponents/Ul.tsx
Original file line number Diff line number Diff line change
@@ -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)} />;
}
Loading

0 comments on commit c42f22b

Please sign in to comment.