The weeb for the next gen discord boat - Wamellow
wamellow.com
bot
discord
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}