Metrics Grid
blockanalyticsSix-tile KPI status grid. Each tile pairs a stat header (count / percentage) with a small pie or horizontal bar chart that breaks the metric down. Different tiles use different chart types so each KPI reads at a glance.
Installation
$pnpm dlx shadcn@latest add https://uipkge.dev/r/lit/metrics-grid.json$npx shadcn@latest add https://uipkge.dev/r/lit/metrics-grid.json$yarn dlx shadcn@latest add https://uipkge.dev/r/lit/metrics-grid.json$bunx shadcn@latest add https://uipkge.dev/r/lit/metrics-grid.jsonManifest:
https://uipkge.dev/r/lit/metrics-grid.jsonInstalls to:src/uip/blocks/metrics-grid/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
HTMLElementTagNameMapinterface HTMLElementTagNameMap {
'uip-metrics-grid': UipMetricsGrid
}
}Theming
CSS custom properties referenced in this item. Override any of them in your :root or per-element to retheme.
--chart-1--chart-3--chart-4--chart-5Files installed (1)
src/uip/blocks/metrics-grid/metrics-grid.ts9 kB
import { LitElement, css, html } from 'lit' import { tailwind } from '../../lib/styles' import { ThemeController } from '../../lib/theme' // Side-effect imports: register the composed elements for standalone use // (consumers who install only this block still get working primitives). import '../../components/charts/pie-chart' import '../../components/charts/bar-chart' // Six-tile KPI grid. Each tile renders a labeled KPI header (title + big // number / percentage / count) above a small chart -- pie or horizontal bar // -- that gives the breakdown. Tile shapes are spelled out inline as siblings. const totalRuns = 770 const runsByStatus = [ { name: 'Successful', value: 638 }, { name: 'Partial', value: 0 }, { name: 'Error / Skipped', value: 132 }, ] const totalProcessed = 638 const dispositionSplit = [ { name: 'Non-disputable', value: 168 }, { name: 'Disputable', value: 470 }, ] const totalClassified = 638 const classification = [ { name: 'Type A', value: 525 }, { name: 'Type B', value: 113 }, ] const skipReasons = [ { reason: 'CREDIT_NOTE', count: 11 }, { reason: 'CUSTOMS_INSPECT', count: 5 }, { reason: 'NEGATIVE_COST', count: 8 }, ] const totalSkipped = skipReasons.reduce((a, r) => a + r.count, 0) const errorReasons = [ { reason: 'INVALID_CUTOFF', count: 3 }, { reason: 'NO_SHIPMENT_DATA', count: 20 }, { reason: 'SELF_BILLING', count: 42 }, { reason: 'MISSING_OBLIGATORY_FIELD', count: 2 }, { reason: 'MISSING_OBLIGATORY_TIME', count: 4 }, { reason: 'UNABLE_TO_CLASSIFY', count: 40 }, ] const totalErrors = errorReasons.reduce((a, r) => a + r.count, 0) const openShare = 0.7602 const workflowStatus = [ { name: 'Open', value: 485 }, { name: 'Pending', value: 151 }, { name: 'Closed', value: 2 }, ] const pieOption = { legend: { show: false }, series: [ { radius: ['0%', '70%'], label: { show: true, formatter: (p: any) => `${p.value}\n(${p.percent}%)`, fontSize: 11, color: '#fff', fontWeight: 600, }, labelLine: { show: false }, }, ], } const donutOption = { legend: { show: false }, series: [ { radius: ['40%', '70%'], label: { show: true, formatter: (p: any) => `${p.value}\n(${p.percent}%)`, fontSize: 11, color: '#fff', fontWeight: 600, }, labelLine: { show: false }, }, ], } const horizontalBarOption = { legend: { show: false }, xAxis: { type: 'value' as const, axisLabel: { fontSize: 10 } }, yAxis: { type: 'category' as const, axisLabel: { fontSize: 10 } }, grid: { left: 16, right: 16, top: 8, bottom: 24, containLabel: true }, series: [ { type: 'bar' as const, barMaxWidth: 18, itemStyle: { borderRadius: [0, 3, 3, 0] }, }, ], } /** * <uip-metrics-grid> — the registry MetricsGrid block as a web component. * * Datasets, option objects, and classes are React's verbatim. Tiles are * spelled out inline (different chart types per tile are the point of the * layout). No props, no events. Chart data/options are properties bound from * the module constants above. */ export class UipMetricsGrid extends LitElement { static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true } // :host display can't be a utility class (the host has no template of its own). static styles = [tailwind, css`:host { display: block; }`] constructor() { super() new ThemeController(this) } connectedCallback() { super.connectedCallback() this.setAttribute('data-uipkge', '') this.setAttribute('data-slot', 'metrics-grid') } render() { return html` <div data-slot="metrics-grid" class="space-y-4"> <h2 class="text-primary text-sm font-semibold tracking-tight">Results Overview</h2> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"> <article class="bg-card border-border rounded-lg border p-4"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Runs by status</h3> <div class="text-primary mt-1 mb-2 text-2xl font-semibold">${totalRuns} runs</div> <div class="text-muted-foreground mb-3 flex items-center gap-3 text-xs"> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-1)"></span> Successful </span> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-4)"></span> Error / Skipped </span> </div> <uip-pie-chart .data=${runsByStatus} .option=${pieOption} height="220"></uip-pie-chart> </article> <article class="bg-card border-border rounded-lg border p-4"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Disposition split</h3> <div class="text-primary mt-1 mb-2 text-2xl font-semibold">${totalProcessed} processed</div> <div class="text-muted-foreground mb-3 flex items-center gap-3 text-xs"> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-1)"></span> Non-disputable </span> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-3)"></span> Disputable </span> </div> <uip-pie-chart .data=${dispositionSplit} .option=${pieOption} height="220"></uip-pie-chart> </article> <article class="bg-card border-border rounded-lg border p-4"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Classification</h3> <div class="text-primary mt-1 mb-2 text-2xl font-semibold">${totalClassified} classified</div> <div class="text-muted-foreground mb-3 flex items-center gap-3 text-xs"> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-1)"></span> Type A </span> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-3)"></span> Type B </span> </div> <uip-pie-chart .data=${classification} .option=${pieOption} height="220"></uip-pie-chart> </article> <article class="bg-card border-border rounded-lg border p-4"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Top skip reasons</h3> <div class="text-primary mt-1 mb-2 text-2xl font-semibold">${totalSkipped} skipped</div> <div class="text-muted-foreground mb-3 text-xs">By trigger</div> <uip-bar-chart .data=${skipReasons} x-field="reason" y-field="count" .option=${horizontalBarOption} height="220" ></uip-bar-chart> </article> <article class="bg-card border-border rounded-lg border p-4"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Top error reasons</h3> <div class="text-primary mt-1 mb-2 text-2xl font-semibold">${totalErrors} errors</div> <div class="text-muted-foreground mb-3 text-xs">By failure mode</div> <uip-bar-chart .data=${errorReasons} x-field="reason" y-field="count" .option=${horizontalBarOption} height="220" ></uip-bar-chart> </article> <article class="bg-card border-border rounded-lg border p-4"> <h3 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Workflow status</h3> <div class="text-primary mt-1 mb-2 text-2xl font-semibold">${(openShare * 100).toFixed(2)}% open</div> <div class="text-muted-foreground mb-3 flex items-center gap-3 text-xs"> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-1)"></span> Open </span> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-5)"></span> Pending </span> <span class="inline-flex items-center gap-1.5"> <span class="size-2 rounded-full" style="background:var(--chart-3)"></span> Closed </span> </div> <uip-pie-chart .data=${workflowStatus} .option=${donutOption} donut height="220"></uip-pie-chart> </article> </div> </div> ` } } customElements.get('uip-metrics-grid') || customElements.define('uip-metrics-grid', UipMetricsGrid) declare global { interface HTMLElementTagNameMap { 'uip-metrics-grid': UipMetricsGrid } }
Raw manifest:https://uipkge.dev/r/lit/metrics-grid.json