chartAreaStep
A Charts block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call chartAreaStep() with no arguments for a working demo, or edit the code below live.
Props
| Prop | Type | Description |
|---|---|---|
data | ChartAreaSinglePoint[] | — |
seriesLabel | string | — |
seriesColor | ThemeColor | — |
seriesIcon | ChartTrendDirection | Icon shown on the series in the footer instead of a plain trend arrow — demonstrates attaching an icon to the series definition. |
title | string | — |
description | string | — |
trendText | string | — |
trendDirection | ChartTrendDirection | — |
captionText | string | — |
height | number | — |
Implementation notes
Single-series staircase area (LineSeriesOption step:'end', horizontal-then-vertical segments per the engine's buildPixelPoints step logic), plus an optional seriesIcon prop that swaps the footer's default trend arrow for a caller-chosen icon (demonstrating 'attaching an icon to the series definition' per spec, since @domphy/chart's LineSeriesOption itself has no icon field). Same mount-reveal approximation caveat as chartAreaDefault. Direct-source-diff fix (2026-07-05): Tooltip was missing upstream's hideLabel (an extra date header row rendered that upstream's ChartTooltipContent suppresses), and horizontal gridlines were missing. Both fixed.
Status: partial · Reference: shadcn/ui original
// shadcn/ui "chart-area" (step recipe) — clean-room reimplementation.
//
// A single-series area chart whose outline/fill follows right-angle
// staircase steps between data points — each point sits mid-tread and the
// vertical jump is centered at the midpoint between adjacent x-values
// (recharts `type="step"` / d3 curveStep) — instead of a smooth or straight
// interpolation.
//
// Implemented purely from the block's public functional/visual spec — no
// upstream shadcn/ui source was viewed or copied.
import type { ChartOption, TooltipParams } from "@domphy/chart";
import type { DomphyElement } from "@domphy/core";
import type { ThemeColor } from "@domphy/theme";
import {
CHART_AREA_MONTHLY_DATA,
CHART_AREA_X_AXIS_BARE,
CHART_AREA_Y_AXIS_HIDDEN,
type ChartAreaSinglePoint,
type ChartTrendDirection,
chartAreaFrame,
chartAreaGradientFill,
chartAreaSeriesColor,
chartAreaTooltipRow,
chartCardShell,
chartTrendFooter,
chartTrendIcon,
wrapChartAreaTooltip,
} from "./chart-area-shared.js";
// Upstream's chartConfig assigns this series `icon: Activity`, and shadcn's
// ChartTooltipContent draws that config icon in place of the color-indicator
// dot on hover — even under `hideLabel`. The shared axis-tooltip formatter
// always emits a colored dot, so this recipe supplies its own formatter that
// swaps the dot for a small muted "activity" pulse glyph (an original
// clean-room waveform on a 24x24 grid, not lucide's exact path).
const ACTIVITY_TOOLTIP_ICON =
'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"' +
' stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"' +
' style="display:inline-block;width:10px;height:10px;margin-right:6px;opacity:0.6;vertical-align:middle;">' +
'<polyline points="2,12 7,12 10,5 14,19 17,12 22,12"></polyline></svg>';
function escapeTooltipText(text: string): string {
return text
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Axis-trigger tooltip: no category header (upstream `hideLabel`), one
// activity-icon + muted series-name (left) + mono value (right) row per
// series. Value is the bare number — upstream uses no value formatter here.
function stepTooltipFormatter(
paramsInput: TooltipParams | TooltipParams[],
): string {
const params = Array.isArray(paramsInput) ? paramsInput : [paramsInput];
if (params.length === 0) return "";
const rows = params
.map((p) => {
const label = escapeTooltipText(String(p.seriesName ?? p.name ?? ""));
return chartAreaTooltipRow(
ACTIVITY_TOOLTIP_ICON,
label,
escapeTooltipText(String(p.value ?? "")),
);
})
.join("");
return wrapChartAreaTooltip(rows);
}
export interface ChartAreaStepProps {
data?: ChartAreaSinglePoint[];
seriesLabel?: string;
seriesColor?: ThemeColor;
/** Icon shown on the series in the footer instead of a plain trend arrow — demonstrates attaching an icon to the series definition. */
seriesIcon?: ChartTrendDirection;
title?: string;
description?: string;
trendText?: string;
trendDirection?: ChartTrendDirection;
captionText?: string;
height?: number;
}
/**
* shadcn/ui "chart-area" step recipe — a single-series staircase area
* chart. Call with no arguments for a working demo.
*/
function chartAreaStep(props: ChartAreaStepProps = {}): DomphyElement<"div"> {
const {
data = CHART_AREA_MONTHLY_DATA,
seriesLabel = "Visitors",
seriesColor = "primary",
seriesIcon,
title = "Area Chart - Step",
description = "Showing total visitors for the last 6 months",
trendText = "Trending up by 5.2% this month",
trendDirection = "up",
captionText = `${data[0]?.month ?? ""} - ${data[data.length - 1]?.month ?? ""} 2026`,
height = 64,
} = props;
const categories = data.map((point) => point.month);
// Single-series recipe: upstream strokes the step at full-strength
// `var(--color-desktop)` (chart-2, a medium blue). Use the second ramp step
// for the fill but keep the engine-pinned stroke at full opacity — the
// faded-stroke approximation (0.45/0.72) renders the staircase as a ghost.
const ramp = chartAreaSeriesColor(1);
const option: ChartOption = {
tooltip: {
trigger: "axis",
axisPointer: { type: "none" },
formatter: stepTooltipFormatter,
},
xAxis: { ...CHART_AREA_X_AXIS_BARE, data: categories },
yAxis: CHART_AREA_Y_AXIS_HIDDEN,
// Upstream `<AreaChart margin={{ left: 12, right: 12 }}>`.
grid: { left: 12, right: 12, top: 12, bottom: 32, containLabel: false },
series: [
{
type: "line",
name: seriesLabel,
// recharts `<Area type="step">` maps to d3 curveStep: the vertical jump
// is centered at the midpoint between adjacent months (data points sit
// mid-tread), NOT held flat until the next x. Domphy's engine renders
// `step: "middle"` as that same centered staircase.
step: "middle",
showSymbol: false,
color: seriesColor,
// recharts <Area> default stroke is ~1px.
lineStyle: { width: 1, opacity: 1 },
areaStyle: {
color: chartAreaGradientFill("primary", 0.4, 0.4, ramp.tone),
opacity: 1,
},
data: data.map((point) => point.value),
},
],
};
return chartCardShell({
title,
description,
content: { div: [chartAreaFrame(option, height)] },
footer: chartTrendFooter({
trendText,
direction: trendDirection,
captionText,
trendIconOverride: seriesIcon ? chartTrendIcon(seriesIcon) : undefined,
}),
});
}
export { chartAreaStep };