Skip to content

Commit

Permalink
wip: render quick reactions in context menu
Browse files Browse the repository at this point in the history
  • Loading branch information
hannojg committed Feb 15, 2023
1 parent e31cb53 commit 80333cb
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 20 deletions.
5 changes: 5 additions & 0 deletions assets/images/add-reaction.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,11 @@ import Zoom from '../../../assets/images/zoom.svg';
import FallbackAvatar from '../../../assets/images/avatars/fallback-avatar.svg';
import FallbackWorkspaceAvatar from '../../../assets/images/avatars/fallback-workspace-avatar.svg';
import DragAndDrop from '../../../assets/images/drag-and-drop.svg';
import AddReaction from '../../../assets/images/add-reaction.svg';

export {
ActiveRoomAvatar,
AddReaction,
AdminRoomAvatar,
Android,
AnnounceRoomAvatar,
Expand Down
82 changes: 82 additions & 0 deletions src/components/Reactions/AddReactionBubble.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import {Pressable, View} from 'react-native';
import PropTypes from 'prop-types';
import Tooltip from '../Tooltip';
import styles from '../../styles/styles';
import * as StyleUtils from '../../styles/StyleUtils';
import Icon from '../Icon';
import * as Expensicons from '../Icon/Expensicons';
import Text from '../Text';
import getButtonState from '../../libs/getButtonState';
import * as EmojiPickerAction from '../../libs/actions/EmojiPickerAction';

const propTypes = {
sizeScale: PropTypes.number,
iconSizeScale: PropTypes.number,
onSelectEmoji: PropTypes.func.isRequired,
};

const defaultProps = {
sizeScale: 1,
iconSizeScale: 1,
};

const AddReactionBubble = (props) => {
const ref = React.createRef();

const onPress = () => {
EmojiPickerAction.showEmojiPicker(() => {}, props.onSelectEmoji, ref.current);
};

return (
<Tooltip text="Add Reaction…">
<Pressable
ref={ref}
style={({
hovered,
pressed,
}) => [
styles.emojiReactionBubble,
StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, props.sizeScale),
]}
onPress={onPress}
>
{({
hovered,
pressed,
}) => (
<>
{/* This text will make the view have the same size as a regular
emoji reaction. We make the text invisible and put the
icon on top of it. */}
<Text style={[
styles.emojiReactionText,
styles.opacity0,
StyleUtils.getEmojiReactionTextStyle(props.sizeScale),
]}
>
aw
</Text>
<View style={styles.pAbsolute}>
<Icon
src={Expensicons.AddReaction}
width={16 * props.iconSizeScale}
height={16 * props.iconSizeScale}
fill={StyleUtils.getIconFillColor(
getButtonState(hovered, pressed),
)}
/>
</View>
</>
)}
</Pressable>

</Tooltip>
);
};

AddReactionBubble.propTypes = propTypes;
AddReactionBubble.defaultProps = defaultProps;
AddReactionBubble.displayName = 'AddReactionBubble';

export default AddReactionBubble;
68 changes: 68 additions & 0 deletions src/components/Reactions/EmojiReactionBubble.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Pressable} from 'react-native';
import _ from 'underscore';
import styles from '../../styles/styles';
import Text from '../Text';
import * as StyleUtils from '../../styles/StyleUtils';
import emojis from '../../../assets/emojis';

const propTypes = {
emojiName: PropTypes.string.isRequired,
emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired,
onPress: PropTypes.func.isRequired,
onLongPress: PropTypes.func,
count: PropTypes.number,
hasUserReacted: PropTypes.bool,
senderIDs: PropTypes.arrayOf(PropTypes.string),
sizeScale: PropTypes.number,
};

const defaultProps = {
count: 0,
onLongPress: () => {},
hasUserReacted: false,
senderIDs: [],
sizeScale: 1,
};

