Fancy Basic Number Ticker Demo

PreviousNext

A block component.

Docs
fancyblock

Preview

Loading preview…
examples/text/fancy-basic-number-ticker-demo.tsx
"use client"

import { useEffect, useRef } from "react"
import {
  Activity,
  ArrowDownRight,
  DollarSign,
  LucideIcon,
  TrendingUp,
  Zap,
} from "lucide-react"
import { motion, useInView } from "motion/react"

import NumberTicker, {
  NumberTickerRef,
} from "@/components/fancy/text/basic-number-ticker"

const cards = [
  {
    title: "Revenue",
    icon: DollarSign,
    from: 0,
    target: 1250321,
    prefix: "$",
    suffix: "",
    gradient: "from-gray-100 to-blue-400",
    size: "large",
  },
  {
    title: "Conversion Rate",
    icon: TrendingUp,
    from: 0,
    target: 12.5,
    prefix: "",
    suffix: "%",
    gradient: "from-gray-100 to-purple-200",
    size: "small",
  },
  {
    title: "Bounce Rate",
    icon: ArrowDownRight,
    from: 100,
    target: 35.8,
    prefix: "",
    suffix: "%",
    gradient: "from-gray-100 to-orange-200",
    size: "small",
  },
  {
    title: "Avg. Session Duration",
    icon: Zap,
    from: 0,
    target: 245,
    prefix: "",
    suffix: "s",
    gradient: "from-gray-100 to-purple-200",
    size: "small",
  },
  {
    title: "New Users",
    icon: TrendingUp,
    from: 0,
    target: 15420,
    prefix: "",
    suffix: "",
    gradient: "from-gray-100 to-orange-200",
    size: "small",
  },
  {
    title: "Active Users",
    icon: Activity,
    from: 0,
    target: 8750,
    prefix: "",
    suffix: "",
    gradient: "from-gray-100 to-blue-200",
    size: "small",
  },
]

interface CardProps {
  title: string
  icon: LucideIcon
  from: number
  target: number
  prefix: string
  suffix: string
  gradient: string
  size: string
}

const Card = ({ card, index }: { card: CardProps; index: number }) => {
  const cardRef = useRef<HTMLDivElement>(null)
  const tickerRef = useRef<NumberTickerRef>(null)
  const inView = useInView(cardRef, { once: false })

  useEffect(() => {
    if (inView) {
      tickerRef.current?.startAnimation()
    }
  }, [inView])

  return (
    <motion.div
      ref={cardRef}
      initial={{ opacity: 0, y: 10 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5, delay: index * 0.1 }}
      className={`p-6 bg-linear-to-b from-50% to-130% flex justify-between flex-col text-foreground dark:text-muted ${
        card.gradient
      } ${card.size === "large" ? "col-span-2 row-span-2" : ""}`}
    >
      <div className="flex justify-between items-center mb-4">
        <h3 className="text-xs md:text-sm">{card.title}</h3>
        <card.icon className={`h-4 w-4`} />
      </div>
      <div
        className={`${card.size === "large" ? "text-2xl md:text-5xl" : "text-xl md:text-3xl"}`}
      >
        {card.prefix}
        <NumberTicker
          ref={tickerRef}
          from={card.from}
          target={card.target}
          transition={{
            duration: 3,
            ease: "easeInOut",
            type: "tween",
            delay: index * 0.2,
          }}
          className="tabular-nums"
          autoStart={false}
        />
        {card.suffix}
      </div>
    </motion.div>
  )
}

export default function FancyNumberTickerDemo() {
  return (
    <div className="w-dvw h-dvh font-azeret-mono bg-white">
      <div className="grid grid-cols-3 grid-rows-2 h-full">
        {cards.map((card, index) => (
          <Card key={index} card={card} index={index} />
        ))}
      </div>
    </div>
  )
}

Installation

npx shadcn@latest add @fancy/fancy-basic-number-ticker-demo

Usage

import { FancyBasicNumberTickerDemo } from "@/components/fancy-basic-number-ticker-demo"
<FancyBasicNumberTickerDemo />