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

Added Tooltips to the report users' titles. #1632

Merged
merged 22 commits into from
Mar 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
cf86c6f
started Tooltips
parasharrajat Mar 4, 2021
815daf4
Merge remote-tracking branch 'up/master' into parasharrajat/tooltip
parasharrajat Mar 4, 2021
21fe2e0
New: added tooltip to various parts of the app
parasharrajat Mar 5, 2021
8dece9d
added ellipsis detection
parasharrajat Mar 5, 2021
f8f6e98
new: Tooltip position for Movable elments
parasharrajat Mar 11, 2021
9c60e2b
added tooltip for various parts of the app
parasharrajat Mar 11, 2021
f798513
new: tooltip comments & linting
parasharrajat Mar 11, 2021
de5bbbc
Merge branch 'master' into parasharrajat/tooltip
parasharrajat Mar 11, 2021
b34457c
fix: Report is option in headerView
parasharrajat Mar 11, 2021
134367b
chg: refactor
parasharrajat Mar 11, 2021
9b85137
fix: styling of tooltip
parasharrajat Mar 11, 2021
e204a20
fix: Added tooltip prop & ecllipse only on multiple particpants
parasharrajat Mar 11, 2021
9900d03
new: added tooltip support for report Header
parasharrajat Mar 12, 2021
6ca2c49
fix: tooltip pointer fix
parasharrajat Mar 12, 2021
6d1d6af
Merge remote-tracking branch 'up/master' into parasharrajat/tooltip
parasharrajat Mar 12, 2021
951f971
Merge remote-tracking branch 'up/master' into parasharrajat/tooltip
parasharrajat Mar 12, 2021
a63c24c
Merge branch 'master' into parasharrajat/tooltip
parasharrajat Mar 15, 2021
3a9df36
fix: linting issues and refactor code
parasharrajat Mar 16, 2021
21b51ee
refactor Code && showtooltip glitch
parasharrajat Mar 16, 2021
f4ef0cd
Merge remote-tracking branch 'up/master' into parasharrajat/tooltip
parasharrajat Mar 18, 2021
237968c
removed unnecessary line
parasharrajat Mar 19, 2021
996ae0e
refactor
parasharrajat Mar 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/Hoverable/HoverablePropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ const propTypes = {
PropTypes.func,
]).isRequired,

// Styles to be assigned to the Hoverable Container
// eslint-disable-next-line react/forbid-prop-types
containerStyle: PropTypes.object,
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved

// Function that executes when the mouse moves over the children.
onHoverIn: PropTypes.func,

Expand All @@ -15,6 +19,7 @@ const propTypes = {
};

