UNPKG

@zohodesk/components

Version:

In this Package, we Provide Some Basic Components to Build Web App

530 lines (472 loc) • 22.3 kB
import React from 'react'; import { defaultProps } from "./props/defaultProps"; import { propTypes } from "./props/propTypes"; import { getLibraryConfig } from "../../Provider/Config"; import ResizeObserver from "../../Responsive/ResizeObserver"; import selectn from 'selectn'; import { whiteSpaceClassMapping } from "../../utils/cssUtils"; import style from "../../Tooltip/Tooltip.module.css"; export default class Tooltip extends React.Component { constructor(props) { super(props); this.state = { title: null, left: 0, top: 0, arrowLeft: 0, isArrowRight: null, isHtml: false }; this.handleOver = this.handleOver.bind(this); this.getToolTipDOM = this.getToolTipDOM.bind(this); this.reset = this.reset.bind(this); this.getDirection = document.getElementsByTagName('html')[0]; this.leftRightScreenEdge = this.leftRightScreenEdge.bind(this); this.topBottomScreenEdge = this.topBottomScreenEdge.bind(this); this.observer = new ResizeObserver(this.onResize); this.isResized = true; this.tooltipContainerClientRect = {}; this.tooltipContainerEl = {}; } onResize(sizeOfObservedEl, observedEl) { this.isResized = true; } observeElement() { this.tooltipContainerEl = this.getToolTipContainerEl(); this.observer.observe(this.tooltipContainerEl); } unObserveElement() { this.observer.disconnect(); } getClientRectOfContEl(el) { if (this.isResized) { return this.setClientRectOfContEl(el); } return this.tooltipContainerClientRect; } setClientRectOfContEl(containerEl) { this.isResized = false; this.tooltipContainerClientRect = containerEl.getBoundingClientRect(); return this.tooltipContainerClientRect; } getToolTipContainerEl() { const getTooltipContainer = getLibraryConfig('getTooltipContainer'); const tooltipContainer = typeof getTooltipContainer === 'function' ? getTooltipContainer() : null; return tooltipContainer ? tooltipContainer : document.body; } getToolTipDOM(el) { this.toolTip = el; } reset() { let { title } = this.state; if (title !== null) { this.setState({ title: null, top: 0, left: 0 }); } } /* left and right screen edge check for top and bottom tooltip position */ leftRightScreenEdge(tLeft, toolTipArrowLeft, thisLeft, thisWidth, tooltipoffsetWidth, bodyWidth, rightEdge, bodyLeft) { if (tLeft - bodyLeft <= 1) { // top & bottom position left side screen edge case tLeft = bodyLeft + 2; toolTipArrowLeft = parseInt(thisLeft - bodyLeft + thisWidth / 2 - 6); } else if (tLeft - bodyLeft + tooltipoffsetWidth > bodyWidth) { // top & bottom position right side screen edge case rightEdge = tLeft + tooltipoffsetWidth - bodyWidth + 2; tLeft = tLeft + bodyLeft - rightEdge; toolTipArrowLeft = toolTipArrowLeft - bodyLeft + rightEdge; } else if (tLeft + tooltipoffsetWidth + tooltipoffsetWidth >= bodyWidth - 2) { // top & bottom center screen tooltip collide with right edge screen tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2); toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 4); } return { tLeft, toolTipArrowLeft, thisLeft, thisWidth, tooltipoffsetWidth, bodyWidth, rightEdge }; } /* top and bottom screen edge check for left and right tooltip position */ topBottomScreenEdge(tTop, toolTipArrowTop, thisTop, thisHeight, tooltipoffsetHeight, bodyHeight, bottomEdge) { if (tTop <= 1) { tTop = 2; toolTipArrowTop = parseInt(thisTop + thisHeight / 2); } else if (tTop + tooltipoffsetHeight > bodyHeight) { bottomEdge = tTop + tooltipoffsetHeight - bodyHeight + 2; tTop -= bottomEdge; toolTipArrowTop += bottomEdge; } return { tTop, toolTipArrowTop, thisTop, thisHeight, tooltipoffsetHeight, bodyHeight, bottomEdge }; } handleOver(e, targetElement) { let containerElement = this.tooltipContainerEl; let element = e.target; let titleDiv = element.hasAttribute('data-title') || element.hasAttribute('title') ? element : element.closest('[data-title]') || element.closest('[title]'); if (titleDiv) { let title = titleDiv.getAttribute('data-title') || titleDiv.getAttribute('title'); /* if data-title-position is left or right change right and left in rtl case */ let isPosition = titleDiv.getAttribute('data-title-position'); let elem = this.getDirection; if (elem.getAttribute('dir') == 'rtl') { if (isPosition == 'left') { isPosition = 'right'; } else if (isPosition == 'right') { isPosition = 'left'; } } if (title !== '' && title) { titleDiv.setAttribute('data-title', title); titleDiv.removeAttribute('title'); let isInputElementType = element.type === 'text'; let elementText = isInputElementType ? element.value : element.innerText; if (element.nodeName !== 'I' && elementText && elementText.trim() !== '') { let isContentDotted = ''; if (element.scrollWidth !== 0) { isContentDotted = element.offsetWidth < element.scrollWidth; if (!isContentDotted) { isContentDotted = element.offsetHeight < element.scrollHeight; } // if (!isContentDotted) {Need to check the code Sivanesh // isContentDotted = element.offsetHeight < element.scrollHeight; // } } else { const offWidth = selectn('parentElement.offsetWidth', element) || 0; const scrollWidth = selectn('parentElement.scrollWidth', element) || 0; isContentDotted = offWidth < scrollWidth; } let originText = elementText.replace(/\s/g, '').toLowerCase(); let tooltipText = title.replace(/\s/g, '').toLowerCase(); let isDefaultTooltip = element.hasAttribute('data-istitle') || element.hasAttribute('istitle') ? element.getAttribute('data-istitle') || element.getAttribute('istitle') : 'false'; isDefaultTooltip = isDefaultTooltip === 'true'; let isSameText = originText.indexOf(tooltipText) !== -1 ? true : false; if (!isContentDotted && isSameText && !isDefaultTooltip) { return false; } if (isContentDotted && titleDiv.getAttribute('data-dottedTitle')) { title = titleDiv.getAttribute('data-dottedTitle'); } } let isHtml = titleDiv.getAttribute('data-ishtml'); let dataTooltipnoArrow = titleDiv.getAttribute('data-tooltip-noarrow') === 'true' ? true : false; let dataTooltipWrap = titleDiv.getAttribute('data-title-wrap') || 'normal'; // values: normal, pre, pre-wrap, pre-line, nowrap, break-spaces let clientRect = titleDiv.getBoundingClientRect(); let boxLayout = this.getClientRectOfContEl(containerElement); this.setState({ title, isHtml, dataTooltipnoArrow, dataTooltipWrap }, () => { let tooltip = this.toolTip; if (tooltip) { /* element top, left, height, width */ let thisTop = clientRect.top; let thisLeft = clientRect.left; let thisHeight = clientRect.height; let thisWidth = clientRect.width; /* box layout left spacing */ let bodyLeft = boxLayout.left; // let checkTop = thisTop + thisHeight; /* element left plus element width */ let checkLeft = thisLeft + thisWidth; let tTop; let tLeft; let toolTipArrowTop; let toolTipArrowLeft; let rightEdge; let bottomEdge; let tooltipLeft; /* offset width, height of body */ let bodyWidth = containerElement.offsetWidth; let bodyHeight = containerElement.offsetHeight; let isArrowHorizontal = false; let isArrowDown = false; let isArrowRight = false; let tWidth = ''; /* overall body height minus element top + element height */ let thisBottom = bodyHeight - (thisTop + thisHeight); /* overall body width minus element left + element width */ let thisRight = bodyWidth - (thisLeft + thisWidth); /* tooltip width and height */ let tooltipoffsetWidth = tooltip.offsetWidth; let tooltipoffsetHeight = tooltip.offsetHeight; if (isPosition) { if (isPosition == 'top') { /* if top does not have enough space show tooltip in bottom area */ if (thisTop <= tooltipoffsetHeight + 5) { /* if top space is larger than bottom space show tooltip in top area */ if (thisTop > thisBottom) { tTop = parseInt(thisTop - (tooltipoffsetHeight + 10)); tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2 + 2); toolTipArrowTop = parseInt(tooltipoffsetHeight - 4); toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 6); isArrowDown = true; } else { /* bottom position */ tTop = parseInt(thisTop + (thisHeight + 10)); tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2 + 2); toolTipArrowTop = -4; toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 6); isArrowDown = false; } } else { /* if top have enough space show tooltip in top area */ tTop = parseInt(thisTop - (tooltipoffsetHeight + 10)); tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2 + 2); toolTipArrowTop = parseInt(tooltipoffsetHeight - 4); toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 6); isArrowDown = true; } /* top tooltip left and right corner edge case */ let values = this.leftRightScreenEdge(tLeft, toolTipArrowLeft, thisLeft, thisWidth, tooltipoffsetWidth, bodyWidth, rightEdge, bodyLeft); tLeft = values.tLeft, toolTipArrowLeft = values.toolTipArrowLeft, thisLeft = values.thisLeft, thisWidth = values.thisWidth, tooltipoffsetWidth = values.tooltipoffsetWidth, bodyWidth = values.bodyWidth, rightEdge = values.rightEdge; } else if (isPosition == 'bottom') { /* if bottom does not have enough space show tooltip in top area */ if (thisBottom <= tooltipoffsetHeight + 5) { /* if bottom space is larger than top space show tooltip in bottom area */ if (thisTop > thisBottom) { tTop = parseInt(thisTop - (tooltipoffsetHeight + 10)); tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2 + 2); toolTipArrowTop = parseInt(tooltipoffsetHeight - 4); toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 6); isArrowDown = true; } else { /* top position */ tTop = parseInt(thisTop + (thisHeight + 10)); tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2 + 2); toolTipArrowTop = -4; toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 6); isArrowDown = false; } } else { /* if bottom have enough space show tooltip in bottom area */ tTop = parseInt(thisTop + (thisHeight + 10)); tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2 + 2); toolTipArrowTop = -4; toolTipArrowLeft = parseInt(tooltipoffsetWidth / 2 - 6); isArrowDown = false; } /* bottom tooltip left and right corner edge case */ let values = this.leftRightScreenEdge(tLeft, toolTipArrowLeft, thisLeft, thisWidth, tooltipoffsetWidth, bodyWidth, rightEdge, bodyLeft); tLeft = values.tLeft, toolTipArrowLeft = values.toolTipArrowLeft, thisLeft = values.thisLeft, thisWidth = values.thisWidth, tooltipoffsetWidth = values.tooltipoffsetWidth, bodyWidth = values.bodyWidth, rightEdge = values.rightEdge; } else if (isPosition == 'left') { isArrowHorizontal = true; if (thisLeft - bodyLeft <= tooltipoffsetWidth) { if (thisLeft - bodyLeft <= thisRight) { /* if left does not have enough space show tooltip in right area */ tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft + thisWidth + 10); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = -1; isArrowRight = false; } else { /* if left space is larger than right space show tooltip in left (default) area */ if (bodyLeft + tooltipoffsetWidth >= thisLeft) { /* if tooltip width is greater than left space, set left space width to tooltip */ tWidth = thisLeft; tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft - tWidth - 7); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = parseInt(tWidth - 2); isArrowRight = true; } } } else { /* if left have enough space show tooltip in left area */ tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft - tooltipoffsetWidth - 7); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = parseInt(tooltipoffsetWidth - 2); isArrowRight = true; } /* left tooltip top and bottom corner edge case */ let values = this.topBottomScreenEdge(tTop, toolTipArrowTop, thisTop, thisHeight, tooltipoffsetHeight, bodyHeight, bottomEdge); tTop = values.tTop, toolTipArrowTop = values.toolTipArrowTop, thisTop = values.thisTop, thisHeight = values.thisHeight, tooltipoffsetHeight = values.tooltipoffsetHeight, bodyHeight = values.bodyHeight, bottomEdge = values.bottomEdge; } else if (isPosition == 'right') { isArrowHorizontal = true; if (thisRight + bodyLeft <= tooltipoffsetWidth) { if (thisLeft + bodyLeft <= thisRight) { /* if right space is larger than left space show tooltip in right (default) area */ tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft + thisWidth + 7); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = -1; isArrowRight = false; } else { /* if right does not have enough space show tooltip in left area */ if (tooltipoffsetWidth >= thisLeft) { /* if tooltip width is greater than left space, set left space width to tooltip */ tWidth = thisLeft; tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft - tWidth - 7); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = parseInt(tWidth - 2); isArrowRight = true; } else { /* show tooltip in left area without setting left space width to tooltip */ tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft - tooltipoffsetWidth - 7); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = parseInt(tooltipoffsetWidth - 2); isArrowRight = true; } } } else { /* if right have enough space show tooltip in right area */ tTop = parseInt(thisTop + (thisHeight / 2 - tooltipoffsetHeight / 2)); tLeft = parseInt(thisLeft + thisWidth + 7); toolTipArrowTop = parseInt(tooltipoffsetHeight / 2 - 1); toolTipArrowLeft = -1; isArrowRight = false; } /* right tooltip left and right corner edge case */ let values = this.topBottomScreenEdge(tTop, toolTipArrowTop, thisTop, thisHeight, tooltipoffsetHeight, bodyHeight, bottomEdge); tTop = values.tTop, toolTipArrowTop = values.toolTipArrowTop, thisTop = values.thisTop, thisHeight = values.thisHeight, tooltipoffsetHeight = values.tooltipoffsetHeight, bodyHeight = values.bodyHeight, bottomEdge = values.bottomEdge; } this.setState({ top: tTop, left: tLeft, arrowTop: toolTipArrowTop, arrowLeft: toolTipArrowLeft, isArrowHorizontal: isArrowHorizontal, isArrowDown: isArrowDown, isArrowRight: isArrowRight, width: tWidth }); } else { this.setState({ isArrowRight: null }); tTop = parseInt(thisTop + thisHeight + 10); tLeft = parseInt(thisLeft - (tooltipoffsetWidth / 2 - thisWidth / 2) + 2); tooltipLeft = parseInt(tooltipoffsetWidth / 2 - 6); if (tLeft - bodyLeft <= 1) { // default left side screen edge case tooltipLeft = parseInt(thisLeft - bodyLeft + thisWidth / 2 - 6); tLeft = bodyLeft + 2; } else if (tLeft - bodyLeft + tooltipoffsetWidth > bodyWidth) { checkLeft = tLeft + tooltipoffsetWidth - bodyWidth + 2; tooltipLeft = tooltipLeft - bodyLeft + checkLeft; tLeft = tLeft + bodyLeft - checkLeft; } else if (tLeft + tooltipoffsetWidth >= bodyWidth - 2) { // default center screen tooltip collide with right edge screen tLeft = parseInt(thisLeft - (tooltipoffsetWidth - thisWidth) / 2); tooltipLeft = parseInt(tooltipoffsetWidth / 2 - 4); } isArrowDown = false; if (tTop + tooltipoffsetHeight > bodyHeight) { /* if bottom does not have enough space show tooltip in top area */ if (thisTop > thisBottom) { isArrowDown = true; tTop -= tooltipoffsetHeight + thisHeight + 20; } } if (isArrowDown) { let arrowTop = tooltipoffsetHeight - 4; this.setState({ top: tTop, left: tLeft, arrowLeft: tooltipLeft, arrowTop, isArrowDown: true, isArrowHorizontal: isArrowHorizontal }); } else { this.setState({ top: tTop, left: tLeft, arrowLeft: tooltipLeft, arrowTop: -4, isArrowDown: false, isArrowHorizontal: isArrowHorizontal }); } } } }); titleDiv.addEventListener('click', this.reset); titleDiv.addEventListener('mouseup', this.reset); titleDiv.addEventListener('mouseleave', this.reset); } } else { this.reset(); } } render() { let { title, left, top, arrowLeft, arrowTop, isArrowDown, isArrowRight, isArrowHorizontal, width, isHtml, dataTooltipnoArrow, dataTooltipWrap } = this.state; let { dataId, customClass } = this.props; let arrowStyle = isArrowHorizontal ? isArrowRight ? style.arrowRight : style.arrowLeft : isArrowDown ? style.arrowDown : style.arrowUp; title = title ? title.trim() : null; let tooltipWhiteSpaceModification = dataTooltipWrap; if (dataTooltipWrap == 'pre') { tooltipWhiteSpaceModification = 'pre-wrap'; } else if (dataTooltipWrap == 'nowrap') { tooltipWhiteSpaceModification = 'normal'; } let tooltipCss = `${style.tooltipcont} ${whiteSpaceClassMapping[tooltipWhiteSpaceModification]}`; return title ? /*#__PURE__*/React.createElement("div", { className: `${customClass} ${style.tooltiptext}`, style: { left, top, width }, ref: this.getToolTipDOM, "data-id": dataId, "data-test-id": dataId }, !dataTooltipnoArrow ? /*#__PURE__*/React.createElement("span", { className: `${style.tooltiparrow} ${arrowStyle}`, style: { left: arrowLeft, top: arrowTop } }) : null, isHtml ? /*#__PURE__*/React.createElement("div", { className: tooltipCss, dangerouslySetInnerHTML: { __html: title } }) : /*#__PURE__*/React.createElement("div", { className: tooltipCss }, title)) : null; } } Tooltip.propTypes = propTypes; Tooltip.defaultProps = defaultProps; // if (__DOCS__) { // Tooltip.docs = { // componentGroup: 'Atom', // folderName: 'Style Guide', // description: ' ', // external: true // }; // }