TanStack
Catalog

Premium KPI sparklines

composition

485 lines · 2 files · 12.5 kB

cases/122-premium-kpi-sparklines/example.tsx386 lines · entry
cases/122-premium-kpi-sparklines/example.tsx
import { useMemo, useRef } from 'react'
import { Chart } from '@tanstack/charts/react/core'
import { premiumKpisForRevision } from './model'
import type { CSSProperties } from 'react'
import type { PremiumKpiMetric } from './model'
import { areaY, d3Curve, defineChart, lineY } from '@tanstack/charts'
import { motion } from '@tanstack/charts/motion'
import { scaleLinear } from '@tanstack/charts/scales/linear'
import { scalePoint } from '@tanstack/charts/scales/point'
import { curveMonotoneX } from 'd3-shape'
import type { PremiumKpiId, PremiumKpiPoint } from './model'
const fullGap = 12

function KpiCard({
  metric,
  width,
  height,
  idPrefix,
  preview = false,
}: {
  readonly metric: PremiumKpiMetric
  readonly width: number
  readonly height: number
  readonly idPrefix?: string
  readonly preview?: boolean
}) {
  const definition = useMemo(() => premiumKpiDefinition(metric), [metric])
  const renderer = useMemo(() => createPremiumKpiRenderer(!preview), [preview])
  const compact = preview || width < 200 || height < 150
  const padding = compact ? 9 : 18
  const headerHeight = compact ? 44 : 72
  const chartWidth = Math.max(24, width - padding * 2)
  const chartHeight = Math.max(24, height - padding * 2 - headerHeight)

  return (
    <article
      data-premium-kpi={metric.id}
      data-conformance-view={metric.id}
      data-compact={compact || undefined}
      className="premium-kpi-card"
      style={
        {
          '--premium-kpi-card-width': `${width}px`,
          '--premium-kpi-card-height': `${height}px`,
        } as CSSProperties
      }
    >
      <header className="premium-kpi-header">
        <span className="premium-kpi-label">{metric.label}</span>
        <span
          className="premium-kpi-trend"
          aria-label={`${metric.trendDirection === 'up' ? 'Up' : 'Down'} ${metric.trend}`}
        >
          <span aria-hidden="true">
            {metric.trendDirection === 'up' ? '↑' : '↓'}
          </span>{' '}
          {metric.trend}
        </span>
        <strong className="premium-kpi-value">{metric.value}</strong>
      </header>
      <div className="premium-kpi-chart">
        <Chart
          idPrefix={idPrefix ? `${idPrefix}-${metric.id}` : undefined}
          definition={definition}
          renderer={renderer}
          width={chartWidth}
          height={chartHeight}
          ariaLabel={`${metric.label} trend, ending at ${metric.value}`}
        />
      </div>
    </article>
  )
}

function fullLayout(width: number, height: number) {
  const padding = 14
  const availableWidth = Math.max(1, width - padding * 2)
  const availableHeight = Math.max(1, height - padding * 2)
  const horizontal = width >= 680 || width / height >= 1.2

  return horizontal
    ? {
        columns: 'repeat(3, minmax(0, 1fr))',
        rows: 'minmax(0, 1fr)',
        cardWidth: Math.max(1, (availableWidth - fullGap * 2) / 3),
        cardHeight: availableHeight,
      }
    : {
        columns: 'minmax(0, 1fr)',
        rows: 'repeat(3, minmax(0, 1fr))',
        cardWidth: availableWidth,
        cardHeight: Math.max(1, (availableHeight - fullGap * 2) / 3),
      }
}

