165 lines
4.0 KiB
TypeScript

import React, { useEffect, useState, useRef } from 'react';
import { SxProps } from '@mui/material';
import Box from '@mui/material/Box';
interface PulseProps {
timestamp: number | string;
sx?: SxProps;
}
const Pulse: React.FC<PulseProps> = ({ timestamp, sx }) => {
const [isAnimating, setIsAnimating] = useState(false);
const [animationKey, setAnimationKey] = useState(0);
const previousTimestamp = useRef<number | string | null>(null);
useEffect(() => {
if (timestamp && timestamp !== previousTimestamp.current) {
previousTimestamp.current = timestamp;
setAnimationKey(prev => prev + 1);
setIsAnimating(true);
// Reset animation state after animation completes
const timer = setTimeout(() => {
setIsAnimating(false);
}, 1000);
return () => clearTimeout(timer);
}
}, [timestamp]);
const containerStyle: React.CSSProperties = {
position: 'relative',
width: 80,
height: 80,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
};
const baseCoreStyle: React.CSSProperties = {
width: 0,
height: 0,
borderRadius: '50%',
backgroundColor: '#2196f3',
position: 'relative',
zIndex: 3,
};
const coreStyle: React.CSSProperties = {
...baseCoreStyle,
animation: isAnimating ? 'pulse-glow 1s ease-out' : 'none',
};
const pulseRing1Style: React.CSSProperties = {
position: 'absolute',
width: 24,
height: 24,
borderRadius: '50%',
backgroundColor: '#2196f3',
zIndex: 2,
animation: 'pulse-expand 1s ease-out forwards',
};
const pulseRing2Style: React.CSSProperties = {
position: 'absolute',
width: 24,
height: 24,
borderRadius: '50%',
backgroundColor: '#64b5f6',
zIndex: 1,
animation: 'pulse-expand 1s ease-out 0.2s forwards',
};
const rippleStyle: React.CSSProperties = {
position: 'absolute',
width: 32,
height: 32,
borderRadius: '50%',
border: '2px solid #2196f3',
backgroundColor: 'transparent',
zIndex: 0,
animation: 'ripple-expand 1s ease-out forwards',
};
const outerRippleStyle: React.CSSProperties = {
position: 'absolute',
width: 40,
height: 40,
borderRadius: '50%',
border: '1px solid #90caf9',
backgroundColor: 'transparent',
zIndex: 0,
animation: 'ripple-expand 1s ease-out 0.3s forwards',
};
return (
<>
<style>
{`
@keyframes pulse-expand {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.3);
opacity: 0.7;
}
100% {
transform: scale(1.6);
opacity: 0;
}
}
@keyframes ripple-expand {
0% {
transform: scale(0.8);
opacity: 0.8;
}
100% {
transform: scale(2);
opacity: 0;
}
}
@keyframes pulse-glow {
0% {
box-shadow: 0 0 5px #2196f3, 0 0 10px #2196f3, 0 0 15px #2196f3;
}
50% {
box-shadow: 0 0 10px #2196f3, 0 0 20px #2196f3, 0 0 30px #2196f3;
}
100% {
box-shadow: 0 0 5px #2196f3, 0 0 10px #2196f3, 0 0 15px #2196f3;
}
}
`}
</style>
<Box sx={{ ...containerStyle, ...sx }}>
{/* Base circle */}
<div style={coreStyle} />
{/* Pulse rings */}
{isAnimating && (
<>
{/* Primary pulse ring */}
<div key={`pulse-1-${animationKey}`} style={pulseRing1Style} />
{/* Secondary pulse ring with delay */}
<div key={`pulse-2-${animationKey}`} style={pulseRing2Style} />
{/* Ripple effect */}
<div key={`ripple-${animationKey}`} style={rippleStyle} />
{/* Outer ripple */}
<div key={`ripple-outer-${animationKey}`} style={outerRippleStyle} />
</>
)}
</Box>
</>
);
};
export { Pulse };