From e59d87f870bf9731b251d7286675ed1a44076b52 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Tue, 12 Dec 2023 16:51:43 +0100 Subject: [PATCH 01/16] [TS migration] Migrate 'Reactions' component --- assets/emojis/types.ts | 2 +- ...eactionBubble.js => AddReactionBubble.tsx} | 74 ++++++------ .../Reactions/EmojiReactionBubble.js | 110 ------------------ .../Reactions/EmojiReactionBubble.tsx | 93 +++++++++++++++ ...actions.js => MiniQuickEmojiReactions.tsx} | 97 +++++++-------- .../BaseQuickEmojiReactions.js | 106 ----------------- .../BaseQuickEmojiReactions.tsx | 64 ++++++++++ .../Reactions/QuickEmojiReactions/index.js | 38 ------ .../{index.native.js => index.native.tsx} | 25 ++-- .../Reactions/QuickEmojiReactions/index.tsx | 27 +++++ .../Reactions/QuickEmojiReactions/types.ts | 83 +++++++++++++ .../Reactions/ReactionTooltipContent.js | 67 ----------- .../Reactions/ReactionTooltipContent.tsx | 63 ++++++++++ ....js => ReportActionItemEmojiReactions.tsx} | 103 +++++++++------- .../withCurrentUserPersonalDetails.tsx | 14 +-- src/libs/EmojiTrie.ts | 4 +- src/libs/EmojiUtils.ts | 20 ++-- src/libs/actions/EmojiPickerAction.ts | 31 ++++- src/pages/home/ReportScreenContext.ts | 4 +- src/types/onyx/FrequentlyUsedEmoji.ts | 2 +- src/types/onyx/ReportActionReactions.ts | 2 +- 21 files changed, 524 insertions(+), 505 deletions(-) rename src/components/Reactions/{AddReactionBubble.js => AddReactionBubble.tsx} (62%) delete mode 100644 src/components/Reactions/EmojiReactionBubble.js create mode 100644 src/components/Reactions/EmojiReactionBubble.tsx rename src/components/Reactions/{MiniQuickEmojiReactions.js => MiniQuickEmojiReactions.tsx} (56%) delete mode 100644 src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js create mode 100644 src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx delete mode 100644 src/components/Reactions/QuickEmojiReactions/index.js rename src/components/Reactions/QuickEmojiReactions/{index.native.js => index.native.tsx} (57%) create mode 100644 src/components/Reactions/QuickEmojiReactions/index.tsx create mode 100644 src/components/Reactions/QuickEmojiReactions/types.ts delete mode 100644 src/components/Reactions/ReactionTooltipContent.js create mode 100644 src/components/Reactions/ReactionTooltipContent.tsx rename src/components/Reactions/{ReportActionItemEmojiReactions.js => ReportActionItemEmojiReactions.tsx} (56%) diff --git a/assets/emojis/types.ts b/assets/emojis/types.ts index a42f44ed7fa7..28e3a2b31561 100644 --- a/assets/emojis/types.ts +++ b/assets/emojis/types.ts @@ -3,7 +3,7 @@ import {SvgProps} from 'react-native-svg'; type Emoji = { code: string; name: string; - types?: string[]; + types?: readonly string[]; }; type HeaderEmoji = { diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.tsx similarity index 62% rename from src/components/Reactions/AddReactionBubble.js rename to src/components/Reactions/AddReactionBubble.tsx index 994d467dfd6e..486697c977ba 100644 --- a/src/components/Reactions/AddReactionBubble.js +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -1,81 +1,79 @@ -import PropTypes from 'prop-types'; import React, {useEffect, useRef} from 'react'; -import {View} from 'react-native'; +import {TextInput, View} from 'react-native'; +import type {Emoji} from '@assets/emojis/types'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import getButtonState from '@libs/getButtonState'; import useStyleUtils from '@styles/useStyleUtils'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; +import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; +import type {ReportAction} from '@src/types/onyx'; +import type {CloseContextMenuCallback, OpenPickerCallback, ReportActionContextMenu} from './QuickEmojiReactions/types'; -const propTypes = { +type AddReactionBubbleProps = { /** Whether it is for context menu so we can modify its style */ - isContextMenu: PropTypes.bool, + isContextMenu?: boolean; /** * Called when the user presses on the icon button. * Will have a function as parameter which you can call * to open the picker. */ - onPressOpenPicker: PropTypes.func, + onPressOpenPicker?: (openPicker: OpenPickerCallback) => void; /** * Will get called the moment before the picker opens. */ - onWillShowPicker: PropTypes.func, + onWillShowPicker?: (callback: CloseContextMenuCallback) => void; /** * Called when the user selects an emoji. */ - onSelectEmoji: PropTypes.func.isRequired, + onSelectEmoji: (emoji: Emoji) => void; /** * ReportAction for EmojiPicker. */ - reportAction: PropTypes.shape({ - reportActionID: PropTypes.string.isRequired, - }), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - isContextMenu: false, - onWillShowPicker: () => {}, - onPressOpenPicker: undefined, - reportAction: {}, + reportAction: ReportAction; }; -function AddReactionBubble(props) { +function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWillShowPicker = () => {}, isContextMenu = false}: AddReactionBubbleProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const ref = useRef(); + const ref = useRef(null); + const {translate} = useLocalize(); + useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); const onPress = () => { - const openPicker = (refParam, anchorOrigin) => { + const openPicker = (refParam?: TextInput | ReportActionContextMenu | null, anchorOrigin?: AnchorOrigin) => { EmojiPickerAction.showEmojiPicker( () => {}, (emojiCode, emojiObject) => { - props.onSelectEmoji(emojiObject); + if (!emojiObject) { + return; + } + + onSelectEmoji(emojiObject); }, - refParam || ref.current, + refParam ?? ref.current, anchorOrigin, - props.onWillShowPicker, - props.reportAction.reportActionID, + onWillShowPicker, + reportAction.reportActionID, ); }; - if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) { - if (props.onPressOpenPicker) { - props.onPressOpenPicker(openPicker); + if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) { + if (onPressOpenPicker) { + onPressOpenPicker(openPicker); } else { openPicker(); } @@ -85,10 +83,10 @@ function AddReactionBubble(props) { }; return ( - + [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, props.isContextMenu)]} + style={({hovered, pressed}) => [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, isContextMenu)]} onPress={Session.checkIfActionIsAllowed(onPress)} onMouseDown={(e) => { // Allow text input blur when Add reaction is right clicked @@ -99,7 +97,7 @@ function AddReactionBubble(props) { // Prevent text input blur when Add reaction is left clicked e.preventDefault(); }} - accessibilityLabel={props.translate('emojiReactions.addReactionTooltip')} + accessibilityLabel={translate('emojiReactions.addReactionTooltip')} role={CONST.ACCESSIBILITY_ROLE.BUTTON} // disable dimming pressDimmingValue={1} @@ -110,12 +108,12 @@ function AddReactionBubble(props) { {/* This (invisible) 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. */} - {'\u2800\u2800'} + {'\u2800\u2800'} @@ -126,8 +124,6 @@ function AddReactionBubble(props) { ); } -AddReactionBubble.propTypes = propTypes; -AddReactionBubble.defaultProps = defaultProps; AddReactionBubble.displayName = 'AddReactionBubble'; -export default withLocalize(AddReactionBubble); +export default AddReactionBubble; diff --git a/src/components/Reactions/EmojiReactionBubble.js b/src/components/Reactions/EmojiReactionBubble.js deleted file mode 100644 index 7fcdae8c0a5a..000000000000 --- a/src/components/Reactions/EmojiReactionBubble.js +++ /dev/null @@ -1,110 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import Text from '@components/Text'; -import {withCurrentUserPersonalDetailsDefaultProps} from '@components/withCurrentUserPersonalDetails'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; -import CONST from '@src/CONST'; - -const propTypes = { - /** - * The emoji codes to display in the bubble. - */ - emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired, - - /** - * Called when the user presses on the reaction bubble. - */ - onPress: PropTypes.func.isRequired, - - /** - * Called when the user long presses or right clicks - * on the reaction bubble. - */ - onReactionListOpen: PropTypes.func, - - /** - * The number of reactions to display in the bubble. - */ - count: PropTypes.number, - - /** Whether it is for context menu so we can modify its style */ - isContextMenu: PropTypes.bool, - - /** - * Returns true if the current account has reacted to the report action (with the given skin tone). - */ - hasUserReacted: PropTypes.bool, - - /** We disable reacting with emojis on report actions that have errors */ - shouldBlockReactions: PropTypes.bool, - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - count: 0, - onReactionListOpen: () => {}, - isContextMenu: false, - shouldBlockReactions: false, - - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function EmojiReactionBubble(props) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - return ( - [ - styles.emojiReactionBubble, - StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, props.hasUserReacted, props.isContextMenu), - props.shouldBlockReactions && styles.cursorDisabled, - styles.userSelectNone, - ]} - onPress={() => { - if (props.shouldBlockReactions) { - return; - } - - props.onPress(); - }} - onSecondaryInteraction={props.onReactionListOpen} - ref={props.forwardedRef} - enableLongPressWithHover={props.isSmallScreenWidth} - onMouseDown={(e) => { - // Allow text input blur when emoji reaction is right clicked - if (e && e.button === 2) { - return; - } - - // Prevent text input blur when emoji reaction is left clicked - e.preventDefault(); - }} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} - accessibilityLabel={props.emojiCodes.join('')} - dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - > - {props.emojiCodes.join('')} - {props.count > 0 && {props.count}} - - ); -} - -EmojiReactionBubble.propTypes = propTypes; -EmojiReactionBubble.defaultProps = defaultProps; -EmojiReactionBubble.displayName = 'EmojiReactionBubble'; - -const EmojiReactionBubbleWithRef = React.forwardRef((props, ref) => ( - -)); - -EmojiReactionBubbleWithRef.displayName = 'EmojiReactionBubbleWithRef'; - -export default withWindowDimensions(EmojiReactionBubbleWithRef); diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx new file mode 100644 index 000000000000..63cb42d5bc29 --- /dev/null +++ b/src/components/Reactions/EmojiReactionBubble.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import {GestureResponderEvent} from 'react-native'; +import {PressableRef} from '@components/Pressable/GenericPressable/types'; +import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import Text from '@components/Text'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import useStyleUtils from '@styles/useStyleUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; + +type EmojiReactionBubbleProps = { + /** + * The emoji codes to display in the bubble. + */ + emojiCodes: string[]; + + /** + * Called when the user presses on the reaction bubble. + */ + onPress: () => void; + + /** + * Called when the user long presses or right clicks + * on the reaction bubble. + */ + onReactionListOpen?: (event: GestureResponderEvent | MouseEvent) => void; + + /** + * The number of reactions to display in the bubble. + */ + count?: number; + + /** Whether it is for context menu so we can modify its style */ + isContextMenu?: boolean; + + /** + * Returns true if the current account has reacted to the report action (with the given skin tone). + */ + hasUserReacted?: boolean; + + /** We disable reacting with emojis on report actions that have errors */ + shouldBlockReactions?: boolean; +}; + +function EmojiReactionBubble( + {onPress, onReactionListOpen = () => {}, emojiCodes, hasUserReacted = false, count = 0, isContextMenu = false, shouldBlockReactions = false}: EmojiReactionBubbleProps, + ref: PressableRef, +) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const {isSmallScreenWidth} = useWindowDimensions(); + + return ( + [ + styles.emojiReactionBubble, + StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, hasUserReacted, isContextMenu), + shouldBlockReactions && styles.cursorDisabled, + styles.userSelectNone, + ]} + onPress={() => { + if (shouldBlockReactions) { + return; + } + + onPress(); + }} + onSecondaryInteraction={onReactionListOpen} + ref={ref} + enableLongPressWithHover={isSmallScreenWidth} + onMouseDown={(event) => { + // Allow text input blur when emoji reaction is right clicked + if (event?.button === 2) { + return; + } + + // Prevent text input blur when emoji reaction is left clicked + event.preventDefault(); + }} + role={CONST.ACCESSIBILITY_ROLE.BUTTON} + accessibilityLabel={emojiCodes.join('')} + accessible + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} + > + {emojiCodes.join('')} + {count > 0 && {count}} + + ); +} + +EmojiReactionBubble.displayName = 'EmojiReactionBubble'; + +export default React.forwardRef(EmojiReactionBubble); diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.tsx similarity index 56% rename from src/components/Reactions/MiniQuickEmojiReactions.js rename to src/components/Reactions/MiniQuickEmojiReactions.tsx index 92913a7c4c5e..6e08ddd09458 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.js +++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx @@ -1,14 +1,12 @@ -import PropTypes from 'prop-types'; import React, {useRef} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {Emoji} from '@assets/emojis/types'; import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import compose from '@libs/compose'; +import useLocalize from '@hooks/useLocalize'; import * as EmojiUtils from '@libs/EmojiUtils'; import getButtonState from '@libs/getButtonState'; import useStyleUtils from '@styles/useStyleUtils'; @@ -17,33 +15,16 @@ import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {baseQuickEmojiReactionsDefaultProps, baseQuickEmojiReactionsPropTypes} from './QuickEmojiReactions/BaseQuickEmojiReactions'; +import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types'; -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, +type MiniQuickEmojiReactionsOnyxProps = Omit; +type MiniQuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { /** * Will be called when the user closed the emoji picker * without selecting an emoji. */ - onEmojiPickerClosed: PropTypes.func, - - /** - * ReportAction for EmojiPicker. - */ - reportAction: PropTypes.shape({ - reportActionID: PropTypes.string.isRequired, - }), - - ...withLocalizePropTypes, - preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -const defaultProps = { - ...baseQuickEmojiReactionsDefaultProps, - onEmojiPickerClosed: () => {}, - preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, - reportAction: {}, + onEmojiPickerClosed?: () => void; }; /** @@ -51,56 +32,67 @@ const defaultProps = { * emoji picker icon button. This is used for the mini * context menu which we just show on web, when hovering * a message. - * @param {Props} props - * @returns {JSX.Element} */ -function MiniQuickEmojiReactions(props) { +function MiniQuickEmojiReactions({ + reportAction, + onEmojiSelected, + preferredLocale = CONST.LOCALES.DEFAULT, + preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, + emojiReactions = {}, + onPressOpenPicker = () => {}, + onEmojiPickerClosed = () => {}, +}: MiniQuickEmojiReactionsProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const ref = useRef(); + const ref = useRef(null); + const {translate} = useLocalize(); const openEmojiPicker = () => { - props.onPressOpenPicker(); + onPressOpenPicker(); EmojiPickerAction.showEmojiPicker( - props.onEmojiPickerClosed, + onEmojiPickerClosed, (emojiCode, emojiObject) => { - props.onEmojiSelected(emojiObject, props.emojiReactions); + if (!emojiObject) { + return; + } + + onEmojiSelected(emojiObject, emojiReactions); }, ref.current, undefined, () => {}, - props.reportAction.reportActionID, + reportAction.reportActionID, ); }; return ( - {_.map(CONST.QUICK_REACTIONS, (emoji) => ( + {CONST.QUICK_REACTIONS.map((emoji: Emoji) => ( props.onEmojiSelected(emoji, props.emojiReactions))} + tooltipText={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, preferredLocale)}:`} + onPress={Session.checkIfActionIsAllowed(() => onEmojiSelected(emoji, emojiReactions))} > - {EmojiUtils.getPreferredEmojiCode(emoji, props.preferredSkinTone)} + {EmojiUtils.getPreferredEmojiCode(emoji, preferredSkinTone)} ))} { - if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) { + if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) { openEmojiPicker(); } else { - EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker(); + EmojiPickerAction.emojiPickerRef.current?.hideEmojiPicker(); } })} isDelayButtonStateComplete={false} - tooltipText={props.translate('emojiReactions.addReactionTooltip')} + tooltipText={translate('emojiReactions.addReactionTooltip')} > {({hovered, pressed}) => ( `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, - }, - }), -)(MiniQuickEmojiReactions); + +export default withOnyx({ + preferredSkinTone: { + key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + }, + emojiReactions: { + key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, + }, +})(MiniQuickEmojiReactions); diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js deleted file mode 100644 index 9d32b0240a23..000000000000 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import AddReactionBubble from '@components/Reactions/AddReactionBubble'; -import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble'; -import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes'; -import Tooltip from '@components/Tooltip'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import useThemeStyles from '@styles/useThemeStyles'; -import * as Session from '@userActions/Session'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const baseQuickEmojiReactionsPropTypes = { - emojiReactions: EmojiReactionsPropTypes, - - /** - * Callback to fire when an emoji is selected. - */ - onEmojiSelected: PropTypes.func.isRequired, - - /** - * Will be called when the emoji picker is about to show. - */ - onWillShowPicker: PropTypes.func, - - /** - * Callback to fire when the "open emoji picker" button is pressed. - * The function receives an argument which can be called - * to actually open the emoji picker. - */ - onPressOpenPicker: PropTypes.func, - - /** - * ReportAction for EmojiPicker. - */ - reportAction: PropTypes.object, - - preferredLocale: PropTypes.string, -}; - -const baseQuickEmojiReactionsDefaultProps = { - emojiReactions: {}, - onWillShowPicker: () => {}, - onPressOpenPicker: () => {}, - reportAction: {}, - preferredLocale: CONST.LOCALES.DEFAULT, -}; - -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, - preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -const defaultProps = { - ...baseQuickEmojiReactionsDefaultProps, - preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, -}; - -function BaseQuickEmojiReactions(props) { - const styles = useThemeStyles(); - return ( - - {_.map(CONST.QUICK_REACTIONS, (emoji) => ( - - - props.onEmojiSelected(emoji, props.emojiReactions))} - /> - - - ))} - props.onEmojiSelected(emoji, props.emojiReactions)} - reportAction={props.reportAction} - /> - - ); -} - -BaseQuickEmojiReactions.displayName = 'BaseQuickEmojiReactions'; -BaseQuickEmojiReactions.propTypes = propTypes; -BaseQuickEmojiReactions.defaultProps = defaultProps; -export default withOnyx({ - preferredSkinTone: { - key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - }, - emojiReactions: { - key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, -})(BaseQuickEmojiReactions); - -export {baseQuickEmojiReactionsPropTypes, baseQuickEmojiReactionsDefaultProps}; diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx new file mode 100644 index 000000000000..80efab704f52 --- /dev/null +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import {Emoji} from '@assets/emojis/types'; +import AddReactionBubble from '@components/Reactions/AddReactionBubble'; +import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble'; +import Tooltip from '@components/Tooltip'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './types'; + +function BaseQuickEmojiReactions({ + reportAction, + onEmojiSelected, + preferredLocale = CONST.LOCALES.DEFAULT, + preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, + emojiReactions = {}, + onPressOpenPicker = () => {}, + onWillShowPicker = () => {}, +}: BaseQuickEmojiReactionsProps) { + const styles = useThemeStyles(); + return ( + + {CONST.QUICK_REACTIONS.map((emoji: Emoji) => ( + + + onEmojiSelected(emoji, emojiReactions))} + /> + + + ))} + onEmojiSelected(emoji, emojiReactions)} + reportAction={reportAction} + /> + + ); +} + +BaseQuickEmojiReactions.displayName = 'BaseQuickEmojiReactions'; + +export default withOnyx({ + preferredSkinTone: { + key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + }, + emojiReactions: { + key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, +})(BaseQuickEmojiReactions); diff --git a/src/components/Reactions/QuickEmojiReactions/index.js b/src/components/Reactions/QuickEmojiReactions/index.js deleted file mode 100644 index e4399b634136..000000000000 --- a/src/components/Reactions/QuickEmojiReactions/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import CONST from '@src/CONST'; -import BaseQuickEmojiReactions, {baseQuickEmojiReactionsPropTypes} from './BaseQuickEmojiReactions'; - -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, - - /** - * Function that can be called to close the - * context menu in which this component is - * rendered. - */ - closeContextMenu: PropTypes.func.isRequired, -}; - -function QuickEmojiReactions(props) { - const onPressOpenPicker = (openPicker) => { - openPicker(contextMenuRef.current.contentRef.current, { - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, - }); - }; - - return ( - - ); -} - -QuickEmojiReactions.displayName = 'QuickEmojiReactions'; -QuickEmojiReactions.propTypes = propTypes; -export default QuickEmojiReactions; diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.js b/src/components/Reactions/QuickEmojiReactions/index.native.tsx similarity index 57% rename from src/components/Reactions/QuickEmojiReactions/index.native.js rename to src/components/Reactions/QuickEmojiReactions/index.native.tsx index 239fd7b4c8a8..37502c33409e 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.native.js +++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx @@ -1,29 +1,18 @@ -import PropTypes from 'prop-types'; import React from 'react'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import BaseQuickEmojiReactions, {baseQuickEmojiReactionsPropTypes} from './BaseQuickEmojiReactions'; +import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; +import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, - - /** - * Function that can be called to close the - * context menu in which this component is - * rendered. - */ - closeContextMenu: PropTypes.func.isRequired, -}; - -function QuickEmojiReactions(props) { - const onPressOpenPicker = (openPicker) => { +function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsProps) { + const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { // We first need to close the menu as it's a popover. // The picker is a popover as well and on mobile there can only // be one active popover at a time. - props.closeContextMenu(() => { + closeContextMenu(() => { // As the menu which includes the button to open the emoji picker // gets closed, before the picker actually opens, we pass the composer // ref as anchor for the emoji picker popover. - openPicker(ReportActionComposeFocusManager.composerRef.current); + openPicker?.(ReportActionComposeFocusManager.composerRef.current); }); }; @@ -37,5 +26,5 @@ function QuickEmojiReactions(props) { } QuickEmojiReactions.displayName = 'QuickEmojiReactions'; -QuickEmojiReactions.propTypes = propTypes; + export default QuickEmojiReactions; diff --git a/src/components/Reactions/QuickEmojiReactions/index.tsx b/src/components/Reactions/QuickEmojiReactions/index.tsx new file mode 100644 index 000000000000..cb900f652f2a --- /dev/null +++ b/src/components/Reactions/QuickEmojiReactions/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from '@src/CONST'; +import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; +import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; + +function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsProps) { + const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { + openPicker?.(contextMenuRef.current.contentRef.current, { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, + }); + }; + + return ( + + ); +} + +QuickEmojiReactions.displayName = 'QuickEmojiReactions'; + +export default QuickEmojiReactions; diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts new file mode 100644 index 000000000000..d3afc7bbb13d --- /dev/null +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -0,0 +1,83 @@ +import {GestureResponderEvent, Text as RNText, TextInput} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {Emoji} from '@assets/emojis/types'; +import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; +import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; + +type ShowContextMenu = ( + type: 'LINK' | 'REPORT_ACTION' | 'EMAIL' | 'REPORT', + event: GestureResponderEvent | MouseEvent, + selection: string, + contextMenuAnchor: RNText | null, + reportID?: string, + reportActionID?: string, + originalReportID?: string, + draftMessage?: string, + onShow?: () => void, + onHide?: () => void, + isArchivedRoom?: boolean, + isChronosReport?: boolean, + isPinnedChat?: boolean, + isUnreadChat?: boolean, +) => void; + +// TODO: remove when https://github.com/Expensify/App/pull/32670 is merged +type ReportActionContextMenu = { + showContextMenu: ShowContextMenu; + hideContextMenu: (callback: () => void) => void; + showDeleteModal: (reportID: string, reportAction: OnyxEntry, shouldSetModalVisibility?: boolean, onConfirm?: () => void, onCancel?: () => void) => void; + hideDeleteModal: () => void; + isActiveReportAction: (accountID: string | number) => boolean; + instanceID: string; + runAndResetOnPopoverHide: () => void; + clearActiveReportAction: () => void; +}; + +type OpenPickerCallback = (element: TextInput | ReportActionContextMenu | null, anchorOrigin?: AnchorOrigin) => void; + +type CloseContextMenuCallback = () => void; + +type BaseQuickEmojiReactionsOnyxProps = { + /** All the emoji reactions for the report action. */ + emojiReactions: OnyxEntry; + + /** The user's preferred locale. */ + preferredLocale: OnyxEntry; + + /** The user's preferred skin tone. */ + preferredSkinTone: OnyxEntry; +}; + +type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & { + /** Callback to fire when an emoji is selected. */ + onEmojiSelected: (emoji: Emoji, emojiReactions: OnyxEntry) => void; + + /** + * Will be called when the emoji picker is about to show. + */ + onWillShowPicker?: (callback: CloseContextMenuCallback) => void; + + /** + * Callback to fire when the "open emoji picker" button is pressed. + * The function receives an argument which can be called + * to actually open the emoji picker. + */ + onPressOpenPicker?: (openPicker?: OpenPickerCallback) => void; + + /** ReportAction for EmojiPicker. */ + reportAction: ReportAction; + + /** Id of the ReportAction for EmojiPicker. */ + // eslint-disable-next-line react/no-unused-prop-types -- It's used inside withOnyx HOC + reportActionID: string; +}; + +type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { + /** + * Function that can be called to close the context menu + * in which this component is rendered. + */ + closeContextMenu: (callback: CloseContextMenuCallback) => void; +}; + +export type {BaseQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps, QuickEmojiReactionsProps, OpenPickerCallback, CloseContextMenuCallback, ReportActionContextMenu}; diff --git a/src/components/Reactions/ReactionTooltipContent.js b/src/components/Reactions/ReactionTooltipContent.js deleted file mode 100644 index 1a7a06e9487d..000000000000 --- a/src/components/Reactions/ReactionTooltipContent.js +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useMemo} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import Text from '@components/Text'; -import {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import useThemeStyles from '@styles/useThemeStyles'; - -const propTypes = { - /** - * A list of emoji codes to display in the tooltip. - */ - emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired, - - /** - * The name of the emoji to display in the tooltip. - */ - emojiName: PropTypes.string.isRequired, - - /** - * A list of account IDs to display in the tooltip. - */ - accountIDs: PropTypes.arrayOf(PropTypes.number).isRequired, - - ...withCurrentUserPersonalDetailsPropTypes, -}; - -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function ReactionTooltipContent(props) { - const styles = useThemeStyles(); - const users = useMemo( - () => PersonalDetailsUtils.getPersonalDetailsByIDs(props.accountIDs, props.currentUserPersonalDetails.accountID, true), - [props.currentUserPersonalDetails.accountID, props.accountIDs], - ); - const namesString = _.filter( - _.map(users, (user) => user && user.displayName), - (n) => n, - ).join(', '); - return ( - - - {_.map(props.emojiCodes, (emojiCode) => ( - - {emojiCode} - - ))} - - - {namesString} - - {`${props.translate('emojiReactions.reactedWith')} :${props.emojiName}:`} - - ); -} - -ReactionTooltipContent.propTypes = propTypes; -ReactionTooltipContent.defaultProps = defaultProps; -ReactionTooltipContent.displayName = 'ReactionTooltipContent'; -export default React.memo(withLocalize(ReactionTooltipContent)); diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx new file mode 100644 index 000000000000..99a83df25a89 --- /dev/null +++ b/src/components/Reactions/ReactionTooltipContent.tsx @@ -0,0 +1,63 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import Text from '@components/Text'; +import type {WithCurrentUserPersonalDetailsHOCProps} from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import useThemeStyles from '@styles/useThemeStyles'; +import {PersonalDetails} from '@src/types/onyx'; + +type ReactionTooltipContentProps = WithCurrentUserPersonalDetailsHOCProps & { + /** + * A list of emoji codes to display in the tooltip. + */ + emojiCodes: string[]; + + /** + * The name of the emoji to display in the tooltip. + */ + emojiName: string; + + /** + * A list of account IDs to display in the tooltip. + */ + accountIDs: number[]; +}; + +function ReactionTooltipContent({accountIDs, currentUserPersonalDetails = {}, emojiCodes, emojiName}: ReactionTooltipContentProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const users: PersonalDetails[] = useMemo( + // TODO: remove eslint disable when https://github.com/Expensify/App/pull/30169 is merged + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + () => PersonalDetailsUtils.getPersonalDetailsByIDs(accountIDs, currentUserPersonalDetails.accountID, true), + [currentUserPersonalDetails.accountID, accountIDs], + ); + const namesString = users + .map((user) => user?.displayName) + .filter((name) => name) + .join(', '); + + return ( + + + {emojiCodes.map((emojiCode) => ( + + {emojiCode} + + ))} + + + {namesString} + + {`${translate('emojiReactions.reactedWith')} :${emojiName}:`} + + ); +} + +ReactionTooltipContent.displayName = 'ReactionTooltipContent'; + +export default React.memo(ReactionTooltipContent); diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.tsx similarity index 56% rename from src/components/Reactions/ReportActionItemEmojiReactions.js rename to src/components/Reactions/ReportActionItemEmojiReactions.tsx index 7c504e35cb9f..4f4c269b9903 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.js +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -1,63 +1,78 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import sortBy from 'lodash/sortBy'; import React, {useContext, useRef} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; +import {GestureResponderEvent, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {Emoji} from '@assets/emojis/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Tooltip from '@components/Tooltip'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; -import compose from '@libs/compose'; +import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import * as EmojiUtils from '@libs/EmojiUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; import useThemeStyles from '@styles/useThemeStyles'; +import CONST from '@src/CONST'; +import type {ReportAction, ReportActionReactions} from '@src/types/onyx'; +import {Locale} from '@src/types/onyx'; +import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import AddReactionBubble from './AddReactionBubble'; import EmojiReactionBubble from './EmojiReactionBubble'; -import EmojiReactionsPropTypes from './EmojiReactionsPropTypes'; import ReactionTooltipContent from './ReactionTooltipContent'; -const propTypes = { - emojiReactions: EmojiReactionsPropTypes, +type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & { + /** All the emoji reactions for the report action. */ + emojiReactions: OnyxEntry; + + /** The user's preferred locale. */ + preferredLocale: OnyxEntry; /** The report action that these reactions are for */ - reportAction: PropTypes.shape(reportActionPropTypes).isRequired, + reportAction: ReportAction; /** * Function to call when the user presses on an emoji. * This can also be an emoji the user already reacted with, * hence this function asks to toggle the reaction by emoji. */ - toggleReaction: PropTypes.func.isRequired, + toggleReaction: (emoji: Emoji) => void; /** We disable reacting with emojis on report actions that have errors */ - shouldBlockReactions: PropTypes.bool, - - ...withCurrentUserPersonalDetailsPropTypes, + shouldBlockReactions?: boolean; }; -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, - emojiReactions: {}, - shouldBlockReactions: false, +type FormattedReaction = { + emojiCodes: string[]; + userAccountIDs: number[]; + reactionCount: number; + hasUserReacted: boolean; + oldestTimestamp: string; + onPress: () => void; + onReactionListOpen: (event: GestureResponderEvent | MouseEvent) => void; + reactionEmojiName: string; + pendingAction: PendingAction; }; -function ReportActionItemEmojiReactions(props) { +function ReportActionItemEmojiReactions({ + reportAction, + currentUserPersonalDetails, + toggleReaction, + emojiReactions = {}, + shouldBlockReactions = false, + preferredLocale = CONST.LOCALES.DEFAULT, +}: ReportActionItemEmojiReactionsProps) { const styles = useThemeStyles(); const reactionListRef = useContext(ReactionListContext); - const popoverReactionListAnchors = useRef({}); + const popoverReactionListAnchors = useRef>({}); let totalReactionCount = 0; - const reportAction = props.reportAction; const reportActionID = reportAction.reportActionID; - const formattedReactions = _.chain(props.emojiReactions) - .map((emojiReaction, emojiName) => { + // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone + const formattedReactions: Array = sortBy( + Object.entries(emojiReactions ?? {}).map(([emojiName, emojiReaction]) => { const {emoji, emojiCodes, reactionCount, hasUserReacted, userAccountIDs, oldestTimestamp} = EmojiUtils.getEmojiReactionDetails( emojiName, emojiReaction, - props.currentUserPersonalDetails.accountID, + currentUserPersonalDetails.accountID, ); if (reactionCount === 0) { @@ -66,11 +81,11 @@ function ReportActionItemEmojiReactions(props) { totalReactionCount += reactionCount; const onPress = () => { - props.toggleReaction(emoji); + toggleReaction(emoji); }; - const onReactionListOpen = (event) => { - reactionListRef.current.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID); + const onReactionListOpen = (event: GestureResponderEvent | MouseEvent) => { + reactionListRef?.current?.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID); }; return { @@ -84,15 +99,14 @@ function ReportActionItemEmojiReactions(props) { reactionEmojiName: emojiName, pendingAction: emojiReaction.pendingAction, }; - }) - // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone - .sortBy('oldestTimestamp') - .value(); + }), + ['oldestTimestamp'], + ); return ( totalReactionCount > 0 && ( - {_.map(formattedReactions, (reaction) => { + {formattedReactions.map((reaction) => { if (reaction === null) { return; } @@ -100,19 +114,19 @@ function ReportActionItemEmojiReactions(props) { ( )} - renderTooltipContentKey={[..._.map(reaction.userAccountIDs, String), ...reaction.emojiCodes]} + renderTooltipContentKey={[...reaction.userAccountIDs.map(String), ...reaction.emojiCodes]} key={reaction.reactionEmojiName} > (popoverReactionListAnchors.current[reaction.reactionEmojiName] = ref)} @@ -121,17 +135,17 @@ function ReportActionItemEmojiReactions(props) { onPress={reaction.onPress} hasUserReacted={reaction.hasUserReacted} onReactionListOpen={reaction.onReactionListOpen} - shouldBlockReactions={props.shouldBlockReactions} + shouldBlockReactions={shouldBlockReactions} /> ); })} - {!props.shouldBlockReactions && ( + {!shouldBlockReactions && ( )} @@ -140,6 +154,5 @@ function ReportActionItemEmojiReactions(props) { } ReportActionItemEmojiReactions.displayName = 'ReportActionItemReactions'; -ReportActionItemEmojiReactions.propTypes = propTypes; -ReportActionItemEmojiReactions.defaultProps = defaultProps; -export default compose(withLocalize, withCurrentUserPersonalDetails)(ReportActionItemEmojiReactions); + +export default withCurrentUserPersonalDetails(ReportActionItemEmojiReactions); diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index a97067c32c72..9f78ac809ada 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -14,25 +14,25 @@ type OnyxProps = { session: OnyxEntry; }; -type HOCProps = { +type WithCurrentUserPersonalDetailsHOCProps = { currentUserPersonalDetails: CurrentUserPersonalDetails; }; -type WithCurrentUserPersonalDetailsProps = OnyxProps & HOCProps; +type WithCurrentUserPersonalDetailsProps = OnyxProps & WithCurrentUserPersonalDetailsHOCProps; // TODO: remove when all components that use it will be migrated to TS const withCurrentUserPersonalDetailsPropTypes = { currentUserPersonalDetails: personalDetailsPropType, }; -const withCurrentUserPersonalDetailsDefaultProps: HOCProps = { +const withCurrentUserPersonalDetailsDefaultProps: WithCurrentUserPersonalDetailsHOCProps = { currentUserPersonalDetails: {}, }; export default function ( WrappedComponent: ComponentType>, -): ComponentType & RefAttributes, keyof OnyxProps>> { - function WithCurrentUserPersonalDetails(props: Omit, ref: ForwardedRef) { +): ComponentType & RefAttributes, keyof OnyxProps>> { + function WithCurrentUserPersonalDetails(props: Omit, ref: ForwardedRef) { const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const accountID = props.session?.accountID ?? 0; const accountPersonalDetails = personalDetails?.[accountID]; @@ -54,7 +54,7 @@ export default function & RefAttributes, OnyxProps>({ + return withOnyx & RefAttributes, OnyxProps>({ session: { key: ONYXKEYS.SESSION, }, @@ -62,4 +62,4 @@ export default function ; type Suggestion = { code: string; - types?: string[]; + types?: readonly string[]; name: string; }; diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 655f6a40609f..21c8e685d61e 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -1,13 +1,13 @@ import {getUnixTime} from 'date-fns'; import Str from 'expensify-common/lib/str'; import memoize from 'lodash/memoize'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import {SvgProps} from 'react-native-svg'; import * as Emojis from '@assets/emojis'; import {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {FrequentlyUsedEmoji} from '@src/types/onyx'; +import {FrequentlyUsedEmoji, Locale} from '@src/types/onyx'; import {ReportActionReaction, UsersReactions} from '@src/types/onyx/ReportActionReactions'; import {SupportedLanguage} from './EmojiTrie'; @@ -48,13 +48,17 @@ const getEmojiName = (emoji: Emoji, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): /** * Given an English emoji name, get its localized version */ -const getLocalizedEmojiName = (name: string, lang: 'en' | 'es'): string => { +const getLocalizedEmojiName = (name: string, lang: OnyxEntry): string => { if (lang === CONST.LOCALES.DEFAULT) { return name; } - const emojiCode = Emojis.emojiNameTable[name]?.code ?? ''; - return Emojis.localeEmojis[lang]?.[emojiCode]?.name ?? ''; + if (lang === CONST.LOCALES.ES) { + const emojiCode = Emojis.emojiNameTable[name]?.code ?? ''; + return Emojis.localeEmojis[lang]?.[emojiCode]?.name ?? ''; + } + + return ''; }; /** @@ -438,9 +442,9 @@ const getPreferredSkinToneIndex = (value: string | number | null): number => { * Given an emoji object it returns the correct emoji code * based on the users preferred skin tone. */ -const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: number): string => { - if (emoji.types) { - const emojiCodeWithSkinTone = emoji.types[preferredSkinTone]; +const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: OnyxEntry): string => { + if (emoji.types && preferredSkinTone) { + const emojiCodeWithSkinTone = emoji.types[Number(preferredSkinTone)]; // Note: it can happen that preferredSkinTone has a outdated format, // so it makes sense to check if we actually got a valid emoji code back diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index 07b00a508dad..a6873bc14d04 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -1,6 +1,8 @@ import React from 'react'; -import {View} from 'react-native'; +import {TextInput, View} from 'react-native'; import {ValueOf} from 'type-fest'; +import {Emoji} from '@assets/emojis/types'; +import type {CloseContextMenuCallback, ReportActionContextMenu} from '@components/Reactions/QuickEmojiReactions/types'; import CONST from '@src/CONST'; type AnchorOrigin = { @@ -8,16 +10,29 @@ type AnchorOrigin = { vertical: ValueOf; }; +type EmojiPopoverAnchor = View | HTMLDivElement | ReportActionContextMenu | TextInput | null; + +type OnWillShowPicker = (callback: CloseContextMenuCallback) => void; + // TODO: Move this type to src/components/EmojiPicker/EmojiPicker.js once it is converted to TS type EmojiPickerRef = { - showEmojiPicker: (onModalHideValue?: () => void, onEmojiSelectedValue?: () => void, emojiPopoverAnchor?: View, anchorOrigin?: AnchorOrigin, onWillShow?: () => void, id?: string) => void; + showEmojiPicker: ( + onModalHideValue?: () => void, + onEmojiSelectedValue?: () => void, + emojiPopoverAnchor?: EmojiPopoverAnchor, + anchorOrigin?: AnchorOrigin, + onWillShow?: OnWillShowPicker, + id?: string, + ) => void; isActive: (id: string) => boolean; clearActive: () => void; - hideEmojiPicker: (isNavigating: boolean) => void; + hideEmojiPicker: (isNavigating?: boolean) => void; isEmojiPickerVisible: boolean; resetEmojiPopoverAnchor: () => void; }; +type OnEmojiSelected = (emojiCode?: string, emojiObject?: Emoji) => void; + const emojiPickerRef = React.createRef(); /** @@ -30,7 +45,14 @@ const emojiPickerRef = React.createRef(); * @param onWillShow - Run a callback when Popover will show * @param id - Unique id for EmojiPicker */ -function showEmojiPicker(onModalHide = () => {}, onEmojiSelected = () => {}, emojiPopoverAnchor = undefined, anchorOrigin = undefined, onWillShow = () => {}, id = undefined) { +function showEmojiPicker( + onModalHide = () => {}, + onEmojiSelected: OnEmojiSelected = () => {}, + emojiPopoverAnchor: EmojiPopoverAnchor = null, + anchorOrigin: AnchorOrigin | undefined = undefined, + onWillShow: OnWillShowPicker = () => {}, + id: string | undefined = undefined, +) { if (!emojiPickerRef.current) { return; } @@ -85,3 +107,4 @@ function resetEmojiPopoverAnchor() { } export {emojiPickerRef, showEmojiPicker, hideEmojiPicker, isActive, clearActive, isEmojiPickerVisible, resetEmojiPopoverAnchor}; +export type {AnchorOrigin}; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts index 49681d46e4be..9cbdeee06c51 100644 --- a/src/pages/home/ReportScreenContext.ts +++ b/src/pages/home/ReportScreenContext.ts @@ -1,8 +1,8 @@ import {createContext, RefObject} from 'react'; -import {FlatList, GestureResponderEvent} from 'react-native'; +import {FlatList, GestureResponderEvent, View} from 'react-native'; type ReactionListRef = { - showReactionList: (event: GestureResponderEvent | undefined, reactionListAnchor: Element, emojiName: string, reportActionID: string) => void; + showReactionList: (event: GestureResponderEvent | MouseEvent | undefined, reactionListAnchor: View | HTMLDivElement | null, emojiName: string, reportActionID: string) => void; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; }; diff --git a/src/types/onyx/FrequentlyUsedEmoji.ts b/src/types/onyx/FrequentlyUsedEmoji.ts index 333721b25b52..c8f6a5179fc6 100644 --- a/src/types/onyx/FrequentlyUsedEmoji.ts +++ b/src/types/onyx/FrequentlyUsedEmoji.ts @@ -12,7 +12,7 @@ type FrequentlyUsedEmoji = { lastUpdatedAt: number; /** The emoji skin tone type */ - types?: string[]; + types?: readonly string[]; /** The emoji keywords */ keywords?: string[]; diff --git a/src/types/onyx/ReportActionReactions.ts b/src/types/onyx/ReportActionReactions.ts index 348a4b1baf62..457ce772b2c9 100644 --- a/src/types/onyx/ReportActionReactions.ts +++ b/src/types/onyx/ReportActionReactions.ts @@ -24,7 +24,7 @@ type ReportActionReaction = { users: UsersReactions; /** Is this action pending? */ - pendingAction?: OnyxCommon.PendingAction; + pendingAction: OnyxCommon.PendingAction; }; type ReportActionReactions = Record; From 2374486da5b0e3daa80f3f6350fca85b8b7fb26f Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 13 Dec 2023 10:49:24 +0100 Subject: [PATCH 02/16] Fix after merging main --- src/components/Reactions/EmojiReactionBubble.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx index 63cb42d5bc29..d61793220614 100644 --- a/src/components/Reactions/EmojiReactionBubble.tsx +++ b/src/components/Reactions/EmojiReactionBubble.tsx @@ -77,7 +77,7 @@ function EmojiReactionBubble( // Prevent text input blur when emoji reaction is left clicked event.preventDefault(); }} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} accessibilityLabel={emojiCodes.join('')} accessible dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} From 2b4037d74a93f843547c5e00568e7cecc74666d6 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 13 Dec 2023 11:31:46 +0100 Subject: [PATCH 03/16] Minor code improvements --- src/components/Reactions/AddReactionBubble.tsx | 12 ++++++------ .../BaseQuickEmojiReactions.tsx | 1 + .../Reactions/QuickEmojiReactions/types.ts | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/Reactions/AddReactionBubble.tsx b/src/components/Reactions/AddReactionBubble.tsx index 5afc1961bfe1..ce5e4a434d10 100644 --- a/src/components/Reactions/AddReactionBubble.tsx +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useRef} from 'react'; -import {TextInput, View} from 'react-native'; +import {View} from 'react-native'; import type {Emoji} from '@assets/emojis/types'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -16,7 +16,7 @@ import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import type {ReportAction} from '@src/types/onyx'; -import type {CloseContextMenuCallback, OpenPickerCallback, ReportActionContextMenu} from './QuickEmojiReactions/types'; +import type {CloseContextMenuCallback, OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types'; type AddReactionBubbleProps = { /** Whether it is for context menu so we can modify its style */ @@ -54,7 +54,7 @@ function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWi useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); const onPress = () => { - const openPicker = (refParam?: TextInput | ReportActionContextMenu | null, anchorOrigin?: AnchorOrigin) => { + const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin) => { EmojiPickerAction.showEmojiPicker( () => {}, (emojiCode, emojiObject) => { @@ -88,14 +88,14 @@ function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWi ref={ref} style={({hovered, pressed}) => [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, isContextMenu)]} onPress={Session.checkIfActionIsAllowed(onPress)} - onMouseDown={(e) => { + onMouseDown={(event) => { // Allow text input blur when Add reaction is right clicked - if (!e || e.button === 2) { + if (!event || event.button === 2) { return; } // Prevent text input blur when Add reaction is left clicked - e.preventDefault(); + event.preventDefault(); }} accessibilityLabel={translate('emojiReactions.addReactionTooltip')} role={CONST.ROLE.BUTTON} diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx index 80efab704f52..287d028c36b8 100644 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx @@ -22,6 +22,7 @@ function BaseQuickEmojiReactions({ onWillShowPicker = () => {}, }: BaseQuickEmojiReactionsProps) { const styles = useThemeStyles(); + return ( {CONST.QUICK_REACTIONS.map((emoji: Emoji) => ( diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index d3afc7bbb13d..b6751e780386 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -33,7 +33,9 @@ type ReportActionContextMenu = { clearActiveReportAction: () => void; }; -type OpenPickerCallback = (element: TextInput | ReportActionContextMenu | null, anchorOrigin?: AnchorOrigin) => void; +type PickerRefElement = TextInput | ReportActionContextMenu | null; + +type OpenPickerCallback = (element: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; type CloseContextMenuCallback = () => void; @@ -80,4 +82,12 @@ type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { closeContextMenu: (callback: CloseContextMenuCallback) => void; }; -export type {BaseQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps, QuickEmojiReactionsProps, OpenPickerCallback, CloseContextMenuCallback, ReportActionContextMenu}; +export type { + BaseQuickEmojiReactionsProps, + BaseQuickEmojiReactionsOnyxProps, + QuickEmojiReactionsProps, + OpenPickerCallback, + CloseContextMenuCallback, + ReportActionContextMenu, + PickerRefElement, +}; From a2134b2ba30a42bcc25f9180a3aefd6bdc2b3fdf Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 18 Dec 2023 20:05:54 +0100 Subject: [PATCH 04/16] Updates to follow main branch --- src/components/Reactions/EmojiReactionBubble.tsx | 4 ++-- .../Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx | 2 +- src/components/Reactions/ReactionTooltipContent.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx index d61793220614..c4c0db50cc86 100644 --- a/src/components/Reactions/EmojiReactionBubble.tsx +++ b/src/components/Reactions/EmojiReactionBubble.tsx @@ -3,9 +3,9 @@ import {GestureResponderEvent} from 'react-native'; import {PressableRef} from '@components/Pressable/GenericPressable/types'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; type EmojiReactionBubbleProps = { diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx index 287d028c36b8..1e74e2d6bf70 100644 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx @@ -5,8 +5,8 @@ import {Emoji} from '@assets/emojis/types'; import AddReactionBubble from '@components/Reactions/AddReactionBubble'; import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble'; import Tooltip from '@components/Tooltip'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as EmojiUtils from '@libs/EmojiUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx index 99a83df25a89..7e25a720ea85 100644 --- a/src/components/Reactions/ReactionTooltipContent.tsx +++ b/src/components/Reactions/ReactionTooltipContent.tsx @@ -3,8 +3,8 @@ import {View} from 'react-native'; import Text from '@components/Text'; import type {WithCurrentUserPersonalDetailsHOCProps} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import {PersonalDetails} from '@src/types/onyx'; type ReactionTooltipContentProps = WithCurrentUserPersonalDetailsHOCProps & { From d4f7ba86ccea2ac8354f29463d81e1146726d707 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Mon, 18 Dec 2023 20:10:28 +0100 Subject: [PATCH 05/16] Remove TODO related to PersonalDetailsUtils --- src/components/Reactions/ReactionTooltipContent.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx index 7e25a720ea85..83f214e7dd7d 100644 --- a/src/components/Reactions/ReactionTooltipContent.tsx +++ b/src/components/Reactions/ReactionTooltipContent.tsx @@ -5,7 +5,6 @@ import type {WithCurrentUserPersonalDetailsHOCProps} from '@components/withCurre import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -import {PersonalDetails} from '@src/types/onyx'; type ReactionTooltipContentProps = WithCurrentUserPersonalDetailsHOCProps & { /** @@ -27,12 +26,7 @@ type ReactionTooltipContentProps = WithCurrentUserPersonalDetailsHOCProps & { function ReactionTooltipContent({accountIDs, currentUserPersonalDetails = {}, emojiCodes, emojiName}: ReactionTooltipContentProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const users: PersonalDetails[] = useMemo( - // TODO: remove eslint disable when https://github.com/Expensify/App/pull/30169 is merged - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - () => PersonalDetailsUtils.getPersonalDetailsByIDs(accountIDs, currentUserPersonalDetails.accountID, true), - [currentUserPersonalDetails.accountID, accountIDs], - ); + const users = useMemo(() => PersonalDetailsUtils.getPersonalDetailsByIDs(accountIDs, currentUserPersonalDetails.accountID, true), [currentUserPersonalDetails.accountID, accountIDs]); const namesString = users .map((user) => user?.displayName) .filter((name) => name) From a21eef6ff8f9d760ab9aa73938c5ae9c9f17bc9e Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 21 Dec 2023 10:27:18 +0100 Subject: [PATCH 06/16] Type improvements --- .../Reactions/AddReactionBubble.tsx | 4 ---- .../Reactions/MiniQuickEmojiReactions.tsx | 4 ---- .../Reactions/ReactionTooltipContent.tsx | 1 + .../ReportActionItemEmojiReactions.tsx | 21 ++++++++++++++++++- src/libs/actions/EmojiPickerAction.ts | 14 +++++++------ 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/components/Reactions/AddReactionBubble.tsx b/src/components/Reactions/AddReactionBubble.tsx index df2150bc9237..9235b33ecdf4 100644 --- a/src/components/Reactions/AddReactionBubble.tsx +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -58,10 +58,6 @@ function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWi EmojiPickerAction.showEmojiPicker( () => {}, (emojiCode, emojiObject) => { - if (!emojiObject) { - return; - } - onSelectEmoji(emojiObject); }, refParam ?? ref.current, diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx index 5d028286533b..7a874139820e 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.tsx +++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx @@ -52,10 +52,6 @@ function MiniQuickEmojiReactions({ EmojiPickerAction.showEmojiPicker( onEmojiPickerClosed, (emojiCode, emojiObject) => { - if (!emojiObject) { - return; - } - onEmojiSelected(emojiObject, emojiReactions); }, ref.current, diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx index 83f214e7dd7d..824a4d6a6571 100644 --- a/src/components/Reactions/ReactionTooltipContent.tsx +++ b/src/components/Reactions/ReactionTooltipContent.tsx @@ -27,6 +27,7 @@ function ReactionTooltipContent({accountIDs, currentUserPersonalDetails = {}, em const styles = useThemeStyles(); const {translate} = useLocalize(); const users = useMemo(() => PersonalDetailsUtils.getPersonalDetailsByIDs(accountIDs, currentUserPersonalDetails.accountID, true), [currentUserPersonalDetails.accountID, accountIDs]); + const namesString = users .map((user) => user?.displayName) .filter((name) => name) diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index 38630b8887bf..a2d5208be589 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -38,15 +38,34 @@ type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & shouldBlockReactions?: boolean; }; +type PopoverReactionListAnchors = Record; + type FormattedReaction = { + /** The emoji codes to display in the bubble */ emojiCodes: string[]; + + /** IDs of users used the reaction */ userAccountIDs: number[]; + + /** Total reaction count */ reactionCount: number; + + /** Whether the current account has reacted to the report action */ hasUserReacted: boolean; + + /** Oldest timestamp of when the emoji was added */ oldestTimestamp: string; + + /** Callback to fire on press */ onPress: () => void; + + /** Callback to fire on reaction list open */ onReactionListOpen: (event: GestureResponderEvent | MouseEvent) => void; + + /** The name of the emoji */ reactionEmojiName: string; + + /** The type of action that's pending */ pendingAction: PendingAction; }; @@ -60,7 +79,7 @@ function ReportActionItemEmojiReactions({ }: ReportActionItemEmojiReactionsProps) { const styles = useThemeStyles(); const reactionListRef = useContext(ReactionListContext); - const popoverReactionListAnchors = useRef>({}); + const popoverReactionListAnchors = useRef({}); let totalReactionCount = 0; diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index a6873bc14d04..b259f30a1c85 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -14,11 +14,13 @@ type EmojiPopoverAnchor = View | HTMLDivElement | ReportActionContextMenu | Text type OnWillShowPicker = (callback: CloseContextMenuCallback) => void; +type OnModalHideValue = () => void; + // TODO: Move this type to src/components/EmojiPicker/EmojiPicker.js once it is converted to TS type EmojiPickerRef = { showEmojiPicker: ( - onModalHideValue?: () => void, - onEmojiSelectedValue?: () => void, + onModalHideValue?: OnModalHideValue, + onEmojiSelectedValue?: OnEmojiSelected, emojiPopoverAnchor?: EmojiPopoverAnchor, anchorOrigin?: AnchorOrigin, onWillShow?: OnWillShowPicker, @@ -31,7 +33,7 @@ type EmojiPickerRef = { resetEmojiPopoverAnchor: () => void; }; -type OnEmojiSelected = (emojiCode?: string, emojiObject?: Emoji) => void; +type OnEmojiSelected = (emojiCode: string, emojiObject: Emoji) => void; const emojiPickerRef = React.createRef(); @@ -46,12 +48,12 @@ const emojiPickerRef = React.createRef(); * @param id - Unique id for EmojiPicker */ function showEmojiPicker( - onModalHide = () => {}, + onModalHide: OnModalHideValue = () => {}, onEmojiSelected: OnEmojiSelected = () => {}, emojiPopoverAnchor: EmojiPopoverAnchor = null, - anchorOrigin: AnchorOrigin | undefined = undefined, + anchorOrigin?: AnchorOrigin, onWillShow: OnWillShowPicker = () => {}, - id: string | undefined = undefined, + id?: string, ) { if (!emojiPickerRef.current) { return; From 78d61d78ed5c12a9f6980719228636540801d3f9 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 21 Dec 2023 10:52:53 +0100 Subject: [PATCH 07/16] Add ReactionListEvent and ReactionListAnchor types --- .../Reactions/EmojiReactionBubble.tsx | 4 +-- .../Reactions/QuickEmojiReactions/types.ts | 33 ++----------------- .../ReportActionItemEmojiReactions.tsx | 10 +++--- src/pages/home/ReportScreenContext.ts | 8 +++-- 4 files changed, 16 insertions(+), 39 deletions(-) diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx index c4c0db50cc86..9d0c4fa89916 100644 --- a/src/components/Reactions/EmojiReactionBubble.tsx +++ b/src/components/Reactions/EmojiReactionBubble.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import {GestureResponderEvent} from 'react-native'; import {PressableRef} from '@components/Pressable/GenericPressable/types'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {ReactionListEvent} from '@pages/home/ReportScreenContext'; import CONST from '@src/CONST'; type EmojiReactionBubbleProps = { @@ -23,7 +23,7 @@ type EmojiReactionBubbleProps = { * Called when the user long presses or right clicks * on the reaction bubble. */ - onReactionListOpen?: (event: GestureResponderEvent | MouseEvent) => void; + onReactionListOpen?: (event: ReactionListEvent) => void; /** * The number of reactions to display in the bubble. diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index b6751e780386..f96b1b8bcd75 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -1,37 +1,11 @@ -import {GestureResponderEvent, Text as RNText, TextInput} from 'react-native'; +import {TextInput} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; -type ShowContextMenu = ( - type: 'LINK' | 'REPORT_ACTION' | 'EMAIL' | 'REPORT', - event: GestureResponderEvent | MouseEvent, - selection: string, - contextMenuAnchor: RNText | null, - reportID?: string, - reportActionID?: string, - originalReportID?: string, - draftMessage?: string, - onShow?: () => void, - onHide?: () => void, - isArchivedRoom?: boolean, - isChronosReport?: boolean, - isPinnedChat?: boolean, - isUnreadChat?: boolean, -) => void; - -// TODO: remove when https://github.com/Expensify/App/pull/32670 is merged -type ReportActionContextMenu = { - showContextMenu: ShowContextMenu; - hideContextMenu: (callback: () => void) => void; - showDeleteModal: (reportID: string, reportAction: OnyxEntry, shouldSetModalVisibility?: boolean, onConfirm?: () => void, onCancel?: () => void) => void; - hideDeleteModal: () => void; - isActiveReportAction: (accountID: string | number) => boolean; - instanceID: string; - runAndResetOnPopoverHide: () => void; - clearActiveReportAction: () => void; -}; +// TODO: remove when ReportActionContextMenu file migration https://github.com/Expensify/App/pull/32670 is merged +type ReportActionContextMenu = Record; type PickerRefElement = TextInput | ReportActionContextMenu | null; @@ -70,7 +44,6 @@ type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & { reportAction: ReportAction; /** Id of the ReportAction for EmojiPicker. */ - // eslint-disable-next-line react/no-unused-prop-types -- It's used inside withOnyx HOC reportActionID: string; }; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index a2d5208be589..843474cd8e9e 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -1,6 +1,6 @@ import sortBy from 'lodash/sortBy'; import React, {useContext, useRef} from 'react'; -import {GestureResponderEvent, View} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {Emoji} from '@assets/emojis/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; @@ -8,7 +8,7 @@ import Tooltip from '@components/Tooltip'; import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; import * as EmojiUtils from '@libs/EmojiUtils'; -import {ReactionListContext} from '@pages/home/ReportScreenContext'; +import {type ReactionListAnchor, ReactionListContext, type ReactionListEvent} from '@pages/home/ReportScreenContext'; import CONST from '@src/CONST'; import type {ReportAction, ReportActionReactions} from '@src/types/onyx'; import {Locale} from '@src/types/onyx'; @@ -38,7 +38,7 @@ type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & shouldBlockReactions?: boolean; }; -type PopoverReactionListAnchors = Record; +type PopoverReactionListAnchors = Record; type FormattedReaction = { /** The emoji codes to display in the bubble */ @@ -60,7 +60,7 @@ type FormattedReaction = { onPress: () => void; /** Callback to fire on reaction list open */ - onReactionListOpen: (event: GestureResponderEvent | MouseEvent) => void; + onReactionListOpen: (event: ReactionListEvent) => void; /** The name of the emoji */ reactionEmojiName: string; @@ -103,7 +103,7 @@ function ReportActionItemEmojiReactions({ toggleReaction(emoji); }; - const onReactionListOpen = (event: GestureResponderEvent | MouseEvent) => { + const onReactionListOpen = (event: ReactionListEvent) => { reactionListRef?.current?.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID); }; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts index 9cbdeee06c51..6387fd83b751 100644 --- a/src/pages/home/ReportScreenContext.ts +++ b/src/pages/home/ReportScreenContext.ts @@ -1,8 +1,12 @@ import {createContext, RefObject} from 'react'; import {FlatList, GestureResponderEvent, View} from 'react-native'; +type ReactionListAnchor = View | HTMLDivElement | null; + +type ReactionListEvent = GestureResponderEvent | MouseEvent; + type ReactionListRef = { - showReactionList: (event: GestureResponderEvent | MouseEvent | undefined, reactionListAnchor: View | HTMLDivElement | null, emojiName: string, reportActionID: string) => void; + showReactionList: (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor, emojiName: string, reportActionID: string) => void; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; }; @@ -20,4 +24,4 @@ const ActionListContext = createContext({flatListRef: nul const ReactionListContext = createContext(null); export {ActionListContext, ReactionListContext}; -export type {ReactionListRef, ActionListContextType, ReactionListContextType, FlatListRefType}; +export type {ReactionListRef, ActionListContextType, ReactionListContextType, FlatListRefType, ReactionListAnchor, ReactionListEvent}; From 6e94929a34f82ccff4ff17ba8d683f8d21fce6ac Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 21 Dec 2023 12:26:06 +0100 Subject: [PATCH 08/16] Undo unnecessary updates in withCurrentUserPersonalDetails HOC --- .../Reactions/ReactionTooltipContent.tsx | 4 ++-- src/components/withCurrentUserPersonalDetails.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx index 824a4d6a6571..198eba1f969c 100644 --- a/src/components/Reactions/ReactionTooltipContent.tsx +++ b/src/components/Reactions/ReactionTooltipContent.tsx @@ -1,12 +1,12 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; -import type {WithCurrentUserPersonalDetailsHOCProps} from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; -type ReactionTooltipContentProps = WithCurrentUserPersonalDetailsHOCProps & { +type ReactionTooltipContentProps = Pick & { /** * A list of emoji codes to display in the tooltip. */ diff --git a/src/components/withCurrentUserPersonalDetails.tsx b/src/components/withCurrentUserPersonalDetails.tsx index fe8c101b2625..289e2254952f 100644 --- a/src/components/withCurrentUserPersonalDetails.tsx +++ b/src/components/withCurrentUserPersonalDetails.tsx @@ -14,25 +14,25 @@ type OnyxProps = { session: OnyxEntry; }; -type WithCurrentUserPersonalDetailsHOCProps = { +type HOCProps = { currentUserPersonalDetails: CurrentUserPersonalDetails; }; -type WithCurrentUserPersonalDetailsProps = OnyxProps & WithCurrentUserPersonalDetailsHOCProps; +type WithCurrentUserPersonalDetailsProps = OnyxProps & HOCProps; // TODO: remove when all components that use it will be migrated to TS const withCurrentUserPersonalDetailsPropTypes = { currentUserPersonalDetails: personalDetailsPropType, }; -const withCurrentUserPersonalDetailsDefaultProps: WithCurrentUserPersonalDetailsHOCProps = { +const withCurrentUserPersonalDetailsDefaultProps: HOCProps = { currentUserPersonalDetails: {}, }; export default function ( WrappedComponent: ComponentType>, -): ComponentType & RefAttributes, keyof OnyxProps>> { - function WithCurrentUserPersonalDetails(props: Omit, ref: ForwardedRef) { +): ComponentType & RefAttributes, keyof OnyxProps>> { + function WithCurrentUserPersonalDetails(props: Omit, ref: ForwardedRef) { const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT; const accountID = props.session?.accountID ?? 0; const accountPersonalDetails = personalDetails?.[accountID]; @@ -54,7 +54,7 @@ export default function & RefAttributes, OnyxProps>({ + return withOnyx & RefAttributes, OnyxProps>({ session: { key: ONYXKEYS.SESSION, }, @@ -62,4 +62,4 @@ export default function Date: Thu, 21 Dec 2023 12:36:23 +0100 Subject: [PATCH 09/16] Get rid of extra emoji types --- assets/emojis/types.ts | 6 ++++-- src/components/EmojiSuggestions.tsx | 8 +++---- src/libs/EmojiTrie.ts | 33 ++++++----------------------- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/assets/emojis/types.ts b/assets/emojis/types.ts index 28e3a2b31561..7190dbc74d8c 100644 --- a/assets/emojis/types.ts +++ b/assets/emojis/types.ts @@ -12,8 +12,10 @@ type HeaderEmoji = { code: string; }; -type PickerEmojis = Array; +type PickerEmoji = Emoji | HeaderEmoji; + +type PickerEmojis = PickerEmoji[]; type EmojisList = Record; -export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis}; +export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis, PickerEmoji}; diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 2fcf8e827b4e..b12ed0afb176 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -1,8 +1,8 @@ import React, {ReactElement, useCallback} from 'react'; import {View} from 'react-native'; +import type {Emoji} from '@assets/emojis/types'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {SimpleEmoji} from '@libs/EmojiTrie'; import * as EmojiUtils from '@libs/EmojiUtils'; import getStyledTextArray from '@libs/GetStyledTextArray'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; @@ -15,7 +15,7 @@ type EmojiSuggestionsProps = { highlightedEmojiIndex?: number; /** Array of suggested emoji */ - emojis: SimpleEmoji[]; + emojis: Emoji[]; /** Fired when the user selects an emoji */ onSelect: (index: number) => void; @@ -39,7 +39,7 @@ type EmojiSuggestionsProps = { /** * Create unique keys for each emoji item */ -const keyExtractor = (item: SimpleEmoji, index: number): string => `${item.name}+${index}}`; +const keyExtractor = (item: Emoji, index: number): string => `${item.name}+${index}}`; function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferredSkinToneIndex, highlightedEmojiIndex = 0, measureParentContainer = () => {}}: EmojiSuggestionsProps) { const styles = useThemeStyles(); @@ -48,7 +48,7 @@ function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferr * Render an emoji suggestion menu item component. */ const renderSuggestionMenuItem = useCallback( - (item: SimpleEmoji): ReactElement => { + (item: Emoji): ReactElement => { const styledTextArray = getStyledTextArray(item.name, prefix); return ( diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index f13652bed132..c9f2595b82cf 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -1,24 +1,9 @@ -import React from 'react'; -import {SvgProps} from 'react-native-svg'; import emojis, {localeEmojis} from '@assets/emojis'; +import type {Emoji, HeaderEmoji, PickerEmoji} from '@assets/emojis/types'; import CONST from '@src/CONST'; import Timing from './actions/Timing'; import Trie from './Trie'; -type HeaderEmoji = { - code: string; - header: boolean; - icon: React.FC; -}; - -type SimpleEmoji = { - code: string; - name: string; - types?: readonly string[]; -}; - -type Emoji = HeaderEmoji | SimpleEmoji; - type LocalizedEmoji = { name?: string; keywords: string[]; @@ -26,14 +11,8 @@ type LocalizedEmoji = { type LocalizedEmojis = Record; -type Suggestion = { - code: string; - types?: readonly string[]; - name: string; -}; - type EmojiMetaData = { - suggestions?: Suggestion[]; + suggestions?: Emoji[]; code?: string; types?: string[]; name?: string; @@ -57,7 +36,7 @@ type EmojiTrie = { * @param name The localized name of the emoji. * @param shouldPrependKeyword Prepend the keyword (instead of append) to the suggestions */ -function addKeywordsToTrie(trie: Trie, keywords: string[], item: SimpleEmoji, name: string, shouldPrependKeyword = false) { +function addKeywordsToTrie(trie: Trie, keywords: string[], item: Emoji, name: string, shouldPrependKeyword = false) { keywords.forEach((keyword) => { const keywordNode = trie.search(keyword); if (!keywordNode) { @@ -91,8 +70,8 @@ function createTrie(lang: SupportedLanguage = CONST.LOCALES.DEFAULT): Trie !(item as HeaderEmoji).header) - .forEach((item: SimpleEmoji) => { + .filter((item: PickerEmoji): item is Emoji => !(item as HeaderEmoji).header) + .forEach((item: Emoji) => { const englishName = item.name; const localeName = langEmojis?.[item.code]?.name ?? englishName; @@ -128,4 +107,4 @@ const emojiTrie: EmojiTrie = supportedLanguages.reduce((prev, cur) => ({...prev, Timing.end(CONST.TIMING.TRIE_INITIALIZATION); export default emojiTrie; -export type {SimpleEmoji, SupportedLanguage}; +export type {SupportedLanguage}; From e1670cab2cdb78df227a7412de434e82ccb5fd8c Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 21 Dec 2023 13:10:58 +0100 Subject: [PATCH 10/16] Add LocaleEmojis type --- assets/emojis/index.ts | 9 ++++++--- src/libs/EmojiTrie.ts | 11 ++--------- src/libs/EmojiUtils.ts | 12 ++++-------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/assets/emojis/index.ts b/assets/emojis/index.ts index aade4e557a64..f05bac7219f5 100644 --- a/assets/emojis/index.ts +++ b/assets/emojis/index.ts @@ -1,10 +1,13 @@ +import type {Locale} from '@src/types/onyx'; import emojis from './common'; import enEmojis from './en'; import esEmojis from './es'; -import {Emoji} from './types'; +import {Emoji, EmojisList} from './types'; type EmojiTable = Record; +type LocaleEmojis = Partial>; + const emojiNameTable = emojis.reduce((prev, cur) => { const newValue = prev; if (!('header' in cur) && cur.name) { @@ -26,10 +29,10 @@ const emojiCodeTableWithSkinTones = emojis.reduce((prev, cur) => { return newValue; }, {}); -const localeEmojis = { +const localeEmojis: LocaleEmojis = { en: enEmojis, es: esEmojis, -} as const; +}; export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis}; export {skinTones, categoryFrequentlyUsed, default} from './common'; diff --git a/src/libs/EmojiTrie.ts b/src/libs/EmojiTrie.ts index c9f2595b82cf..d0f0b0dcfab6 100644 --- a/src/libs/EmojiTrie.ts +++ b/src/libs/EmojiTrie.ts @@ -4,13 +4,6 @@ import CONST from '@src/CONST'; import Timing from './actions/Timing'; import Trie from './Trie'; -type LocalizedEmoji = { - name?: string; - keywords: string[]; -}; - -type LocalizedEmojis = Record; - type EmojiMetaData = { suggestions?: Emoji[]; code?: string; @@ -65,8 +58,8 @@ function getNameParts(name: string): string[] { function createTrie(lang: SupportedLanguage = CONST.LOCALES.DEFAULT): Trie { const trie = new Trie(); - const langEmojis: LocalizedEmojis = localeEmojis[lang]; - const defaultLangEmojis: LocalizedEmojis = localeEmojis[CONST.LOCALES.DEFAULT]; + const langEmojis = localeEmojis[lang]; + const defaultLangEmojis = localeEmojis[CONST.LOCALES.DEFAULT]; const isDefaultLocale = lang === CONST.LOCALES.DEFAULT; emojis diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 21c8e685d61e..19c709c8b6f5 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -53,12 +53,8 @@ const getLocalizedEmojiName = (name: string, lang: OnyxEntry): string => return name; } - if (lang === CONST.LOCALES.ES) { - const emojiCode = Emojis.emojiNameTable[name]?.code ?? ''; - return Emojis.localeEmojis[lang]?.[emojiCode]?.name ?? ''; - } - - return ''; + const emojiCode = Emojis.emojiNameTable[name]?.code ?? ''; + return (lang && Emojis.localeEmojis[lang]?.[emojiCode]?.name) ?? ''; }; /** @@ -443,8 +439,8 @@ const getPreferredSkinToneIndex = (value: string | number | null): number => { * based on the users preferred skin tone. */ const getPreferredEmojiCode = (emoji: Emoji, preferredSkinTone: OnyxEntry): string => { - if (emoji.types && preferredSkinTone) { - const emojiCodeWithSkinTone = emoji.types[Number(preferredSkinTone)]; + if (emoji.types && typeof preferredSkinTone === 'number') { + const emojiCodeWithSkinTone = emoji.types[preferredSkinTone]; // Note: it can happen that preferredSkinTone has a outdated format, // so it makes sense to check if we actually got a valid emoji code back From 30ea4d52e35cb7ac89ae2d37911576d29b8e1bf1 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 21 Dec 2023 14:23:28 +0100 Subject: [PATCH 11/16] Update type imports --- assets/emojis/index.ts | 2 +- .../QuickEmojiReactions/BaseQuickEmojiReactions.tsx | 2 +- src/components/Reactions/ReportActionItemEmojiReactions.tsx | 5 ++--- src/libs/EmojiUtils.ts | 2 +- src/libs/actions/EmojiPickerAction.ts | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/assets/emojis/index.ts b/assets/emojis/index.ts index f05bac7219f5..32dc9ebd9c73 100644 --- a/assets/emojis/index.ts +++ b/assets/emojis/index.ts @@ -2,7 +2,7 @@ import type {Locale} from '@src/types/onyx'; import emojis from './common'; import enEmojis from './en'; import esEmojis from './es'; -import {Emoji, EmojisList} from './types'; +import type {Emoji, EmojisList} from './types'; type EmojiTable = Record; diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx index 1e74e2d6bf70..58973e90b9c4 100644 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import {Emoji} from '@assets/emojis/types'; +import type {Emoji} from '@assets/emojis/types'; import AddReactionBubble from '@components/Reactions/AddReactionBubble'; import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble'; import Tooltip from '@components/Tooltip'; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index 843474cd8e9e..b9f2207a13e8 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -2,7 +2,7 @@ import sortBy from 'lodash/sortBy'; import React, {useContext, useRef} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {Emoji} from '@assets/emojis/types'; +import type {Emoji} from '@assets/emojis/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Tooltip from '@components/Tooltip'; import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; @@ -10,8 +10,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as EmojiUtils from '@libs/EmojiUtils'; import {type ReactionListAnchor, ReactionListContext, type ReactionListEvent} from '@pages/home/ReportScreenContext'; import CONST from '@src/CONST'; -import type {ReportAction, ReportActionReactions} from '@src/types/onyx'; -import {Locale} from '@src/types/onyx'; +import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import AddReactionBubble from './AddReactionBubble'; import EmojiReactionBubble from './EmojiReactionBubble'; diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 19c709c8b6f5..44f06af4406f 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -7,7 +7,7 @@ import * as Emojis from '@assets/emojis'; import {Emoji, HeaderEmoji, PickerEmojis} from '@assets/emojis/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {FrequentlyUsedEmoji, Locale} from '@src/types/onyx'; +import type {FrequentlyUsedEmoji, Locale} from '@src/types/onyx'; import {ReportActionReaction, UsersReactions} from '@src/types/onyx/ReportActionReactions'; import {SupportedLanguage} from './EmojiTrie'; diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index b259f30a1c85..0b8aa0ac116f 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -1,7 +1,7 @@ import React from 'react'; import {TextInput, View} from 'react-native'; import {ValueOf} from 'type-fest'; -import {Emoji} from '@assets/emojis/types'; +import type {Emoji} from '@assets/emojis/types'; import type {CloseContextMenuCallback, ReportActionContextMenu} from '@components/Reactions/QuickEmojiReactions/types'; import CONST from '@src/CONST'; From 60c58b03568ef5f9aa533c8e2cfa7d05b5a270b2 Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Thu, 21 Dec 2023 17:35:42 +0100 Subject: [PATCH 12/16] Update TODO comment --- src/components/Reactions/QuickEmojiReactions/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index f96b1b8bcd75..e53ca81c3a6a 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -4,7 +4,7 @@ import type {Emoji} from '@assets/emojis/types'; import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; -// TODO: remove when ReportActionContextMenu file migration https://github.com/Expensify/App/pull/32670 is merged +// TODO: Remove this once AnchorForCommentsOnly (https://github.com/Expensify/App/issues/25135) is migrated to TypeScript. type ReportActionContextMenu = Record; type PickerRefElement = TextInput | ReportActionContextMenu | null; From 0137c9aafc729d23093782f7aa51ca21a1e1eaab Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Fri, 22 Dec 2023 09:30:18 +0100 Subject: [PATCH 13/16] TS fixes after merging main --- .../Reactions/QuickEmojiReactions/index.tsx | 2 +- .../Reactions/QuickEmojiReactions/types.ts | 17 +++-------------- src/libs/actions/EmojiPickerAction.ts | 4 ++-- .../ContextMenu/ReportActionContextMenu.ts | 5 +++-- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/components/Reactions/QuickEmojiReactions/index.tsx b/src/components/Reactions/QuickEmojiReactions/index.tsx index cb900f652f2a..16b3eccc331b 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.tsx @@ -6,7 +6,7 @@ import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsProps) { const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { - openPicker?.(contextMenuRef.current.contentRef.current, { + openPicker?.(contextMenuRef.current?.contentRef.current, { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }); diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index e53ca81c3a6a..7a79ba351855 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -1,13 +1,10 @@ -import {TextInput} from 'react-native'; +import {TextInput, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; -// TODO: Remove this once AnchorForCommentsOnly (https://github.com/Expensify/App/issues/25135) is migrated to TypeScript. -type ReportActionContextMenu = Record; - -type PickerRefElement = TextInput | ReportActionContextMenu | null; +type PickerRefElement = TextInput | View | null | undefined; type OpenPickerCallback = (element: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; @@ -55,12 +52,4 @@ type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { closeContextMenu: (callback: CloseContextMenuCallback) => void; }; -export type { - BaseQuickEmojiReactionsProps, - BaseQuickEmojiReactionsOnyxProps, - QuickEmojiReactionsProps, - OpenPickerCallback, - CloseContextMenuCallback, - ReportActionContextMenu, - PickerRefElement, -}; +export type {BaseQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps, QuickEmojiReactionsProps, OpenPickerCallback, CloseContextMenuCallback, PickerRefElement}; diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index 0b8aa0ac116f..bf54ac522989 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -2,7 +2,7 @@ import React from 'react'; import {TextInput, View} from 'react-native'; import {ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; -import type {CloseContextMenuCallback, ReportActionContextMenu} from '@components/Reactions/QuickEmojiReactions/types'; +import type {CloseContextMenuCallback} from '@components/Reactions/QuickEmojiReactions/types'; import CONST from '@src/CONST'; type AnchorOrigin = { @@ -10,7 +10,7 @@ type AnchorOrigin = { vertical: ValueOf; }; -type EmojiPopoverAnchor = View | HTMLDivElement | ReportActionContextMenu | TextInput | null; +type EmojiPopoverAnchor = View | HTMLDivElement | TextInput | null; type OnWillShowPicker = (callback: CloseContextMenuCallback) => void; diff --git a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts index b269bc276b55..63ce519a17ea 100644 --- a/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts +++ b/src/pages/home/report/ContextMenu/ReportActionContextMenu.ts @@ -1,5 +1,5 @@ -import React from 'react'; -import {GestureResponderEvent, Text as RNText} from 'react-native'; +import React, {RefObject} from 'react'; +import {GestureResponderEvent, Text as RNText, View} from 'react-native'; import {OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; @@ -39,6 +39,7 @@ type ReportActionContextMenu = { instanceID: string; runAndResetOnPopoverHide: () => void; clearActiveReportAction: () => void; + contentRef: RefObject; }; const contextMenuRef = React.createRef(); From 0ae9f50f07d9e35e7dc8a3422e924502c8c81dbb Mon Sep 17 00:00:00 2001 From: Viktoryia Kliushun Date: Wed, 27 Dec 2023 12:45:20 +0100 Subject: [PATCH 14/16] Fix TS issues after merging main --- .../Reactions/QuickEmojiReactions/index.tsx | 2 +- .../Reactions/QuickEmojiReactions/types.ts | 5 +++-- src/libs/actions/EmojiPickerAction.ts | 16 ++++++++-------- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/Reactions/QuickEmojiReactions/index.tsx b/src/components/Reactions/QuickEmojiReactions/index.tsx index 16b3eccc331b..98c6378e44e2 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.tsx @@ -6,7 +6,7 @@ import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsProps) { const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { - openPicker?.(contextMenuRef.current?.contentRef.current, { + openPicker?.(contextMenuRef.current?.contentRef, { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, }); diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index 7a79ba351855..558a3cda4e53 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -1,12 +1,13 @@ +import {RefObject} from 'react'; import {TextInput, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; -type PickerRefElement = TextInput | View | null | undefined; +type PickerRefElement = RefObject; -type OpenPickerCallback = (element: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; +type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; type CloseContextMenuCallback = () => void; diff --git a/src/libs/actions/EmojiPickerAction.ts b/src/libs/actions/EmojiPickerAction.ts index bf54ac522989..b2618551e633 100644 --- a/src/libs/actions/EmojiPickerAction.ts +++ b/src/libs/actions/EmojiPickerAction.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {MutableRefObject} from 'react'; import {TextInput, View} from 'react-native'; import {ValueOf} from 'type-fest'; import type {Emoji} from '@assets/emojis/types'; @@ -10,7 +10,7 @@ type AnchorOrigin = { vertical: ValueOf; }; -type EmojiPopoverAnchor = View | HTMLDivElement | TextInput | null; +type EmojiPopoverAnchor = MutableRefObject; type OnWillShowPicker = (callback: CloseContextMenuCallback) => void; @@ -19,9 +19,9 @@ type OnModalHideValue = () => void; // TODO: Move this type to src/components/EmojiPicker/EmojiPicker.js once it is converted to TS type EmojiPickerRef = { showEmojiPicker: ( - onModalHideValue?: OnModalHideValue, - onEmojiSelectedValue?: OnEmojiSelected, - emojiPopoverAnchor?: EmojiPopoverAnchor, + onModalHideValue: OnModalHideValue, + onEmojiSelectedValue: OnEmojiSelected, + emojiPopoverAnchor: EmojiPopoverAnchor, anchorOrigin?: AnchorOrigin, onWillShow?: OnWillShowPicker, id?: string, @@ -48,9 +48,9 @@ const emojiPickerRef = React.createRef(); * @param id - Unique id for EmojiPicker */ function showEmojiPicker( - onModalHide: OnModalHideValue = () => {}, - onEmojiSelected: OnEmojiSelected = () => {}, - emojiPopoverAnchor: EmojiPopoverAnchor = null, + onModalHide: OnModalHideValue, + onEmojiSelected: OnEmojiSelected, + emojiPopoverAnchor: EmojiPopoverAnchor, anchorOrigin?: AnchorOrigin, onWillShow: OnWillShowPicker = () => {}, id?: string, From 8ed60173ff1d3ed99cb47fe868675d22253a1aa2 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 2 Jan 2024 17:40:04 +0100 Subject: [PATCH 15/16] Update MiniQuickEmojiReactionsOnyxProps type --- .../Reactions/MiniQuickEmojiReactions.tsx | 13 ++++++++++--- .../Reactions/QuickEmojiReactions/index.native.tsx | 4 ++-- .../Reactions/QuickEmojiReactions/index.tsx | 4 ++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx index cebe40e6835c..623ecd19a93a 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.tsx +++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx @@ -1,6 +1,6 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem'; import Icon from '@components/Icon'; @@ -15,9 +15,16 @@ import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types'; +import type {ReportActionReactions} from '@src/types/onyx'; +import type {BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types'; -type MiniQuickEmojiReactionsOnyxProps = Omit; +type MiniQuickEmojiReactionsOnyxProps = { + /** All the emoji reactions for the report action. */ + emojiReactions: OnyxEntry; + + /** The user's preferred skin tone. */ + preferredSkinTone: OnyxEntry; +}; type MiniQuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { /** diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.tsx b/src/components/Reactions/QuickEmojiReactions/index.native.tsx index 9b2fadd3e284..b0eb88b31b68 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.native.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx @@ -3,7 +3,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; -function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsProps) { +function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) { const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { // We first need to close the menu as it's a popover. // The picker is a popover as well and on mobile there can only @@ -19,7 +19,7 @@ function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsPr return ( ); diff --git a/src/components/Reactions/QuickEmojiReactions/index.tsx b/src/components/Reactions/QuickEmojiReactions/index.tsx index 98c6378e44e2..3b44f4fe4826 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.tsx +++ b/src/components/Reactions/QuickEmojiReactions/index.tsx @@ -4,7 +4,7 @@ import CONST from '@src/CONST'; import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; -function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsProps) { +function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) { const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { openPicker?.(contextMenuRef.current?.contentRef, { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, @@ -15,7 +15,7 @@ function QuickEmojiReactions({closeContextMenu, ...props}: QuickEmojiReactionsPr return ( From 1d4010ec667d661f7d09665a17a0cd12901edae4 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Thu, 4 Jan 2024 10:11:58 +0100 Subject: [PATCH 16/16] Fix lint errors --- src/components/Reactions/EmojiReactionBubble.tsx | 2 +- src/components/Reactions/MiniQuickEmojiReactions.tsx | 3 ++- src/components/Reactions/QuickEmojiReactions/types.ts | 4 ++-- src/components/Reactions/ReportActionItemEmojiReactions.tsx | 6 ++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx index 9d0c4fa89916..d83689de2dc1 100644 --- a/src/components/Reactions/EmojiReactionBubble.tsx +++ b/src/components/Reactions/EmojiReactionBubble.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {PressableRef} from '@components/Pressable/GenericPressable/types'; +import type {PressableRef} from '@components/Pressable/GenericPressable/types'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; diff --git a/src/components/Reactions/MiniQuickEmojiReactions.tsx b/src/components/Reactions/MiniQuickEmojiReactions.tsx index 623ecd19a93a..9f38da6bdb3d 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.tsx +++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx @@ -1,6 +1,7 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem'; import Icon from '@components/Icon'; diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts index 558a3cda4e53..d782d5ae35c7 100644 --- a/src/components/Reactions/QuickEmojiReactions/types.ts +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -1,5 +1,5 @@ -import {RefObject} from 'react'; -import {TextInput, View} from 'react-native'; +import type {RefObject} from 'react'; +import type {TextInput, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.tsx b/src/components/Reactions/ReportActionItemEmojiReactions.tsx index b9f2207a13e8..d1a2cf56b6a5 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.tsx +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -5,10 +5,12 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {Emoji} from '@assets/emojis/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Tooltip from '@components/Tooltip'; -import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; import * as EmojiUtils from '@libs/EmojiUtils'; -import {type ReactionListAnchor, ReactionListContext, type ReactionListEvent} from '@pages/home/ReportScreenContext'; +import {ReactionListContext} from '@pages/home/ReportScreenContext'; +import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext'; import CONST from '@src/CONST'; import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; import type {PendingAction} from '@src/types/onyx/OnyxCommon';