🐛(frontend) fix panel collapse handle

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.
This commit is contained in:
Anthony LC
2026-03-09 14:03:57 +01:00
parent 6cef5ff2a0
commit 76cb6d66a4
2 changed files with 57 additions and 77 deletions
@@ -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'};
@@ -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<ImperativePanelHandle>(null);
const savedWidthPxRef = useRef<number>(minPanelSizePx);
const previousPanelOpenRef = useRef<boolean>(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 = ({
<Panel
ref={ref}
className="--docs--resizable-left-panel"
order={0}
collapsible
collapsible={!isPanelOpen}
collapsedSize={0}
style={{
overflow: 'hidden',
transition: isToggleAnimating ? PANEL_TOGGLE_TRANSITION : 'none',
transition: isDragging
? 'none'
: 'flex var(--c--globals--transitions--duration) var(--c--globals--transitions--ease-out)',
}}
order={0}
defaultSize={
isDesktop
? Math.max(
minPanelSizePercent,
Math.min(
pxToPercent(savedWidthPxRef.current),
maxPanelSizePercent,
),
Math.min(panelSizePercent, maxPanelSizePercent),
)
: 0
}
@@ -129,25 +113,20 @@ export const ResizableLeftPanel = ({
>
{leftPanel}
</Panel>
<PanelResizeHandle
style={{
borderRightWidth: '1px',
borderRightStyle: 'solid',
borderRightColor: 'var(--c--contextuals--border--surface--primary)',
width: '1px',
cursor: 'col-resize',
}}
disabled={!isDesktop || !isPanelOpen}
/>
<Panel
order={1}
style={{
transition: isToggleAnimating ? PANEL_TOGGLE_TRANSITION : 'none',
}}
>
{children}
</Panel>
{isPanelOpen && (
<PanelResizeHandle
style={{
borderRightWidth: '1px',
borderRightStyle: 'solid',
borderRightColor: 'var(--c--contextuals--border--surface--primary)',
width: '1px',
cursor: 'col-resize',
}}
onDragging={setIsDragging}
disabled={!isDesktop}
/>
)}
<Panel order={1}>{children}</Panel>
</PanelGroup>
);
};