GRV-Summit-Site/components/grants/CyclingGrantAmount.tsx
“kirukib” 1a710aa3c6
Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
first commit + project setup
2026-05-20 11:57:21 +03:00

75 lines
1.8 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { grantFundingCycle } from "@/content/grants";
import { cn } from "@/lib/utils";
type Figure = (typeof grantFundingCycle)[number];
type Props = {
figures?: readonly Figure[];
intervalMs?: number;
className?: string;
valueClassName?: string;
showCaption?: boolean;
captionClassName?: string;
};
export function CyclingGrantAmount({
figures = grantFundingCycle,
intervalMs = 3200,
className,
valueClassName,
showCaption = false,
captionClassName,
}: Props) {
const [index, setIndex] = useState(0);
const [visible, setVisible] = useState(true);
useEffect(() => {
let swapTimer: ReturnType<typeof setTimeout>;
const tick = setInterval(() => {
setVisible(false);
swapTimer = setTimeout(() => {
setIndex((i) => (i + 1) % figures.length);
setVisible(true);
}, 220);
}, intervalMs);
return () => {
clearInterval(tick);
clearTimeout(swapTimer);
};
}, [figures.length, intervalMs]);
const current = figures[index];
return (
<span className={cn("inline-flex flex-col items-center", className)}>
<span
className={cn(
"inline-block transition-all duration-200",
visible ? "translate-y-0 opacity-100" : "-translate-y-2 opacity-0",
valueClassName
)}
aria-live="polite"
aria-atomic="true"
>
<span className="sr-only">{current.label}: </span>
{current.display}
</span>
{showCaption && (
<span
className={cn(
"mt-1 text-xs font-normal normal-case tracking-normal text-muted-foreground transition-opacity duration-200",
visible ? "opacity-100" : "opacity-0",
captionClassName
)}
>
{current.label}
</span>
)}
</span>
);
}