- Rewrite chatbot container + panel to new surface/signal/ink tokens (was still on dead glass/text-primary tokens -> wrong colors) - loading-skeleton: glass -> surface-2 - Fix a11y: SVG charts role=img+aria-label, audio aria-label, message-entry as real <button>, tooltip biome-ignore (intentional) - Type messages/page initialPage (noImplicitAny) - tsc clean, next build green, biome 0 errors
78 lines
1.8 KiB
TypeScript
78 lines
1.8 KiB
TypeScript
"use client";
|
|
|
|
import { useId } from "react";
|
|
|
|
export interface SparklineProps {
|
|
data: number[];
|
|
width?: number;
|
|
height?: number;
|
|
stroke?: string;
|
|
className?: string;
|
|
fill?: boolean;
|
|
}
|
|
|
|
export function Sparkline({
|
|
data,
|
|
width = 120,
|
|
height = 36,
|
|
stroke = "var(--color-signal)",
|
|
className,
|
|
fill = true,
|
|
}: SparklineProps) {
|
|
const id = useId().replace(/:/g, "");
|
|
if (data.length < 2)
|
|
return (
|
|
<svg
|
|
width={width}
|
|
height={height}
|
|
className={className}
|
|
role="img"
|
|
aria-label="No data"
|
|
/>
|
|
);
|
|
|
|
const min = Math.min(...data);
|
|
const max = Math.max(...data);
|
|
const span = max - min || 1;
|
|
const stepX = width / (data.length - 1);
|
|
const pts = data.map((v, i) => {
|
|
const x = i * stepX;
|
|
const y = height - ((v - min) / span) * (height - 4) - 2;
|
|
return [x, y] as const;
|
|
});
|
|
const line = pts
|
|
.map(
|
|
(p, i) => `${i === 0 ? "M" : "L"}${p[0].toFixed(1)},${p[1].toFixed(1)}`,
|
|
)
|
|
.join(" ");
|
|
const area = `${line} L${width},${height} L0,${height} Z`;
|
|
|
|
return (
|
|
<svg
|
|
width={width}
|
|
height={height}
|
|
viewBox={`0 0 ${width} ${height}`}
|
|
className={className}
|
|
preserveAspectRatio="none"
|
|
role="img"
|
|
aria-label="Trend sparkline"
|
|
>
|
|
<defs>
|
|
<linearGradient id={`spark-${id}`} x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor={stroke} stopOpacity="0.28" />
|
|
<stop offset="100%" stopColor={stroke} stopOpacity="0" />
|
|
</linearGradient>
|
|
</defs>
|
|
{fill && <path d={area} fill={`url(#spark-${id})`} />}
|
|
<path
|
|
d={line}
|
|
fill="none"
|
|
stroke={stroke}
|
|
strokeWidth={1.6}
|
|
strokeLinejoin="round"
|
|
strokeLinecap="round"
|
|
/>
|
|
</svg>
|
|
);
|
|
}
|