| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- import {
- cloneElement,
- useEffect,
- forwardRef,
- useState,
- useCallback,
- } from 'react'
- import {
- OverlayTrigger,
- OverlayTriggerProps,
- Tooltip as BSTooltip,
- TooltipProps as BSTooltipProps,
- } from 'react-bootstrap'
- import { callFnsInSequence } from '@/utils/functions'
- const DEFAULT_DELAY_SHOW = 300
- // Slightly lower value avoids flickering when an adjacent tooltip is shown before the previous one hides
- const DEFAULT_DELAY_HIDE = 290
- type OverlayProps = Omit<OverlayTriggerProps, 'overlay' | 'children'>
- const UpdatingTooltip = forwardRef<HTMLDivElement, BSTooltipProps>(
- ({ popper, children, show: _, ...props }, ref) => {
- useEffect(() => {
- popper?.scheduleUpdate?.()
- }, [children, popper])
- return (
- <BSTooltip ref={ref} {...props}>
- {children}
- </BSTooltip>
- )
- }
- )
- UpdatingTooltip.displayName = 'UpdatingTooltip'
- export type TooltipProps = {
- description: React.ReactNode
- id: string
- overlayProps?: OverlayProps
- tooltipProps?: BSTooltipProps
- hidden?: boolean
- children: React.ReactElement
- }
- function Tooltip({
- id,
- description,
- children,
- tooltipProps,
- overlayProps,
- hidden,
- }: TooltipProps) {
- const [show, setShow] = useState(false)
- const handleKeyDown = useCallback(
- (e: KeyboardEvent) => {
- if (show && e.key === 'Escape') {
- setShow(false)
- e.stopPropagation()
- }
- },
- [show, setShow]
- )
- useEffect(() => {
- if (hidden) {
- setShow(false)
- }
- }, [hidden])
- useEffect(() => {
- document.addEventListener('keydown', handleKeyDown, true)
- return () => document.removeEventListener('keydown', handleKeyDown, true)
- }, [handleKeyDown])
- const hideTooltip = (e: React.MouseEvent) => {
- if (e.currentTarget instanceof HTMLElement) {
- e.currentTarget.blur()
- }
- 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
- }
- return (
- <OverlayTrigger
- overlay={
- <UpdatingTooltip
- id={`${id}-tooltip`}
- {...tooltipProps}
- style={{ display: hidden ? 'none' : 'block' }}
- >
- {description}
- </UpdatingTooltip>
- }
- {...overlayProps}
- delay={{ show: delayShow, hide: delayHide }}
- placement={overlayProps?.placement || 'top'}
- show={hidden ? false : show}
- onToggle={hidden ? undefined : setShow}
- >
- {overlayProps?.trigger === 'click'
- ? children
- : cloneElement(children, {
- onClick: callFnsInSequence(children.props.onClick, hideTooltip),
- })}
- </OverlayTrigger>
- )
- }
- export default Tooltip
|