UNPKG

bee-tooltip

Version:
299 lines (279 loc) 9.41 kB
import classnames from 'classnames'; import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import OverlayTrigger from 'bee-overlay/build/OverlayTrigger'; const propTypes = { /** * @required */ id: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]), inverse: PropTypes.bool, visible: PropTypes.bool, onVisibleChange: PropTypes.func, /** * 相对目标元素显示上下左右的位置 */ placement: PropTypes.oneOf(['top', 'right', 'bottom', 'left', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom']), /** * 绝对定位上边距. */ positionTop: PropTypes.oneOfType([ PropTypes.number, PropTypes.string, ]), /** * 绝对定位左边距 */ positionLeft: PropTypes.oneOfType([ PropTypes.number, PropTypes.string, ]), /** * 与目标Top的距离 */ arrowOffsetTop: PropTypes.oneOfType([ PropTypes.number, PropTypes.string, ]), /** * 与目标Left的距离 */ arrowOffsetLeft: PropTypes.oneOfType([ PropTypes.number, PropTypes.string, ]), /** * 是否开启内容区域按可视区最大可用高度设置 */ overlayMaxHeight: PropTypes.bool }; const defaultProps = { placement: 'right', clsPrefix: 'u-tooltip', overlayMaxHeight: false }; function OverlayNode(props){ let { id, className, classNames, style, overlay, overlayStyle, otherProps } = props; // style 包含用户传入的自定义样式,以及 bee-overlay 计算返回的样式。 // overlayStyle 是用户传入的自定义样式。 if(overlayStyle && overlayStyle.width) { style.width = overlayStyle.width; } else { delete style.width; } let {top, left, width, height, maxWidth, maxHeight} = style let reg = /\d+/g // top、left 设置最小值0 // 如果是负数被校正为0,则相应调整宽高,避免弹层遮挡触发元素 if(top !== undefined && top < 0) { reg.test(height) && (style.height = Math.max(height + top, 0)) reg.test(maxHeight) && (style.maxHeight = Math.max(maxHeight + top, 0)) style.top = 0 } if(left !== undefined && left < 0) { reg.test(width) && (style.width = Math.max(width + left, 0)) reg.test(maxWidth) && (style.maxWidth = Math.max(maxWidth + left, 0)) style.left = 0 } return ( <div id={id} role="tooltip" className={classnames(className, classNames)} onMouseEnter={props.onMouseEnter} onMouseLeave={props.onMouseLeave} style={style} {...otherProps} > { overlay?( <div className='tooltip-arrow'/> ):'' } { overlay?( <div className='tooltip-inner'> {overlay} </div> ):'' } </div> ) } function cloneElement(element, props) { return React.cloneElement( element, typeof props === 'function' ? props(element.props || {}) : props ) } class Tooltip extends React.Component { constructor(props){ super(props); let initState = { isHoverShow: false, autoHeightStyle: {} } if ('visible' in props) { Object.assign(initState, { visible: props.visible }) } this.state = initState; this.childRef = React.createRef(null) } componentDidMount(){ let { visible, overlayMaxHeight } = this.props; if((visible || this.state.isHoverShow) && overlayMaxHeight){ this.autoAdjustPlacement() } } componentDidUpdate(prevProps) { let { visible, overlayMaxHeight } = this.props; if ('visible' in this.props && prevProps.visible !== visible) { if(visible && overlayMaxHeight){ this.autoAdjustPlacement() } this.setState({ visible: visible }) } } handleVisibleChange = (visible) => { const { onVisibleChange, overlayMaxHeight } = this.props onVisibleChange && onVisibleChange(visible) if (visible && overlayMaxHeight) { this.autoAdjustPlacement() } } /** * 自动校正max-height和placement * */ autoAdjustPlacement = () => { const {findDOMNode} = ReactDOM const autoHeight = {} const placement = this.props.placement || defaultProps.placement const ARROW_SPACE = 10, EXTRA_SPACE = 15 const bodyStyle = document.body.getBoundingClientRect() const childStyle = findDOMNode(this.childRef.current) && findDOMNode(this.childRef.current).getBoundingClientRect() if(childStyle) { const availableSpace = { top: childStyle.top - ARROW_SPACE, bottom: bodyStyle.height - childStyle.bottom - ARROW_SPACE } if (placement.indexOf('top') >= 0 || placement.indexOf('bottom') >= 0) { Object.assign(autoHeight, { maxHeight: Math.max(availableSpace.top, availableSpace.bottom) - EXTRA_SPACE + 'px', overflowY: 'scroll' }) } else if (placement === 'left' || placement === 'right') { Object.assign(autoHeight, { maxHeight: bodyStyle.height - EXTRA_SPACE + 'px', overflowY: 'scroll' }) } else if (placement.indexOf('Top') >= 0) { Object.assign(autoHeight, { maxHeight: bodyStyle.height - childStyle.top - EXTRA_SPACE + 'px', overflowY: 'scroll' }) } else if (placement.indexOf('Bottom') >= 0) { Object.assign(autoHeight, { maxHeight: childStyle.bottom - EXTRA_SPACE + 'px', overflowY: 'scroll' }) } this.setState({autoHeightStyle : autoHeight}); } // console.log(placement, bodyStyle, autoHeight); } /** * @desc 鼠标划入时候的事件 */ onMouseEnter = () => { let {trigger} = this.props; if(trigger === 'click') return; this.setState({ isHoverShow: true }) } /** * @desc 鼠标划出时候的事件 */ onMouseLeave = () => { let {trigger} = this.props; if(trigger === 'click') return; this.setState({ isHoverShow: false }) } handleOnHide = () => { let { onHide } = this.props; onHide && onHide(false) } render() { const { placement, id, arrowOffsetTop, arrowOffsetLeft, className, style, children, clsPrefix, overlay, inverse, trigger, onVisibleChange, onHide, rootClose, visible, defaultOverlayShown, positionTop, positionLeft, overlayMaxHeight, ...others } = this.props; const { autoHeightStyle, isHoverShow } = this.state; let overlayStyle = Object.assign({},autoHeightStyle,style); let classes = { [placement]: true, 'inverse': inverse }; let classNames = classnames(clsPrefix, classes); let overlayNode = <OverlayNode id={id} className={className} classNames={classNames} overlay={overlay} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} style={overlayStyle} overlayStyle={overlayStyle} // 用户自定义样式 arrowOffsetTop={arrowOffsetTop} arrowOffsetLeft={arrowOffsetLeft} otherProps={others} /> const visibleProp = 'visible' in this.props ? { visible: this.state.visible } : { isHoverShow: isHoverShow } return ( <OverlayTrigger {...others} {...visibleProp} onVisibleChange={this.handleVisibleChange} ref={ref => this.trigger = ref} shouldUpdatePosition placement={placement} overlay={overlayNode} onHide={this.handleOnHide} rootClose={rootClose} defaultOverlayShown={defaultOverlayShown} positionTop={positionTop} positionLeft={positionLeft} trigger={trigger} > { cloneElement(children, {ref: this.childRef}) } </OverlayTrigger> ) } } Tooltip.propTypes = propTypes; Tooltip.defaultProps = defaultProps; export default Tooltip;