mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 23:57:22 +02:00
refactor: split and cleanup theme/DocPage (#7006)
This commit is contained in:
parent
2964e6f65d
commit
1b974e8b1b
10 changed files with 299 additions and 163 deletions
|
@ -275,6 +275,40 @@ declare module '@theme/DocPage' {
|
||||||
export default function DocPage(props: Props): JSX.Element;
|
export default function DocPage(props: Props): JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@theme/DocPage/Layout' {
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocPageLayout(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/DocPage/Layout/Aside' {
|
||||||
|
import type {Dispatch, SetStateAction} from 'react';
|
||||||
|
import type {PropSidebar} from '@docusaurus/plugin-content-docs';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
sidebar: PropSidebar;
|
||||||
|
hiddenSidebarContainer: boolean;
|
||||||
|
setHiddenSidebarContainer: Dispatch<SetStateAction<boolean>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocPageLayoutAside(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '@theme/DocPage/Layout/Main' {
|
||||||
|
import type {ReactNode} from 'react';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
hiddenSidebarContainer: boolean;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocPageLayoutMain(props: Props): JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO until TS supports exports field... hope it's in 4.6
|
// TODO until TS supports exports field... hope it's in 4.6
|
||||||
declare module '@docusaurus/plugin-content-docs/client' {
|
declare module '@docusaurus/plugin-content-docs/client' {
|
||||||
export type ActivePlugin = {
|
export type ActivePlugin = {
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* 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 ReactNode, useState, useCallback} from 'react';
|
||||||
|
import DocSidebar from '@theme/DocSidebar';
|
||||||
|
import IconArrow from '@theme/IconArrow';
|
||||||
|
import {translate} from '@docusaurus/Translate';
|
||||||
|
import {useLocation} from '@docusaurus/router';
|
||||||
|
import type {Props} from '@theme/DocPage/Layout/Aside';
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
import {ThemeClassNames, useDocsSidebar} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
function SidebarExpandButton({toggleSidebar}: {toggleSidebar: () => void}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={styles.collapsedDocSidebar}
|
||||||
|
title={translate({
|
||||||
|
id: 'theme.docs.sidebar.expandButtonTitle',
|
||||||
|
message: 'Expand sidebar',
|
||||||
|
description:
|
||||||
|
'The ARIA label and title attribute for expand button of doc sidebar',
|
||||||
|
})}
|
||||||
|
aria-label={translate({
|
||||||
|
id: 'theme.docs.sidebar.expandButtonAriaLabel',
|
||||||
|
message: 'Expand sidebar',
|
||||||
|
description:
|
||||||
|
'The ARIA label and title attribute for expand button of doc sidebar',
|
||||||
|
})}
|
||||||
|
tabIndex={0}
|
||||||
|
role="button"
|
||||||
|
onKeyDown={toggleSidebar}
|
||||||
|
onClick={toggleSidebar}>
|
||||||
|
<IconArrow className={styles.expandSidebarButtonIcon} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset sidebar state when sidebar changes
|
||||||
|
// Use React key to unmount/remount the children
|
||||||
|
// See https://github.com/facebook/docusaurus/issues/3414
|
||||||
|
function ResetOnSidebarChange({children}: {children: ReactNode}) {
|
||||||
|
const sidebar = useDocsSidebar();
|
||||||
|
return (
|
||||||
|
<React.Fragment key={sidebar?.name ?? 'noSidebar'}>
|
||||||
|
{children}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocPageLayoutAside({
|
||||||
|
sidebar,
|
||||||
|
hiddenSidebarContainer,
|
||||||
|
setHiddenSidebarContainer,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const {pathname} = useLocation();
|
||||||
|
|
||||||
|
const [hiddenSidebar, setHiddenSidebar] = useState(false);
|
||||||
|
const toggleSidebar = useCallback(() => {
|
||||||
|
if (hiddenSidebar) {
|
||||||
|
setHiddenSidebar(false);
|
||||||
|
}
|
||||||
|
setHiddenSidebarContainer((value) => !value);
|
||||||
|
}, [setHiddenSidebarContainer, hiddenSidebar]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside
|
||||||
|
className={clsx(
|
||||||
|
ThemeClassNames.docs.docSidebarContainer,
|
||||||
|
styles.docSidebarContainer,
|
||||||
|
hiddenSidebarContainer && styles.docSidebarContainerHidden,
|
||||||
|
)}
|
||||||
|
onTransitionEnd={(e) => {
|
||||||
|
if (!e.currentTarget.classList.contains(styles.docSidebarContainer!)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hiddenSidebarContainer) {
|
||||||
|
setHiddenSidebar(true);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<ResetOnSidebarChange>
|
||||||
|
<DocSidebar
|
||||||
|
sidebar={sidebar}
|
||||||
|
path={pathname}
|
||||||
|
onCollapse={toggleSidebar}
|
||||||
|
isHidden={hiddenSidebar}
|
||||||
|
/>
|
||||||
|
</ResetOnSidebarChange>
|
||||||
|
|
||||||
|
{hiddenSidebar && <SidebarExpandButton toggleSidebar={toggleSidebar} />}
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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 React from 'react';
|
||||||
|
|
||||||
|
import {useDocsSidebar} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
import type {Props} from '@theme/DocPage/Layout/Main';
|
||||||
|
|
||||||
|
export default function DocPageLayoutMain({
|
||||||
|
hiddenSidebarContainer,
|
||||||
|
children,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const sidebar = useDocsSidebar();
|
||||||
|
return (
|
||||||
|
<main
|
||||||
|
className={clsx(
|
||||||
|
styles.docMainContainer,
|
||||||
|
(hiddenSidebarContainer || !sidebar) && styles.docMainContainerEnhanced,
|
||||||
|
)}>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'container padding-top--md padding-bottom--lg',
|
||||||
|
styles.docItemWrapper,
|
||||||
|
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/**
|
||||||
|
* 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, {useState} from 'react';
|
||||||
|
import Layout from '@theme/Layout';
|
||||||
|
import BackToTopButton from '@theme/BackToTopButton';
|
||||||
|
import type {Props} from '@theme/DocPage/Layout';
|
||||||
|
import DocPageLayoutAside from '@theme/DocPage/Layout/Aside';
|
||||||
|
import DocPageLayoutMain from '@theme/DocPage/Layout/Main';
|
||||||
|
|
||||||
|
import styles from './styles.module.css';
|
||||||
|
|
||||||
|
import {useDocsSidebar} from '@docusaurus/theme-common';
|
||||||
|
|
||||||
|
export default function DocPageLayout({children}: Props): JSX.Element {
|
||||||
|
const sidebar = useDocsSidebar();
|
||||||
|
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<BackToTopButton />
|
||||||
|
<div className={styles.docPage}>
|
||||||
|
{sidebar && (
|
||||||
|
<DocPageLayoutAside
|
||||||
|
sidebar={sidebar.items}
|
||||||
|
hiddenSidebarContainer={hiddenSidebarContainer}
|
||||||
|
setHiddenSidebarContainer={setHiddenSidebarContainer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<DocPageLayoutMain hiddenSidebarContainer={hiddenSidebarContainer}>
|
||||||
|
{children}
|
||||||
|
</DocPageLayoutMain>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,145 +5,30 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {type ReactNode, useState, useCallback} from 'react';
|
import React from 'react';
|
||||||
import renderRoutes from '@docusaurus/renderRoutes';
|
import renderRoutes from '@docusaurus/renderRoutes';
|
||||||
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
|
import type {PropSidebar} from '@docusaurus/plugin-content-docs';
|
||||||
import Layout from '@theme/Layout';
|
|
||||||
import DocSidebar from '@theme/DocSidebar';
|
|
||||||
import NotFound from '@theme/NotFound';
|
import NotFound from '@theme/NotFound';
|
||||||
import type {DocumentRoute} from '@theme/DocItem';
|
|
||||||
import type {Props} from '@theme/DocPage';
|
import type {Props} from '@theme/DocPage';
|
||||||
import IconArrow from '@theme/IconArrow';
|
import DocPageLayout from '@theme/DocPage/Layout';
|
||||||
import BackToTopButton from '@theme/BackToTopButton';
|
|
||||||
import {matchPath} from '@docusaurus/router';
|
import {matchPath} from '@docusaurus/router';
|
||||||
import {translate} from '@docusaurus/Translate';
|
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import styles from './styles.module.css';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HtmlClassNameProvider,
|
HtmlClassNameProvider,
|
||||||
ThemeClassNames,
|
ThemeClassNames,
|
||||||
docVersionSearchTag,
|
docVersionSearchTag,
|
||||||
DocsSidebarProvider,
|
DocsSidebarProvider,
|
||||||
useDocsSidebar,
|
|
||||||
DocsVersionProvider,
|
DocsVersionProvider,
|
||||||
} from '@docusaurus/theme-common';
|
} from '@docusaurus/theme-common';
|
||||||
import SearchMetadata from '@theme/SearchMetadata';
|
import SearchMetadata from '@theme/SearchMetadata';
|
||||||
|
|
||||||
type DocPageContentProps = {
|
function extractDocRouteMetadata(props: Props): null | {
|
||||||
readonly currentDocRoute: DocumentRoute;
|
docElement: JSX.Element;
|
||||||
readonly versionMetadata: PropVersionMetadata;
|
sidebarName: string | undefined;
|
||||||
readonly children: ReactNode;
|
sidebarItems: PropSidebar | undefined;
|
||||||
readonly sidebarName: string | undefined;
|
} {
|
||||||
};
|
|
||||||
|
|
||||||
function DocPageContent({
|
|
||||||
currentDocRoute,
|
|
||||||
versionMetadata,
|
|
||||||
children,
|
|
||||||
sidebarName,
|
|
||||||
}: DocPageContentProps): JSX.Element {
|
|
||||||
const sidebar = useDocsSidebar();
|
|
||||||
const {pluginId, version} = versionMetadata;
|
|
||||||
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
|
|
||||||
const [hiddenSidebar, setHiddenSidebar] = useState(false);
|
|
||||||
const toggleSidebar = useCallback(() => {
|
|
||||||
if (hiddenSidebar) {
|
|
||||||
setHiddenSidebar(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setHiddenSidebarContainer((value) => !value);
|
|
||||||
}, [hiddenSidebar]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<SearchMetadata
|
|
||||||
version={version}
|
|
||||||
tag={docVersionSearchTag(pluginId, version)}
|
|
||||||
/>
|
|
||||||
<Layout>
|
|
||||||
<div className={styles.docPage}>
|
|
||||||
<BackToTopButton />
|
|
||||||
|
|
||||||
{sidebar && (
|
|
||||||
<aside
|
|
||||||
className={clsx(
|
|
||||||
ThemeClassNames.docs.docSidebarContainer,
|
|
||||||
styles.docSidebarContainer,
|
|
||||||
hiddenSidebarContainer && styles.docSidebarContainerHidden,
|
|
||||||
)}
|
|
||||||
onTransitionEnd={(e) => {
|
|
||||||
if (
|
|
||||||
!e.currentTarget.classList.contains(
|
|
||||||
styles.docSidebarContainer!,
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hiddenSidebarContainer) {
|
|
||||||
setHiddenSidebar(true);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
<DocSidebar
|
|
||||||
key={
|
|
||||||
// Reset sidebar state on sidebar changes
|
|
||||||
// See https://github.com/facebook/docusaurus/issues/3414
|
|
||||||
sidebarName
|
|
||||||
}
|
|
||||||
sidebar={sidebar}
|
|
||||||
path={currentDocRoute.path}
|
|
||||||
onCollapse={toggleSidebar}
|
|
||||||
isHidden={hiddenSidebar}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hiddenSidebar && (
|
|
||||||
<div
|
|
||||||
className={styles.collapsedDocSidebar}
|
|
||||||
title={translate({
|
|
||||||
id: 'theme.docs.sidebar.expandButtonTitle',
|
|
||||||
message: 'Expand sidebar',
|
|
||||||
description:
|
|
||||||
'The ARIA label and title attribute for expand button of doc sidebar',
|
|
||||||
})}
|
|
||||||
aria-label={translate({
|
|
||||||
id: 'theme.docs.sidebar.expandButtonAriaLabel',
|
|
||||||
message: 'Expand sidebar',
|
|
||||||
description:
|
|
||||||
'The ARIA label and title attribute for expand button of doc sidebar',
|
|
||||||
})}
|
|
||||||
tabIndex={0}
|
|
||||||
role="button"
|
|
||||||
onKeyDown={toggleSidebar}
|
|
||||||
onClick={toggleSidebar}>
|
|
||||||
<IconArrow className={styles.expandSidebarButtonIcon} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</aside>
|
|
||||||
)}
|
|
||||||
<main
|
|
||||||
className={clsx(
|
|
||||||
styles.docMainContainer,
|
|
||||||
(hiddenSidebarContainer || !sidebar) &&
|
|
||||||
styles.docMainContainerEnhanced,
|
|
||||||
)}>
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
'container padding-top--md padding-bottom--lg',
|
|
||||||
styles.docItemWrapper,
|
|
||||||
hiddenSidebarContainer && styles.docItemWrapperEnhanced,
|
|
||||||
)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function DocPage(props: Props): JSX.Element {
|
|
||||||
const {
|
const {
|
||||||
route: {routes: docRoutes},
|
route: {routes: docRoutes},
|
||||||
versionMetadata,
|
versionMetadata,
|
||||||
|
@ -153,33 +38,55 @@ export default function DocPage(props: Props): JSX.Element {
|
||||||
matchPath(location.pathname, docRoute),
|
matchPath(location.pathname, docRoute),
|
||||||
);
|
);
|
||||||
if (!currentDocRoute) {
|
if (!currentDocRoute) {
|
||||||
return <NotFound />;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For now, the sidebarName is added as route config: not ideal!
|
// For now, the sidebarName is added as route config: not ideal!
|
||||||
const sidebarName = currentDocRoute.sidebar;
|
const sidebarName = currentDocRoute.sidebar;
|
||||||
|
|
||||||
const sidebar = sidebarName
|
const sidebarItems = sidebarName
|
||||||
? versionMetadata.docsSidebars[sidebarName]
|
? versionMetadata.docsSidebars[sidebarName]
|
||||||
: null;
|
: undefined;
|
||||||
|
|
||||||
|
const docElement = renderRoutes(props.route.routes, {
|
||||||
|
versionMetadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
docElement,
|
||||||
|
sidebarName,
|
||||||
|
sidebarItems,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocPage(props: Props): JSX.Element {
|
||||||
|
const {versionMetadata} = props;
|
||||||
|
const currentDocRouteMetadata = extractDocRouteMetadata(props);
|
||||||
|
if (!currentDocRouteMetadata) {
|
||||||
|
return <NotFound />;
|
||||||
|
}
|
||||||
|
const {docElement, sidebarName, sidebarItems} = currentDocRouteMetadata;
|
||||||
return (
|
return (
|
||||||
<HtmlClassNameProvider
|
<>
|
||||||
className={clsx(
|
<SearchMetadata
|
||||||
ThemeClassNames.wrapper.docsPages,
|
version={versionMetadata.version}
|
||||||
ThemeClassNames.page.docsDocPage,
|
tag={docVersionSearchTag(
|
||||||
versionMetadata.className,
|
versionMetadata.pluginId,
|
||||||
)}>
|
versionMetadata.version,
|
||||||
<DocsVersionProvider version={versionMetadata}>
|
)}
|
||||||
<DocsSidebarProvider sidebar={sidebar ?? null}>
|
/>
|
||||||
<DocPageContent
|
<HtmlClassNameProvider
|
||||||
currentDocRoute={currentDocRoute}
|
className={clsx(
|
||||||
versionMetadata={versionMetadata}
|
ThemeClassNames.wrapper.docsPages,
|
||||||
sidebarName={sidebarName}>
|
ThemeClassNames.page.docsDocPage,
|
||||||
{renderRoutes(docRoutes, {versionMetadata})}
|
props.versionMetadata.className,
|
||||||
</DocPageContent>
|
)}>
|
||||||
</DocsSidebarProvider>
|
<DocsVersionProvider version={versionMetadata}>
|
||||||
</DocsVersionProvider>
|
<DocsSidebarProvider name={sidebarName} items={sidebarItems}>
|
||||||
</HtmlClassNameProvider>
|
<DocPageLayout>{docElement}</DocPageLayout>
|
||||||
|
</DocsSidebarProvider>
|
||||||
|
</DocsVersionProvider>
|
||||||
|
</HtmlClassNameProvider>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,19 +13,24 @@ import type {PropSidebar} from '@docusaurus/plugin-content-docs';
|
||||||
describe('useDocsSidebar', () => {
|
describe('useDocsSidebar', () => {
|
||||||
it('throws if context provider is missing', () => {
|
it('throws if context provider is missing', () => {
|
||||||
expect(
|
expect(
|
||||||
() => renderHook(() => useDocsSidebar()).result.current,
|
() => renderHook(() => useDocsSidebar()).result.current?.items,
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Hook useDocsSidebar is called outside the <DocsSidebarProvider>. "`,
|
`"Hook useDocsSidebar is called outside the <DocsSidebarProvider>. "`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads value from context provider', () => {
|
it('reads value from context provider', () => {
|
||||||
const sidebar: PropSidebar = [];
|
const name = 'mySidebarName';
|
||||||
|
const items: PropSidebar = [];
|
||||||
const {result} = renderHook(() => useDocsSidebar(), {
|
const {result} = renderHook(() => useDocsSidebar(), {
|
||||||
wrapper: ({children}) => (
|
wrapper: ({children}) => (
|
||||||
<DocsSidebarProvider sidebar={sidebar}>{children}</DocsSidebarProvider>
|
<DocsSidebarProvider name={name} items={items}>
|
||||||
|
{children}
|
||||||
|
</DocsSidebarProvider>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
expect(result.current).toBe(sidebar);
|
expect(result.current).toBeDefined();
|
||||||
|
expect(result.current!.name).toBe(name);
|
||||||
|
expect(result.current!.items).toBe(items);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, {type ReactNode, useContext} from 'react';
|
import React, {useMemo, useContext, type ReactNode} from 'react';
|
||||||
import type {PropSidebar} from '@docusaurus/plugin-content-docs';
|
import type {PropSidebar} from '@docusaurus/plugin-content-docs';
|
||||||
import {ReactContextError} from '../utils/reactUtils';
|
import {ReactContextError} from '../utils/reactUtils';
|
||||||
|
|
||||||
|
@ -13,30 +13,44 @@ import {ReactContextError} from '../utils/reactUtils';
|
||||||
// Inspired by https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx
|
// Inspired by https://github.com/jamiebuilds/unstated-next/blob/master/src/unstated-next.tsx
|
||||||
const EmptyContext: unique symbol = Symbol('EmptyContext');
|
const EmptyContext: unique symbol = Symbol('EmptyContext');
|
||||||
|
|
||||||
const Context = React.createContext<PropSidebar | null | typeof EmptyContext>(
|
type SidebarContextValue = {name: string; items: PropSidebar};
|
||||||
EmptyContext,
|
|
||||||
);
|
const Context = React.createContext<
|
||||||
|
SidebarContextValue | null | typeof EmptyContext
|
||||||
|
>(EmptyContext);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide the current sidebar to your children.
|
* Provide the current sidebar to your children.
|
||||||
*/
|
*/
|
||||||
export function DocsSidebarProvider({
|
export function DocsSidebarProvider({
|
||||||
children,
|
children,
|
||||||
sidebar,
|
name,
|
||||||
|
items,
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
sidebar: PropSidebar | null;
|
name: string | undefined;
|
||||||
|
items: PropSidebar | undefined;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
return <Context.Provider value={sidebar}>{children}</Context.Provider>;
|
const stableValue: SidebarContextValue | null = useMemo(
|
||||||
|
() =>
|
||||||
|
name && items
|
||||||
|
? {
|
||||||
|
name,
|
||||||
|
items,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
[name, items],
|
||||||
|
);
|
||||||
|
return <Context.Provider value={stableValue}>{children}</Context.Provider>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the sidebar that's currently displayed, or `null` if there isn't one
|
* Gets the sidebar data that's currently displayed, or `null` if there isn't one
|
||||||
*/
|
*/
|
||||||
export function useDocsSidebar(): PropSidebar | null {
|
export function useDocsSidebar(): SidebarContextValue | null {
|
||||||
const sidebar = useContext(Context);
|
const value = useContext(Context);
|
||||||
if (sidebar === EmptyContext) {
|
if (value === EmptyContext) {
|
||||||
throw new ReactContextError('DocsSidebarProvider');
|
throw new ReactContextError('DocsSidebarProvider');
|
||||||
}
|
}
|
||||||
return sidebar;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -307,7 +307,7 @@ describe('useSidebarBreadcrumbs', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}>
|
}}>
|
||||||
<DocsSidebarProvider sidebar={sidebar}>
|
<DocsSidebarProvider name="sidebarName" items={sidebar}>
|
||||||
{children}
|
{children}
|
||||||
</DocsSidebarProvider>
|
</DocsSidebarProvider>
|
||||||
</Context.Provider>
|
</Context.Provider>
|
||||||
|
@ -430,7 +430,7 @@ describe('useCurrentSidebarCategory', () => {
|
||||||
(sidebar?: PropSidebar) => (location: string) =>
|
(sidebar?: PropSidebar) => (location: string) =>
|
||||||
renderHook(() => useCurrentSidebarCategory(), {
|
renderHook(() => useCurrentSidebarCategory(), {
|
||||||
wrapper: ({children}) => (
|
wrapper: ({children}) => (
|
||||||
<DocsSidebarProvider sidebar={sidebar}>
|
<DocsSidebarProvider name="sidebarName" items={sidebar}>
|
||||||
<StaticRouter location={location}>{children}</StaticRouter>
|
<StaticRouter location={location}>{children}</StaticRouter>
|
||||||
</DocsSidebarProvider>
|
</DocsSidebarProvider>
|
||||||
),
|
),
|
||||||
|
|
|
@ -105,7 +105,7 @@ export function useCurrentSidebarCategory(): PropSidebarItemCategory {
|
||||||
if (!sidebar) {
|
if (!sidebar) {
|
||||||
throw new Error('Unexpected: cant find current sidebar in context');
|
throw new Error('Unexpected: cant find current sidebar in context');
|
||||||
}
|
}
|
||||||
const category = findSidebarCategory(sidebar, (item) =>
|
const category = findSidebarCategory(sidebar.items, (item) =>
|
||||||
isSamePath(item.href, pathname),
|
isSamePath(item.href, pathname),
|
||||||
);
|
);
|
||||||
if (!category) {
|
if (!category) {
|
||||||
|
@ -174,7 +174,7 @@ export function useSidebarBreadcrumbs(): PropSidebarBreadcrumbsItem[] | null {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
extract(sidebar);
|
extract(sidebar.items);
|
||||||
|
|
||||||
return breadcrumbs.reverse();
|
return breadcrumbs.reverse();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue