From 76cb6d66a45a4d7f209ca6d03d5ec797e95bd212 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Fri, 6 Mar 2026 16:45:32 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20fix=20panel=20collapse?= =?UTF-8?q?=20handle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When handling to the left, the panel could totally collapse, and do beeing unsync with the state of the left panel (open or closed). We now keep the collapsing deactivate until the state of the left panel is closing. --- .../docs/doc-tree/components/DocTree.tsx | 1 + .../components/ResizableLeftPanel.tsx | 133 ++++++++---------- 2 files changed, 57 insertions(+), 77 deletions(-) diff --git a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx index b0c416fc..4c7f3f95 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-tree/components/DocTree.tsx @@ -287,6 +287,7 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => { padding: ${spacingsTokens['2xs']}; border-radius: var(--c--globals--spacings--st); width: 100%; + min-width: 200px; background-color: ${rootIsSelected || rootActionsOpen ? 'var(--c--contextuals--background--semantic--contextual--primary)' : 'transparent'}; diff --git a/src/frontend/apps/impress/src/features/left-panel/components/ResizableLeftPanel.tsx b/src/frontend/apps/impress/src/features/left-panel/components/ResizableLeftPanel.tsx index 193b0bc8..2922c662 100644 --- a/src/frontend/apps/impress/src/features/left-panel/components/ResizableLeftPanel.tsx +++ b/src/frontend/apps/impress/src/features/left-panel/components/ResizableLeftPanel.tsx @@ -6,17 +6,15 @@ import { PanelResizeHandle, } from 'react-resizable-panels'; -import { useLeftPanelStore } from '@/features/left-panel/stores'; import { useResponsiveStore } from '@/stores'; +import { useLeftPanelStore } from '../stores'; + // Convert a target pixel width to a percentage of the current viewport width. const pxToPercent = (px: number) => { return (px / window.innerWidth) * 100; }; -const PANEL_TOGGLE_TRANSITION = - 'flex-grow 180ms var(--c--globals--transitions--ease-out), flex-basis 180ms var(--c--globals--transitions--ease-out)'; - type ResizableLeftPanelProps = { leftPanel: React.ReactNode; children: React.ReactNode; @@ -34,70 +32,58 @@ export const ResizableLeftPanel = ({ const { isPanelOpen } = useLeftPanelStore(); const ref = useRef(null); const savedWidthPxRef = useRef(minPanelSizePx); - const previousPanelOpenRef = useRef(isPanelOpen); - const [isToggleAnimating, setIsToggleAnimating] = useState(false); + const [isDragging, setIsDragging] = useState(false); const minPanelSizePercent = pxToPercent(minPanelSizePx); const maxPanelSizePercent = Math.min(pxToPercent(maxPanelSizePx), 40); + + const [panelSizePercent, setPanelSizePercent] = useState(() => { + const initialSize = pxToPercent(minPanelSizePx); + return Math.max( + minPanelSizePercent, + Math.min(initialSize, maxPanelSizePercent), + ); + }); + + /** + * When the panel is toggled open/closed, we want + * to either expand/collapse + */ useEffect(() => { - const syncPanelState = () => { - if (!ref.current || !isDesktop) { - return; - } - - if (!isPanelOpen) { - ref.current.collapse(); - return; - } - - const restoredSizePercent = Math.max( - minPanelSizePercent, - Math.min(pxToPercent(savedWidthPxRef.current), maxPanelSizePercent), - ); - - ref.current.expand(); - ref.current.resize(restoredSizePercent); - }; - - const hasPanelToggleChanged = previousPanelOpenRef.current !== isPanelOpen; - previousPanelOpenRef.current = isPanelOpen; - - if (hasPanelToggleChanged) { - setIsToggleAnimating(true); - const animationFrameId = requestAnimationFrame(() => { - syncPanelState(); - }); - const timeoutId = setTimeout(() => { - setIsToggleAnimating(false); - }, 180); - - return () => { - window.cancelAnimationFrame(animationFrameId); - window.clearTimeout(timeoutId); - }; + if (!ref.current || !isDesktop) { + return; } + if (isPanelOpen) { + ref.current.expand(); + } else { + ref.current.collapse(); + } + }, [isPanelOpen, isDesktop]); - syncPanelState(); - - if (!isDesktop || !isPanelOpen) { + // Keep pixel width constant on window resize + useEffect(() => { + if (!isDesktop) { return; } const handleResize = () => { - syncPanelState(); + const newPercent = pxToPercent(savedWidthPxRef.current); + setPanelSizePercent(newPercent); + if (ref.current) { + ref.current.resize?.(newPercent - (ref.current.getSize() || 0)); + } }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); }; - }, [isDesktop, isPanelOpen, minPanelSizePercent, maxPanelSizePercent]); + }, [isDesktop]); const handleResize = (sizePercent: number) => { - if (isDesktop && sizePercent > 0) { - const widthPx = (sizePercent / 100) * window.innerWidth; - savedWidthPxRef.current = widthPx; - } + const widthPx = (sizePercent / 100) * window.innerWidth; + savedWidthPxRef.current = widthPx; + setPanelSizePercent(sizePercent); }; return ( @@ -105,21 +91,19 @@ export const ResizableLeftPanel = ({ {leftPanel} - - - - {children} - + {isPanelOpen && ( + + )} + {children} ); };