fix: smooth tool expansion animation
This commit is contained in:
@@ -34,8 +34,8 @@ const cleanReasoningText = (text: string): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SUMMARY_MAX_CHARS = 80;
|
const SUMMARY_MAX_CHARS = 80;
|
||||||
const EXPANDED_CONTENT_UNMOUNT_DELAY_MS = 350;
|
const EXPANDED_CONTENT_UNMOUNT_DELAY_MS = 200;
|
||||||
const EXPANDED_CONTENT_SPRING = { type: 'spring' as const, visualDuration: 0.35, bounce: 0 };
|
const EXPANDED_CONTENT_TRANSITION = { duration: 0.2, ease: 'easeOut' as const };
|
||||||
|
|
||||||
/** Strip common markdown syntax so the header preview reads as plain text. */
|
/** Strip common markdown syntax so the header preview reads as plain text. */
|
||||||
const stripMarkdown = (text: string): string =>
|
const stripMarkdown = (text: string): string =>
|
||||||
@@ -202,19 +202,17 @@ export const ReasoningTimelineBlock: React.FC<ReasoningTimelineBlockProps> = ({
|
|||||||
contentMountedRef.current = true;
|
contentMountedRef.current = true;
|
||||||
if (!isExpanded) {
|
if (!isExpanded) {
|
||||||
element.style.height = '0px';
|
element.style.height = '0px';
|
||||||
element.style.opacity = '0';
|
|
||||||
element.style.overflow = 'hidden';
|
element.style.overflow = 'hidden';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
element.style.height = '0px';
|
element.style.height = '0px';
|
||||||
element.style.opacity = '0';
|
|
||||||
element.style.overflow = 'hidden';
|
element.style.overflow = 'hidden';
|
||||||
|
|
||||||
const animation = animate(
|
const animation = animate(
|
||||||
element,
|
element,
|
||||||
{ height: 'auto', opacity: 1 },
|
{ height: 'auto' },
|
||||||
EXPANDED_CONTENT_SPRING,
|
EXPANDED_CONTENT_TRANSITION,
|
||||||
);
|
);
|
||||||
contentAnimationRef.current = animation;
|
contentAnimationRef.current = animation;
|
||||||
|
|
||||||
@@ -239,16 +237,14 @@ export const ReasoningTimelineBlock: React.FC<ReasoningTimelineBlockProps> = ({
|
|||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
element.style.height = '0px';
|
element.style.height = '0px';
|
||||||
element.style.opacity = '0';
|
|
||||||
} else {
|
} else {
|
||||||
element.style.height = `${element.scrollHeight}px`;
|
element.style.height = `${element.scrollHeight}px`;
|
||||||
element.style.opacity = '1';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const animation = animate(
|
const animation = animate(
|
||||||
element,
|
element,
|
||||||
{ height: isExpanded ? 'auto' : '0px', opacity: isExpanded ? 1 : 0 },
|
{ height: isExpanded ? 'auto' : '0px' },
|
||||||
EXPANDED_CONTENT_SPRING,
|
EXPANDED_CONTENT_TRANSITION,
|
||||||
);
|
);
|
||||||
contentAnimationRef.current = animation;
|
contentAnimationRef.current = animation;
|
||||||
|
|
||||||
@@ -366,12 +362,18 @@ export const ReasoningTimelineBlock: React.FC<ReasoningTimelineBlockProps> = ({
|
|||||||
aria-hidden={!isExpanded}
|
aria-hidden={!isExpanded}
|
||||||
style={{
|
style={{
|
||||||
height: isExpanded ? 'auto' : '0px',
|
height: isExpanded ? 'auto' : '0px',
|
||||||
opacity: isExpanded ? 1 : 0,
|
|
||||||
overflow: isExpanded ? 'visible' : 'hidden',
|
overflow: isExpanded ? 'visible' : 'hidden',
|
||||||
overflowAnchor: 'none',
|
overflowAnchor: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="relative ml-2 pl-3 pb-1 pt-0.5">
|
<div
|
||||||
|
className="relative ml-2 pl-3 pb-1 pt-0.5"
|
||||||
|
style={{
|
||||||
|
opacity: isExpanded ? 1 : 0,
|
||||||
|
transform: isExpanded ? 'translateY(0)' : 'translateY(-4px)',
|
||||||
|
transition: 'opacity 180ms ease-out, transform 180ms ease-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute left-0 top-0 bottom-0 w-px"
|
className="pointer-events-none absolute left-0 top-0 bottom-0 w-px"
|
||||||
|
|||||||
@@ -197,8 +197,8 @@ const LiveDuration: React.FC<{ start: number; end?: number; active: boolean }> =
|
|||||||
return <>{formatDuration(start, end, now)}</>;
|
return <>{formatDuration(start, end, now)}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const EXPANDED_CONTENT_TRANSITION_MS = 350;
|
const EXPANDED_CONTENT_TRANSITION_MS = 200;
|
||||||
const EXPANDED_CONTENT_SPRING = { type: 'spring' as const, visualDuration: 0.35, bounce: 0 };
|
const EXPANDED_CONTENT_TRANSITION = { duration: 0.2, ease: 'easeOut' as const };
|
||||||
|
|
||||||
const useAnimatedExpandedContent = (isExpanded: boolean) => {
|
const useAnimatedExpandedContent = (isExpanded: boolean) => {
|
||||||
const [shouldRender, setShouldRender] = React.useState(isExpanded);
|
const [shouldRender, setShouldRender] = React.useState(isExpanded);
|
||||||
@@ -1876,7 +1876,6 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
|||||||
if (!expandedContentMountedRef.current) {
|
if (!expandedContentMountedRef.current) {
|
||||||
expandedContentMountedRef.current = true;
|
expandedContentMountedRef.current = true;
|
||||||
element.style.height = isExpanded ? 'auto' : '0px';
|
element.style.height = isExpanded ? 'auto' : '0px';
|
||||||
element.style.opacity = isExpanded ? '1' : '0';
|
|
||||||
element.style.overflow = isExpanded ? 'visible' : 'hidden';
|
element.style.overflow = isExpanded ? 'visible' : 'hidden';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1885,16 +1884,14 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
|||||||
|
|
||||||
if (isExpanded) {
|
if (isExpanded) {
|
||||||
element.style.height = '0px';
|
element.style.height = '0px';
|
||||||
element.style.opacity = '0';
|
|
||||||
} else {
|
} else {
|
||||||
element.style.height = `${element.scrollHeight}px`;
|
element.style.height = `${element.scrollHeight}px`;
|
||||||
element.style.opacity = '1';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const animation = animate(
|
const animation = animate(
|
||||||
element,
|
element,
|
||||||
{ height: isExpanded ? 'auto' : '0px', opacity: isExpanded ? 1 : 0 },
|
{ height: isExpanded ? 'auto' : '0px' },
|
||||||
EXPANDED_CONTENT_SPRING,
|
EXPANDED_CONTENT_TRANSITION,
|
||||||
);
|
);
|
||||||
expandedContentAnimationRef.current = animation;
|
expandedContentAnimationRef.current = animation;
|
||||||
|
|
||||||
@@ -2725,13 +2722,19 @@ const ToolPartContent: React.FC<ToolPartProps> = ({
|
|||||||
aria-hidden={!isExpanded}
|
aria-hidden={!isExpanded}
|
||||||
style={{
|
style={{
|
||||||
height: isExpanded ? 'auto' : '0px',
|
height: isExpanded ? 'auto' : '0px',
|
||||||
opacity: isExpanded ? 1 : 0,
|
|
||||||
overflow: isExpanded ? 'visible' : 'hidden',
|
overflow: isExpanded ? 'visible' : 'hidden',
|
||||||
overflowAnchor: 'none',
|
overflowAnchor: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{shouldRenderExpandedContent ? (
|
{shouldRenderExpandedContent ? (
|
||||||
<div className="relative ml-2 pl-3">
|
<div
|
||||||
|
className="relative ml-2 pl-3"
|
||||||
|
style={{
|
||||||
|
opacity: isExpanded ? 1 : 0,
|
||||||
|
transform: isExpanded ? 'translateY(0)' : 'translateY(-4px)',
|
||||||
|
transition: 'opacity 180ms ease-out, transform 180ms ease-out',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute left-0 top-px bottom-0 w-px"
|
className="pointer-events-none absolute left-0 top-px bottom-0 w-px"
|
||||||
|
|||||||
Reference in New Issue
Block a user