const EmojiReactionBubble = (props) => {
const emoji = _.find(emojis, e => `${e.name}` === props.emojiName);
if (!emoji || props.senderIDs === 0) {
return null;
}

return (
<Pressable
style={({hovered}) => [
styles.emojiReactionBubble,
StyleUtils.getEmojiReactionBubbleStyle(hovered, props.hasUserReacted, props.sizeScale),
]}
onPress={props.onPress}
onLongPress={props.onLongPress}
>
<Text style={[
styles.emojiReactionText,
StyleUtils.getEmojiReactionTextStyle(props.sizeScale),
]}
>
{props.emojiCodes.join('')}
</Text>
{props.count > 0 && (
<Text style={[
styles.reactionCounterText,
StyleUtils.getEmojiReactionCounterTextStyle(props.hasUserReacted, props.sizeScale),
]}
>
{props.count}
</Text>
)}
</Pressable>
);
};

EmojiReactionBubble.propTypes = propTypes;
EmojiReactionBubble.defaultProps = defaultProps;
EmojiReactionBubble.displayName = 'EmojiReactionBubble';

export default EmojiReactionBubble;
53 changes: 53 additions & 0 deletions src/components/Reactions/QuickEmojiReactions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {View} from 'react-native';
import _ from 'underscore';
import EmojiReactionBubble from './EmojiReactionBubble';
import AddReactionBubble from './AddReactionBubble';

const QUICK_REACTIONS = [
{
name: '+1',
codes: ['👍'],
},
{
name: 'heart',
codes: ['❤️'],
},
{
name: 'joy',
codes: ['😂'],
},
{
name: 'fire',
codes: ['🔥'],
},
];

const EMOJI_BUBBLE_SCALE = 1.5;

const QuickEmojiReactions = () => (
<View style={{
flexDirection: 'row',
paddingHorizontal: 30,
paddingVertical: 12,
justifyContent: 'space-between',
}}
>
{_.map(QUICK_REACTIONS, reaction => (
<EmojiReactionBubble
key={reaction.name}
onPress={console.log}
emojiName={reaction.name}
emojiCodes={reaction.codes}
sizeScale={EMOJI_BUBBLE_SCALE}
/>
))}
<AddReactionBubble
iconSizeScale={1.2}
onSelectEmoji={console.log}
sizeScale={EMOJI_BUBBLE_SCALE}
/>
</View>
);

QuickEmojiReactions.displayName = 'QuickEmojiReactions';
export default QuickEmojiReactions;
54 changes: 34 additions & 20 deletions src/pages/home/report/ContextMenu/BaseReportActionContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,26 +57,40 @@ class BaseReportActionContextMenu extends React.Component {

return this.props.isVisible && (
<View style={this.wrapperStyle}>
{_.map(_.filter(ContextMenuActions, shouldShowFilter), contextAction => (
<ContextMenuItem
icon={contextAction.icon}
text={this.props.translate(contextAction.textTranslateKey)}
successIcon={contextAction.successIcon}
successText={contextAction.successTextTranslateKey
? this.props.translate(contextAction.successTextTranslateKey)
: undefined}
isMini={this.props.isMini}
key={contextAction.textTranslateKey}
onPress={() => contextAction.onPress(!this.props.isMini, {
reportAction: this.props.reportAction,
reportID: this.props.reportID,
draftMessage: this.props.draftMessage,
selection: this.props.selection,
})}
description={contextAction.getDescription(this.props.selection, this.props.isSmallScreenWidth)}
autoReset={contextAction.autoReset}
/>
))}
{_.map(_.filter(ContextMenuActions, shouldShowFilter), (contextAction) => {
const closePopup = !this.props.isMini;
const payload = {
reportAction: this.props.reportAction,
reportID: this.props.reportID,
draftMessage: this.props.draftMessage,
selection: this.props.selection,
};

if (contextAction.renderContent) {
// make sure that renderContent isn't mixed with unsupported props
if (__DEV__ && (contextAction.text != null || contextAction.icon != null)) {
throw new Error('Dev error: renderContent() and text/icon cannot be used together.');
}

return contextAction.renderContent(closePopup, payload);
}

return (
<ContextMenuItem
icon={contextAction.icon}
text={this.props.translate(contextAction.textTranslateKey)}
successIcon={contextAction.successIcon}
successText={contextAction.successTextTranslateKey
? this.props.translate(contextAction.successTextTranslateKey)
: undefined}
isMini={this.props.isMini}
key={contextAction.textTranslateKey}
onPress={() => contextAction.onPress(closePopup, payload)}
description={contextAction.getDescription(this.props.selection, this.props.isSmallScreenWidth)}
autoReset={contextAction.autoReset}
/>
);
})}
</View>
);
}
Expand Down
7 changes: 7 additions & 0 deletions src/pages/home/report/ContextMenu/ContextMenuActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import addEncryptedAuthTokenToURL from '../../../../libs/addEncryptedAuthTokenTo
import * as ContextMenuUtils from './ContextMenuUtils';
import * as Environment from '../../../../libs/Environment/Environment';
import Permissions from '../../../../libs/Permissions';
import QuickEmojiReactions from '../../../../components/Reactions/QuickEmojiReactions';

