1 estrella en GitHub = 1 componente nuevo.

Dar una estrella
Gráficos y Estadísticaschartsvgzero-dependencytablecomparisondeltasparkline

Metric Delta Table

Tabla comparativa con barras en línea, valores actuales y previos, variaciones y sparklines.

Vista Previa en Vivo

Channel performance

Signups this month vs last month

MetricThis monthLast monthChangeTrend
Organic search4,8204,310up11.8%
Direct3,1203,290down5.2%
Newsletter1,8401,220up50.8%
Paid social1,3101,480down11.5%
Partners960710up35.2%

Uso

Copia y pega este código directamente en tu componente React:

tsx
import { MetricDeltaTable } from 'react-component-kit-ui/charts';

<MetricDeltaTable rows={[
    { label: 'Organic', current: 4820, previous: 4310, trend: [30, 34, 38, 44, 48] },
    { label: 'Paid', current: 1310, previous: 1480 },
  ]} />

Instalación Manual

Si no estás usando el paquete npm, crea un archivo nuevo y pega el siguiente código: src/components/MetricDeltaTable.tsx + MetricDeltaTable.css:

MetricDeltaTable.tsx
'use client';

import type { CSSProperties } from 'react';
import ChartFrame from './ChartFrame';
import SparklineChart from './SparklineChart';
import { formatValue } from './chart-utils';
import './MetricDeltaTable.css';

/** "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;
}

interface Row { label: string; current: number; previous: number; invert?: boolean; trend?: number[] }

/**
 * MetricDeltaTable — Comparison table with inline bars, current vs previous
 * values and signed deltas with direction icons. A "table + chart" hybrid.
 *
 * @param {Array<{label: string, current: number, previous: number, invert?: boolean}>} rows - Metrics.
 * @param {string} currentLabel - Header for current values.
 * @param {string} previousLabel - Header for previous values.
 * @param {string} prefix - Value prefix.
 * @param {string} suffix - Value suffix.
 * @param {string} color - Inline bar color.
 * @param {string} variant - Card style (glass|solid|ghost).
 * @param {string} size - Density (sm|md|lg).
 */
