From 69f787d5cff304491b71d5ba93ea1bf979b10c15 Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Thu, 19 Aug 2021 21:04:55 +0200 Subject: [PATCH] UX tweak for tooltips * When pointer moves from one tooltip-ed node to another, their tooltips don't flash out and in. The new tooltip replaces the old one immediatgely. --- .../uiComponents/Popover/TooltipContext.tsx | 46 +++++++++++-------- .../toolbar/ToolbarIconButton.tsx | 4 +- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx b/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx index 01cab78..1e970b2 100644 --- a/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx +++ b/theatre/studio/src/uiComponents/Popover/TooltipContext.tsx @@ -1,3 +1,4 @@ +import type {IDerivation} from '@theatre/dataverse' import {Box} from '@theatre/dataverse' import useRefAndState from '@theatre/studio/utils/useRefAndState' import React, { @@ -9,8 +10,9 @@ import React, { } from 'react' const ctx = createContext<{ - currentTooltipId: Box portalTarget: HTMLElement + cur: IDerivation + set: (id: number, delay: number) => void }>(null!) let lastTooltipId = 0 @@ -20,29 +22,20 @@ export const useTooltipOpenState = (): [ setIsOpen: (isOpen: boolean, delay: number) => void, ] => { const id = useMemo(() => lastTooltipId++, []) - const {currentTooltipId} = useContext(ctx) + const {cur, set} = useContext(ctx) const [isOpenRef, isOpen] = useRefAndState(false) const setIsOpen = useCallback((shouldOpen: boolean, delay: number) => { - if (shouldOpen) { - if (currentTooltipId.get() !== id) { - currentTooltipId.set(id) - } - } else { - if (currentTooltipId.get() === id) { - currentTooltipId.set(-1) - } - } + set(shouldOpen ? id : -1, delay) }, []) useEffect(() => { - const {derivation} = currentTooltipId - return derivation.changesWithoutValues().tap(() => { - const flag = derivation.getValue() === id + return cur.changesWithoutValues().tap(() => { + const flag = cur.getValue() === id if (isOpenRef.current !== flag) isOpenRef.current = flag }) - }, [currentTooltipId, id]) + }, [cur, id]) return [isOpen, setIsOpen] } @@ -52,11 +45,28 @@ const TooltipContext: React.FC<{portalTarget: HTMLElement}> = ({ portalTarget, }) => { const currentTooltipId = useMemo(() => new Box(-1), []) + const cur = currentTooltipId.derivation + + const set = useMemo(() => { + let lastTimeout: NodeJS.Timeout | undefined = undefined + return (id: number, delay: number) => { + if (lastTimeout !== undefined) { + clearTimeout(lastTimeout) + lastTimeout = undefined + } + if (delay === 0) { + currentTooltipId.set(id) + } else { + lastTimeout = setTimeout(() => { + currentTooltipId.set(id) + lastTimeout = undefined + }, delay) + } + } + }, []) return ( - - {children} - + {children} ) } diff --git a/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx b/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx index 62bb422..b9ff8ae 100644 --- a/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx +++ b/theatre/studio/src/uiComponents/toolbar/ToolbarIconButton.tsx @@ -3,7 +3,7 @@ import {outlinePanelTheme} from '@theatre/studio/panels/OutlinePanel/BaseItem' import {darken, opacify} from 'polished' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import React from 'react' -import type {$IntentionalAny} from '@theatre/shared/utils/types' +import type {$FixMe, $IntentionalAny} from '@theatre/shared/utils/types' import useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip' import mergeRefs from 'react-merge-refs' import MinimalTooltip from '@theatre/studio/uiComponents/Popover/MinimalTooltip' @@ -58,7 +58,7 @@ const Container = styled.button` ` const ToolbarIconButton: typeof Container = React.forwardRef( - ({title, ...props}, ref: $IntentionalAny) => { + ({title, ...props}: $FixMe, ref: $FixMe) => { const [tooltip, localRef] = useTooltip( {enabled: typeof title === 'string'}, () => {title},