'use client' import { useId, useRef, useState } from 'react' import clsx from 'clsx' import { motion, useInView, useMotionValue } from 'framer-motion' import { AppScreen } from '@/components/AppScreen' const prices = [ 997.56, 944.34, 972.25, 832.4, 888.76, 834.8, 805.56, 767.38, 861.21, 669.6, 694.39, 721.32, 694.03, 610.1, 502.2, 549.56, 611.03, 583.4, 610.14, 660.6, 752.11, 721.19, 638.89, 661.7, 694.51, 580.3, 638.0, 613.3, 651.64, 560.51, 611.45, 670.68, 752.56, ] const maxPrice = Math.max(...prices) const minPrice = Math.min(...prices) function Chart({ className, activePointIndex, onChangeActivePointIndex, width: totalWidth, height: totalHeight, paddingX = 0, paddingY = 0, gridLines = 6, ...props }: React.ComponentPropsWithoutRef<'svg'> & { activePointIndex: number | null onChangeActivePointIndex: (index: number | null) => void width: number height: number paddingX?: number paddingY?: number gridLines?: number }) { let width = totalWidth - paddingX * 2 let height = totalHeight - paddingY * 2 let id = useId() let svgRef = useRef>(null) let pathRef = useRef>(null) let isInView = useInView(svgRef, { amount: 0.5, once: true }) let pathWidth = useMotionValue(0) let [interactionEnabled, setInteractionEnabled] = useState(false) let path = '' let points: Array<{ x: number; y: number }> = [] for (let index = 0; index < prices.length; index++) { let x = paddingX + (index / (prices.length - 1)) * width let y = paddingY + (1 - (prices[index] - minPrice) / (maxPrice - minPrice)) * height points.push({ x, y }) path += `${index === 0 ? 'M' : 'L'} ${x.toFixed(4)} ${y.toFixed(4)}` } return ( onChangeActivePointIndex(null), onPointerMove: (event) => { let x = event.nativeEvent.offsetX let closestPointIndex: number | null = null let closestDistance = Infinity for ( let pointIndex = 0; pointIndex < points.length; pointIndex++ ) { let point = points[pointIndex] let distance = Math.abs(point.x - x) if (distance < closestDistance) { closestDistance = distance closestPointIndex = pointIndex } else { break } } onChangeActivePointIndex(closestPointIndex) }, } : {})} {...props} > {[...Array(gridLines - 1).keys()].map((index) => ( ))} { if (pathRef.current && typeof pathLength === 'number') { pathWidth.set( pathRef.current.getPointAtLength( pathLength * pathRef.current.getTotalLength(), ).x, ) } }} onAnimationComplete={() => setInteractionEnabled(true)} /> {activePointIndex !== null && ( <> )} ) } export function AppDemo() { let [activePointIndex, setActivePointIndex] = useState(null) let activePriceIndex = activePointIndex ?? prices.length - 1 let activeValue = prices[activePriceIndex] let previousValue = prices[activePriceIndex - 1] let percentageChange = activePriceIndex === 0 ? null : ((activeValue - previousValue) / previousValue) * 100 return (
Tailwind Labs, Inc.
$CSS
{activeValue.toFixed(2)}
USD
{percentageChange && (
= 0 ? 'text-cyan-500' : 'text-gray-500', )} > {`${ percentageChange >= 0 ? '+' : '' }${percentageChange.toFixed(2)}%`}
)}
1D
5D
1M
6M
1Y
5Y
Trade
Open
6,387.55
Closed
6,487.09
Low
6,322.01
) }