- {/* View switcher floats in the card's top-right corner so it
- doesn't reserve a vertical band of its own. The chart fills
- the card top-to-bottom; the switcher sits in the existing
- card padding and never competes with the chart for space.
- Popover opens downward (default) since there's room below
- the corner before the chart geometry begins. */}
-
- {/* Fade-in until the localStorage view preference resolves.
- Without this the SSR-default view paints first, then
- useViewPreference swaps to the saved view in a visible
- second frame ("two charts at once" user report). The
- wrapper keeps a min-h so the card doesn't collapse to
- zero while the chart is invisible. */}
+
+ {/* Each chart owns its header row and accepts a headerActions
+ slot. We pass the ViewSwitcher there so the control sits
+ on the same baseline as the chart's own title text -
+ consistent across all views, no separate row reserved,
+ no absolute overlay that risks clipping the legend or
+ action buttons each chart already has on the right. */}
- {view === "countLeaderboard" && }
+ {view === "countLeaderboard" && (
+ }
+ />
+ )}
{view === "rankedBar" && (
}
/>
)}
{view === "distribution" && (
@@ -329,6 +322,7 @@ export function BenchmarkBody({
benchmark={benchmark}
excluded={excluded}
onToggleExclude={toggleExclude}
+ headerActions={ }
/>
)}
{view === "donut" && (
@@ -336,12 +330,14 @@ export function BenchmarkBody({
benchmark={benchmark}
excluded={excluded}
onToggleExclude={toggleExclude}
+ headerActions={ }
/>
)}
{view === "timeseries" && (
}
/>
)}
diff --git a/src/components/count-leaderboard.tsx b/src/components/count-leaderboard.tsx
index 0b864154..5e94342a 100644
--- a/src/components/count-leaderboard.tsx
+++ b/src/components/count-leaderboard.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useMemo } from "react";
+import { type ReactNode, useMemo } from "react";
import type { Benchmark } from "@/types/benchmark";
import { fmtValue } from "@/lib/format";
@@ -14,7 +14,13 @@ import { buildProviderColors } from "@/lib/series-colors";
* bar instead. the two visualizations that actually carry information
* for this metric shape.
*/
-export function CountLeaderboard({ benchmark }: { benchmark: Benchmark }) {
+export function CountLeaderboard({
+ benchmark,
+ headerActions,
+}: {
+ benchmark: Benchmark;
+ headerActions?: ReactNode;
+}) {
const ranked = rankResults(benchmark.results, benchmark.higherIsBetter);
const max = Math.max(...ranked.map((r) => r.ms.p50)) || 1;
const colors = useMemo(() => buildProviderColors(benchmark.results), [benchmark.results]);
@@ -27,8 +33,20 @@ export function CountLeaderboard({ benchmark }: { benchmark: Benchmark }) {
return (
<>
+ {/* Header row: leaderboard label + headerActions slot (view
+ switcher). Aligned with the same baseline pattern as the
+ other chart views so the toolbar position is consistent
+ across views. */}
+ {headerActions && (
+
+
+ Leaderboard
+
+ {headerActions}
+
+ )}
{/* Thin summary strip. matches the latency-bench layout. */}
-
+
;
onToggleExclude?: (slug: string) => void;
+ /** Optional slot rendered in the chart's header row, right-aligned.
+ * BenchmarkBody passes the here so the control sits
+ * on the same baseline as the chart title instead of floating in
+ * the card corner or eating a footer row of its own. */
+ headerActions?: ReactNode;
}) {
const { results, unit, higherIsBetter } = benchmark;
const { excluded, toggle } = useChartExclusion(
@@ -59,11 +65,14 @@ export function DistributionChart({
return (
-
+
Latency spread
-
+
+
+ {headerActions}
+
{sorted.map((r) => {
diff --git a/src/components/donut-chart.tsx b/src/components/donut-chart.tsx
index d0d5a567..1ec44253 100644
--- a/src/components/donut-chart.tsx
+++ b/src/components/donut-chart.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useMemo, useState } from "react";
+import { type ReactNode, useMemo, useState } from "react";
import type { Benchmark } from "@/types/benchmark";
import { liveResults } from "@/lib/provider-filters";
import { ProviderLogo } from "@/components/provider-logo";
@@ -36,10 +36,12 @@ export function DonutChart({
benchmark,
excluded: controlledExcluded,
onToggleExclude,
+ headerActions,
}: {
benchmark: Benchmark;
excluded?: Set;
onToggleExclude?: (slug: string) => void;
+ headerActions?: ReactNode;
}) {
const { results } = benchmark;
const { excluded, toggle } = useChartExclusion(
@@ -106,13 +108,16 @@ export function DonutChart({
return (
-
+
Share by p50
-
- {live.length} of {liveAll.length} live
-
+
+
+ {live.length} of {liveAll.length} live
+
+ {headerActions}
+
;
onToggleExclude?: (slug: string) => void;
onResetExcluded?: () => void;
+ /** Optional slot rendered in the chart's header row, right-aligned.
+ * BenchmarkBody passes the
here. */
+ headerActions?: import("react").ReactNode;
};
/**
@@ -45,6 +48,7 @@ export function RankedBarChart({
excluded: controlledExcluded,
onToggleExclude,
onResetExcluded,
+ headerActions,
}: Props) {
const { excluded, toggle, reset } = useChartExclusion(
controlledExcluded,
@@ -100,20 +104,23 @@ export function RankedBarChart({
return (
-
+
{benchmark.metric} · last 24 hours
- {excludedCount > 0 && (
-
- Reset · {excludedCount} excluded
-
- )}
+
+ {excludedCount > 0 && (
+
+ Reset · {excludedCount} excluded
+
+ )}
+ {headerActions}
+
{rows.map((r) => {
diff --git a/src/components/time-series-chart.tsx b/src/components/time-series-chart.tsx
index 1f3ff18a..402f2a57 100644
--- a/src/components/time-series-chart.tsx
+++ b/src/components/time-series-chart.tsx
@@ -14,6 +14,8 @@ type Props = {
* filters its lines by this value and hides its internal region tabs
* (the parent component renders them in a shared dimension row). */
region?: string;
+ /** Optional slot rendered in the chart's header row, right-aligned. */
+ headerActions?: import("react").ReactNode;
};
type Range = "1h" | "6h" | "24h" | "7d";
@@ -40,7 +42,7 @@ const REGION_LABEL: Record = {
global: "Global",
};
-export function TimeSeriesChart({ benchmark, region: regionProp }: Props) {
+export function TimeSeriesChart({ benchmark, region: regionProp, headerActions }: Props) {
const [range, setRange] = useState("24h");
const [regionLocal, setRegionLocal] = useState("all");
const region = regionProp ?? regionLocal;
@@ -88,12 +90,15 @@ export function TimeSeriesChart({ benchmark, region: regionProp }: Props) {
return (
-
-
-
- {benchmark.metric} · {RANGE_LABEL[range]}
-
-
+
+
+
+
+ {benchmark.metric} · {RANGE_LABEL[range]}
+
+
+ {headerActions}
+