BRIDGE Platform Agent
feat(platform): BRIDGE Modules hub with /modules/e and /modules/s routed workspaces
1955a0f
Raw History Blame Contribute Delete
4.75 kB
import { LucideIcon, TrendingUp, TrendingDown, Minus } from "lucide-react";
import { cn } from "../../../utils/cn";
interface StatCardProps {
title: string;
value: string | number;
icon: LucideIcon;
iconColor?: "indigo" | "emerald" | "amber" | "rose" | "teal" | "blue" | "purple";
change?: {
value: string;
isPositive?: boolean | null;
period?: string;
};
subtitle?: string;
progress?: number; // 0 to 100
className?: string;
}
const colorMap = {
indigo: {
bg: "bg-indigo-500/10",
border: "border-indigo-500/20",
text: "text-indigo-400",
glow: "group-hover:border-indigo-500/40",
},
emerald: {
bg: "bg-emerald-500/10",
border: "border-emerald-500/20",
text: "text-emerald-400",
glow: "group-hover:border-emerald-500/40",
},
amber: {
bg: "bg-amber-500/10",
border: "border-amber-500/20",
text: "text-amber-400",
glow: "group-hover:border-amber-500/40",
},
rose: {
bg: "bg-rose-500/10",
border: "border-rose-500/20",
text: "text-rose-400",
glow: "group-hover:border-rose-500/40",
},
teal: {
bg: "bg-teal-500/10",
border: "border-teal-500/20",
text: "text-teal-400",
glow: "group-hover:border-teal-500/40",
},
blue: {
bg: "bg-blue-500/10",
border: "border-blue-500/20",
text: "text-blue-400",
glow: "group-hover:border-blue-500/40",
},
purple: {
bg: "bg-purple-500/10",
border: "border-purple-500/20",
text: "text-purple-400",
glow: "group-hover:border-purple-500/40",
},
};
export default function StatCard({
title,
value,
icon: Icon,
iconColor = "indigo",
change,
subtitle,
progress,
className,
}: StatCardProps) {
const c = colorMap[iconColor];
return (
<div
className={cn(
"group relative bg-white/5 hover:bg-white/10 backdrop-blur-md border border-white/10 rounded-xl p-5 transition-all duration-300 shadow-[0_8px_32px_-8px_rgba(0,0,0,0.5)] overflow-hidden",
className
)}
>
<div className="absolute inset-0 bg-gradient-to-br from-white/[0.03] to-transparent pointer-events-none" />
<div className="relative z-10 flex justify-between items-start">
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-zinc-400 font-sans">
{title}
</p>
<h3 className="text-2xl font-bold text-zinc-100 mt-2 tracking-tight font-mono">
{value}
</h3>
{subtitle && (
<p className="text-xs text-zinc-500 mt-1 font-sans">{subtitle}</p>
)}
</div>
<div
className={cn(
"p-2.5 rounded-xl border transition-transform duration-300 group-hover:scale-110 shadow-[inset_0_1px_1px_rgba(255,255,255,0.1)]",
c.bg,
c.border
)}
>
<Icon className={cn("w-5 h-5 drop-shadow-[0_0_8px_currentColor]", c.text)} />
</div>
</div>
{progress !== undefined && (
<div className="relative z-10 mt-4 space-y-1.5">
<div className="flex justify-between text-[11px] text-zinc-400 font-sans font-medium">
<span>Capacity</span>
<span className="font-mono">{Math.round(progress)}%</span>
</div>
<div className="w-full h-1.5 bg-black/50 border border-white/5 rounded-full overflow-hidden">
<div
className={cn("h-full rounded-full transition-all duration-500", c.text.replace("text-", "bg-"))}
style={{ width: `${Math.min(100, Math.max(0, progress))}%`, boxShadow: '0 0 10px currentColor' }}
/>
</div>
</div>
)}
{change && (
<div className="relative z-10 mt-3.5 flex items-center text-xs font-medium font-sans">
{change.isPositive === true && (
<span className="flex items-center text-emerald-400 mr-1.5">
<TrendingUp className="w-3.5 h-3.5 mr-0.5" />
<span className="font-mono">{change.value}</span>
</span>
)}
{change.isPositive === false && (
<span className="flex items-center text-rose-400 mr-1.5">
<TrendingDown className="w-3.5 h-3.5 mr-0.5" />
<span className="font-mono">{change.value}</span>
</span>
)}
{change.isPositive === null && (
<span className="flex items-center text-zinc-400 mr-1.5">
<Minus className="w-3.5 h-3.5 mr-0.5" />
<span className="font-mono">{change.value}</span>
</span>
)}
<span className="text-zinc-500 font-normal">
{change.period || "vs last month"}
</span>
</div>
)}
</div>
);
}