Fix theme text visibility, tooltip colours, and chart hover states
- Extract shared TOOLTIP_STYLE and ACTIVE_DOT to utils/chartTheme.ts so all four chart files use one source of truth - itemStyle now uses card-foreground (not muted-foreground) — guarantees tooltip text is readable on all themes including Terminal, Vault, Synthwave - cursor now uses primary at 12% opacity — always visible and thematic, replaces near-invisible muted-foreground at 8% opacity - activeDot is now explicit on every Line/Area — prevents Recharts default white dot breaking dark themes - Terminal: muted-foreground bumped 38%→55% lightness, border lightened, warning brightened, text-shadow scoped to headings/labels/table cells (was applying to every p and span, causing tooltip glow bleed) - Synthwave: muted-foreground bumped 56%→68% lightness for legibility - Vault: muted-foreground bumped 52%→60% lightness Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
54f6f66ff8
commit
1854026a76
6 changed files with 69 additions and 65 deletions
|
|
@ -143,7 +143,7 @@
|
||||||
--secondary: 30 18% 14%;
|
--secondary: 30 18% 14%;
|
||||||
--secondary-foreground: 38 20% 88%;
|
--secondary-foreground: 38 20% 88%;
|
||||||
--muted: 30 18% 14%;
|
--muted: 30 18% 14%;
|
||||||
--muted-foreground: 38 12% 52%;
|
--muted-foreground: 38 18% 60%;
|
||||||
--accent: 38 92% 50%;
|
--accent: 38 92% 50%;
|
||||||
--accent-foreground: 30 18% 6%;
|
--accent-foreground: 30 18% 6%;
|
||||||
--destructive: 0 72% 55%;
|
--destructive: 0 72% 55%;
|
||||||
|
|
@ -170,17 +170,17 @@
|
||||||
--secondary: 120 60% 9%;
|
--secondary: 120 60% 9%;
|
||||||
--secondary-foreground: 120 100% 72%;
|
--secondary-foreground: 120 100% 72%;
|
||||||
--muted: 120 60% 9%;
|
--muted: 120 60% 9%;
|
||||||
--muted-foreground: 120 50% 38%;
|
--muted-foreground: 120 60% 55%;
|
||||||
--accent: 120 100% 50%;
|
--accent: 120 100% 50%;
|
||||||
--accent-foreground: 120 60% 3%;
|
--accent-foreground: 120 60% 3%;
|
||||||
--destructive: 0 84% 55%;
|
--destructive: 0 84% 55%;
|
||||||
--destructive-foreground: 0 0% 100%;
|
--destructive-foreground: 0 0% 100%;
|
||||||
--border: 120 60% 14%;
|
--border: 120 60% 16%;
|
||||||
--input: 120 60% 9%;
|
--input: 120 60% 9%;
|
||||||
--ring: 120 100% 50%;
|
--ring: 120 100% 50%;
|
||||||
--radius: 0.2rem;
|
--radius: 0.2rem;
|
||||||
--success: 120 100% 50%;
|
--success: 120 100% 50%;
|
||||||
--warning: 60 100% 55%;
|
--warning: 60 100% 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-terminal body,
|
.theme-terminal body,
|
||||||
|
|
@ -213,11 +213,13 @@
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Phosphor glow on text */
|
/* Phosphor glow — targeted to headings and explicit foreground text only */
|
||||||
.theme-terminal .text-foreground,
|
.theme-terminal .text-foreground,
|
||||||
.theme-terminal p,
|
.theme-terminal h1, .theme-terminal h2, .theme-terminal h3,
|
||||||
.theme-terminal span:not(.text-muted-foreground):not(.text-destructive) {
|
.theme-terminal h4, .theme-terminal h5, .theme-terminal h6,
|
||||||
text-shadow: 0 0 8px hsl(120 100% 50% / 0.4);
|
.theme-terminal th, .theme-terminal td,
|
||||||
|
.theme-terminal label {
|
||||||
|
text-shadow: 0 0 8px hsl(120 100% 50% / 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Glow on cards */
|
/* Glow on cards */
|
||||||
|
|
@ -239,7 +241,7 @@
|
||||||
--secondary: 268 90% 14%;
|
--secondary: 268 90% 14%;
|
||||||
--secondary-foreground: 280 30% 92%;
|
--secondary-foreground: 280 30% 92%;
|
||||||
--muted: 268 90% 14%;
|
--muted: 268 90% 14%;
|
||||||
--muted-foreground: 270 20% 56%;
|
--muted-foreground: 270 25% 68%;
|
||||||
--accent: 190 100% 55%;
|
--accent: 190 100% 55%;
|
||||||
--accent-foreground: 268 90% 6%;
|
--accent-foreground: 268 90% 6%;
|
||||||
--destructive: 0 84% 60%;
|
--destructive: 0 84% 60%;
|
||||||
|
|
|
||||||
|
|
@ -15,23 +15,10 @@ import {
|
||||||
XAxis, YAxis, Tooltip, ResponsiveContainer,
|
XAxis, YAxis, Tooltip, ResponsiveContainer,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
||||||
|
|
||||||
const COLORS = ["#6366f1","#22c55e","#f97316","#ec4899","#14b8a6","#f59e0b","#8b5cf6","#ef4444"];
|
const COLORS = ["#6366f1","#22c55e","#f97316","#ec4899","#14b8a6","#f59e0b","#8b5cf6","#ef4444"];
|
||||||
|
|
||||||
const TOOLTIP_STYLE = {
|
|
||||||
contentStyle: {
|
|
||||||
background: "hsl(var(--card))",
|
|
||||||
border: "1px solid hsl(var(--border))",
|
|
||||||
borderRadius: "8px",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "hsl(var(--foreground))",
|
|
||||||
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
||||||
},
|
|
||||||
labelStyle: { color: "hsl(var(--foreground))", fontWeight: 500, marginBottom: "4px" },
|
|
||||||
itemStyle: { color: "hsl(var(--muted-foreground))" },
|
|
||||||
cursor: { fill: "hsl(var(--muted-foreground))", fillOpacity: 0.08 },
|
|
||||||
};
|
|
||||||
|
|
||||||
const TYPE_COLORS: Record<string, string> = {
|
const TYPE_COLORS: Record<string, string> = {
|
||||||
income: "text-success",
|
income: "text-success",
|
||||||
expense: "text-destructive",
|
expense: "text-destructive",
|
||||||
|
|
@ -132,7 +119,7 @@ export default function Dashboard() {
|
||||||
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" />
|
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" />
|
||||||
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${(v/1000).toFixed(0)}k`} width={45} />
|
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${(v/1000).toFixed(0)}k`} width={45} />
|
||||||
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, nwReport.base_currency)} />
|
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, nwReport.base_currency)} />
|
||||||
<Area type="monotone" dataKey="value" stroke="hsl(var(--primary))" fill="url(#nwGrad)" strokeWidth={2} />
|
<Area type="monotone" dataKey="value" stroke="hsl(var(--primary))" fill="url(#nwGrad)" strokeWidth={2} activeDot={ACTIVE_DOT} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,13 @@ import {
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { formatCurrency } from "@/utils/currency";
|
import { formatCurrency } from "@/utils/currency";
|
||||||
import type { PortfolioSummary, HoldingResponse } from "@/api/investments";
|
import type { PortfolioSummary, HoldingResponse } from "@/api/investments";
|
||||||
|
import { TOOLTIP_STYLE } from "@/utils/chartTheme";
|
||||||
|
|
||||||
const COLORS = [
|
const COLORS = [
|
||||||
"#6366f1","#22c55e","#f97316","#ec4899","#14b8a6",
|
"#6366f1","#22c55e","#f97316","#ec4899","#14b8a6",
|
||||||
"#f59e0b","#8b5cf6","#06b6d4","#84cc16","#ef4444",
|
"#f59e0b","#8b5cf6","#06b6d4","#84cc16","#ef4444",
|
||||||
];
|
];
|
||||||
|
|
||||||
const TOOLTIP_STYLE = {
|
|
||||||
contentStyle: {
|
|
||||||
background: "hsl(var(--card))",
|
|
||||||
border: "1px solid hsl(var(--border))",
|
|
||||||
borderRadius: "8px",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "hsl(var(--foreground))",
|
|
||||||
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
||||||
},
|
|
||||||
labelStyle: { color: "hsl(var(--foreground))", fontWeight: 500, marginBottom: "4px" },
|
|
||||||
itemStyle: { color: "hsl(var(--muted-foreground))" },
|
|
||||||
cursor: { fill: "hsl(var(--muted-foreground))", fillOpacity: 0.08 },
|
|
||||||
};
|
|
||||||
|
|
||||||
const TYPE_COLORS: Record<string, string> = {
|
const TYPE_COLORS: Record<string, string> = {
|
||||||
stock: "#6366f1",
|
stock: "#6366f1",
|
||||||
etf: "#22c55e",
|
etf: "#22c55e",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import Plot from "react-plotly.js";
|
import Plot from "react-plotly.js";
|
||||||
import { cssVar } from "@/utils/cssVar";
|
import { cssVar } from "@/utils/cssVar";
|
||||||
|
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: "spending", label: "Spending", icon: BarChart3 },
|
{ id: "spending", label: "Spending", icon: BarChart3 },
|
||||||
|
|
@ -108,7 +109,7 @@ function SpendingTab() {
|
||||||
<BarChart data={chartData} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
|
<BarChart data={chartData} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
|
||||||
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" />
|
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" />
|
||||||
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${v}`} width={55} />
|
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${v}`} width={55} />
|
||||||
<Tooltip formatter={(v: number) => formatCurrency(v, "GBP")} />
|
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, "GBP")} />
|
||||||
<Bar dataKey="actual" fill="hsl(var(--primary))" name="Actual" radius={[2, 2, 0, 0]} />
|
<Bar dataKey="actual" fill="hsl(var(--primary))" name="Actual" radius={[2, 2, 0, 0]} />
|
||||||
<Bar dataKey="forecast" fill="hsl(var(--primary) / 0.5)" name="Forecast" radius={[2, 2, 0, 0]} />
|
<Bar dataKey="forecast" fill="hsl(var(--primary) / 0.5)" name="Forecast" radius={[2, 2, 0, 0]} />
|
||||||
</BarChart>
|
</BarChart>
|
||||||
|
|
@ -241,13 +242,13 @@ function NetWorthTab() {
|
||||||
<LineChart data={chartData} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
|
<LineChart data={chartData} margin={{ top: 5, right: 10, left: 0, bottom: 5 }}>
|
||||||
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" interval="preserveStartEnd" />
|
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" interval="preserveStartEnd" />
|
||||||
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${(v / 1000).toFixed(0)}k`} width={55} />
|
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${(v / 1000).toFixed(0)}k`} width={55} />
|
||||||
<Tooltip formatter={(v: number) => formatCurrency(v, "GBP")} />
|
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, "GBP")} />
|
||||||
<Legend />
|
<Legend />
|
||||||
{lastHistory && <ReferenceLine x={lastHistory.date} stroke="hsl(var(--border))" strokeDasharray="4 2" label={{ value: "Today", fontSize: 10 }} />}
|
{lastHistory && <ReferenceLine x={lastHistory.date} stroke="hsl(var(--border))" strokeDasharray="4 2" label={{ value: "Today", fontSize: 10 }} />}
|
||||||
<Line type="monotone" dataKey="history" stroke="hsl(var(--primary))" strokeWidth={2} dot={false} name="History" />
|
<Line type="monotone" dataKey="history" stroke="hsl(var(--primary))" strokeWidth={2} dot={false} name="History" activeDot={ACTIVE_DOT} />
|
||||||
<Line type="monotone" dataKey="conservative" stroke="#ef4444" strokeWidth={1.5} strokeDasharray="4 2" dot={false} name="Conservative" />
|
<Line type="monotone" dataKey="conservative" stroke="#ef4444" strokeWidth={1.5} strokeDasharray="4 2" dot={false} name="Conservative" activeDot={{ r: 3, fill: "#ef4444", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||||
<Line type="monotone" dataKey="base" stroke="#22c55e" strokeWidth={2} strokeDasharray="4 2" dot={false} name="Base" />
|
<Line type="monotone" dataKey="base" stroke="#22c55e" strokeWidth={2} strokeDasharray="4 2" dot={false} name="Base" activeDot={{ r: 3, fill: "#22c55e", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||||
<Line type="monotone" dataKey="optimistic" stroke="#f59e0b" strokeWidth={1.5} strokeDasharray="4 2" dot={false} name="Optimistic" />
|
<Line type="monotone" dataKey="optimistic" stroke="#f59e0b" strokeWidth={1.5} strokeDasharray="4 2" dot={false} name="Optimistic" activeDot={{ r: 3, fill: "#f59e0b", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -468,9 +469,9 @@ function CashFlowTab() {
|
||||||
</defs>
|
</defs>
|
||||||
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => v.slice(5)} />
|
<XAxis dataKey="date" tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => v.slice(5)} />
|
||||||
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${(v / 1000).toFixed(1)}k`} width={55} />
|
<YAxis tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={v => `£${(v / 1000).toFixed(1)}k`} width={55} />
|
||||||
<Tooltip formatter={(v: number) => formatCurrency(v, "GBP")} />
|
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, "GBP")} />
|
||||||
<ReferenceLine y={0} stroke="hsl(var(--destructive))" strokeDasharray="4 2" />
|
<ReferenceLine y={0} stroke="hsl(var(--destructive))" strokeDasharray="4 2" />
|
||||||
<Area type="monotone" dataKey="balance" stroke="hsl(var(--primary))" fill="url(#balanceGrad)" strokeWidth={2} name="Balance" />
|
<Area type="monotone" dataKey="balance" stroke="hsl(var(--primary))" fill="url(#balanceGrad)" strokeWidth={2} name="Balance" activeDot={ACTIVE_DOT} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
Tooltip, ResponsiveContainer, Legend
|
Tooltip, ResponsiveContainer, Legend
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { TrendingUp, TrendingDown, Minus, Landmark, CreditCard, PiggyBank } from "lucide-react";
|
import { TrendingUp, TrendingDown, Minus, Landmark, CreditCard, PiggyBank } from "lucide-react";
|
||||||
|
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
"Balance Sheet", "Net Worth", "Income vs Expense",
|
"Balance Sheet", "Net Worth", "Income vs Expense",
|
||||||
|
|
@ -37,20 +38,6 @@ const COLORS = [
|
||||||
const ASSET_COLORS = ["#22c55e", "#14b8a6", "#6366f1", "#8b5cf6", "#06b6d4", "#84cc16"];
|
const ASSET_COLORS = ["#22c55e", "#14b8a6", "#6366f1", "#8b5cf6", "#06b6d4", "#84cc16"];
|
||||||
const LIABILITY_COLORS = ["#ef4444", "#f97316", "#ec4899"];
|
const LIABILITY_COLORS = ["#ef4444", "#f97316", "#ec4899"];
|
||||||
|
|
||||||
const TOOLTIP_STYLE = {
|
|
||||||
contentStyle: {
|
|
||||||
background: "hsl(var(--card))",
|
|
||||||
border: "1px solid hsl(var(--border))",
|
|
||||||
borderRadius: "8px",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "hsl(var(--foreground))",
|
|
||||||
boxShadow: "0 4px 12px rgba(0,0,0,0.15)",
|
|
||||||
},
|
|
||||||
labelStyle: { color: "hsl(var(--foreground))", fontWeight: 500, marginBottom: "4px" },
|
|
||||||
itemStyle: { color: "hsl(var(--muted-foreground))" },
|
|
||||||
cursor: { fill: "hsl(var(--muted-foreground))", fillOpacity: 0.08 },
|
|
||||||
};
|
|
||||||
|
|
||||||
function StatCard({ label, value, change, currency }: {
|
function StatCard({ label, value, change, currency }: {
|
||||||
label: string; value: number; change?: number; currency: string;
|
label: string; value: number; change?: number; currency: string;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -271,9 +258,9 @@ function NetWorthTab() {
|
||||||
<XAxis dataKey="date" tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" />
|
<XAxis dataKey="date" tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" />
|
||||||
<YAxis tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={(v) => `£${(v/1000).toFixed(0)}k`} />
|
<YAxis tick={{ fontSize: 11, fill: "hsl(var(--muted-foreground))" }} stroke="hsl(var(--muted-foreground))" tickFormatter={(v) => `£${(v/1000).toFixed(0)}k`} />
|
||||||
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, data.base_currency)} />
|
<Tooltip {...TOOLTIP_STYLE} formatter={(v: number) => formatCurrency(v, data.base_currency)} />
|
||||||
<Area type="monotone" dataKey="net_worth" stroke="hsl(var(--primary))" fill="url(#nwGrad)" strokeWidth={2} name="Net Worth" />
|
<Area type="monotone" dataKey="net_worth" stroke="hsl(var(--primary))" fill="url(#nwGrad)" strokeWidth={2} name="Net Worth" activeDot={ACTIVE_DOT} />
|
||||||
<Area type="monotone" dataKey="total_assets" stroke="#22c55e" fill="none" strokeWidth={1.5} strokeDasharray="4 2" name="Assets" />
|
<Area type="monotone" dataKey="total_assets" stroke="#22c55e" fill="none" strokeWidth={1.5} strokeDasharray="4 2" name="Assets" activeDot={{ r: 3, fill: "#22c55e", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||||
<Area type="monotone" dataKey="total_liabilities" stroke="#ef4444" fill="none" strokeWidth={1.5} strokeDasharray="4 2" name="Liabilities" />
|
<Area type="monotone" dataKey="total_liabilities" stroke="#ef4444" fill="none" strokeWidth={1.5} strokeDasharray="4 2" name="Liabilities" activeDot={{ r: 3, fill: "#ef4444", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||||
</AreaChart>
|
</AreaChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -362,7 +349,7 @@ function CashFlowTab() {
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar yAxisId="bars" dataKey="inflow" fill="#22c55e" name="Inflow" radius={[2, 2, 0, 0]} />
|
<Bar yAxisId="bars" dataKey="inflow" fill="#22c55e" name="Inflow" radius={[2, 2, 0, 0]} />
|
||||||
<Bar yAxisId="bars" dataKey="outflow" fill="#ef4444" name="Outflow" radius={[2, 2, 0, 0]} />
|
<Bar yAxisId="bars" dataKey="outflow" fill="#ef4444" name="Outflow" radius={[2, 2, 0, 0]} />
|
||||||
<Line yAxisId="line" type="monotone" dataKey="balance" stroke="hsl(var(--primary))" strokeWidth={2} dot={false} name="Running Balance" />
|
<Line yAxisId="line" type="monotone" dataKey="balance" stroke="hsl(var(--primary))" strokeWidth={2} dot={false} name="Running Balance" activeDot={ACTIVE_DOT} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -426,7 +413,7 @@ function SavingsRateTab() {
|
||||||
<Legend />
|
<Legend />
|
||||||
<Bar yAxisId="bars" dataKey="income" fill="#22c55e" name="Income" radius={[2, 2, 0, 0]} />
|
<Bar yAxisId="bars" dataKey="income" fill="#22c55e" name="Income" radius={[2, 2, 0, 0]} />
|
||||||
<Bar yAxisId="bars" dataKey="expenses" fill="#ef4444" name="Expenses" radius={[2, 2, 0, 0]} />
|
<Bar yAxisId="bars" dataKey="expenses" fill="#ef4444" name="Expenses" radius={[2, 2, 0, 0]} />
|
||||||
<Line yAxisId="rate" type="monotone" dataKey="rate" stroke="hsl(var(--primary))" strokeWidth={2.5} dot={{ r: 3 }} name="Savings Rate %" />
|
<Line yAxisId="rate" type="monotone" dataKey="rate" stroke="hsl(var(--primary))" strokeWidth={2.5} dot={{ r: 3, fill: "hsl(var(--primary))", stroke: "hsl(var(--card))", strokeWidth: 1.5 }} name="Savings Rate %" activeDot={ACTIVE_DOT} />
|
||||||
</ComposedChart>
|
</ComposedChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
40
frontend/src/utils/chartTheme.ts
Normal file
40
frontend/src/utils/chartTheme.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Shared Recharts tooltip and chart style constants.
|
||||||
|
*
|
||||||
|
* All colour values use CSS custom properties so they adapt to every theme.
|
||||||
|
* itemStyle uses card-foreground (not muted-foreground) to guarantee
|
||||||
|
* readability inside the tooltip card on all themes including Terminal,
|
||||||
|
* Synthwave and Vault which have dim muted-foreground values.
|
||||||
|
*
|
||||||
|
* cursor uses --primary at low opacity — always visible and thematic.
|
||||||
|
*/
|
||||||
|
export const TOOLTIP_STYLE = {
|
||||||
|
contentStyle: {
|
||||||
|
background: "hsl(var(--card))",
|
||||||
|
border: "1px solid hsl(var(--border))",
|
||||||
|
borderRadius: "8px",
|
||||||
|
fontSize: "12px",
|
||||||
|
color: "hsl(var(--card-foreground))",
|
||||||
|
boxShadow: "0 4px 20px rgba(0,0,0,0.35)",
|
||||||
|
padding: "8px 12px",
|
||||||
|
},
|
||||||
|
labelStyle: {
|
||||||
|
color: "hsl(var(--card-foreground))",
|
||||||
|
fontWeight: 600,
|
||||||
|
marginBottom: "4px",
|
||||||
|
fontSize: "12px",
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: "hsl(var(--card-foreground))",
|
||||||
|
opacity: 0.8,
|
||||||
|
},
|
||||||
|
cursor: { fill: "hsl(var(--primary))", fillOpacity: 0.12 },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Standard activeDot for Line/Area charts — theme-adaptive. */
|
||||||
|
export const ACTIVE_DOT = {
|
||||||
|
r: 4,
|
||||||
|
stroke: "hsl(var(--card))",
|
||||||
|
strokeWidth: 2,
|
||||||
|
fill: "hsl(var(--primary))",
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue