| 1 | "use client" |
| 2 | |
| 3 | import { AnimatePresence, motion } from "motion/react" |
| 4 | import { useState } |
| 5 | import { useCommonChart } |
| 6 | import { cn } |
| 7 | import { rgb } |
| 8 | |
| 9 | export type TooltipVariant |
| 10 | |
| 11 | const VARIANT: Record |
| 12 | default: "bg-popover", |
| 13 | "frosted-glass": "bg-popover/70 backdrop-blur-sm", |
| 14 | } |
| 15 | |
| 16 | /** |
| 17 | * Floating hover tooltip. Reads the shared common context so it works in every |
| 18 | * chart family. It glides between points and fades in/out (instead of snapping), |
| 19 | * and dims unselected series/slices. |
| 20 | */ |
| 21 | export function Tooltip({ |
| 22 | labelKey, |
| 23 | valueFormatter, |
| 24 | variant = "default" |
| 25 | }: { |
| 26 | labelKey?: string |
| 27 | valueFormatter?: ( |
| 28 | variant?: TooltipVariant |
| 29 | }) { |
| 30 | const chart = |
| 31 | const show = |
| 32 | |
| 33 | // Retain the last hovered index so the card keeps its content while fading |
| 34 | // out - adjust-state-during-render (no refs in render). |
| 35 | const [lastIndex, |
| 36 | if (chart.hoverIndex |
| 37 | setLastIndex(chart.hoverIndex |
| 38 | } |
| 39 | const index = |
| 40 | |
| 41 | const heading = |
| 42 | const items = |
| 43 | |
| 44 | return ( |
| 45 | <AnimatePresence> |
| 46 | {show && items |
| 47 | <motion.div |
| 48 | key="dither-tooltip" |
| 49 | initial={{ |
| 50 | opacity: 0, |
| 51 | x: "-50%", |
| 52 | y: "-115%", |
| 53 | top: chart.tooltipTop |
| 54 | left: chart.tooltipLeft |
| 55 | }} |
| 56 | animate={{ |
| 57 | opacity: 1, |
| 58 | x: "-50%", |
| 59 | y: "-115%", |
| 60 | top: chart.tooltipTop |
| 61 | left: chart.tooltipLeft |
| 62 | }} |
| 63 | exit={{ opacity: |
| 64 | transition={{ |
| 65 | type: "spring", |
| 66 | stiffness: 520, |
| 67 | damping: 38, |
| 68 | mass: 0.6, |
| 69 | }} |
| 70 | className={cn( |
| 71 | "pointer-events-none absolute z-10 rounded-md border px-2 py-1 shadow-sm", |
| 72 | VARIANT[variant] |
| 73 | )} |
| 74 | > |
| 75 | {heading && ( |
| 76 | <div className="mb-0.5 font-mono text-[10px] text-muted-foreground |
| 77 | {heading} |
| 78 | </div> |
| 79 | )} |
| 80 | <div className="flex flex-col gap-0.5 |
| 81 | {items.map(( |
| 82 | <div |
| 83 | key={item.name |
| 84 | className="flex items-center gap-1.5 font-mono text-[11px] text-popover-foreground tabular-nums" |
| 85 | style={{ opacity: |
| 86 | > |
| 87 | <span |
| 88 | className="size-2 rounded-[1px]" |
| 89 | style={{ backgroundColor: |
| 90 | /> |
| 91 | <span className="text-muted-foreground |
| 92 | <span className="ml-auto pl-2 text-foreground |
| 93 | {valueFormatter |
| 94 | ? valueFormatter(item. |
| 95 | : item.value. |
| 96 | </span> |
| 97 | </div> |
| 98 | ))} |
| 99 | </div> |
| 100 | </motion.div> |
| 101 | )} |
| 102 | </AnimatePresence> |
| 103 | ) |
| 104 | } |
| 105 | |
| 106 | Tooltip.chartLayer = " |