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;
|
|
|
|
|
|
2026-01-25 19:16:26 +02:00
|
|
|
// 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 }) => {
|
2026-01-25 19:16:26 +02:00
|
|
|
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(() => {
|
2026-01-25 19:16:26 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
};
|
2026-01-25 19:16:26 +02:00
|
|
|
}, [shouldSkip]);
|
2025-12-07 19:32:53 +02:00
|
|
|
|
2026-01-25 19:16:26 +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>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|