Some checks are pending
Deploy to Cloudflare Workers (OpenNext) / deploy (push) Waiting to run
75 lines
1.8 KiB
TypeScript
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>
|
|
);
|
|
}
|