'use client' import { forwardRef, useCallback, useEffect, useMemo, useRef, useState, } from 'react' import { isEmpty } from '~/src/utils/type' import { AlphaTooltipPrimitive, AlphaTooltipPrimitiveContent, type AlphaTooltipPrimitiveContentProps, AlphaTooltipPrimitivePortal, AlphaTooltipPrimitiveProvider, AlphaTooltipPrimitiveTrigger, } from '~/src/components/AlphaTooltipPrimitive' import { Icon } from '~/src/components/Icon' import { HStack } from '~/src/components/Stack' import { Text } from '~/src/components/Text' import { InvertedThemeProvider } from '~/src/components/ThemeProvider' import { useRootElement } from '~/src/components/WindowProvider' import { type TooltipPosition, type TooltipProps } from './Tooltip.types' import styles from './Tooltip.module.scss' function getSideAndAlign( placement: TooltipPosition ): Pick { switch (placement) { case 'top-center': return { side: 'top', align: 'center', } case 'top-left': return { side: 'top', align: 'start', } case 'top-right': return { side: 'top', align: 'end', } case 'right-center': return { side: 'right', align: 'center', } case 'right-top': return { side: 'right', align: 'start', } case 'right-bottom': return { side: 'right', align: 'end', } case 'bottom-center': return { side: 'bottom', align: 'center', } case 'bottom-left': return { side: 'bottom', align: 'start', } case 'bottom-right': return { side: 'bottom', align: 'end', } case 'left-center': return { side: 'left', align: 'center', } case 'left-top': return { side: 'left', align: 'start', } case 'left-bottom': return { side: 'left', align: 'end', } default: // NOTE: should not reach here return { side: undefined, align: undefined, } } } /** * `Tooltip` is a component that shows additional information when the mouse hovers or the keyboard is focused. * * Components that pass to children **must spread props and forward ref.** * @example * ```tsx * // Your component must spread props and forward ref. * const Button = React.forwardRef((props, forwardedRef) => ( * * ``` */ export const Tooltip = forwardRef( function Tooltip( { children, defaultShow, onShow: onShowProp, onHide: onHideProp, disabled, title, content, description, icon, placement = 'bottom-center', offset = 4, container: containerProp, keepInContainer = true, allowHover = false, delayShow = 0, delayHide = 0, ...rest }, forwardedRef ) { const [show, setShow] = useState(defaultShow ?? false) const timeoutRef = useRef(undefined) const rootElement = useRootElement() const container = containerProp ?? rootElement const shouldBeHidden = useMemo( () => disabled || isEmpty(content), [disabled, content] ) const onShow = useCallback(() => { setShow(true) onShowProp?.() }, [onShowProp]) const onHide = useCallback(() => { setShow(false) onHideProp?.() }, [onHideProp]) useEffect( function forceHide() { if (shouldBeHidden) { onHide() } }, [shouldBeHidden, onHide] ) useEffect( function cleanUpTimeout() { return function cleanUp() { if (timeoutRef.current) { clearTimeout(timeoutRef.current) } } }, [shouldBeHidden] ) const onOpenChange = useCallback( (open: boolean) => { if (shouldBeHidden) { return } if (open) { onShow() return } if (timeoutRef.current) { clearTimeout(timeoutRef.current) timeoutRef.current = undefined } if (delayHide > 0) { timeoutRef.current = setTimeout(() => { onHide() }, delayHide) return } onHide() }, [shouldBeHidden, delayHide, onShow, onHide] ) return ( {children}
{title && ( {title} )} {content} {description && ( {description} )}
{icon && ( )}
) } )