const defaultProps = {
containerStyle: {},
onHoverIn: () => {},
onHoverOut: () => {},
};
Expand Down
1 change: 1 addition & 0 deletions src/components/Hoverable/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Hoverable extends Component {
render() {
return (
<View
style={this.props.containerStyle}
ref={el => this.wrapperView = el}
onMouseEnter={() => this.setIsHovered(true)}
onMouseLeave={() => this.setIsHovered(false)}
Expand Down
5 changes: 5 additions & 0 deletions src/components/OptionsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ const propTypes = {
PropTypes.func,
PropTypes.shape({current: PropTypes.instanceOf(SectionList)}),
]),

// Whether to show the title tooltip
showTitleTooltip: PropTypes.bool,
};

const defaultProps = {
Expand All @@ -77,6 +80,7 @@ const defaultProps = {
onSelectRow: () => {},
headerMessage: '',
innerRef: null,
showTitleTooltip: false,
};

class OptionsList extends Component {
Expand Down Expand Up @@ -142,6 +146,7 @@ class OptionsList extends Component {
return (
<OptionRow
option={item}
showTitleTooltip={this.props.showTitleTooltip}
hoverStyle={this.props.optionHoveredStyle}
optionIsFocused={!this.props.disableFocusOptions
&& this.props.focusedIndex === (index + section.indexOffset)}
Expand Down
5 changes: 5 additions & 0 deletions src/components/OptionsSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const propTypes = {

// Force the text style to be the unread style on all rows
forceTextUnreadStyle: PropTypes.bool,

// Whether to show the title tooltip
showTitleTooltip: PropTypes.bool,
};

const defaultProps = {
Expand All @@ -68,6 +71,7 @@ const defaultProps = {
disableArrowKeysActions: false,
hideAdditionalOptionStates: false,
forceTextUnreadStyle: false,
showTitleTooltip: false,
};

class OptionsSelector extends Component {
Expand Down Expand Up @@ -191,6 +195,7 @@ class OptionsSelector extends Component {
disableFocusOptions={this.props.disableArrowKeysActions}
hideAdditionalOptionStates={this.props.hideAdditionalOptionStates}
forceTextUnreadStyle={this.props.forceTextUnreadStyle}
showTitleTooltip={this.props.showTitleTooltip}
/>
</View>
);
Expand Down
34 changes: 34 additions & 0 deletions src/components/Tooltip/TooltipPropTypes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import {windowDimensionsPropTypes} from '../withWindowDimensions';

const propTypes = {
// The text to display in the tooltip.
text: PropTypes.string.isRequired,

// Styles to be assigned to the Tooltip wrapper views
containerStyle: PropTypes.object,

// Children to wrap with Tooltip.
children: PropTypes.node.isRequired,

// Props inherited from withWindowDimensions
...windowDimensionsPropTypes,

// 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.
shiftHorizontal: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),

// Any additional amount to manually adjust the vertical position of the tooltip.
// A positive value shifts the tooltip down, and a negative value shifts it up.
shiftVertical: PropTypes.oneOfType([PropTypes.number, PropTypes.func]),
};

const defaultProps = {
shiftHorizontal: 0,
shiftVertical: 0,
};

export {
propTypes,
defaultProps,
};
62 changes: 62 additions & 0 deletions src/components/Tooltip/TooltipRenderedOnPageBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, {memo} from 'react';
import PropTypes from 'prop-types';
import {Animated, Text, View} from 'react-native';
import ReactDOM from 'react-dom';

const propTypes = {
// Style for Animation
// eslint-disable-next-line react/forbid-prop-types
animationStyle: PropTypes.object.isRequired,

// Syle for Tooltip wrapper
// eslint-disable-next-line react/forbid-prop-types
tooltipWrapperStyle: PropTypes.object.isRequired,

// Style for the text rendered inside tooltip
// eslint-disable-next-line react/forbid-prop-types
tooltipTextStyle: PropTypes.object.isRequired,

// Style for the Tooltip pointer Wrapper
// eslint-disable-next-line react/forbid-prop-types
pointerWrapperStyle: PropTypes.object.isRequired,

// Style for the Tooltip pointer
// eslint-disable-next-line react/forbid-prop-types
pointerStyle: PropTypes.object.isRequired,

// Callback to set the Ref to the Tooltip
setTooltipRef: PropTypes.func.isRequired,

// Text to be shown in the tooltip
text: PropTypes.string.isRequired,

// Callback to be used to calulate the width and height of tooltip
measureTooltip: PropTypes.func.isRequired,
};

const defaultProps = {};

const TooltipRenderedOnPageBody = props => ReactDOM.createPortal(
<Animated.View
ref={props.setTooltipRef}
onLayout={props.measureTooltip}
style={[props.tooltipWrapperStyle, props.animationStyle]}
>
<Text style={props.tooltipTextStyle} numberOfLines={1}>{props.text}</Text>
<View style={props.pointerWrapperStyle}>
<View style={props.pointerStyle} />
</View>
</Animated.View>,
document.querySelector('body'),
);

TooltipRenderedOnPageBody.propTypes = propTypes;
TooltipRenderedOnPageBody.defaultProps = defaultProps;
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
TooltipRenderedOnPageBody.displayName = 'TooltipRenderedOnPageBody';

// Props will change frequently.
// On every tooltip hover, we update the position in state which will result in re-rendering.
// We also update the state on layout changes which will be triggered often.
// There will be n number of tooltip components in the page.
// Its good to memorize this one.
export default memo(TooltipRenderedOnPageBody);
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
125 changes: 65 additions & 60 deletions src/components/Tooltip.js → src/components/Tooltip/index.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
import _ from 'underscore';
import React, {PureComponent} from 'react';
import PropTypes from 'prop-types';
import {Animated, Text, View} from 'react-native';
import Hoverable from './Hoverable';
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
import getTooltipStyles from '../styles/getTooltipStyles';

const propTypes = {
// The text to display in the tooltip.
text: PropTypes.string.isRequired,

// Children to wrap with Tooltip.
children: PropTypes.node.isRequired,

// Props inherited from withWindowDimensions
...windowDimensionsPropTypes,

// 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.
shiftHorizontal: PropTypes.number,

// Any additional amount to manually adjust the vertical position of the tooltip.
// A positive value shifts the tooltip down, and a negative value shifts it up.
shiftVertical: PropTypes.number,
};

const defaultProps = {
shiftHorizontal: 0,
shiftVertical: 0,
};
import {Animated, View} from 'react-native';
import TooltipRenderedOnPageBody from './TooltipRenderedOnPageBody';
import Hoverable from '../Hoverable';
import withWindowDimensions from '../withWindowDimensions';
import getTooltipStyles from '../../styles/getTooltipStyles';
import {propTypes, defaultProps} from './TooltipPropTypes';

class Tooltip extends PureComponent {
constructor(props) {
Expand Down Expand Up @@ -56,6 +34,7 @@ class Tooltip extends PureComponent {
this.tooltip = null;

this.isComponentMounted = false;
this.shouldStartShowAnimation = false;
this.animation = new Animated.Value(0);

this.getWrapperPosition = this.getWrapperPosition.bind(this);
Expand Down Expand Up @@ -102,9 +81,13 @@ class Tooltip extends PureComponent {
return new Promise(((resolve) => {
// Make sure the wrapper is mounted before attempting to measure it.
if (this.wrapperView) {
this.wrapperView.measureInWindow((x, y) => resolve({x, y}));
this.wrapperView.measureInWindow((x, y, width, height) => resolve({
x, y, width, height,
}));
} else {
resolve({x: 0, y: 0});
resolve({
x: 0, y: 0, width: 0, height: 0,
});
}
}));
}
Expand Down Expand Up @@ -144,16 +127,37 @@ class Tooltip extends PureComponent {
* Display the tooltip in an animation.
*/
showTooltip() {
Animated.timing(this.animation, {
toValue: 1,
duration: 140,
}).start();
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.getWrapperPosition()
.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) {
Animated.timing(this.animation, {
toValue: 1,
duration: 140,
}).start();
}
});
}

/**
* Hide the tooltip in an animation.
*/
hideTooltip() {
this.shouldStartShowAnimation = false;
Animated.timing(this.animation, {
toValue: 0,
duration: 140,
Expand All @@ -176,34 +180,35 @@ class Tooltip extends PureComponent {
this.state.wrapperHeight,
this.state.tooltipWidth,
this.state.tooltipHeight,
this.props.shiftHorizontal,
this.props.shiftVertical,
_.result(this.props, 'shiftHorizontal'),
_.result(this.props, 'shiftVertical'),
);

return (
<Hoverable
onHoverIn={this.showTooltip}
onHoverOut={this.hideTooltip}
>
<View
ref={el => this.wrapperView = el}
onLayout={this.measureWrapperAndGetPosition}
<>
<TooltipRenderedOnPageBody
animationStyle={animationStyle}
tooltipWrapperStyle={tooltipWrapperStyle}
tooltipTextStyle={tooltipTextStyle}
pointerWrapperStyle={pointerWrapperStyle}
pointerStyle={pointerStyle}
setTooltipRef={el => this.tooltip = el}
measureTooltip={this.measureTooltip}
text={this.props.text}
/>
<Hoverable
containerStyle={this.props.containerStyle}
onHoverIn={this.showTooltip}
onHoverOut={this.hideTooltip}
>
<Animated.View style={animationStyle}>
<View
ref={el => this.tooltip = el}
onLayout={this.measureTooltip}
style={tooltipWrapperStyle}
>
<Text style={tooltipTextStyle} numberOfLines={1}>{this.props.text}</Text>
</View>
<View style={pointerWrapperStyle}>
<View style={pointerStyle} />
</View>
</Animated.View>
{this.props.children}
</View>
</Hoverable>
<View
ref={el => this.wrapperView = el}
onLayout={this.measureWrapperAndGetPosition}
style={this.props.containerStyle}
>
{this.props.children}
</View>
</Hoverable>
</>
);
}
}
Expand Down
15 changes: 15 additions & 0 deletions src/components/Tooltip/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// We can't use the common component for the Tooltip as Web implementation uses DOM specific method to
// render the View which is not present on the Mobile.
import {propTypes, defaultProps} from './TooltipPropTypes';

parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
/**
* There is no native support for the Hover on the Mobile platform so we just return the enclosing childrens
* @param {propTypes} props
* @returns {ReactNodeLike}
*/
const Tooltip = props => props.children;

Tooltip.propTypes = propTypes;
Tooltip.defaultProps = defaultProps;
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved
Tooltip.displayName = 'Tooltip';
export default Tooltip;
5 changes: 5 additions & 0 deletions src/libs/OptionsListUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Str from 'expensify-common/lib/str';
import {getDefaultAvatar} from './actions/PersonalDetails';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
import {getReportParticipantsTitle} from './reportUtils';

/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
Expand Down Expand Up @@ -92,11 +93,14 @@ function createOption(personalDetailList, report, draftComments, activeReportID,
: '')
+ _.unescape(report.lastMessageText)
: '';
const tooltipText = getReportParticipantsTitle(lodashGet(report, ['participants'], []));
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved

return {
text: report ? report.reportName : personalDetail.displayName,
alternateText: (showChatPreviewLine && lastMessageText) ? lastMessageText : personalDetail.login,
icons: report ? report.icons : [personalDetail.avatar],
tooltipText,
participantsList: personalDetailList,
parasharrajat marked this conversation as resolved.
Show resolved Hide resolved

// It doesn't make sense to provide a login in the case of a report with multiple participants since
// there isn't any one single login to refer to for a report.
Expand Down Expand Up @@ -424,4 +428,5 @@ export {
getNewGroupOptions,
getSidebarOptions,
getHeaderMessage,
getPersonalDetailsForLogins,
};
Loading