Browse Source

fix: smooth settings panel transition and prevent image flicker on toggle

Animate InlineParameterPanel expand/collapse with max-height transition
instead of conditional rendering. Lock BaseNode content height during
resize operations to prevent intermediate layout states causing image
flicker.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 4 months ago
parent
commit
aacae7e3cb
  1. 31
      src/components/nodes/BaseNode.tsx
  2. 28
      src/components/nodes/InlineParameterPanel.tsx

31
src/components/nodes/BaseNode.tsx

@ -75,6 +75,7 @@ export function BaseNode({
const { getNodes, setNodes } = useReactFlow();
const settingsPanelRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const trackedSettingsHeightRef = useRef(0);
// Adjust node height when settings collapse
@ -82,6 +83,13 @@ export function BaseNode({
if (!settingsExpanded && trackedSettingsHeightRef.current > 0) {
const heightToRemove = trackedSettingsHeightRef.current;
trackedSettingsHeightRef.current = 0;
// Lock content height to prevent image flicker during resize
const contentEl = contentRef.current;
if (contentEl) {
contentEl.style.height = contentEl.offsetHeight + "px";
}
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== id) return node;
@ -90,6 +98,13 @@ export function BaseNode({
return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight);
})
);
// Release locked height after layout settles
requestAnimationFrame(() => {
if (contentEl) {
contentEl.style.height = "";
}
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [settingsExpanded]);
@ -108,6 +123,13 @@ export function BaseNode({
if (Math.abs(delta) < 2) continue; // Ignore sub-pixel changes
trackedSettingsHeightRef.current = newPanelHeight;
// Lock content height to prevent image flicker during resize
const contentEl = contentRef.current;
if (contentEl) {
contentEl.style.height = contentEl.offsetHeight + "px";
}
setNodes((nodes) =>
nodes.map((node) => {
if (node.id !== id) return node;
@ -116,6 +138,13 @@ export function BaseNode({
return applyNodeDimensions(node, getNodeDimension(node, "width"), newHeight);
})
);
// Release locked height after layout settles
requestAnimationFrame(() => {
if (contentEl) {
contentEl.style.height = "";
}
});
}
});
@ -214,7 +243,7 @@ export function BaseNode({
setHoveredNodeId(null);
}}
>
<div className={contentClassName ?? (fullBleed ? "flex-1 min-h-0 relative" : "px-3 pb-4 flex-1 min-h-0 overflow-hidden flex flex-col")}>{children}</div>
<div ref={contentRef} className={contentClassName ?? (fullBleed ? "flex-1 min-h-0 relative" : "px-3 pb-4 flex-1 min-h-0 overflow-hidden flex flex-col")}>{children}</div>
</div>
{settingsPanel && (
<div ref={settingsPanelRef}>

28
src/components/nodes/InlineParameterPanel.tsx

@ -1,6 +1,6 @@
"use client";
import React, { ReactNode } from "react";
import React, { ReactNode, useRef, useState, useEffect } from "react";
interface InlineParameterPanelProps {
expanded: boolean;
@ -11,7 +11,7 @@ interface InlineParameterPanelProps {
/**
* Collapsible parameter container for inline display within generation nodes.
* Provides a chevron toggle button and instant expand/collapse.
* Provides a chevron toggle button and smooth animated expand/collapse.
*/
function InlineParameterPanelInner({
expanded,
@ -19,6 +19,15 @@ function InlineParameterPanelInner({
children,
nodeId,
}: InlineParameterPanelProps) {
const contentRef = useRef<HTMLDivElement>(null);
const [contentHeight, setContentHeight] = useState(0);
useEffect(() => {
if (expanded && contentRef.current) {
setContentHeight(contentRef.current.scrollHeight);
}
}, [expanded, children]);
return (
<>
{/* Settings toggle button — no background when collapsed, floats below node edge */}
@ -45,15 +54,16 @@ function InlineParameterPanelInner({
</svg>
</button>
{/* Content area — instant show/hide */}
{expanded && (
<div
id={`params-${nodeId}`}
className="nodrag nopan nowheel bg-[#2a2a2a] px-3 pt-2 pb-3 rounded-b-lg"
>
{/* Content area — smooth height animation */}
<div
id={`params-${nodeId}`}
className="nodrag nopan nowheel bg-[#2a2a2a] overflow-hidden transition-[max-height] duration-150 ease-out"
style={{ maxHeight: expanded ? contentHeight : 0 }}
>
<div ref={contentRef} className="px-3 pt-2 pb-3 rounded-b-lg">
{children}
</div>
)}
</div>
</>
);
}

Loading…
Cancel
Save