Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: extract MDX components #6989

Merged
merged 8 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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} />;
}
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>
);
}
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;
}
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)} />;
}
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;
}
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