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

Change Display Name to Functional Component #20953

Merged
merged 24 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
5b427bb
Change Display Name to Functional Component
srikarparsi Jun 16, 2023
82e25ab
fix linter issues
srikarparsi Jul 6, 2023
02f8551
wrap getTooltipShiftX in useCallback
srikarparsi Jul 6, 2023
b0c6a0a
don't unnecessarily re-render getTooltipShiftX function
srikarparsi Jul 6, 2023
819e2bd
empty dependency array
srikarparsi Jul 6, 2023
bf44327
prettier
srikarparsi Jul 6, 2023
2fb8276
Merge branch 'main' into srikar-refactorDisplayNames
srikarparsi Jul 6, 2023
d232719
wrap childRefs in useRef and fix imports
srikarparsi Jul 6, 2023
b614760
remove use of lambda function for getTooltipShiftX
srikarparsi Jul 12, 2023
6438951
moved UserTooltipItem to a different component
srikarparsi Jul 17, 2023
c8935db
merge main
srikarparsi Jul 31, 2023
e09ac7f
display names without tooltip
srikarparsi Jul 31, 2023
964ef0c
display names with tooltip
srikarparsi Jul 31, 2023
544236d
add getTooltipShiftXBridge
srikarparsi Jul 31, 2023
531ab8d
lint and prettier
srikarparsi Jul 31, 2023
9d29eef
not sure why prettier didn't work the first time
srikarparsi Jul 31, 2023
30806b8
remove unnecessary if condition and destructure props
srikarparsi Aug 1, 2023
36e042b
incorporate changes from pr 23990
srikarparsi Aug 2, 2023
0385a63
merge conflict
srikarparsi Aug 2, 2023
2f91a58
add missed import
srikarparsi Aug 2, 2023
562f809
prettier
srikarparsi Aug 2, 2023
310b932
Merge branch 'main' into srikar-refactorDisplayNames
srikarparsi Aug 17, 2023
3d44ae0
remove containerRef dependency and wrap toolTipShift in useCallback
srikarparsi Aug 17, 2023
040cf96
prettier
srikarparsi Aug 17, 2023
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
79 changes: 79 additions & 0 deletions src/components/DisplayNames/DisplayNamesTooltipItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import PropTypes from 'prop-types';
import React, {useCallback} from 'react';
import styles from '../../styles/styles';
import Text from '../Text';
import UserDetailsTooltip from '../UserDetailsTooltip';

const propTypes = {
index: PropTypes.number,

/** The full title of the DisplayNames component (not split up) */
getTooltipShiftX: PropTypes.func,

/** The Account ID for the tooltip */
accountID: PropTypes.number,

/** The name to display in bold */
displayName: PropTypes.string,

/** The login for the tooltip fallback */
login: PropTypes.string,

/** The avatar for the tooltip fallback */
avatar: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),

/** Arbitrary styles of the displayName text */
// eslint-disable-next-line react/forbid-prop-types
textStyles: PropTypes.arrayOf(PropTypes.object),

/** Refs to all the names which will be used to correct the horizontal position of the tooltip */
childRefs: PropTypes.shape({
// eslint-disable-next-line react/forbid-prop-types
current: PropTypes.arrayOf(PropTypes.object),
}),
};

const defaultProps = {
index: 0,
getTooltipShiftX: () => {},
accountID: 0,
displayName: '',
login: '',
avatar: '',
textStyles: [],
childRefs: {current: []},
};

function DisplayNamesTooltipItem({index, getTooltipShiftX, accountID, avatar, login, displayName, textStyles, childRefs}) {
const tooltipIndexBridge = useCallback(() => getTooltipShiftX(index), [getTooltipShiftX, index]);

return (
<UserDetailsTooltip
key={index}
accountID={accountID}
fallbackUserDetails={{
avatar,
login,
displayName,
}}
shiftHorizontal={tooltipIndexBridge}
>
{/* We need to get the refs to all the names which will be used to correct the horizontal position of the tooltip */}
<Text
eslint-disable-next-line
no-param-reassign
// eslint-disable-next-line no-param-reassign
ref={(el) => (childRefs.current[index] = el)}
style={[...textStyles, styles.pre]}
>
{displayName}
</Text>
</UserDetailsTooltip>
);
}

DisplayNamesTooltipItem.propTypes = propTypes;
DisplayNamesTooltipItem.defaultProps = defaultProps;
DisplayNamesTooltipItem.displayName = 'DisplayNamesTooltipItem';