const premiumKpiStyles = `
.premium-kpi-shell {
  --premium-kpi-canvas: light-dark(#f5f6f8, #09090b);
  --premium-kpi-card: light-dark(rgba(255, 255, 255, 0.92), rgba(24, 24, 27, 0.9));
  --premium-kpi-foreground: light-dark(#18181b, #fafafa);
  --premium-kpi-muted: light-dark(#71717a, #a1a1aa);
  --premium-kpi-border: light-dark(rgba(24, 24, 27, 0.09), rgba(250, 250, 250, 0.1));
  --premium-kpi-shadow: light-dark(rgba(24, 24, 27, 0.07), rgba(0, 0, 0, 0.34));
  color-scheme: inherit;
  box-sizing: border-box;
  display: grid;
  place-items: stretch;
  padding: 14px;
  overflow: hidden;
  color: var(--premium-kpi-foreground);
  background: var(--premium-kpi-canvas);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

.premium-kpi-grid {
  display: grid;
  min-width: 0;
  min-height: 0;
}

.premium-kpi-card {
  --premium-kpi-accent: #6d5dfc;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  width: var(--premium-kpi-card-width);
  height: var(--premium-kpi-card-height);
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  padding: 18px;
  border: 1px solid var(--premium-kpi-border);
  border-radius: 18px;
  background: var(--premium-kpi-card);
  box-shadow: 0 14px 42px var(--premium-kpi-shadow);
}

.premium-kpi-card[data-premium-kpi="customers"] {
  --premium-kpi-accent: #0f91c7;
}

.premium-kpi-card[data-premium-kpi="churn"] {
  --premium-kpi-accent: #0c9b6c;
}

.premium-kpi-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  column-gap: 8px;
  flex: 0 0 72px;
}

.premium-kpi-label {
  overflow: hidden;
  color: var(--premium-kpi-muted);
  font-size: 12px;
  font-weight: 620;
  line-height: 1.2;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.premium-kpi-value {
  grid-column: 1 / -1;
  margin-top: 7px;
  font-size: clamp(24px, 3.1vw, 36px);
  font-weight: 730;
  letter-spacing: -0.055em;
  line-height: 1;
}

.premium-kpi-trend {
  color: var(--premium-kpi-accent);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
}

.premium-kpi-chart {
  display: grid;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  align-items: end;
}

.premium-kpi-preview {
  display: flex;
  gap: 5px;
  padding: 0;
  background: transparent;
}

.premium-kpi-card[data-compact] {
  padding: 9px;
  border-radius: 11px;
  box-shadow: 0 6px 18px var(--premium-kpi-shadow);
}

.premium-kpi-card[data-compact] .premium-kpi-header {
  flex-basis: 44px;
  column-gap: 4px;
}

.premium-kpi-card[data-compact] .premium-kpi-label {
  font-size: 7px;
}

.premium-kpi-card[data-compact] .premium-kpi-value {
  margin-top: 3px;
  font-size: 15px;
}

.premium-kpi-card[data-compact] .premium-kpi-trend {
  font-size: 7px;
}

.premium-kpi-preview-stack {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}

`

export interface ExampleProps {
  width?: number
  height?: number
  revision?: number
}

const monotone = d3Curve(curveMonotoneX)

const accentFallbacks = {
  revenue: '#6d5dfc',
  customers: '#0f91c7',
  churn: '#0c9b6c',
} satisfies Record<PremiumKpiId, string>

export const premiumKpiSpring = {
  type: 'spring' as const,
  stiffness: 180,
  damping: 24,
  mass: 0.8,
}

export function premiumKpiDefinition(metric: PremiumKpiMetric) {
  const values = metric.rows.map((row) => row.value)
  const minimum = Math.min(...values)
  const maximum = Math.max(...values)
  const padding = Math.max((maximum - minimum) * 0.16, 0.1)
  const baseline = minimum - padding
  const accent = `var(--premium-kpi-accent, ${accentFallbacks[metric.id]})`
  const line = lineY(metric.rows, {
    id: `${metric.id}-line`,
    x: 'period',
    y: 'value',
    key: 'id',
    stroke: accent,
    strokeWidth: 2.4,
    curve: monotone,
  })
  const marks =
    metric.surface === 'area'
      ? [
          areaY(metric.rows, {
            id: `${metric.id}-area`,
            x: 'period',
            y: 'value',
            y1: baseline,
            key: 'id',
            fill: `url(#${metric.id}-fill)`,
            fillOpacity: 1,
            curve: monotone,
          }),
          line,
        ]
      : [line]

  return defineChart(
    defineChart({
      guides: false,
      marks,
      gradients:
        metric.surface === 'area'
          ? [
              {
                id: `${metric.id}-fill`,
                x1: 0,
                y1: 0,
                x2: 0,
                y2: 1,
                stops: [
                  {
                    offset: 0,
                    color: accent,
                    opacity: 0.26,
                  },
                  {
                    offset: 1,
                    color: accent,
                    opacity: 0,
                  },
                ],
              },
            ]
          : [],
      x: {
        scale: scalePoint<number>()
          .domain(metric.rows.map((row) => row.period))
          .padding(0.08),
        axis: false,
      },
      y: {
        scale: scaleLinear().domain([baseline, maximum + padding]),
        axis: false,
      },
      margin: { top: 4, right: 3, bottom: 3, left: 3 },
      clip: true,
      motion: { transition: premiumKpiSpring },
    }),
    {
      focus: false,
      pointer: false,
      keyboard: false,
      tooltip: false,
      svgAnimation: false,
    },
  )
}

