Skip to content

Commit 44cbeaa

Browse files
ofershapcursoragent
andcommitted
feat: clickable top spenders bars with hover effect
Clicking a bar navigates to the user detail page. Bars lighten on hover with a smooth color transition. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 5226f5b commit 44cbeaa

1 file changed

Lines changed: 45 additions & 5 deletions

File tree

src/components/charts/spend-bar-chart.tsx

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"use client";
22

3+
import { useCallback, useState } from "react";
4+
import { useRouter } from "next/navigation";
35
import {
46
BarChart,
57
Bar,
@@ -27,7 +29,15 @@ const TOOLTIP_STYLE = {
2729
const TOOLTIP_LABEL_STYLE = { color: "#fafafa" } as const;
2830
const TOOLTIP_ITEM_STYLE = { color: "#fafafa" } as const;
2931

32+
const BAR_COLOR = "#3b82f6";
33+
const BAR_HOVER_COLOR = "#60a5fa";
34+
const HIGHLIGHT_COLOR = "#f59e0b";
35+
const HIGHLIGHT_HOVER_COLOR = "#fbbf24";
36+
3037
export function SpendBarChart({ data, highlightEmail }: SpendBarChartProps) {
38+
const router = useRouter();
39+
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
40+
3141
const chartData = data.slice(0, 8).map((d) => ({
3242
label: `#${d.spend_rank} ${d.name.split(" ")[0] ?? d.email.split("@")[0]}`,
3343
spend: d.spend_cents / 100,
@@ -36,11 +46,30 @@ export function SpendBarChart({ data, highlightEmail }: SpendBarChartProps) {
3646
name: d.name,
3747
}));
3848

49+
const handleBarClick = useCallback(
50+
(entry: (typeof chartData)[number]) => {
51+
router.push(`/users/${encodeURIComponent(entry.email)}`);
52+
},
53+
[router],
54+
);
55+
56+
const getFill = (entry: (typeof chartData)[number], index: number) => {
57+
const isHighlight = entry.email === highlightEmail;
58+
const isHovered = hoveredIndex === index;
59+
if (isHighlight) return isHovered ? HIGHLIGHT_HOVER_COLOR : HIGHLIGHT_COLOR;
60+
return isHovered ? BAR_HOVER_COLOR : BAR_COLOR;
61+
};
62+
3963
return (
4064
<div className="bg-zinc-900 rounded-lg border border-zinc-800 p-4">
4165
<h3 className="text-xs font-medium text-zinc-400 mb-2">Top Spenders (Current Cycle)</h3>
4266
<ResponsiveContainer width="100%" height={240}>
43-
<BarChart data={chartData} layout="vertical" margin={{ left: 10, right: 50 }}>
67+
<BarChart
68+
data={chartData}
69+
layout="vertical"
70+
margin={{ left: 10, right: 50 }}
71+
style={{ cursor: "pointer" }}
72+
>
4473
<CartesianGrid strokeDasharray="3 3" stroke="#27272a" />
4574
<XAxis
4675
type="number"
@@ -60,18 +89,29 @@ export function SpendBarChart({ data, highlightEmail }: SpendBarChartProps) {
6089
contentStyle={TOOLTIP_STYLE}
6190
labelStyle={TOOLTIP_LABEL_STYLE}
6291
itemStyle={TOOLTIP_ITEM_STYLE}
63-
cursor={{ fill: "rgba(255,255,255,0.05)" }}
92+
cursor={{ fill: "rgba(255,255,255,0.08)" }}
6493
formatter={(value) => [`$${Number(value).toFixed(2)}`, "Spend"]}
6594
labelFormatter={(_label, payload) => {
6695
const item = payload?.[0]?.payload;
6796
return item ? `#${item.rank} ${item.name} (${item.email})` : String(_label);
6897
}}
6998
/>
70-
<Bar dataKey="spend" radius={[0, 4, 4, 0]}>
71-
{chartData.map((entry) => (
99+
<Bar
100+
dataKey="spend"
101+
radius={[0, 4, 4, 0]}
102+
onClick={(_data, index) => {
103+
const entry = chartData[index];
104+
if (entry) handleBarClick(entry);
105+
}}
106+
onMouseEnter={(_data, index) => setHoveredIndex(index)}
107+
onMouseLeave={() => setHoveredIndex(null)}
108+
>
109+
{chartData.map((entry, index) => (
72110
<Cell
73111
key={entry.email}
74-
fill={entry.email === highlightEmail ? "#f59e0b" : "#3b82f6"}
112+
fill={getFill(entry, index)}
113+
style={{ transition: "fill 0.15s ease, filter 0.15s ease" }}
114+
filter={hoveredIndex === index ? "brightness(1.15)" : undefined}
75115
/>
76116
))}
77117
<LabelList

0 commit comments

Comments
 (0)