Compare commits
No commits in common. "fdb304505353a013dbd0a85627d5fd38c9445127" and "8ab2e057bbe6bccc7164277fd137a0d5036b1a59" have entirely different histories.
fdb3045053
...
8ab2e057bb
@ -361,16 +361,6 @@ code {
|
|||||||
padding: 8px !important;
|
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 {
|
.cursor-tooltip .ant-card {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
0 6px 16px 0 rgba(0, 0, 0, 0.08),
|
||||||
@ -1558,8 +1548,3 @@ span.ant-skeleton-input.ant-skeleton-input-sm.text-skeleton {
|
|||||||
.ant-splitter-bar-dragger::after {
|
.ant-splitter-bar-dragger::after {
|
||||||
content: none;
|
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
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
const CursorTooltip = ({ content, visible, pointRef }) => {
|
const CursorTooltip = ({ content, pointRef }) => {
|
||||||
const tooltipRef = useRef(null)
|
const tooltipRef = useRef(null)
|
||||||
const sizeRef = useRef({ width: 0, height: 0 })
|
const sizeRef = useRef({ width: 0, height: 0 })
|
||||||
const frameRef = useRef(null)
|
const frameRef = useRef(null)
|
||||||
@ -94,19 +94,23 @@ const CursorTooltip = ({ content, visible, pointRef }) => {
|
|||||||
}
|
}
|
||||||
}, [applyPosition, root, schedulePosition])
|
}, [applyPosition, root, schedulePosition])
|
||||||
|
|
||||||
|
const visible = content != null && content !== false && content !== ''
|
||||||
|
|
||||||
if (!root) return null
|
if (!root) return null
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div
|
<div
|
||||||
ref={tooltipRef}
|
ref={tooltipRef}
|
||||||
className={`cursor-tooltip${visible ? ' is-visible' : ''}`}
|
className='cursor-tooltip'
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
zIndex: 1100,
|
zIndex: 1100,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
willChange: 'transform, opacity'
|
visibility: visible ? 'visible' : 'hidden',
|
||||||
|
opacity: visible ? 1 : 0,
|
||||||
|
willChange: 'transform'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
@ -127,7 +131,6 @@ const CursorTooltip = ({ content, visible, pointRef }) => {
|
|||||||
|
|
||||||
CursorTooltip.propTypes = {
|
CursorTooltip.propTypes = {
|
||||||
content: PropTypes.node,
|
content: PropTypes.node,
|
||||||
visible: PropTypes.bool,
|
|
||||||
pointRef: PropTypes.shape({
|
pointRef: PropTypes.shape({
|
||||||
current: PropTypes.shape({
|
current: PropTypes.shape({
|
||||||
x: PropTypes.number,
|
x: PropTypes.number,
|
||||||
|
|||||||
@ -1,10 +1,4 @@
|
|||||||
import {
|
import { cloneElement, isValidElement, useState, useEffect, useContext } from 'react'
|
||||||
cloneElement,
|
|
||||||
isValidElement,
|
|
||||||
useState,
|
|
||||||
useEffect,
|
|
||||||
useContext
|
|
||||||
} from 'react'
|
|
||||||
import { Layout, Menu, Flex, Button } from 'antd'
|
import { Layout, Menu, Flex, Button } from 'antd'
|
||||||
import { CaretDownFilled } from '@ant-design/icons'
|
import { CaretDownFilled } from '@ant-design/icons'
|
||||||
import CollapseSidebarIcon from '../../Icons/CollapseSidebarIcon'
|
import CollapseSidebarIcon from '../../Icons/CollapseSidebarIcon'
|
||||||
@ -29,7 +23,7 @@ const CollapsedItemIcon = ({ className, icon, title }) => {
|
|||||||
: icon
|
: icon
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip title={title} listenParents={1}>
|
<Tooltip title={title}>
|
||||||
{iconNode ?? <span className={className} />}
|
{iconNode ?? <span className={className} />}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -208,9 +208,7 @@ const DashboardSidebarSplitter = ({ sidebar, children }) => {
|
|||||||
>
|
>
|
||||||
{sidebarNode}
|
{sidebarNode}
|
||||||
</Splitter.Panel>
|
</Splitter.Panel>
|
||||||
<Splitter.Panel min={320} className='dashboard-splitter-content-panel'>
|
<Splitter.Panel min={320}>{children}</Splitter.Panel>
|
||||||
{children}
|
|
||||||
</Splitter.Panel>
|
|
||||||
</Splitter>
|
</Splitter>
|
||||||
</Layout>
|
</Layout>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -13,15 +13,11 @@ const mergeHandler = (original, next) => (event) => {
|
|||||||
original?.(event)
|
original?.(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tooltip = ({ children, title, content, listenParents = 0 }) => {
|
const Tooltip = ({ children, title, content }) => {
|
||||||
const { showTooltip, hideTooltip } = useTooltipContext()
|
const { showTooltip, hideTooltip } = useTooltipContext()
|
||||||
const id = useId()
|
const id = useId()
|
||||||
const tooltipContent = title ?? content
|
const tooltipContent = title ?? content
|
||||||
const spanRef = useRef(null)
|
|
||||||
const hoveredTargetsRef = useRef(new Set())
|
|
||||||
const hoveringRef = useRef(false)
|
const hoveringRef = useRef(false)
|
||||||
const tooltipContentRef = useRef(tooltipContent)
|
|
||||||
tooltipContentRef.current = tooltipContent
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => hideTooltip(id)
|
return () => hideTooltip(id)
|
||||||
@ -34,86 +30,43 @@ const Tooltip = ({ children, title, content, listenParents = 0 }) => {
|
|||||||
}, [id, showTooltip, tooltipContent])
|
}, [id, showTooltip, tooltipContent])
|
||||||
|
|
||||||
const onMouseEnter = (event) => {
|
const onMouseEnter = (event) => {
|
||||||
hoveredTargetsRef.current.add(event.currentTarget)
|
|
||||||
hoveringRef.current = true
|
hoveringRef.current = true
|
||||||
showTooltip(id, tooltipContentRef.current, event.clientX, event.clientY)
|
showTooltip(id, tooltipContent, event.clientX, event.clientY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const onMouseLeave = (event) => {
|
const onMouseLeave = () => {
|
||||||
hoveredTargetsRef.current.delete(event.currentTarget)
|
|
||||||
if (hoveredTargetsRef.current.size > 0) return
|
|
||||||
hoveringRef.current = false
|
hoveringRef.current = false
|
||||||
hideTooltip(id)
|
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)) {
|
if (isValidElement(children)) {
|
||||||
const trigger = cloneElement(children, {
|
const trigger = cloneElement(children, {
|
||||||
onMouseEnter: mergeHandler(children.props.onMouseEnter, onMouseEnter),
|
onMouseEnter: mergeHandler(children.props.onMouseEnter, onMouseEnter),
|
||||||
onMouseLeave: mergeHandler(children.props.onMouseLeave, onMouseLeave)
|
onMouseLeave: mergeHandler(children.props.onMouseLeave, onMouseLeave)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (listenParents > 0) {
|
|
||||||
return wrapWithSpan(children)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (children.props.disabled) {
|
if (children.props.disabled) {
|
||||||
return wrapWithSpan(trigger)
|
return (
|
||||||
|
<span onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
|
{trigger}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return trigger
|
return trigger
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapWithSpan(children)
|
return (
|
||||||
|
<span onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.propTypes = {
|
Tooltip.propTypes = {
|
||||||
children: PropTypes.node.isRequired,
|
children: PropTypes.node.isRequired,
|
||||||
title: PropTypes.node,
|
title: PropTypes.node,
|
||||||
content: PropTypes.node,
|
content: PropTypes.node
|
||||||
listenParents: PropTypes.number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Tooltip
|
export default Tooltip
|
||||||
|
|||||||
@ -11,31 +11,11 @@ import CursorTooltip from '../common/CursorTooltip'
|
|||||||
|
|
||||||
const TooltipContext = createContext(null)
|
const TooltipContext = createContext(null)
|
||||||
|
|
||||||
const HIDE_DELAY_MS = 10
|
|
||||||
const FADE_OUT_MS = 250
|
|
||||||
|
|
||||||
export const TooltipProvider = ({ children }) => {
|
export const TooltipProvider = ({ children }) => {
|
||||||
const [content, setContent] = useState(null)
|
const [content, setContent] = useState(null)
|
||||||
const [visible, setVisible] = useState(false)
|
|
||||||
const stackRef = useRef([])
|
const stackRef = useRef([])
|
||||||
const hideTimerRef = useRef(null)
|
const hideTimerRef = useRef(null)
|
||||||
const fadeTimerRef = useRef(null)
|
|
||||||
const pointRef = useRef({ x: 0, y: 0 })
|
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(() => {
|
useEffect(() => {
|
||||||
const onMove = (event) => {
|
const onMove = (event) => {
|
||||||
@ -45,8 +25,7 @@ export const TooltipProvider = ({ children }) => {
|
|||||||
return () => window.removeEventListener('mousemove', onMove)
|
return () => window.removeEventListener('mousemove', onMove)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const showTooltip = useCallback(
|
const showTooltip = useCallback((id, nextContent, x, y) => {
|
||||||
(id, nextContent, x, y) => {
|
|
||||||
if (nextContent == null || nextContent === false || nextContent === '') {
|
if (nextContent == null || nextContent === false || nextContent === '') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -55,52 +34,36 @@ export const TooltipProvider = ({ children }) => {
|
|||||||
pointRef.current = { x, y }
|
pointRef.current = { x, y }
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimers()
|
if (hideTimerRef.current != null) {
|
||||||
|
window.clearTimeout(hideTimerRef.current)
|
||||||
|
hideTimerRef.current = null
|
||||||
|
}
|
||||||
|
|
||||||
stackRef.current = [
|
stackRef.current = [
|
||||||
...stackRef.current.filter((entry) => entry.id !== id),
|
...stackRef.current.filter((entry) => entry.id !== id),
|
||||||
{ id, content: nextContent }
|
{ id, content: nextContent }
|
||||||
]
|
]
|
||||||
setContent(nextContent)
|
setContent(nextContent)
|
||||||
|
}, [])
|
||||||
|
|
||||||
if (!visibleRef.current) {
|
const hideTooltip = useCallback((id) => {
|
||||||
window.requestAnimationFrame(() => setVisible(true))
|
|
||||||
} else {
|
|
||||||
setVisible(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[clearTimers]
|
|
||||||
)
|
|
||||||
|
|
||||||
const hideTooltip = useCallback(
|
|
||||||
(id) => {
|
|
||||||
stackRef.current = stackRef.current.filter((entry) => entry.id !== id)
|
stackRef.current = stackRef.current.filter((entry) => entry.id !== id)
|
||||||
const last = stackRef.current[stackRef.current.length - 1]
|
const last = stackRef.current[stackRef.current.length - 1]
|
||||||
|
|
||||||
clearTimers()
|
if (hideTimerRef.current != null) {
|
||||||
|
window.clearTimeout(hideTimerRef.current)
|
||||||
if (last) {
|
|
||||||
setContent(last.content)
|
|
||||||
setVisible(true)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hideTimerRef.current = window.setTimeout(() => {
|
hideTimerRef.current = window.setTimeout(() => {
|
||||||
hideTimerRef.current = null
|
hideTimerRef.current = null
|
||||||
setVisible(false)
|
setContent(last?.content ?? null)
|
||||||
fadeTimerRef.current = window.setTimeout(() => {
|
}, 80)
|
||||||
fadeTimerRef.current = null
|
}, [])
|
||||||
setContent(null)
|
|
||||||
}, FADE_OUT_MS)
|
|
||||||
}, HIDE_DELAY_MS)
|
|
||||||
},
|
|
||||||
[clearTimers]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TooltipContext.Provider value={{ showTooltip, hideTooltip }}>
|
<TooltipContext.Provider value={{ showTooltip, hideTooltip }}>
|
||||||
{children}
|
{children}
|
||||||
<CursorTooltip content={content} visible={visible} pointRef={pointRef} />
|
<CursorTooltip content={content} pointRef={pointRef} />
|
||||||
</TooltipContext.Provider>
|
</TooltipContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user