import React, { useState, useEffect, useRef, useLayoutEffect } from "react"; import { css, cx } from "@emotion/css"; import useLocation from "wouter/use-location"; import { ReactComponent as Logo } from "../assets/logo.svg"; import { ReactComponent as Right } from "../assets/arrow-right.svg"; import { get, getTimeout } from "../util"; import Menu, { MenuEntries } from "./Menu"; import useMediaQuery from "../util/useMediaQuery"; const [timer, clear] = getTimeout(); const Container: React.FC<{ children: ( | string | React.DetailedReactHTMLElement | React.ReactElement )[]; hideNav?: boolean; className?: string; }> = ({ children: _children, hideNav = false, className, ...props }) => { const [location, navigate] = useLocation(); const mobile = useMediaQuery("(max-width: 50rem)"); const logoContainer = useRef(null); const highlightCircle = useRef(null); const containerChild = useRef(null); const nextBtn = useRef(null); const [showMenu, setShowMenu] = useState(false); const children = React.Children.map( _children, ( child: | string | React.DetailedReactHTMLElement | React.ReactElement, ) => !child || typeof child === "string" ? child : React.cloneElement(child, { style: { opacity: 0, transform: "translateY(3rem)", transition: "all 300ms", }, }), ); useEffect(() => { // scroll back to top when new page is loaded window.scrollTo({ top: 0 }); if (highlightCircle.current) { highlightCircle.current.classList.add("highlight"); timer( () => highlightCircle.current && highlightCircle.current.classList.remove("highlight"), 1500, ); } if (nextBtn.current) { nextBtn.current.style.width = "4rem"; timer( () => nextBtn.current && (nextBtn.current.style.right = "10vw"), 300, ); } if (containerChild.current) { const containerChildren = [...containerChild.current.children] as ( | HTMLElement | SVGElement )[]; containerChildren.forEach((child, idx) => { timer(() => { child.style.removeProperty("opacity"); child.style.removeProperty("transform"); }, 100 * idx); }); } // cleanup return clear; }, []); const handleResize = () => { if (containerChild.current && logoContainer.current) logoContainer.current.style.left = `${ containerChild.current.getBoundingClientRect().x }px`; }; useEffect(() => { window.addEventListener("resize", handleResize); // cleanup return () => window.removeEventListener("resize", handleResize); }, []); type MouseKb = React.MouseEvent | React.KeyboardEvent | KeyboardEvent; const animateArrow = (e: MouseKb) => { if (containerChild.current) { ( [...containerChild.current.children] as (HTMLElement | SVGElement)[] ).forEach(child => { child.style.marginBottom = "2rem"; child.style.opacity = "0"; }); } try { const target = e.currentTarget! as HTMLButtonElement; target.style.width = "0"; } catch {} }; const current = MenuEntries.findIndex( ([, path]) => location === path || location.startsWith("${path}" + "/"), ); const next = get.next(MenuEntries, current)[1]; const prev = get.prev(MenuEntries, current)[1]; const end = current === MenuEntries.length - 1; const handlePrev = (e: MouseKb) => { animateArrow(e); timer(() => prev && navigate(prev), 300); }; const handleNext = (e: MouseKb) => { animateArrow(e); timer(() => next && navigate(next), 300); }; function kbnav(e: KeyboardEvent) { switch (e.key) { case "ArrowLeft": return handlePrev(e); case "ArrowRight": return handleNext(e); } } useEffect(() => { window.addEventListener("keydown", kbnav); // cleanup return () => window.removeEventListener("keydown", kbnav); }, []); // on first render useLayoutEffect(handleResize, []); return (
{!hideNav && ( !mobile && setShowMenu(true)} onMouseOut={() => !mobile && setShowMenu(false)}> )}
{children}
); }; export default Container;