agent-ecosystem/src/renderer/components/report/AssessmentBadge.tsx
iliya 47dac2e8b5 feat: migrate to React 19
Upgrade React 18.3.1 → 19.2.4 with full type compatibility.

Changes:
- react, react-dom → ^19.0.0
- @types/react, @types/react-dom → ^19.0.0
- lucide-react → ^0.577.0 (React 19 type fixes)
- @tiptap/* → ^3.20.4 (React 19 support)
- useRef calls now require explicit initial value (undefined)
- RefObject types updated for React 19 (includes null)
- MutableRefObject → RefObject (deprecated in 19)
- act() import moved from react-dom/test-utils to react
- Scoped JSX namespace imports added where needed
2026-03-24 17:11:55 +02:00

78 lines
2.4 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import {
assessmentColor,
assessmentExplanation,
assessmentLabel,
} from '@renderer/utils/reportAssessments';
import type { MetricKey } from '@renderer/utils/reportAssessments';
interface AssessmentBadgeProps {
assessment: string;
metricKey?: MetricKey;
}
export const AssessmentBadge = ({ assessment, metricKey }: AssessmentBadgeProps) => {
const color = assessmentColor(assessment);
const explanation = metricKey ? assessmentExplanation(metricKey, assessment) : '';
const [showTooltip, setShowTooltip] = useState(false);
const [tooltipPos, setTooltipPos] = useState({ top: 0, left: 0 });
const badgeRef = useRef<HTMLSpanElement>(null);
const enterTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
const leaveTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
const handleMouseEnter = useCallback(() => {
if (!explanation) return;
clearTimeout(leaveTimer.current);
enterTimer.current = setTimeout(() => {
if (badgeRef.current) {
const rect = badgeRef.current.getBoundingClientRect();
setTooltipPos({ top: rect.bottom + 4, left: rect.left + rect.width / 2 });
}
setShowTooltip(true);
}, 200);
}, [explanation]);
const handleMouseLeave = useCallback(() => {
clearTimeout(enterTimer.current);
leaveTimer.current = setTimeout(() => setShowTooltip(false), 150);
}, []);
useEffect(() => {
return () => {
clearTimeout(enterTimer.current);
clearTimeout(leaveTimer.current);
};
}, []);
return (
<>
<span
ref={badgeRef}
className="rounded px-2 py-0.5 text-xs font-medium"
style={{ backgroundColor: `${color}20`, color }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{assessmentLabel(assessment)}
</span>
{showTooltip &&
explanation &&
createPortal(
<div
className="pointer-events-none fixed z-50 max-w-60 rounded border border-border bg-surface-raised px-2.5 py-1.5 text-xs text-text-secondary shadow-lg"
style={{
top: tooltipPos.top,
left: tooltipPos.left,
transform: 'translateX(-50%)',
}}
>
{explanation}
</div>,
document.body
)}
</>
);
};