Skip to content

Commit f4ffc7c

Browse files
Merge pull request #15210 from margelo/hanno/feat-add-remove-reactions
feat: add/remove reaction
2 parents 28bda4d + 9e3596f commit f4ffc7c

30 files changed

+1327
-62
lines changed

assets/images/add-reaction.svg

Lines changed: 5 additions & 0 deletions
Loading

src/CONST.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,32 @@ const CONST = {
973973
MAKE_REQUEST_WITH_SIDE_EFFECTS: 'makeRequestWithSideEffects',
974974
},
975975

976+
QUICK_REACTIONS: [
977+
{
978+
name: '+1',
979+
code: '👍',
980+
types: [
981+
'👍🏿',
982+
'👍🏾',
983+
'👍🏽',
984+
'👍🏼',
985+
'👍🏻',
986+
],
987+
},
988+
{
989+
name: 'heart',
990+
code: '❤️',
991+
},
992+
{
993+
name: 'joy',
994+
code: '😂',
995+
},
996+
{
997+
name: 'fire',
998+
code: '🔥',
999+
},
1000+
],
1001+
9761002
TFA_CODE_LENGTH: 6,
9771003
CHAT_ATTACHMENT_TOKEN_KEY: 'X-Chat-Attachment-Token',
9781004

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {Pressable, View} from 'react-native';
2+
import React from 'react';
3+
import PropTypes from 'prop-types';
4+
import _ from 'underscore';
5+
import styles from '../styles/styles';
6+
import * as StyleUtils from '../styles/StyleUtils';
7+
import getButtonState from '../libs/getButtonState';
8+
import variables from '../styles/variables';
9+
import Tooltip from './Tooltip';
10+
11+
const propTypes = {
12+
/**
13+
* Text to display when hovering the menu item
14+
*/
15+
tooltipText: PropTypes.string.isRequired,
16+
17+
/**
18+
* Callback to fire on press
19+
*/
20+
onPress: PropTypes.func.isRequired,
21+
22+
/**
23+
* The children to display within the menu item
24+
*/
25+
children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]).isRequired,
26+
27+
/**
28+
* Whether the button should be in the active state
29+
*/
30+
isDelayButtonStateComplete: PropTypes.bool,
31+
32+
/**
33+
* A ref to forward to the Pressable
34+
*/
35+
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
36+
};
37+
38+
const defaultProps = {
39+
isDelayButtonStateComplete: true,
40+
innerRef: () => {},
41+
};
42+
43+
/**
44+
* Component that renders a mini context menu item with a
45+
* pressable. Also renders a tooltip when hovering the item.
46+
* @param {Object} props
47+
* @returns {JSX.Element}
48+
*/
49+
const BaseMiniContextMenuItem = props => (
50+
<Tooltip text={props.tooltipText}>
51+
<Pressable
52+
ref={props.innerRef}
53+
focusable
54+
onPress={props.onPress}
55+
accessibilityLabel={props.tooltipText}
56+
style={
57+
({hovered, pressed}) => [
58+
styles.reportActionContextMenuMiniButton,
59+
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, props.isDelayButtonStateComplete)),
60+
]
61+
}
62+
>
63+
{pressableState => (
64+
<View style={[StyleUtils.getWidthAndHeightStyle(variables.iconSizeNormal), styles.alignItemsCenter, styles.justifyContentCenter]}>
65+
{_.isFunction(props.children) ? props.children(pressableState) : props.children}
66+
</View>
67+
)}
68+
</Pressable>
69+
</Tooltip>
70+
);
71+
72+
BaseMiniContextMenuItem.propTypes = propTypes;
73+
BaseMiniContextMenuItem.defaultProps = defaultProps;
74+
BaseMiniContextMenuItem.displayName = 'BaseMiniContextMenuItem';
75+
76+
// eslint-disable-next-line react/jsx-props-no-spreading
77+
export default React.forwardRef((props, ref) => <BaseMiniContextMenuItem {...props} innerRef={ref} />);

src/components/ContextMenuItem.js

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import React, {Component} from 'react';
22
import PropTypes from 'prop-types';
3-
import {Pressable, View} from 'react-native';
43
import MenuItem from './MenuItem';
5-
import Tooltip from './Tooltip';
64
import Icon from './Icon';
75
import styles from '../styles/styles';
86
import * as StyleUtils from '../styles/StyleUtils';
97
import getButtonState from '../libs/getButtonState';
108
import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState';
11-
import variables from '../styles/variables';
9+
import BaseMiniContextMenuItem from './BaseMiniContextMenuItem';
1210

