import React, { useCallback, useEffect, useRef, useState } from "react"; import { cls } from "../util"; export type ResizablePanelsProps = { firstPanel: React.ReactNode; secondPanel: React.ReactNode; /** Whether the first panel is visible (e.g. Sidebar) */ showFirstPanel?: boolean; /** Whether the second panel is visible (e.g. Results) */ showSecondPanel?: boolean; /** 0-100 representing the width/height of the first panel */ panelSizePercent: number; onPanelSizeChange: (sizePercent: number) => void; minPanelSizePx?: number; orientation?: 'horizontal' | 'vertical'; className?: string; }; export function ResizablePanels({ firstPanel, secondPanel, showFirstPanel = true, showSecondPanel = true, panelSizePercent, onPanelSizeChange, minPanelSizePx = 200, orientation = 'horizontal', className }: ResizablePanelsProps) { const containerRef = useRef(null); const isResizingRef = useRef(false); // For local layout tracking without triggering React rerenders during drag const firstPanelRef = useRef(null); const startPosRef = useRef(0); const startSizeRef = useRef(0); const [isResizing, setIsResizing] = useState(false); const isHorizontal = orientation === 'horizontal'; const handleResizeStart = useCallback((e: React.MouseEvent) => { if (!showFirstPanel || !showSecondPanel) return; e.preventDefault(); isResizingRef.current = true; setIsResizing(true); startPosRef.current = isHorizontal ? e.clientX : e.clientY; if (firstPanelRef.current) { const rect = firstPanelRef.current.getBoundingClientRect(); startSizeRef.current = isHorizontal ? rect.width : rect.height; } document.body.style.cursor = isHorizontal ? 'col-resize' : 'row-resize'; document.body.style.userSelect = 'none'; }, [isHorizontal, showFirstPanel, showSecondPanel]); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isResizingRef.current || !containerRef.current) return; const currentPos = isHorizontal ? e.clientX : e.clientY; const delta = currentPos - startPosRef.current; // Dragging right/down increases first panel size let newSize = startSizeRef.current + delta; const containerRect = containerRef.current.getBoundingClientRect(); const containerTotal = isHorizontal ? containerRect.width : containerRect.height; // Limit the maximum size to prevent pushing the second panel offscreen const maxSize = containerTotal - minPanelSizePx; newSize = Math.max(minPanelSizePx, Math.min(newSize, maxSize)); // Directly update the DOM for performance while dragging if (firstPanelRef.current) { if (isHorizontal) { firstPanelRef.current.style.width = `${newSize}px`; } else { firstPanelRef.current.style.height = `${newSize}px`; } } }; const handleMouseUp = () => { if (isResizingRef.current && containerRef.current && firstPanelRef.current) { isResizingRef.current = false; setIsResizing(false); document.body.style.cursor = ""; document.body.style.userSelect = ""; // Calculate the final percentage and notify parent const containerRect = containerRef.current.getBoundingClientRect(); const firstPanelRect = firstPanelRef.current.getBoundingClientRect(); const containerSize = isHorizontal ? containerRect.width : containerRect.height; const finalSize = isHorizontal ? firstPanelRect.width : firstPanelRect.height; if (containerSize > 0) { const newPercent = (finalSize / containerSize) * 100; onPanelSizeChange(Math.max(0, Math.min(100, newPercent))); } } }; window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseup", handleMouseUp); return () => { window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseup", handleMouseUp); }; }, [onPanelSizeChange, isHorizontal, minPanelSizePx]); // Calculate applied size const appliedBasis = !showFirstPanel ? "0%" : (showSecondPanel ? `${panelSizePercent}%` : "100%"); return (
{/* First Panel */}
{firstPanel}
{/* Divider */} {showFirstPanel && showSecondPanel && (
{/* Transparent Hit Area with Pill inside */}
)} {/* Second Panel */}
{secondPanel}
); }