import React from 'react'; import { cn } from '@/lib/utils'; import { ErrorBoundary } from '../ui/ErrorBoundary'; import { useI18n } from '@/lib/i18n'; import { useUIStore } from '@/stores/useUIStore'; export const SIDEBAR_CONTENT_WIDTH = 280; const SIDEBAR_MIN_WIDTH = 280; const SIDEBAR_MAX_WIDTH = 500; interface SidebarProps { isOpen: boolean; isMobile: boolean; children: React.ReactNode; className?: string; } export const Sidebar: React.FC = ({ isOpen, isMobile, children, className }) => { const { t } = useI18n(); const sidebarWidth = useUIStore((state) => state.sidebarWidth); const setSidebarWidth = useUIStore((state) => state.setSidebarWidth); const [isResizing, setIsResizing] = React.useState(false); const startXRef = React.useRef(0); const startWidthRef = React.useRef(sidebarWidth || SIDEBAR_CONTENT_WIDTH); const resizingWidthRef = React.useRef(null); const activeResizePointerIDRef = React.useRef(null); const sidebarRef = React.useRef(null); const clampSidebarWidth = React.useCallback((value: number) => { return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, value)); }, []); const applyLiveWidth = React.useCallback((nextWidth: number) => { const sidebar = sidebarRef.current; if (!sidebar) { return; } sidebar.style.width = `${nextWidth}px`; sidebar.style.minWidth = `${nextWidth}px`; sidebar.style.maxWidth = `${nextWidth}px`; sidebar.style.setProperty('--oc-left-sidebar-width', `${nextWidth}px`); }, []); React.useEffect(() => { if (isMobile && isResizing) { setIsResizing(false); } }, [isMobile, isResizing]); React.useEffect(() => { if (!isResizing) { resizingWidthRef.current = null; activeResizePointerIDRef.current = null; } }, [isResizing]); if (isMobile) { return null; } const openWidth = Math.min( SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_MIN_WIDTH, sidebarWidth || SIDEBAR_CONTENT_WIDTH) ); const appliedWidth = isOpen ? openWidth : 0; const handlePointerDown = (event: React.PointerEvent) => { if (!isOpen) { return; } try { event.currentTarget.setPointerCapture(event.pointerId); } catch { // ignore } activeResizePointerIDRef.current = event.pointerId; setIsResizing(true); startXRef.current = event.clientX; startWidthRef.current = appliedWidth; resizingWidthRef.current = appliedWidth; applyLiveWidth(appliedWidth); event.preventDefault(); }; const handlePointerMove = (event: React.PointerEvent) => { if (isMobile || !isResizing || activeResizePointerIDRef.current !== event.pointerId) { return; } const delta = event.clientX - startXRef.current; const nextWidth = clampSidebarWidth(startWidthRef.current + delta); if (resizingWidthRef.current === nextWidth) { return; } resizingWidthRef.current = nextWidth; applyLiveWidth(nextWidth); }; const handlePointerEnd = (event: React.PointerEvent) => { if (activeResizePointerIDRef.current !== event.pointerId || isMobile) { return; } try { event.currentTarget.releasePointerCapture(event.pointerId); } catch { // ignore } const finalWidth = clampSidebarWidth(resizingWidthRef.current ?? appliedWidth); activeResizePointerIDRef.current = null; resizingWidthRef.current = null; setIsResizing(false); setSidebarWidth(finalWidth); }; const currentWidth = isResizing ? (resizingWidthRef.current ?? appliedWidth) : appliedWidth; return ( ); };