Skip to content

Commit ad1e244

Browse files
authored
Merge pull request #15671 from margelo/hanno/feat-tolltip-reaction-senders
[web/desktop] feat: tooltip reaction senders
2 parents d59bc07 + 03d4169 commit ad1e244

File tree

9 files changed

+165
-22
lines changed

9 files changed

+165
-22
lines changed

src/components/Reactions/EmojiReactionBubble.js

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ import withCurrentUserPersonalDetails, {
99
withCurrentUserPersonalDetailsPropTypes,
1010
} from '../withCurrentUserPersonalDetails';
1111
import * as Report from '../../libs/actions/Report';
12+
import Tooltip from '../Tooltip';
13+
import ReactionTooltipContent from './ReactionTooltipContent';
1214

1315
const propTypes = {
16+
emojiName: PropTypes.string.isRequired,
17+
1418
/**
1519
* The emoji codes to display in the bubble.
1620
*/
@@ -35,7 +39,7 @@ const propTypes = {
3539
/**
3640
* The account ids of the users who reacted.
3741
*/
38-
reactionUsers: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
42+
reactionUsers: PropTypes.arrayOf(PropTypes.string),
3943

4044
/**
4145
* The default size of the reaction bubble is defined
@@ -59,31 +63,41 @@ const defaultProps = {
5963
const EmojiReactionBubble = (props) => {
6064
const hasUserReacted = Report.hasAccountIDReacted(props.currentUserPersonalDetails.accountID, props.reactionUsers);
6165
return (
62-
<Pressable
63-
style={({hovered, pressed}) => [
64-
styles.emojiReactionBubble,
65-
StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, hasUserReacted, props.sizeScale),
66-
]}
67-
onPress={props.onPress}
68-
onLongPress={props.onReactionListOpen}
66+
<Tooltip
67+
renderTooltipContent={() => (
68+
<ReactionTooltipContent
69+
emojiName={props.emojiName}
70+
emojiCodes={props.emojiCodes}
71+
accountIDs={props.reactionUsers}
72+
/>
73+
)}
6974
>
70-
<Text style={[
71-
styles.emojiReactionText,
72-
StyleUtils.getEmojiReactionTextStyle(props.sizeScale),
73-
]}
75+
<Pressable
76+
style={({hovered, pressed}) => [
77+
styles.emojiReactionBubble,
78+
StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, hasUserReacted, props.sizeScale),
79+
]}
80+
onPress={props.onPress}
81+
onLongPress={props.onReactionListOpen}
7482
>
75-
{props.emojiCodes.join('')}
76-
</Text>
77-
{props.count > 0 && (
83+
<Text style={[
84+
styles.emojiReactionText,
85+
StyleUtils.getEmojiReactionTextStyle(props.sizeScale),
86+
]}
87+
>
88+
{props.emojiCodes.join('')}
89+
</Text>
90+
{props.count > 0 && (
7891
<Text style={[
7992
styles.reactionCounterText,
8093
StyleUtils.getEmojiReactionCounterTextStyle(hasUserReacted, props.sizeScale),
8194
]}
8295
>
8396
{props.count}
8497
</Text>
85-
)}
86-
</Pressable>
98+
)}
99+
</Pressable>
100+
</Tooltip>
87101
);
88102
};
89103

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import PropTypes from 'prop-types';
4+
import _ from 'underscore';
5+
import styles from '../../styles/styles';
6+
import {withPersonalDetails} from '../OnyxProvider';
7+
import * as PersonalDetailsUtils from '../../libs/PersonalDetailsUtils';
8+
import Text from '../Text';
9+
import withCurrentUserPersonalDetails, {
10+
withCurrentUserPersonalDetailsPropTypes,
11+
} from '../withCurrentUserPersonalDetails';
12+
import compose from '../../libs/compose';
13+
import withLocalize from '../withLocalize';
14+
15+
const propTypes = {
16+
/**
17+
* A list of emoji codes to display in the tooltip.
18+
*/
19+
emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired,
20+
21+
/**
22+
* The name of the emoji to display in the tooltip.
23+
*/
24+
emojiName: PropTypes.string.isRequired,
25+
26+
/**
27+
* A list of account IDs to display in the tooltip.
28+
*/
29+
accountIDs: PropTypes.arrayOf(PropTypes.string).isRequired,
30+
31+
...withCurrentUserPersonalDetailsPropTypes,
32+
};
33+
34+
const ReactionTooltipContent = (props) => {
35+
const users = PersonalDetailsUtils.getPersonalDetailsByIDs(props.accountIDs, true);
36+
const namesString = _.filter(_.map(users, user => user && user.displayName), n => n).join(', ');
37+
38+
return (
39+
<View style={[styles.alignItemsCenter, styles.ph2]}>
40+
<View style={styles.flexRow}>
41+
{_.map(props.emojiCodes, emojiCode => (
42+
<Text
43+
key={emojiCode}
44+
style={styles.reactionEmojiTitle}
45+
>
46+
{emojiCode}
47+
</Text>
48+
))}
49+
</View>
50+
51+
<Text style={[
52+
styles.mt1,
53+
styles.textMicroBold,
54+
styles.textReactionSenders,
55+
]}
56+
>
57+
{namesString}
58+
</Text>
59+
60+
<Text style={[
61+
styles.textMicro,
62+
styles.fontColorReactionLabel,
63+
]}
64+
>
65+
{`reacted with :${props.emojiName}:`}
66+
</Text>
67+
</View>
68+
);
69+
};
70+
71+
ReactionTooltipContent.propTypes = propTypes;
72+
ReactionTooltipContent.defaultProps = withCurrentUserPersonalDetails;
73+
ReactionTooltipContent.displayName = 'ReactionTooltipContent';
74+
export default React.memo(compose(
75+
withPersonalDetails(),
76+
withLocalize,
77+
)(ReactionTooltipContent));

src/components/Reactions/ReportActionItemReactions.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,7 @@ const ReportActionItemReactions = (props) => {
5959
<View style={[styles.flexRow, styles.flexWrap]}>
6060
{_.map(reactionsWithCount, (reaction) => {
6161
const reactionCount = reaction.users.length;
62-
if (reactionCount === 0) {
63-
return null;
64-
}
65-
66-
const reactionUsers = _.map(reaction.users, sender => sender.accountID);
62+
const reactionUsers = _.map(reaction.users, sender => sender.accountID.toString());
6763
const emoji = _.find(emojis, e => e.name === reaction.emoji);
6864
const emojiCodes = getUniqueEmojiCodes(emoji, reaction.users);
6965

src/languages/en.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export default {
115115
enterManually: 'Enter it manually',
116116
message: 'Message ',
117117
leaveRoom: 'Leave room',
118+
you: 'You',
118119
your: 'your',
119120
conciergeHelp: 'Please reach out to Concierge for help.',
120121
maxParticipantsReached: ({count}) => `You've selected the maximum number (${count}) of participants.`,

src/languages/es.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ export default {
114114
enterManually: 'Ingresar manualmente',
115115
message: 'Chatear con ',
116116
leaveRoom: 'Salir de la sala de chat',
117+
you: 'Tú',
117118
your: 'tu',
118119
conciergeHelp: 'Por favor contacta con Concierge para obtener ayuda.',
119120
maxParticipantsReached: ({count}) => `Has seleccionado el número máximo (${count}) de participantes.`,

src/libs/PersonalDetailsUtils.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Onyx from 'react-native-onyx';
2+
import _ from 'underscore';
3+
import ONYXKEYS from '../ONYXKEYS';
4+
import * as Report from './actions/Report';
5+
import * as Localize from './Localize';
6+
7+
let personalDetails = [];
8+
Onyx.connect({
9+
key: ONYXKEYS.PERSONAL_DETAILS,
10+
callback: val => personalDetails = _.values(val),
11+
});
12+
13+
/**
14+
* Given a list of account IDs (as string) it will return an array of personal details objects.
15+
* @param {Array<string>} accountIDs - Array of accountIDs
16+
* @param {boolean} shouldChangeUserDisplayName - It will replace the current user's personal detail object's displayName with 'You'.
17+
* @returns {Array} - Array of personal detail objects
18+
*/
19+
function getPersonalDetailsByIDs(accountIDs, shouldChangeUserDisplayName = false) {
20+
const result = [];
21+
const currentAccountID = Report.getCurrentUserAccountID();
22+
_.each(personalDetails, (detail) => {
23+
for (let i = 0; i < accountIDs.length; i++) {
24+
if (detail.accountID === accountIDs[i]) {
25+
if (shouldChangeUserDisplayName && currentAccountID.toString() === detail.accountID) {
26+
result[i] = {
27+
...detail,
28+
displayName: Localize.translateLocal('common.you'),
29+
};
30+
} else {
31+
result[i] = detail;
32+
}
33+
break;
34+
}
35+
}
36+
});
37+
return result;
38+
}
39+
40+
export {
41+
// eslint-disable-next-line import/prefer-default-export
42+
getPersonalDetailsByIDs,
43+
};

src/libs/actions/Report.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,10 @@ function toggleEmojiReaction(reportID, reportAction, emoji, paramSkinTone = pref
13661366
return addEmojiReaction(reportID, reportAction, emoji, skinTone);
13671367
}
13681368

1369+
function getCurrentUserAccountID() {
1370+
return currentUserAccountID;
1371+
}
1372+
13691373
export {
13701374
addComment,
13711375
addAttachment,
@@ -1401,4 +1405,5 @@ export {
14011405
removeEmojiReaction,
14021406
toggleEmojiReaction,
14031407
hasAccountIDReacted,
1408+
getCurrentUserAccountID,
14041409
};

src/styles/styles.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2996,6 +2996,11 @@ const styles = {
29962996
lineHeight: variables.iconSizeXLarge,
29972997
},
29982998

2999+
textReactionSenders: {
3000+
color: themeColors.dark,
3001+
...wordBreak.breakWord,
3002+
},
3003+
29993004
quickReactionsContainer: {
30003005
gap: 12,
30013006
flexDirection: 'row',

src/styles/themes/default.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const darkTheme = {
2929
successPressed: colors.greenPressed,
3030
transparent: colors.transparent,
3131
midtone: colors.green700,
32+
dark: colors.midnight,
3233

3334
// Additional keys
3435
overlay: colors.greenHighlightBackground,

0 commit comments

Comments
 (0)