Files
wehub-resource-sync e30e75b5d4
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:40:13 +08:00

266 lines
8.3 KiB
TypeScript

"use client";
import { useId, useState } from "react";
import {
Area,
AreaChart,
CartesianGrid,
Line,
ReferenceLine,
XAxis,
YAxis,
} from "recharts";
import {
ChartContainer,
type ChartConfig,
ChartLegend,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { cn } from "@/lib/utils";
import { formatCompact } from "@/lib/format";
import type { TimelineSeries } from "@/lib/traction";
const MONTH_NAMES = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
];
const formatTick = (yearMonth: string) => {
const parts = yearMonth.split("-");
const idx = Number(parts[1]) - 1;
return MONTH_NAMES[idx] ?? yearMonth;
};
const formatTooltipLabel = (yearMonth: string) => {
const [y, m] = yearMonth.split("-");
const date = new Date(Number(y), Number(m) - 1, 1);
return date.toLocaleDateString("en-US", { month: "long", year: "numeric" });
};
export function DownloadsChart({ timeline }: { timeline: TimelineSeries }) {
const gradientPrefix = useId();
const [hidden, setHidden] = useState<Set<string>>(new Set());
if (timeline.data.length < 2 || timeline.series.length === 0) {
return (
<div className="border-border text-muted-foreground flex h-[260px] items-center justify-center rounded-lg border border-dashed text-sm md:h-[360px]">
Download history is currently unavailable.
</div>
);
}
const config: ChartConfig = {};
for (const s of timeline.series) {
config[s.key] = {
label: s.label,
color: `var(--chart-${s.chartIndex})`,
};
}
const toggle = (key: string) => {
setHidden((prev) => {
const next = new Set(prev);
if (next.has(key)) next.delete(key);
else next.add(key);
return next;
});
};
return (
<ChartContainer
config={config}
className="aspect-auto h-[260px] w-full md:h-[360px]"
>
<AreaChart
data={timeline.data}
margin={{ left: 8, right: 16, top: 8, bottom: 0 }}
>
<defs>
{timeline.series.map((s) => (
<linearGradient
key={s.key}
id={`${gradientPrefix}-${s.key}`}
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor={`var(--color-${s.key})`}
stopOpacity={0.18}
/>
<stop
offset="100%"
stopColor={`var(--color-${s.key})`}
stopOpacity={0}
/>
</linearGradient>
))}
</defs>
<CartesianGrid vertical={false} strokeDasharray="3 3" />
<XAxis
dataKey="date"
tickFormatter={formatTick}
tickLine={false}
axisLine={false}
minTickGap={32}
/>
<YAxis
tickFormatter={(v) => formatCompact(v as number)}
tickLine={false}
axisLine={false}
width={42}
/>
<ChartTooltip
cursor={{ stroke: "var(--border)" }}
content={(tooltipProps) => {
// hide _proj entries when their raw counterpart has a value (avoids dup rows on the bridging month).
const filtered = tooltipProps.payload
?.filter((entry) => {
const key = String(entry?.dataKey ?? "");
if (!key.endsWith("_proj")) return true;
const baseKey = key.slice(0, -5);
const rowPayload = entry.payload as
| Record<string, number | undefined>
| undefined;
return rowPayload?.[baseKey] === undefined;
})
.slice()
.sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0));
return (
<ChartTooltipContent
{...(tooltipProps as Record<string, unknown>)}
payload={filtered}
labelFormatter={(_, p) => {
const ym = p?.[0]?.payload?.date as string | undefined;
return ym ? formatTooltipLabel(ym) : "";
}}
formatter={(value, name, item) => {
const rawName = String(name);
const baseKey = rawName.endsWith("_proj")
? rawName.slice(0, -5)
: rawName;
const series = timeline.series.find((s) => s.key === baseKey);
const color =
(item as { color?: string } | undefined)?.color ??
(item?.payload as { fill?: string } | undefined)?.fill;
return (
<>
<div
className="h-2.5 w-2.5 shrink-0 rounded-[2px]"
style={{ backgroundColor: color }}
/>
<div className="flex flex-1 items-center justify-between gap-3 leading-none">
<span className="text-muted-foreground">
{series?.label ?? rawName}
</span>
<span className="text-foreground font-mono font-medium tabular-nums">
{formatCompact(value as number)}
</span>
</div>
</>
);
}}
indicator="dot"
/>
);
}}
/>
{timeline.series.map((s) => {
const isHidden = hidden.has(s.key);
return (
<Area
key={s.key}
dataKey={s.key}
type="monotone"
stroke={isHidden ? "transparent" : `var(--color-${s.key})`}
strokeWidth={1.75}
fill={
isHidden ? "transparent" : `url(#${gradientPrefix}-${s.key})`
}
isAnimationActive={false}
/>
);
})}
{timeline.projectedMonth ? (
<>
<ReferenceLine
x={timeline.projectedMonth}
stroke="var(--muted-foreground)"
strokeDasharray="3 3"
strokeOpacity={0.4}
/>
{timeline.series.map((s) => {
const isHidden = hidden.has(s.key);
return (
<Line
key={`${s.key}-proj`}
dataKey={`${s.key}_proj`}
stroke={isHidden ? "transparent" : `var(--color-${s.key})`}
strokeWidth={1.5}
strokeDasharray="5 4"
type="monotone"
dot={false}
activeDot={false}
connectNulls={false}
isAnimationActive={false}
legendType="none"
/>
);
})}
</>
) : null}
<ChartLegend
content={({ payload }) => (
<div className="flex flex-wrap items-center justify-center gap-x-4 gap-y-1 pt-3">
{(
payload as
| Array<{
dataKey?: string | number;
value?: string;
color?: string;
}>
| undefined
)?.map((item) => {
const key = String(item.dataKey ?? item.value ?? "");
const series = timeline.series.find((s) => s.key === key);
if (!series) return null;
const isHidden = hidden.has(key);
return (
<button
key={key}
type="button"
onClick={() => toggle(key)}
className={cn(
"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 flex cursor-pointer items-center gap-1.5 rounded-sm text-xs transition-opacity outline-none focus-visible:ring-2",
isHidden && "opacity-40",
)}
>
<div
className="h-2 w-2 shrink-0 rounded-[2px]"
style={{ backgroundColor: `var(--color-${key})` }}
/>
<span>{series.label}</span>
</button>
);
})}
</div>
)}
/>
</AreaChart>
</ChartContainer>
);
}