/**
* Gets the HTML version of the message in an action.
Expand All @@ -35,6 +36,12 @@ const CONTEXT_MENU_TYPES = {

// A list of all the context actions in this menu.
export default [
{
shouldShow: () => true,
renderContent: (closePopup, {reportAction}) => (
<QuickEmojiReactions key="QuickEmojiReactions" />
),
},
{
textTranslateKey: 'common.download',
icon: Expensicons.Download,
Expand Down
41 changes: 41 additions & 0 deletions src/styles/StyleUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,44 @@ function getReportWelcomeContainerStyle(isSmallScreenWidth) {
};
}

function getEmojiReactionBubbleStyle(isHovered, hasUserReacted, sizeScale = 1) {
const sizeStyles = {
paddingVertical: styles.emojiReactionBubble.paddingVertical * sizeScale,
paddingHorizontal: styles.emojiReactionBubble.paddingHorizontal * sizeScale,
};

if (hasUserReacted) {
return {backgroundColor: '#003C73', ...sizeStyles};
}
if (isHovered) {
return {backgroundColor: themeColors.buttonHoveredBG, ...sizeStyles};
}

return sizeStyles;
}

function getEmojiReactionTextStyle(sizeScale = 1) {
return {
fontSize: styles.emojiReactionText.fontSize * sizeScale,
lineHeight: styles.emojiReactionText.lineHeight * sizeScale,
};
}

function getEmojiReactionCounterTextStyle(hasUserReacted, sizeScale = 1) {
const sizeStyles = {
fontSize: styles.reactionCounterText.fontSize * sizeScale,
};

if (hasUserReacted) {
return {
...sizeStyles,
color: themeColors.link,
};
}

return sizeStyles;
}

export {
getAvatarSize,
getAvatarStyle,
Expand Down Expand Up @@ -773,4 +811,7 @@ export {
getReportWelcomeBackgroundImageStyle,
getReportWelcomeBackgroundImageViewStyle,
getReportWelcomeContainerStyle,
getEmojiReactionBubbleStyle,
getEmojiReactionTextStyle,
getEmojiReactionCounterTextStyle,
};
33 changes: 33 additions & 0 deletions src/styles/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -2910,6 +2910,39 @@ const styles = {
paddingTop: 80,
paddingBottom: 45,
},

emojiReactionBubble: {
paddingVertical: 2,
paddingHorizontal: 8,
borderRadius: 28,
backgroundColor: themeColors.border,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
alignSelf: 'flex-start',
marginTop: 8,
marginRight: 4,
},

emojiReactionText: {
fontSize: 12,
lineHeight: 20,
textAlignVertical: 'center',
},
reactionCounterText: {
fontSize: 11,
fontWeight: 'bold',
color: themeColors.textLight,
},

fontColorReactionLabel: {
color: '#586A64',
},

reactionEmojiTitle: {
fontSize: variables.iconSizeLarge,
lineHeight: variables.iconSizeXLarge,
},
};

export default styles;

0 comments on commit 80333cb

Please sign in to comment.