beta/src/components/Layout/MarkdownPage.tsx (114 lines of code) (raw):
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {MDXProvider} from '@mdx-js/react';
import {DocsPageFooter} from 'components/DocsFooter';
import {MDXComponents} from 'components/MDX/MDXComponents';
import {Seo} from 'components/Seo';
import PageHeading from 'components/PageHeading';
import {useRouteMeta} from './useRouteMeta';
import {Toc} from './Toc';
export interface MarkdownProps<Frontmatter> {
meta: Frontmatter & {description?: string};
children?: React.ReactNode;
}
function MaxWidth({children}: {children: any}) {
return <div className="max-w-4xl ml-0 2xl:mx-auto">{children}</div>;
}
export function MarkdownPage<
T extends {title: string; status?: string} = {title: string; status?: string}
>({children, meta}: MarkdownProps<T>) {
const {route, nextRoute, prevRoute} = useRouteMeta();
const title = meta.title || route?.title || '';
const description = meta.description || route?.description || '';
let anchors: Array<{
url: string;
text: React.ReactNode;
depth: number;
}> = React.Children.toArray(children)
.filter((child: any) => {
if (child.props?.mdxType) {
return ['h1', 'h2', 'h3', 'Challenges', 'Recipes', 'Recap'].includes(
child.props.mdxType
);
}
return false;
})
.map((child: any) => {
if (child.props.mdxType === 'Challenges') {
return {
url: '#challenges',
depth: 0,
text: 'Challenges',
};
}
if (child.props.mdxType === 'Recipes') {
return {
url: '#recipes',
depth: 0,
text: 'Recipes',
};
}
if (child.props.mdxType === 'Recap') {
return {
url: '#recap',
depth: 0,
text: 'Recap',
};
}
return {
url: '#' + child.props.id,
depth:
(child.props?.mdxType &&
parseInt(child.props.mdxType.replace('h', ''), 0)) ??
0,
text: child.props.children,
};
});
if (anchors.length > 0) {
anchors.unshift({
depth: 1,
text: 'Overview',
url: '#',
});
}
if (!route) {
console.error('This page was not added to one of the sidebar JSON files.');
}
const isHomePage = route?.path === '/';
// Auto-wrap everything except a few types into
// <MaxWidth> wrappers. Keep reusing the same
// wrapper as long as we can until we meet
// a full-width section which interrupts it.
let fullWidthTypes = [
'Sandpack',
'APIAnatomy',
'FullWidth',
'Illustration',
'IllustrationBlock',
'Challenges',
'Recipes',
];
let wrapQueue: React.ReactNode[] = [];
let finalChildren: React.ReactNode[] = [];
function flushWrapper(key: string | number) {
if (wrapQueue.length > 0) {
finalChildren.push(<MaxWidth key={key}>{wrapQueue}</MaxWidth>);
wrapQueue = [];
}
}
function handleChild(child: any, key: string | number) {
if (child == null) {
return;
}
if (typeof child !== 'object') {
wrapQueue.push(child);
return;
}
if (fullWidthTypes.includes(child.props.mdxType)) {
flushWrapper(key);
finalChildren.push(child);
} else {
wrapQueue.push(child);
}
}
React.Children.forEach(children, handleChild);
flushWrapper('last');
return (
<article className="h-full mx-auto relative w-full min-w-0">
<div className="lg:pt-0 pt-20 pl-0 lg:pl-80 2xl:px-80 ">
<Seo title={title} />
{!isHomePage && (
<PageHeading
title={title}
description={description}
tags={route?.tags}
/>
)}
<div className="px-5 sm:px-12">
<div className="max-w-7xl mx-auto">
<MDXProvider components={MDXComponents}>
{finalChildren}
</MDXProvider>
</div>
<DocsPageFooter
route={route}
nextRoute={nextRoute}
prevRoute={prevRoute}
/>
</div>
</div>
<div className="w-full lg:max-w-xs hidden 2xl:block">
{!isHomePage && anchors.length > 0 && <Toc headings={anchors} />}
</div>
</article>
);
}