From 40b2526da1d594f8f7caf67dd36ffe7396550a7a Mon Sep 17 00:00:00 2001 From: sebastienlorber Date: Fri, 17 Jun 2022 19:27:36 +0200 Subject: [PATCH 1/5] Refactor DocItem: - put doc route data in context - access data through useDoc() hook - split components into much smaller subcomponents --- .../src/plugin-content-docs.d.ts | 20 +-- .../src/theme-classic.d.ts | 28 +++- .../src/theme/DocItem/Content/index.tsx | 49 +++++++ .../src/theme/DocItem/Layout/index.tsx | 66 ++++++++++ .../theme/DocItem/Layout/styles.module.css | 17 +++ .../src/theme/DocItem/Metadata/index.tsx | 21 +++ .../src/theme/DocItem/Paginator/index.tsx | 19 +++ .../src/theme/DocItem/TOC/Desktop/index.tsx | 23 ++++ .../src/theme/DocItem/TOC/Mobile/index.tsx | 25 ++++ .../{ => TOC/Mobile}/styles.module.css | 9 -- .../src/theme/DocItem/index.tsx | 123 ++---------------- .../src/theme/DocItemFooter/index.tsx | 14 +- .../src/theme/DocPaginator/index.tsx | 1 - .../src/contexts/doc.tsx | 63 +++++++++ packages/docusaurus-theme-common/src/index.ts | 2 + .../src/utils/docsUtils.tsx | 2 +- 16 files changed, 338 insertions(+), 144 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/Content/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/Layout/styles.module.css create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/Metadata/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/Paginator/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Desktop/index.tsx create mode 100644 packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/index.tsx rename packages/docusaurus-theme-classic/src/theme/DocItem/{ => TOC/Mobile}/styles.module.css (72%) create mode 100644 packages/docusaurus-theme-common/src/contexts/doc.tsx diff --git a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts index 8622b55543ac..35f1224c3b56 100644 --- a/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts +++ b/packages/docusaurus-plugin-content-docs/src/plugin-content-docs.d.ts @@ -8,7 +8,8 @@ /// declare module '@docusaurus/plugin-content-docs' { - import type {MDXOptions} from '@docusaurus/mdx-loader'; + import type {MDXOptions, LoadedMDXContent} from '@docusaurus/mdx-loader'; + import type { ContentPaths, FrontMatterTag, @@ -491,6 +492,12 @@ declare module '@docusaurus/plugin-content-docs' { [docId: string]: PropVersionDoc; }; + export type PropDocContent = LoadedMDXContent< + DocFrontMatter, + DocMetadata, + Assets + >; + export type PropVersionMetadata = Pick< VersionMetadata, 'label' | 'banner' | 'badge' | 'className' | 'isLast' @@ -549,13 +556,7 @@ declare module '@docusaurus/plugin-content-docs' { } declare module '@theme/DocItem' { - import type {LoadedMDXContent} from '@docusaurus/mdx-loader'; - import type { - PropVersionMetadata, - Assets, - DocMetadata, - DocFrontMatter, - } from '@docusaurus/plugin-content-docs'; + import type {PropDocContent} from '@docusaurus/plugin-content-docs'; export type DocumentRoute = { readonly component: () => JSX.Element; @@ -566,8 +567,7 @@ declare module '@theme/DocItem' { export interface Props { readonly route: DocumentRoute; - readonly versionMetadata: PropVersionMetadata; - readonly content: LoadedMDXContent; + readonly content: PropDocContent; } export default function DocItem(props: Props): JSX.Element; diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 69be740d60ae..defeeebc2f96 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -261,10 +261,32 @@ declare module '@theme/DocCardList' { export default function DocCardList(props: Props): JSX.Element; } -declare module '@theme/DocItemFooter' { - import type {Props} from '@theme/DocItem'; +declare module '@theme/DocItem/Layout' { + export default function DocItemLayout(): JSX.Element; +} + +declare module '@theme/DocItem/Metadata' { + export default function DocItemMetadata(): JSX.Element; +} - export default function DocItemFooter(props: Props): JSX.Element; +declare module '@theme/DocItem/Content' { + export default function DocItemContent(): JSX.Element; +} + +declare module '@theme/DocItem/TOC/Mobile' { + export default function DocItemTOCMobile(): JSX.Element; +} + +declare module '@theme/DocItem/TOC/Desktop' { + export default function DocItemTOCDesktop(): JSX.Element; +} + +declare module '@theme/DocItem/Paginator' { + export default function DocItemPaginator(): JSX.Element; +} + +declare module '@theme/DocItemFooter' { + export default function DocItemFooter(): JSX.Element; } declare module '@theme/DocPage/Layout' { diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/Content/index.tsx new file mode 100644 index 000000000000..0ceaff7e7773 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/Content/index.tsx @@ -0,0 +1,49 @@ +/** + * 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 {ThemeClassNames, useDoc} from '@docusaurus/theme-common'; +import Heading from '@theme/Heading'; +import MDXContent from '@theme/MDXContent'; + +/** + Title can be declared inside md content or declared through + front matter and added manually. To make both cases consistent, + the added title is added under the same div.markdown block + See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120 + + We render a "synthetic title" if: + - user doesn't ask to hide it with front matter + - the markdown content does not already contain a top-level h1 heading +*/ +function useSyntheticTitle(): string | null { + const {metadata, frontMatter, contentTitle} = useDoc(); + const shouldRender = + !frontMatter.hide_title && typeof contentTitle === 'undefined'; + if (!shouldRender) { + return null; + } + return metadata.title; +} + +export default function DocItemContent(): JSX.Element { + const {MDXComponent} = useDoc(); + const syntheticTitle = useSyntheticTitle(); + return ( +
+ {syntheticTitle && ( +
+ {syntheticTitle} +
+ )} + + + +
+ ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx new file mode 100644 index 000000000000..f4aefd535e57 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/index.tsx @@ -0,0 +1,66 @@ +/** + * 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 {useWindowSize, useDoc} from '@docusaurus/theme-common'; +import DocItemPaginator from '@theme/DocItem/Paginator'; +import DocVersionBanner from '@theme/DocVersionBanner'; +import DocVersionBadge from '@theme/DocVersionBadge'; +import DocItemFooter from '@theme/DocItemFooter'; +import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile'; +import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop'; +import DocItemContent from '@theme/DocItem/Content'; +import DocBreadcrumbs from '@theme/DocBreadcrumbs'; + +import styles from './styles.module.css'; + +/** + * Decide if the toc should be rendered, on mobile or desktop viewports + */ +function useDocTOC() { + const {frontMatter, toc} = useDoc(); + const windowSize = useWindowSize(); + + const hidden = frontMatter.hide_table_of_contents; + const canRender = !hidden && toc.length > 0; + + const mobile = canRender ? : undefined; + + const desktop = + canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? ( + + ) : undefined; + + return { + hidden, + mobile, + desktop, + }; +} + +export default function DocItemLayout(): JSX.Element { + const docTOC = useDocTOC(); + return ( +
+
+ +
+
+ + + {docTOC.mobile} + + +
+ +
+
+ {docTOC.desktop &&
{docTOC.desktop}
} +
+ ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/styles.module.css new file mode 100644 index 000000000000..784498f2e1fd --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/Layout/styles.module.css @@ -0,0 +1,17 @@ +/** + * 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. + */ + +.docItemContainer header + *, +.docItemContainer article > *:first-child { + margin-top: 0; +} + +@media (min-width: 997px) { + .docItemCol { + max-width: 75% !important; + } +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/Metadata/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/Metadata/index.tsx new file mode 100644 index 000000000000..c2f41a75628f --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/Metadata/index.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 from 'react'; +import {PageMetadata, useDoc} from '@docusaurus/theme-common'; + +export default function DocItemMetadata(): JSX.Element { + const {metadata, frontMatter, assets} = useDoc(); + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/Paginator/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/Paginator/index.tsx new file mode 100644 index 000000000000..6a42fc8abba9 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/Paginator/index.tsx @@ -0,0 +1,19 @@ +/** + * 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 {useDoc} from '@docusaurus/theme-common'; +import DocPaginator from '@theme/DocPaginator'; + +/** + * This extra component is needed, because should remain generic. + * DocPaginator is used in non-docs contexts too: generated-index pages... + */ +export default function DocItemPaginator(): JSX.Element { + const {metadata} = useDoc(); + return ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Desktop/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Desktop/index.tsx new file mode 100644 index 000000000000..1ec0a1f59162 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Desktop/index.tsx @@ -0,0 +1,23 @@ +/** + * 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 {ThemeClassNames, useDoc} from '@docusaurus/theme-common'; + +import TOC from '@theme/TOC'; + +export default function DocItemTOCDesktop(): JSX.Element { + const {toc, frontMatter} = useDoc(); + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/index.tsx new file mode 100644 index 000000000000..dd3a954dbbfb --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/index.tsx @@ -0,0 +1,25 @@ +/** + * 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 {ThemeClassNames, useDoc} from '@docusaurus/theme-common'; +import TOCCollapsible from '@theme/TOCCollapsible'; + +import styles from './styles.module.css'; + +export default function DocItemTOCMobile(): JSX.Element { + const {toc, frontMatter} = useDoc(); + return ( + + ); +} diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css b/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/styles.module.css similarity index 72% rename from packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css rename to packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/styles.module.css index 63c6bba9ce72..a298b5e3502e 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/styles.module.css +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/TOC/Mobile/styles.module.css @@ -5,16 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -.docItemContainer header + *, -.docItemContainer article > *:first-child { - margin-top: 0; -} - @media (min-width: 997px) { - .docItemCol { - max-width: 75% !important; - } - /* Prevent hydration FOUC, as the mobile TOC needs to be server-rendered */ .tocMobile { display: none; diff --git a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx index 5e1e9debcf44..751c2791fa92 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItem/index.tsx @@ -6,124 +6,19 @@ */ import React from 'react'; -import clsx from 'clsx'; -import { - PageMetadata, - HtmlClassNameProvider, - ThemeClassNames, - useWindowSize, -} from '@docusaurus/theme-common'; -import DocPaginator from '@theme/DocPaginator'; -import DocVersionBanner from '@theme/DocVersionBanner'; -import DocVersionBadge from '@theme/DocVersionBadge'; -import DocItemFooter from '@theme/DocItemFooter'; -import TOC from '@theme/TOC'; -import TOCCollapsible from '@theme/TOCCollapsible'; -import Heading from '@theme/Heading'; -import DocBreadcrumbs from '@theme/DocBreadcrumbs'; -import MDXContent from '@theme/MDXContent'; +import {HtmlClassNameProvider, DocProvider} from '@docusaurus/theme-common'; +import DocItemMetadata from '@theme/DocItem/Metadata'; +import DocItemLayout from '@theme/DocItem/Layout'; import type {Props} from '@theme/DocItem'; -import styles from './styles.module.css'; - -function DocItemMetadata(props: Props): JSX.Element { - const {content: DocContent} = props; - const {metadata, frontMatter, assets} = DocContent; - const {keywords} = frontMatter; - const {description, title} = metadata; - const image = assets.image ?? frontMatter.image; - - return ; -} - -function DocItemContent(props: Props): JSX.Element { - const {content: DocContent} = props; - const {metadata, frontMatter} = DocContent; - const { - hide_title: hideTitle, - hide_table_of_contents: hideTableOfContents, - toc_min_heading_level: tocMinHeadingLevel, - toc_max_heading_level: tocMaxHeadingLevel, - } = frontMatter; - const {title} = metadata; - - // We only add a title if: - // - user doesn't ask to hide it with front matter - // - the markdown content does not already contain a top-level h1 heading - const shouldAddTitle = - !hideTitle && typeof DocContent.contentTitle === 'undefined'; - - const windowSize = useWindowSize(); - - const canRenderTOC = !hideTableOfContents && DocContent.toc.length > 0; - - const renderTocDesktop = - canRenderTOC && (windowSize === 'desktop' || windowSize === 'ssr'); - - return ( -
-
- -
-
- - - - {canRenderTOC && ( - - )} - -
- {/* - Title can be declared inside md content or declared through - front matter and added manually. To make both cases consistent, - the added title is added under the same div.markdown block - See https://github.com/facebook/docusaurus/pull/4882#issuecomment-853021120 - */} - {shouldAddTitle && ( -
- {title} -
- )} - - - -
- - -
- - -
-
- {renderTocDesktop && ( -
- -
- )} -
- ); -} - export default function DocItem(props: Props): JSX.Element { const docHtmlClassName = `docs-doc-id-${props.content.metadata.unversionedId}`; return ( - - - - + + + + + + ); } diff --git a/packages/docusaurus-theme-classic/src/theme/DocItemFooter/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocItemFooter/index.tsx index c6f998cb61c9..0522e95207a2 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocItemFooter/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocItemFooter/index.tsx @@ -7,13 +7,16 @@ import React from 'react'; import clsx from 'clsx'; -import {ThemeClassNames} from '@docusaurus/theme-common'; +import { + ThemeClassNames, + useDoc, + type DocContextValue, +} from '@docusaurus/theme-common'; import LastUpdated from '@theme/LastUpdated'; import EditThisPage from '@theme/EditThisPage'; import TagsListInline, { type Props as TagsListInlineProps, } from '@theme/TagsListInline'; -import type {Props} from '@theme/DocItem'; import styles from './styles.module.css'; @@ -32,7 +35,7 @@ function TagsRow(props: TagsListInlineProps) { } type EditMetaRowProps = Pick< - Props['content']['metadata'], + DocContextValue['metadata'], 'editUrl' | 'lastUpdatedAt' | 'lastUpdatedBy' | 'formattedLastUpdatedAt' >; function EditMetaRow({ @@ -58,9 +61,8 @@ function EditMetaRow({ ); } -export default function DocItemFooter(props: Props): JSX.Element | null { - const {content: DocContent} = props; - const {metadata} = DocContent; +export default function DocItemFooter(): JSX.Element | null { + const {metadata} = useDoc(); const {editUrl, lastUpdatedAt, formattedLastUpdatedAt, lastUpdatedBy, tags} = metadata; diff --git a/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx b/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx index 24a625263fda..1d8823caf079 100644 --- a/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/DocPaginator/index.tsx @@ -12,7 +12,6 @@ import type {Props} from '@theme/DocPaginator'; export default function DocPaginator(props: Props): JSX.Element { const {previous, next} = props; - return (