MedASR-Bench / src /lib /hooks.ts
ngoan
MedASR Bench: full platform + Hugging Face Docker Space packaging
d70f132
Raw
History Blame Contribute Delete
3.7 kB
"use client";
import { useEffect, useRef, useState } from "react";
/**
* useScrollReveal - Hook to detect when elements come into view
* Adds/removes 'in-view' class for CSS scroll animations
*/
export function useScrollReveal<T extends HTMLElement>(
threshold = 0.1,
rootMargin = "-50px"
) {
const ref = useRef<T>(null);
const [isInView, setIsInView] = useState(false);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.unobserve(element);
} else {
setIsInView(false);
}
},
{
threshold,
rootMargin,
}
);
observer.observe(element);
return () => {
observer.disconnect();
};
}, [threshold, rootMargin]);
return { ref, isInView };
}
/**
* useStaggerReveal - Hook for staggered child animations
*/
export function useStaggerReveal<T extends HTMLElement>(count: number) {
const ref = useRef<T>(null);
const [visibleCount, setVisibleCount] = useState(0);
useEffect(() => {
const element = ref.current;
if (!element) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
// Animate children in with stagger
let current = 0;
const interval = setInterval(() => {
current++;
setVisibleCount(current);
if (current >= count) {
clearInterval(interval);
observer.unobserve(element);
}
}, 60);
return () => clearInterval(interval);
}
},
{ threshold: 0.1, rootMargin: "-50px" }
);
observer.observe(element);
return () => observer.disconnect();
}, [count]);
return { ref, visibleCount };
}
/**
* useParallax - Hook for parallax scroll effects
*/
export function useParallax(speed: number = 0.5) {
const [offset, setOffset] = useState(0);
useEffect(() => {
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
setOffset(window.scrollY * speed);
ticking = false;
});
ticking = true;
}
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [speed]);
return offset;
}
/**
* useReducedMotion - Hook to check user's motion preference
*/
export function useReducedMotion(): boolean {
const [prefersReduced, setPrefersReduced] = useState(() => {
// Check synchronously during render to avoid flash
if (typeof window !== "undefined") {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
return false;
});
useEffect(() => {
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches);
mediaQuery.addEventListener("change", handler);
return () => mediaQuery.removeEventListener("change", handler);
}, []);
return prefersReduced;
}
/**
* useMousePosition - Hook to track mouse position
*/
export function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return position;
}