export function createPremiumKpiRenderer(initial = true) {
  return motion<PremiumKpiPoint, number, number>({
    initial,
    respectReducedMotion: true,
    transition: premiumKpiSpring,
  })
}

export default function PremiumKpiSparklines({
  width = 640,
  height = 480,
  revision = 0,
}: ExampleProps = {}) {
  const input = { width, height, revision, preview: false, interactive: true }
  const idPrefix = '122-premium-kpi-sparklines'
  const rootRef = useRef<HTMLElement>(null)

  const metrics = premiumKpisForRevision(input.revision)

  const layout = fullLayout(input.width, input.height)

  return (
    <section
      ref={rootRef}
      data-conformance-view="main"
      data-premium-kpi-shell=""
      aria-label="Business metrics"
      className="premium-kpi-shell"
      style={{ width: input.width, height: input.height }}
    >
      <style>{premiumKpiStyles}</style>
      <div
        className="premium-kpi-grid"
        style={{
          gridTemplateColumns: layout.columns,
          gridTemplateRows: layout.rows,
          gap: fullGap,
        }}
      >
        {metrics.map((metric) => (
          <KpiCard
            key={metric.id}
            metric={metric}
            width={layout.cardWidth}
            height={layout.cardHeight}
            idPrefix={idPrefix}
          />
        ))}
      </div>
    </section>
  )
}
cases/122-premium-kpi-sparklines/model.ts99 lines · dependency
cases/122-premium-kpi-sparklines/model.ts
export type PremiumKpiId = 'revenue' | 'customers' | 'churn'

export interface PremiumKpiPoint {
  readonly id: string
  readonly period: number
  readonly value: number
}

export interface PremiumKpiMetric {
  readonly id: PremiumKpiId
  readonly label: string
  readonly value: string
  readonly trend: string
  readonly trendDirection: 'up' | 'down'
  readonly surface: 'area' | 'line'
  readonly rows: readonly PremiumKpiPoint[]
}

const stages = [
  {
    revenue: [
      264_400, 281_200, 276_800, 302_600, 319_100, 337_500, 331_800, 356_200,
      371_600, 365_900, 394_300, 412_840,
    ],
    customers: [
      2_810, 2_950, 3_030, 3_170, 3_120, 3_290, 3_410, 3_500, 3_590, 3_670,
      3_750, 3_842,
    ],
    churn: [2.8, 2.6, 2.7, 2.5, 2.4, 2.3, 2.35, 2.15, 2.1, 1.95, 1.86, 1.7],
  },
  {
    revenue: [
      279_200, 288_900, 286_400, 315_300, 329_800, 351_500, 345_900, 369_600,
      388_100, 381_700, 406_900, 429_180,
    ],
    customers: [
      2_940, 3_020, 3_140, 3_240, 3_210, 3_380, 3_500, 3_610, 3_690, 3_790,
      3_880, 3_976,
    ],
    churn: [
      2.7, 2.55, 2.62, 2.42, 2.33, 2.2, 2.24, 2.05, 1.98, 1.84, 1.75, 1.62,
    ],
  },
] as const

export function premiumKpisForRevision(
  revision: number,
): readonly PremiumKpiMetric[] {
  const stage = stages[Math.abs(revision) % stages.length] ?? stages[0]

  return [
    {
      id: 'revenue',
      label: 'Monthly revenue',
      value: formatRevenue(last(stage.revenue)),
      trend: revision % 2 === 0 ? '+9.2%' : '+10.6%',
      trendDirection: 'up',
      surface: 'area',
      rows: points('revenue', stage.revenue),
    },
    {
      id: 'customers',
      label: 'Active customers',
      value: last(stage.customers).toLocaleString('en-US'),
      trend: revision % 2 === 0 ? '+6.4%' : '+7.1%',
      trendDirection: 'up',
      surface: 'line',
      rows: points('customers', stage.customers),
    },
    {
      id: 'churn',
      label: 'Net churn',
      value: `${last(stage.churn).toFixed(1)}%`,
      trend: revision % 2 === 0 ? '−0.5 pt' : '−0.6 pt',
      trendDirection: 'down',
      surface: 'area',
      rows: points('churn', stage.churn),
    },
  ]
}

function points(
  metric: PremiumKpiId,
  values: readonly number[],
): readonly PremiumKpiPoint[] {
  return values.map((value, period) => ({
    id: `${metric}-${period}`,
    period,
    value,
  }))
}

function formatRevenue(value: number) {
  return `$${(value / 1_000).toFixed(1)}K`
}

function last(values: readonly number[]) {
  return values[values.length - 1] ?? 0
}