Enhance Tooltip Component with Parent Listening Feature
- Updated the Tooltip component to include a new prop, `listenParents`, allowing it to listen for mouse events on parent elements, improving tooltip visibility and interaction. - Refactored mouse event handling to manage hover states more effectively, ensuring tooltips behave consistently when interacting with parent elements. - Improved code organization and readability by consolidating event handling logic and utilizing refs for better performance.
This commit is contained in:
parent
8ab2e057bb
commit
ef57837371
@ -1,4 +1,10 @@
|
||||
import { cloneElement, isValidElement, useState, useEffect, useContext } from 'react'
|
||||
import {
|
||||
cloneElement,
|
||||
isValidElement,
|
||||
useState,
|
||||
useEffect,
|
||||
useContext
|
||||
} from 'react'
|
||||
import { Layout, Menu, Flex, Button } from 'antd'
|
||||
import { CaretDownFilled } from '@ant-design/icons'
|
||||
import CollapseSidebarIcon from '../../Icons/CollapseSidebarIcon'
|
||||
@ -23,7 +29,7 @@ const CollapsedItemIcon = ({ className, icon, title }) => {
|
||||
: icon
|
||||
|
||||
return (
|
||||
<Tooltip title={title}>
|
||||
<Tooltip title={title} listenParents={1}>
|
||||
{iconNode ?? <span className={className} />}
|
||||
</Tooltip>
|
||||
)
|
||||
|
||||
@ -13,11 +13,15 @@ const mergeHandler = (original, next) => (event) => {
|
||||
original?.(event)
|
||||
}
|
||||
|
||||
const Tooltip = ({ children, title, content }) => {
|
||||
const Tooltip = ({ children, title, content, listenParents = 0 }) => {
|
||||
const { showTooltip, hideTooltip } = useTooltipContext()
|
||||
const id = useId()
|
||||
const tooltipContent = title ?? content
|
||||
const spanRef = useRef(null)
|
||||
const hoveredTargetsRef = useRef(new Set())
|
||||
const hoveringRef = useRef(false)
|
||||
const tooltipContentRef = useRef(tooltipContent)
|
||||
tooltipContentRef.current = tooltipContent
|
||||
|
||||
useEffect(() => {
|
||||
return () => hideTooltip(id)
|
||||
@ -30,43 +34,86 @@ const Tooltip = ({ children, title, content }) => {
|
||||
}, [id, showTooltip, tooltipContent])
|
||||
|
||||
const onMouseEnter = (event) => {
|
||||
hoveredTargetsRef.current.add(event.currentTarget)
|
||||
hoveringRef.current = true
|
||||
showTooltip(id, tooltipContent, event.clientX, event.clientY)
|
||||
showTooltip(id, tooltipContentRef.current, event.clientX, event.clientY)
|
||||
}
|
||||
|
||||
const onMouseLeave = () => {
|
||||
const onMouseLeave = (event) => {
|
||||
hoveredTargetsRef.current.delete(event.currentTarget)
|
||||
if (hoveredTargetsRef.current.size > 0) return
|
||||
hoveringRef.current = false
|
||||
hideTooltip(id)
|
||||
}
|
||||
|
||||
const onMouseEnterRef = useRef(onMouseEnter)
|
||||
const onMouseLeaveRef = useRef(onMouseLeave)
|
||||
onMouseEnterRef.current = onMouseEnter
|
||||
onMouseLeaveRef.current = onMouseLeave
|
||||
|
||||
useEffect(() => {
|
||||
const span = spanRef.current
|
||||
if (!span || listenParents < 1) return
|
||||
|
||||
const handleEnter = (event) => onMouseEnterRef.current(event)
|
||||
const handleLeave = (event) => onMouseLeaveRef.current(event)
|
||||
|
||||
const parents = []
|
||||
let current = span.parentElement
|
||||
for (let i = 0; i < listenParents && current; i += 1) {
|
||||
parents.push(current)
|
||||
current = current.parentElement
|
||||
}
|
||||
|
||||
parents.forEach((parent) => {
|
||||
parent.addEventListener('mouseenter', handleEnter)
|
||||
parent.addEventListener('mouseleave', handleLeave)
|
||||
})
|
||||
|
||||
return () => {
|
||||
parents.forEach((parent) => {
|
||||
parent.removeEventListener('mouseenter', handleEnter)
|
||||
parent.removeEventListener('mouseleave', handleLeave)
|
||||
})
|
||||
hoveredTargetsRef.current.clear()
|
||||
}
|
||||
}, [listenParents])
|
||||
|
||||
const wrapWithSpan = (node) => (
|
||||
<span
|
||||
ref={spanRef}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{node}
|
||||
</span>
|
||||
)
|
||||
|
||||
if (isValidElement(children)) {
|
||||
const trigger = cloneElement(children, {
|
||||
onMouseEnter: mergeHandler(children.props.onMouseEnter, onMouseEnter),
|
||||
onMouseLeave: mergeHandler(children.props.onMouseLeave, onMouseLeave)
|
||||
})
|
||||
|
||||
if (listenParents > 0) {
|
||||
return wrapWithSpan(children)
|
||||
}
|
||||
|
||||
if (children.props.disabled) {
|
||||
return (
|
||||
<span onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
{trigger}
|
||||
</span>
|
||||
)
|
||||
return wrapWithSpan(trigger)
|
||||
}
|
||||
|
||||
return trigger
|
||||
}
|
||||
|
||||
return (
|
||||
<span onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
return wrapWithSpan(children)
|
||||
}
|
||||
|
||||
Tooltip.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
title: PropTypes.node,
|
||||
content: PropTypes.node
|
||||
content: PropTypes.node,
|
||||
listenParents: PropTypes.number
|
||||
}
|
||||
|
||||
export default Tooltip
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user