jengacore.shadcn registry · Tailwind v4 · Radix and Base UI
Components
jengacore components · @jengacore/kpi-strip

KpiStrip

One ledger of 2–4 numbers in Bricolage with a comparison line each. Not floating tiles.

Screened today1,284,330+4.1% vs last Monday
Open alerts427 over SLA · 9 unassigned
Blocked value38,920USD12 transfers
Median decision14MINTarget 30 min · met

Install

terminal
npx shadcn@latest add @jengacore/kpi-strip

Without the namespace configured: npx shadcn@latest add https://ui.jengacore.com/r/<new-york-v4|base-nova>/kpi-strip.json

Registry dependencies

Source

components/jc/kpi-strip.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

export type Kpi = {
  /** Mono eyebrow label. */
  label: string
  value: React.ReactNode
  /** Unit or currency, set in mono after the value. */
  unit?: string
  /** One comparison line. Wrap the meaningful part in <b> to colour it. */
  note?: React.ReactNode
  /** Colour the <b> in the note only when the change is good or bad for the reader. */
  trend?: "up" | "down" | "warn"
  /** Optional sparkline: recent values, oldest first. Drawn in the chart colour, with the first value as a baseline. */
  spark?: number[]
  /** Chart series colour for the sparkline (1 = ours/primary … 4 = baseline grey). */
  series?: 1 | 2 | 3 | 4
}

/** A 200×30 sparkline: no axes, no fill, no animation of the number itself. */
function Sparkline({ values, series = 1 }: { values: number[]; series?: 1 | 2 | 3 | 4 }) {
  if (values.length < 2) return null
  const min = Math.min(...values)
  const max = Math.max(...values)
  const y = (v: number) => (max === min ? 15 : 27 - ((v - min) / (max - min)) * 24)
  const step = 200 / (values.length - 1)
  const d = values.map((v, i) => `${i ? "L" : "M"}${(i * step).toFixed(1)} ${y(v).toFixed(1)}`).join(" ")
  return (
    <svg viewBox="0 0 200 30" preserveAspectRatio="none" aria-hidden className="mt-1.5 block h-[30px] w-full">
      <path d={`M0 ${y(values[0] ?? min).toFixed(1)} H200`} className="stroke-border" strokeWidth={1} strokeDasharray="2 3" fill="none" vectorEffect="non-scaling-stroke" />
      <path d={d} stroke={`var(--chart-${series})`} strokeWidth={1.5} fill="none" strokeLinejoin="round" vectorEffect="non-scaling-stroke" />
    </svg>
  )
}

/**
 * One ledger of 2–4 numbers (not floating tiles). Value in Bricolage with tabular figures.
 * Never fake precision: round to what an operator acts on.
 */
export function KpiStrip({ items, className }: { items: Kpi[]; className?: string }) {
  return (
    <section
      data-slot="kpi-strip"
      className={cn(
        "grid overflow-hidden rounded-lg border border-border bg-card shadow-card max-md:grid-cols-2! contrast:border-input",
        className
      )}
      style={{ gridTemplateColumns: `repeat(${Math.min(items.length, 4)}, minmax(0, 1fr))` }}
    >
      {items.slice(0, 4).map((k) => (
        <div key={k.label} className="flex min-w-0 flex-col gap-1 border-r border-b border-border px-5 pt-4 pb-3 last:border-r-0 md:border-b-0">
          <span className="eyebrow">{k.label}</span>
          <span className="font-display text-[30px] leading-9 font-semibold tracking-[-0.01em] tabular-nums">
            {k.value}
            {k.unit ? <small className="ml-1.5 font-mono text-xs font-medium tracking-normal text-muted-foreground">{k.unit}</small> : null}
          </span>
          {k.note ? (
            <span
              className={cn(
                "text-[12.5px] text-muted-foreground [&_b]:font-medium",
                k.trend === "up" && "[&_b]:text-success",
                k.trend === "down" && "[&_b]:text-destructive",
                k.trend === "warn" && "[&_b]:text-warning"
              )}
            >
              {k.note}
            </span>
          ) : null}
          {k.spark ? <Sparkline values={k.spark} series={k.series} /> : null}
        </div>
      ))}
    </section>
  )
}