export default DisplayNamesTooltipItem;
106 changes: 106 additions & 0 deletions src/components/DisplayNames/DisplayNamesWithTooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, {Fragment, useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import styles from '../../styles/styles';
import Text from '../Text';
import Tooltip from '../Tooltip';
import DisplayNamesTooltipItem from './DisplayNamesTooltipItem';
import {defaultProps, propTypes} from './displayNamesPropTypes';

function DisplayNamesWithToolTip(props) {
const containerRef = useRef(null);
const childRefs = useRef([]);
const [isEllipsisActive, setIsEllipsisActive] = useState(false);
const [containerLayout, setContainerLayout] = useState(null);

useEffect(() => {
setIsEllipsisActive(
containerRef.current && containerRef.current.offsetWidth && containerRef.current.scrollWidth && containerRef.current.offsetWidth < containerRef.current.scrollWidth,
);
}, [containerRef]);
srikarparsi marked this conversation as resolved.
Show resolved Hide resolved

/**
* We may need to shift the Tooltip horizontally as some of the inline text wraps well with ellipsis,
* but their container node overflows the parent view which causes the tooltip to be misplaced.
*
* So we shift it by calculating it as follows:
* 1. We get the container layout and take the Child inline text node.
* 2. Now we get the tooltip original position.
* 3. If inline node's right edge is overflowing the container's right edge, we set the tooltip to the center
* of the distance between the left edge of the inline node and right edge of the container.
* @param {Number} index Used to get the Ref to the node at the current index
* @returns {Number} Distance to shift the tooltip horizontally
*/
const getTooltipShiftX = (index) => {
srikarparsi marked this conversation as resolved.
Show resolved Hide resolved
// Only shift the tooltip in case the containerLayout or Refs to the text node are available
if (!containerLayout || !childRefs.current[index]) {
return;
}
const {width: containerWidth, left: containerLeft} = containerLayout;

// We have to return the value as Number so we can't use `measureWindow` which takes a callback
const {width: textNodeWidth, left: textNodeLeft} = childRefs.current[index].getBoundingClientRect();
const tooltipX = textNodeWidth / 2 + textNodeLeft;
const containerRight = containerWidth + containerLeft;
const textNodeRight = textNodeWidth + textNodeLeft;
const newToolX = textNodeLeft + (containerRight - textNodeLeft) / 2;

// When text right end is beyond the Container right end
return textNodeRight > containerRight ? -(tooltipX - newToolX) : 0;
};

if (!props.tooltipEnabled) {
// No need for any complex text-splitting, just return a simple Text component
return (
<Text
style={[...props.textStyles, props.numberOfLines === 1 ? styles.pre : styles.preWrap]}
numberOfLines={props.numberOfLines}
>
{props.fullTitle}
</Text>
);
}
srikarparsi marked this conversation as resolved.
Show resolved Hide resolved

return (
// Tokenization of string only support prop numberOfLines on Web
<Text
style={[...props.textStyles, styles.pRelative]}
onLayout={({nativeEvent}) => setContainerLayout(nativeEvent.layout)}
numberOfLines={props.numberOfLines || undefined}
ref={(el) => (containerRef.current = el)}
>
{props.shouldUseFullTitle
? props.fullTitle
: _.map(props.displayNamesWithTooltips, ({displayName, accountID, avatar, login}, index) => (
<Fragment key={index}>
<DisplayNamesTooltipItem
index={index}
getTooltipShiftX={getTooltipShiftX}
accountID={accountID}
displayName={displayName}
login={login}
avatar={avatar}
textStyles={props.textStyles}
childRefs={childRefs}
addComma={index < props.displayNamesWithTooltips.length - 1}
/>
{index < props.displayNamesWithTooltips.length - 1 && <Text style={props.textStyles}>,&nbsp;</Text>}
</Fragment>
))}
{Boolean(isEllipsisActive) && (
<View style={styles.displayNameTooltipEllipsis}>
<Tooltip text={props.fullTitle}>
{/* There is some Gap for real ellipsis so we are adding 4 `.` to cover */}
<Text>....</Text>
</Tooltip>
</View>
)}
</Text>
);
}

DisplayNamesWithToolTip.propTypes = propTypes;
DisplayNamesWithToolTip.defaultProps = defaultProps;
DisplayNamesWithToolTip.displayName = 'DisplayNamesWithTooltip';

export default DisplayNamesWithToolTip;
38 changes: 38 additions & 0 deletions src/components/DisplayNames/DisplayNamesWithoutTooltip.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from '../../styles/styles';

const propTypes = {
/** The full title of the DisplayNames component (not split up) */
fullTitle: PropTypes.string,

/** Arbitrary styles of the displayName text */
// eslint-disable-next-line react/forbid-prop-types
textStyles: PropTypes.arrayOf(PropTypes.object),

/** Number of lines before wrapping */
numberOfLines: PropTypes.number,
};

const defaultProps = {
fullTitle: '',
textStyles: [],
numberOfLines: 1,
};

function DisplayNamesWithoutTooltip(props) {
srikarparsi marked this conversation as resolved.
Show resolved Hide resolved
return (
<Text
style={[...props.textStyles, props.numberOfLines === 1 ? styles.pre : styles.preWrap]}
numberOfLines={props.numberOfLines}
>
{props.fullTitle}
</Text>
);
}

DisplayNamesWithoutTooltip.propTypes = propTypes;
DisplayNamesWithoutTooltip.defaultProps = defaultProps;
DisplayNamesWithoutTooltip.displayName = 'DisplayNamesWithoutTooltip';

export default DisplayNamesWithoutTooltip;
136 changes: 16 additions & 120 deletions src/components/DisplayNames/index.js
Original file line number Diff line number Diff line change
@@ -1,129 +1,25 @@
import _ from 'underscore';
import React, {Fragment, PureComponent} from 'react';
import {View} from 'react-native';
import {propTypes, defaultProps} from './displayNamesPropTypes';
import styles from '../../styles/styles';
import Tooltip from '../Tooltip';
import Text from '../Text';
import UserDetailsTooltip from '../UserDetailsTooltip';

class DisplayNames extends PureComponent {
constructor(props) {
super(props);
this.containerRef = null;
this.childRefs = [];
this.state = {
isEllipsisActive: false,
};
this.setContainerLayout = this.setContainerLayout.bind(this);
this.getTooltipShiftX = this.getTooltipShiftX.bind(this);
}

componentDidMount() {
this.setState({
isEllipsisActive: this.containerRef && this.containerRef.offsetWidth && this.containerRef.scrollWidth && this.containerRef.offsetWidth < this.containerRef.scrollWidth,
});
}

/**
* Set the container layout for post calculations
*
* @param {*} {nativeEvent}
*/
setContainerLayout({nativeEvent}) {
this.containerLayout = nativeEvent.layout;
}

/**
* We may need to shift the Tooltip horizontally as some of the inline text wraps well with ellipsis,
* but their container node overflows the parent view which causes the tooltip to be misplaced.
*
* So we shift it by calculating it as follows:
* 1. We get the container layout and take the Child inline text node.
* 2. Now we get the tooltip original position.
* 3. If inline node's right edge is overflowing the container's right edge, we set the tooltip to the center
* of the distance between the left edge of the inline node and right edge of the container.
* @param {Number} index Used to get the Ref to the node at the current index
* @returns {Number} Distance to shift the tooltip horizontally
*/
getTooltipShiftX(index) {
// Only shift the tooltip in case the containerLayout or Refs to the text node are available
if (!this.containerLayout || !this.childRefs[index]) {
return;
}
const {width: containerWidth, left: containerLeft} = this.containerLayout;

// We have to return the value as Number so we can't use `measureWindow` which takes a callback
const {width: textNodeWidth, left: textNodeLeft} = this.childRefs[index].getBoundingClientRect();
const tooltipX = textNodeWidth / 2 + textNodeLeft;
const containerRight = containerWidth + containerLeft;
const textNodeRight = textNodeWidth + textNodeLeft;
const newToolX = textNodeLeft + (containerRight - textNodeLeft) / 2;

// When text right end is beyond the Container right end
return textNodeRight > containerRight ? -(tooltipX - newToolX) : 0;
}

render() {
if (!this.props.tooltipEnabled) {
// No need for any complex text-splitting, just return a simple Text component
return (
<Text
style={[...this.props.textStyles, this.props.numberOfLines === 1 ? styles.pre : styles.preWrap]}
numberOfLines={this.props.numberOfLines}
>
{this.props.fullTitle}
</Text>
);
}
import React from 'react';
import DisplayNamesWithToolTip from './DisplayNamesWithTooltip';
import DisplayNamesWithoutTooltip from './DisplayNamesWithoutTooltip';
import {defaultProps, propTypes} from './displayNamesPropTypes';

function DisplayNames(props) {
if (!props.tooltipEnabled) {
return (
// Tokenization of string only support prop numberOfLines on Web
<Text
style={[...this.props.textStyles, styles.pRelative]}
onLayout={this.setContainerLayout}
numberOfLines={this.props.numberOfLines || undefined}
ref={(el) => (this.containerRef = el)}
>
{this.props.shouldUseFullTitle
? this.props.fullTitle
: _.map(this.props.displayNamesWithTooltips, ({displayName, accountID, avatar, login}, index) => (
<Fragment key={index}>
<UserDetailsTooltip
key={index}
accountID={accountID}
fallbackUserDetails={{
avatar,
login,
displayName,
}}
shiftHorizontal={() => 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 */}
<Text
ref={(el) => (this.childRefs[index] = el)}
style={[...this.props.textStyles, styles.pre]}
>
{displayName}
</Text>
</UserDetailsTooltip>
{index < this.props.displayNamesWithTooltips.length - 1 && <Text style={this.props.textStyles}>,&nbsp;</Text>}
</Fragment>
))}
{Boolean(this.state.isEllipsisActive) && (
<View style={styles.displayNameTooltipEllipsis}>
<Tooltip text={this.props.fullTitle}>
{/* There is some Gap for real ellipsis so we are adding 4 `.` to cover */}
<Text>....</Text>
</Tooltip>
</View>
)}
</Text>
<DisplayNamesWithoutTooltip
fullTitle={props.fullTitle}
textStyles={props.textStyles}
numberOfLines={props.numberOfLines}
/>
);
}

// eslint-disable-next-line react/jsx-props-no-spreading
return <DisplayNamesWithToolTip {...props} />;
}

DisplayNames.propTypes = propTypes;
DisplayNames.defaultProps = defaultProps;
DisplayNames.displayName = 'DisplayNames';

export default DisplayNames;
Loading