1311
const propTypes = {
1412
/** Icon Component */
@@ -75,29 +73,19 @@ class ContextMenuItem extends Component {
7573
return (
7674
this.props.isMini
7775
? (
78-
<Tooltip text={text}>
79-
<Pressable
80-
focusable
81-
accessibilityLabel={text}
82-
onPress={this.triggerPressAndUpdateSuccess}
83-
style={
84-
({hovered, pressed}) => [
85-
styles.reportActionContextMenuMiniButton,
86-
StyleUtils.getButtonBackgroundColorStyle(getButtonState(hovered, pressed, this.props.isDelayButtonStateComplete)),
87-
]
88-
}
89-
>
90-
{({hovered, pressed}) => (
91-
<View style={[StyleUtils.getWidthAndHeightStyle(variables.iconSizeNormal), styles.alignItemsCenter, styles.justifyContentCenter]}>
92-
<Icon
93-
small
94-
src={icon}
95-
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, this.props.isDelayButtonStateComplete))}
96-
/>
97-
</View>
98-
)}
99-
</Pressable>
100-
</Tooltip>
76+
<BaseMiniContextMenuItem
77+
tooltipText={text}
78+
onPress={this.triggerPressAndUpdateSuccess}
79+
isDelayButtonStateComplete={this.props.isDelayButtonStateComplete}
80+
>
81+
{({hovered, pressed}) => (
82+
<Icon
83+
small
84+
src={icon}
85+
fill={StyleUtils.getIconFillColor(getButtonState(hovered, pressed, this.props.isDelayButtonStateComplete))}
86+
/>
87+
)}
88+
</BaseMiniContextMenuItem>
10189
) : (
10290
<MenuItem
10391
title={text}

src/components/EmojiPicker/EmojiPicker.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import EmojiPickerMenu from './EmojiPickerMenu';
55
import CONST from '../../CONST';
66
import PopoverWithMeasuredContent from '../PopoverWithMeasuredContent';
77

8+
const DEFAULT_ANCHOR_ORIGIN = {
9+
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
10+
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
11+
};
12+
813
class EmojiPicker extends React.Component {
914
constructor(props) {
1015
super(props);
@@ -27,6 +32,8 @@ class EmojiPicker extends React.Component {
2732
horizontal: 0,
2833
vertical: 0,
2934
},
35+
36+
emojiPopoverAnchorOrigin: DEFAULT_ANCHOR_ORIGIN,
3037
};
3138
}
3239

@@ -54,8 +61,9 @@ class EmojiPicker extends React.Component {
5461
* Callback for the emoji picker to add whatever emoji is chosen into the main input
5562
*
5663
* @param {String} emoji
64+
* @param {Object} emojiObject
5765
*/
58-
selectEmoji(emoji) {
66+
selectEmoji(emoji, emojiObject) {
5967
// Prevent fast click / multiple emoji selection;
6068
// The first click will hide the emoji picker by calling the hideEmojiPicker() function
6169
// and in that function the emojiPopoverAnchor prop to will be set to null (synchronously)
@@ -66,7 +74,7 @@ class EmojiPicker extends React.Component {
6674

6775
this.hideEmojiPicker();
6876
if (_.isFunction(this.onEmojiSelected)) {
69-
this.onEmojiSelected(emoji);
77+
this.onEmojiSelected(emoji, emojiObject);
7078
}
7179
}
7280

@@ -81,8 +89,10 @@ class EmojiPicker extends React.Component {
8189
* @param {Function} [onModalHide=() => {}] - Run a callback when Modal hides.
8290
* @param {Function} [onEmojiSelected=() => {}] - Run a callback when Emoji selected.
8391
* @param {Element} emojiPopoverAnchor - Element to which Popover is anchored
92+
* @param {Object} [anchorOrigin=DEFAULT_ANCHOR_ORIGIN] - Anchor origin for Popover
93+
* @param {Function} [onWillShow=() => {}] - Run a callback when Popover will show
8494
*/
85-
showEmojiPicker(onModalHide, onEmojiSelected, emojiPopoverAnchor) {
95+
showEmojiPicker(onModalHide, onEmojiSelected, emojiPopoverAnchor, anchorOrigin, onWillShow = () => {}) {
8696
this.onModalHide = onModalHide;
8797
this.onEmojiSelected = onEmojiSelected;
8898
this.emojiPopoverAnchor = emojiPopoverAnchor;
@@ -93,7 +103,8 @@ class EmojiPicker extends React.Component {
93103
}
94104

95105
this.measureEmojiPopoverAnchorPosition().then((emojiPopoverAnchorPosition) => {
96-
this.setState({isEmojiPickerVisible: true, emojiPopoverAnchorPosition});
106+
onWillShow();
107+
this.setState({isEmojiPickerVisible: true, emojiPopoverAnchorPosition, emojiPopoverAnchorOrigin: anchorOrigin || DEFAULT_ANCHOR_ORIGIN});
97108
});
98109
}
99110

@@ -157,10 +168,7 @@ class EmojiPicker extends React.Component {
157168
width: CONST.EMOJI_PICKER_SIZE.WIDTH,
158169
height: CONST.EMOJI_PICKER_SIZE.HEIGHT,
159170
}}
160-
anchorOrigin={{
161-
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT,
162-
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
163-
}}
171+
anchorOrigin={this.state.emojiPopoverAnchorOrigin}
164172
measureContent={this.measureContent}
165173
>
166174
<EmojiPickerMenu

src/components/EmojiPicker/EmojiPickerMenu/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ class EmojiPickerMenu extends Component {
223223
*/
224224
addToFrequentAndSelectEmoji(emoji, emojiObject) {
225225
EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject);
226-
this.props.onEmojiSelected(emoji);
226+
this.props.onEmojiSelected(emoji, emojiObject);
227227
}
228228

229229
/**

src/components/EmojiPicker/EmojiPickerMenu/index.native.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class EmojiPickerMenu extends Component {
7777
*/
7878
addToFrequentAndSelectEmoji(emoji, emojiObject) {
7979
EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject);
80-
this.props.onEmojiSelected(emoji);
80+
this.props.onEmojiSelected(emoji, emojiObject);
8181
}
8282

8383
/**

src/components/Icon/Expensicons.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,11 @@ import Facebook from '../../../assets/images/social-facebook.svg';
106106
import Podcast from '../../../assets/images/social-podcast.svg';
107107
import Linkedin from '../../../assets/images/social-linkedin.svg';
108108
import Instagram from '../../../assets/images/social-instagram.svg';
109+
import AddReaction from '../../../assets/images/add-reaction.svg';
109110

110111
export {
111112
ActiveRoomAvatar,
113+
AddReaction,
112114
AdminRoomAvatar,
113115
Android,
114116
AnnounceRoomAvatar,

0 commit comments

Comments
 (0)