export interface MetricDeltaTableProps {
  title?: string;
  subtitle?: string;
  rows?: Row[];
  currentLabel?: string;
  previousLabel?: string;
  prefix?: string;
  suffix?: string;
  color?: string;
  variant?: 'glass' | 'solid' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

export function MetricDeltaTable({
  title = 'Channel performance',
  subtitle = 'Signups this month vs last month',
  rows = [
    { label: 'Organic search', current: 4820, previous: 4310, trend: [30, 34, 33, 38, 41, 44, 48] },
    { label: 'Direct', current: 3120, previous: 3290, trend: [36, 35, 34, 33, 33, 32, 31] },
    { label: 'Newsletter', current: 1840, previous: 1220, trend: [10, 12, 13, 14, 16, 17, 18] },
    { label: 'Paid social', current: 1310, previous: 1480, trend: [16, 15, 15, 14, 14, 13, 13] },
    { label: 'Partners', current: 960, previous: 710, trend: [6, 7, 7, 8, 8, 9, 10] },
  ],
  currentLabel = 'This month',
  previousLabel = 'Last month',
  prefix = '',
  suffix = '',
  color = 'var(--chart-1, #3987e5)',
  variant = 'glass',
  size = 'md',
  className = '',
}: MetricDeltaTableProps) {
  const max = Math.max(...rows.map((r) => r.current)) || 1;
  const fmt = (v: number) => formatValue(v, prefix, suffix, false);
  return (
    <>
      <ChartFrame title={title} subtitle={subtitle} variant={variant} size={size} className={className}>
        <div className="md" data-rk-df9b34="">
          <table className="md__table" data-rk-df9b34="">
            <thead data-rk-df9b34="">
              <tr data-rk-df9b34="">
                <th scope="col" data-rk-df9b34="">Metric</th>
                <th scope="col" className="md__num" data-rk-df9b34="">{currentLabel}</th>
                <th scope="col" className="md__num md__hide-sm" data-rk-df9b34="">{previousLabel}</th>
                <th scope="col" className="md__num" data-rk-df9b34="">Change</th>
                <th scope="col" className="md__hide-sm" data-rk-df9b34=""><span className="sr-only" data-rk-df9b34="">Trend</span></th>
              </tr>
            </thead>
            <tbody data-rk-df9b34="">
              {rows.map((r) => {
                const d = r.previous ? ((r.current - r.previous) / r.previous) * 100 : 0;
                const up = d >= 0;
                const good = r.invert ? !up : up;
                return (
                  <tr data-rk-df9b34="">
                    <th scope="row" data-rk-df9b34="">
                      <span className="md__label" data-rk-df9b34="">{r.label}</span>
                      <span className="md__bar" aria-hidden="true" data-rk-df9b34=""><span style={st(`width:${(r.current / max) * 100}%; background:${color}`)} data-rk-df9b34=""></span></span>
                    </th>
                    <td className="md__num md__strong" data-rk-df9b34="">{fmt(r.current)}</td>
                    <td className="md__num md__hide-sm" data-rk-df9b34="">{fmt(r.previous)}</td>
                    <td className="md__num" data-rk-df9b34="">
                      <span className={`md__delta ${good ? 'md__delta--good' : 'md__delta--bad'}`} data-rk-df9b34="">
                        <svg width="10" height="10" viewBox="0 0 10 10" aria-hidden="true" data-rk-df9b34=""><path d={up ? 'M5 1 L9 7 H1 Z' : 'M5 9 L9 3 H1 Z'} fill="currentColor" data-rk-df9b34="" /></svg>
                        <span className="sr-only" data-rk-df9b34="">{up ? 'up' : 'down'}</span>
                        {Math.abs(d).toFixed(1)}%
                      </span>
                    </td>
                    <td className="md__hide-sm" data-rk-df9b34="">{r.trend && <SparklineChart data={r.trend} size="sm" variant="line" label={`${r.label} trend`} color={good ? 'var(--chart-pos, #34c38f)' : 'var(--chart-neg, #f06a6a)'} />}</td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </ChartFrame>
    </>
  );
}

export default MetricDeltaTable;
MetricDeltaTable.css
/* MetricDeltaTable — ported from astro-component-kit (charts/MetricDeltaTable.astro) */

.md:where([data-rk-df9b34]) { overflow-x: auto; }
.md__table:where([data-rk-df9b34]) { width: 100%; border-collapse: collapse; font-size: 0.8125rem; font-variant-numeric: tabular-nums; }
.md__table:where([data-rk-df9b34]) th:where([data-rk-df9b34]), .md__table:where([data-rk-df9b34]) td:where([data-rk-df9b34]) {
  padding: 0.7rem 0.6rem;
  border-bottom: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
  text-align: left;
  vertical-align: middle;
}
.md__table:where([data-rk-df9b34]) thead:where([data-rk-df9b34]) th:where([data-rk-df9b34]) { font-size: 0.6875rem; font-weight: 600; color: var(--c-text-3, #8a8a8a); text-transform: uppercase; letter-spacing: 0.04em; }
.md__table:where([data-rk-df9b34]) tbody:where([data-rk-df9b34]) tr:where([data-rk-df9b34]) { transition: background 0.2s ease; }
.md__table:where([data-rk-df9b34]) tbody:where([data-rk-df9b34]) tr:hover:where([data-rk-df9b34]) { background: rgb(var(--ink-rgb, 255 255 255) / 0.03); }
.md__table:where([data-rk-df9b34]) tbody:where([data-rk-df9b34]) th:where([data-rk-df9b34]) { font-weight: 500; min-width: 9rem; }
.md__label:where([data-rk-df9b34]) { display: block; color: var(--c-text-1, #fff); margin-bottom: 0.35rem; }
.md__bar:where([data-rk-df9b34]) { display: block; height: 4px; border-radius: 2px; background: rgb(var(--ink-rgb, 255 255 255) / 0.05); overflow: hidden; }
.md__bar:where([data-rk-df9b34]) span:where([data-rk-df9b34]) { display: block; height: 100%; border-radius: 2px; }
.md__num:where([data-rk-df9b34]) { text-align: right !important; white-space: nowrap; color: var(--c-text-2, #a3a3a3); }
.md__strong:where([data-rk-df9b34]) { color: var(--c-text-1, #fff); font-weight: 650; }
.md__delta:where([data-rk-df9b34]) { display: inline-flex; align-items: center; gap: 0.3rem; color: var(--c-text-1, #fff); font-weight: 600; }
.md__delta--good:where([data-rk-df9b34]) svg:where([data-rk-df9b34]) { color: var(--chart-pos, #34c38f); }
.md__delta--bad:where([data-rk-df9b34]) svg:where([data-rk-df9b34]) { color: var(--chart-neg, #f06a6a); }
.sr-only:where([data-rk-df9b34]) {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
@media (max-width: 560px) { .md__hide-sm:where([data-rk-df9b34]) { display: none; } }

Información Rápida

Categoría
Gráficos y Estadísticas
Nombre de archivo
MetricDeltaTable.tsx
Dependencias
Ninguna — React + CSS puro
Etiquetas
chartsvgzero-dependencytablecomparisondeltasparkline