/* eslint-disable */ // @ts-nocheck import React, { forwardRef, useContext, useImperativeHandle, useRef, useState, } from "react" import PropTypes from "prop-types" import classNames from "classnames" import PopTrigger, { ITrigger, Placement } from "../pop-trigger" import { ConfigContext } from "../config-provider" import getPlacements from "./placements" import Popup from "./Popup" import "./style" const prefix = "adui-tooltip" export interface ITooltipProps { [key: string]: any /** * 是否边缘对齐 */ alignEdge?: boolean /** * 自动调整位置 */ autoAdjustPlacement?: boolean /** * 子节点 */ children?: React.ReactNode /** * 气泡提示是否默认显示 */ defaultVisible?: null | boolean /** * forceRender before first visible */ forceRender?: boolean /** * 指定气泡提示的父级,默认为 document.body */ getPopupContainer?: null | ((node: HTMLElement) => HTMLElement) /** * mouseEnter 显示的延时,单位:s,只在 trigger="hover" 时有效 */ mouseEnterDelay?: number /** * mouseLeave 消失的延时,单位:s,只在 trigger="hover" 时有效 */ mouseLeaveDelay?: number /** * 显示发生变化时的 handler */ onVisibleChange?: (visible: boolean) => void /** * 弹出层位置 */ popup?: React.ReactNode /** * 弹出内容 */ popupClassName?: string /** * 气泡提示的附加类名 */ popupTransitionName?: string /** * 气泡提示的附加样式 */ placement?: Placement /** * 弹出框的动画类名,动画格式遵循 rc-animate */ popupStyle?: React.CSSProperties | null /** * 触发方式 */ trigger?: "hover" | "focus" | "click" /** * 手动控制气泡提示是否显示 */ visible?: null | boolean } /** * 工具提示用于针对某个元素的、轻量的操作说明。 * 与气泡提示 Popover 不同,工具提示 Tooltip 不承载复杂内容信息,而主要用于解释工具的操作含义,如“复制”、“编辑”、“删除”等。 * Tooltip 是基于 PopTrigger 进行封装的。 * Tooltip 负责定义箭头与内容的相关样式。 */ const Tooltip: React.ForwardRefExoticComponent< ITooltipProps & React.RefAttributes > = forwardRef( ( { alignEdge, autoAdjustPlacement, children, defaultVisible, getPopupContainer, onVisibleChange, placement: placementProp, popup, popupClassName, popupStyle, popupTransitionName, trigger, visible: visibleProp, ...otherProps }: ITooltipProps, ref ) => { const triggerRef = useRef() const [placement, setPlacement] = useState(placementProp || "bottom") const [visible, setVisible] = useState(visibleProp) const [motionName, setMotionName] = useState(popupTransitionName) useImperativeHandle(ref, () => ({ current: triggerRef.current, })) // 相当于生命周期 getDerivedStateFromProps if (visibleProp !== null && visible !== visibleProp) { setVisible(!!visibleProp) } const { getPopupContainer: getPopupContainerContext } = useContext(ConfigContext) /** * 根据 placement 设置相关样式 */ const getPopupStyle = () => { const transformOrigin = { x: "50%", y: "50%", } const extraStyle: React.CSSProperties = {} if (popupStyle) { if (placement.indexOf("top") === 0) { transformOrigin.y = "100%" if ( typeof popupStyle.paddingBottom === "undefined" && typeof popupStyle.padding === "undefined" ) { extraStyle.paddingBottom = "12px" } } else if (placement.indexOf("bottom") === 0) { transformOrigin.y = "-6px" if ( typeof popupStyle.paddingTop === "undefined" && typeof popupStyle.padding === "undefined" ) { extraStyle.paddingTop = "12px" } } else if (placement.indexOf("right") === 0) { transformOrigin.x = "0%" if ( typeof popupStyle.paddingLeft === "undefined" && typeof popupStyle.padding === "undefined" ) { extraStyle.paddingLeft = "12px" } } else if (placement.indexOf("left") === 0) { transformOrigin.x = "100%" if ( typeof popupStyle.paddingRight === "undefined" && typeof popupStyle.padding === "undefined" ) { extraStyle.paddingRight = "12px" } } if (placement.includes("Left")) { transformOrigin.x = "18px" } else if (placement.includes("Right")) { transformOrigin.x = "calc(100% - 18px)" } else if (placement.includes("Top")) { transformOrigin.y = "10px" } else if (placement.includes("Bottom")) { transformOrigin.y = "calc(100% - 10px)" } extraStyle.transformOrigin = `${transformOrigin.x} ${transformOrigin.y}` } return { ...extraStyle, ...popupStyle, } } /** * 此方法的目的是在 window 无法显示完整 popup 时,调整 placement; * 可能存在 x, y 轴都需要调整的情况, * 比如在屏幕右下角的 "bottomLeft" || "bottom",那么目标是将其调整为 "topRight"。 * * 20190828: 尝试将 “top*” 的 CSS 定位从 top: xxx; 改为 bottom: xxx; * 因为这时候如果弹出框高度变化的话,从下定位就不会有样式问题。 */ const handlePopupAlign = (node: HTMLElement) => { if (!autoAdjustPlacement) { return } const rect = node.getBoundingClientRect() const { width, height, top: Y, left: X } = rect let newPlacement: any = placement setTimeout(() => { if ( triggerRef.current && triggerRef.current.getRootDomNode && placementProp ) { const wrapperRect = triggerRef.current .getRootDomNode() .getBoundingClientRect() /** * top + bottom 的定位优化 * 对 Y 轴进行判断 */ if (placementProp.indexOf("top") === 0) { if (Y < 0 && placement.indexOf("top") === 0) { newPlacement = "bottom".concat(placement.slice(3)) } if ( Y > rect.height + wrapperRect.height + 12 && placement.indexOf("bottom") === 0 ) { newPlacement = "top".concat(placement.slice(6)) } } if (placementProp.indexOf("bottom") === 0) { if ( Y + height > window.innerHeight && placement.indexOf("bottom") === 0 ) { newPlacement = "top".concat(placement.slice(6)) } if ( wrapperRect.top + wrapperRect.height + height + 12 < window.innerHeight && placement.indexOf("top") === 0 ) { newPlacement = "bottom".concat(placement.slice(3)) } } /** * 对 X 轴进行判断 */ if (width + X > window.innerWidth) { if ( ["topLeft", "top", "bottomLeft", "bottom"].includes(newPlacement) ) { newPlacement = `${newPlacement.replace("Left", "")}Right` } } else if (X < 0) { if ( ["topRight", "top", "bottomRight", "bottom"].includes( newPlacement ) ) { newPlacement = `${newPlacement.replace("Right", "")}Left` } } // 判断是否是 "top*" 定位 if (node.className.includes("-top")) { const nodeTop = node.style.top || "0" node.style.top = "unset" node.style.bottom = `-${parseInt(nodeTop, 10) + height}px` } /** * left + right 的定位优化 */ if (placementProp.indexOf("left") === 0) { if (X < 0 && placement.indexOf("left") === 0) { newPlacement = "right".concat(placement.slice(4)) } if ( X > rect.width + wrapperRect.width + 12 && placement.indexOf("right") === 0 ) { newPlacement = "left".concat(placement.slice(5)) } } if (placementProp.indexOf("right") === 0) { if ( X + width > window.innerWidth && placement.indexOf("right") === 0 ) { newPlacement = "left".concat(placement.slice(5)) } if ( wrapperRect.left + wrapperRect.width + width + 12 < window.innerWidth && placement.indexOf("left") === 0 ) { newPlacement = "right".concat(placement.slice(4)) } } if (newPlacement !== placement) { setPlacement(newPlacement) if (triggerRef.current.forcePopupAlign) { triggerRef.current.forcePopupAlign() } } } }, 0) } const handleVisibleChange = (bool: boolean) => { if (visibleProp === null) { setVisible(bool) } if (onVisibleChange) { onVisibleChange(bool) } } return ( } popupPlacement={placement} popupMotion={{ motionName, motionDeadline: 1000, }} popupStyle={getPopupStyle()} popupVisible={visible} {...otherProps} > {children} ) } ) Tooltip.displayName = "Tooltip" Tooltip.propTypes = { /** * 是否边缘对齐 */ alignEdge: PropTypes.bool, /** * 自动调整位置 */ autoAdjustPlacement: PropTypes.bool, /** * 子节点 */ children: PropTypes.node, /** * 气泡提示是否默认显示 */ defaultVisible: PropTypes.oneOf([null, true, false]), /** * 指定气泡提示的父级,默认为 document.body */ getPopupContainer: PropTypes.func, /** * mouseEnter 显示的延时,单位:s,只在 trigger="hover" 时有效 */ mouseEnterDelay: PropTypes.number, /** * mouseLeave 消失的延时,单位:s,只在 trigger="hover" 时有效 */ mouseLeaveDelay: PropTypes.number, /** * 显示发生变化时的 handler */ onVisibleChange: PropTypes.func, /** * 弹出层位置 */ placement: PropTypes.oneOf([ "top", "left", "right", "bottom", "topLeft", "topRight", "bottomLeft", "bottomRight", "leftTop", "leftBottom", "rightTop", "rightBottom", ]), /** * 弹出内容 */ popup: PropTypes.node, /** * 气泡提示的附加类名 */ popupClassName: PropTypes.string, /** * 气泡提示的附加样式 */ popupStyle: PropTypes.object, /** * 弹出框的动画类名,动画格式遵循 rc-animate */ popupTransitionName: PropTypes.string, /** * 触发方式 */ trigger: PropTypes.oneOf(["hover", "focus", "click"]), /** * 手动控制气泡提示是否显示 */ visible: PropTypes.oneOf([null, true, false]), } Tooltip.defaultProps = { alignEdge: false, autoAdjustPlacement: true, children: "", defaultVisible: null, getPopupContainer: null, mouseEnterDelay: 0, mouseLeaveDelay: 0.1, onVisibleChange: () => {}, placement: "bottom", popup: "复制", popupClassName: "", popupStyle: {}, popupTransitionName: "zoom-big", trigger: "hover", visible: null, zIndex: "var(--z-index-tooltip)", } export default Tooltip