72 lines
2.0 KiB
TypeScript
72 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
import { cn } from "@/lib/utils";
|
|
import type { PointerState } from "@/lib/visualizations/types";
|
|
|
|
interface PointerProps {
|
|
pointer: PointerState;
|
|
elementWidth: number;
|
|
gap: number;
|
|
value?: number;
|
|
/** Hide the value to save space when pointers are close */
|
|
hideValue?: boolean;
|
|
/** Horizontal offset when multiple pointers at same position */
|
|
horizontalOffset?: number;
|
|
className?: string;
|
|
}
|
|
|
|
const COLOR_CLASSES = {
|
|
left: "text-blue-500 fill-blue-500",
|
|
right: "text-orange-500 fill-orange-500",
|
|
mid: "text-purple-500 fill-purple-500",
|
|
default: "text-blue-500 fill-blue-500",
|
|
} as const;
|
|
|
|
export function Pointer({
|
|
pointer,
|
|
elementWidth,
|
|
gap,
|
|
value,
|
|
hideValue = false,
|
|
horizontalOffset = 0,
|
|
className,
|
|
}: PointerProps) {
|
|
// Calculate center of element: index * (width + gap) + width / 2, plus any offset for overlapping
|
|
const offset = pointer.index * (elementWidth + gap) + elementWidth / 2 + horizontalOffset;
|
|
|
|
return (
|
|
<motion.div
|
|
initial={false}
|
|
animate={{ left: offset }}
|
|
transition={{
|
|
type: "spring",
|
|
stiffness: 300,
|
|
damping: 30,
|
|
}}
|
|
className={cn("absolute top-0 h-full", className)}
|
|
>
|
|
{/* Label - centered above the arrow */}
|
|
<span
|
|
className={cn(
|
|
"absolute left-0 top-0 -translate-x-1/2 whitespace-nowrap rounded-full px-2 py-0.5 text-xs font-medium",
|
|
COLOR_CLASSES[pointer.color]
|
|
)}
|
|
>
|
|
{pointer.name}
|
|
{!hideValue && pointer.showValue && value !== undefined && (
|
|
<span className="ml-1 font-mono">= {value}</span>
|
|
)}
|
|
</span>
|
|
{/* Arrow - centered at position */}
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
className={cn("absolute left-0 bottom-0 -translate-x-1/2 h-4 w-4", COLOR_CLASSES[pointer.color])}
|
|
aria-hidden="true"
|
|
>
|
|
<path d="M12 22L4 14h6V2h4v12h6L12 22z" />
|
|
</svg>
|
|
</motion.div>
|
|
);
|
|
}
|