// components/Popover.tsx import { motion, AnimatePresence } from "framer-motion"; import React, { ReactNode, useState, cloneElement, isValidElement, ReactElement, } from "react"; type PopoverPosition = "top" | "bottom" | "left" | "right"; type TriggerType = "hover" | "click" | "focus"; interface PopoverProps { title?: string; content: ReactNode; position?: PopoverPosition; trigger?: TriggerType; children: ReactNode; // 可选的延迟时间(毫秒),用于 hover 模式 hoverDelay?: number; } const positionStyles = { top: { initial: { opacity: 0, y: 10, scale: 0.95 }, animate: { opacity: 1, y: 0, scale: 1 }, className: "bottom-full left-1/2 -translate-x-1/2 mb-2", }, bottom: { initial: { opacity: 0, y: -10, scale: 0.95 }, animate: { opacity: 1, y: 0, scale: 1 }, className: "top-full left-1/2 -translate-x-1/2 mt-2", }, left: { initial: { opacity: 0, x: 10, scale: 0.95 }, animate: { opacity: 1, x: 0, scale: 1 }, className: "right-full top-1/2 -translate-y-1/2 mr-2", }, right: { initial: { opacity: 0, x: -10, scale: 0.95 }, animate: { opacity: 1, x: 0, scale: 1 }, className: "left-full top-1/2 -translate-y-1/2 ml-2", }, }; export const Popover: React.FC = ({ title, content, position = "right", trigger = "hover", children, hoverDelay = 200, }) => { const [isOpen, setIsOpen] = useState(false); const [hoverTimeout, setHoverTimeout] = useState(null); const handleMouseEnter = () => { if (trigger === "hover") { if (hoverTimeout) window.clearTimeout(hoverTimeout); setIsOpen(true); } }; const handleMouseLeave = () => { if (trigger === "hover") { // window.setTimeout 返回 number 类型 const timeout = window.setTimeout( () => setIsOpen(false), hoverDelay ); setHoverTimeout(timeout); } }; const handleClick = (e: React.MouseEvent) => { if (trigger === "click") { setIsOpen(!isOpen); } }; const handleFocus = () => { if (trigger === "focus") { setIsOpen(true); } }; const handleBlur = () => { if (trigger === "focus") { setIsOpen(false); } }; // 添加类型断言来处理 children 的类型 const childrenWithProps = isValidElement(children) ? cloneElement(children as ReactElement, { onClick: (e: React.MouseEvent) => { handleClick(e); const child = children as ReactElement<{ onClick?: (e: React.MouseEvent) => void; }>; if (child.props.onClick) { child.props.onClick(e); } }, onMouseEnter: (e: React.MouseEvent) => { handleMouseEnter(); const child = children as ReactElement<{ onMouseEnter?: (e: React.MouseEvent) => void; }>; if (child.props.onMouseEnter) { child.props.onMouseEnter(e); } }, onMouseLeave: (e: React.MouseEvent) => { handleMouseLeave(); const child = children as ReactElement<{ onMouseLeave?: (e: React.MouseEvent) => void; }>; if (child.props.onMouseLeave) { child.props.onMouseLeave(e); } }, onFocus: (e: React.FocusEvent) => { handleFocus(); const child = children as ReactElement<{ onFocus?: (e: React.FocusEvent) => void; }>; if (child.props.onFocus) { child.props.onFocus(e); } }, onBlur: (e: React.FocusEvent) => { handleBlur(); const child = children as ReactElement<{ onBlur?: (e: React.FocusEvent) => void; }>; if (child.props.onBlur) { child.props.onBlur(e); } }, }) : children; return (
{childrenWithProps} {isOpen && ( {title && (

{title}

)}
{content}
)}
); };