feat: Add GSAP animated counters and fix Team Schedule theme issues
- Created reusable AnimatedNumber component with GSAP - Integrated animated counters in Dashboard (metrics, weather) - Integrated animated counters in Leaderboard (podium, table) - Fixed AdminSchedule dark mode styling with theme-aware classes - Added responsive columns and functional dropdown to Team Schedule - Implemented date range and status filters for Team Schedule - Improved animation smoothness with expo.out easing
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { gsap } from 'gsap';
|
||||
|
||||
/**
|
||||
* AnimatedNumber - A reusable component that animates numbers from 0 to their target value using GSAP
|
||||
* @param {number|string} value - The target value to animate to
|
||||
* @param {number} duration - Animation duration in seconds (default: 2)
|
||||
* @param {string} prefix - Optional prefix (e.g., "$")
|
||||
* @param {string} suffix - Optional suffix (e.g., "%", "K", "M")
|
||||
* @param {number} decimals - Number of decimal places (default: 0)
|
||||
* @param {boolean} useLocaleString - Whether to format with commas (default: false)
|
||||
* @param {string} className - Additional CSS classes
|
||||
*/
|
||||
const AnimatedNumber = ({
|
||||
value,
|
||||
duration = 2,
|
||||
prefix = '',
|
||||
suffix = '',
|
||||
decimals = 0,
|
||||
useLocaleString = false,
|
||||
className = '',
|
||||
delay = 0
|
||||
}) => {
|
||||
const numberRef = useRef(null);
|
||||
const animationRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!numberRef.current) return;
|
||||
|
||||
// Extract numeric value if it's a string like "$4.2M"
|
||||
let numericValue = value;
|
||||
let extractedPrefix = prefix;
|
||||
let extractedSuffix = suffix;
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// Check for currency symbols
|
||||
if (value.startsWith('$')) {
|
||||
extractedPrefix = '$';
|
||||
value = value.substring(1);
|
||||
}
|
||||
|
||||
// Check for suffixes like M, K, B
|
||||
const lastChar = value.slice(-1);
|
||||
if (['K', 'M', 'B'].includes(lastChar.toUpperCase())) {
|
||||
extractedSuffix = lastChar.toUpperCase();
|
||||
value = value.slice(0, -1);
|
||||
}
|
||||
|
||||
// Parse the numeric part
|
||||
numericValue = parseFloat(value.replace(/,/g, ''));
|
||||
}
|
||||
|
||||
// Handle non-numeric values
|
||||
if (isNaN(numericValue)) {
|
||||
numberRef.current.textContent = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert suffix multipliers to actual numbers
|
||||
let multiplier = 1;
|
||||
if (extractedSuffix === 'K') multiplier = 1000;
|
||||
if (extractedSuffix === 'M') multiplier = 1000000;
|
||||
if (extractedSuffix === 'B') multiplier = 1000000000;
|
||||
|
||||
const targetValue = numericValue * multiplier;
|
||||
|
||||
// Animate from 0 to target value
|
||||
const obj = { val: 0 };
|
||||
|
||||
animationRef.current = gsap.to(obj, {
|
||||
val: numericValue,
|
||||
duration: duration,
|
||||
delay: delay,
|
||||
ease: 'expo.out',
|
||||
onUpdate: () => {
|
||||
if (numberRef.current) {
|
||||
let displayValue = obj.val.toFixed(decimals);
|
||||
|
||||
if (useLocaleString) {
|
||||
displayValue = parseFloat(displayValue).toLocaleString(undefined, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals
|
||||
});
|
||||
}
|
||||
|
||||
numberRef.current.textContent = `${extractedPrefix}${displayValue}${extractedSuffix}`;
|
||||
}
|
||||
},
|
||||
onComplete: () => {
|
||||
// Ensure final value is exact
|
||||
if (numberRef.current) {
|
||||
let displayValue = numericValue.toFixed(decimals);
|
||||
|
||||
if (useLocaleString) {
|
||||
displayValue = parseFloat(displayValue).toLocaleString(undefined, {
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals
|
||||
});
|
||||
}
|
||||
|
||||
numberRef.current.textContent = `${extractedPrefix}${displayValue}${extractedSuffix}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
animationRef.current.kill();
|
||||
}
|
||||
};
|
||||
}, [value, duration, prefix, suffix, decimals, useLocaleString, delay]);
|
||||
|
||||
return <span ref={numberRef} className={className}>0</span>;
|
||||
};
|
||||
|
||||
export default AnimatedNumber;
|
||||
Reference in New Issue
Block a user