Browse Source

[Beta] Make banner scrollable (#5002)

main
dan 2 years ago
committed by GitHub
parent
commit
9247d1a359
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      beta/src/components/Layout/Feedback.tsx
  2. 2
      beta/src/components/Layout/Footer.tsx
  3. 5
      beta/src/components/Layout/MarkdownPage.tsx
  4. 77
      beta/src/components/Layout/Nav/Nav.tsx
  5. 29
      beta/src/components/Layout/Page.tsx
  6. 20
      beta/src/components/Layout/Toc.tsx
  7. 2
      beta/src/components/PageHeading.tsx
  8. 33
      beta/src/components/SocialBanner.tsx
  9. 4
      beta/src/pages/404.js
  10. 4
      beta/src/pages/500.js
  11. 7
      beta/src/pages/[[...markdownPath]].js
  12. 17
      beta/yarn.lock

2
beta/src/components/Layout/Feedback.tsx

@ -61,7 +61,7 @@ function SendFeedback({onSubmit}: {onSubmit: () => void}) {
const [isSubmitted, setIsSubmitted] = React.useState(false); const [isSubmitted, setIsSubmitted] = React.useState(false);
return ( return (
<div className="max-w-xs w-80 lg:w-auto py-3 shadow-lg rounded-lg m-4 bg-wash dark:bg-gray-95 px-4 flex"> <div className="max-w-xs w-80 lg:w-auto py-3 shadow-lg rounded-lg m-4 bg-wash dark:bg-gray-95 px-4 flex">
<p className="w-full font-bold text-primary dark:text-primary-dark text-lg"> <p className="w-full font-bold text-primary dark:text-primary-dark text-lg mr-4">
{isSubmitted ? 'Thank you for your feedback!' : 'Is this page useful?'} {isSubmitted ? 'Thank you for your feedback!' : 'Is this page useful?'}
</p> </p>
{!isSubmitted && ( {!isSubmitted && (

2
beta/src/components/Layout/Footer.tsx

@ -13,7 +13,7 @@ export function Footer() {
const socialLinkClasses = 'hover:text-primary dark:text-primary-dark'; const socialLinkClasses = 'hover:text-primary dark:text-primary-dark';
return ( return (
<> <>
<div className="self-stretch w-full sm:pl-0 lg:pl-80 sm:pr-0 2xl:pr-80 pl-0 pr-0"> <div className="self-stretch w-full">
<div className="mx-auto w-full px-5 sm:px-12 md:px-12 pt-10 md:pt-12 lg:pt-10"> <div className="mx-auto w-full px-5 sm:px-12 md:px-12 pt-10 md:pt-12 lg:pt-10">
<hr className="max-w-7xl mx-auto border-border dark:border-border-dark" /> <hr className="max-w-7xl mx-auto border-border dark:border-border-dark" />
</div> </div>

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

@ -34,7 +34,7 @@ export function MarkdownPage<
const isHomePage = route?.path === '/'; const isHomePage = route?.path === '/';
return ( return (
<> <>
<div className="lg:pt-0 pt-20 pl-0 lg:pl-80 2xl:px-80 "> <div className="pl-0">
<Seo title={title} /> <Seo title={title} />
{!isHomePage && ( {!isHomePage && (
<PageHeading <PageHeading
@ -54,9 +54,6 @@ export function MarkdownPage<
/> />
</div> </div>
</div> </div>
<div className="w-full lg:max-w-xs hidden 2xl:block">
{!isHomePage && toc.length > 0 && <Toc headings={toc} />}
</div>
</> </>
); );
} }

77
beta/src/components/Layout/Nav/Nav.tsx

@ -99,7 +99,7 @@ const lightIcon = (
export default function Nav() { export default function Nav() {
const [isOpen, setIsOpen] = React.useState(false); const [isOpen, setIsOpen] = React.useState(false);
const [showFeedback, setShowFeedback] = React.useState(false); const [showFeedback, setShowFeedback] = React.useState(false);
const menuRef = React.useRef<HTMLDivElement>(null); const scrollParentRef = React.useRef<HTMLDivElement>(null);
const feedbackAutohideRef = React.useRef<any>(null); const feedbackAutohideRef = React.useRef<any>(null);
const section = useActiveSection(); const section = useActiveSection();
const {asPath} = useRouter(); const {asPath} = useRouter();
@ -136,7 +136,7 @@ export default function Nav() {
// While the overlay is open, disable body scroll. // While the overlay is open, disable body scroll.
React.useEffect(() => { React.useEffect(() => {
if (isOpen) { if (isOpen) {
const preferredScrollParent = menuRef.current!; const preferredScrollParent = scrollParentRef.current!;
disableBodyScroll(preferredScrollParent); disableBodyScroll(preferredScrollParent);
return () => enableBodyScroll(preferredScrollParent); return () => enableBodyScroll(preferredScrollParent);
} else { } else {
@ -190,9 +190,14 @@ export default function Nav() {
capture: true, capture: true,
}); });
}, [showFeedback]); }, [showFeedback]);
return ( return (
<> <div
<nav className="lg:sticky top-0 items-center w-full flex lg:block justify-between bg-wash dark:bg-wash-dark pt-0 lg:pt-4 pr-5 lg:px-5 z-50"> className={cn(
'sticky top-0 lg:bottom-0 lg:h-screen flex flex-col',
isOpen && 'h-screen'
)}>
<nav className="items-center w-full flex lg:block justify-between bg-wash dark:bg-wash-dark pt-0 lg:pt-4 pr-5 lg:px-5 z-50">
<div className="xl:w-full xl:max-w-xs flex items-center"> <div className="xl:w-full xl:max-w-xs flex items-center">
<button <button
type="button" type="button"
@ -316,37 +321,41 @@ export default function Nav() {
</div> </div>
)} )}
<aside <div
className={cn( ref={scrollParentRef}
`lg:grow lg:flex flex-col w-full pb-8 lg:pb-0 lg:max-w-xs bg-wash dark:bg-wash-dark z-10`, className="overflow-y-scroll no-bg-scrollbar lg:w-[336px] grow bg-wash dark:bg-wash-dark">
isOpen ? 'block z-40' : 'hidden lg:block' <aside
)}> className={cn(
{!isOpen && ( `lg:grow lg:flex flex-col w-full pb-8 lg:pb-0 lg:max-w-xs z-10`,
<div className="px-5 sm:pt-10 lg:pt-4"> isOpen ? 'block z-40' : 'hidden lg:block'
<Search /> )}>
{!isOpen && (
<div className="px-5 sm:pt-10 lg:pt-4">
<Search />
</div>
)}
<nav
role="navigation"
style={{'--bg-opacity': '.2'} as React.CSSProperties} // Need to cast here because CSS vars aren't considered valid in TS types (cuz they could be anything)
className="w-full lg:h-auto grow pr-0 lg:pr-5 pt-6 lg:py-6 md:pt-4 lg:pt-4 scrolling-touch scrolling-gpu">
{/* No fallback UI so need to be careful not to suspend directly inside. */}
<React.Suspense fallback={null}>
<SidebarRouteTree
// Don't share state between the desktop and mobile versions.
// This avoids unnecessary animations and visual flicker.
key={isOpen ? 'mobile-overlay' : 'desktop-or-hidden'}
routeTree={routeTree}
isForceExpanded={isOpen}
/>
</React.Suspense>
<div className="h-20" />
</nav>
<div className="fixed bottom-0 hidden lg:block">
<Feedback />
</div> </div>
)} </aside>
<nav </div>
role="navigation" </div>
ref={menuRef}
style={{'--bg-opacity': '.2'} as React.CSSProperties}
className="w-full h-screen lg:h-auto grow pr-0 lg:pr-5 pt-2 pb-44 lg:pb-0 lg:py-6 md:pt-2 lg:pt-4 overflow-y-scroll lg:overflow-y-auto scrolling-touch scrolling-gpu">
{/* No fallback UI so need to be careful not to suspend directly inside. */}
<React.Suspense fallback={null}>
<SidebarRouteTree
// Don't share state between the desktop and mobile versions.
// This avoids unnecessary animations and visual flicker.
key={isOpen ? 'mobile-overlay' : 'desktop-or-hidden'}
routeTree={routeTree}
isForceExpanded={isOpen}
/>
</React.Suspense>
</nav>
<div className="sticky bottom-0 hidden lg:block">
<Feedback />
</div>
</aside>
</>
); );
} }

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

@ -8,16 +8,19 @@ import {Nav} from './Nav';
import {RouteItem, SidebarContext} from './useRouteMeta'; import {RouteItem, SidebarContext} from './useRouteMeta';
import {useActiveSection} from 'hooks/useActiveSection'; import {useActiveSection} from 'hooks/useActiveSection';
import {Footer} from './Footer'; import {Footer} from './Footer';
import {Toc} from './Toc';
import SocialBanner from '../SocialBanner'; import SocialBanner from '../SocialBanner';
import sidebarHome from '../../sidebarHome.json'; import sidebarHome from '../../sidebarHome.json';
import sidebarLearn from '../../sidebarLearn.json'; import sidebarLearn from '../../sidebarLearn.json';
import sidebarReference from '../../sidebarReference.json'; import sidebarReference from '../../sidebarReference.json';
import type {TocItem} from 'components/MDX/TocContext';
interface PageProps { interface PageProps {
children: React.ReactNode; children: React.ReactNode;
toc: Array<TocItem>;
} }
export function Page({children}: PageProps) { export function Page({children, toc}: PageProps) {
const {query, asPath} = useRouter(); const {query, asPath} = useRouter();
const section = useActiveSection(); const section = useActiveSection();
let routeTree = sidebarHome as RouteItem; let routeTree = sidebarHome as RouteItem;
@ -33,26 +36,24 @@ export function Page({children}: PageProps) {
<> <>
<SocialBanner /> <SocialBanner />
<SidebarContext.Provider value={routeTree}> <SidebarContext.Provider value={routeTree}>
<div className="h-auto lg:h-screen flex flex-row"> <div className="h-auto flex flex-col lg:flex-row">
<div className="no-bg-scrollbar h-auto lg:h-[calc(100%-40px)] lg:overflow-y-scroll fixed flex flex-col py-0 top-16 sm:top-10 left-0 right-0 lg:max-w-xs w-full shadow lg:shadow-none z-50"> <div className="sticky top-0 py-0 lg:w-80 flex-none lg:static shadow lg:shadow-none z-50">
<Nav /> <Nav />
</div> </div>
{/* No fallback UI so need to be careful not to suspend directly inside. */} {/* No fallback UI so need to be careful not to suspend directly inside. */}
<React.Suspense fallback={null}> <React.Suspense fallback={null}>
<div className="flex flex-1 w-full h-full self-stretch"> <div className="flex flex-1 w-full h-full self-stretch min-w-0">
<div className="w-full min-w-0"> <main className="w-full self-stretch h-full mx-auto relative w-full min-w-0">
<main className="flex flex-1 self-stretch mt-16 sm:mt-10 flex-col items-end justify-around"> <article key={asPath}>{children}</article>
<article <Footer />
key={asPath} </main>
className="h-full mx-auto relative w-full min-w-0">
{children}
</article>
<Footer />
</main>
</div>
</div> </div>
</React.Suspense> </React.Suspense>
<div className="lg:w-80 flex-none lg:max-w-xs hidden 2xl:block">
{toc.length > 0 && <Toc headings={toc} />}
</div>
</div> </div>
</SidebarContext.Provider> </SidebarContext.Provider>
</> </>

20
beta/src/components/Layout/Toc.tsx

@ -14,21 +14,15 @@ export function Toc({headings}: {headings: Toc}) {
// Select the max TOC item we have here for now, but remove this after the fix. // Select the max TOC item we have here for now, but remove this after the fix.
const selectedIndex = Math.min(currentIndex, headings.length - 1); const selectedIndex = Math.min(currentIndex, headings.length - 1);
return ( return (
<nav <nav role="navigation" className="pt-[22px] sticky top-0 right-0">
role="navigation" {headings.length > 0 && (
className="pt-6 fixed top-10 right-0" <h2 className="mb-3 lg:mb-3 uppercase tracking-wide font-bold text-sm text-secondary dark:text-secondary-dark px-4 w-full">
style={{ On this page
// This keeps the layout fixed width instead of adjusting for content. </h2>
width: 'inherit', )}
maxWidth: 'inherit',
}}>
<h2 className="mb-3 lg:mb-3 uppercase tracking-wide font-bold text-sm text-secondary dark:text-secondary-dark px-4 w-full">
On this page
</h2>
<div className="h-full overflow-y-auto pl-4 max-h-[calc(100vh-7.5rem)]"> <div className="h-full overflow-y-auto pl-4 max-h-[calc(100vh-7.5rem)]">
<ul className="space-y-2 pb-16"> <ul className="space-y-2 pb-16">
{headings && {headings.length > 0 &&
headings.length > 0 &&
headings.map((h, i) => { headings.map((h, i) => {
if (h.url == null) { if (h.url == null) {
// TODO: only log in DEV // TODO: only log in DEV

2
beta/src/components/PageHeading.tsx

@ -22,7 +22,7 @@ function PageHeading({
tags = [], tags = [],
}: PageHeadingProps) { }: PageHeadingProps) {
return ( return (
<div className="px-5 sm:px-12 pt-5"> <div className="px-5 sm:px-12 pt-8 sm:pt-7 lg:pt-5">
<div className="max-w-4xl ml-0 2xl:mx-auto"> <div className="max-w-4xl ml-0 2xl:mx-auto">
{tags ? <Breadcrumbs /> : null} {tags ? <Breadcrumbs /> : null}
<H1 className="mt-0 text-primary dark:text-primary-dark -mx-.5 break-words"> <H1 className="mt-0 text-primary dark:text-primary-dark -mx-.5 break-words">

33
beta/src/components/SocialBanner.tsx

@ -7,19 +7,44 @@ import React from 'react';
import {ExternalLink} from './ExternalLink'; import {ExternalLink} from './ExternalLink';
// TODO: Unify with the old site settings. // TODO: Unify with the old site settings.
// Turning this off also requires changing the Page top value to pull up the sidebar.
const bannerText = 'Support Ukraine 🇺🇦'; const bannerText = 'Support Ukraine 🇺🇦';
const bannerLink = 'https://opensource.fb.com/support-ukraine'; const bannerLink = 'https://opensource.fb.com/support-ukraine';
const bannerLinkText = 'Help Provide Humanitarian Aid to Ukraine.'; const bannerLinkText = 'Help Provide Humanitarian Aid to Ukraine';
// Keep these synced:
const bannerHeightJs = 40;
const bannerHeightTw = 'h-[40px]';
if (typeof window !== 'undefined') {
// Assume it's Next.js scroll restoration.
const realScrollTo = window.scrollTo;
(window as any).scrollTo = function scrollToPatchedForSocialBanner(
x: number,
y: number
) {
if (y === 0) {
// We're trying to reset scroll.
// If we already scrolled past the banner, consider it as y = 0.
y = Math.min(window.scrollY, bannerHeightJs);
}
return realScrollTo(x, y);
};
}
export default function SocialBanner() { export default function SocialBanner() {
return ( return (
<div className="w-full bg-gray-100 dark:bg-gray-700 fixed py-2 h-16 sm:h-10 sm:py-0 flex items-center justify-center flex-col sm:flex-row z-[100]"> <div
{bannerText} className={
bannerHeightTw +
` w-full bg-gray-100 dark:bg-gray-700 text-base md:text-lg py-2 sm:py-0 flex items-center justify-center flex-col sm:flex-row z-[100]`
}>
<div className="hidden sm:block">{bannerText}</div>
<ExternalLink <ExternalLink
className="ml-0 sm:ml-1 text-link dark:text-link-dark hover:underline" className="ml-0 sm:ml-1 text-link dark:text-link-dark hover:underline"
href={bannerLink}> href={bannerLink}>
<div className="inline sm:hidden">🇺🇦 </div>
{bannerLinkText} {bannerLinkText}
<span className="hidden sm:inline">.</span>
</ExternalLink> </ExternalLink>
</div> </div>
); );

4
beta/src/pages/404.js

@ -10,8 +10,8 @@ const {Intro, MaxWidth, p: P, a: A} = MDXComponents;
export default function NotFound() { export default function NotFound() {
return ( return (
<Page> <Page toc={[]}>
<MarkdownPage meta={{title: 'Not Found'}} toc={[]}> <MarkdownPage meta={{title: 'Not Found'}}>
<MaxWidth> <MaxWidth>
<Intro> <Intro>
<P>This page doesnt exist.</P> <P>This page doesnt exist.</P>

4
beta/src/pages/500.js

@ -10,8 +10,8 @@ const {Intro, MaxWidth, p: P, a: A} = MDXComponents;
export default function NotFound() { export default function NotFound() {
return ( return (
<Page> <Page toc={[]}>
<MarkdownPage meta={{title: 'Something Went Wrong'}} toc={[]}> <MarkdownPage meta={{title: 'Something Went Wrong'}}>
<MaxWidth> <MaxWidth>
<Intro> <Intro>
<P>Something went very wrong.</P> <P>Something went very wrong.</P>

7
beta/src/pages/[[...markdownPath]].js

@ -14,7 +14,7 @@ export default function Layout({content, toc, meta}) {
); );
const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]); const parsedToc = useMemo(() => JSON.parse(toc, reviveNodeOnClient), [toc]);
return ( return (
<Page> <Page toc={parsedToc}>
<MarkdownPage meta={meta} toc={parsedToc}> <MarkdownPage meta={meta} toc={parsedToc}>
{parsedContent} {parsedContent}
</MarkdownPage> </MarkdownPage>
@ -123,7 +123,10 @@ export async function getStaticProps(context) {
// Pre-process MDX output and serialize it. // Pre-process MDX output and serialize it.
const {prepareMDX} = require('../utils/prepareMDX'); const {prepareMDX} = require('../utils/prepareMDX');
const {toc, children} = prepareMDX(reactTree.props.children); let {toc, children} = prepareMDX(reactTree.props.children);
if (path === 'index') {
toc = [];
}
return { return {
props: { props: {
content: JSON.stringify(children, stringifyNodeOnServer), content: JSON.stringify(children, stringifyNodeOnServer),

17
beta/yarn.lock

@ -995,11 +995,6 @@
unist-builder "2.0.3" unist-builder "2.0.3"
unist-util-visit "2.0.3" unist-util-visit "2.0.3"
"@mdx-js/react@^1.6.16":
version "1.6.22"
resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573"
integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==
"@mdx-js/util@1.6.22": "@mdx-js/util@1.6.22":
version "1.6.22" version "1.6.22"
resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b"
@ -1923,11 +1918,6 @@ commander@^8.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
compute-scroll-into-view@^1.0.17:
version "1.0.17"
resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz#6a88f18acd9d42e9cf4baa6bec7e0522607ab7ab"
integrity sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg==
concat-map@0.0.1: concat-map@0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -4976,13 +4966,6 @@ scheduler@0.0.0-experimental-82c64e1a4-20220520:
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
scroll-into-view-if-needed@^2.2.25:
version "2.2.28"
resolved "https://registry.yarnpkg.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.28.tgz#5a15b2f58a52642c88c8eca584644e01703d645a"
integrity sha512-8LuxJSuFVc92+0AdNv4QOxRL4Abeo1DgLnGNkn1XlaujPH/3cCFz3QI60r2VNu4obJJROzgnIUw5TKQkZvZI1w==
dependencies:
compute-scroll-into-view "^1.0.17"
section-matter@^1.0.0: section-matter@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167" resolved "https://registry.yarnpkg.com/section-matter/-/section-matter-1.0.0.tgz#e9041953506780ec01d59f292a19c7b850b84167"

Loading…
Cancel
Save