fix: guard recharts ResponsiveContainer behind measured container dimensions

This commit is contained in:
imagineer99 2026-03-05 21:54:27 +00:00
commit 5d907b0449
2 changed files with 81 additions and 37 deletions

View file

@ -46,22 +46,66 @@ function ChartContainer({
}) {
const uniqueId = React.useId();
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`;
const containerRef = React.useRef<HTMLDivElement | null>(null);
const [containerSize, setContainerSize] = React.useState<{
width: number;
height: number;
} | null>(null);
React.useEffect(() => {
const element = containerRef.current;
if (!element) return;
const updateSizeState = () => {
const { width, height } = element.getBoundingClientRect();
if (width > 0 && height > 0) {
setContainerSize({
width: Math.round(width),
height: Math.round(height),
});
return;
}
setContainerSize(null);
};
updateSizeState();
if (typeof ResizeObserver === "undefined") {
return;
}
const observer = new ResizeObserver(() => {
updateSizeState();
});
observer.observe(element);
return () => observer.disconnect();
}, []);
return (
<ChartContext.Provider value={{ config }}>
<div
ref={containerRef}
data-slot="chart"
data-chart={chartId}
className={cn(
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
"[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex min-w-0 aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
className,
)}
{...props}
>
<ChartStyle id={chartId} config={config} />
<RechartsPrimitive.ResponsiveContainer>
{children}
</RechartsPrimitive.ResponsiveContainer>
{containerSize ? (
<RechartsPrimitive.ResponsiveContainer
width="100%"
height="100%"
minWidth={0}
minHeight={1}
initialDimension={containerSize}
>
{children}
</RechartsPrimitive.ResponsiveContainer>
) : null}
</div>
</ChartContext.Provider>
);
@ -100,30 +144,30 @@ ${colorConfig
);
};
const ChartTooltip = RechartsPrimitive.Tooltip;
function ChartTooltipContent({
active,
payload,
className,
const ChartTooltip = RechartsPrimitive.Tooltip;
function ChartTooltipContent({
active,
payload,
className,
indicator = "dot",
hideLabel = false,
hideIndicator = false,
label,
labelFormatter,
labelClassName,
formatter,
color,
nameKey,
labelKey,
}: Partial<RechartsPrimitive.TooltipContentProps<any, any>> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
}) {
formatter,
color,
nameKey,
labelKey,
}: Partial<RechartsPrimitive.TooltipContentProps<any, any>> &
React.ComponentProps<"div"> & {
hideLabel?: boolean;
hideIndicator?: boolean;
indicator?: "line" | "dot" | "dashed";
nameKey?: string;
labelKey?: string;
}) {
const { config } = useChart();
const tooltipLabel = React.useMemo(() => {
@ -248,20 +292,20 @@ function ChartTooltipContent({
);
}
const ChartLegend = RechartsPrimitive.Legend;
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.DefaultLegendContentProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
}) {
const { config } = useChart();
const ChartLegend = RechartsPrimitive.Legend;
function ChartLegendContent({
className,
hideIcon = false,
payload,
verticalAlign = "bottom",
nameKey,
}: React.ComponentProps<"div"> &
Pick<RechartsPrimitive.DefaultLegendContentProps, "payload" | "verticalAlign"> & {
hideIcon?: boolean;
nameKey?: string;
}) {
const { config } = useChart();
if (!payload?.length) {
return null;

View file

@ -105,7 +105,7 @@ export function TrainingSection() {
<div className="relative ">
<ChartContainer
config={chartConfig}
className="min-h-[180px] w-full relative right-8 w-full blur "
className="h-[180px] w-full relative right-8 blur"
>
<LineChart data={placeholderData} accessibilityLayer={true}>
<CartesianGrid vertical={false} strokeDasharray="3 3" />