mirror of
https://github.com/reactjs/react.dev.git
synced 2026-02-27 03:08:06 +00:00
Co-authored-by: Dan Abramov <dan.abramov@me.com> Co-authored-by: Sylwia Vargas <sylwia.vargas@gmail.com> Co-authored-by: Dan Lebowitz <dan.lebo@me.com> Co-authored-by: Razvan Gradinar <grazvan@fb.com> Co-authored-by: Jared Palmer <jared@palmer.net> Co-authored-by: Dane Grant <danecando@gmail.com> Co-authored-by: Dustin Goodman <dustin.s.goodman@gmail.com> Co-authored-by: Rick Hanlon <rickhanlonii@gmail.com> Co-authored-by: Maggie Appleton <maggie.fm.appleton@gmail.com> Co-authored-by: Alex Moldovan <alex.n.moldovan@gmail.com> Co-authored-by: Ives van Hoorne <ives.v.h@gmail.com> Co-authored-by: Brian Vaughn <bvaughn@fb.com> Co-authored-by: Dmitri Pavlutin <dpavlutin@gmail.com>
156 lines
4.3 KiB
TypeScript
156 lines
4.3 KiB
TypeScript
/*
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*/
|
|
|
|
import * as React from 'react';
|
|
import {RouteItem} from 'components/Layout/useRouteMeta';
|
|
import {useRouter} from 'next/router';
|
|
import {removeFromLast} from 'utils/removeFromLast';
|
|
import {useRouteMeta} from '../useRouteMeta';
|
|
import {SidebarLink} from './SidebarLink';
|
|
import useCollapse from 'react-collapsed';
|
|
import {useLayoutEffect} from 'react';
|
|
|
|
interface SidebarRouteTreeProps {
|
|
isMobile?: boolean;
|
|
routeTree: RouteItem;
|
|
level?: number;
|
|
}
|
|
|
|
function CollapseWrapper({
|
|
isExpanded,
|
|
duration,
|
|
children,
|
|
}: {
|
|
isExpanded: boolean;
|
|
duration: number;
|
|
children: any;
|
|
}) {
|
|
const ref = React.useRef<HTMLDivElement | null>(null);
|
|
const timeoutRef = React.useRef<number | null>(null);
|
|
const {getCollapseProps} = useCollapse({
|
|
isExpanded,
|
|
duration,
|
|
});
|
|
|
|
// Disable pointer events while animating.
|
|
const isExpandedRef = React.useRef(isExpanded);
|
|
if (typeof window !== 'undefined') {
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useLayoutEffect(() => {
|
|
const wasExpanded = isExpandedRef.current;
|
|
if (wasExpanded === isExpanded) {
|
|
return;
|
|
}
|
|
isExpandedRef.current = isExpanded;
|
|
if (ref.current !== null) {
|
|
const node: HTMLDivElement = ref.current;
|
|
node.style.pointerEvents = 'none';
|
|
if (timeoutRef.current !== null) {
|
|
window.clearTimeout(timeoutRef.current);
|
|
}
|
|
timeoutRef.current = window.setTimeout(() => {
|
|
node.style.pointerEvents = '';
|
|
}, duration + 100);
|
|
}
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
style={{
|
|
opacity: isExpanded ? 1 : 0.5,
|
|
transition: `opacity ${duration}ms ease-in-out`,
|
|
animation: `nav-fadein ${duration}ms ease-in-out`,
|
|
}}>
|
|
<div {...getCollapseProps()}>{children}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function SidebarRouteTree({
|
|
isMobile,
|
|
routeTree,
|
|
level = 0,
|
|
}: SidebarRouteTreeProps) {
|
|
const {breadcrumbs} = useRouteMeta(routeTree);
|
|
const {pathname} = useRouter();
|
|
const slug = pathname;
|
|
|
|
const currentRoutes = routeTree.routes as RouteItem[];
|
|
const expandedPath = currentRoutes.reduce(
|
|
(acc: string | undefined, curr: RouteItem) => {
|
|
if (acc) return acc;
|
|
const breadcrumb = breadcrumbs.find((b) => b.path === curr.path);
|
|
if (breadcrumb) {
|
|
return curr.path;
|
|
}
|
|
if (curr.path === pathname) {
|
|
return pathname;
|
|
}
|
|
return undefined;
|
|
},
|
|
undefined
|
|
);
|
|
|
|
const expanded = expandedPath;
|
|
return (
|
|
<ul>
|
|
{currentRoutes.map(({path, title, routes, heading}) => {
|
|
const pagePath = path && removeFromLast(path, '.');
|
|
const selected = slug === pagePath;
|
|
|
|
// if current route item has no path and children treat it as an API sidebar heading
|
|
if (!path || !pagePath || heading) {
|
|
return (
|
|
<SidebarRouteTree
|
|
level={level + 1}
|
|
isMobile={isMobile}
|
|
routeTree={{title, routes}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
// if route has a path and child routes, treat it as an expandable sidebar item
|
|
if (routes) {
|
|
const isExpanded = isMobile || expanded === path;
|
|
return (
|
|
<li key={`${title}-${path}-${level}-heading`}>
|
|
<SidebarLink
|
|
key={`${title}-${path}-${level}-link`}
|
|
href={pagePath}
|
|
selected={selected}
|
|
level={level}
|
|
title={title}
|
|
isExpanded={isExpanded}
|
|
isBreadcrumb={expandedPath === path}
|
|
hideArrow={isMobile}
|
|
/>
|
|
<CollapseWrapper duration={250} isExpanded={isExpanded}>
|
|
<SidebarRouteTree
|
|
isMobile={isMobile}
|
|
routeTree={{title, routes}}
|
|
level={level + 1}
|
|
/>
|
|
</CollapseWrapper>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
// if route has a path and no child routes, treat it as a sidebar link
|
|
return (
|
|
<li key={`${title}-${path}-${level}-link`}>
|
|
<SidebarLink
|
|
href={pagePath}
|
|
selected={selected}
|
|
level={level}
|
|
title={title}
|
|
/>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
);
|
|
}
|