mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
revert(charts): remove gradient background fills (clean lines only)
The Apple-Health-style gradient area underneath each chart line muddied the visual. Drop the linearGradient defs and the area-fill primitives on every chart wrapper and let the line stroke speak for itself. Tooltip, animation, and Dracula tokens stay as they were. Also delete the chart-gradient module — there are no remaining call sites once the fills come out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
874878fdc3
commit
008a8fba3e
8 files changed
+38
-381
No files matched your search
@@ -1,67 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import {
|
||||
ChartLinearGradient,
|
||||
chartGradientFill,
|
||||
} from "../chart-gradient";
|
||||
|
||||
/**
|
||||
* v1.4.16 B1a — pin the contract for the gradient primitive that every
|
||||
* chart wrapper now consumes:
|
||||
*
|
||||
* - emits an SVG `<defs><linearGradient>` block with the requested id,
|
||||
* - top stop carries the metric colour at ~35 % alpha by default,
|
||||
* - bottom stop fades to 0 %,
|
||||
* - the helper `chartGradientFill()` builds the matching `url(#id)`
|
||||
* reference Recharts expects on `<Area fill=...>`.
|
||||
*/
|
||||
|
||||
describe("<ChartLinearGradient>", () => {
|
||||
it("renders a vertical linearGradient with the supplied id and colour token", () => {
|
||||
// Wrap in <svg> because <defs> outside an SVG triggers a React
|
||||
// hydration warning under jsdom; here we render to static markup so
|
||||
// the wrapper just keeps the output well-formed.
|
||||
const html = renderToStaticMarkup(
|
||||
<svg>
|
||||
<ChartLinearGradient id="bp-gradient" colorVar="--dracula-purple" />
|
||||
</svg>,
|
||||
);
|
||||
|
||||
expect(html).toContain('id="bp-gradient"');
|
||||
// Vertical gradient — top to bottom.
|
||||
expect(html).toContain('x1="0"');
|
||||
expect(html).toContain('y1="0"');
|
||||
expect(html).toContain('y2="1"');
|
||||
// The colour token round-trips into a `var(...)` reference.
|
||||
expect(html).toContain("var(--dracula-purple)");
|
||||
// Default opacity: 0.35 at top, 0 at bottom.
|
||||
expect(html).toContain('stop-opacity="0.35"');
|
||||
expect(html).toContain('stop-opacity="0"');
|
||||
// Marker used by visual-regression / e2e smoke tests.
|
||||
expect(html).toContain('data-slot="chart-linear-gradient"');
|
||||
expect(html).toContain('data-color-var="--dracula-purple"');
|
||||
});
|
||||
|
||||
it("respects custom topOpacity / bottomOpacity overrides", () => {
|
||||
const html = renderToStaticMarkup(
|
||||
<svg>
|
||||
<ChartLinearGradient
|
||||
id="weight-gradient"
|
||||
colorVar="--dracula-cyan"
|
||||
topOpacity={0.5}
|
||||
bottomOpacity={0.05}
|
||||
/>
|
||||
</svg>,
|
||||
);
|
||||
expect(html).toContain('stop-opacity="0.5"');
|
||||
expect(html).toContain('stop-opacity="0.05"');
|
||||
});
|
||||
});
|
||||
|
||||
describe("chartGradientFill()", () => {
|
||||
it("builds a Recharts-shaped url() reference", () => {
|
||||
expect(chartGradientFill("bp-gradient")).toBe("url(#bp-gradient)");
|
||||
expect(chartGradientFill("mood")).toBe("url(#mood)");
|
||||
});
|
||||
});
|
||||
@@ -112,15 +112,13 @@ describe("computePersonalBaseline()", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("<HealthChart> v1.4.16 B1a polish", () => {
|
||||
it("emits a sibling-SVG <defs> gradient block addressable by Recharts via url(#id)", async () => {
|
||||
// ResponsiveContainer renders empty during SSR (its dimensions
|
||||
// depend on the live DOM), so Recharts' own SSR output ships no
|
||||
// <defs>. To keep the gradient SSR-discoverable (and so visual
|
||||
// regressions can target it without a browser), the wrapper emits
|
||||
// an external `<svg width=0 height=0>` next to ResponsiveContainer
|
||||
// containing the gradient. Recharts picks it up via `url(#id)`
|
||||
// because SVG IDs are document-scoped.
|
||||
describe("<HealthChart> v1.4.18 clean-line revert", () => {
|
||||
it("does NOT paint a gradient fill under the line (clean line only)", async () => {
|
||||
// v1.4.18 reverts B1a's gradient-area treatment. Marc explicitly
|
||||
// rejected the soft-color fill below the line: the line itself is
|
||||
// the chart, no painted background under it. We assert the SSR
|
||||
// output ships no `<linearGradient>` defs and no `chart-gradient`
|
||||
// primitive markers.
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { HealthChart } = await import("../health-chart");
|
||||
|
||||
@@ -134,22 +132,8 @@ describe("<HealthChart> v1.4.16 B1a polish", () => {
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
expect(html).toContain('data-slot="chart-linear-gradient"');
|
||||
});
|
||||
|
||||
it("namespaces the gradient id by metric type so multi-metric charts don't collide", async () => {
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { HealthChart } = await import("../health-chart");
|
||||
|
||||
const html = renderToStaticMarkup(
|
||||
<I18nProvider initialLocale="en">
|
||||
<HealthChart
|
||||
types={["BLOOD_PRESSURE_SYS"]}
|
||||
title="Blood Pressure"
|
||||
unit="mmHg"
|
||||
/>
|
||||
</I18nProvider>,
|
||||
);
|
||||
expect(html).toMatch(/id="chart-gradient-BLOOD_PRESSURE_SYS"/);
|
||||
expect(html).not.toContain("data-slot=\"chart-linear-gradient\"");
|
||||
expect(html).not.toContain("chart-gradient-BLOOD_PRESSURE_SYS");
|
||||
expect(html).not.toContain("linearGradient");
|
||||
});
|
||||
});
|
||||
@@ -2,12 +2,11 @@ import { describe, it, expect, vi } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
/**
|
||||
* v1.4.16 B1a — Medication compliance chart polish contract.
|
||||
* v1.4.18 — Medication compliance chart clean-line revert.
|
||||
*
|
||||
* The medication chart already has 7-day-trend + target lines from A6;
|
||||
* this commit adds the rest of the v1.4.16 visual leap so the surface
|
||||
* matches BP/weight/pulse/mood: gradient fill, animated first render,
|
||||
* rich tooltip primitive.
|
||||
* Rolls back B1a's gradient fill. The animation, target / threshold
|
||||
* lines, rich tooltip, and 7-day trend chip stay (those weren't
|
||||
* rejected).
|
||||
*/
|
||||
|
||||
const sampleData = vi.hoisted(() =>
|
||||
@@ -26,8 +25,8 @@ vi.mock("@/hooks/use-auth", () => ({
|
||||
useAuth: () => ({ isAuthenticated: true, user: null, isLoading: false }),
|
||||
}));
|
||||
|
||||
describe("<MedicationComplianceChart> v1.4.16 B1a polish", () => {
|
||||
it("emits the gradient defs sibling-SVG block", async () => {
|
||||
describe("<MedicationComplianceChart> v1.4.18 clean-line revert", () => {
|
||||
it("does NOT paint a gradient fill under the line", async () => {
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { MedicationComplianceChart } = await import(
|
||||
"../medication-compliance-chart"
|
||||
@@ -39,7 +38,8 @@ describe("<MedicationComplianceChart> v1.4.16 B1a polish", () => {
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
expect(html).toContain('data-slot="chart-linear-gradient"');
|
||||
expect(html).toContain('id="chart-gradient-medication"');
|
||||
expect(html).not.toContain("data-slot=\"chart-linear-gradient\"");
|
||||
expect(html).not.toContain("chart-gradient-medication");
|
||||
expect(html).not.toContain("linearGradient");
|
||||
});
|
||||
});
|
||||
@@ -2,14 +2,12 @@ import { describe, it, expect, vi } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
/**
|
||||
* v1.4.16 B1a — MoodChart Apple-Health-style polish contract.
|
||||
* v1.4.18 — MoodChart clean-line revert (gradient + emoji rolled back).
|
||||
*
|
||||
* The mood chart picks up the same gradient-fill + animation polish
|
||||
* the rest of the dashboard charts get; the differentiator is per-
|
||||
* point emoji glyphs (a smiley, frown, etc.) instead of plain dots so
|
||||
* the user can scan a week at a glance — Apple's Health app uses the
|
||||
* same affordance for "Mindfulness Minutes" and similar mood-adjacent
|
||||
* surfaces.
|
||||
* Marc rejected B1a's coloured area fill below the chart line and the
|
||||
* smiley/emoji glyphs at every data point. The line stroke + Dracula
|
||||
* tokens stay; the chart now paints a clean monotone line with simple
|
||||
* Recharts dots.
|
||||
*/
|
||||
|
||||
const sampleMoodData = vi.hoisted(() => {
|
||||
@@ -43,8 +41,8 @@ vi.mock("@/hooks/use-auth", () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("<MoodChart> v1.4.16 B1a polish", () => {
|
||||
it("emits the gradient defs sibling-SVG block", async () => {
|
||||
describe("<MoodChart> v1.4.18 clean-line revert", () => {
|
||||
it("does NOT paint a gradient fill under the line", async () => {
|
||||
const { I18nProvider } = await import("@/lib/i18n/context");
|
||||
const { MoodChart } = await import("../mood-chart");
|
||||
|
||||
@@ -54,7 +52,8 @@ describe("<MoodChart> v1.4.16 B1a polish", () => {
|
||||
</I18nProvider>,
|
||||
);
|
||||
|
||||
expect(html).toContain('data-slot="chart-linear-gradient"');
|
||||
expect(html).toContain('id="chart-gradient-mood"');
|
||||
expect(html).not.toContain("data-slot=\"chart-linear-gradient\"");
|
||||
expect(html).not.toContain("chart-gradient-mood");
|
||||
expect(html).not.toContain("linearGradient");
|
||||
});
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import type { ReactElement } from "react";
|
||||
|
||||
/**
|
||||
* Reusable Apple-Health-style linear gradient for area / line fills.
|
||||
*
|
||||
* Recharts ships an Area component that accepts a `fill="url(#id)"` prop
|
||||
* pointing at a `<linearGradient>` defined inside an SVG `<defs>` block.
|
||||
* v1.4.16 B1a paints a soft Dracula-token gradient under the line so the
|
||||
* chart reads like the Apple Health "Trends" cards: the metric colour at
|
||||
* the top of the area fades down to nearly transparent at the bottom.
|
||||
*
|
||||
* Why a wrapper component:
|
||||
* 1. The gradient lives in `<defs>` and must be sibling-rendered to
|
||||
* `<Area fill="url(#id)" />`. Recharts allows arbitrary children in
|
||||
* `LineChart` / `AreaChart` and renders them inside its `<svg>`, so
|
||||
* a small named primitive keeps the call sites readable
|
||||
* (`<ChartLinearGradient id="bp" colorVar="--dracula-purple" />`)
|
||||
* instead of inlining 8 lines of `<defs><linearGradient>...` per
|
||||
* chart.
|
||||
* 2. Tokens stay canonical — only the `colorVar` prop is exposed; the
|
||||
* stop offsets / opacities are baked in to enforce a consistent
|
||||
* look across all 5 charts (BP, weight, pulse, mood, medication).
|
||||
* 3. SSR safe — pure SVG, no DOM APIs touched. The unit test renders
|
||||
* it via `renderToStaticMarkup`.
|
||||
*
|
||||
* Defaults follow the Apple Health pattern: ~35 % alpha at the top of
|
||||
* the area, 0 % at the bottom. The pinned stops also stay clear of the
|
||||
* grid lines so a domain like "60 .. 110 mmHg" still reads as a
|
||||
* gradient, not a flat colour wash.
|
||||
*/
|
||||
|
||||
export interface ChartLinearGradientProps {
|
||||
/** SVG id used by Recharts: `<Area fill="url(#${id})" />`. */
|
||||
id: string;
|
||||
/**
|
||||
* CSS variable name (e.g. `--dracula-purple`). The component wraps it
|
||||
* in `var(...)` so callers can pass the token verbatim.
|
||||
*/
|
||||
colorVar: string;
|
||||
/** Alpha at the top of the area. Defaults to 0.35. */
|
||||
topOpacity?: number;
|
||||
/** Alpha at the bottom of the area. Defaults to 0.0. */
|
||||
bottomOpacity?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a `<defs><linearGradient .../></defs>` block.
|
||||
*
|
||||
* The gradient runs vertically (x1=0,x2=0,y1=0,y2=1) so it shades the
|
||||
* area top-down, matching how Apple Health paints the metric colour
|
||||
* over the chart background.
|
||||
*/
|
||||
export function ChartLinearGradient({
|
||||
id,
|
||||
colorVar,
|
||||
topOpacity = 0.35,
|
||||
bottomOpacity = 0,
|
||||
}: ChartLinearGradientProps): ReactElement {
|
||||
const fill = `var(${colorVar})`;
|
||||
|
||||
return (
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={id}
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
data-slot="chart-linear-gradient"
|
||||
data-color-var={colorVar}
|
||||
>
|
||||
<stop offset="0%" stopColor={fill} stopOpacity={topOpacity} />
|
||||
<stop offset="100%" stopColor={fill} stopOpacity={bottomOpacity} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the `fill` attribute string Recharts expects when wiring an
|
||||
* `<Area>` to a `<ChartLinearGradient id="x">` defined alongside.
|
||||
*/
|
||||
export function chartGradientFill(id: string): string {
|
||||
return `url(#${id})`;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
Area,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
@@ -17,10 +16,6 @@ import {
|
||||
} from "recharts";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { useState, useMemo, useId } from "react";
|
||||
import {
|
||||
ChartLinearGradient,
|
||||
chartGradientFill,
|
||||
} from "./chart-gradient";
|
||||
import { RichChartTooltip, type RichTooltipRow } from "./chart-tooltip";
|
||||
import { ChartEmptyState } from "./chart-empty-state";
|
||||
import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
|
||||
@@ -987,73 +982,15 @@ export function HealthChart({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* v1.4.16 B1a — sibling SVG <defs> block.
|
||||
ResponsiveContainer is empty during SSR so a gradient
|
||||
defined inside its child <svg> isn't discoverable in
|
||||
static-rendered markup. Emitting the gradient in a
|
||||
standalone hidden <svg> next to the chart keeps it
|
||||
available document-wide; Recharts' <Area fill="url(#id)">
|
||||
looks the gradient up by id at paint time and finds it
|
||||
regardless of which SVG it lives in. */}
|
||||
<svg
|
||||
width={0}
|
||||
height={0}
|
||||
aria-hidden="true"
|
||||
style={{ position: "absolute", pointerEvents: "none" }}
|
||||
data-slot="chart-gradient-defs"
|
||||
>
|
||||
{types.map((type, i) => (
|
||||
<ChartLinearGradient
|
||||
key={`grad-${type}`}
|
||||
id={`chart-gradient-${type}`}
|
||||
colorVar={
|
||||
// Best-effort mapping from the dot colour back to a
|
||||
// Dracula token — the chart already accepts an
|
||||
// arbitrary `colors` array (defaults to purple/pink/
|
||||
// cyan), so we mirror that here.
|
||||
colors[i % colors.length]?.startsWith("var(")
|
||||
? colors[i % colors.length]
|
||||
.replace("var(", "")
|
||||
.replace(")", "")
|
||||
: "--dracula-purple"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
|
||||
{/* v1.4.18 — gradient fill removed per Marc's feedback.
|
||||
The clean line is the chart; no painted background under
|
||||
it. */}
|
||||
<div className="relative z-10 h-full touch-pan-y">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<ComposedChart
|
||||
data={chartDataWithCompare ?? chartData}
|
||||
margin={{ top: 10, right: 8, bottom: 8, left: 8 }}
|
||||
>
|
||||
{/* Inline gradient defs as Recharts children — same id
|
||||
space as the sibling SVG above, so even browsers
|
||||
that scope SVG-id resolution to the local document
|
||||
fragment (Safari edge cases) still find a match. */}
|
||||
<defs>
|
||||
{types.map((type, i) => (
|
||||
<linearGradient
|
||||
key={`inline-grad-${type}`}
|
||||
id={`chart-gradient-inline-${type}`}
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={colors[i % colors.length]}
|
||||
stopOpacity={0.35}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={colors[i % colors.length]}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
stroke="var(--border)"
|
||||
@@ -1323,26 +1260,8 @@ export function HealthChart({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* v1.4.16 B1a — gradient-filled Area painted *under*
|
||||
the line. Each type gets its own gradient
|
||||
referenced by id. The Area's stroke is set to
|
||||
transparent so only the fill paints; the line on
|
||||
top of it carries the actual stroke. */}
|
||||
{types.map((type) => (
|
||||
<Area
|
||||
key={`area-${type}`}
|
||||
type="monotone"
|
||||
dataKey={type}
|
||||
stroke="transparent"
|
||||
fill={chartGradientFill(`chart-gradient-inline-${type}`)}
|
||||
fillOpacity={1}
|
||||
isAnimationActive={animationsEnabled}
|
||||
animationDuration={animationsEnabled ? 600 : 0}
|
||||
animationEasing="ease-out"
|
||||
connectNulls
|
||||
legendType="none"
|
||||
/>
|
||||
))}
|
||||
{/* v1.4.18 — gradient Area removed; only the clean
|
||||
line below paints the metric. */}
|
||||
{types.map((type, i) => (
|
||||
<Line
|
||||
key={type}
|
||||
|
||||
@@ -37,7 +37,6 @@ import { useMemo, useState } from "react";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
Area,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
@@ -51,7 +50,6 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import { useTranslations, useFormatters } from "@/lib/i18n/context";
|
||||
import { formatDateShort } from "@/lib/format";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { ChartLinearGradient, chartGradientFill } from "./chart-gradient";
|
||||
import { RichChartTooltip, type RichTooltipRow } from "./chart-tooltip";
|
||||
import { ChartEmptyState } from "./chart-empty-state";
|
||||
import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
|
||||
@@ -294,21 +292,7 @@ export function MedicationComplianceChart({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* v1.4.16 B1a — sibling SVG <defs> block for SSR-discoverable
|
||||
gradient. */}
|
||||
<svg
|
||||
width={0}
|
||||
height={0}
|
||||
aria-hidden="true"
|
||||
style={{ position: "absolute", pointerEvents: "none" }}
|
||||
data-slot="chart-gradient-defs"
|
||||
>
|
||||
<ChartLinearGradient
|
||||
id="chart-gradient-medication"
|
||||
colorVar="--dracula-purple"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* v1.4.18 — gradient defs removed; clean line only. */}
|
||||
{isLoading ? (
|
||||
<div className="flex h-48 items-center justify-center">
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin" />
|
||||
@@ -331,18 +315,6 @@ export function MedicationComplianceChart({
|
||||
data={chartData}
|
||||
margin={{ top: 10, right: 8, bottom: 8, left: 8 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="chart-gradient-medication-inline"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop offset="0%" stopColor={COLOR_LINE} stopOpacity={0.35} />
|
||||
<stop offset="100%" stopColor={COLOR_LINE} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
stroke="var(--border)"
|
||||
@@ -430,21 +402,7 @@ export function MedicationComplianceChart({
|
||||
strokeOpacity={0.85}
|
||||
data-slot="medication-goal-line"
|
||||
/>
|
||||
{/* v1.4.16 B1a — gradient-filled Area painted under the
|
||||
line. Animated on first render unless the user opts
|
||||
for reduced motion. */}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="rate"
|
||||
stroke="transparent"
|
||||
fill={chartGradientFill("chart-gradient-medication-inline")}
|
||||
fillOpacity={1}
|
||||
isAnimationActive={animationsEnabled}
|
||||
animationDuration={animationsEnabled ? 600 : 0}
|
||||
animationEasing="ease-out"
|
||||
connectNulls
|
||||
legendType="none"
|
||||
/>
|
||||
{/* v1.4.18 — gradient Area removed; clean line only. */}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="rate"
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useAuth } from "@/hooks/use-auth";
|
||||
import {
|
||||
ResponsiveContainer,
|
||||
ComposedChart,
|
||||
Area,
|
||||
Line,
|
||||
XAxis,
|
||||
YAxis,
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
pickBucket,
|
||||
type ChartBucketType,
|
||||
} from "@/lib/charts/bucket-time-series";
|
||||
import { ChartLinearGradient, chartGradientFill } from "./chart-gradient";
|
||||
import { RichChartTooltip, type RichTooltipRow } from "./chart-tooltip";
|
||||
import { ChartEmptyState } from "./chart-empty-state";
|
||||
import { prefersReducedMotion } from "@/lib/charts/reduced-motion";
|
||||
@@ -603,21 +601,7 @@ export function MoodChart({
|
||||
)}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{/* v1.4.16 B1a — sibling SVG <defs> block for the gradient
|
||||
primitive. SSR-discoverable; Recharts also picks up the id
|
||||
client-side via the inline <defs> below. */}
|
||||
<svg
|
||||
width={0}
|
||||
height={0}
|
||||
aria-hidden="true"
|
||||
style={{ position: "absolute", pointerEvents: "none" }}
|
||||
data-slot="chart-gradient-defs"
|
||||
>
|
||||
<ChartLinearGradient
|
||||
id="chart-gradient-mood"
|
||||
colorVar="--dracula-lavender"
|
||||
/>
|
||||
</svg>
|
||||
{/* v1.4.18 — gradient defs removed; clean line only. */}
|
||||
{isLoading ? (
|
||||
<div className="flex h-48 items-center justify-center">
|
||||
<Loader2 className="text-primary h-6 w-6 animate-spin" />
|
||||
@@ -641,26 +625,6 @@ export function MoodChart({
|
||||
data={chartDataWithCompare ?? chartData}
|
||||
margin={{ top: 10, right: 8, bottom: 8, left: 8 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="chart-gradient-mood-inline"
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="0"
|
||||
y2="1"
|
||||
>
|
||||
<stop
|
||||
offset="0%"
|
||||
stopColor={COLOR_MAIN}
|
||||
stopOpacity={0.4}
|
||||
/>
|
||||
<stop
|
||||
offset="100%"
|
||||
stopColor={COLOR_MAIN}
|
||||
stopOpacity={0}
|
||||
/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
stroke="hsl(var(--border))"
|
||||
@@ -841,20 +805,7 @@ export function MoodChart({
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{/* v1.4.16 B1a — gradient-filled Area painted under
|
||||
the line. */}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey="score"
|
||||
stroke="transparent"
|
||||
fill={chartGradientFill("chart-gradient-mood-inline")}
|
||||
fillOpacity={1}
|
||||
isAnimationActive={animationsEnabled}
|
||||
animationDuration={animationsEnabled ? 600 : 0}
|
||||
animationEasing="ease-out"
|
||||
connectNulls
|
||||
legendType="none"
|
||||
/>
|
||||
{/* v1.4.18 — gradient Area removed; clean line only. */}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="score"
|
||||
|
||||
Reference in new issue
Block a user