Compare commits
3 Commits
8ab2e057bb
...
fdb3045053
| Author | SHA1 | Date | |
|---|---|---|---|
| fdb3045053 | |||
| 80ac635bf9 | |||
| ef57837371 |
@ -361,6 +361,16 @@ code {
|
||||
padding: 8px !important;
|
||||
}
|
||||
|
||||
.cursor-tooltip {
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.cursor-tooltip.is-visible {
|
||||
opacity: 1;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.cursor-tooltip .ant-card {
|
||||
box-shadow:
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||
@ -1548,3 +1558,8 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
||||
.ant-splitter-bar-dragger::after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
.dashboard-splitter.farmcontrol-splitter
|
||||
.ant-splitter-panel.dashboard-splitter-content-panel {
|
||||
flex-grow: 1 !important;
|
||||
}
|
||||
|
||||
@ -15,7 +15,7 @@ const getTooltipRoot = () => {
|
||||
return root
|
||||
}
|
||||
|
||||
const CursorTooltip = ({ content, pointRef }) => {
|
||||
const CursorTooltip = ({ content, visible, pointRef }) => {
|
||||
const tooltipRef = useRef(null)
|
||||
const sizeRef = useRef({ width: 0, height: 0 })
|
||||
const frameRef = useRef(null)
|
||||
@ -94,23 +94,19 @@ const CursorTooltip = ({ content, pointRef }) => {
|
||||
}
|
||||
}, [applyPosition, root, schedulePosition])
|
||||
|
||||
const visible = content != null && content !== false && content !== ''
|
||||
|
||||
if (!root) return null
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
ref={tooltipRef}
|
||||
className='cursor-tooltip'
|
||||
className={`cursor-tooltip${visible ? ' is-visible' : ''}`}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 1100,
|
||||
pointerEvents: 'none',
|
||||
visibility: visible ? 'visible' : 'hidden',
|
||||
opacity: visible ? 1 : 0,
|
||||
willChange: 'transform'
|
||||
willChange: 'transform, opacity'
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
@ -131,6 +127,7 @@ const CursorTooltip = ({ content, pointRef }) => {
|
||||
|
||||
CursorTooltip.propTypes = {
|
||||
content: PropTypes.node,
|
||||
visible: PropTypes.bool,
|
||||
pointRef: PropTypes.shape({
|
||||
current: PropTypes.shape({
|
||||
x: PropTypes.number,
|
||||
|
||||
@ -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>
|
||||
)
|
||||
|
||||
@ -208,7 +208,9 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
||||
>
|
||||
{sidebarNode}
|
||||
</Splitter.Panel>
|
||||
<Splitter.Panel min={320}>{children}</Splitter.Panel>
|
||||
<Splitter.Panel min={320} className='dashboard-splitter-content-panel'>
|
||||
{children}
|
||||
</Splitter.Panel>
|
||||
</Splitter>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -11,11 +11,31 @@ import CursorTooltip from '../common/CursorTooltip'
|
||||
|
||||
const TooltipContext = createContext(null)
|
||||
|
||||
const HIDE_DELAY_MS = 10
|
||||
const FADE_OUT_MS = 250
|
||||
|
||||
export const TooltipProvider = ({ children }) => {
|
||||
const [content, setContent] = useState(null)
|
||||
const [visible, setVisible] = useState(false)
|
||||
const stackRef = useRef([])
|
||||
const hideTimerRef = useRef(null)
|
||||
const fadeTimerRef = useRef(null)
|
||||
const pointRef = useRef({ x: 0, y: 0 })
|
||||
const visibleRef = useRef(false)
|
||||
visibleRef.current = visible
|
||||
|
||||
const clearTimers = useCallback(() => {
|
||||
if (hideTimerRef.current != null) {
|
||||
window.clearTimeout(hideTimerRef.current)
|
||||
hideTimerRef.current = null
|
||||
}
|
||||
if (fadeTimerRef.current != null) {
|
||||
window.clearTimeout(fadeTimerRef.current)
|
||||
fadeTimerRef.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => () => clearTimers(), [clearTimers])
|
||||
|
||||
useEffect(() => {
|
||||
const onMove = (event) => {
|
||||
@ -25,7 +45,8 @@ export const TooltipProvider = ({ children }) => {
|
||||
return () => window.removeEventListener('mousemove', onMove)
|
||||
}, [])
|
||||
|
||||
const showTooltip = useCallback((id, nextContent, x, y) => {
|
||||
const showTooltip = useCallback(
|
||||
(id, nextContent, x, y) => {
|
||||
if (nextContent == null || nextContent === false || nextContent === '') {
|
||||
return
|
||||
}
|
||||
@ -34,36 +55,52 @@ export const TooltipProvider = ({ children }) => {
|
||||
pointRef.current = { x, y }
|
||||
}
|
||||
|
||||
if (hideTimerRef.current != null) {
|
||||
window.clearTimeout(hideTimerRef.current)
|
||||
hideTimerRef.current = null
|
||||
}
|
||||
clearTimers()
|
||||
|
||||
stackRef.current = [
|
||||
...stackRef.current.filter((entry) => entry.id !== id),
|
||||
{ id, content: nextContent }
|
||||
]
|
||||
setContent(nextContent)
|
||||
}, [])
|
||||
|
||||
const hideTooltip = useCallback((id) => {
|
||||
if (!visibleRef.current) {
|
||||
window.requestAnimationFrame(() => setVisible(true))
|
||||
} else {
|
||||
setVisible(true)
|
||||
}
|
||||
},
|
||||
[clearTimers]
|
||||
)
|
||||
|
||||
const hideTooltip = useCallback(
|
||||
(id) => {
|
||||
stackRef.current = stackRef.current.filter((entry) => entry.id !== id)
|
||||
const last = stackRef.current[stackRef.current.length - 1]
|
||||
|
||||
if (hideTimerRef.current != null) {
|
||||
window.clearTimeout(hideTimerRef.current)
|
||||
clearTimers()
|
||||
|
||||
if (last) {
|
||||
setContent(last.content)
|
||||
setVisible(true)
|
||||
return
|
||||
}
|
||||
|
||||
hideTimerRef.current = window.setTimeout(() => {
|
||||
hideTimerRef.current = null
|
||||
setContent(last?.content ?? null)
|
||||
}, 80)
|
||||
}, [])
|
||||
setVisible(false)
|
||||
fadeTimerRef.current = window.setTimeout(() => {
|
||||
fadeTimerRef.current = null
|
||||
setContent(null)
|
||||
}, FADE_OUT_MS)
|
||||
}, HIDE_DELAY_MS)
|
||||
},
|
||||
[clearTimers]
|
||||
)
|
||||
|
||||
return (
|
||||
<TooltipContext.Provider value={{ showTooltip, hideTooltip }}>
|
||||
{children}
|
||||
<CursorTooltip content={content} pointRef={pointRef} />
|
||||
<CursorTooltip content={content} visible={visible} pointRef={pointRef} />
|
||||
</TooltipContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user