feat(frontend): wire Vite app to FastAPI parser with NDJSON streaming + Upload page
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
/**
|
||||
* Animates a numeric value with a smooth ease-out curve.
|
||||
* Zero dependencies, single rAF loop. Re-runs when `value` changes.
|
||||
*/
|
||||
export function useCountUp(value: number, duration = 850): number {
|
||||
const [n, setN] = useState(0);
|
||||
const displayed = useRef(0);
|
||||
const raf = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const from = displayed.current;
|
||||
const to = value;
|
||||
const start = performance.now();
|
||||
if (raf.current !== null) cancelAnimationFrame(raf.current);
|
||||
|
||||
const tick = (now: number) => {
|
||||
const t = Math.min(1, (now - start) / duration);
|
||||
const e = 1 - Math.pow(1 - t, 3); // easeOutCubic
|
||||
const current = from + (to - from) * e;
|
||||
displayed.current = current;
|
||||
setN(current);
|
||||
if (t < 1) raf.current = requestAnimationFrame(tick);
|
||||
};
|
||||
raf.current = requestAnimationFrame(tick);
|
||||
return () => {
|
||||
if (raf.current !== null) cancelAnimationFrame(raf.current);
|
||||
};
|
||||
}, [value, duration]);
|
||||
|
||||
return n;
|
||||
}
|
||||
Reference in New Issue
Block a user