import { createContext, useContext, useEffect, useRef, useState, type ReactNode, } from "react"; import { Outlet, matchPath, useLocation } from "react-router-dom"; type TwoPaneLayoutConfig = { left?: ReactNode; rightTop?: ReactNode; leftWidthClassName?: string; leftCollapsedWidthClassName?: string; }; const TwoPaneLayoutContext = createContext(null); const DEFAULT_LEFT_WIDTH_PX = 300; const LEFT_RAIL_WIDTH_PX = 32; const AUTO_HIDE_MS = 5000; export function useTwoPaneLayout() { const ctx = useContext(TwoPaneLayoutContext); if (!ctx) throw new Error("TwoPaneLayoutContext missing"); return ctx; } export default function TwoPaneLayout({ value }: { value: TwoPaneLayoutConfig }) { const { left, rightTop, leftWidthClassName = "md:grid-cols-[300px_minmax(0,1fr)]", leftCollapsedWidthClassName = "md:grid-cols-[10px_minmax(0,1fr)]", } = value; const location = useLocation(); const isContestsIndex = !!matchPath({ path: "/contests", end: true }, location.pathname); const isContestDetail = !!matchPath({ path: "/contests/:contestId", end: true }, location.pathname); const isRoundDetail = !!matchPath( { path: "/contests/:contestId/rounds/:roundId", end: true }, location.pathname, ); const [leftCollapsed, setLeftCollapsed] = useState(!isContestsIndex); const [leftPaneWidth, setLeftPaneWidth] = useState(DEFAULT_LEFT_WIDTH_PX); const leftPaneRef = useRef(null); const autoHideTimerRef = useRef(null); useEffect(() => { setLeftCollapsed(!(isContestsIndex || isContestDetail)); }, [isContestsIndex, isContestDetail]); useEffect(() => { if (!leftPaneRef.current || leftCollapsed) return; const width = leftPaneRef.current.getBoundingClientRect().width; if (width > 0 && width !== leftPaneWidth) { setLeftPaneWidth(width); } }, [leftCollapsed, leftPaneWidth, leftWidthClassName]); const clearAutoHide = () => { if (autoHideTimerRef.current) { window.clearTimeout(autoHideTimerRef.current); autoHideTimerRef.current = null; } }; const scheduleAutoHide = () => { if (!isRoundDetail || leftCollapsed) return; clearAutoHide(); autoHideTimerRef.current = window.setTimeout(() => { setLeftCollapsed(true); }, AUTO_HIDE_MS); }; useEffect(() => { if (!isRoundDetail || leftCollapsed) { clearAutoHide(); return; } scheduleAutoHide(); return clearAutoHide; }, [isRoundDetail, leftCollapsed]); const handlePaneMouseEnter = () => { clearAutoHide(); }; const handlePaneMouseLeave = () => { scheduleAutoHide(); }; const translateX = leftCollapsed ? -(Math.max(0, leftPaneWidth - LEFT_RAIL_WIDTH_PX)) : 0; const gridClassName = leftCollapsed ? leftCollapsedWidthClassName : leftWidthClassName; return (
{rightTop}
); }