import ScrollToTopButton from '@/components/ScrollToTopButton' import { Titlebar } from '@/components/Titlebar' import { TPrimaryPageName, usePrimaryPage } from '@/PageManager' import { DeepBrowsingProvider } from '@/providers/DeepBrowsingProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider' import { FOCUS_PRIMARY_SCROLL_SHORTCUT_KEY, isRadixDialogOpen, shouldIgnoreKeyboardShortcutEvent } from '@/lib/keyboard-shortcuts' import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react' const PrimaryPageLayout = forwardRef( ( { children, titlebar, pageName, displayScrollToTopButton = false, hideTitlebarBottomBorder = false, subHeader }: { children?: React.ReactNode titlebar: React.ReactNode pageName: TPrimaryPageName displayScrollToTopButton?: boolean hideTitlebarBottomBorder?: boolean /** Rendered between titlebar and scroll area; not in scroll flow so it never overlaps content */ subHeader?: React.ReactNode }, ref ) => { const scrollAreaRef = useRef(null) const smallScreenScrollAreaRef = useRef(null) const smallScreenLastScrollTopRef = useRef(0) const { isSmallScreen } = useScreenSize() const { current, display } = usePrimaryPage() useImperativeHandle( ref, () => ({ scrollToTop: (behavior: ScrollBehavior = 'smooth') => { setTimeout(() => { if (scrollAreaRef.current) { return scrollAreaRef.current.scrollTo({ top: 0, behavior }) } window.scrollTo({ top: 0, behavior }) }, 10) } }), [] ) useEffect(() => { if (!isSmallScreen) return const isVisible = () => { return smallScreenScrollAreaRef.current?.checkVisibility ? smallScreenScrollAreaRef.current?.checkVisibility() : false } if (isVisible()) { window.scrollTo({ top: smallScreenLastScrollTopRef.current, behavior: 'instant' }) } const handleScroll = () => { if (isVisible()) { smallScreenLastScrollTopRef.current = window.scrollY } } window.addEventListener('scroll', handleScroll) return () => { window.removeEventListener('scroll', handleScroll) } }, [current, isSmallScreen, display]) useEffect(() => { if (isSmallScreen) return if (current !== pageName || !display) return const onKeyDown = (e: KeyboardEvent) => { if (!e.altKey || !e.shiftKey || e.key.toLowerCase() !== FOCUS_PRIMARY_SCROLL_SHORTCUT_KEY) return if (e.metaKey || e.ctrlKey) return if (shouldIgnoreKeyboardShortcutEvent(e.target)) return if (isRadixDialogOpen()) return e.preventDefault() scrollAreaRef.current?.focus({ preventScroll: true }) } document.addEventListener('keydown', onKeyDown) return () => document.removeEventListener('keydown', onKeyDown) }, [isSmallScreen, current, pageName, display]) if (isSmallScreen) { return (
{titlebar} {subHeader &&
{subHeader}
}
{children}
{displayScrollToTopButton && }
) } return (
{titlebar} {subHeader &&
{subHeader}
}
{children}
{displayScrollToTopButton && } ) } ) PrimaryPageLayout.displayName = 'PrimaryPageLayout' export default PrimaryPageLayout export type TPrimaryPageLayoutRef = { scrollToTop: (behavior?: ScrollBehavior) => void } function PrimaryPageTitlebar({ children, hideBottomBorder = false }: { children?: React.ReactNode hideBottomBorder?: boolean }) { return ( {children} ) }