mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-14 22:23:42 +01:00
Show tooltip immediately if a tooltip is already open (#28870)
* Memoize delayProps * Refactor Escape key handler * Use useTooltipContext * Remove delay: 0 from tooltips * Only use isTooltipOpen if available * Only show transition for initial tooltip GitOrigin-RevId: 74950ea7e705acb8f42dea552b23ce93c66058c7
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
||||
useEffect,
|
||||
forwardRef,
|
||||
useState,
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import {
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
TooltipProps as BSTooltipProps,
|
||||
} from 'react-bootstrap'
|
||||
import { callFnsInSequence } from '@/utils/functions'
|
||||
import { useTooltipContext } from '@/shared/context/tooltip-provider'
|
||||
|
||||
const DEFAULT_DELAY_SHOW = 300
|
||||
// Slightly lower value avoids flickering when an adjacent tooltip is shown before the previous one hides
|
||||
@@ -34,6 +36,26 @@ const UpdatingTooltip = forwardRef<HTMLDivElement, BSTooltipProps>(
|
||||
)
|
||||
UpdatingTooltip.displayName = 'UpdatingTooltip'
|
||||
|
||||
const chooseDelayOptions = (
|
||||
delay?: number | { show: number; hide: number }
|
||||
): { show: number; hide: number } => {
|
||||
if (typeof delay === 'object') {
|
||||
return delay
|
||||
}
|
||||
|
||||
if (typeof delay === 'number') {
|
||||
return {
|
||||
show: delay,
|
||||
hide: Math.max(delay - 10, 0),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
show: DEFAULT_DELAY_SHOW,
|
||||
hide: DEFAULT_DELAY_HIDE,
|
||||
}
|
||||
}
|
||||
|
||||
export type TooltipProps = {
|
||||
description: React.ReactNode
|
||||
id: string
|
||||
@@ -53,20 +75,22 @@ function Tooltip({
|
||||
}: TooltipProps) {
|
||||
const [show, setShow] = useState(false)
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
const tooltipContext = useTooltipContext()
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (e: KeyboardEvent) => {
|
||||
if (show && e.key === 'Escape') {
|
||||
setShow(false)
|
||||
e.stopPropagation()
|
||||
}
|
||||
},
|
||||
[show, setShow]
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown, true)
|
||||
return () => document.removeEventListener('keydown', handleKeyDown, true)
|
||||
}, [handleKeyDown])
|
||||
document.addEventListener('keydown', listener, true)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', listener, true)
|
||||
}
|
||||
}, [show])
|
||||
|
||||
const hideTooltip = (e: React.MouseEvent) => {
|
||||
if (e.currentTarget instanceof HTMLElement) {
|
||||
@@ -75,13 +99,22 @@ function Tooltip({
|
||||
setShow(false)
|
||||
}
|
||||
|
||||
const delay = overlayProps?.delay
|
||||
let delayShow = DEFAULT_DELAY_SHOW
|
||||
let delayHide = DEFAULT_DELAY_HIDE
|
||||
if (delay !== undefined) {
|
||||
delayShow = typeof delay === 'number' ? delay : delay.show
|
||||
delayHide = typeof delay === 'number' ? Math.max(delay - 10, 0) : delay.hide
|
||||
}
|
||||
const delayProps = useMemo(() => {
|
||||
const delayOptions = chooseDelayOptions(overlayProps?.delay)
|
||||
if (tooltipContext?.isTooltipOpen) {
|
||||
delayOptions.show = 0
|
||||
delayOptions.hide = 0
|
||||
}
|
||||
return delayOptions
|
||||
}, [overlayProps?.delay, tooltipContext])
|
||||
|
||||
const handleToggle = useCallback(
|
||||
(value: boolean) => {
|
||||
tooltipContext?.setIsTooltipOpen(value)
|
||||
setShow(value)
|
||||
},
|
||||
[tooltipContext]
|
||||
)
|
||||
|
||||
return (
|
||||
<OverlayTrigger
|
||||
@@ -95,10 +128,11 @@ function Tooltip({
|
||||
</UpdatingTooltip>
|
||||
}
|
||||
{...overlayProps}
|
||||
delay={{ show: delayShow, hide: delayHide }}
|
||||
delay={delayProps}
|
||||
placement={overlayProps?.placement || 'top'}
|
||||
show={show}
|
||||
onToggle={setShow}
|
||||
onToggle={handleToggle}
|
||||
transition={!tooltipContext?.isTooltipOpen}
|
||||
>
|
||||
{overlayProps?.trigger === 'click'
|
||||
? children
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
FC,
|
||||
PropsWithChildren,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import useDebounce from '@/shared/hooks/use-debounce'
|
||||
|
||||
const TooltipContext = createContext<
|
||||
| {
|
||||
isTooltipOpen: boolean
|
||||
setIsTooltipOpen: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
| undefined
|
||||
>(undefined)
|
||||
|
||||
export const TooltipProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const [isTooltipOpen, setIsTooltipOpen] = useState(false)
|
||||
|
||||
const debouncedIsTooltipOpen = useDebounce(isTooltipOpen, 100)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
isTooltipOpen: debouncedIsTooltipOpen,
|
||||
setIsTooltipOpen,
|
||||
}),
|
||||
[debouncedIsTooltipOpen, setIsTooltipOpen]
|
||||
)
|
||||
|
||||
return (
|
||||
<TooltipContext.Provider value={value}>{children}</TooltipContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useTooltipContext = () => {
|
||||
return useContext(TooltipContext)
|
||||
}
|
||||
Reference in New Issue
Block a user