Runtime Overview Panel

Top-of-dock summary tile grid for runtime health when no canvas object is selected.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/runtime-overview-panel.json
bash

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client";

import {
  type ComponentPropsWithoutRef,
  forwardRef,
  type ReactNode,
} from "react";

import { cn } from "../../lib/utils";

/**
 * Trend direction for a runtime metric.
 *
 * @public
 */
export type RuntimeMetricTrend = "down" | "flat" | "up";

/**
 * Tone of the metric value — drives the dot color.
 *
 * @public
 */
export type RuntimeMetricTone = "danger" | "neutral" | "success" | "warn";

/**
 * One metric tile in the runtime overview.
 *
 * @public
 */
export type RuntimeMetric = {
  /** Optional secondary line (delta, target, owner). */
  detail?: ReactNode;
  /** Stable identifier — used as the React key. */
  id: string;
  /** Short label (e.g. `"Active runs"`). */
  label: ReactNode;
  /** Optional tone for the dot. Defaults to `"neutral"`. */
  tone?: RuntimeMetricTone;
  /** Optional trend arrow next to the value. */
  trend?: RuntimeMetricTrend;
  /** Display value (already formatted by the host). */
  value: ReactNode;
};

/**
 * Localizable strings.
 *
 * @public
 */
export type RuntimeOverviewPanelLabels = {
  /** Empty-state copy. Defaults to `"No runtime metrics"`. */
  empty?: string;
  /** Aria-label for the panel. Defaults to `"Runtime overview"`. */
  region?: string;
};

const DEFAULT_LABELS = {
  empty: "No runtime metrics",
  region: "Runtime overview",
} as const satisfies Required<RuntimeOverviewPanelLabels>;

const TONE_DOT: Record<RuntimeMetricTone, string> = {
  danger: "bg-red-500",
  neutral: "bg-muted-foreground",
  success: "bg-emerald-500",
  warn: "bg-amber-500",
};

const TREND_GLYPH: Record<RuntimeMetricTrend, string> = {
  down: "↓",
  flat: "·",
  up: "↑",
};

/**
 * Props for {@link RuntimeOverviewPanel}.
 *
 * @public
 */
export type RuntimeOverviewPanelProps = {
  /** Localizable strings. */
  labels?: RuntimeOverviewPanelLabels;
  /** Metric tiles in render order. */
  metrics: RuntimeMetric[];
  /** Panel title. Defaults to `"Runtime"`. */
  title?: ReactNode;
} & ComponentPropsWithoutRef<"section">;

const Tile = (props: { metric: RuntimeMetric }): React.ReactElement => {
  const { metric } = props;
  const tone = metric.tone ?? "neutral";
  return (
    <li
      className="flex flex-col gap-1 rounded-md border border-border/60 bg-muted/20 px-2.5 py-2"
      data-runtime-metric={metric.id}
      data-runtime-tone={tone}
    >
      <span className="flex items-center gap-1.5 text-[10px] uppercase tracking-wide text-muted-foreground">
        <span
          aria-hidden="true"
          className={cn("size-1.5 rounded-full", TONE_DOT[tone])}
        />
        {metric.label}
      </span>
      <span className="flex items-baseline gap-1.5 text-sm font-semibold text-foreground">
        <span>{metric.value}</span>
        {metric.trend ? (
          <span
            aria-hidden="true"
            className="text-[10px] text-muted-foreground"
          >
            {TREND_GLYPH[metric.trend]}
          </span>
        ) : null}
      </span>
      {metric.detail ? (
        <span className="text-[10px] text-muted-foreground">
          {metric.detail}
        </span>
      ) : null}
    </li>
  );
};

/**
 * Top-of-dock summary for the runtime when nothing on the canvas is
 * selected. Renders a compact grid of metric tiles (active runs,
 * throughput, error rate, etc.). Pure presentation; the host computes
 * the metric list from the runtime stream.
 *
 * @example
 * ```tsx
 * <RuntimeOverviewPanel
 *   metrics={[
 *     { id: "runs",  label: "Active runs", value: 3, tone: "success" },
 *     { id: "errs",  label: "Errors / hr", value: 0, tone: "neutral", trend: "flat" },
 *     { id: "tps",   label: "Throughput", value: "120 / s", tone: "success", trend: "up" },
 *   ]}
 * />
 * ```
 *
 * @public
 */
export const RuntimeOverviewPanel = forwardRef<
  HTMLElement,
  RuntimeOverviewPanelProps
>((props, ref) => {
  const { className, labels, metrics, title = "Runtime", ...rest } = props;
  const resolvedLabels = { ...DEFAULT_LABELS, ...labels };
  return (
    <section
      aria-label={resolvedLabels.region}
      className={cn(
        "flex w-full flex-col gap-2 rounded-2xl border bg-background p-3 text-foreground",
        className,
      )}
      data-runtime-overview-panel
      ref={ref}
      {...rest}
    >
      <header>
        <h3 className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
          {title}
        </h3>
      </header>
      {metrics.length === 0 ? (
        <p
          className="px-2 py-3 text-center text-xs text-muted-foreground"
          data-runtime-state="empty"
        >
          {resolvedLabels.empty}
        </p>
      ) : (
        <ul className="grid grid-cols-2 gap-1.5" data-runtime-grid>
          {metrics.map((metric) => (
            <Tile key={metric.id} metric={metric} />
          ))}
        </ul>
      )}
    </section>
  );
});
RuntimeOverviewPanel.displayName = "RuntimeOverviewPanel";
typescript

Dependencies

  • @vllnt/ui@^0.2.1