diff --git a/assets/stylesheets/App.css b/assets/stylesheets/App.css
index 23b7ad3d..7c94490a 100644
--- a/assets/stylesheets/App.css
+++ b/assets/stylesheets/App.css
@@ -361,6 +361,16 @@ code {
padding: 8px !important;
}
+.cursor-tooltip {
+ opacity: 0;
+ transition: opacity 0.5s 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),
diff --git a/src/components/Dashboard/common/CursorTooltip.jsx b/src/components/Dashboard/common/CursorTooltip.jsx
index 36348991..70fb736c 100644
--- a/src/components/Dashboard/common/CursorTooltip.jsx
+++ b/src/components/Dashboard/common/CursorTooltip.jsx
@@ -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(
{
CursorTooltip.propTypes = {
content: PropTypes.node,
+ visible: PropTypes.bool,
pointRef: PropTypes.shape({
current: PropTypes.shape({
x: PropTypes.number,
diff --git a/src/components/Dashboard/context/TooltipContext.jsx b/src/components/Dashboard/context/TooltipContext.jsx
index 1a760ee1..b5438881 100644
--- a/src/components/Dashboard/context/TooltipContext.jsx
+++ b/src/components/Dashboard/context/TooltipContext.jsx
@@ -11,11 +11,31 @@ import CursorTooltip from '../common/CursorTooltip'
const TooltipContext = createContext(null)
+const HIDE_DELAY_MS = 100
+const FADE_OUT_MS = 500
+
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,45 +45,62 @@ export const TooltipProvider = ({ children }) => {
return () => window.removeEventListener('mousemove', onMove)
}, [])
- const showTooltip = useCallback((id, nextContent, x, y) => {
- if (nextContent == null || nextContent === false || nextContent === '') {
- return
- }
+ const showTooltip = useCallback(
+ (id, nextContent, x, y) => {
+ if (nextContent == null || nextContent === false || nextContent === '') {
+ return
+ }
- if (typeof x === 'number' && typeof y === 'number') {
- pointRef.current = { x, y }
- }
+ if (typeof x === 'number' && typeof y === 'number') {
+ 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)
- }, [])
+ stackRef.current = [
+ ...stackRef.current.filter((entry) => entry.id !== id),
+ { id, content: nextContent }
+ ]
+ setContent(nextContent)
- const hideTooltip = useCallback((id) => {
- stackRef.current = stackRef.current.filter((entry) => entry.id !== id)
- const last = stackRef.current[stackRef.current.length - 1]
+ if (!visibleRef.current) {
+ window.requestAnimationFrame(() => setVisible(true))
+ } else {
+ setVisible(true)
+ }
+ },
+ [clearTimers]
+ )
- if (hideTimerRef.current != null) {
- window.clearTimeout(hideTimerRef.current)
- }
+ const hideTooltip = useCallback(
+ (id) => {
+ stackRef.current = stackRef.current.filter((entry) => entry.id !== id)
+ const last = stackRef.current[stackRef.current.length - 1]
- hideTimerRef.current = window.setTimeout(() => {
- hideTimerRef.current = null
- setContent(last?.content ?? null)
- }, 80)
- }, [])
+ clearTimers()
+
+ if (last) {
+ setContent(last.content)
+ setVisible(true)
+ return
+ }
+
+ hideTimerRef.current = window.setTimeout(() => {
+ hideTimerRef.current = null
+ setVisible(false)
+ fadeTimerRef.current = window.setTimeout(() => {
+ fadeTimerRef.current = null
+ setContent(null)
+ }, FADE_OUT_MS)
+ }, HIDE_DELAY_MS)
+ },
+ [clearTimers]
+ )
return (
{children}
-
+
)
}