Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | 1x 1x 1x 1x 1x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 36x 34x 34x 34x 14x 14x 6x 34x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 6x 28x 34x 36x 1x | import ReactDOM from 'react-dom';
import { Tooltip } from 'recharts';
// Constants
import { ICONS_BY_KEY } from '@/constants/chart';
// Utils
import { formatResult } from '@/utils/statistic';
// Types
import { ChartKey } from '@/types/chart';
type CustomTooltipProps = {
cursor?: boolean;
isAnimationActive?: boolean;
colorsByKey?: Record<string, string>;
iconsByKey?: Record<string, React.ComponentType<{ className?: string }>>;
modeKey?: ChartKey;
containerRef?: React.RefObject<HTMLDivElement | null>;
offset?: number;
dotRef?: React.RefObject<Record<number, { cx: number; cy: number }>>;
activeIndex?: number | null;
};
const CustomTooltip = ({
cursor = false,
isAnimationActive = false,
colorsByKey,
iconsByKey = ICONS_BY_KEY,
modeKey,
containerRef,
offset = 16,
dotRef,
activeIndex = null,
}: CustomTooltipProps) => (
<Tooltip
cursor={cursor}
isAnimationActive={isAnimationActive}
content={({ active = false, payload = [], label = '', coordinate }) => {
// Only render when tooltip is active, has data, and a valid container
if (
active &&
activeIndex !== null &&
!!payload.length &&
coordinate &&
containerRef?.current
) {
const rect = containerRef.current.getBoundingClientRect();
const activeDot = dotRef?.current?.[activeIndex ?? 0];
const dotCy = activeDot?.cy ?? coordinate.y;
// Adjust for container scroll
const scrollLeft = containerRef.current.scrollLeft;
// Calculate absolute position relative to container
const left = rect.left + coordinate.x - scrollLeft;
const top = rect.top + dotCy - offset;
const row = payload[0]?.payload;
const title = row?.title || '';
// Render tooltip in a portal so it's positioned globally
return ReactDOM.createPortal(
<div
className="fixed bg-primary-750 px-2.75 py-2 rounded-lg -translate-x-1/2 -translate-y-full pointer-events-none z-50 whitespace-nowrap"
style={{ left, top }}
>
{title && (
<p className="font-medium text-white text-5xs mb-1">{title}</p>
)}
<ul className="space-y-0.5 gap-1 flex flex-col -ml-0.5">
{payload.map((entry: any, idx: number) => {
const key = modeKey || entry.dataKey;
const Icon = iconsByKey[key];
return (
<li
key={idx}
className="flex flex-row items-center gap-2.5"
style={{ color: colorsByKey?.[entry.dataKey] }}
>
{Icon && <Icon className="w-6 h-6" />}
<span className="text-3xs font-medium text-white mt-0.5">
{formatResult(entry.value)}
</span>
</li>
);
})}
</ul>
<div className="absolute left-1/2 -bottom-1 transform -translate-x-1/2 w-0 h-0 border-l-[6px] border-r-[6px] border-t-[6px] border-l-transparent border-r-transparent border-t-primary-750" />
</div>,
document.body,
);
}
return null;
}}
/>
);
export default CustomTooltip;
|