import { useEffect, useState } from 'react' import { AnimatePresence, motion } from 'framer-motion' import { cn, getBackgroundImage } from '@/lib/utils' export type HintPositionOffset = { /** 相对窗口中心点的水平偏移(px) */ x?: number /** 相对窗口中心点的垂直偏移(px) */ y?: number } export interface ShowHintOptions { /** 自动消失时间(ms)。默认 2000 */ durationMs?: number /** 是否常驻(常驻时不会自动消失) */ persistent?: boolean /** 相对中心点微调位置 */ offset?: HintPositionOffset /** 自定义层级(默认 9999) */ zIndex?: number } type HintState = { visible: boolean text: string options: Required> } const DEFAULT_OPTIONS: HintState['options'] = { durationMs: 2000, persistent: false, offset: { x: 0, y: 0 }, zIndex: 9999, } let current: HintState = { visible: false, text: '', options: DEFAULT_OPTIONS, } let hideTimer: number | null = null const listeners = new Set<(s: HintState) => void>() function emit(next: HintState) { current = next listeners.forEach((l) => l(current)) } function clearHideTimer() { if (hideTimer) { window.clearTimeout(hideTimer) hideTimer = null } } export function showHint(text: string, options?: ShowHintOptions) { if (typeof window === 'undefined') return clearHideTimer() const mergedOptions: HintState['options'] = { ...DEFAULT_OPTIONS, ...(options ?? {}), offset: { ...DEFAULT_OPTIONS.offset, ...(options?.offset ?? {}), }, } emit({ visible: true, text, options: mergedOptions, }) if (!mergedOptions.persistent) { hideTimer = window.setTimeout(() => { hideHint() }, mergedOptions.durationMs) } } export function hideHint() { if (typeof window === 'undefined') return clearHideTimer() emit({ ...current, visible: false }) } export function HintHost() { const [state, setState] = useState(current) useEffect(() => { listeners.add(setState) return () => { listeners.delete(setState) } }, []) const offsetX = state.options.offset.x ?? 0 const offsetY = state.options.offset.y ?? 0 return (
{state.visible && state.text && (
{state.text}
)}
) }