diff --git a/packages/ui/src/components/layout/ContextPanel.tsx b/packages/ui/src/components/layout/ContextPanel.tsx index 1571fb37..23368bc4 100644 --- a/packages/ui/src/components/layout/ContextPanel.tsx +++ b/packages/ui/src/components/layout/ContextPanel.tsx @@ -23,7 +23,7 @@ import { Icon } from "@/components/icon/Icon"; import { OpenChamberLogo } from "@/components/ui/OpenChamberLogo"; import { invokeDesktopCommand } from '@/lib/desktopNative'; -const CONTEXT_PANEL_MIN_WIDTH = 360; +const CONTEXT_PANEL_MIN_WIDTH = 380; const CONTEXT_PANEL_MAX_WIDTH = 1400; const CONTEXT_PANEL_DEFAULT_WIDTH = 600; const CONTEXT_TAB_LABEL_MAX_CHARS = 24; @@ -1588,6 +1588,7 @@ export const ContextPanel: React.FC = () => { const width = clampWidth(panelState?.width ?? CONTEXT_PANEL_DEFAULT_WIDTH); const [isResizing, setIsResizing] = React.useState(false); + const [suppressWidthTransition, setSuppressWidthTransition] = React.useState(false); const startXRef = React.useRef(0); const startWidthRef = React.useRef(width); const resizingWidthRef = React.useRef(null); @@ -1595,6 +1596,41 @@ export const ContextPanel: React.FC = () => { const panelRef = React.useRef(null); const chatFrameRefs = React.useRef>(new Map()); const wasOpenRef = React.useRef(false); + const previousIsOpenRef = React.useRef(isOpen); + const suppressWidthTransitionFrameRef = React.useRef(null); + + const suppressWidthTransitionForFrame = React.useCallback(() => { + setSuppressWidthTransition(true); + if (suppressWidthTransitionFrameRef.current !== null) { + window.cancelAnimationFrame(suppressWidthTransitionFrameRef.current); + } + suppressWidthTransitionFrameRef.current = window.requestAnimationFrame(() => { + suppressWidthTransitionFrameRef.current = null; + setSuppressWidthTransition(false); + }); + }, []); + + React.useEffect(() => () => { + if (suppressWidthTransitionFrameRef.current !== null) { + window.cancelAnimationFrame(suppressWidthTransitionFrameRef.current); + } + }, []); + + React.useLayoutEffect(() => { + const wasOpen = previousIsOpenRef.current; + previousIsOpenRef.current = isOpen; + + if (!isOpen) { + setSuppressWidthTransition(false); + return; + } + + if (wasOpen) { + return; + } + + suppressWidthTransitionForFrame(); + }, [isOpen, suppressWidthTransitionForFrame]); React.useEffect(() => { if (!isOpen || wasOpenRef.current) { @@ -1666,11 +1702,13 @@ export const ContextPanel: React.FC = () => { } const finalWidth = clampWidthToAvailableSpace(resizingWidthRef.current ?? width, panelRef.current); + suppressWidthTransitionForFrame(); + applyLiveWidth(finalWidth); + resizingWidthRef.current = finalWidth; + setContextPanelWidth(directoryKey, finalWidth); setIsResizing(false); activeResizePointerIDRef.current = null; - resizingWidthRef.current = null; - setContextPanelWidth(directoryKey, finalWidth); - }, [directoryKey, setContextPanelWidth, width]); + }, [applyLiveWidth, directoryKey, setContextPanelWidth, suppressWidthTransitionForFrame, width]); React.useEffect(() => { if (!isResizing) { @@ -1910,36 +1948,44 @@ export const ContextPanel: React.FC = () => { ); - if (!isOpen) { - return null; - } - - const panelStyle: React.CSSProperties = isExpanded + const panelStyle: React.CSSProperties = !isOpen ? { - ['--oc-context-panel-width' as string]: '100%', - width: '100%', - minWidth: '100%', - maxWidth: '100%', - } - : { - width: 'min(var(--oc-context-panel-width), 100%)', - minWidth: `min(${CONTEXT_PANEL_MIN_WIDTH}px, 100%)`, - maxWidth: '100%', ['--oc-context-panel-width' as string]: `${isResizing ? (resizingWidthRef.current ?? width) : width}px`, - }; + width: 0, + minWidth: 0, + maxWidth: 0, + opacity: 0, + overflow: 'hidden', + visibility: 'hidden', + } + : isExpanded + ? { + ['--oc-context-panel-width' as string]: '100%', + width: '100%', + minWidth: '100%', + maxWidth: '100%', + } + : { + width: 'min(var(--oc-context-panel-width), 100%)', + minWidth: `min(${CONTEXT_PANEL_MIN_WIDTH}px, 100%)`, + maxWidth: '100%', + ['--oc-context-panel-width' as string]: `${isResizing ? (resizingWidthRef.current ?? width) : width}px`, + }; return (