import { useCallback, useEffect, useRef, useState } from "react"; import { SidebarSimpleIcon } from "@phosphor-icons/react"; import { cn } from "@/lib/utils"; const STORAGE_WIDTH = "open-swe.sidebar.width"; const STORAGE_COLLAPSED = "open-swe.sidebar.collapsed"; export const SIDEBAR_DEFAULT_WIDTH = 260; export const SIDEBAR_MIN_WIDTH = 200; export const SIDEBAR_MAX_WIDTH = 420; function readStoredWidth(): number { if (typeof window === "undefined") return SIDEBAR_DEFAULT_WIDTH; const raw = window.localStorage.getItem(STORAGE_WIDTH); const parsed = raw ? Number(raw) : NaN; if (!Number.isFinite(parsed)) return SIDEBAR_DEFAULT_WIDTH; return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, parsed)); } function readStoredCollapsed(): boolean { if (typeof window === "undefined") return false; return window.localStorage.getItem(STORAGE_COLLAPSED) === "1"; } export function useSidebarLayout() { const [width, setWidthState] = useState(() => readStoredWidth()); const [collapsed, setCollapsedState] = useState(() => readStoredCollapsed()); const setWidth = useCallback((next: number) => { const clamped = Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, next)); setWidthState(clamped); window.localStorage.setItem(STORAGE_WIDTH, String(clamped)); }, []); const setCollapsed = useCallback((next: boolean) => { setCollapsedState(next); window.localStorage.setItem(STORAGE_COLLAPSED, next ? "1" : "0"); }, []); const toggle = useCallback(() => setCollapsed(!collapsed), [collapsed, setCollapsed]); return { width, collapsed, setWidth, setCollapsed, toggle }; } interface SidebarFrameProps { width: number; setWidth: (next: number) => void; collapsed: boolean; toggle: () => void; className?: string; children: React.ReactNode; } export function SidebarFrame({ width, setWidth, collapsed, toggle, className, children, }: SidebarFrameProps) { if (collapsed) { return ( ); } return ( ); } function ResizeHandle({ width, onResize }: { width: number; onResize: (next: number) => void }) { const startRef = useRef<{ x: number; width: number } | null>(null); const [dragging, setDragging] = useState(false); const onPointerDown = (e: React.PointerEvent) => { e.preventDefault(); startRef.current = { x: e.clientX, width }; setDragging(true); e.currentTarget.setPointerCapture(e.pointerId); }; const onPointerMove = (e: React.PointerEvent) => { if (!startRef.current) return; const next = startRef.current.width + (e.clientX - startRef.current.x); onResize(next); }; const onPointerUp = (e: React.PointerEvent) => { startRef.current = null; setDragging(false); if (e.currentTarget.hasPointerCapture(e.pointerId)) { e.currentTarget.releasePointerCapture(e.pointerId); } }; useEffect(() => { if (!dragging) return; const prev = document.body.style.cursor; document.body.style.cursor = "col-resize"; return () => { document.body.style.cursor = prev; }; }, [dragging]); return (
); } interface SidebarCollapseButtonProps { onToggle: () => void; className?: string; } export function SidebarCollapseButton({ onToggle, className }: SidebarCollapseButtonProps) { return ( ); }