import classNames from "classnames"; import * as m from "motion/react-m"; import type { CSSProperties, ReactNode } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useContainerSize } from "../hooks/useContainerSize"; import { Overlay } from "./Overlay"; import type { ResizeHandleEvent } from "./ResizeHandle"; import { ResizeHandle } from "./ResizeHandle"; const FLOATING_BREAKPOINT = 600; const side = { gridArea: "side", minWidth: 0 }; const drag = { gridArea: "drag" }; const body = { gridArea: "body", minWidth: 0 }; interface Props { width: number; onWidthChange: (width: number) => void; hidden?: boolean; onHiddenChange?: (hidden: boolean) => void; floatingHidden?: boolean; onFloatingHiddenChange?: (hidden: boolean) => void; onFloatingChange?: (floating: boolean) => void; floatingWidth?: number; defaultWidth?: number; minWidth?: number; className?: string; sidebar: ReactNode; children: ReactNode; } export function SidebarLayout({ width, onWidthChange, hidden = false, onHiddenChange, floatingHidden = true, onFloatingHiddenChange, onFloatingChange, floatingWidth = 320, defaultWidth = 250, minWidth = 50, className, sidebar, children, }: Props) { const containerRef = useRef(null); const containerSize = useContainerSize(containerRef); const floating = containerSize.width > 0 && containerSize.width <= FLOATING_BREAKPOINT; useEffect(() => { onFloatingChange?.(floating); }, [floating]); // eslint-disable-line react-hooks/exhaustive-deps const [isResizing, setIsResizing] = useState(false); const startWidth = useRef(null); const sideWidth = hidden ? 0 : width; const styles = useMemo( () => ({ gridTemplate: ` ' ${side.gridArea} ${drag.gridArea} ${body.gridArea}' minmax(0,1fr) / ${sideWidth}px 0 1fr`, }), [sideWidth], ); const handleResizeStart = useCallback(() => { startWidth.current = width; setIsResizing(true); }, [width]); const handleResizeEnd = useCallback(() => { setIsResizing(false); startWidth.current = null; }, []); const handleResizeMove = useCallback( ({ x, xStart }: ResizeHandleEvent) => { if (startWidth.current == null) return; const newWidth = startWidth.current + (x - xStart); if (newWidth < minWidth) { onHiddenChange?.(true); onWidthChange(defaultWidth); } else { if (hidden) onHiddenChange?.(false); onWidthChange(newWidth); } }, [minWidth, hidden, onHiddenChange, onWidthChange, defaultWidth], ); const handleReset = useCallback(() => { onWidthChange(defaultWidth); }, [onWidthChange, defaultWidth]); if (floating) { return (
onFloatingHiddenChange?.(true)} zIndex={20} > {sidebar} {children}
); } return (
{sidebar}
{children}
); }