diff --git a/src/components/Hoverable/HoverablePropTypes.js b/src/components/Hoverable/HoverablePropTypes.js
index 2d2842be19c5..9125e9c8669a 100644
--- a/src/components/Hoverable/HoverablePropTypes.js
+++ b/src/components/Hoverable/HoverablePropTypes.js
@@ -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,
+
// Function that executes when the mouse moves over the children.
onHoverIn: PropTypes.func,
@@ -15,6 +19,7 @@ const propTypes = {
};
const defaultProps = {
+ containerStyle: {},
onHoverIn: () => {},
onHoverOut: () => {},
};
diff --git a/src/components/Hoverable/index.js b/src/components/Hoverable/index.js
index da6463561b2b..c67b26c3f2de 100644
--- a/src/components/Hoverable/index.js
+++ b/src/components/Hoverable/index.js
@@ -57,6 +57,7 @@ class Hoverable extends Component {
render() {
return (
this.wrapperView = el}
onMouseEnter={() => this.setIsHovered(true)}
onMouseLeave={() => this.setIsHovered(false)}
diff --git a/src/components/OptionsList.js b/src/components/OptionsList.js
index 7f759d705788..b783cca19866 100644
--- a/src/components/OptionsList.js
+++ b/src/components/OptionsList.js
@@ -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 = {
@@ -77,6 +80,7 @@ const defaultProps = {
onSelectRow: () => {},
headerMessage: '',
innerRef: null,
+ showTitleTooltip: false,
};
class OptionsList extends Component {
@@ -142,6 +146,7 @@ class OptionsList extends Component {
return (
);
diff --git a/src/components/Tooltip/TooltipPropTypes.js b/src/components/Tooltip/TooltipPropTypes.js
new file mode 100644
index 000000000000..699c6c643fb3
--- /dev/null
+++ b/src/components/Tooltip/TooltipPropTypes.js
@@ -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,
+};
diff --git a/src/components/Tooltip/TooltipRenderedOnPageBody.js b/src/components/Tooltip/TooltipRenderedOnPageBody.js
new file mode 100644
index 000000000000..1611147cc08b
--- /dev/null
+++ b/src/components/Tooltip/TooltipRenderedOnPageBody.js
@@ -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(
+
+ {props.text}
+
+
+
+ ,
+ document.querySelector('body'),
+);
+
+TooltipRenderedOnPageBody.propTypes = propTypes;
+TooltipRenderedOnPageBody.defaultProps = defaultProps;
+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);
diff --git a/src/components/Tooltip.js b/src/components/Tooltip/index.js
similarity index 61%
rename from src/components/Tooltip.js
rename to src/components/Tooltip/index.js
index c72df808e47e..cd38fc3ce0fc 100644
--- a/src/components/Tooltip.js
+++ b/src/components/Tooltip/index.js
@@ -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) {
@@ -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);
@@ -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,
+ });
}
}));
}
@@ -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,
@@ -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 (
-
- this.wrapperView = el}
- onLayout={this.measureWrapperAndGetPosition}
+ <>
+ this.tooltip = el}
+ measureTooltip={this.measureTooltip}
+ text={this.props.text}
+ />
+
-
- this.tooltip = el}
- onLayout={this.measureTooltip}
- style={tooltipWrapperStyle}
- >
- {this.props.text}
-
-
-
-
-
- {this.props.children}
-
-
+ this.wrapperView = el}
+ onLayout={this.measureWrapperAndGetPosition}
+ style={this.props.containerStyle}
+ >
+ {this.props.children}
+
+
+ >
);
}
}
diff --git a/src/components/Tooltip/index.native.js b/src/components/Tooltip/index.native.js
new file mode 100644
index 000000000000..d5092883bbad
--- /dev/null
+++ b/src/components/Tooltip/index.native.js
@@ -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';
+
+/**
+ * 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;
+Tooltip.displayName = 'Tooltip';
+export default Tooltip;
diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js
index f6546c64a3e7..e919f280e3db 100644
--- a/src/libs/OptionsListUtils.js
+++ b/src/libs/OptionsListUtils.js
@@ -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
@@ -92,11 +93,14 @@ function createOption(personalDetailList, report, draftComments, activeReportID,
: '')
+ _.unescape(report.lastMessageText)
: '';
+ const tooltipText = getReportParticipantsTitle(lodashGet(report, ['participants'], []));
return {
text: report ? report.reportName : personalDetail.displayName,
alternateText: (showChatPreviewLine && lastMessageText) ? lastMessageText : personalDetail.login,
icons: report ? report.icons : [personalDetail.avatar],
+ tooltipText,
+ participantsList: personalDetailList,
// 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.
@@ -424,4 +428,5 @@ export {
getNewGroupOptions,
getSidebarOptions,
getHeaderMessage,
+ getPersonalDetailsForLogins,
};
diff --git a/src/libs/hasEllipsis/index.js b/src/libs/hasEllipsis/index.js
new file mode 100644
index 000000000000..d18af237be35
--- /dev/null
+++ b/src/libs/hasEllipsis/index.js
@@ -0,0 +1,11 @@
+/**
+ * Does an elment have ellipsis
+ *
+ * @param {HTMLElement} el Element to check
+ * @returns {Boolean}
+ */
+function hasEllipsis(el) {
+ return el.offsetWidth < el.scrollWidth;
+}
+
+export default hasEllipsis;
diff --git a/src/libs/hasEllipsis/index.native.js b/src/libs/hasEllipsis/index.native.js
new file mode 100644
index 000000000000..2d1ec238274a
--- /dev/null
+++ b/src/libs/hasEllipsis/index.native.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/src/libs/reportUtils.js b/src/libs/reportUtils.js
new file mode 100644
index 000000000000..538076d6c183
--- /dev/null
+++ b/src/libs/reportUtils.js
@@ -0,0 +1,17 @@
+import _ from 'underscore';
+import Str from 'expensify-common/lib/str';
+
+/**
+ * Returns the concatenated title for the PrimaryLogins of a report
+ *
+ * @param {Array} logins
+ * @returns {string}
+ */
+function getReportParticipantsTitle(logins) {
+ return _.map(logins, login => Str.removeSMSDomain(login)).join(', ');
+}
+
+export {
+ // eslint-disable-next-line import/prefer-default-export
+ getReportParticipantsTitle,
+};
diff --git a/src/pages/SearchPage.js b/src/pages/SearchPage.js
index 76a06bf34e46..08be33ce2af5 100644
--- a/src/pages/SearchPage.js
+++ b/src/pages/SearchPage.js
@@ -165,6 +165,7 @@ class SearchPage extends Component {
headerMessage={headerMessage}
hideSectionHeaders
hideAdditionalOptionStates
+ showTitleTooltip
/>
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index bd2a0fcf4fac..8304509eb351 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -2,7 +2,7 @@ import React from 'react';
import {View, Pressable} from 'react-native';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
-import Header from '../../components/Header';
+import lodashGet from 'lodash.get';
import styles from '../../styles/styles';
import ONYXKEYS from '../../ONYXKEYS';
import themeColors from '../../styles/themes/default';
@@ -14,6 +14,10 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/
import MultipleAvatars from '../../components/MultipleAvatars';
import Navigation from '../../libs/Navigation/Navigation';
import ROUTES from '../../ROUTES';
+import {getReportParticipantsTitle} from '../../libs/reportUtils';
+import OptionRowTitle from './sidebar/OptionRowTitle';
+import {getPersonalDetailsForLogins} from '../../libs/OptionsListUtils';
+import {participantPropTypes} from './sidebar/optionPropTypes';
const propTypes = {
// Toggles the navigationMenu open and closed
@@ -25,6 +29,9 @@ const propTypes = {
// Name of the report
reportName: PropTypes.string,
+ // List of primarylogins of participants of the report
+ participants: PropTypes.arrayOf(PropTypes.string),
+
// ID of the report
reportID: PropTypes.number,
@@ -32,6 +39,9 @@ const propTypes = {
isPinned: PropTypes.bool,
}),
+ // Personal details of all the users
+ personalDetails: PropTypes.arrayOf(participantPropTypes).isRequired,
+
...windowDimensionsPropTypes,
};
@@ -39,51 +49,65 @@ const defaultProps = {
report: null,
};
-const HeaderView = props => (
-
-
- {props.isSmallScreenWidth && (
-
-
-
- )}
- {props.report && props.report.reportName ? (
-
+const HeaderView = (props) => {
+ const participants = lodashGet(props.report, 'participants', []);
+ const reportOption = {
+ text: lodashGet(props.report, 'reportName', ''),
+ tooltipText: getReportParticipantsTitle(participants),
+ participantsList: getPersonalDetailsForLogins(participants, props.personalDetails),
+ };
+
+ return (
+
+
+ {props.isSmallScreenWidth && (
{
- const {participants} = props.report;
- if (participants.length === 1) {
- Navigation.navigate(ROUTES.getProfileRoute(participants[0]));
- }
- }}
+ onPress={props.onNavigationMenuButtonClicked}
+ style={[styles.LHNToggle]}
>
-
+
-
-
+ )}
+ {props.report && props.report.reportName && (
+
togglePinnedState(props.report)}
- style={[styles.touchableButtonImage, styles.mr0]}
+ onPress={() => {
+ if (participants.length === 1) {
+ Navigation.navigate(ROUTES.getProfileRoute(participants[0]));
+ }
+ }}
>
-
+
+
+
+
+
+ togglePinnedState(props.report)}
+ style={[styles.touchableButtonImage, styles.mr0]}
+ >
+
+
+
-
- ) : null}
+ )}
+
-
-);
-
+ );
+};
HeaderView.propTypes = propTypes;
HeaderView.displayName = 'HeaderView';
HeaderView.defaultProps = defaultProps;
@@ -94,5 +118,8 @@ export default compose(
report: {
key: ({reportID}) => `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
},
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS,
+ },
}),
)(HeaderView);
diff --git a/src/pages/home/report/ReportActionItemFragment.js b/src/pages/home/report/ReportActionItemFragment.js
index 16abf1eaa504..a94cfebdb49c 100644
--- a/src/pages/home/report/ReportActionItemFragment.js
+++ b/src/pages/home/report/ReportActionItemFragment.js
@@ -7,11 +7,15 @@ import styles from '../../../styles/styles';
import themeColors from '../../../styles/themes/default';
import RenderHTML from '../../../components/RenderHTML';
import Text from '../../../components/Text';
+import Tooltip from '../../../components/Tooltip';
const propTypes = {
// The message fragment needing to be displayed
fragment: ReportActionFragmentPropTypes.isRequired,
+ // Text to be shown for tooltip When Fragment is report Actor
+ tooltipText: PropTypes.string,
+
// Is this fragment an attachment?
isAttachment: PropTypes.bool,
@@ -22,11 +26,12 @@ const propTypes = {
const defaultProps = {
isAttachment: false,
loading: false,
+ tooltipText: '',
};
class ReportActionItemFragment extends React.PureComponent {
render() {
- const {fragment} = this.props;
+ const {fragment, tooltipText} = this.props;
switch (fragment.type) {
case 'COMMENT':
// If this is an attachment placeholder, return the placeholder component
@@ -50,12 +55,14 @@ class ReportActionItemFragment extends React.PureComponent {
);
case 'TEXT':
return (
-
- {Str.htmlDecode(fragment.text)}
-
+
+
+ {Str.htmlDecode(fragment.text)}
+
+
);
case 'LINK':
return LINK;
diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js
index ccd9ea1df280..bf40be5e8cda 100644
--- a/src/pages/home/report/ReportActionItemSingle.js
+++ b/src/pages/home/report/ReportActionItemSingle.js
@@ -31,6 +31,7 @@ const ReportActionItemSingle = ({action}) => {
diff --git a/src/pages/home/sidebar/OptionRow.js b/src/pages/home/sidebar/OptionRow.js
index 641109bc45c6..c31d128f5d8d 100644
--- a/src/pages/home/sidebar/OptionRow.js
+++ b/src/pages/home/sidebar/OptionRow.js
@@ -8,12 +8,13 @@ import {
StyleSheet,
} from 'react-native';
import styles from '../../../styles/styles';
-import optionPropTypes from './optionPropTypes';
+import {optionPropTypes} from './optionPropTypes';
import Icon from '../../../components/Icon';
import {Pencil, PinCircle, Checkmark} from '../../../components/Icon/Expensicons';
import MultipleAvatars from '../../../components/MultipleAvatars';
import themeColors from '../../../styles/themes/default';
import Hoverable from '../../../components/Hoverable';
+import OptionRowTitle from './OptionRowTitle';
const propTypes = {
// Style for hovered state
@@ -40,6 +41,9 @@ const propTypes = {
// Force the text style to be the unread style
forceTextUnreadStyle: PropTypes.bool,
+
+ // Whether to show the title tooltip
+ showTitleTooltip: PropTypes.bool,
};
const defaultProps = {
@@ -48,6 +52,7 @@ const defaultProps = {
showSelectedState: false,
isSelected: false,
forceTextUnreadStyle: false,
+ showTitleTooltip: false,
};
const OptionRow = ({
@@ -59,6 +64,7 @@ const OptionRow = ({
showSelectedState,
isSelected,
forceTextUnreadStyle,
+ showTitleTooltip,
}) => {
const textStyle = optionIsFocused
? styles.sidebarLinkActiveText
@@ -105,9 +111,13 @@ const OptionRow = ({
)
}
-
- {option.text}
-
+
+
{option.alternateText ? (
containerRight ? -(tooltipX - newToolX) : 0;
+ }
+
+
+ render() {
+ const {
+ option, style, tooltipEnabled, numberOfLines,
+ } = this.props;
+
+ if (!tooltipEnabled) {
+ return {option.text};
+ }
+ return (
+
+ // Tokenization of string only support 1 numberofLines on Web
+ this.containerRef = el}
+ >
+ {_.map(option.participantsList, (participant, index) => (
+
+ this.getTooltipShiftX(index)}
+ >
+ {/* // We need to get the refs to all the names which will be used to correct
+ the horizontal position of the tooltip */}
+ this.childRefs[index] = el}>
+ {participant.displayName}
+
+
+ {index < option.participantsList.length - 1 && , }
+
+ ))}
+ {option.participantsList.length > 1 && this.state.isEllipsisActive
+ && (
+
+
+ {/* There is some Gap for real ellipsis so we are adding 4 `.` to cover */}
+ ....
+
+
+ )}
+
+ );
+ }
+}
+OptionRowTitle.propTypes = propTypes;
+OptionRowTitle.defaultProps = defaultProps;
+OptionRowTitle.displayName = 'OptionRowTitle';
+
+export default OptionRowTitle;
diff --git a/src/pages/home/sidebar/OptionRowTitle/index.native.js b/src/pages/home/sidebar/OptionRowTitle/index.native.js
new file mode 100644
index 000000000000..b863a115579a
--- /dev/null
+++ b/src/pages/home/sidebar/OptionRowTitle/index.native.js
@@ -0,0 +1,20 @@
+// As we don't have to show tooltips of the Native platform so we simply render the option title which wraps.
+import React from 'react';
+import {Text} from 'react-native';
+import {propTypes, defaultProps} from './OptionRowTitleProps';
+
+const OptionRowTitle = ({
+ style,
+ option,
+ numberOfLines,
+}) => (
+
+ {option.text}
+
+);
+
+OptionRowTitle.propTypes = propTypes;
+OptionRowTitle.defaultProps = defaultProps;
+OptionRowTitle.displayName = 'OptionRowTitle';
+
+export default OptionRowTitle;
diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js
index c4215b28e658..2f64b227b13e 100644
--- a/src/pages/home/sidebar/SidebarLinks.js
+++ b/src/pages/home/sidebar/SidebarLinks.js
@@ -18,6 +18,7 @@ import {getSidebarOptions} from '../../../libs/OptionsListUtils';
import {getDefaultAvatar} from '../../../libs/actions/PersonalDetails';
import KeyboardSpacer from '../../../components/KeyboardSpacer';
import CONST from '../../../CONST';
+import {participantPropTypes} from './optionPropTypes';
const propTypes = {
// Toggles the navigation menu open and closed
@@ -41,11 +42,7 @@ const propTypes = {
draftComments: PropTypes.objectOf(PropTypes.string),
// List of users' personal details
- personalDetails: PropTypes.objectOf(PropTypes.shape({
- login: PropTypes.string.isRequired,
- avatar: PropTypes.string.isRequired,
- displayName: PropTypes.string.isRequired,
- })),
+ personalDetails: PropTypes.objectOf(participantPropTypes),
// The personal details of the person who is logged in
myPersonalDetails: PropTypes.shape({
@@ -149,6 +146,7 @@ class SidebarLinks extends React.Component {
this.props.onLinkClick();
}}
hideSectionHeaders
+ showTitleTooltip
disableFocusOptions={this.props.isSmallScreenWidth}
/>
diff --git a/src/pages/home/sidebar/optionPropTypes.js b/src/pages/home/sidebar/optionPropTypes.js
index 2d1d8d999dc1..ec2881f790b5 100644
--- a/src/pages/home/sidebar/optionPropTypes.js
+++ b/src/pages/home/sidebar/optionPropTypes.js
@@ -1,12 +1,26 @@
import PropTypes from 'prop-types';
-export default PropTypes.shape({
+const participantPropTypes = PropTypes.shape({
+ // Primary login of participant
+ login: PropTypes.string,
+
+ // Display Name of participant
+ displayName: PropTypes.string,
+
+ // Avatar url of participant
+ avatar: PropTypes.string,
+});
+
+const optionPropTypes = PropTypes.shape({
// The full name of the user if available, otherwise the login (email/phone number) of the user
text: PropTypes.string.isRequired,
// Subtitle to show under report displayName, mostly lastMessageText of the report
alternateText: PropTypes.string.isRequired,
+ // List of participants of the report
+ participantsList: PropTypes.arrayOf(participantPropTypes).isRequired,
+
// The array URLs of the person's avatar
icon: PropTypes.arrayOf(PropTypes.string),
@@ -15,4 +29,12 @@ export default PropTypes.shape({
// The option key provided to FlatList keyExtractor
keyForList: PropTypes.string,
+
+ // Text to show for tooltip
+ tooltipText: PropTypes.string,
});
+
+export {
+ participantPropTypes,
+ optionPropTypes,
+};
diff --git a/src/styles/getTooltipStyles.js b/src/styles/getTooltipStyles.js
index 29b6c3c02c97..21699e5428d1 100644
--- a/src/styles/getTooltipStyles.js
+++ b/src/styles/getTooltipStyles.js
@@ -111,19 +111,24 @@ export default function getTooltipStyles(
return {
animationStyle: {
+ // remember Transform causes a new Local cordinate system
+ // https://drafts.csswg.org/css-transforms-1/#transform-rendering
+ // so Position fixed children will be relative to this new Local cordinate system
transform: [{
scale: currentSize,
}],
},
tooltipWrapperStyle: {
- position: 'absolute',
+ position: 'fixed',
backgroundColor: themeColors.heading,
borderRadius: variables.componentBorderRadiusSmall,
...tooltipVerticalPadding,
...spacing.ph2,
+ zIndex: variables.tooltipzIndex,
- // Because it uses absolute positioning, the top-left corner of the tooltip is aligned
- // with the top-left corner of the wrapped component by default.
+ // Because it uses fixed positioning, the top-left corner of the tooltip is aligned
+ // with the top-left corner of the window by default.
+ // we will use yOffset to position the tooltip relative to the Wrapped Component
// So we need to shift the tooltip vertically and horizontally to position it correctly.
//
// First, we'll position it vertically.
@@ -132,12 +137,13 @@ export default function getTooltipStyles(
top: shouldShowBelow
// We need to shift the tooltip down below the component. So shift the tooltip down (+) by...
- ? componentHeight + POINTER_HEIGHT + manualShiftVertical
+ ? yOffset + componentHeight + POINTER_HEIGHT + manualShiftVertical
// We need to shift the tooltip up above the component. So shift the tooltip up (-) by...
- : -(tooltipHeight + POINTER_HEIGHT) + manualShiftVertical,
+ : (yOffset - (tooltipHeight + POINTER_HEIGHT)) + manualShiftVertical,
// Next, we'll position it horizontally.
+ // we will use xOffset to position the tooltip relative to the Wrapped Component
// To shift the tooltip right, we'll give `left` a positive value.
// To shift the tooltip left, we'll give `left` a negative value.
//
@@ -148,7 +154,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.
- left: ((componentWidth / 2) - (tooltipWidth / 2)) + horizontalShift + manualShiftHorizontal,
+ left: xOffset + ((componentWidth / 2) - (tooltipWidth / 2)) + horizontalShift + manualShiftHorizontal,
},
tooltipTextStyle: {
color: themeColors.textReversed,
@@ -156,14 +162,13 @@ export default function getTooltipStyles(
fontSize: tooltipFontSize,
},
pointerWrapperStyle: {
- position: 'absolute',
+ position: 'fixed',
-
- // By default, the pointer's top-left will align with the top-left of the wrapped component.
+ // By default, the pointer's top-left will align with the top-left of the wrapped tooltip.
//
// To align it vertically, we'll:
//
- // Shift the pointer up (-) by its height, so that the bottom of the pointer lines up
+ // Shift the pointer up (-) by component's height, so that the bottom of the pointer lines up
// with the top of the wrapped component.
//
// OR if it should show below:
@@ -172,14 +177,16 @@ export default function getTooltipStyles(
// so that the top of the pointer aligns with the bottom of the component.
//
// Always add the manual vertical shift passed in as a parameter.
- top: shouldShowBelow ? componentHeight + manualShiftVertical : manualShiftVertical - POINTER_HEIGHT,
+ top: shouldShowBelow ? manualShiftVertical - POINTER_HEIGHT : tooltipHeight + manualShiftVertical,
// To align it horizontally, we'll:
- // 1) Shift the pointer to the right (+) by the half the component's width,
- // so the left edge of the pointer lines up with the component's center.
+ // 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.
// 2) To the left (-) by half the pointer's width,
- // so the pointer's center lines up with the component's center.
- left: (componentWidth / 2) - (POINTER_WIDTH / 2),
+ // 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
+ // horizontalShift which is added to adjust it into the Window
+ left: -horizontalShift + ((tooltipWidth / 2) - (POINTER_WIDTH / 2)),
},
pointerStyle: {
width: 0,
diff --git a/src/styles/styles.js b/src/styles/styles.js
index 1f44256c7858..955fd6a852cc 100644
--- a/src/styles/styles.js
+++ b/src/styles/styles.js
@@ -559,6 +559,17 @@ const styles = {
...whiteSpace.noWrap,
},
+ optionDisplayNameTooltipWrapper: {
+ position: 'relative',
+ },
+
+ optionDisplayNameTooltipEllipsis: {
+ position: 'absolute',
+ opacity: 0,
+ right: 0,
+ bottom: 0,
+ },
+
optionAlternateText: {
color: themeColors.textSupporting,
fontFamily: fontFamily.GTA,
diff --git a/src/styles/utilities/display.js b/src/styles/utilities/display.js
index 275ac345883e..a16a62694af8 100644
--- a/src/styles/utilities/display.js
+++ b/src/styles/utilities/display.js
@@ -16,4 +16,8 @@ export default {
dNone: {
display: 'none',
},
+
+ dInline: {
+ display: 'inline',
+ },
};
diff --git a/src/styles/variables.js b/src/styles/variables.js
index d4b5ecc592a3..6463935aa836 100644
--- a/src/styles/variables.js
+++ b/src/styles/variables.js
@@ -19,4 +19,5 @@ export default {
safeInsertPercentage: 0.7,
sideBarWidth: 375,
pdfPageMaxWidth: 992,
+ tooltipzIndex: 10050,
};