Sextant
← Playground

Deploy Calendar

free

A GitHub-style calendar heatmap of deploy frequency — surfacing cadence, the busiest day, and week-over-week momentum.

Editable — click an element to tune it

npx shadcn add @sextant/eng-deploy-calendar

Production deployments

279

deploys · 12 weeks · busiest Thu

Deploys grew +22.7% WoW to 27 — driven by Thu pushes.

lessmore

GitHub Actions · last 12 weeks

Generated code

<EngDeployCalendar />

Source — this is the whole widget

"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { ChartCard } from "@/registry/default/widget-kit/chart-card";
import {
  ChartNarrative,
  resolveNarrative,
  type NarrativeProp,
} from "@/registry/default/chart-narrative/chart-narrative";
import type { Insight } from "@/registry/default/sextant-insights/types";
import { StudioTarget, useStudio } from "@/registry/default/widget-kit/studio";
import { SparkTile } from "@/registry/default/widget-kit/spark-tile";
import { useSizePlan } from "@/registry/default/widget-kit/use-chart-size";
import type { SizeProp } from "@/registry/default/widget-kit/chart-size";
import type { SizePlan } from "@/registry/default/widget-kit/size-plan";
import type {
  ChartClassNames,
  SeriesConfig,
} from "@/registry/default/widget-kit/contract";
import { safeData } from "@/registry/default/sextant-contracts/contracts";
import { ChartStates } from "@/registry/default/chart-states/chart-states";
import { deployDaysSchema, deployDays, type DeployDay } from "./data";

// Re-export the data contract so consumers get the schema + types from a
// single import (no reaching into ./data).
export { deployDaysSchema, type DeployDay } from "./data";

const COLOR_PRESETS = [
  { name: "Brand", value: "var(--accent-brand)" },
  { name: "Sage", value: "var(--chart-positive)" },
  { name: "Maroon", value: "var(--chart-1)" },
  { name: "Ink", value: "var(--chart-2)" },
  { name: "Cool gray", value: "var(--chart-3)" },
];

/** The single heat encoding: the deploy-count color ramp. */
export type DeployCalendarSeries = { deploys?: SeriesConfig };

type Props = {
  data?: DeployDay[];
  label?: string;
  attribution?: string;
  /** Shorthand for `classNames.root`. */
  className?: string;
  /** Targeted class overrides per region. */
  classNames?: ChartClassNames;
  /** Force a size tier, or "auto" (default) to adapt to the card's own width. */
  size?: SizeProp;

  // ── Customize ───────────────────────────────────────────────────────────
  /** Recolor the heat ramp: `series={{ deploys: { color: "var(--chart-positive)" } }}`. */
  series?: DeployCalendarSeries;
  /** Override count formatting (hero, tooltip). */
  valueFormatter?: (n: number) => string;
  /** Replace, hide (`false`), or fully render the one-line narrative. */
  narrative?: NarrativeProp;
};

const WEEKDAY = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
// Left rail shows alternating labels, GitHub-style.
const RAIL = ["Mon", "", "Wed", "", "Fri", "", ""];

/** Five intensity buckets → percentage of the base hue mixed over the card. */
const LEVEL_PCT = [0, 22, 44, 68, 100];

function bucket(count: number): number {
  if (count <= 0) return 0;
  if (count <= 1) return 1;
  if (count <= 3) return 2;
  if (count <= 5) return 3;
  return 4;
}

const COLLAPSE_PLAN: SizePlan = {
  sm: { shape: "compact" },
  mobile: { shape: "full" },
};

export function EngDeployCalendar(props: Props) {
  // Validate consumer data at the boundary: a bad shape shows the error
  // state instead of crashing. The built-in demo data is trusted, so skip it.
  const parsed =
    props.data === undefined ? null : safeData(deployDaysSchema, props.data);
  if (parsed && !parsed.ok) {
    return (
      <ChartStates status="error" shape="area" error={{ message: parsed.error }}>
        {null}
      </ChartStates>
    );
  }
  return <EngDeployCalendarImpl {...props} />;
}

function EngDeployCalendarImpl({
  data = deployDays,
  label,
  attribution,
  className,
  classNames,
  size = "auto",
  series,
  valueFormatter,
  narrative,
}: Props) {
  const studio = useStudio();
  const title = studio.text("title", label ?? "Production deployments", {
    label: "Chart title",
  });
  // `series.deploys.color` sets the default; Studio (when mounted) edits on top.
  const base = studio.color(
    "deploysColor",
    series?.deploys?.color ?? "var(--accent-brand)",
    { label: "Heat color", presets: COLOR_PRESETS },
  );
  const attributionText = studio.text(
    "attribution",
    attribution ?? "GitHub Actions · last 12 weeks",
    { label: "Attribution" },
  );
  const fmt = valueFormatter ?? ((n: number) => `${n}`);

  const stats = React.useMemo(() => {
    const total = data.reduce((s, d) => s + d.count, 0);
    // Busiest weekday by total volume.
    const byDow = new Array(7).fill(0) as number[];
    data.forEach((d, i) => {
      byDow[i % 7] += d.count;
    });
    let busiest = 0;
    for (let i = 1; i < 7; i++) if (byDow[i] > byDow[busiest]) busiest = i;
    // Recent 7 days vs the prior 7 — week-over-week momentum.
    const last7 = data.slice(-7).reduce((s, d) => s + d.count, 0);
    const prior7 = data.slice(-14, -7).reduce((s, d) => s + d.count, 0);
    const delta = prior7 > 0 ? (last7 - prior7) / prior7 : 0;
    const weeks = Math.ceil(data.length / 7);
    return { total, busiest: WEEKDAY[busiest], last7, delta, weeks };
  }, [data]);

  const insight: Insight | undefined =
    data.length >= 14
      ? {
          kind: "delta",
          metric: "Deploys",
          value: stats.last7,
          delta: stats.delta,
          period: "WoW",
          driver: `${stats.busiest} pushes`,
          format: "number",
        }
      : undefined;

  return (
    <ChartCard.Root
      size={size}
      className={cn(className, classNames?.root)}
      exportable
      exportTitle={title}
    >
      <Inner
        title={title}
        data={data}
        base={base}
        fmt={fmt}
        stats={stats}
        insight={insight}
        narrative={narrative}
        attributionText={attributionText}
        classNames={classNames}
      />
    </ChartCard.Root>
  );
}

