All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 27s
Build & Deploy / 🚀 Deploy (push) Successful in 34s
Build & Deploy / 🧪 QA (push) Successful in 1m46s
Build & Deploy / 🏗️ Build (push) Successful in 3m38s
Build & Deploy / 🧪 Post-Deploy Verification (push) Successful in 1m19s
Build & Deploy / 🔔 Notify (push) Successful in 5s
49 lines
1.1 KiB
TypeScript
49 lines
1.1 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { useMotionValue, useTransform, animate, useInView, m } from 'framer-motion';
|
|
|
|
interface AnimatedCounterProps {
|
|
value: number;
|
|
duration?: number;
|
|
delay?: number;
|
|
suffix?: string;
|
|
prefix?: string;
|
|
className?: string;
|
|
}
|
|
|
|
export function AnimatedCounter({
|
|
value,
|
|
duration = 2,
|
|
delay = 0,
|
|
suffix = '',
|
|
prefix = '',
|
|
className = '',
|
|
}: AnimatedCounterProps) {
|
|
const count = useMotionValue(0);
|
|
const rounded = useTransform(count, (latest) => {
|
|
// Format with thousands separator
|
|
return new Intl.NumberFormat('de-DE').format(Math.round(latest));
|
|
});
|
|
|
|
const ref = React.useRef(null);
|
|
const inView = useInView(ref, { once: true, margin: "-100px" });
|
|
|
|
useEffect(() => {
|
|
if (inView) {
|
|
const controls = animate(count, value, {
|
|
duration: duration,
|
|
delay: delay,
|
|
ease: 'easeOut',
|
|
});
|
|
return controls.stop;
|
|
}
|
|
}, [inView, value, duration, delay, count]);
|
|
|
|
return (
|
|
<m.span ref={ref} className={className}>
|
|
{prefix}<m.span>{rounded}</m.span>{suffix}
|
|
</m.span>
|
|
);
|
|
}
|