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-foreground: 38 20% 88%;
|
||||
--muted: 30 18% 14%;
|
||||
--muted-foreground: 38 12% 52%;
|
||||
--muted-foreground: 38 18% 60%;
|
||||
--accent: 38 92% 50%;
|
||||
--accent-foreground: 30 18% 6%;
|
||||
--destructive: 0 72% 55%;
|
||||
|
|
@ -170,17 +170,17 @@
|
|||
--secondary: 120 60% 9%;
|
||||
--secondary-foreground: 120 100% 72%;
|
||||
--muted: 120 60% 9%;
|
||||
--muted-foreground: 120 50% 38%;
|
||||
--muted-foreground: 120 60% 55%;
|
||||
--accent: 120 100% 50%;
|
||||
--accent-foreground: 120 60% 3%;
|
||||
--destructive: 0 84% 55%;
|
||||
--destructive-foreground: 0 0% 100%;
|
||||
--border: 120 60% 14%;
|
||||
--border: 120 60% 16%;
|
||||
--input: 120 60% 9%;
|
||||
--ring: 120 100% 50%;
|
||||
--radius: 0.2rem;
|
||||
--success: 120 100% 50%;
|
||||
--warning: 60 100% 55%;
|
||||
--warning: 60 100% 60%;
|
||||
}
|
||||
|
||||
.theme-terminal body,
|
||||
|
|
@ -213,11 +213,13 @@
|
|||
z-index: 9999;
|
||||
}
|
||||
|
||||
/* Phosphor glow on text */
|
||||
/* Phosphor glow — targeted to headings and explicit foreground text only */
|
||||
.theme-terminal .text-foreground,
|
||||
.theme-terminal p,
|
||||
.theme-terminal span:not(.text-muted-foreground):not(.text-destructive) {
|
||||
text-shadow: 0 0 8px hsl(120 100% 50% / 0.4);
|
||||
.theme-terminal h1, .theme-terminal h2, .theme-terminal h3,
|
||||
.theme-terminal h4, .theme-terminal h5, .theme-terminal h6,
|
||||
.theme-terminal th, .theme-terminal td,
|
||||
.theme-terminal label {
|
||||
text-shadow: 0 0 8px hsl(120 100% 50% / 0.35);
|
||||
}
|
||||
|
||||
/* Glow on cards */
|
||||
|
|
@ -239,7 +241,7 @@
|
|||
--secondary: 268 90% 14%;
|
||||
--secondary-foreground: 280 30% 92%;
|
||||
--muted: 268 90% 14%;
|
||||
--muted-foreground: 270 20% 56%;
|
||||
--muted-foreground: 270 25% 68%;
|
||||
--accent: 190 100% 55%;
|
||||
--accent-foreground: 268 90% 6%;
|
||||
--destructive: 0 84% 60%;
|
||||
|
|
|
|||
|
|
@ -15,23 +15,10 @@ import {
|
|||
XAxis, YAxis, Tooltip, ResponsiveContainer,
|
||||
} from "recharts";
|
||||
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 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> = {
|
||||
income: "text-success",
|
||||
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))" />
|
||||
<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)} />
|
||||
<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>
|
||||
</ResponsiveContainer>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -4,26 +4,13 @@ import {
|
|||
} from "recharts";
|
||||
import { formatCurrency } from "@/utils/currency";
|
||||
import type { PortfolioSummary, HoldingResponse } from "@/api/investments";
|
||||
import { TOOLTIP_STYLE } from "@/utils/chartTheme";
|
||||
|
||||
const COLORS = [
|
||||
"#6366f1","#22c55e","#f97316","#ec4899","#14b8a6",
|
||||
"#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> = {
|
||||
stock: "#6366f1",
|
||||
etf: "#22c55e",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from "recharts";
|
||||
import Plot from "react-plotly.js";
|
||||
import { cssVar } from "@/utils/cssVar";
|
||||
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
||||
|
||||
const TABS = [
|
||||
{ id: "spending", label: "Spending", icon: BarChart3 },
|
||||
|
|
@ -108,7 +109,7 @@ function SpendingTab() {
|
|||
<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))" />
|
||||
<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="forecast" fill="hsl(var(--primary) / 0.5)" name="Forecast" radius={[2, 2, 0, 0]} />
|
||||
</BarChart>
|
||||
|
|
@ -241,13 +242,13 @@ function NetWorthTab() {
|
|||
<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" />
|
||||
<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 />
|
||||
{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="conservative" stroke="#ef4444" strokeWidth={1.5} strokeDasharray="4 2" dot={false} name="Conservative" />
|
||||
<Line type="monotone" dataKey="base" stroke="#22c55e" strokeWidth={2} strokeDasharray="4 2" dot={false} name="Base" />
|
||||
<Line type="monotone" dataKey="optimistic" stroke="#f59e0b" strokeWidth={1.5} strokeDasharray="4 2" dot={false} name="Optimistic" />
|
||||
<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" 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" 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" activeDot={{ r: 3, fill: "#f59e0b", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
|
@ -468,9 +469,9 @@ function CashFlowTab() {
|
|||
</defs>
|
||||
<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} />
|
||||
<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" />
|
||||
<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>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
Tooltip, ResponsiveContainer, Legend
|
||||
} from "recharts";
|
||||
import { TrendingUp, TrendingDown, Minus, Landmark, CreditCard, PiggyBank } from "lucide-react";
|
||||
import { TOOLTIP_STYLE, ACTIVE_DOT } from "@/utils/chartTheme";
|
||||
|
||||
const TABS = [
|
||||
"Balance Sheet", "Net Worth", "Income vs Expense",
|
||||
|
|
@ -37,20 +38,6 @@ const COLORS = [
|
|||
const ASSET_COLORS = ["#22c55e", "#14b8a6", "#6366f1", "#8b5cf6", "#06b6d4", "#84cc16"];
|
||||
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 }: {
|
||||
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))" />
|
||||
<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)} />
|
||||
<Area type="monotone" dataKey="net_worth" stroke="hsl(var(--primary))" fill="url(#nwGrad)" strokeWidth={2} name="Net Worth" />
|
||||
<Area type="monotone" dataKey="total_assets" stroke="#22c55e" fill="none" strokeWidth={1.5} strokeDasharray="4 2" name="Assets" />
|
||||
<Area type="monotone" dataKey="total_liabilities" stroke="#ef4444" fill="none" strokeWidth={1.5} strokeDasharray="4 2" name="Liabilities" />
|
||||
<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" 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" activeDot={{ r: 3, fill: "#ef4444", stroke: "hsl(var(--card))", strokeWidth: 2 }} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
|
@ -362,7 +349,7 @@ function CashFlowTab() {
|
|||
<Legend />
|
||||
<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]} />
|
||||
<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>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
|
|
@ -426,7 +413,7 @@ function SavingsRateTab() {
|
|||
<Legend />
|
||||
<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]} />
|
||||
<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>
|
||||
</ResponsiveContainer>
|
||||
</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