brackt/app/hooks/useRoundTransition.ts

50 lines
1.6 KiB
TypeScript
Raw Normal View History

import { useEffect, useRef, useState } from "react";
import { SLOT_WIDTH } from "~/components/scoring/BracketTreeView";
interface AnimState {
fromPage: number;
toPage: number;
dir: "left" | "right";
phase: "sliding" | "settling";
}
export function useRoundTransition(maxPage: number, defaultPage: number) {
const [page, setPage] = useState(defaultPage);
const [anim, setAnim] = useState<AnimState | null>(null);
const stripRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!anim || anim.phase !== "sliding" || !stripRef.current) return;
const el = stripRef.current;
const finalX = anim.dir === "right" ? -SLOT_WIDTH : 0;
const raf = requestAnimationFrame(() => {
el.style.transition = "transform 200ms ease";
el.style.transform = `translateX(${finalX}px)`;
});
return () => cancelAnimationFrame(raf);
}, [anim]);
useEffect(() => {
if (!anim || anim.phase !== "settling") return;
const timer = setTimeout(() => setAnim(null), 520);
return () => clearTimeout(timer);
}, [anim]);
const navigate = (newPage: number) => {
if (anim || newPage < 0 || newPage > maxPage) return;
setAnim({ fromPage: page, toPage: newPage, dir: newPage > page ? "right" : "left", phase: "sliding" });
};
const handleTransitionEnd = () => {
if (!anim || anim.phase !== "sliding") return;
if (stripRef.current) {
stripRef.current.style.transition = "none";
stripRef.current.style.transform = "translateX(0)";
}
setPage(anim.toPage);
setAnim((prev) => (prev ? { ...prev, phase: "settling" } : null));
};
return { page, anim, stripRef, navigate, handleTransitionEnd };
}