mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Ship my-products / customer-products UI with gallery uploads, status controls, module gating, and related shared UI polish. Co-authored-by: Cursor <cursoragent@cursor.com>
78 lines
1.8 KiB
TypeScript
78 lines
1.8 KiB
TypeScript
import { useEffect, useRef, useState, type ReactElement } from 'react'
|
|
import { createPortal } from 'react-dom'
|
|
import styles from './Tooltip.module.css'
|
|
|
|
interface TooltipProps {
|
|
label: string
|
|
children: ReactElement
|
|
}
|
|
|
|
export function Tooltip({ label, children }: TooltipProps) {
|
|
const wrapRef = useRef<HTMLSpanElement>(null)
|
|
const [visible, setVisible] = useState(false)
|
|
const [coords, setCoords] = useState({ top: 0, left: 0 })
|
|
|
|
function updatePosition() {
|
|
const el = wrapRef.current
|
|
if (!el) return
|
|
const rect = el.getBoundingClientRect()
|
|
setCoords({
|
|
top: rect.top - 8,
|
|
left: rect.left + rect.width / 2,
|
|
})
|
|
}
|
|
|
|
function show() {
|
|
updatePosition()
|
|
setVisible(true)
|
|
}
|
|
|
|
function hide() {
|
|
setVisible(false)
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!visible) return
|
|
|
|
function onReposition() {
|
|
updatePosition()
|
|
}
|
|
|
|
window.addEventListener('scroll', onReposition, true)
|
|
window.addEventListener('resize', onReposition)
|
|
return () => {
|
|
window.removeEventListener('scroll', onReposition, true)
|
|
window.removeEventListener('resize', onReposition)
|
|
}
|
|
}, [visible])
|
|
|
|
return (
|
|
<span
|
|
ref={wrapRef}
|
|
className={styles.wrap}
|
|
onMouseEnter={show}
|
|
onMouseLeave={hide}
|
|
onFocusCapture={show}
|
|
onBlurCapture={(e) => {
|
|
if (!wrapRef.current?.contains(e.relatedTarget as Node | null)) {
|
|
hide()
|
|
}
|
|
}}
|
|
>
|
|
{children}
|
|
{visible
|
|
? createPortal(
|
|
<span
|
|
className={`${styles.tip} ${styles.tipVisible}`}
|
|
role="tooltip"
|
|
style={{ top: coords.top, left: coords.left }}
|
|
>
|
|
{label}
|
|
</span>,
|
|
document.body,
|
|
)
|
|
: null}
|
|
</span>
|
|
)
|
|
}
|