Tarjetascardsstatsanalyticskpidashboard
Stats Trend Card
Tarjeta analítica de KPI con valor actual, badge de tendencia y minigráfico sparkline.
Vista Previa en Vivo
Monthly Active Builders↑+18.4%
14,290
vs last 30 days
Uso
Copia y pega este código directamente en tu componente React:
tsx
import { StatsTrendCard } from 'react-component-kit-ui/cards';
<StatsTrendCard title="Active Subscriptions" value="14,290" change="+12.8%" isPositive={true} subtitle="Compared to previous 30 days" />Instalación Manual
Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/StatsTrendCard.tsx + StatsTrendCard.css:
StatsTrendCard.tsx
'use client';
import type { CSSProperties } from 'react';
import './StatsTrendCard.css';
/** class:list → className */
function cx(...args: unknown[]): string {
const out: string[] = [];
const walk = (v: unknown) => {
if (!v) return;
if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
else if (Array.isArray(v)) v.forEach(walk);
else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
};
args.forEach(walk);
return out.join(' ');
}
/** "a: b; --c: d" → style object */
function st(css: string | undefined): CSSProperties {
const o: Record<string, string> = {};
for (const decl of String(css ?? '').split(';')) {
const i = decl.indexOf(':');
if (i < 0) continue;
const k = decl.slice(0, i).trim();
if (k) o[k.startsWith('--') ? k : k.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = decl.slice(i + 1).trim();
}
return o as CSSProperties;
}
/**
* StatsTrendCard — Compact KPI metric card featuring delta percentages, trend directions, and mini activity bars.
*
* @param {string} label - Metric name (e.g. 'Active Users').
* @param {string} value - Formatted value figure (e.g. '42,850').
* @param {string} change - Delta percentage string (e.g. '+18.4%').
* @param {'up' | 'down' | 'neutral'} trend - Direction of the trend. Default is 'up'.
* @param {string} timeframe - Timeframe subtitle (e.g. 'vs last month').
*/
export interface StatsTrendCardProps {
label: string;
value: string;
change: string;
trend?: 'up' | 'down' | 'neutral';
timeframe?: string;
}
export function StatsTrendCard({
label = 'Monthly Active Builders',
value = '14,290',
change = '+18.4%',
trend = 'up',
timeframe = 'vs last 30 days',
}: StatsTrendCardProps) {
const bars = [35, 55, 40, 75, 60, 90, 80, 100];
return (
<>
<div className="trend-card" data-rk-0bf6d9="">
<div className="trend-card__top" data-rk-0bf6d9="">
<span className="trend-card__label" data-rk-0bf6d9="">{label}</span>
<span className={cx(['trend-card__badge', `trend-card__badge--${trend}`])} data-rk-0bf6d9="">
{trend === 'up' && '↑'}
{trend === 'down' && '↓'}
{change}
</span>
</div>
<div className="trend-card__value" data-rk-0bf6d9="">{value}</div>
<div className="trend-card__bottom" data-rk-0bf6d9="">
<span className="trend-card__timeframe" data-rk-0bf6d9="">{timeframe}</span>
<div className="trend-card__chart" aria-hidden="true" data-rk-0bf6d9="">
{bars.map((h, __i: number) => (
<span key={__i} className="bar" style={st(`height: ${h}%;`)} data-rk-0bf6d9=""></span>
))}
</div>
</div>
</div>
</>
);
}
export default StatsTrendCard;StatsTrendCard.css
/* StatsTrendCard — ported from astro-component-kit (cards/StatsTrendCard.astro) */
.trend-card:where([data-rk-0bf6d9]) {
display: flex;
flex-direction: column;
padding: var(--sp-6, 1.5rem);
background: rgba(255, 255, 255, 0.02);
backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--r-xl, 20px);
width: 100%;
max-width: 280px;
font-family: var(--font-sans, system-ui, sans-serif);
transition: transform 0.25s ease, border-color 0.25s ease;
}
.trend-card:hover:where([data-rk-0bf6d9]) {
transform: translateY(-2px);
border-color: rgba(184, 134, 11, 0.3);
}
.trend-card__top:where([data-rk-0bf6d9]) {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.trend-card__label:where([data-rk-0bf6d9]) {
font-size: 0.8rem;
font-weight: 600;
color: var(--c-text-2, #a3a3a3);
}
.trend-card__badge:where([data-rk-0bf6d9]) {
font-size: 0.72rem;
font-weight: 700;
padding: 2px 8px;
border-radius: var(--r-full, 9999px);
display: inline-flex;
align-items: center;
gap: 2px;
}
.trend-card__badge--up:where([data-rk-0bf6d9]) {
background: rgba(16, 185, 129, 0.15);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.3);
}
.trend-card__badge--down:where([data-rk-0bf6d9]) {
background: rgba(239, 68, 68, 0.15);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
.trend-card__badge--neutral:where([data-rk-0bf6d9]) {
background: rgba(255, 255, 255, 0.08);
color: var(--c-text-2, #a3a3a3);
}
.trend-card__value:where([data-rk-0bf6d9]) {
font-size: 1.85rem;
font-weight: 900;
color: var(--c-text-1, #ffffff);
letter-spacing: -0.02em;
margin-bottom: 12px;
}
.trend-card__bottom:where([data-rk-0bf6d9]) {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.trend-card__timeframe:where([data-rk-0bf6d9]) {
font-size: 0.74rem;
color: var(--c-text-3, #737373);
}
.trend-card__chart:where([data-rk-0bf6d9]) {
display: flex;
align-items: flex-end;
gap: 3px;
height: 24px;
width: 60px;
}
.bar:where([data-rk-0bf6d9]) {
flex: 1;
background: rgba(184, 134, 11, 0.35);
border-radius: 2px;
transition: background 0.2s;
}
.trend-card:hover:where([data-rk-0bf6d9]) .bar:where([data-rk-0bf6d9]) {
background: var(--c-primary-light, #d4a017);
}Información Rápida
- Categoría
- Tarjetas
- Nombre de archivo
StatsTrendCard.tsx- Dependencias
- Ninguna — React + CSS puro
- Etiquetas
- cardsstatsanalyticskpidashboard