import { useEffect, useRef, useState, type ReactNode } from "react"; const DEFAULT_WIDTH = 256; const MIN_WIDTH = 200; const MAX_WIDTH = 420; const MAIN_MIN_WIDTH = 660; const STORAGE_KEY = "opsxapi.sidebar.width"; function savedWidth() { try { const value = Number(localStorage.getItem(STORAGE_KEY)); return Number.isFinite(value) && value >= MIN_WIDTH && value <= MAX_WIDTH ? value : DEFAULT_WIDTH; } catch { return DEFAULT_WIDTH; } } /** All workspace views share the same sidebar width and resize affordance. */ export function WorkspaceSidebar({ label, className = "", children }: { label: string; className?: string; children: ReactNode }) { const divider = useRef(null); const [width, setWidth] = useState(savedWidth); const [available, setAvailable] = useState(Infinity); const visibleWidth = Math.min(width, Math.max(MIN_WIDTH, available - MAIN_MIN_WIDTH - 6)); useEffect(() => { const parent = divider.current?.parentElement; if (!parent) return; const observer = new ResizeObserver(() => setAvailable(parent.clientWidth)); observer.observe(parent); setAvailable(parent.clientWidth); return () => { observer.disconnect(); document.body.style.cursor = ""; document.body.style.userSelect = ""; }; }, []); const setAndSave = (value: number) => { const next = Math.round(Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, available - MAIN_MIN_WIDTH - 6, value))); setWidth(next); try { localStorage.setItem(STORAGE_KEY, String(next)); } catch { /* optional preference */ } }; const resize = (clientX: number) => { const parent = divider.current?.parentElement; if (parent) setAndSave(clientX - parent.getBoundingClientRect().left); }; const stop = (element: HTMLElement, pointerId: number) => { if (element.hasPointerCapture(pointerId)) element.releasePointerCapture(pointerId); document.body.style.cursor = ""; document.body.style.userSelect = ""; }; return <>
{ event.currentTarget.setPointerCapture(event.pointerId); document.body.style.cursor = "col-resize"; document.body.style.userSelect = "none"; }} onPointerMove={(event) => { if (event.currentTarget.hasPointerCapture(event.pointerId)) resize(event.clientX); }} onPointerUp={(event) => stop(event.currentTarget, event.pointerId)} onPointerCancel={(event) => stop(event.currentTarget, event.pointerId)} onDoubleClick={() => setAndSave(DEFAULT_WIDTH)} onKeyDown={(event) => { if (event.key === "ArrowLeft" || event.key === "ArrowRight") { event.preventDefault(); setAndSave(visibleWidth + (event.key === "ArrowRight" ? 16 : -16)); } }} /> ; }