import * as React from 'react' import { useContext, useEffect, useRef, useState } from "react" import { PageLayout } from '@xyd-js/core'; import * as cn from "./LayoutPrimary.styles" export interface LayoutPrimaryProps { children: React.ReactNode; subheader?: boolean; className?: string; layout?: PageLayout scrollKey?: string id?: string style?: React.CSSProperties } const LayoutPrimaryContext = React.createContext<{ scrollRef: React.RefObject; isMobileNavOpen: boolean; setIsMobileNavOpen: (isOpen: boolean) => void; }>({ scrollRef: React.createRef(), isMobileNavOpen: false, setIsMobileNavOpen: () => { }, }) // TODO: move scroller to xyd-foo export function LayoutPrimary(props: LayoutPrimaryProps) { const scrollRef = useRef(null) const [isMobileNavOpen, setIsMobileNavOpen] = useState(false) const { hideMainHeader } = useSubHeader(props.subheader ? scrollRef : null, props.scrollKey) useEffect(() => { scrollRef.current = window }, []) return {props.children} } interface LayoutPrimaryHeaderProps { header: React.ReactNode; banner?: React.ReactNode; subheader?: React.ReactNode; } LayoutPrimary.Header = function LayoutPrimaryHeader(props: LayoutPrimaryHeaderProps) { const { isMobileNavOpen, setIsMobileNavOpen } = useContext(LayoutPrimaryContext) return <>
{props.banner}
{props.header}
{props.subheader &&
{props.subheader}
}
} LayoutPrimary.Hamburger = function LayoutPrimaryHamburger() { const { isMobileNavOpen, setIsMobileNavOpen } = useContext(LayoutPrimaryContext) return } interface LayoutPrimaryMobileAsideProps { aside: React.ReactNode; } LayoutPrimary.MobileAside = function LayoutPrimaryAside(props: LayoutPrimaryMobileAsideProps) { const { isMobileNavOpen, setIsMobileNavOpen } = useContext(LayoutPrimaryContext) return <>
setIsMobileNavOpen(false)} /> } interface LayoutPrimaryPageProps { children: React.ReactNode; contentNav?: React.ReactNode; after?: React.ReactNode; } LayoutPrimary.Page = function LayoutPrimaryPage(props: LayoutPrimaryPageProps) { return <>
{props.children}
{ props.contentNav && }
{props.after}
} function $HamburgerLine({ active }: { active: boolean }) { return } const SCROLL_DOWN_TRIGGER_THRESHOLD = 200; const SCROLL_UP_TRIGGER_THRESHOLD = 100; // TODO: move to `xyd-foo` or somewhere else // TODO better solution than `key` function useSubHeader(ref: React.RefObject | null, key?: any) { const [hideMainHeader, setHideMainHeader] = useState(false) const [lastScrollTop, setLastScrollTop] = useState(0) const [scrollDirection, setScrollDirection] = useState<'up' | 'down' | null>(null) const [scrollThreshold, setScrollThreshold] = useState(0) const [scrollStartPosition, setScrollStartPosition] = useState(0) const [isScrolling, setIsScrolling] = useState(false) function reset() { setHideMainHeader(false) setLastScrollTop(0) setScrollDirection(null) setScrollThreshold(0) setScrollStartPosition(0) setIsScrolling(false) } useEffect(() => { reset() }, [key]) function onScroll(e: Event) { if (!(e.target instanceof HTMLElement)) { return } const currentScrollTop = e.target.scrollTop // Always show header when near the top of the page if (currentScrollTop < SCROLL_UP_TRIGGER_THRESHOLD) { setHideMainHeader(false) setScrollThreshold(0) setLastScrollTop(currentScrollTop) setIsScrolling(false) return } // Determine scroll direction const direction = currentScrollTop > lastScrollTop ? 'down' : 'up' // If direction changed, reset scroll tracking if (direction !== scrollDirection) { setScrollDirection(direction) setScrollStartPosition(currentScrollTop) setIsScrolling(true) } // Calculate total scroll distance from start position const totalScrollDistance = Math.abs(currentScrollTop - scrollStartPosition) // Only trigger header changes if we've scrolled enough distance in the current direction if (direction === 'down' && totalScrollDistance > SCROLL_DOWN_TRIGGER_THRESHOLD) { // When scrolling down, hide header setHideMainHeader(true) setScrollThreshold(currentScrollTop) // Reset scroll tracking after triggering setScrollStartPosition(currentScrollTop) } else if (direction === 'up' && totalScrollDistance > SCROLL_UP_TRIGGER_THRESHOLD) { // When scrolling up, show header setHideMainHeader(false) setScrollThreshold(currentScrollTop) // Reset scroll tracking after triggering setScrollStartPosition(currentScrollTop) } setLastScrollTop(currentScrollTop) } useEffect(() => { if (!ref?.current) { return } ref.current.addEventListener("scroll", onScroll) return () => { ref.current?.removeEventListener("scroll", onScroll) } }, [ref, key, lastScrollTop, scrollDirection, scrollThreshold, scrollStartPosition]); return { hideMainHeader, } }