feat: add animated number transitions to dashboard stats

Numbers smoothly count up/down when switching date ranges,
applying filters, or as real-time visitor count changes.
Uses framer-motion useSpring for natural spring physics.
This commit is contained in:
Usman Baig
2026-03-15 21:37:11 +01:00
parent df2b3cadd7
commit c21d7b9073
4 changed files with 40 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ import { Select, DownloadIcon, PlusIcon, XIcon } from '@ciphera-net/ui'
import { Checkbox } from '@ciphera-net/ui'
import { ArrowUpRight, ArrowDownRight } from '@phosphor-icons/react'
import { motion } from 'framer-motion'
import { AnimatedNumber } from '@/components/ui/animated-number'
import { cn } from '@/lib/utils'
import { formatTime, formatDateShort, formatDate } from '@/lib/utils/formatDate'
@@ -350,7 +351,7 @@ export default function Chart({
>
<div className={cn('text-[10px] font-semibold uppercase tracking-widest mb-2', metric === m.key ? 'text-brand-orange' : 'text-neutral-400 dark:text-neutral-500')}>{m.label}</div>
<div className="flex items-baseline gap-2">
<span className="text-2xl font-bold text-neutral-900 dark:text-white">{m.format(m.value)}</span>
<AnimatedNumber value={m.value} format={m.format} className="text-2xl font-bold text-neutral-900 dark:text-white" />
{m.change !== null && (
<span className={cn('flex items-center gap-0.5 text-sm font-semibold', m.isPositive ? 'text-[#10B981]' : 'text-[#EF4444]')}>
{m.isPositive ? <ArrowUpRight weight="bold" className="size-3.5" /> : <ArrowDownRight weight="bold" className="size-3.5" />}

View File

@@ -1,3 +1,7 @@
'use client'
import { AnimatedNumber } from '@/components/ui/animated-number'
interface RealtimeVisitorsProps {
count: number
}
@@ -14,7 +18,7 @@ export default function RealtimeVisitors({ count }: RealtimeVisitorsProps) {
<div className="h-2 w-2 bg-green-500 rounded-full animate-pulse"></div>
</div>
<div className="text-3xl font-bold text-neutral-900 dark:text-white">
{count}
<AnimatedNumber value={count} format={(v) => v.toLocaleString()} />
</div>
</div>
)