Files
openchamber/packages/ui/src/components/chat/message/FadeInOnReveal.tsx
T

70 lines
2.0 KiB
TypeScript
Raw Normal View History

2025-12-07 19:32:53 +02:00
import React from 'react';
import { cn } from '@/lib/utils';
interface FadeInOnRevealProps {
children: React.ReactNode;
className?: string;
2026-01-20 03:23:39 +02:00
skipAnimation?: boolean;
2025-12-07 19:32:53 +02:00
}
const FADE_ANIMATION_ENABLED = true;
// Context to allow parent components (like VirtualMessageList) to disable animations
// for items entering the viewport due to scrolling rather than new content
const FadeInDisabledContext = React.createContext(false);
export const FadeInDisabledProvider: React.FC<{ disabled: boolean; children: React.ReactNode }> = ({ disabled, children }) => (
<FadeInDisabledContext.Provider value={disabled}>
{children}
</FadeInDisabledContext.Provider>
);
2026-01-20 03:23:39 +02:00
export const FadeInOnReveal: React.FC<FadeInOnRevealProps> = ({ children, className, skipAnimation }) => {
const contextDisabled = React.useContext(FadeInDisabledContext);
const shouldSkip = skipAnimation || contextDisabled;
const [visible, setVisible] = React.useState(shouldSkip);
2025-12-07 19:32:53 +02:00
React.useEffect(() => {
if (!FADE_ANIMATION_ENABLED || shouldSkip) {
2025-12-07 19:32:53 +02:00
return;
}
let frame: number | null = null;
const enable = () => setVisible(true);
if (typeof window !== 'undefined' && typeof window.requestAnimationFrame === 'function') {
frame = window.requestAnimationFrame(enable);
} else {
enable();
}
return () => {
if (
frame !== null &&
typeof window !== 'undefined' &&
typeof window.cancelAnimationFrame === 'function'
) {
window.cancelAnimationFrame(frame);
}
};
}, [shouldSkip]);
2025-12-07 19:32:53 +02:00
if (!FADE_ANIMATION_ENABLED || shouldSkip) {
2025-12-07 19:32:53 +02:00
return <>{children}</>;
}
return (
<div
className={cn(
'w-full transition-all duration-300 ease-out',
visible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2',
className
)}
>
{children}
</div>
);
};