Import source: src/components/StatsBar
Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { useInView } from 'framer-motion';
|
||||
import { useLocale, useTranslations } from 'next-intl';
|
||||
|
||||
type CountUpProps = {
|
||||
end: number;
|
||||
duration?: number;
|
||||
};
|
||||
|
||||
function CountUp({ end, duration = 1800 }: CountUpProps) {
|
||||
const [count, setCount] = useState(0);
|
||||
const ref = useRef(null);
|
||||
const isInView = useInView(ref, { once: true, margin: '-50px' });
|
||||
const locale = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInView) return;
|
||||
|
||||
let startTime: number | null = null;
|
||||
const startValue = 0;
|
||||
|
||||
const animate = (timestamp: number) => {
|
||||
if (!startTime) startTime = timestamp;
|
||||
const elapsed = timestamp - startTime;
|
||||
const progress = Math.min(elapsed / duration, 1);
|
||||
|
||||
// Easing function: easeOutQuad
|
||||
const easeProgress = progress * (2 - progress);
|
||||
|
||||
const current = Math.floor(easeProgress * (end - startValue) + startValue);
|
||||
setCount(current);
|
||||
|
||||
if (progress < 1) {
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
setCount(end);
|
||||
}
|
||||
};
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
}, [isInView, end, duration]);
|
||||
|
||||
return <span ref={ref}>{count.toLocaleString(locale === 'fa' ? 'fa-IR' : 'en-US')}</span>;
|
||||
}
|
||||
|
||||
export default function StatsBar() {
|
||||
const t = useTranslations('Stats');
|
||||
|
||||
const stats = [
|
||||
{ value: 28, key: 'articles' },
|
||||
{ value: 118280, key: 'views' },
|
||||
{ value: 4789, key: 'reactions' },
|
||||
];
|
||||
|
||||
return (
|
||||
<section className="w-full bg-sand/20 dark:bg-charcoal/20 border-y border-sand/40 dark:border-charcoal/40 py-8 transition-colors duration-300">
|
||||
<div className="max-w-[1200px] mx-auto px-6 grid grid-cols-1 md:grid-cols-3 gap-8 text-center">
|
||||
{stats.map((stat, idx) => (
|
||||
<div
|
||||
key={stat.key}
|
||||
className={`flex flex-col items-center justify-center ${
|
||||
idx !== stats.length - 1 ? 'md:border-r border-sand/50 dark:border-charcoal/50 rtl:md:border-r-0 rtl:md:border-l' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="text-3xl md:text-5xl font-black text-teal-brand dark:text-teal-brand tracking-tight mb-2">
|
||||
<CountUp end={stat.value} />
|
||||
</div>
|
||||
<div className="text-xs uppercase tracking-widest text-charcoal/60 dark:text-warm-white/50 font-semibold">
|
||||
{t(stat.key)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user