Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Web] Change tooltip component to accept generic content #15325

Merged
79 changes: 28 additions & 51 deletions src/components/Tooltip/TooltipRenderedOnPageBody.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types';
import {Animated, View} from 'react-native';
import ReactDOM from 'react-dom';
import _ from 'underscore';
import getTooltipStyles from '../../styles/getTooltipStyles';
import Text from '../Text';
import styles from '../../styles/styles';

const propTypes = {
/** Window width */
Expand Down Expand Up @@ -65,7 +63,7 @@ class TooltipRenderedOnPageBody extends React.PureComponent {
super(props);
this.state = {
// The width of tooltip's inner content
tooltipContentWidth: 0,
tooltipContentWidth: undefined,
hannojg marked this conversation as resolved.
Show resolved Hide resolved

// The width and height of the tooltip itself
tooltipWidth: 0,
Expand All @@ -81,30 +79,24 @@ class TooltipRenderedOnPageBody extends React.PureComponent {
}

componentDidUpdate(prevProps) {
if (prevProps.text === this.props.text) {
if (prevProps.text === this.props.text && prevProps.renderTooltipContent === this.props.renderTooltipContent) {
return;
}

// Reset the tooltip text width to 0 so that we can measure it again.
// Reset the tooltip content width to undefined so that we can measure it again.
// eslint-disable-next-line react/no-did-update-set-state
this.setState({tooltipContentWidth: 0}, this.updateTooltipContentWidth);
this.setState({tooltipContentWidth: undefined}, this.updateTooltipContentWidth);
}

/**
* Updates the tooltipContentWidth state with the width of the tooltip's inner content.
* Can be called in onLayout, or if you are using text just call it after the first render
* to get the text's width.
*
* @param {Object} [event]
*/
updateTooltipContentWidth(event) {
if (!this.textRef && !event) {
updateTooltipContentWidth() {
hannojg marked this conversation as resolved.
Show resolved Hide resolved
if (!this.contentWrapperRef) {
return;
}

const widthFromEvent = _.get(event, ['nativeEvent', 'layout', 'width']);
const contentWidth = this.contentWrapperRef.offsetWidth;

this.setState({
tooltipContentWidth: widthFromEvent || this.textRef.offsetWidth,
tooltipContentWidth: contentWidth,
});
}

Expand Down Expand Up @@ -148,49 +140,34 @@ class TooltipRenderedOnPageBody extends React.PureComponent {
} else {
content = (
<Text numberOfLines={this.props.numberOfLines} style={tooltipTextStyle}>
<Text
style={tooltipTextStyle}
ref={(ref) => {
// Once the text for the tooltip first renders, update the width of the tooltip dynamically to fit the width of the text.
// Note that we can't have this code in componentDidMount because the ref for the text won't be set until after the first render
if (this.textRef) {
return;
}

this.textRef = ref;
this.updateTooltipContentWidth();
}}
>
<Text style={tooltipTextStyle}>
{this.props.text}
</Text>
</Text>
);
}

const isCustomContent = _.isFunction(this.props.renderTooltipContent);

return ReactDOM.createPortal(
<>
{/* If rendering custom content always render an invisible version of
it to detect layout size changes, if the content updates. */}
{isCustomContent && (
<View
style={styles.invisible}
onLayout={this.updateTooltipContentWidth}
>
{this.props.renderTooltipContent()}
</View>
)}
<Animated.View
onLayout={this.measureTooltip}
style={[tooltipWrapperStyle, animationStyle]}
<Animated.View
onLayout={this.measureTooltip}
style={[tooltipWrapperStyle, animationStyle]}
>
<View
hannojg marked this conversation as resolved.
Show resolved Hide resolved
ref={(ref) => {
if (this.contentWrapperRef) {
return;
}

this.contentWrapperRef = ref;
this.updateTooltipContentWidth();
}}
>
{content}
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>
</>,
</View>
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>,
document.querySelector('body'),
);
}
Expand Down
9 changes: 4 additions & 5 deletions src/styles/getTooltipStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,16 @@ export default function getTooltipStyles(
// 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.
// 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 < maxWidth
? tooltipContentWidth + (spacing.ph2.paddingHorizontal * 2) + 1
: maxWidth;
const wrapperWidth = tooltipContentWidth && tooltipContentWidth + (spacing.ph2.paddingHorizontal * 2) + 1;
hannojg marked this conversation as resolved.
Show resolved Hide resolved

// 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;

return {
animationStyle: {
// remember Transform causes a new Local cordinate system
// remember Transform causes a new Local coordinate system
// https://drafts.csswg.org/css-transforms-1/#transform-rendering
// so Position fixed children will be relative to this new Local cordinate system
// so Position fixed children will be relative to this new Local coordinate system
transform: [{
scale: currentSize,
}],
Expand All @@ -124,6 +122,7 @@ export default function getTooltipStyles(
...spacing.ph2,
zIndex: variables.tooltipzIndex,
width: wrapperWidth,
maxWidth,

// Because it uses fixed positioning, the top-left corner of the tooltip is aligned
// with the top-left corner of the window by default.
Expand Down