feat: animate expandable tool details

Add smooth expand and collapse transitions to tool details
Match the existing thinking block animation style
Keep existing default expanded states unchanged
This commit is contained in:
Bohdan Triapitsyn
2026-05-17 23:55:22 +03:00
parent 5c1333a139
commit 9cf0fe0551
@@ -187,26 +187,28 @@ const LiveDuration: React.FC<{ start: number; end?: number; active: boolean }> =
return <>{formatDuration(start, end, now)}</>; return <>{formatDuration(start, end, now)}</>;
}; };
const useDeferredExpandedContent = (isExpanded: boolean) => { const EXPANDED_CONTENT_TRANSITION_MS = 350;
const [shouldRender, setShouldRender] = React.useState(false);
const useAnimatedExpandedContent = (isExpanded: boolean) => {
const [shouldRender, setShouldRender] = React.useState(isExpanded);
React.useEffect(() => { React.useEffect(() => {
if (!isExpanded) {
setShouldRender(false);
return;
}
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
setShouldRender(isExpanded);
return;
}
if (isExpanded) {
setShouldRender(true); setShouldRender(true);
return; return;
} }
const frame = window.requestAnimationFrame(() => { const timer = window.setTimeout(() => {
setShouldRender(true); setShouldRender(false);
}); }, EXPANDED_CONTENT_TRANSITION_MS);
return () => { return () => {
window.cancelAnimationFrame(frame); window.clearTimeout(timer);
}; };
}, [isExpanded]); }, [isExpanded]);
@@ -2528,7 +2530,7 @@ const ToolPart: React.FC<ToolPartProps> = ({
const iconStyle = !isTaskTool && isError ? TOOL_ERROR_ICON_STYLE : TOOL_NORMAL_ICON_STYLE; const iconStyle = !isTaskTool && isError ? TOOL_ERROR_ICON_STYLE : TOOL_NORMAL_ICON_STYLE;
const titleStyle = !isTaskTool && isError ? TOOL_ERROR_TITLE_STYLE : TOOL_NORMAL_TITLE_STYLE; const titleStyle = !isTaskTool && isError ? TOOL_ERROR_TITLE_STYLE : TOOL_NORMAL_TITLE_STYLE;
const shouldRenderExpandedContent = useDeferredExpandedContent(isExpanded); const shouldRenderExpandedContent = useAnimatedExpandedContent(isExpanded);
if (!shouldTreatAsFinalized && !isActive && !isTaskTool) { if (!shouldTreatAsFinalized && !isActive && !isTaskTool) {
return null; return null;
@@ -2672,20 +2674,32 @@ const ToolPart: React.FC<ToolPartProps> = ({
/> />
) : null} ) : null}
{!isTaskTool && shouldRenderExpandedContent ? ( {!isTaskTool ? (
<div className="relative ml-2 pl-3"> <div
<span aria-hidden={!isExpanded}
aria-hidden="true" className={cn(
className="pointer-events-none absolute left-0 top-px bottom-0 w-px" 'grid transition-[grid-template-rows,opacity] duration-[350ms] ease-out motion-reduce:transition-none',
style={{ backgroundColor: 'var(--tools-border)' }} isExpanded ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0',
/> )}
<ToolExpandedContent >
part={part} <div className="min-h-0 overflow-hidden">
state={state} {shouldRenderExpandedContent ? (
syntaxTheme={syntaxTheme} <div className="relative ml-2 pl-3">
currentDirectory={currentDirectory} <span
onShowPopup={onShowPopup} aria-hidden="true"
/> className="pointer-events-none absolute left-0 top-px bottom-0 w-px"
style={{ backgroundColor: 'var(--tools-border)' }}
/>
<ToolExpandedContent
part={part}
state={state}
syntaxTheme={syntaxTheme}
currentDirectory={currentDirectory}
onShowPopup={onShowPopup}
/>
</div>
) : null}
</div>
</div> </div>
) : null} ) : null}
</div> </div>