Browse Source

[Beta] Refactor Layouts (#4975)

* [Beta] Refactor Layouts

* TypeScript

* oops

* Make it work for all pages
main
dan 2 years ago
committed by GitHub
parent
commit
ccf8576ab4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      beta/plugins/md-layout-loader.js
  2. 26
      beta/src/components/Layout/LayoutAPI.tsx
  3. 26
      beta/src/components/Layout/LayoutHome.tsx
  4. 24
      beta/src/components/Layout/LayoutLearn.tsx
  5. 120
      beta/src/components/Layout/LayoutPost.tsx
  6. 4
      beta/src/components/Layout/MarkdownPage.tsx
  7. 4
      beta/src/components/Layout/Page.tsx
  8. 9
      beta/src/pages/500.md
  9. 36
      beta/src/pages/_app.tsx
  10. 71
      beta/src/pages/blog/all.tsx
  11. 95
      beta/src/pages/blog/index.tsx

16
beta/plugins/md-layout-loader.js

@ -20,19 +20,11 @@ module.exports = async function (src) {
.dirname(path.relative('./src/pages', this.resourcePath))
.split(path.sep)
.shift();
const layoutMap = {
blog: 'Post',
learn: 'Learn',
apis: 'API',
};
const layout = layoutMap[pageParentDir] || 'Home';
const code =
`import withLayout from 'components/Layout/Layout${layout}';
export default withLayout(${JSON.stringify(data)})
`export const layout = {
section: '${pageParentDir}',
meta: ${JSON.stringify(data)}
};\n
` + content;
return callback(null, code);
};

26
beta/src/components/Layout/LayoutAPI.tsx

@ -1,26 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import sidebarReference from 'sidebarReference.json';
import {MarkdownPage, MarkdownProps} from './MarkdownPage';
import {Page} from './Page';
import {RouteItem} from './useRouteMeta';
interface PageFrontmatter {
title: string;
status: string;
}
export default function withAPI(p: PageFrontmatter) {
function LayoutAPI(props: MarkdownProps<PageFrontmatter>) {
return <MarkdownPage {...props} meta={p} />;
}
LayoutAPI.appShell = AppShell;
return LayoutAPI;
}
function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={sidebarReference as RouteItem} {...props} />;
}

26
beta/src/components/Layout/LayoutHome.tsx

@ -1,26 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import sidebarHome from 'sidebarHome.json';
import {MarkdownPage, MarkdownProps} from './MarkdownPage';
import {Page} from './Page';
import {RouteItem} from './useRouteMeta';
interface PageFrontmatter {
title: string;
status: string;
}
export default function withDocs(p: PageFrontmatter) {
function LayoutHome(props: MarkdownProps<PageFrontmatter>) {
return <MarkdownPage {...props} meta={p} />;
}
LayoutHome.appShell = AppShell;
return LayoutHome;
}
function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={sidebarHome as RouteItem} {...props} />;
}

24
beta/src/components/Layout/LayoutLearn.tsx

@ -1,24 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
import * as React from 'react';
import {MarkdownPage, MarkdownProps} from './MarkdownPage';
import {RouteItem} from 'components/Layout/useRouteMeta';
import {Page} from './Page';
import sidebarLearn from '../../sidebarLearn.json';
interface PageFrontmatter {
title: string;
}
export default function withLearn(meta: PageFrontmatter) {
function LayoutLearn(props: MarkdownProps<PageFrontmatter>) {
return <MarkdownPage {...props} meta={meta} />;
}
LayoutLearn.appShell = AppShell;
return LayoutLearn;
}
function AppShell(props: {children: React.ReactNode}) {
return <Page {...props} routeTree={sidebarLearn as RouteItem} />;
}

120
beta/src/components/Layout/LayoutPost.tsx

@ -1,120 +0,0 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*/
// @ts-ignore
import {MDXContext} from '@mdx-js/react';
import recentPostsRouteTree from 'blogIndexRecent.json';
import {DocsPageFooter} from 'components/DocsFooter';
import {ExternalLink} from 'components/ExternalLink';
import {MDXComponents} from 'components/MDX/MDXComponents';
import {Seo} from 'components/Seo';
import {Toc} from 'components/Layout/Toc';
import format from 'date-fns/format';
import {useRouter} from 'next/router';
import * as React from 'react';
import {getAuthor} from 'utils/getAuthor';
import toCommaSeparatedList from 'utils/toCommaSeparatedList';
import {Page} from './Page';
import {RouteItem, useRouteMeta} from './useRouteMeta';
import {useTwitter} from './useTwitter';
interface PageFrontmatter {
id?: string;
title: string;
author: string[];
date?: string;
}
interface LayoutPostProps {
/** Sidebar/Nav */
routes: RouteItem[];
/** Markdown frontmatter */
meta: PageFrontmatter;
/** The mdx */
children: React.ReactNode;
}
/** Return the date of the current post given the path */
function getDateFromPath(path: string) {
// All paths are /blog/year/month/day/title
const [year, month, day] = path
.substr(1) // first `/`
.split('/') // make an array
.slice(1) // ignore blog
.map((i) => parseInt(i, 10)); // convert to numbers
return {
date: format(new Date(year, month, day), 'MMMM dd, yyyy'),
dateTime: [year, month, day].join('-'),
};
}
function LayoutPost({meta, children}: LayoutPostProps) {
const {pathname} = useRouter();
const {date, dateTime} = getDateFromPath(pathname);
const {route, nextRoute, prevRoute} = useRouteMeta();
const anchors = React.Children.toArray(children)
.filter(
(child: any) =>
child.props?.mdxType && ['h2', 'h3'].includes(child.props.mdxType)
)
.map((child: any) => ({
url: '#' + child.props.id,
depth: parseInt(child.props.mdxType.replace('h', ''), 0),
text: child.props.children,
}));
useTwitter();
return (
<>
<div className="w-full px-12">
<div className="h-full mx-auto max-w-4xl relative pt-16 w-full overflow-x-hidden">
<Seo title={meta.title} />
<h1 className="mb-6 pt-8 text-4xl md:text-5xl font-bold leading-snug tracking-tight text-primary dark:text-primary-dark">
{meta.title}
</h1>
<p className="mb-6 text-lgtext-secondary dark:text-secondary-dark">
By{' '}
{toCommaSeparatedList(meta.author, (author) => (
<ExternalLink
href={getAuthor(author).url}
className="text-link dark:text-link-dark underline font-bold">
{getAuthor(author).name}
</ExternalLink>
))}
<span className="mx-2">·</span>
<span className="lead inline-flex text-gray-50">
<time dateTime={dateTime}>{date}</time>
</span>
</p>
<MDXContext.Provider value={MDXComponents}>
{children}
</MDXContext.Provider>
<DocsPageFooter
route={route}
nextRoute={nextRoute}
prevRoute={prevRoute}
/>
</div>
</div>
<div className="w-full lg:max-w-xs h-full hidden 2xl:block">
<Toc headings={anchors} />
</div>
</>
);
}
function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={recentPostsRouteTree as RouteItem} {...props} />;
}
export default function withLayoutPost(meta: any) {
function LayoutPostWrapper(props: LayoutPostProps) {
return <LayoutPost {...props} meta={meta} />;
}
LayoutPostWrapper.appShell = AppShell;
return LayoutPostWrapper;
}

4
beta/src/components/Layout/MarkdownPage.tsx

@ -116,7 +116,7 @@ export function MarkdownPage<
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 && (
@ -142,6 +142,6 @@ export function MarkdownPage<
<div className="w-full lg:max-w-xs hidden 2xl:block">
{!isHomePage && anchors.length > 0 && <Toc headings={anchors} />}
</div>
</article>
</>
);
}

4
beta/src/components/Layout/Page.tsx

@ -31,7 +31,9 @@ export function Page({routeTree, children}: PageProps) {
<div className="flex flex-1 w-full h-full self-stretch">
<div className="w-full min-w-0">
<main className="flex flex-1 self-stretch mt-16 sm:mt-10 flex-col items-end justify-around">
{children}
<article className="h-full mx-auto relative w-full min-w-0">
{children}
</article>
<Footer />
</main>
</div>

9
beta/src/pages/500.md

@ -0,0 +1,9 @@
---
title: Server Error
---
Something went very wrong.
Sorry about that!
You can file a bug [here.](https://github.com/reactjs/reactjs.org/issues/new)

36
beta/src/pages/_app.tsx

@ -5,17 +5,20 @@
import * as React from 'react';
import {AppProps} from 'next/app';
import {useRouter} from 'next/router';
import {MarkdownPage} from 'components/Layout/MarkdownPage';
import {Page} from 'components/Layout/Page';
import {ga} from '../utils/analytics';
import type {RouteItem} from 'components/Layout/useRouteMeta';
import sidebarHome from '../sidebarHome.json';
import sidebarLearn from '../sidebarLearn.json';
import sidebarReference from '../sidebarReference.json';
import '@docsearch/css';
import '../styles/algolia.css';
import '../styles/index.css';
import '../styles/sandpack.css';
import '@codesandbox/sandpack-react/dist/index.css';
const EmptyAppShell = ({children}: {children: React.ReactNode}) => (
<>{children}</>
);
if (typeof window !== 'undefined') {
if (process.env.NODE_ENV === 'production') {
ga('create', process.env.NEXT_PUBLIC_GA_TRACKING_ID, 'auto');
@ -39,16 +42,23 @@ export default function MyApp({Component, pageProps}: AppProps) {
};
}, [router.events]);
let AppShell = (Component as any).appShell || EmptyAppShell;
// In order to make sidebar scrolling between pages work as expected
// we need to access the underlying MDX component.
let routeTree = sidebarHome as RouteItem;
let content = <Component {...pageProps} />;
if ((Component as any).isMDXComponent) {
AppShell = (Component as any)({}).props.originalType.appShell;
const mdxContent = (Component as any)({}); // HACK: Extract MDX out of the generated wrapper
const {section, meta} = mdxContent.props.layout; // Injected by md-layout-loader.js
switch (section) {
case 'apis':
routeTree = sidebarReference as RouteItem;
break;
case 'learn':
routeTree = sidebarLearn as RouteItem;
break;
}
content = (
<MarkdownPage meta={meta}>{mdxContent.props.children}</MarkdownPage>
);
}
return (
<AppShell>
<Component {...pageProps} />
</AppShell>
);
return <Page routeTree={routeTree}>{content}</Page>;
}

71
beta/src/pages/blog/all.tsx

@ -1,71 +0,0 @@
import blogIndex from 'blogIndex.json';
import blogIndexRecentRouteTree from 'blogIndexRecent.json';
import {ExternalLink} from 'components/ExternalLink';
import {IconRss} from 'components/Icon/IconRss';
import {Page} from 'components/Layout/Page';
import format from 'date-fns/format';
import parseISO from 'date-fns/parseISO';
import Link from 'next/link';
import * as React from 'react';
import {getAuthor} from 'utils/getAuthor';
import {removeFromLast} from 'utils/removeFromLast';
import toCommaSeparatedList from 'utils/toCommaSeparatedList';
export default function Archive() {
return (
<div className="mx-auto max-w-5xl container px-4 sm:px-6 lg:px-8 pt-16">
<header className="py-16 ">
<div className="flex items-center justify-between">
<h1 className="text-5xl font-bold">Blog Archive</h1>
<a
href="/feed.xml"
className="p-2 betterhover:hover:bg-gray-20 transition duration-150 ease-in-out rounded-lg inline-flex items-center">
<IconRss className="w-5 h-5 mr-2" />
RSS
</a>
</div>
<p className="text-gray-70 text-2xl">
Historical archive of React news, announcements, and release notes.
</p>
</header>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 pb-40">
{blogIndex.routes.map((post) => (
<div key={post.path}>
<h3 className="font-bold text-xl ">
<Link href={removeFromLast(post.path, '.')}>
<a>{post.title}</a>
</Link>
</h3>
<div className="flex items-center">
<div>
<p className="text-sm leading-5 text-gray-80">
By{' '}
{toCommaSeparatedList(post.author, (author) => (
<ExternalLink
href={getAuthor(author).url}
className="font-bold betterhover:hover:underline">
<span>{getAuthor(author).name}</span>
</ExternalLink>
))}
</p>
<div className="flex text-sm leading-5 text-gray-50">
<time dateTime={post.date}>
{format(parseISO(post.date), 'MMMM dd, yyyy')}
</time>
<span className="mx-1">·</span>
<span>{post.readingTime}</span>
</div>
</div>
</div>
</div>
))}
</div>
</div>
);
}
Archive.displayName = 'Index';
Archive.appShell = function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={blogIndexRecentRouteTree} {...props} />;
};

95
beta/src/pages/blog/index.tsx

@ -1,95 +0,0 @@
import blogIndexRecentRouteTree from 'blogIndexRecent.json';
import {ExternalLink} from 'components/ExternalLink';
import {IconRss} from 'components/Icon/IconRss';
import {Page} from 'components/Layout/Page';
import styles from 'components/MDX/MDXComponents.module.css';
import {Seo} from 'components/Seo';
import format from 'date-fns/format';
import parseISO from 'date-fns/parseISO';
import Link from 'next/link';
import * as React from 'react';
import {getAuthor} from 'utils/getAuthor';
import {removeFromLast} from 'utils/removeFromLast';
import toCommaSeparatedList from 'utils/toCommaSeparatedList';
export default function RecentPosts() {
return (
<>
<div className="w-full px-12">
<div className="max-w-4xl mx-auto w-full container pt-10">
<header className="pt-14 pb-8">
<div className="flex items-center justify-between">
<Seo
title="Blog"
description="Offical React.js news, announcements, and release notes."
/>
<h1 className="text-5xl font-bold text-primary dark:text-primary-dark mb-8">
Blog
</h1>
<a
href="/feed.xml"
className="p-2 betterhover:hover:bg-gray-20 transition duration-150 ease-in-out rounded-lg inline-flex items-center">
<IconRss className="w-5 h-5 mr-2" />
RSS
</a>
</div>
<p className="text-primary dark:text-primary-dark text-xl leading-large">
Offical React.js news, announcements, and release notes.
</p>
</header>
<div className="space-y-12 pb-40">
{blogIndexRecentRouteTree.routes.map((post) => (
<div key={post.path}>
<h3 className="font-bold leading-8 text-primary dark:text-primary-dark text-2xl mb-2 hover:underline">
<Link href={removeFromLast(post.path, '.')}>
<a>{post.title}</a>
</Link>
</h3>
<div
className={styles.markdown + ' mb-0'}
dangerouslySetInnerHTML={{__html: post.excerpt.trim()}}
/>
<div className="flex items-center">
<div>
<p className="text-sm leading-5 text-gray-80">
By{' '}
{toCommaSeparatedList(post.author, (author) => (
<ExternalLink
href={getAuthor(author).url}
className="font-bold betterhover:hover:underline">
<span>{getAuthor(author).name}</span>
</ExternalLink>
))}
</p>
<div className="flex text-sm leading-5 text-gray-50">
<time dateTime={post.date}>
{format(parseISO(post.date), 'MMMM dd, yyyy')}
</time>
<span className="mx-1">·</span>
<span>{post.readingTime}</span>
</div>
</div>
</div>
</div>
))}
<div className="text-center">
<Link href="/blog/all">
<a className="p-2 text-center bg-card dark:bg-card-dark font-bold betterhover:hover:bg-secondary-button dark:bg-secondary-button-dark transition duration-150 ease-in-out rounded-lg inline-flex items-center">
View all articles
</a>
</Link>
</div>
</div>
</div>
</div>
<div className="pt-20 w-full lg:max-w-xs lg:sticky top-0 h-full hidden xl:block"></div>
</>
);
}
RecentPosts.displayName = 'Index';
RecentPosts.appShell = function AppShell(props: {children: React.ReactNode}) {
return <Page routeTree={blogIndexRecentRouteTree} {...props} />;
};
Loading…
Cancel
Save