130 lines
5.1 KiB
TypeScript
130 lines
5.1 KiB
TypeScript
"use client";
|
||
import React, { useState, useRef, useEffect } from 'react';
|
||
import { motion, AnimatePresence } from 'motion/react';
|
||
import { Info, ExternalLink } from 'lucide-react';
|
||
import { SCIENTIFIC_TERMS } from '../lib/data/scientificTerms';
|
||
import { useSettingsStore } from '../lib/store/settingsStore';
|
||
|
||
interface TooltipProps {
|
||
termKey: string;
|
||
children: React.ReactNode;
|
||
onWikiNavigate?: (id: string) => void;
|
||
}
|
||
|
||
export default function Tooltip({ termKey, children, onWikiNavigate }: TooltipProps) {
|
||
const [isVisible, setIsVisible] = useState(false);
|
||
const [positionClass, setPositionClass] = useState("left-1/2 -translate-x-1/2");
|
||
const [arrowClass, setArrowClass] = useState("left-1/2 -translate-x-1/2");
|
||
const containerRef = useRef<HTMLSpanElement>(null);
|
||
const tooltipRef = useRef<HTMLDivElement>(null);
|
||
|
||
const scientificTerms = useSettingsStore(state => state.scientificTerms);
|
||
const termData = scientificTerms[termKey] || SCIENTIFIC_TERMS[termKey];
|
||
|
||
// Adjust horizontal position dynamically to prevent overflowing viewport edges
|
||
useEffect(() => {
|
||
if (isVisible && containerRef.current) {
|
||
const rect = containerRef.current.getBoundingClientRect();
|
||
const screenWidth = window.innerWidth;
|
||
|
||
if (rect.left < 140) {
|
||
// Too close to left edge (in LTR) or screen boundary
|
||
setPositionClass("left-0 translate-x-0");
|
||
setArrowClass("left-6 translate-x-0");
|
||
} else if (screenWidth - rect.right < 140) {
|
||
// Too close to right edge
|
||
setPositionClass("right-0 translate-x-0");
|
||
setArrowClass("right-6 translate-x-0");
|
||
} else {
|
||
setPositionClass("left-1/2 -translate-x-1/2");
|
||
setArrowClass("left-1/2 -translate-x-1/2");
|
||
}
|
||
}
|
||
}, [isVisible]);
|
||
|
||
// Click outside listener to dismiss on mobile
|
||
useEffect(() => {
|
||
if (!isVisible) return;
|
||
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
|
||
if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
|
||
setIsVisible(false);
|
||
}
|
||
};
|
||
document.addEventListener("mousedown", handleClickOutside);
|
||
document.addEventListener("touchstart", handleClickOutside);
|
||
return () => {
|
||
document.removeEventListener("mousedown", handleClickOutside);
|
||
document.removeEventListener("touchstart", handleClickOutside);
|
||
};
|
||
}, [isVisible]);
|
||
|
||
if (!termData) return <>{children}</>;
|
||
|
||
const handleToggle = (e: React.MouseEvent) => {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
setIsVisible(prev => !prev);
|
||
};
|
||
|
||
return (
|
||
<span
|
||
ref={containerRef}
|
||
className={`relative inline-block group cursor-pointer border-b border-dotted border-canina-blue/60 select-none ${isVisible ? 'z-40' : 'z-10'}`}
|
||
onClick={handleToggle}
|
||
onMouseEnter={() => {
|
||
if (typeof window !== "undefined" && window.matchMedia?.("(hover: hover)")?.matches !== false) {
|
||
setIsVisible(true);
|
||
}
|
||
}}
|
||
onMouseLeave={() => {
|
||
if (typeof window !== "undefined" && window.matchMedia?.("(hover: hover)")?.matches !== false) {
|
||
setIsVisible(false);
|
||
}
|
||
}}
|
||
>
|
||
{children}
|
||
|
||
<AnimatePresence>
|
||
{isVisible && (
|
||
<motion.div
|
||
ref={tooltipRef}
|
||
initial={{ opacity: 0, y: 10, scale: 0.95 }}
|
||
animate={{ opacity: 1, y: 0, scale: 1 }}
|
||
exit={{ opacity: 0, y: 10, scale: 0.95 }}
|
||
onClick={(e) => e.stopPropagation()}
|
||
style={{ backgroundColor: '#ffffff' }}
|
||
className={`absolute bottom-full mb-3 w-72 max-w-[calc(100vw-2rem)] p-4 sm:p-5 !bg-white text-medical-gray-900 rounded-2xl shadow-2xl border-2 border-medical-gray-200 z-[100] text-sm ${positionClass}`}
|
||
dir="rtl"
|
||
>
|
||
<div className="flex items-center gap-2 mb-2 text-canina-blue">
|
||
<Info className="w-4 h-4 shrink-0 text-canina-blue" />
|
||
<span className="font-black text-xs-plus uppercase tracking-widest text-canina-blue">دانشنامه علمی کنینا</span>
|
||
</div>
|
||
<h5 className="font-black text-base text-medical-gray-900 mb-2 leading-snug">{termData.term}</h5>
|
||
<p className="text-medical-gray-800 text-xs leading-relaxed mb-4 font-bold text-justify">
|
||
{termData.definition}
|
||
</p>
|
||
{termData.wikiId && (
|
||
<button
|
||
onClick={(e) => {
|
||
e.stopPropagation();
|
||
onWikiNavigate?.(termData.wikiId!);
|
||
}}
|
||
className="flex items-center justify-between text-canina-blue hover:text-blue-700 transition-colors text-xs font-black cursor-pointer pt-2 border-t border-medical-gray-100 w-full"
|
||
>
|
||
<span>مطالعه مقاله کامل</span>
|
||
<ExternalLink className="w-3.5 h-3.5 shrink-0" />
|
||
</button>
|
||
)}
|
||
{/* Arrow */}
|
||
<div
|
||
style={{ borderTopColor: '#ffffff' }}
|
||
className={`absolute top-full border-8 border-transparent border-t-white ${arrowClass}`}
|
||
/>
|
||
</motion.div>
|
||
)}
|
||
</AnimatePresence>
|
||
</span>
|
||
);
|
||
}
|