@codecademy/gamut

72.3.072.3.1-alpha.44ae7c.0
Sparkline.tsx
+Sparkline.tsxNew file
+92
Index: Sparkline.tsx
===================================================================
--- Sparkline.tsx
+++ Sparkline.tsx
@@ -0,0 +1,92 @@
+import { Colors, useColorModes } from '@codecademy/gamut-styles';
+import * as React from 'react';
+
+export interface SparklineProps
+  extends Omit<React.SVGProps<SVGSVGElement>, 'color' | 'points'> {
+  /** Numeric values to plot, left to right. */
+  points: number[];
+  /** Rendered width of the SVG viewport, in px. */
+  width?: number;
+  /** Rendered height of the SVG viewport, in px. */
+  height?: number;
+  /** Thickness of the trend line, in px. */
+  strokeWidth?: number;
+  /**
+   * Semantic (or palette) color key to draw the line in.
+   * Resolved through the active theme/ColorMode, never a hardcoded hex value.
+   */
+  color?: Colors;
+}
+
+/**
+ * Builds an SVG path `d` attribute for a simple polyline trend chart,
+ * scaling `points` to fit within `width`/`height`.
+ */
+const buildSparklinePath = (
+  points: number[],
+  width: number,
+  height: number
+): string => {
+  if (points.length === 0) return '';
+  if (points.length === 1) {
+    const y = height / 2;
+    return `M0 ${y} L${width} ${y}`;
+  }
+
+  const min = Math.min(...points);
+  const max = Math.max(...points);
+  const range = max - min || 1;
+  const stepX = width / (points.length - 1);
+
+  return points
+    .map((value, index) => {
+      const x = index * stepX;
+      // Invert y so higher values are drawn nearer the top of the viewport.
+      const y = height - ((value - min) / range) * height;
+      return `${index === 0 ? 'M' : 'L'}${x.toFixed(2)} ${y.toFixed(2)}`;
+    })
+    .join(' ');
+};
+
+/**
+ * Sparkline draws a minimal inline SVG trend line.
+ *
+ * The line is drawn imperatively via the `<path>` `d` and `stroke`
+ * attributes rather than through a styled component's CSS, so the stroke
+ * color can't come from `css()`/`variant()`/`states()`. Instead it's read
+ * from the design system theme in plain JS via the `useColorModes()`
+ * escape hatch (see the `gamut-style-utilities` skill's `useTheme()`
+ * section) — this keeps the color a semantic token that resolves through
+ * the active ColorMode instead of a hardcoded hex value.
+ */
+export const Sparkline: React.FC<SparklineProps> = ({
+  points,
+  width = 120,
+  height = 32,
+  strokeWidth = 2,
+  color = 'primary',
+  ...rest
+}) => {
+  const [, , , getColorValue] = useColorModes();
+  const strokeColor = getColorValue(color);
+
+  const path = buildSparklinePath(points, width, height);
+
+  return (
+    <svg
+      height={height}
+      viewBox={`0 0 ${width} ${height}`}
+      width={width}
+      {...rest}
+    >
+      <path
+        d={path}
+        fill="none"
+        stroke={strokeColor}
+        strokeLinecap="round"
+        strokeLinejoin="round"
+        strokeWidth={strokeWidth}
+      />
+    </svg>
+  );
+};