Files
hwilliams-dev/hooks/useAnimations.ts
2026-06-04 01:54:38 -04:00

93 lines
2.4 KiB
TypeScript

"use client";
import { useState, useEffect, useRef, useCallback } from "react";
export function useStaggerReveal(count: number, baseDelay = 80) {
const [visible, setVisible] = useState<Set<number>>(new Set());
useEffect(() => {
const timers: ReturnType<typeof setTimeout>[] = [];
for (let i = 0; i < count; i++) {
timers.push(
setTimeout(
() => setVisible((prev) => new Set(prev).add(i)),
baseDelay * (i + 1) + 200
)
);
}
return () => timers.forEach(clearTimeout);
}, [count, baseDelay]);
return visible;
}
export function useMountTransition(delay = 50) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
const t = setTimeout(() => setMounted(true), delay);
return () => clearTimeout(t);
}, [delay]);
return mounted;
}
export function useWiggle(durationMs = 1500, onComplete: () => void) {
const [wiggling, setWiggling] = useState(false);
const [progress, setProgress] = useState(0);
const startTime = useRef<number>(0);
const rafId = useRef<number>(0);
const completeTimer = useRef<ReturnType<typeof setTimeout>>(null);
const completedRef = useRef(false);
const stop = useCallback(() => {
setWiggling(false);
setProgress(0);
cancelAnimationFrame(rafId.current);
if (completeTimer.current) clearTimeout(completeTimer.current);
}, []);
const tick = useCallback(() => {
const elapsed = Date.now() - startTime.current;
const p = Math.min(1, elapsed / durationMs);
setProgress(p);
if (p < 1) {
rafId.current = requestAnimationFrame(tick);
}
}, [durationMs]);
const start = useCallback(() => {
completedRef.current = false;
startTime.current = Date.now();
setWiggling(true);
setProgress(0);
rafId.current = requestAnimationFrame(tick);
completeTimer.current = setTimeout(() => {
completedRef.current = true;
stop();
onComplete();
}, durationMs);
}, [durationMs, onComplete, tick, stop]);
const release = useCallback(() => {
if (!completedRef.current) stop();
}, [stop]);
// cleanup on unmount
useEffect(() => () => {
cancelAnimationFrame(rafId.current);
if (completeTimer.current) clearTimeout(completeTimer.current);
}, []);
const handlers = {
onPointerDown: start,
onPointerEnter: start,
onPointerUp: release,
onPointerLeave: release,
};
return { wiggling, progress, handlers };
}