mirror of https://github.com/buster-so/buster.git
update animation lifecycle
This commit is contained in:
parent
d437535d50
commit
a2ae1b8b38
|
@ -0,0 +1,27 @@
|
||||||
|
import React, { createContext, useContext } from 'react';
|
||||||
|
|
||||||
|
interface AnimationContextValue {
|
||||||
|
shouldStopAnimations: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimationContext = createContext<AnimationContextValue | undefined>(undefined);
|
||||||
|
|
||||||
|
export const AnimationProvider: React.FC<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
shouldStopAnimations: boolean;
|
||||||
|
}> = ({ children, shouldStopAnimations }) => {
|
||||||
|
return (
|
||||||
|
<AnimationContext.Provider value={{ shouldStopAnimations }}>
|
||||||
|
{children}
|
||||||
|
</AnimationContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAnimationContext = () => {
|
||||||
|
const context = useContext(AnimationContext);
|
||||||
|
if (!context) {
|
||||||
|
// Return default value if context is not provided
|
||||||
|
return { shouldStopAnimations: false };
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
|
@ -4,15 +4,7 @@ import { animateTokenizedText, createAnimationStyle } from './animation-helpers'
|
||||||
import { cva } from 'class-variance-authority';
|
import { cva } from 'class-variance-authority';
|
||||||
import { StreamingMessageCode } from '../../../streaming/StreamingMessageCode';
|
import { StreamingMessageCode } from '../../../streaming/StreamingMessageCode';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
|
import { useAnimationContext, AnimationProvider } from './AnimationContext';
|
||||||
// Create a context to track list item state
|
|
||||||
const ListItemContext = React.createContext<{
|
|
||||||
isInListItem: boolean;
|
|
||||||
initialHadParagraph: boolean | null;
|
|
||||||
}>({
|
|
||||||
isInListItem: false,
|
|
||||||
initialHadParagraph: null
|
|
||||||
});
|
|
||||||
|
|
||||||
type MarkdownComponentProps = {
|
type MarkdownComponentProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -35,17 +27,14 @@ export const ParagraphComponent: React.FC<MarkdownComponentProps> = ({
|
||||||
style,
|
style,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
const listItemContext = React.useContext(ListItemContext);
|
const { shouldStopAnimations } = useAnimationContext();
|
||||||
|
|
||||||
// If we're in a list item that started without paragraphs, don't render the p tag
|
|
||||||
if (listItemContext.isInListItem && listItemContext.initialHadParagraph === false) {
|
|
||||||
// Return children directly without the p tag wrapper
|
|
||||||
return <>{animateTokenizedText(children, rest)}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p style={style} className={className} data-testid="paragraph-component">
|
<p style={style} className={className} data-testid="paragraph-component">
|
||||||
{animateTokenizedText(children, rest)}
|
{animateTokenizedText(children, {
|
||||||
|
...rest,
|
||||||
|
animation: shouldStopAnimations ? 'none' : rest.animation
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -263,12 +252,23 @@ export const ListItemComponent: React.FC<MarkdownComponentProps> = ({
|
||||||
style,
|
style,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
const numberOfChildren = React.Children.count(children);
|
||||||
|
const stopAnimations = React.useRef(false);
|
||||||
|
const previousChildrenLength = React.useRef(numberOfChildren);
|
||||||
|
|
||||||
|
const numberOfChildrenIsLessThanPrevious = numberOfChildren < previousChildrenLength.current;
|
||||||
|
previousChildrenLength.current = numberOfChildren;
|
||||||
|
|
||||||
|
if (numberOfChildrenIsLessThanPrevious) {
|
||||||
|
stopAnimations.current = true;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<AnimationProvider shouldStopAnimations={stopAnimations.current}>
|
||||||
<li
|
<li
|
||||||
style={style}
|
style={style}
|
||||||
className={cn(
|
className={cn(
|
||||||
className,
|
className,
|
||||||
|
|
||||||
'[&_span]:inline',
|
'[&_span]:inline',
|
||||||
// // Normal text flow
|
// // Normal text flow
|
||||||
'whitespace-normal',
|
'whitespace-normal',
|
||||||
|
@ -276,8 +276,12 @@ export const ListItemComponent: React.FC<MarkdownComponentProps> = ({
|
||||||
'[&>span]:inline [&>span]:align-top',
|
'[&>span]:inline [&>span]:align-top',
|
||||||
'[&>p]:inline [&>p]:align-top'
|
'[&>p]:inline [&>p]:align-top'
|
||||||
)}>
|
)}>
|
||||||
{animateTokenizedText(children, rest)}
|
{animateTokenizedText(children, {
|
||||||
|
...rest,
|
||||||
|
animation: stopAnimations.current ? 'none' : rest.animation
|
||||||
|
})}
|
||||||
</li>
|
</li>
|
||||||
|
</AnimationProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ const TokenizedText: React.FC<TokenizedTextProps> = React.memo(
|
||||||
{chunk}
|
{chunk}
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
[animationStyle]
|
[animationStyle, doNotAnimateInitialText]
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = useMemo(() => {
|
const content = useMemo(() => {
|
||||||
|
|
|
@ -16,7 +16,8 @@ export type MarkdownAnimation =
|
||||||
| 'typewriter'
|
| 'typewriter'
|
||||||
| 'highlight'
|
| 'highlight'
|
||||||
| 'blurAndSharpen'
|
| 'blurAndSharpen'
|
||||||
| 'dropIn';
|
| 'dropIn'
|
||||||
|
| 'none';
|
||||||
|
|
||||||
export type MarkdownAnimationTimingFunction = 'ease-in-out' | 'ease-out' | 'ease-in' | 'linear';
|
export type MarkdownAnimationTimingFunction = 'ease-in-out' | 'ease-out' | 'ease-in' | 'linear';
|
||||||
|
|
||||||
|
@ -34,7 +35,8 @@ export const animations: Record<MarkdownAnimation, string> = {
|
||||||
typewriter: 'buster-typewriter',
|
typewriter: 'buster-typewriter',
|
||||||
highlight: 'buster-highlight',
|
highlight: 'buster-highlight',
|
||||||
blurAndSharpen: 'buster-blurAndSharpen',
|
blurAndSharpen: 'buster-blurAndSharpen',
|
||||||
dropIn: 'buster-dropIn'
|
dropIn: 'buster-dropIn',
|
||||||
|
none: 'none'
|
||||||
};
|
};
|
||||||
|
|
||||||
interface AnimationStyleProps {
|
interface AnimationStyleProps {
|
||||||
|
@ -50,11 +52,12 @@ export const createAnimationStyle = ({
|
||||||
animationTimingFunction = 'ease-in-out',
|
animationTimingFunction = 'ease-in-out',
|
||||||
isStreamFinished = true
|
isStreamFinished = true
|
||||||
}: AnimationStyleProps) => {
|
}: AnimationStyleProps) => {
|
||||||
|
if (animation === 'none' || isStreamFinished) {
|
||||||
|
return { animation: 'none' };
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
animation:
|
animation: `${animations[animation]} ${animationDuration}ms ${animationTimingFunction}`
|
||||||
animation && !isStreamFinished
|
|
||||||
? `${animations[animation]} ${animationDuration}ms ${animationTimingFunction}`
|
|
||||||
: 'none'
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -115,7 +118,6 @@ export const animateTokenizedText = (
|
||||||
data-testid="other-markdown-element"
|
data-testid="other-markdown-element"
|
||||||
style={{
|
style={{
|
||||||
...createAnimationStyle(animationsProps),
|
...createAnimationStyle(animationsProps),
|
||||||
animationIterationCount: 1,
|
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
display: isInlineElement ? 'inline' : 'inline-block'
|
display: isInlineElement ? 'inline' : 'inline-block'
|
||||||
}}>
|
}}>
|
||||||
|
@ -129,7 +131,6 @@ export const animateTokenizedText = (
|
||||||
data-testid="animated-markdown-element"
|
data-testid="animated-markdown-element"
|
||||||
style={{
|
style={{
|
||||||
...createAnimationStyle(animationsProps),
|
...createAnimationStyle(animationsProps),
|
||||||
animationIterationCount: 1,
|
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
display: 'inline'
|
display: 'inline'
|
||||||
}}>
|
}}>
|
||||||
|
|
Loading…
Reference in New Issue