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.
This commit is contained in:
Aria Minaei 2021-08-19 21:04:55 +02:00
parent e63d273830
commit 69f787d5cf
2 changed files with 30 additions and 20 deletions

View file

@ -1,3 +1,4 @@
import type {IDerivation} from '@theatre/dataverse'
import {Box} from '@theatre/dataverse' import {Box} from '@theatre/dataverse'
import useRefAndState from '@theatre/studio/utils/useRefAndState' import useRefAndState from '@theatre/studio/utils/useRefAndState'
import React, { import React, {
@ -9,8 +10,9 @@ import React, {
} from 'react' } from 'react'
const ctx = createContext<{ const ctx = createContext<{
currentTooltipId: Box<number>
portalTarget: HTMLElement portalTarget: HTMLElement
cur: IDerivation<number>
set: (id: number, delay: number) => void
}>(null!) }>(null!)
let lastTooltipId = 0 let lastTooltipId = 0
@ -20,29 +22,20 @@ export const useTooltipOpenState = (): [
setIsOpen: (isOpen: boolean, delay: number) => void, setIsOpen: (isOpen: boolean, delay: number) => void,
] => { ] => {
const id = useMemo(() => lastTooltipId++, []) const id = useMemo(() => lastTooltipId++, [])
const {currentTooltipId} = useContext(ctx) const {cur, set} = useContext(ctx)
const [isOpenRef, isOpen] = useRefAndState<boolean>(false) const [isOpenRef, isOpen] = useRefAndState<boolean>(false)
const setIsOpen = useCallback((shouldOpen: boolean, delay: number) => { const setIsOpen = useCallback((shouldOpen: boolean, delay: number) => {
if (shouldOpen) { set(shouldOpen ? id : -1, delay)
if (currentTooltipId.get() !== id) {
currentTooltipId.set(id)
}
} else {
if (currentTooltipId.get() === id) {
currentTooltipId.set(-1)
}
}
}, []) }, [])
useEffect(() => { useEffect(() => {
const {derivation} = currentTooltipId return cur.changesWithoutValues().tap(() => {
return derivation.changesWithoutValues().tap(() => { const flag = cur.getValue() === id
const flag = derivation.getValue() === id
if (isOpenRef.current !== flag) isOpenRef.current = flag if (isOpenRef.current !== flag) isOpenRef.current = flag
}) })
}, [currentTooltipId, id]) }, [cur, id])
return [isOpen, setIsOpen] return [isOpen, setIsOpen]
} }
@ -52,11 +45,28 @@ const TooltipContext: React.FC<{portalTarget: HTMLElement}> = ({
portalTarget, portalTarget,
}) => { }) => {
const currentTooltipId = useMemo(() => new Box(-1), []) 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 ( return (
<ctx.Provider value={{currentTooltipId, portalTarget}}> <ctx.Provider value={{cur, set, portalTarget}}>{children}</ctx.Provider>
{children}
</ctx.Provider>
) )
} }

View file

@ -3,7 +3,7 @@ import {outlinePanelTheme} from '@theatre/studio/panels/OutlinePanel/BaseItem'
import {darken, opacify} from 'polished' import {darken, opacify} from 'polished'
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import React from 'react' 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 useTooltip from '@theatre/studio/uiComponents/Popover/useTooltip'
import mergeRefs from 'react-merge-refs' import mergeRefs from 'react-merge-refs'
import MinimalTooltip from '@theatre/studio/uiComponents/Popover/MinimalTooltip' import MinimalTooltip from '@theatre/studio/uiComponents/Popover/MinimalTooltip'
@ -58,7 +58,7 @@ const Container = styled.button`
` `
const ToolbarIconButton: typeof Container = React.forwardRef( const ToolbarIconButton: typeof Container = React.forwardRef(
({title, ...props}, ref: $IntentionalAny) => { ({title, ...props}: $FixMe, ref: $FixMe) => {
const [tooltip, localRef] = useTooltip( const [tooltip, localRef] = useTooltip(
{enabled: typeof title === 'string'}, {enabled: typeof title === 'string'},
() => <MinimalTooltip>{title}</MinimalTooltip>, () => <MinimalTooltip>{title}</MinimalTooltip>,