Spaces:
Running
Running
Download src/components/StatCard.tsx from sitcod3/BRIDGE_Modul-S_prod: direct link, hf CLI and curl.
- Browser
- Download file 5.37 kB
-
https://huggingface.co/spaces/sitcod3/BRIDGE_Modul-S_prod/resolve/main/src/components/StatCard.tsx
- Command line
-
hf download hf://spaces/sitcod3/BRIDGE_Modul-S_prod/src/components/StatCard.tsx
-
curl -L -o StatCard.tsx https://huggingface.co/spaces/sitcod3/BRIDGE_Modul-S_prod/resolve/main/src/components/StatCard.tsx
5.37 kB
| import { LucideIcon, TrendingUp, TrendingDown, Minus } from "lucide-react"; | |
| import { cn } from "../utils/cn"; | |
| import { Skeleton } from "./Skeleton"; | |
| 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; | |
| isLoading?: boolean; | |
| } | |
| const colorMap = { | |
| indigo: { | |
| bg: "bg-emerald-500/10", | |
| border: "border-emerald-500/20", | |
| text: "text-emerald-400", | |
| glow: "group-hover:border-emerald-500/40", | |
| }, | |
| emerald: { | |
| bg: "bg-teal-500/10", | |
| border: "border-teal-500/20", | |
| text: "text-teal-400", | |
| glow: "group-hover:border-teal-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, | |
| isLoading = false, | |
| }: StatCardProps) { | |
| const c = colorMap[iconColor]; | |
| if (isLoading) { | |
| return ( | |
| <div | |
| className={cn( | |
| "relative bg-white/5 border border-white/10 rounded-xl p-5 space-y-3 shadow-md", | |
| className | |
| )} | |
| > | |
| <div className="flex justify-between items-start"> | |
| <div className="space-y-2 w-2/3"> | |
| <Skeleton className="h-3 w-1/2" /> | |
| <Skeleton className="h-7 w-3/4" /> | |
| </div> | |
| <Skeleton className="w-10 h-10 rounded-xl" /> | |
| </div> | |
| <Skeleton className="h-3 w-1/3" /> | |
| </div> | |
| ); | |
| } | |
| 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> | |
| ); | |
| } | |