diff --git a/src/components/common/PriceSparkline.tsx b/src/components/common/PriceSparkline.tsx
new file mode 100644
index 00000000..bf67d7e3
--- /dev/null
+++ b/src/components/common/PriceSparkline.tsx
@@ -0,0 +1,83 @@
+import { cn } from '@/lib/utils';
+
+interface PriceSparklineProps {
+ dataPoints: number[];
+ width?: number;
+ height?: number;
+ className?: string;
+}
+
+const POSITIVE_COLOR = '#34d399';
+const NEUTRAL_COLOR = 'currentColor';
+
+export function PriceSparkline({
+ dataPoints,
+ width = 120,
+ height = 40,
+ className,
+}: PriceSparklineProps) {
+ if (dataPoints.length === 0) return null;
+
+ const lineColor =
+ dataPoints.length >= 2 && dataPoints[dataPoints.length - 1] > dataPoints[0]
+ ? POSITIVE_COLOR
+ : NEUTRAL_COLOR;
+
+ const padding = 2;
+ const innerWidth = width - padding * 2;
+ const innerHeight = height - padding * 2;
+
+ if (dataPoints.length === 1) {
+ return (
+
+ );
+ }
+
+ const min = Math.min(...dataPoints);
+ const max = Math.max(...dataPoints);
+ const range = max - min || 1;
+
+ const buildPathD = () =>
+ dataPoints
+ .map((value, index) => {
+ const x = padding + (index / (dataPoints.length - 1)) * innerWidth;
+ const y =
+ padding + (1 - (value - min) / range) * innerHeight;
+ return `${index === 0 ? 'M' : 'L'}${x.toFixed(1)} ${y.toFixed(1)}`;
+ })
+ .join(' ');
+
+ return (
+
+ );
+}
diff --git a/src/components/common/__tests__/PriceSparkline.test.tsx b/src/components/common/__tests__/PriceSparkline.test.tsx
new file mode 100644
index 00000000..1ebdbf40
--- /dev/null
+++ b/src/components/common/__tests__/PriceSparkline.test.tsx
@@ -0,0 +1,49 @@
+import { render } from '@testing-library/react';
+import { describe, expect, it } from 'vitest';
+import { PriceSparkline } from '../PriceSparkline';
+
+describe('PriceSparkline', () => {
+ it('renders an SVG path for 7 data points', () => {
+ const { container } = render(
+
+ );
+
+ const svg = container.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+
+ const path = container.querySelector('path');
+ expect(path).toBeInTheDocument();
+ expect(path).toHaveAttribute('d');
+ });
+
+ it('renders without error for 1 data point and does not produce a line path', () => {
+ const { container } = render(
+
+ );
+
+ const svg = container.querySelector('svg');
+ expect(svg).toBeInTheDocument();
+
+ const path = container.querySelector('path');
+ expect(path).not.toBeInTheDocument();
+ });
+
+ it('renders nothing for 0 data points', () => {
+ const { container } = render(
+
+ );
+
+ expect(container.innerHTML).toBe('');
+ });
+
+ it('applies green line colour when the last value is higher than the first', () => {
+ const { container } = render(
+
+ );
+
+ const path = container.querySelector('path');
+ expect(path).toHaveAttribute('stroke', '#34d399');
+ });
+});