140 lines
4.5 KiB
TypeScript
140 lines
4.5 KiB
TypeScript
import { LucideIcon } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
|
|
interface StatsCardProps {
|
|
title: string;
|
|
value: string | number;
|
|
change?: string;
|
|
trend?: "up" | "down" | "neutral";
|
|
icon: LucideIcon;
|
|
color?: "blue" | "green" | "purple" | "orange" | "red";
|
|
className?: string;
|
|
}
|
|
|
|
export function StatsCard({
|
|
title,
|
|
value,
|
|
change,
|
|
trend,
|
|
icon: Icon,
|
|
color = "blue",
|
|
className,
|
|
}: StatsCardProps) {
|
|
const colorStyles = {
|
|
blue: {
|
|
gradient: "from-blue-500/10 to-blue-600/5",
|
|
iconBg: "bg-gradient-to-br from-blue-500 to-blue-600",
|
|
iconShadow: "shadow-blue-500/25",
|
|
text: "text-blue-600",
|
|
trendUp: "text-emerald-600",
|
|
trendDown: "text-red-600",
|
|
},
|
|
green: {
|
|
gradient: "from-emerald-500/10 to-emerald-600/5",
|
|
iconBg: "bg-gradient-to-br from-emerald-500 to-emerald-600",
|
|
iconShadow: "shadow-emerald-500/25",
|
|
text: "text-emerald-600",
|
|
trendUp: "text-emerald-600",
|
|
trendDown: "text-red-600",
|
|
},
|
|
purple: {
|
|
gradient: "from-violet-500/10 to-violet-600/5",
|
|
iconBg: "bg-gradient-to-br from-violet-500 to-violet-600",
|
|
iconShadow: "shadow-violet-500/25",
|
|
text: "text-violet-600",
|
|
trendUp: "text-emerald-600",
|
|
trendDown: "text-red-600",
|
|
},
|
|
orange: {
|
|
gradient: "from-amber-500/10 to-amber-600/5",
|
|
iconBg: "bg-gradient-to-br from-amber-500 to-orange-600",
|
|
iconShadow: "shadow-amber-500/25",
|
|
text: "text-amber-600",
|
|
trendUp: "text-emerald-600",
|
|
trendDown: "text-red-600",
|
|
},
|
|
red: {
|
|
gradient: "from-rose-500/10 to-rose-600/5",
|
|
iconBg: "bg-gradient-to-br from-rose-500 to-rose-600",
|
|
iconShadow: "shadow-rose-500/25",
|
|
text: "text-rose-600",
|
|
trendUp: "text-emerald-600",
|
|
trendDown: "text-red-600",
|
|
},
|
|
};
|
|
|
|
const styles = colorStyles[color];
|
|
const TrendIcon =
|
|
trend === "up" ? TrendingUp : trend === "down" ? TrendingDown : Minus;
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"group relative overflow-hidden rounded-2xl border border-border/50 bg-gradient-to-br p-6 transition-all duration-300 hover:shadow-lg hover:-translate-y-1",
|
|
styles.gradient,
|
|
className,
|
|
)}
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<div className="space-y-2">
|
|
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
<div className="flex items-baseline gap-2">
|
|
<span className="text-3xl font-bold tracking-tight">{value}</span>
|
|
</div>
|
|
{change && (
|
|
<div className="flex items-center gap-1 text-sm">
|
|
<TrendIcon
|
|
className={cn(
|
|
"h-4 w-4",
|
|
trend === "up" && styles.trendUp,
|
|
trend === "down" && styles.trendDown,
|
|
trend === "neutral" && "text-muted-foreground",
|
|
)}
|
|
/>
|
|
<span
|
|
className={cn(
|
|
"font-medium",
|
|
trend === "up" && styles.trendUp,
|
|
trend === "down" && styles.trendDown,
|
|
trend === "neutral" && "text-muted-foreground",
|
|
)}
|
|
>
|
|
{change}
|
|
</span>
|
|
<span className="text-muted-foreground">vs last month</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div
|
|
className={cn(
|
|
"flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br shadow-lg transition-transform duration-300 group-hover:scale-110",
|
|
styles.iconBg,
|
|
styles.iconShadow,
|
|
)}
|
|
>
|
|
<Icon className="h-6 w-6 text-white" />
|
|
</div>
|
|
</div>
|
|
|
|
{/* Decorative corner gradient */}
|
|
<div className="absolute -right-8 -top-8 h-32 w-32 rounded-full bg-gradient-to-br from-white/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export function StatsCardSkeleton() {
|
|
return (
|
|
<div className="animate-pulse rounded-2xl border border-border/50 bg-gradient-to-br from-muted/30 to-muted/20 p-6">
|
|
<div className="flex items-start justify-between">
|
|
<div className="space-y-3">
|
|
<div className="h-4 w-24 rounded bg-muted" />
|
|
<div className="h-9 w-32 rounded bg-muted" />
|
|
<div className="h-4 w-20 rounded bg-muted" />
|
|
</div>
|
|
<div className="h-12 w-12 rounded-xl bg-muted" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|