'use client' import { useRef, useState, useEffect, useCallback } from 'react' import { cn } from '@/lib/utils' const COOKIE_PREFIX = 'split-pane:' const COOKIE_MAX_AGE = 60 * 60 * 24 * 365 function readSplitCookie(key: string): number | null { if (typeof document === 'undefined') return null const match = document.cookie.match(new RegExp(`(?:^|; )${COOKIE_PREFIX}${key}=([^;]+)`)) if (!match) return null const val = parseFloat(decodeURIComponent(match[1])) return !isNaN(val) && val > 0 && val < 100 ? val : null } function writeSplitCookie(key: string, value: number) { document.cookie = `${COOKIE_PREFIX}${key}=${value}; max-age=${COOKIE_MAX_AGE}; path=/; samesite=lax` } interface SplitPaneProps { left: React.ReactNode right: React.ReactNode storageKey: string defaultSplit?: number // percentage for left pane minSize?: number // minimum px per pane, default 200 } export function SplitPane({ left, right, storageKey, defaultSplit = 20, minSize = 200, }: SplitPaneProps) { const containerRef = useRef(null) const [split, setSplit] = useState(() => { return readSplitCookie(storageKey) ?? defaultSplit }) const [isDragging, setIsDragging] = useState(false) const [isMobile, setIsMobile] = useState(false) const [activeTab, setActiveTab] = useState<'left' | 'right'>('left') // Detect mobile useEffect(() => { const check = () => setIsMobile(window.innerWidth < 1024) check() window.addEventListener('resize', check) return () => window.removeEventListener('resize', check) }, []) const onMouseMove = useCallback((e: MouseEvent) => { if (!isDragging || !containerRef.current) return const rect = containerRef.current.getBoundingClientRect() const containerWidth = rect.width const offsetX = e.clientX - rect.left const minPct = (minSize / containerWidth) * 100 const maxPct = 100 - minPct const newSplit = Math.min(maxPct, Math.max(minPct, (offsetX / containerWidth) * 100)) setSplit(newSplit) writeSplitCookie(storageKey, newSplit) }, [isDragging, minSize, storageKey]) const onMouseUp = useCallback(() => setIsDragging(false), []) useEffect(() => { if (isDragging) { window.addEventListener('mousemove', onMouseMove) window.addEventListener('mouseup', onMouseUp) } return () => { window.removeEventListener('mousemove', onMouseMove) window.removeEventListener('mouseup', onMouseUp) } }, [isDragging, onMouseMove, onMouseUp]) if (isMobile) { return (
{activeTab === 'left' ? left : right}
) } return (
{/* Left pane */}
{left}
{/* Divider */}
setIsDragging(true)} className={cn( 'w-1 shrink-0 bg-border hover:bg-primary transition-colors cursor-col-resize', isDragging && 'bg-primary' )} /> {/* Right pane */}
{right}
) }