diff --git a/package-lock.json b/package-lock.json index 5270b292a98..896d09dc347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@react-navigation/drawer": "github:Expensify/react-navigation#react-navigation-drawer-v6.5.0-alpha1-gitpkg", "@react-navigation/native": "6.0.13", "@react-navigation/stack": "6.3.1", + "@react-ng/bounds-observer": "^0.2.1", "@ua/react-native-airship": "^15.2.3", "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", @@ -2594,6 +2595,11 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@html-ng/bounding-client-rect-observer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@html-ng/bounding-client-rect-observer/-/bounding-client-rect-observer-0.1.3.tgz", + "integrity": "sha512-RV1Lz23ckbpOgU1bNGxxTS4XTCEFGxiXoEmi8EOHtzTVzS+AEMkoqxllugn6IHEMqNkbcHipURRupEJe8Dsp1g==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.5.0", "dev": true, @@ -7265,6 +7271,38 @@ "react-native-screens": ">= 3.0.0" } }, + "node_modules/@react-ng/bounds-observer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@react-ng/bounds-observer/-/bounds-observer-0.2.1.tgz", + "integrity": "sha512-i0h7x0qOLJz+JKxhOpngHFob6PH2Qmra85aQ0e/viS1yYgidoBvPJHn8WPGn5LXff98fE+fPhngsaD7FSbxcwQ==", + "dependencies": { + "@html-ng/bounding-client-rect-observer": "^0.1.3", + "@types/react": "^18.0.31", + "@types/react-dom": "^18.0.11", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@react-ng/bounds-observer/node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@react-ng/bounds-observer/node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/@sentry/browser": { "version": "7.11.1", "license": "BSD-3-Clause", @@ -15135,8 +15173,7 @@ }, "node_modules/@types/prop-types": { "version": "15.7.5", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/qs": { "version": "6.9.7", @@ -15156,15 +15193,23 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.0.24", - "license": "MIT", - "peer": true, + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", + "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-native": { "version": "0.70.6", "license": "MIT", @@ -15188,8 +15233,7 @@ }, "node_modules/@types/scheduler": { "version": "0.16.2", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/seedrandom": { "version": "2.4.30", @@ -43222,6 +43266,11 @@ "@hapi/hoek": "^9.0.0" } }, + "@html-ng/bounding-client-rect-observer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@html-ng/bounding-client-rect-observer/-/bounding-client-rect-observer-0.1.3.tgz", + "integrity": "sha512-RV1Lz23ckbpOgU1bNGxxTS4XTCEFGxiXoEmi8EOHtzTVzS+AEMkoqxllugn6IHEMqNkbcHipURRupEJe8Dsp1g==" + }, "@humanwhocodes/config-array": { "version": "0.5.0", "dev": true, @@ -46529,6 +46578,37 @@ "warn-once": "^0.1.0" } }, + "@react-ng/bounds-observer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@react-ng/bounds-observer/-/bounds-observer-0.2.1.tgz", + "integrity": "sha512-i0h7x0qOLJz+JKxhOpngHFob6PH2Qmra85aQ0e/viS1yYgidoBvPJHn8WPGn5LXff98fE+fPhngsaD7FSbxcwQ==", + "requires": { + "@html-ng/bounding-client-rect-observer": "^0.1.3", + "@types/react": "^18.0.31", + "@types/react-dom": "^18.0.11", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "dependencies": { + "react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + } + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, "@sentry/browser": { "version": "7.11.1", "requires": { @@ -51720,8 +51800,7 @@ "dev": true }, "@types/prop-types": { - "version": "15.7.5", - "peer": true + "version": "15.7.5" }, "@types/qs": { "version": "6.9.7", @@ -51738,14 +51817,23 @@ "dev": true }, "@types/react": { - "version": "18.0.24", - "peer": true, + "version": "18.2.6", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.6.tgz", + "integrity": "sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, + "@types/react-dom": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz", + "integrity": "sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==", + "requires": { + "@types/react": "*" + } + }, "@types/react-native": { "version": "0.70.6", "peer": true, @@ -51765,8 +51853,7 @@ "dev": true }, "@types/scheduler": { - "version": "0.16.2", - "peer": true + "version": "0.16.2" }, "@types/seedrandom": { "version": "2.4.30", diff --git a/package.json b/package.json index b3f2c07d4d2..07054907ea4 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@react-navigation/drawer": "github:Expensify/react-navigation#react-navigation-drawer-v6.5.0-alpha1-gitpkg", "@react-navigation/native": "6.0.13", "@react-navigation/stack": "6.3.1", + "@react-ng/bounds-observer": "^0.2.1", "@ua/react-native-airship": "^15.2.3", "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js index 360bcca2d79..d22bf2ba65c 100644 --- a/src/components/Tooltip/TooltipRenderedOnPageBody.js +++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js @@ -20,11 +20,11 @@ const propTypes = { /** The distance between the top of the wrapper view and the top of the window */ yOffset: PropTypes.number.isRequired, - /** The width of the tooltip wrapper */ - wrapperWidth: PropTypes.number.isRequired, + /** The width of the tooltip's target */ + targetWidth: PropTypes.number.isRequired, - /** The Height of the tooltip wrapper */ - wrapperHeight: PropTypes.number.isRequired, + /** The height of the tooltip's target */ + targetHeight: PropTypes.number.isRequired, /** Any additional amount to manually adjust the horizontal position of the tooltip. A positive value shifts the tooltip to the right, and a negative value shifts it to the left. */ @@ -63,11 +63,11 @@ const TooltipRenderedOnPageBody = (props) => { // The width of tooltip's inner content. Has to be undefined in the beginning // as a width of 0 will cause the content to be rendered of a width of 0, // which prevents us from measuring it correctly. - const [tooltipContentWidth, setTooltipContentWidth] = useState(undefined); - const [tooltipWidth, setTooltipWidth] = useState(0); - const [tooltipHeight, setTooltipHeight] = useState(0); + const [contentMeasuredWidth, setContentMeasuredWidth] = useState(undefined); + const [wrapperMeasuredWidth, setWrapperMeasuredWidth] = useState(0); + const [wrapperMeasuredHeight, setWrapperMeasuredHeight] = useState(0); const contentRef = useRef(); - const wrapper = useRef(); + const rootWrapper = useRef(); useEffect(() => { if (!props.renderTooltipContent || !props.text) { @@ -79,40 +79,41 @@ const TooltipRenderedOnPageBody = (props) => { useLayoutEffect(() => { // Calculate the tooltip width and height before the browser repaints the screen to prevent flicker // because of the late update of the width and the height from onLayout. - const rect = wrapper.current.getBoundingClientRect(); + const rect = rootWrapper.current.getBoundingClientRect(); - setTooltipWidth(rect.width); - setTooltipHeight(rect.height); - setTooltipContentWidth(contentRef.current.offsetWidth); + setWrapperMeasuredWidth(rect.width); + setWrapperMeasuredHeight(rect.height); + setContentMeasuredWidth(contentRef.current.offsetWidth); }, []); - const {animationStyle, tooltipWrapperStyle, tooltipTextStyle, pointerWrapperStyle, pointerStyle} = useMemo( + const {animationStyle, rootWrapperStyle, textStyle, pointerWrapperStyle, pointerStyle} = useMemo( () => getTooltipStyles( props.animation, props.windowWidth, props.xOffset, props.yOffset, - props.wrapperWidth, - props.wrapperHeight, + props.targetWidth, + props.targetHeight, props.maxWidth, - tooltipWidth, - tooltipHeight, - tooltipContentWidth, + wrapperMeasuredWidth, + wrapperMeasuredHeight, + contentMeasuredWidth, props.shiftHorizontal, props.shiftVertical, + rootWrapper.current, ), [ props.animation, props.windowWidth, props.xOffset, props.yOffset, - props.wrapperWidth, - props.wrapperHeight, + props.targetWidth, + props.targetHeight, props.maxWidth, - tooltipWidth, - tooltipHeight, - tooltipContentWidth, + wrapperMeasuredWidth, + wrapperMeasuredHeight, + contentMeasuredWidth, props.shiftHorizontal, props.shiftVertical, ], @@ -125,10 +126,10 @@ const TooltipRenderedOnPageBody = (props) => { content = ( {props.text} @@ -139,8 +140,8 @@ const TooltipRenderedOnPageBody = (props) => { return ReactDOM.createPortal( {content} diff --git a/src/components/Tooltip/index.js b/src/components/Tooltip/index.js index 44157343069..f18bc803aa4 100644 --- a/src/components/Tooltip/index.js +++ b/src/components/Tooltip/index.js @@ -1,22 +1,27 @@ import _ from 'underscore'; import React, {PureComponent} from 'react'; import {Animated, View} from 'react-native'; +import {BoundsObserver} from '@react-ng/bounds-observer'; import TooltipRenderedOnPageBody from './TooltipRenderedOnPageBody'; import Hoverable from '../Hoverable'; import withWindowDimensions from '../withWindowDimensions'; -import {propTypes, defaultProps} from './tooltipPropTypes'; +import * as tooltipPropTypes from './tooltipPropTypes'; import TooltipSense from './TooltipSense'; -import makeCancellablePromise from '../../libs/MakeCancellablePromise'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; +// A "target" for the tooltip, i.e. an element that, when hovered over, triggers the tooltip to appear. The tooltip will +// point towards this target. class Tooltip extends PureComponent { constructor(props) { super(props); this.state = { - // Is tooltip rendered? + // Is tooltip already rendered on the page's body? This happens once. isRendered: false, + // Is the tooltip currently visible? + isVisible: false, + // The distance between the left side of the wrapper view and the left side of the window xOffset: 0, @@ -30,57 +35,25 @@ class Tooltip extends PureComponent { // Whether the tooltip is first tooltip to activate the TooltipSense this.isTooltipSenseInitiator = false; - this.shouldStartShowAnimation = false; this.animation = new Animated.Value(0); this.hasHoverSupport = DeviceCapabilities.hasHoverSupport(); - this.getWrapperPosition = this.getWrapperPosition.bind(this); this.showTooltip = this.showTooltip.bind(this); this.hideTooltip = this.hideTooltip.bind(this); - } - - componentDidUpdate(prevProps) { - if (this.props.windowWidth === prevProps.windowWidth && this.props.windowHeight === prevProps.windowHeight) { - return; - } - - this.getWrapperPositionPromise = makeCancellablePromise(this.getWrapperPosition()); - this.getWrapperPositionPromise.promise.then(({x, y}) => this.setState({xOffset: x, yOffset: y})); - } - - componentWillUnmount() { - if (!this.getWrapperPositionPromise) { - return; - } - - this.getWrapperPositionPromise.cancel(); + this.updateBounds = this.updateBounds.bind(this); } /** - * Measure the position of the wrapper view relative to the window. + * Update the tooltip bounding rectangle * - * @returns {Promise} + * @param {Object} bounds - updated bounds */ - getWrapperPosition() { - return new Promise((resolve) => { - // Make sure the wrapper is mounted before attempting to measure it. - if (this.wrapperView && _.isFunction(this.wrapperView.measureInWindow)) { - this.wrapperView.measureInWindow((x, y, width, height) => - resolve({ - x, - y, - width, - height, - }), - ); - } else { - resolve({ - x: 0, - y: 0, - width: 0, - height: 0, - }); - } + updateBounds(bounds) { + this.setState({ + wrapperWidth: bounds.width, + wrapperHeight: bounds.height, + xOffset: bounds.x, + yOffset: bounds.y, }); } @@ -91,38 +64,24 @@ class Tooltip extends PureComponent { if (!this.state.isRendered) { this.setState({isRendered: true}); } + + this.setState({isVisible: true}); + this.animation.stopAnimation(); - this.shouldStartShowAnimation = true; - - // We have to dynamically calculate the position here as tooltip could have been rendered on some elments - // that has changed its position - this.getWrapperPositionPromise = makeCancellablePromise(this.getWrapperPosition()); - this.getWrapperPositionPromise.promise.then(({x, y, width, height}) => { - this.setState({ - wrapperWidth: width, - wrapperHeight: height, - xOffset: x, - yOffset: y, - }); - // We may need this check due to the reason that the animation start will fire async - // and hideTooltip could fire before it thus keeping the Tooltip visible - if (this.shouldStartShowAnimation) { - // When TooltipSense is active, immediately show the tooltip - if (TooltipSense.isActive()) { - this.animation.setValue(1); - } else { - this.isTooltipSenseInitiator = true; - Animated.timing(this.animation, { - toValue: 1, - duration: 140, - delay: 500, - useNativeDriver: false, - }).start(); - } - TooltipSense.activate(); - } - }); + // When TooltipSense is active, immediately show the tooltip + if (TooltipSense.isActive()) { + this.animation.setValue(1); + } else { + this.isTooltipSenseInitiator = true; + Animated.timing(this.animation, { + toValue: 1, + duration: 140, + delay: 500, + useNativeDriver: false, + }).start(); + } + TooltipSense.activate(); } /** @@ -130,7 +89,7 @@ class Tooltip extends PureComponent { */ hideTooltip() { this.animation.stopAnimation(); - this.shouldStartShowAnimation = false; + if (TooltipSense.isActive() && !this.isTooltipSenseInitiator) { this.animation.setValue(0); } else { @@ -142,7 +101,10 @@ class Tooltip extends PureComponent { useNativeDriver: false, }).start(); } + TooltipSense.deactivate(); + + this.setState({isVisible: false}); } render() { @@ -192,8 +154,8 @@ class Tooltip extends PureComponent { windowWidth={this.props.windowWidth} xOffset={this.state.xOffset} yOffset={this.state.yOffset} - wrapperWidth={this.state.wrapperWidth} - wrapperHeight={this.state.wrapperHeight} + targetWidth={this.state.wrapperWidth} + targetHeight={this.state.wrapperHeight} shiftHorizontal={_.result(this.props, 'shiftHorizontal')} shiftVertical={_.result(this.props, 'shiftVertical')} text={this.props.text} @@ -205,19 +167,24 @@ class Tooltip extends PureComponent { key={[this.props.text, ...this.props.renderTooltipContentKey]} /> )} - - {child} - + + {child} + + ); } } -Tooltip.propTypes = propTypes; -Tooltip.defaultProps = defaultProps; +Tooltip.propTypes = tooltipPropTypes.propTypes; +Tooltip.defaultProps = tooltipPropTypes.defaultProps; export default withWindowDimensions(Tooltip); diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js index 42ad2df1247..985eddbd3de 100644 --- a/src/styles/getTooltipStyles.js +++ b/src/styles/getTooltipStyles.js @@ -56,22 +56,24 @@ function computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWid * and the left edge of the wrapped component. * @param {Number} yOffset - The distance between the top edge of the window * and the top edge of the wrapped component. + * @param {Element} tooltip - The reference to the tooltip's root element * @returns {Boolean} */ -function isOverlappingAtTop(xOffset, yOffset) { +function isOverlappingAtTop(xOffset, yOffset, tooltip) { if (typeof document.elementFromPoint !== 'function') { return false; } const element = document.elementFromPoint(xOffset, yOffset); - if (!element) { + // Ensure it's not the already rendered element of this very tooltip, so the tooltip doesn't try to "avoid" itself + if (!element || tooltip.contains(element)) { return false; } const rect = element.getBoundingClientRect(); - // Ensure it's not itself + overlapping with another element by checking if the yOffset is greater than the top of the element + // Ensure it's not overlapping with another element by checking if the yOffset is greater than the top of the element // and less than the bottom of the element return yOffset > rect.top && yOffset < rect.bottom; } @@ -85,17 +87,18 @@ function isOverlappingAtTop(xOffset, yOffset) { * and the left edge of the wrapped component. * @param {Number} yOffset - The distance between the top edge of the window * and the top edge of the wrapped component. - * @param {Number} componentWidth - The width of the wrapped component. - * @param {Number} componentHeight - The height of the wrapped component. + * @param {Number} tooltipTargetWidth - The width of the tooltip's target + * @param {Number} tooltipTargetHeight - The height of the tooltip's target * @param {Number} maxWidth - The tooltip's max width. - * @param {Number} tooltipWidth - The width of the tooltip itself. - * @param {Number} tooltipHeight - The height of the tooltip itself. - * @param {Number} tooltipContentWidth - The tooltip's inner content width. + * @param {Number} tooltipWidth - The measured width of the tooltip + * @param {Number} tooltipHeight - The measured height of the tooltip + * @param {Number} tooltipContentWidth - The tooltip's inner content measured width. * @param {Number} [manualShiftHorizontal] - Any additional amount to manually shift the tooltip to the left or right. * A positive value shifts it to the right, * and a negative value shifts it to the left. * @param {Number} [manualShiftVertical] - Any additional amount to manually shift the tooltip up or down. * A positive value shifts it down, and a negative value shifts it up. + * @param {Element} tooltip - The reference to the tooltip's root element * @returns {Object} */ export default function getTooltipStyles( @@ -103,24 +106,25 @@ export default function getTooltipStyles( windowWidth, xOffset, yOffset, - componentWidth, - componentHeight, + tooltipTargetWidth, + tooltipTargetHeight, maxWidth, tooltipWidth, tooltipHeight, tooltipContentWidth, manualShiftHorizontal = 0, manualShiftVertical = 0, + tooltip, ) { // Determine if the tooltip should display below the wrapped component. // If either a tooltip will try to render within GUTTER_WIDTH logical pixels of the top of the screen, // Or the wrapped component is overlapping at top-left with another element // we'll display it beneath its wrapped component rather than above it as usual. - const shouldShowBelow = yOffset - tooltipHeight < GUTTER_WIDTH || isOverlappingAtTop(xOffset, yOffset); + const shouldShowBelow = yOffset - tooltipHeight < GUTTER_WIDTH || isOverlappingAtTop(xOffset, yOffset, tooltip); // Determine if we need to shift the tooltip horizontally to prevent it // from displaying too near to the edge of the screen. - const horizontalShift = computeHorizontalShift(windowWidth, xOffset, componentWidth, tooltipWidth, manualShiftHorizontal); + const horizontalShift = computeHorizontalShift(windowWidth, xOffset, tooltipTargetWidth, tooltipWidth, manualShiftHorizontal); // Determine if we need to shift the pointer horizontally to prevent it from being too near to the edge of the tooltip // We shift it to the right a bit if the tooltip is positioned on the extreme left @@ -133,18 +137,19 @@ export default function getTooltipStyles( const tooltipVerticalPadding = spacing.pv1; const tooltipFontSize = variables.fontSizeSmall; - // We get wrapper width based on the tooltip's inner text width so the wrapper is just big enough to fit text and prevent white space. + // We calculate wrapper width based on the tooltip's inner text width so the wrapper is just big enough to fit text and prevent white space. // If the text width is less than the maximum available width, add horizontal padding. // Note: tooltipContentWidth ignores the fractions (OffsetWidth) so add 1px to fit the text properly. - const wrapperWidth = tooltipContentWidth && tooltipContentWidth + spacing.ph2.paddingHorizontal * 2 + 1; - - // Hide the tooltip entirely if it's position hasn't finished measuring yet. This prevents UI jank where the tooltip flashes in the top left corner of the screen. - const opacity = xOffset === 0 && yOffset === 0 ? 0 : 1; + const rootWrapperWidth = tooltipContentWidth && tooltipContentWidth + spacing.ph2.paddingHorizontal * 2 + 1; const isTooltipSizeReady = tooltipWidth !== 0 && tooltipHeight !== 0; + + // Hide the tooltip entirely if it's size hasn't finished measuring yet. This prevents UI jank where the tooltip flashes close to its expected position. + const opacity = isTooltipSizeReady ? 1 : 0; + const scale = !isTooltipSizeReady ? 1 : currentSize; - let wrapperTop = 0; - let wrapperLeft = 0; + let rootWrapperTop = 0; + let rootWrapperLeft = 0; if (isTooltipSizeReady) { // Because it uses fixed positioning, the top-left corner of the tooltip is aligned @@ -155,9 +160,9 @@ export default function getTooltipStyles( // First, we'll position it vertically. // To shift the tooltip down, we'll give `top` a positive value. // To shift the tooltip up, we'll give `top` a negative value. - wrapperTop = shouldShowBelow + rootWrapperTop = shouldShowBelow ? // We need to shift the tooltip down below the component. So shift the tooltip down (+) by... - yOffset + componentHeight + POINTER_HEIGHT + manualShiftVertical + yOffset + tooltipTargetHeight + POINTER_HEIGHT + manualShiftVertical : // We need to shift the tooltip up above the component. So shift the tooltip up (-) by... yOffset - (tooltipHeight + POINTER_HEIGHT) + manualShiftVertical; @@ -173,7 +178,7 @@ export default function getTooltipStyles( // so the tooltip's center lines up with the center of the wrapped component. // 3) Add the horizontal shift (left or right) computed above to keep it out of the gutters. // 4) Lastly, add the manual horizontal shift passed in as a parameter. - wrapperLeft = xOffset + (componentWidth / 2 - tooltipWidth / 2) + horizontalShift + manualShiftHorizontal; + rootWrapperLeft = xOffset + (tooltipTargetWidth / 2 - tooltipWidth / 2) + horizontalShift + manualShiftHorizontal; } return { @@ -183,23 +188,23 @@ export default function getTooltipStyles( // so Position fixed children will be relative to this new Local cordinate system transform: [{scale}], }, - tooltipWrapperStyle: { + rootWrapperStyle: { position: 'fixed', backgroundColor: themeColors.heading, borderRadius: variables.componentBorderRadiusSmall, ...tooltipVerticalPadding, ...spacing.ph2, zIndex: variables.tooltipzIndex, - width: wrapperWidth, + width: rootWrapperWidth, maxWidth, - top: wrapperTop, - left: wrapperLeft, + top: rootWrapperTop, + left: rootWrapperLeft, opacity, // We are adding this to prevent the tooltip text from being selected and copied on CTRL + A. ...styles.userSelectNone, }, - tooltipTextStyle: { + textStyle: { color: themeColors.textReversed, fontFamily: fontFamily.EXP_NEUE, fontSize: tooltipFontSize, @@ -209,22 +214,22 @@ export default function getTooltipStyles( pointerWrapperStyle: { position: 'fixed', - // By default, the pointer's top-left will align with the top-left of the tooltip wrapper. + // By default, the pointer's top-left will align with the top-left of the tooltip tooltip. // // To align it vertically, we'll: - // If the pointer should be below the tooltip wrapper, shift the pointer down (+) by the tooltip height, + // If the pointer should be below the tooltip tooltip, shift the pointer down (+) by the tooltip height, // so that the top of the pointer lines up with the bottom of the tooltip // - // OR if the pointer should be above the tooltip wrapper, then the pointer up (-) by the pointer's height + // OR if the pointer should be above the tooltip tooltip, then the pointer up (-) by the pointer's height // so that the bottom of the pointer lines up with the top of the tooltip top: shouldShowBelow ? -POINTER_HEIGHT : tooltipHeight, // To align it horizontally, we'll: - // 1) Shift the pointer to the right (+) by the half the tooltipWidth's width, - // so the left edge of the pointer lines up with the tooltipWidth's center. + // 1) Shift the pointer to the right (+) by the half the rootWrapperWidth's width, + // so the left edge of the pointer lines up with the rootWrapperWidth's center. // 2) To the left (-) by half the pointer's width, - // so the pointer's center lines up with the tooltipWidth's center. - // 3) Due to the tip start from the left edge of wrapper Tooltip so we have to remove the + // so the pointer's center lines up with the rootWrapperWidth's center. + // 3) Due to the tip start from the left edge of tooltip Tooltip so we have to remove the // horizontalShift which is added to adjust it into the Window left: horizontalShiftPointer + (tooltipWidth / 2 - POINTER_WIDTH / 2),