The weeb for the next gen discord boat - Wamellow wamellow.com
bot discord
at master 3.4 kB view raw
1"use client"; 2 3import Link from "next/link"; 4import { useEffect, useRef, useState } from "react"; 5import CountUp, { type CountUpProps } from "react-countup"; 6import { HiInformationCircle } from "react-icons/hi"; 7 8import Box from "./box"; 9 10export function ClientCountUp(props: Omit<CountUpProps, "duration">) { 11 const [isInViewport, setIsInViewport] = useState(false); 12 const componentRef = useRef<HTMLDivElement | null>(null); 13 14 const handleIntersection = (entries: IntersectionObserverEntry[]) => { 15 const [entry] = entries; 16 setIsInViewport(entry.isIntersecting); 17 }; 18 19 useEffect(() => { 20 const observer = new IntersectionObserver(handleIntersection, { 21 threshold: 0.5 22 }); 23 24 if (componentRef.current) observer.observe(componentRef.current); 25 26 return () => { 27 if (componentRef.current) observer.unobserve(componentRef.current); 28 }; 29 }, []); 30 31 return ( 32 <span ref={componentRef} className={props.className || ""}> 33 {isInViewport ? <CountUp {...props} className="" duration={4} /> : <>0</>} 34 </span> 35 ); 36 37} 38 39interface Options { 40 items: { 41 name: string; 42 number: number; 43 gained: number | string | React.ReactNode; 44 append?: string; 45 info?: string; 46 }[]; 47} 48 49export function StatsBar(options: Options) { 50 const [width, setWidth] = useState(0); 51 const ref = useRef<NodeJS.Timeout | null>(null); 52 53 useEffect(() => { 54 ref.current = setInterval(() => setWidth(window.innerWidth), 1_000); 55 return () => { 56 if (ref.current) clearInterval(ref.current); 57 }; 58 }, []); 59 60 return ( 61 <Box 62 none 63 className="grid w-full rounded-md overflow-hidden divide-x divide-wamellow" 64 style={{ gridTemplateColumns: `repeat(${width > 768 ? options.items.length : 2}, minmax(0, 1fr))` }} 65 > 66 {options.items.slice(0, width > 768 ? 10 : 2).map((item) => ( 67 <div className="p-5 dark:bg-wamellow bg-wamellow-100" key={"counter" + item.name + item.number.toString() + item.number.toString()}> 68 69 <div className="flex"> 70 <div className="text-sm font-medium mb-1">{item.name}</div> 71 {item.info && <Link href={item.info} className="ml-auto dark:text-neutral-400 text-neutral-600 dark:hover:text-violet-400 hover:text-violet-600 duration-300"> 72 <HiInformationCircle /> 73 <span className="sr-only">information about this card</span> 74 </Link>} 75 </div> 76 77 <div className="md:flex"> 78 <ClientCountUp className="text-3xl dark:text-neutral-100 text-neutral-900 font-medium" end={item.number} /> 79 <div className="text-lg dark:text-violet-400 text-violet-600 font-medium relative md:top-2.5 md:ml-2"> 80 {typeof item.gained === "number" 81 ? 82 <>+<ClientCountUp end={item.gained} /> {item.append || "today"}</> 83 : 84 item.gained 85 } 86 </div> 87 </div> 88 89 </div> 90 ))} 91 </Box> 92 ); 93}