Files
Epicnabbo-Catalogus-Updated…/src/components/animated-counter.tsx
T
openhands 17847545dd Improvements: remove dead config, fix ESM, add URL validation, unify types, add missing logging
- Remove .prettierrc (dead config, Biome replaces Prettier)
- Rename lighthouserc.json to lighthouserc.cjs with module.exports for ESM compat
- Add logger.warn to empty catch blocks in auth, register, site-settings, prisma-cache, redis, security, rate-limit
- Unify ActionResult type: action-helper.ts uses 'ok' consistent with safe-action-shared.ts
- Add noUnusedLocals + noUnusedParameters to tsconfig + fix 25 pre-existing unused vars
- Replace barrel export src/types/index.ts with direct @/types/common imports
- Make trustHost conditional (development only) in auth.ts
- Add pre-flight URL validation to update-Nitrov3.sh to catch image.library.url misconfigurations
- Improve NITRO_IMAGE_LIBRARY_URL content validation in pre-flight & post-compute checks
2026-07-26 20:28:11 +02:00

48 lines
1.0 KiB
TypeScript

"use client";
import { motion, useInView } from "framer-motion";
import { useEffect, useRef, useState } from "react";
export function AnimatedCounter({
value,
suffix = "",
}: {
value: number;
suffix?: string;
}) {
const ref = useRef<HTMLSpanElement>(null);
const isInView = useInView(ref, { once: true, margin: "-50px" });
const [display, setDisplay] = useState(0);
useEffect(() => {
if (!isInView) return;
const duration = 1200;
const steps = 30;
const increment = value / steps;
let current = 0;
let step = 0;
const id = setInterval(() => {
step++;
current = Math.min(Math.round(increment * step), value);
setDisplay(current);
if (step >= steps) {
clearInterval(id);
setDisplay(value);
}
}, duration / steps);
return () => clearInterval(id);
}, [isInView, value]);
return (
<motion.span
ref={ref}
initial={{ opacity: 0, y: 12 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, ease: "easeOut" }}
>
{display.toLocaleString()}
{suffix}
</motion.span>
);
}