type InnerProps = {
  title: string;
  data: DeployDay[];
  base: string;
  fmt: (n: number) => string;
  stats: {
    total: number;
    busiest: string;
    last7: number;
    delta: number;
    weeks: number;
  };
  insight: Insight | undefined;
  narrative: NarrativeProp | undefined;
  attributionText: string;
  classNames?: ChartClassNames;
};

function Inner({
  title,
  data,
  base,
  fmt,
  stats,
  insight,
  narrative,
  attributionText,
  classNames,
}: InnerProps) {
  const spec = useSizePlan(COLLAPSE_PLAN);

  if (spec.shape === "compact") {
    return (
      <SparkTile
        label={title}
        value={fmt(stats.total)}
        chip={
          narrative === false || !insight ? undefined : (
            <ChartNarrative
              {...(typeof narrative === "string" ? { text: narrative } : { insight })}
              variant="chip"
              className={classNames?.narrative}
            />
          )
        }
      />
    );
  }

  return (
    <>
      <ChartCard.Header className={classNames?.header}>
        <ChartCard.HeaderMain>
          <StudioTarget fieldKey="title" variant="inline">
            <ChartCard.Label>{title}</ChartCard.Label>
          </StudioTarget>
          <ChartCard.HeroNumber>{fmt(stats.total)}</ChartCard.HeroNumber>
          <p className="tabular text-sm text-muted-foreground">
            deploys · {stats.weeks} weeks · busiest {stats.busiest}
          </p>
        </ChartCard.HeaderMain>
      </ChartCard.Header>

      {resolveNarrative(narrative, insight, { className: classNames?.narrative })}

      <ChartCard.Body className={classNames?.body}>
        <div
          className="flex gap-2"
          role="img"
          aria-label={`${stats.total} deploys over ${stats.weeks} weeks; busiest on ${stats.busiest}.`}
        >
          {/* Weekday rail — same 7-row track as the grid, so labels align. */}
          <div
            aria-hidden
            className="grid gap-1 pr-1"
            style={{ gridTemplateRows: "repeat(7, 1fr)" }}
          >
            {RAIL.map((l, i) => (
              <span
                key={i}
                className="smallcaps flex items-center text-[9px] leading-none text-muted-foreground"
              >
                {l}
              </span>
            ))}
          </div>

          {/* Calendar — column-major (each column is one Mon→Sun week). */}
          <div
            className="grid flex-1 gap-1"
            style={{
              gridTemplateRows: "repeat(7, 1fr)",
              gridAutoFlow: "column",
              gridAutoColumns: "minmax(0, 1fr)",
            }}
          >
            {data.map((d) => {
              const lvl = bucket(d.count);
              const bg =
                lvl === 0
                  ? "color-mix(in oklch, var(--foreground) 6%, transparent)"
                  : `color-mix(in oklch, ${base} ${LEVEL_PCT[lvl]}%, transparent)`;
              return (
                <div
                  key={d.date}
                  title={`${d.date}: ${fmt(d.count)} ${d.count === 1 ? "deploy" : "deploys"}`}
                  className="aspect-square rounded-[3px]"
                  style={{ backgroundColor: bg }}
                />
              );
            })}
          </div>
        </div>

        {/* Less → more legend. */}
        <div className="mt-4 flex items-center justify-end gap-1.5 text-[10px] text-muted-foreground">
          <span className="smallcaps">less</span>
          {LEVEL_PCT.map((pct, i) => (
            <span
              key={i}
              className="size-2.5 rounded-[3px]"
              style={{
                backgroundColor:
                  i === 0
                    ? "color-mix(in oklch, var(--foreground) 6%, transparent)"
                    : `color-mix(in oklch, ${base} ${pct}%, transparent)`,
              }}
            />
          ))}
          <span className="smallcaps">more</span>
        </div>
      </ChartCard.Body>

      <StudioTarget fieldKey="attribution" variant="inline">
        <ChartCard.Footnote className={classNames?.footnote}>
          {attributionText}
        </ChartCard.Footnote>
      </StudioTarget>
    </>
  );
}