{ "name": "kpi-card", "type": "registry:ui", "registryDependencies": [], "dependencies": [ "lucide-react" ], "devDependencies": [], "tailwind": {}, "cssVars": { "light": {}, "dark": {} }, "files": [ { "path": "kpi-card.tsx", "content": "'use client';\n\nimport * as React from 'react';\nimport { cn } from '@/lib/utils';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\n\ntype Tone = 'default' | 'primary' | 'success' | 'warning' | 'danger';\ntype Size = 'sm' | 'md' | 'lg';\ntype Trend = 'up' | 'down' | 'flat';\n\nexport type KpiCardProps = {\n /** Small label/title */\n label: string;\n /** Main metric (formatted) */\n value: string | number;\n /** Delta in %, number or string -> shown in green/red */\n delta?: number | string;\n /** Visual trend indicator */\n trend?: Trend;\n /** Sub text under value (e.g., “vs Previous 30 days”) */\n caption?: string;\n /** Optional top-right icon */\n icon?: React.ReactNode;\n /** Visual tone */\n tone?: Tone;\n /** Size */\n size?: Size;\n /** Compact density */\n compact?: boolean;\n className?: string;\n};\n\n/** tone -> classes */\nconst toneMap: Record<\n Tone,\n { card: string; value: string; deltaUp: string; deltaDown: string }\n> = {\n default: {\n card: 'bg-zinc-100/70 dark:bg-zinc-900/50 ring-1 ring-zinc-200 dark:ring-zinc-800',\n value: 'text-zinc-950 dark:text-zinc-50',\n deltaUp: 'text-emerald-600 dark:text-emerald-400',\n deltaDown: 'text-rose-600 dark:text-rose-400',\n },\n primary: {\n card: 'bg-blue-100/70 dark:bg-blue-900/30 ring-1 ring-blue-200/60 dark:ring-blue-800/60',\n value: 'text-blue-700 dark:text-blue-200',\n deltaUp: 'text-emerald-600 dark:text-emerald-400',\n deltaDown: 'text-rose-600 dark:text-rose-400',\n },\n success: {\n card: 'bg-emerald-100/70 dark:bg-emerald-900/30 ring-1 ring-emerald-200/60 dark:ring-emerald-800/60',\n value: 'text-emerald-700 dark:text-emerald-200',\n deltaUp: 'text-emerald-700 dark:text-emerald-300',\n deltaDown: 'text-rose-600 dark:text-rose-400',\n },\n warning: {\n card: 'bg-amber-100/70 dark:bg-amber-900/30 ring-1 ring-amber-200/60 dark:ring-amber-800/60',\n value: 'text-amber-700 dark:text-amber-200',\n deltaUp: 'text-emerald-600 dark:text-emerald-400',\n deltaDown: 'text-rose-600 dark:text-rose-400',\n },\n danger: {\n card: 'bg-rose-100/70 dark:bg-rose-900/30 ring-1 ring-rose-200/60 dark:ring-rose-800/60',\n value: 'text-rose-700 dark:text-rose-200',\n deltaUp: 'text-emerald-600 dark:text-emerald-400',\n deltaDown: 'text-rose-700 dark:text-rose-300',\n },\n};\n\nconst sizeMap: Record<\n Size,\n { pad: string; label: string; value: string; caption: string; icon: string }\n> = {\n sm: {\n pad: 'p-3',\n label: 'text-xs',\n value: 'text-xl',\n caption: 'text-[11px]',\n icon: 'h-4 w-4',\n },\n md: {\n pad: 'p-4',\n label: 'text-sm',\n value: 'text-2xl',\n caption: 'text-xs',\n icon: 'h-5 w-5',\n },\n lg: {\n pad: 'p-6',\n label: 'text-sm',\n value: 'text-3xl',\n caption: 'text-sm',\n icon: 'h-6 w-6',\n },\n};\n\n/** Minimal, dependency‑light KPI card */\nexport function KpiCard({\n label,\n value,\n delta,\n trend = 'flat',\n caption,\n icon,\n tone = 'primary',\n size = 'md',\n compact = false,\n className,\n}: KpiCardProps) {\n const t = toneMap[tone];\n const s = sizeMap[size];\n\n const deltaValue =\n typeof delta === 'number' ? `${delta > 0 ? '+' : ''}${delta}%` : delta; // allow pre‑formatted strings\n\n const isUp = trend === 'up';\n const isDown = trend === 'down';\n const DeltaIcon = isUp ? TrendingUp : isDown ? TrendingDown : Minus;\n\n return (\n \n {/* subtle corner pulse like your screenshot */}\n \n \n\n
\n
\n \n {label}\n
\n
\n {typeof value === 'number' ? value.toLocaleString() : value}\n
\n {caption ? (\n
\n {caption}\n
\n ) : null}\n
\n\n
\n {typeof deltaValue !== 'undefined' && (\n \n \n {deltaValue}\n
\n )}\n {icon ? (\n \n {icon}\n \n ) : null}\n \n \n\n {/* tiny baseline bar (optional – matches screenshot) */}\n
\n
\n );\n}\n", "type": "registry:ui" } ] }