From ec74f831b88083175fa147d3ebb69cea73d4f842 Mon Sep 17 00:00:00 2001 From: Vadym Date: Mon, 15 Jan 2024 02:10:23 +0000 Subject: [PATCH 01/28] chore: migrates reactionPropTypes, BaseReactionList and HeaderReactionList to TS --- ...seReactionList.js => BaseReactionList.tsx} | 53 +++++++++---------- .../report/ReactionList/HeaderReactionList.js | 48 ----------------- .../ReactionList/HeaderReactionList.tsx | 48 +++++++++++++++++ .../report/ReactionList/reactionPropTypes.js | 17 ------ .../report/ReactionList/reactionPropTypes.ts | 13 +++++ 5 files changed, 86 insertions(+), 93 deletions(-) rename src/pages/home/report/ReactionList/{BaseReactionList.js => BaseReactionList.tsx} (69%) delete mode 100644 src/pages/home/report/ReactionList/HeaderReactionList.js create mode 100644 src/pages/home/report/ReactionList/HeaderReactionList.tsx delete mode 100644 src/pages/home/report/ReactionList/reactionPropTypes.js create mode 100644 src/pages/home/report/ReactionList/reactionPropTypes.ts diff --git a/src/pages/home/report/ReactionList/BaseReactionList.js b/src/pages/home/report/ReactionList/BaseReactionList.tsx similarity index 69% rename from src/pages/home/report/ReactionList/BaseReactionList.js rename to src/pages/home/report/ReactionList/BaseReactionList.tsx index 2d881d080c31..82541b28c9c8 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.js +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -1,36 +1,34 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import Str from 'expensify-common/lib/str'; -import PropTypes from 'prop-types'; import React from 'react'; -import {FlatList} from 'react-native'; +import {FlatList, type FlatListProps} from 'react-native'; import OptionRow from '@components/OptionRow'; -import participantPropTypes from '@components/participantPropTypes'; -import withWindowDimensions from '@components/withWindowDimensions'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import Navigation from '@libs/Navigation/Navigation'; import * as UserUtils from '@libs/UserUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; +import type {PersonalDetails} from '@src/types/onyx'; import HeaderReactionList from './HeaderReactionList'; -import reactionPropTypes from './reactionPropTypes'; +import type {ReactionListProps} from './reactionPropTypes'; -const propTypes = { +type BaseReactionListProps = ReactionListProps & { /** * Array of personal detail objects */ - users: PropTypes.arrayOf(participantPropTypes).isRequired, + users: PersonalDetails[]; /** * Returns true if the current account has reacted to the report action (with the given skin tone). */ - hasUserReacted: PropTypes.bool, + hasUserReacted: boolean; - ...reactionPropTypes, -}; - -const defaultProps = { - hasUserReacted: false, + /** + * Returns true if the reaction list is visible + */ + isVisible: boolean; }; /** @@ -39,7 +37,7 @@ const defaultProps = { * @param {Number} index * @return {String} */ -const keyExtractor = (item, index) => `${item.login}+${index}`; +const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; /** * This function will be used with FlatList getItemLayout property for optimization purpose that allows skipping @@ -51,14 +49,15 @@ const keyExtractor = (item, index) => `${item.login}+${index}`; * @param {Number} index row index * @returns {Object} */ -const getItemLayout = (_, index) => ({ +const getItemLayout = (_: any, index: number): {length: number; offset: number; index: number} => ({ index, length: variables.listItemHeightNormal, offset: variables.listItemHeightNormal * index, }); -function BaseReactionList(props) { - const styles = useThemeStyles(); +function BaseReactionList(props: BaseReactionListProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + const {hoveredComponentBG, reactionListContainer, reactionListContainerFixedWidth, pv2} = useThemeStyles(); if (!props.isVisible) { return null; } @@ -72,25 +71,25 @@ function BaseReactionList(props) { * @param {Object} params.item * @return {React.Component} */ - const renderItem = ({item}) => ( + const renderItem: FlatListProps['renderItem'] = ({item}) => ( { - props.onClose(); + props.onClose && props.onClose(); Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} option={{ - text: Str.removeSMSDomain(item.displayName), + reportID: String(item.accountID), + text: Str.removeSMSDomain(item.displayName || ''), alternateText: Str.removeSMSDomain(item.login || ''), participantsList: [item], icons: [ { id: item.accountID, source: UserUtils.getAvatar(item.avatar, item.accountID), - name: item.login, + name: item.login || '', type: CONST.ICON_TYPE_AVATAR, }, ], @@ -113,15 +112,13 @@ function BaseReactionList(props) { renderItem={renderItem} keyExtractor={keyExtractor} getItemLayout={getItemLayout} - contentContainerStyle={styles.pv2} - style={[styles.reactionListContainer, !props.isSmallScreenWidth && styles.reactionListContainerFixedWidth]} + contentContainerStyle={pv2} + style={[reactionListContainer, !isSmallScreenWidth && reactionListContainerFixedWidth]} /> ); } -BaseReactionList.propTypes = propTypes; -BaseReactionList.defaultProps = defaultProps; BaseReactionList.displayName = 'BaseReactionList'; -export default withWindowDimensions(BaseReactionList); +export default BaseReactionList; diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.js b/src/pages/home/report/ReactionList/HeaderReactionList.js deleted file mode 100644 index 04b124f969a9..000000000000 --- a/src/pages/home/report/ReactionList/HeaderReactionList.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import reactionPropTypes from './reactionPropTypes'; - -const propTypes = { - ...reactionPropTypes, - ...withLocalizePropTypes, - ...windowDimensionsPropTypes, - - /** - * Returns true if the current account has reacted to the report action (with the given skin tone). - */ - hasUserReacted: PropTypes.bool, -}; - -const defaultProps = { - hasUserReacted: false, -}; - -function HeaderReactionList(props) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - return ( - - - - {props.emojiCodes.join('')} - {props.emojiCount} - - {`:${EmojiUtils.getLocalizedEmojiName(props.emojiName, props.preferredLocale)}:`} - - - ); -} - -HeaderReactionList.propTypes = propTypes; -HeaderReactionList.defaultProps = defaultProps; -HeaderReactionList.displayName = 'HeaderReactionList'; - -export default compose(withWindowDimensions, withLocalize)(HeaderReactionList); diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx new file mode 100644 index 000000000000..551059b697e6 --- /dev/null +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -0,0 +1,48 @@ +import {View} from 'react-native'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import type {ReactionListProps} from './reactionPropTypes'; + +type HeaderReactionListProps = ReactionListProps & { + /** + * Returns true if the current account has reacted to the report action (with the given skin tone). + */ + hasUserReacted: boolean; +}; + +function HeaderReactionList(props: HeaderReactionListProps) { + const { + flexRow, + justifyContentBetween, + alignItemsCenter, + emojiReactionListHeader, + pt4, + emojiReactionListHeaderBubble, + miniQuickEmojiReactionText, + reactionCounterText, + reactionListHeaderText, + } = useThemeStyles(); + const {getEmojiReactionBubbleStyle, getEmojiReactionBubbleTextStyle, getEmojiReactionCounterTextStyle} = useStyleUtils(); + const {preferredLocale} = useLocalize(); + const {isSmallScreenWidth} = useWindowDimensions(); + + return ( + + + + {props.emojiCodes.join('')} + {props.emojiCount} + + {`:${EmojiUtils.getLocalizedEmojiName(props.emojiName, preferredLocale)}:`} + + + ); +} + +HeaderReactionList.displayName = 'HeaderReactionList'; + +export default HeaderReactionList; diff --git a/src/pages/home/report/ReactionList/reactionPropTypes.js b/src/pages/home/report/ReactionList/reactionPropTypes.js deleted file mode 100644 index 3421af230399..000000000000 --- a/src/pages/home/report/ReactionList/reactionPropTypes.js +++ /dev/null @@ -1,17 +0,0 @@ -import PropTypes from 'prop-types'; - -const propTypes = { - /** Hide the ReactionList modal popover */ - onClose: PropTypes.func, - - /** The emoji codes */ - emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired, - - /** The name of the emoji */ - emojiName: PropTypes.string.isRequired, - - /** Count of the emoji */ - emojiCount: PropTypes.number.isRequired, -}; - -export default propTypes; diff --git a/src/pages/home/report/ReactionList/reactionPropTypes.ts b/src/pages/home/report/ReactionList/reactionPropTypes.ts new file mode 100644 index 000000000000..d88316754600 --- /dev/null +++ b/src/pages/home/report/ReactionList/reactionPropTypes.ts @@ -0,0 +1,13 @@ +export type ReactionListProps = { + /** Hide the ReactionList modal popover */ + onClose?: () => void; + + /** The emoji codes */ + emojiCodes: string[]; + + /** The name of the emoji */ + emojiName: string; + + /** Count of the emoji */ + emojiCount: number; +}; From baaa5a5f2c2b5a1922c4caad957378f081b66583 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 03:11:39 +0100 Subject: [PATCH 02/28] chore: migrates BasePopoverReactionList and ReactionList to TS --- ...ionList.js => BasePopoverReactionList.tsx} | 104 +++++++++++++----- .../ReactionList/PopoverReactionList/index.js | 67 ----------- .../PopoverReactionList/index.tsx | 60 ++++++++++ 3 files changed, 136 insertions(+), 95 deletions(-) rename src/pages/home/report/ReactionList/PopoverReactionList/{BasePopoverReactionList.js => BasePopoverReactionList.tsx} (65%) delete mode 100644 src/pages/home/report/ReactionList/PopoverReactionList/index.js create mode 100644 src/pages/home/report/ReactionList/PopoverReactionList/index.tsx diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx similarity index 65% rename from src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js rename to src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index a06ed18de957..8f532253fc43 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.js +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -1,36 +1,63 @@ import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; import {Dimensions} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; -import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes'; -import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withLocalize, {type WithLocalizeProps} from '@components/withLocalize'; import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {ReportActionReactions} from '@src/types/onyx'; +import {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; -const propTypes = { - reportActionID: PropTypes.string, - emojiName: PropTypes.string, - emojiReactions: EmojiReactionsPropTypes, +type BasePopoverReactionListOnyxProps = { + /** The reactions for the report action */ + emojiReactions: OnyxEntry; +}; + +type BasePopoverReactionListProps = { + /** The ID of the report action */ + reportActionID: string; + + /** The emoji name */ + emojiName: string; - ...withLocalizePropTypes, + /** The ref of the action */ + ref: React.Ref; }; -const defaultProps = { - reportActionID: '', - emojiName: '', - emojiReactions: {}, +type BasePopoverReactionListWithLocalizeProps = WithLocalizeProps & WithCurrentUserPersonalDetailsProps; + +type BasePopoverReactionListPropsWithLocalWithOnyx = BasePopoverReactionListWithLocalizeProps & BasePopoverReactionListOnyxProps & BasePopoverReactionListProps; +type BasePopoverReactionListState = { + /** Whether the popover is visible */ + isPopoverVisible: boolean; + + /** The horizontal and vertical position (relative to the screen) where the popover will display. */ + popoverAnchorPosition: { + horizontal: number; + vertical: number; + }; + + /** The horizontal and vertical position (relative to the screen) where the cursor is. */ + cursorRelativePosition: { + horizontal: number; + vertical: number; + }; }; -class BasePopoverReactionList extends React.Component { - constructor(props) { +class BasePopoverReactionList extends React.Component { + reactionListAnchor: React.RefObject; + dimensionsEventListener: { + remove: () => void; + } | null; + constructor(props: BasePopoverReactionListPropsWithLocalWithOnyx) { super(props); this.state = { @@ -60,8 +87,9 @@ class BasePopoverReactionList extends React.Component { this.dimensionsEventListener = Dimensions.addEventListener('change', this.measureReactionListPosition); } - shouldComponentUpdate(nextProps, nextState) { + shouldComponentUpdate(nextProps: BasePopoverReactionListPropsWithLocalWithOnyx, nextState: BasePopoverReactionListState) { if (!this.state.isPopoverVisible && !nextState.isPopoverVisible) { + // If the popover is not visible, we don't need to update the component return false; } @@ -81,11 +109,13 @@ class BasePopoverReactionList extends React.Component { componentDidUpdate() { if (!this.state.isPopoverVisible) { + // If the popover is not visible, we don't need to update the component return; } // Hide the list when all reactions are removed - const isEmptyList = !_.some(lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users'])); + const emojiReactions = lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users']); + const isEmptyList = !emojiReactions || !_.some(emojiReactions); if (!isEmptyList) { return; } @@ -94,6 +124,7 @@ class BasePopoverReactionList extends React.Component { } componentWillUnmount() { + // Remove the event listener if (!this.dimensionsEventListener) { return; } @@ -106,11 +137,13 @@ class BasePopoverReactionList extends React.Component { * * @returns {Promise} */ - getReactionListMeasuredLocation() { + getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { return new Promise((resolve) => { - if (this.reactionListAnchor.current) { - this.reactionListAnchor.current.measureInWindow((x, y) => resolve({x, y})); + const reactionListAnchor = this.reactionListAnchor.current as HTMLElement & {measureInWindow: (callback: (x: number, y: number) => void) => void}; + if (reactionListAnchor) { + reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); } else { + // If the anchor is not available, we return 0, 0 resolve({x: 0, y: 0}); } }); @@ -123,8 +156,9 @@ class BasePopoverReactionList extends React.Component { * @param {String} emojiName * @returns {Object} */ - getReactionInformation(selectedReaction, emojiName) { + getReactionInformation(selectedReaction: ReportActionReaction | null | undefined, emojiName: string) { if (!selectedReaction) { + // If there is no reaction, we return default values return { emojiName: '', reactionCount: 0, @@ -152,9 +186,18 @@ class BasePopoverReactionList extends React.Component { * @param {Object} [event] - A press event. * @param {Element} reactionListAnchor - reactionListAnchor */ - showReactionList(event, reactionListAnchor) { + showReactionList( + event: { + nativeEvent: { + pageX: number; + pageY: number; + }; + }, + reactionListAnchor: HTMLElement, + ) { + // We get the cursor coordinates and the reactionListAnchor coordinates to calculate the popover position const nativeEvent = event.nativeEvent || {}; - this.reactionListAnchor.current = reactionListAnchor; + this.reactionListAnchor = {current: reactionListAnchor}; this.getReactionListMeasuredLocation().then(({x, y}) => { this.setState({ cursorRelativePosition: { @@ -175,6 +218,7 @@ class BasePopoverReactionList extends React.Component { */ measureReactionListPosition() { if (!this.state.isPopoverVisible) { + // If the popover is not visible, we don't need to update the component return; } this.getReactionListMeasuredLocation().then(({x, y}) => { @@ -200,7 +244,10 @@ class BasePopoverReactionList extends React.Component { } render() { + // Get the selected reaction const selectedReaction = this.state.isPopoverVisible ? lodashGet(this.props.emojiReactions, [this.props.emojiName]) : null; + + // Get the reaction information const {emojiName, emojiCodes, reactionCount, hasUserReacted, users} = this.getReactionInformation(selectedReaction, this.props.emojiName); return ( @@ -215,9 +262,12 @@ class BasePopoverReactionList extends React.Component { fullscreen withoutOverlay anchorRef={this.reactionListAnchor} + anchorAlignment={{ + horizontal: 'left', + vertical: 'top', + }} > ({ emojiReactions: { key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, }, diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.js b/src/pages/home/report/ReactionList/PopoverReactionList/index.js deleted file mode 100644 index 9f1e7b3113fc..000000000000 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.js +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; -import BasePopoverReactionList from './BasePopoverReactionList'; - -const propTypes = { - innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), -}; - -const defaultProps = { - innerRef: () => {}, -}; - -function PopoverReactionList(props) { - const innerReactionListRef = useRef(); - const [reactionListReportActionID, setReactionListReportActionID] = useState(''); - const [reactionListEmojiName, setReactionListEmojiName] = useState(''); - - /** - * Show the ReactionList modal popover. - * - * @param {Object} [event] - A press event. - * @param {Element} reactionListAnchor - reactionListAnchor - * @param {String} emojiName - Name of emoji - * @param {String} reportActionID - ID of the report action - */ - const showReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { - setReactionListReportActionID(reportActionID); - setReactionListEmojiName(emojiName); - innerReactionListRef.current.showReactionList(event, reactionListAnchor); - }; - - const hideReactionList = () => { - innerReactionListRef.current.hideReactionList(); - }; - - /** - * Whether PopoverReactionList is active for the Report Action. - * - * @param {Number|String} actionID - * @return {Boolean} - */ - const isActiveReportAction = (actionID) => Boolean(actionID) && reactionListReportActionID === actionID; - - useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction})); - - return ( - - ); -} - -PopoverReactionList.propTypes = propTypes; -PopoverReactionList.defaultProps = defaultProps; -PopoverReactionList.displayName = 'PopoverReactionList'; - -export default React.memo( - forwardRef((props, ref) => ( - - )), -); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx new file mode 100644 index 000000000000..e4a7aa42791b --- /dev/null +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -0,0 +1,60 @@ +import React, {forwardRef, Ref, useImperativeHandle, useRef, useState} from 'react'; +import BasePopoverReactionList from './BasePopoverReactionList'; + +type PopoverReactionListProps = { + innerRef: Ref; +}; + +type InnerReactionListRefType = { + showReactionList: ( + event: { + nativeEvent: { + pageX: number; + pageY: number; + }; + }, + reactionListAnchor: HTMLElement, + ) => void; + hideReactionList: () => void; + isActiveReportAction: (actionID: number | string) => boolean; +}; + +const PopoverReactionList = (props: PopoverReactionListProps) => { + const innerReactionListRef = useRef(null); + const [reactionListReportActionID, setReactionListReportActionID] = useState(''); + const [reactionListEmojiName, setReactionListEmojiName] = useState(''); + + const showReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => { + setReactionListReportActionID(reportActionID); + setReactionListEmojiName(emojiName); + innerReactionListRef.current?.showReactionList(event, reactionListAnchor); + }; + + const hideReactionList = () => { + innerReactionListRef.current?.hideReactionList(); + }; + + const isActiveReportAction = (actionID: number | string) => Boolean(actionID) && reactionListReportActionID === actionID; + + useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction})); + + return ( + + ); +}; + +PopoverReactionList.displayName = 'PopoverReactionList'; + +export default React.memo( + forwardRef((props, ref) => ( + + )), +); From 28dfadc15e19eb23a9c361e336c698c4da4bf586 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Tue, 16 Jan 2024 10:07:10 +0100 Subject: [PATCH 03/28] Update src/pages/home/report/ReactionList/HeaderReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index 551059b697e6..869dd95baf1e 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -8,9 +8,7 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import type {ReactionListProps} from './reactionPropTypes'; type HeaderReactionListProps = ReactionListProps & { - /** - * Returns true if the current account has reacted to the report action (with the given skin tone). - */ + /** Returns true if the current account has reacted to the report action (with the given skin tone). */ hasUserReacted: boolean; }; From 952c5232776b082eecb746119bf0e5b3e399eac3 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 15:14:47 +0100 Subject: [PATCH 04/28] chore: renames the types file and fixes its linting errors --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 2 +- .../report/ReactionList/{reactionPropTypes.ts => types.ts} | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) rename src/pages/home/report/ReactionList/{reactionPropTypes.ts => types.ts} (79%) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 82541b28c9c8..5d41d2f6656f 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -12,7 +12,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {PersonalDetails} from '@src/types/onyx'; import HeaderReactionList from './HeaderReactionList'; -import type {ReactionListProps} from './reactionPropTypes'; +import type {ReactionListProps} from './types'; type BaseReactionListProps = ReactionListProps & { /** diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index 869dd95baf1e..e715f6010fc3 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -5,7 +5,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; -import type {ReactionListProps} from './reactionPropTypes'; +import type {ReactionListProps} from './types'; type HeaderReactionListProps = ReactionListProps & { /** Returns true if the current account has reacted to the report action (with the given skin tone). */ diff --git a/src/pages/home/report/ReactionList/reactionPropTypes.ts b/src/pages/home/report/ReactionList/types.ts similarity index 79% rename from src/pages/home/report/ReactionList/reactionPropTypes.ts rename to src/pages/home/report/ReactionList/types.ts index d88316754600..ec03b141080d 100644 --- a/src/pages/home/report/ReactionList/reactionPropTypes.ts +++ b/src/pages/home/report/ReactionList/types.ts @@ -1,4 +1,4 @@ -export type ReactionListProps = { +type ReactionListProps = { /** Hide the ReactionList modal popover */ onClose?: () => void; @@ -11,3 +11,5 @@ export type ReactionListProps = { /** Count of the emoji */ emojiCount: number; }; + +export type {ReactionListProps}; From efe9d72aaa7f9079b0657c66619ebdfbd857b194 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 16:52:08 +0100 Subject: [PATCH 05/28] chore: removes any typed and fixes linting errors on the index file --- .../BasePopoverReactionList.tsx | 2 ++ .../PopoverReactionList/index.tsx | 31 ++++++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 8f532253fc43..f2c226d7856a 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -54,9 +54,11 @@ type BasePopoverReactionListState = { class BasePopoverReactionList extends React.Component { reactionListAnchor: React.RefObject; + dimensionsEventListener: { remove: () => void; } | null; + constructor(props: BasePopoverReactionListPropsWithLocalWithOnyx) { super(props); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index e4a7aa42791b..8ac89cef6656 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -1,33 +1,28 @@ -import React, {forwardRef, Ref, useImperativeHandle, useRef, useState} from 'react'; +import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; +import type {ForwardedRef} from 'react'; import BasePopoverReactionList from './BasePopoverReactionList'; type PopoverReactionListProps = { - innerRef: Ref; + ref: ForwardedRef; }; +type ShowReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => void; + type InnerReactionListRefType = { - showReactionList: ( - event: { - nativeEvent: { - pageX: number; - pageY: number; - }; - }, - reactionListAnchor: HTMLElement, - ) => void; + showReactionList: ShowReactionList; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; }; -const PopoverReactionList = (props: PopoverReactionListProps) => { +function PopoverReactionList(props: PopoverReactionListProps) { const innerReactionListRef = useRef(null); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); - const showReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => { + const showReactionList: ShowReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { setReactionListReportActionID(reportActionID); setReactionListEmojiName(emojiName); - innerReactionListRef.current?.showReactionList(event, reactionListAnchor); + innerReactionListRef.current?.showReactionList(event, reactionListAnchor, emojiName, reportActionID); }; const hideReactionList = () => { @@ -36,7 +31,7 @@ const PopoverReactionList = (props: PopoverReactionListProps) => { const isActiveReportAction = (actionID: number | string) => Boolean(actionID) && reactionListReportActionID === actionID; - useImperativeHandle(props.innerRef, () => ({showReactionList, hideReactionList, isActiveReportAction})); + useImperativeHandle(props.ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); return ( { emojiName={reactionListEmojiName} /> ); -}; +} PopoverReactionList.displayName = 'PopoverReactionList'; export default React.memo( - forwardRef((props, ref) => ( + forwardRef((props, ref) => ( )), ); From a303a5e6f55cef79a27cc54eec24d650b4e20bdb Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 16 Jan 2024 17:22:07 +0100 Subject: [PATCH 06/28] fix: some linting errors and any types --- .../report/ReactionList/BaseReactionList.tsx | 36 ++++++++++--------- .../BasePopoverReactionList.tsx | 10 +++--- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 5d41d2f6656f..80cbc834f344 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -1,7 +1,8 @@ /* eslint-disable rulesdir/onyx-props-must-have-default */ import Str from 'expensify-common/lib/str'; import React from 'react'; -import {FlatList, type FlatListProps} from 'react-native'; +import {FlatList} from 'react-native'; +import type {FlatListProps} from 'react-native'; import OptionRow from '@components/OptionRow'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; @@ -33,9 +34,9 @@ type BaseReactionListProps = ReactionListProps & { /** * Create a unique key for each action in the FlatList. - * @param {Object} item - * @param {Number} index - * @return {String} + * @param item object + * @param index number + * @return string */ const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; @@ -45,11 +46,11 @@ const keyExtractor: FlatListProps['keyExtractor'] = (item, inde * Generate and return an object with properties length(height of each individual row), * offset(distance of the current row from the top of the FlatList), index(current row index) * - * @param {*} _ FlatList item - * @param {Number} index row index - * @returns {Object} + * @param data FlatList item + * @param index number - row index + * @returns object */ -const getItemLayout = (_: any, index: number): {length: number; offset: number; index: number} => ({ +const getItemLayout = (data: ArrayLike | null | undefined, index: number): {length: number; offset: number; index: number} => ({ index, length: variables.listItemHeightNormal, offset: variables.listItemHeightNormal * index, @@ -67,9 +68,9 @@ function BaseReactionList(props: BaseReactionListProps) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly * - * @param {Object} params - * @param {Object} params.item - * @return {React.Component} + * @param params object + * @param params.item object + * @return React.Component */ const renderItem: FlatListProps['renderItem'] = ({item}) => ( { - props.onClose && props.onClose(); + if (props.onClose) { + props.onClose(); + } + Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} option={{ reportID: String(item.accountID), - text: Str.removeSMSDomain(item.displayName || ''), - alternateText: Str.removeSMSDomain(item.login || ''), + text: Str.removeSMSDomain(item.displayName ?? ''), + alternateText: Str.removeSMSDomain(item.login ?? ''), participantsList: [item], icons: [ { id: item.accountID, source: UserUtils.getAvatar(item.avatar, item.accountID), - name: item.login || '', + name: item.login ?? '', type: CONST.ICON_TYPE_AVATAR, }, ], - keyForList: item.login || String(item.accountID), + keyForList: item.login ?? String(item.accountID), }} /> ); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index f2c226d7856a..4bee2e8d1d3c 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -5,8 +5,10 @@ import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; -import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withLocalize, {type WithLocalizeProps} from '@components/withLocalize'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withLocalize from '@components/withLocalize'; +import type {WithLocalizeProps} from '@components/withLocalize'; import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; @@ -14,7 +16,7 @@ import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {ReportActionReactions} from '@src/types/onyx'; -import {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; +import type {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; type BasePopoverReactionListOnyxProps = { /** The reactions for the report action */ @@ -29,7 +31,7 @@ type BasePopoverReactionListProps = { emojiName: string; /** The ref of the action */ - ref: React.Ref; + ref: React.Ref; }; type BasePopoverReactionListWithLocalizeProps = WithLocalizeProps & WithCurrentUserPersonalDetailsProps; From 0129e58af57e4a81de7fb8eaed1e53543be24a93 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 23 Jan 2024 16:11:22 +0100 Subject: [PATCH 07/28] chore: converts BasePopoverReactionList to functional component --- src/hooks/useBasePopoverReactionList/index.ts | 134 +++++++ src/hooks/useBasePopoverReactionList/types.ts | 65 ++++ src/pages/home/ReportScreenContext.ts | 4 +- .../BasePopoverReactionList.tsx | 334 +++--------------- .../PopoverReactionList/index.tsx | 18 +- 5 files changed, 256 insertions(+), 299 deletions(-) create mode 100644 src/hooks/useBasePopoverReactionList/index.ts create mode 100644 src/hooks/useBasePopoverReactionList/types.ts diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts new file mode 100644 index 000000000000..cccb8a99b526 --- /dev/null +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -0,0 +1,134 @@ +import {useEffect, useMemo, useRef, useState} from 'react'; +import type {SyntheticEvent} from 'react'; +import {Dimensions} from 'react-native'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import type {BasePopoverReactionListHookProps, ReactionListAnchor, ShowReactionList} from './types'; + +export default function useBasePopoverReactionList({emojiName, emojiReactions, accountID, reportActionID, preferredLocale}: BasePopoverReactionListHookProps) { + const [isPopoverVisible, setIsPopoverVisible] = useState(false); + const [cursorRelativePosition, setCursorRelativePosition] = useState({horizontal: 0, vertical: 0}); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); + const reactionListRef = useRef(null); + + // Get the selected reaction + const selectedReaction = useMemo(() => (isPopoverVisible ? emojiReactions?.emojiName : null), [isPopoverVisible, emojiReactions]); + + // custom methods + function getReactionInformation() { + if (!selectedReaction) { + // If there is no reaction, we return default values + return { + emojiName: '', + reactionCount: 0, + emojiCodes: [], + hasUserReacted: false, + users: [], + }; + } + + const {emojiCodes, reactionCount, hasUserReacted, userAccountIDs} = EmojiUtils.getEmojiReactionDetails(emojiName, selectedReaction, accountID); + + const users = PersonalDetailsUtils.getPersonalDetailsByIDs(userAccountIDs, accountID, true); + return { + emojiName, + emojiCodes, + reactionCount, + hasUserReacted, + users, + }; + } + + /** + * Get the BasePopoverReactionList anchor position + * We calculate the achor coordinates from measureInWindow async method + * + * @returns promise + */ + function getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { + return new Promise((resolve) => { + const reactionListAnchor = reactionListRef.current; + if (reactionListAnchor && 'measureInWindow' in reactionListAnchor) { + reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); + } else { + // If the anchor is not available or does not have the measureInWindow method, we return 0, 0 + resolve({x: 0, y: 0}); + } + }); + } + + /** + * Show the ReactionList modal popover. + * + * @param event - Object - A press event. + * @param reactionListAnchor - Element - reactionListAnchor + */ + const showReactionList: ShowReactionList = (event, reactionListAnchor) => { + // We get the cursor coordinates and the reactionListAnchor coordinates to calculate the popover position + const nativeEvent = (event as SyntheticEvent)?.nativeEvent || {}; + reactionListRef.current = reactionListAnchor; + getReactionListMeasuredLocation().then(({x, y}) => { + setCursorRelativePosition({horizontal: nativeEvent.pageX - x, vertical: nativeEvent.pageY - y}); + setPopoverAnchorPosition({ + horizontal: nativeEvent.pageX, + vertical: nativeEvent.pageY, + }); + setIsPopoverVisible(true); + }); + }; + + /** + * Hide the ReactionList modal popover. + */ + function hideReactionList() { + setIsPopoverVisible(false); + } + + useEffect(() => { + const dimensionsEventListener = Dimensions.addEventListener('change', () => { + if (!isPopoverVisible) { + // If the popover is not visible, we don't need to update the component + return; + } + getReactionListMeasuredLocation().then(({x, y}) => { + if (!x || !y) { + return; + } + setPopoverAnchorPosition({ + horizontal: cursorRelativePosition.horizontal + x, + vertical: cursorRelativePosition.vertical + y, + }); + }); + }); + + return () => { + dimensionsEventListener.remove(); + }; + }, [ + isPopoverVisible, + reportActionID, + preferredLocale, + cursorRelativePosition.horizontal, + cursorRelativePosition.vertical, + popoverAnchorPosition.horizontal, + popoverAnchorPosition.vertical, + ]); + + useEffect(() => { + if (!isPopoverVisible) { + // If the popover is not visible, we don't need to update the component + return; + } + + // Hide the list when all reactions are removed + const emojiReactionsList = emojiReactions?.emojiName.users; + const isEmptyList = Array.isArray(emojiReactionsList) && !emojiReactionsList.some((emojiReaction) => emojiReaction); + if (!isEmptyList) { + return; + } + + hideReactionList(); + }); + + return {isPopoverVisible, cursorRelativePosition, popoverAnchorPosition, getReactionInformation, hideReactionList, reactionListRef, showReactionList}; +} diff --git a/src/hooks/useBasePopoverReactionList/types.ts b/src/hooks/useBasePopoverReactionList/types.ts new file mode 100644 index 000000000000..e4ba9263b41b --- /dev/null +++ b/src/hooks/useBasePopoverReactionList/types.ts @@ -0,0 +1,65 @@ +import type {OnyxEntry} from 'react-native-onyx'; +import type {LocaleContextProps} from '@components/LocaleContextProvider'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext'; +import type {ReportActionReactions} from '@src/types/onyx'; + +type BasePopoverReactionListOnyxProps = { + /** The reactions for the report action */ + emojiReactions: OnyxEntry; +}; + +type BasePopoverReactionListProps = { + /** The ID of the report action */ + reportActionID: string; + + /** The emoji name */ + emojiName: string; +}; + +type BasePopoverReactionListHookProps = BasePopoverReactionListProps & { + /** The reactions for the report action */ + emojiReactions: OnyxEntry; + + /** The current user's account ID */ + accountID: WithCurrentUserPersonalDetailsProps['currentUserPersonalDetails']['accountID']; + + preferredLocale: LocaleContextProps['preferredLocale']; +}; + +type BasePopoverReactionListPropsWithLocalWithOnyx = WithCurrentUserPersonalDetailsProps & BasePopoverReactionListOnyxProps & BasePopoverReactionListProps; +type BasePopoverReactionListState = { + /** Whether the popover is visible */ + isPopoverVisible: boolean; + + /** The horizontal and vertical position (relative to the screen) where the popover will display. */ + popoverAnchorPosition: { + horizontal: number; + vertical: number; + }; + + /** The horizontal and vertical position (relative to the screen) where the cursor is. */ + cursorRelativePosition: { + horizontal: number; + vertical: number; + }; +}; + +type ShowReactionList = (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor) => void; + +type InnerReactionListRefType = { + showReactionList: ShowReactionList; + hideReactionList: () => void; + isActiveReportAction: (actionID: number | string) => boolean; +}; + +export type { + BasePopoverReactionListProps, + BasePopoverReactionListHookProps, + BasePopoverReactionListPropsWithLocalWithOnyx, + BasePopoverReactionListState, + BasePopoverReactionListOnyxProps, + ShowReactionList, + ReactionListAnchor, + InnerReactionListRefType, +}; diff --git a/src/pages/home/ReportScreenContext.ts b/src/pages/home/ReportScreenContext.ts index 3b4e574e01a1..e9440ab932d6 100644 --- a/src/pages/home/ReportScreenContext.ts +++ b/src/pages/home/ReportScreenContext.ts @@ -1,10 +1,10 @@ -import type {RefObject} from 'react'; +import type {RefObject, SyntheticEvent} from 'react'; import {createContext} from 'react'; import type {FlatList, GestureResponderEvent, View} from 'react-native'; type ReactionListAnchor = View | HTMLDivElement | null; -type ReactionListEvent = GestureResponderEvent | MouseEvent; +type ReactionListEvent = GestureResponderEvent | MouseEvent | SyntheticEvent; type ReactionListRef = { showReactionList: (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor, emojiName: string, reportActionID: string) => void; diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 4bee2e8d1d3c..3f5fdeb6428f 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -1,297 +1,61 @@ -import lodashGet from 'lodash/get'; import React from 'react'; -import {Dimensions} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import type {OnyxEntry} from 'react-native-onyx'; -import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; -import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; -import type {WithLocalizeProps} from '@components/withLocalize'; -import compose from '@libs/compose'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; +import useBasePopoverReactionList from '@hooks/useBasePopoverReactionList'; +import type {BasePopoverReactionListOnyxProps, BasePopoverReactionListPropsWithLocalWithOnyx} from '@hooks/useBasePopoverReactionList/types'; +import useLocalize from '@hooks/useLocalize'; import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportActionReactions} from '@src/types/onyx'; -import type {ReportActionReaction} from '@src/types/onyx/ReportActionReactions'; -type BasePopoverReactionListOnyxProps = { - /** The reactions for the report action */ - emojiReactions: OnyxEntry; -}; - -type BasePopoverReactionListProps = { - /** The ID of the report action */ - reportActionID: string; - - /** The emoji name */ - emojiName: string; - - /** The ref of the action */ - ref: React.Ref; -}; - -type BasePopoverReactionListWithLocalizeProps = WithLocalizeProps & WithCurrentUserPersonalDetailsProps; - -type BasePopoverReactionListPropsWithLocalWithOnyx = BasePopoverReactionListWithLocalizeProps & BasePopoverReactionListOnyxProps & BasePopoverReactionListProps; -type BasePopoverReactionListState = { - /** Whether the popover is visible */ - isPopoverVisible: boolean; - - /** The horizontal and vertical position (relative to the screen) where the popover will display. */ - popoverAnchorPosition: { - horizontal: number; - vertical: number; - }; - - /** The horizontal and vertical position (relative to the screen) where the cursor is. */ - cursorRelativePosition: { - horizontal: number; - vertical: number; - }; -}; - -class BasePopoverReactionList extends React.Component { - reactionListAnchor: React.RefObject; - - dimensionsEventListener: { - remove: () => void; - } | null; - - constructor(props: BasePopoverReactionListPropsWithLocalWithOnyx) { - super(props); - - this.state = { - isPopoverVisible: false, - cursorRelativePosition: { - horizontal: 0, - vertical: 0, - }, - - // The horizontal and vertical position (relative to the screen) where the popover will display. - popoverAnchorPosition: { - horizontal: 0, - vertical: 0, - }, - }; - - this.reactionListAnchor = React.createRef(); - this.showReactionList = this.showReactionList.bind(this); - this.hideReactionList = this.hideReactionList.bind(this); - this.measureReactionListPosition = this.measureReactionListPosition.bind(this); - this.getReactionListMeasuredLocation = this.getReactionListMeasuredLocation.bind(this); - this.getReactionInformation = this.getReactionInformation.bind(this); - this.dimensionsEventListener = null; - } - - componentDidMount() { - this.dimensionsEventListener = Dimensions.addEventListener('change', this.measureReactionListPosition); - } - - shouldComponentUpdate(nextProps: BasePopoverReactionListPropsWithLocalWithOnyx, nextState: BasePopoverReactionListState) { - if (!this.state.isPopoverVisible && !nextState.isPopoverVisible) { - // If the popover is not visible, we don't need to update the component - return false; - } - - const previousLocale = lodashGet(this.props, 'preferredLocale', CONST.LOCALES.DEFAULT); - const nextLocale = lodashGet(nextProps, 'preferredLocale', CONST.LOCALES.DEFAULT); - const prevReaction = lodashGet(this.props.emojiReactions, this.props.emojiName); - const nextReaction = lodashGet(nextProps.emojiReactions, nextProps.emojiName); - - return ( - this.props.reportActionID !== nextProps.reportActionID || - this.props.emojiName !== nextProps.emojiName || - !_.isEqual(prevReaction, nextReaction) || - !_.isEqual(this.state, nextState) || - previousLocale !== nextLocale - ); - } - - componentDidUpdate() { - if (!this.state.isPopoverVisible) { - // If the popover is not visible, we don't need to update the component - return; - } - - // Hide the list when all reactions are removed - const emojiReactions = lodashGet(this.props.emojiReactions, [this.props.emojiName, 'users']); - const isEmptyList = !emojiReactions || !_.some(emojiReactions); - if (!isEmptyList) { - return; - } - - this.hideReactionList(); - } - - componentWillUnmount() { - // Remove the event listener - if (!this.dimensionsEventListener) { - return; - } - this.dimensionsEventListener.remove(); - } - - /** - * Get the BasePopoverReactionList anchor position - * We calculate the achor coordinates from measureInWindow async method - * - * @returns {Promise} - */ - getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { - return new Promise((resolve) => { - const reactionListAnchor = this.reactionListAnchor.current as HTMLElement & {measureInWindow: (callback: (x: number, y: number) => void) => void}; - if (reactionListAnchor) { - reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); - } else { - // If the anchor is not available, we return 0, 0 - resolve({x: 0, y: 0}); - } - }); - } - - /** - * Get the reaction information. - * - * @param {Object} selectedReaction - * @param {String} emojiName - * @returns {Object} - */ - getReactionInformation(selectedReaction: ReportActionReaction | null | undefined, emojiName: string) { - if (!selectedReaction) { - // If there is no reaction, we return default values - return { - emojiName: '', - reactionCount: 0, - emojiCodes: [], - hasUserReacted: false, - users: [], - }; - } - - const {emojiCodes, reactionCount, hasUserReacted, userAccountIDs} = EmojiUtils.getEmojiReactionDetails(emojiName, selectedReaction, this.props.currentUserPersonalDetails.accountID); - - const users = PersonalDetailsUtils.getPersonalDetailsByIDs(userAccountIDs, this.props.currentUserPersonalDetails.accountID, true); - return { - emojiName, - emojiCodes, - reactionCount, - hasUserReacted, - users, - }; - } - - /** - * Show the ReactionList modal popover. - * - * @param {Object} [event] - A press event. - * @param {Element} reactionListAnchor - reactionListAnchor - */ - showReactionList( - event: { - nativeEvent: { - pageX: number; - pageY: number; - }; - }, - reactionListAnchor: HTMLElement, - ) { - // We get the cursor coordinates and the reactionListAnchor coordinates to calculate the popover position - const nativeEvent = event.nativeEvent || {}; - this.reactionListAnchor = {current: reactionListAnchor}; - this.getReactionListMeasuredLocation().then(({x, y}) => { - this.setState({ - cursorRelativePosition: { - horizontal: nativeEvent.pageX - x, - vertical: nativeEvent.pageY - y, - }, - popoverAnchorPosition: { - horizontal: nativeEvent.pageX, - vertical: nativeEvent.pageY, - }, - isPopoverVisible: true, - }); - }); - } - - /** - * This gets called on Dimensions change to find the anchor coordinates for the action BasePopoverReactionList. - */ - measureReactionListPosition() { - if (!this.state.isPopoverVisible) { - // If the popover is not visible, we don't need to update the component - return; - } - this.getReactionListMeasuredLocation().then(({x, y}) => { - if (!x || !y) { - return; - } - this.setState((prev) => ({ - popoverAnchorPosition: { - horizontal: prev.cursorRelativePosition.horizontal + x, - vertical: prev.cursorRelativePosition.vertical + y, - }, - })); - }); - } - - /** - * Hide the ReactionList modal popover. - */ - hideReactionList() { - this.setState({ - isPopoverVisible: false, - }); - } - - render() { - // Get the selected reaction - const selectedReaction = this.state.isPopoverVisible ? lodashGet(this.props.emojiReactions, [this.props.emojiName]) : null; - - // Get the reaction information - const {emojiName, emojiCodes, reactionCount, hasUserReacted, users} = this.getReactionInformation(selectedReaction, this.props.emojiName); - - return ( - - - - ); - } +function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx) { + // hooks + const {emojiReactions, emojiName, reportActionID, currentUserPersonalDetails} = props; + const {preferredLocale} = useLocalize(); + const {isPopoverVisible, hideReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ + emojiName, + emojiReactions, + accountID: currentUserPersonalDetails.accountID, + reportActionID, + preferredLocale, + }); + // Get the reaction information + const {emojiCodes, reactionCount, hasUserReacted, users} = getReactionInformation(); + + return ( + + + + ); } -export default compose( - // @ts-ignore TODO: Fix this when the type is fixed - withLocalize, - withCurrentUserPersonalDetails, - withOnyx({ +export default withCurrentUserPersonalDetails( + withOnyx({ emojiReactions: { key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, }, - }), -)(BasePopoverReactionList); + })(BasePopoverReactionList), +); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 8ac89cef6656..610146559786 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -1,17 +1,11 @@ import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; +import type {InnerReactionListRefType} from '@hooks/useBasePopoverReactionList/types'; +import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import BasePopoverReactionList from './BasePopoverReactionList'; type PopoverReactionListProps = { - ref: ForwardedRef; -}; - -type ShowReactionList = (event: React.MouseEvent, reactionListAnchor: HTMLElement, emojiName: string, reportActionID: string) => void; - -type InnerReactionListRefType = { - showReactionList: ShowReactionList; - hideReactionList: () => void; - isActiveReportAction: (actionID: number | string) => boolean; + ref: ForwardedRef; }; function PopoverReactionList(props: PopoverReactionListProps) { @@ -19,10 +13,10 @@ function PopoverReactionList(props: PopoverReactionListProps) { const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); - const showReactionList: ShowReactionList = (event, reactionListAnchor, emojiName, reportActionID) => { + const showReactionList: ReactionListRef['showReactionList'] = (event, reactionListAnchor, emojiName, reportActionID) => { setReactionListReportActionID(reportActionID); setReactionListEmojiName(emojiName); - innerReactionListRef.current?.showReactionList(event, reactionListAnchor, emojiName, reportActionID); + innerReactionListRef.current?.showReactionList(event, reactionListAnchor); }; const hideReactionList = () => { @@ -45,7 +39,7 @@ function PopoverReactionList(props: PopoverReactionListProps) { PopoverReactionList.displayName = 'PopoverReactionList'; export default React.memo( - forwardRef((props, ref) => ( + forwardRef((props, ref) => ( Date: Wed, 24 Jan 2024 09:40:09 +0100 Subject: [PATCH 08/28] chore: fixes the linting error regarding the named export --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 2 +- src/pages/home/report/ReactionList/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 80cbc834f344..f1002bddcddc 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -13,7 +13,7 @@ import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import type {PersonalDetails} from '@src/types/onyx'; import HeaderReactionList from './HeaderReactionList'; -import type {ReactionListProps} from './types'; +import type ReactionListProps from './types'; type BaseReactionListProps = ReactionListProps & { /** diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index e715f6010fc3..bf6c7c1192a5 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -5,7 +5,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as EmojiUtils from '@libs/EmojiUtils'; -import type {ReactionListProps} from './types'; +import type ReactionListProps from './types'; type HeaderReactionListProps = ReactionListProps & { /** Returns true if the current account has reacted to the report action (with the given skin tone). */ diff --git a/src/pages/home/report/ReactionList/types.ts b/src/pages/home/report/ReactionList/types.ts index ec03b141080d..eb5dfe1d998e 100644 --- a/src/pages/home/report/ReactionList/types.ts +++ b/src/pages/home/report/ReactionList/types.ts @@ -12,4 +12,4 @@ type ReactionListProps = { emojiCount: number; }; -export type {ReactionListProps}; +export default ReactionListProps; From ffe26d60a5dc6916dcec7c9f913d87f9c4c988e5 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:44:48 +0100 Subject: [PATCH 09/28] Update src/pages/home/report/ReactionList/PopoverReactionList/index.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../report/ReactionList/PopoverReactionList/index.tsx | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 610146559786..4368f5a609ab 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -38,12 +38,4 @@ function PopoverReactionList(props: PopoverReactionListProps) { PopoverReactionList.displayName = 'PopoverReactionList'; -export default React.memo( - forwardRef((props, ref) => ( - - )), -); +export default React.memo(forwardRef(PopoverReactionList)); From a1a4f17f6c44190a5f63b131e0b76056d87a93e4 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:45:38 +0100 Subject: [PATCH 10/28] Update src/pages/home/report/ReactionList/BaseReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index f1002bddcddc..bec68ba268ff 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -78,9 +78,7 @@ function BaseReactionList(props: BaseReactionListProps) { style={{maxWidth: variables.mobileResponsiveWidthBreakpoint}} hoverStyle={hoveredComponentBG} onSelectRow={() => { - if (props.onClose) { - props.onClose(); - } + props.onClose?.(); Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} From 1059ccfb9be071330b004b744a2e46abb3d7e77d Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:46:05 +0100 Subject: [PATCH 11/28] Update src/pages/home/report/ReactionList/BaseReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index bec68ba268ff..c905630f547c 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -68,9 +68,6 @@ function BaseReactionList(props: BaseReactionListProps) { * Items with the code "SPACER" return nothing and are used to fill rows up to 8 * so that the sticky headers function properly * - * @param params object - * @param params.item object - * @return React.Component */ const renderItem: FlatListProps['renderItem'] = ({item}) => ( Date: Wed, 24 Jan 2024 14:46:53 +0100 Subject: [PATCH 12/28] Update src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Błażej Kustra <46095609+blazejkustra@users.noreply.github.com> --- .../ReactionList/PopoverReactionList/BasePopoverReactionList.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 3f5fdeb6428f..5ef74a62ec67 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -9,7 +9,6 @@ import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; import ONYXKEYS from '@src/ONYXKEYS'; function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx) { - // hooks const {emojiReactions, emojiName, reportActionID, currentUserPersonalDetails} = props; const {preferredLocale} = useLocalize(); const {isPopoverVisible, hideReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ From ffca03ed05404e85a1cc271cace70016c03d2c57 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 16:22:38 +0100 Subject: [PATCH 13/28] fix: ref object not getting passed as the second arg --- .../report/ReactionList/PopoverReactionList/index.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 4368f5a609ab..75b8e080b301 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -4,11 +4,7 @@ import type {InnerReactionListRefType} from '@hooks/useBasePopoverReactionList/t import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import BasePopoverReactionList from './BasePopoverReactionList'; -type PopoverReactionListProps = { - ref: ForwardedRef; -}; - -function PopoverReactionList(props: PopoverReactionListProps) { +function PopoverReactionList(props: unknown, ref: ForwardedRef) { const innerReactionListRef = useRef(null); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); @@ -25,7 +21,7 @@ function PopoverReactionList(props: PopoverReactionListProps) { const isActiveReportAction = (actionID: number | string) => Boolean(actionID) && reactionListReportActionID === actionID; - useImperativeHandle(props.ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); + useImperativeHandle(ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); return ( Date: Wed, 24 Jan 2024 16:25:41 +0100 Subject: [PATCH 14/28] fix: types naming convention --- src/hooks/useBasePopoverReactionList/types.ts | 4 ++-- .../home/report/ReactionList/PopoverReactionList/index.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/types.ts b/src/hooks/useBasePopoverReactionList/types.ts index e4ba9263b41b..49620ccd9370 100644 --- a/src/hooks/useBasePopoverReactionList/types.ts +++ b/src/hooks/useBasePopoverReactionList/types.ts @@ -47,7 +47,7 @@ type BasePopoverReactionListState = { type ShowReactionList = (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor) => void; -type InnerReactionListRefType = { +type InnerReactionListRef = { showReactionList: ShowReactionList; hideReactionList: () => void; isActiveReportAction: (actionID: number | string) => boolean; @@ -61,5 +61,5 @@ export type { BasePopoverReactionListOnyxProps, ShowReactionList, ReactionListAnchor, - InnerReactionListRefType, + InnerReactionListRef, }; diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index 75b8e080b301..e7899c814568 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -1,11 +1,11 @@ import React, {forwardRef, useImperativeHandle, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; -import type {InnerReactionListRefType} from '@hooks/useBasePopoverReactionList/types'; +import type {InnerReactionListRef} from '@hooks/useBasePopoverReactionList/types'; import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import BasePopoverReactionList from './BasePopoverReactionList'; function PopoverReactionList(props: unknown, ref: ForwardedRef) { - const innerReactionListRef = useRef(null); + const innerReactionListRef = useRef(null); const [reactionListReportActionID, setReactionListReportActionID] = useState(''); const [reactionListEmojiName, setReactionListEmojiName] = useState(''); From cebd4dcc436ec9a1f9f618f29c4f0a84265e6eb5 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 16:37:36 +0100 Subject: [PATCH 15/28] chore: removes unneeded JSDocs --- .../report/ReactionList/BaseReactionList.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index c905630f547c..80ffd11e59ba 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -32,24 +32,8 @@ type BaseReactionListProps = ReactionListProps & { isVisible: boolean; }; -/** - * Create a unique key for each action in the FlatList. - * @param item object - * @param index number - * @return string - */ const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; -/** - * This function will be used with FlatList getItemLayout property for optimization purpose that allows skipping - * the measurement of dynamic content if we know the size (height or width) of items ahead of time. - * Generate and return an object with properties length(height of each individual row), - * offset(distance of the current row from the top of the FlatList), index(current row index) - * - * @param data FlatList item - * @param index number - row index - * @returns object - */ const getItemLayout = (data: ArrayLike | null | undefined, index: number): {length: number; offset: number; index: number} => ({ index, length: variables.listItemHeightNormal, From 3f8233674cb94e7a6a92ebdb4c517ec4b29aac95 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 16:51:22 +0100 Subject: [PATCH 16/28] chore: makes isVisible prop optional --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 80ffd11e59ba..a49c4d832f30 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -29,7 +29,7 @@ type BaseReactionListProps = ReactionListProps & { /** * Returns true if the reaction list is visible */ - isVisible: boolean; + isVisible?: boolean; }; const keyExtractor: FlatListProps['keyExtractor'] = (item, index) => `${item.login}+${index}`; From ac5f0889d73ca657b25c097ead4cb5159fad02f9 Mon Sep 17 00:00:00 2001 From: Vadym Date: Wed, 24 Jan 2024 20:09:46 +0100 Subject: [PATCH 17/28] fix: menu ref not getting forwarded and menu not showing up --- src/hooks/useBasePopoverReactionList/index.ts | 12 ++++++------ .../home/report/ReactionList/HeaderReactionList.tsx | 1 + .../PopoverReactionList/BasePopoverReactionList.tsx | 12 ++++++++---- .../ReactionList/PopoverReactionList/index.tsx | 4 ++++ 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index cccb8a99b526..64c5f6d8efc0 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -1,4 +1,4 @@ -import {useEffect, useMemo, useRef, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; import type {SyntheticEvent} from 'react'; import {Dimensions} from 'react-native'; import * as EmojiUtils from '@libs/EmojiUtils'; @@ -11,11 +11,10 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); const reactionListRef = useRef(null); - // Get the selected reaction - const selectedReaction = useMemo(() => (isPopoverVisible ? emojiReactions?.emojiName : null), [isPopoverVisible, emojiReactions]); - // custom methods function getReactionInformation() { + const selectedReaction = emojiReactions?.[emojiName]; + if (!selectedReaction) { // If there is no reaction, we return default values return { @@ -121,8 +120,9 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a } // Hide the list when all reactions are removed - const emojiReactionsList = emojiReactions?.emojiName.users; - const isEmptyList = Array.isArray(emojiReactionsList) && !emojiReactionsList.some((emojiReaction) => emojiReaction); + const users = emojiReactions?.[emojiName].users; + const isEmptyList = users && Object.keys(users).length === 0; + if (!isEmptyList) { return; } diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index bf6c7c1192a5..5742566e115c 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import {View} from 'react-native'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 5ef74a62ec67..6a464c46cce8 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, {forwardRef, useImperativeHandle} from 'react'; +import type {ForwardedRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; @@ -6,12 +7,13 @@ import useBasePopoverReactionList from '@hooks/useBasePopoverReactionList'; import type {BasePopoverReactionListOnyxProps, BasePopoverReactionListPropsWithLocalWithOnyx} from '@hooks/useBasePopoverReactionList/types'; import useLocalize from '@hooks/useLocalize'; import BaseReactionList from '@pages/home/report/ReactionList/BaseReactionList'; +import type {ReactionListRef} from '@pages/home/ReportScreenContext'; import ONYXKEYS from '@src/ONYXKEYS'; -function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx) { +function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWithOnyx, ref: ForwardedRef>) { const {emojiReactions, emojiName, reportActionID, currentUserPersonalDetails} = props; const {preferredLocale} = useLocalize(); - const {isPopoverVisible, hideReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ + const {isPopoverVisible, hideReactionList, showReactionList, popoverAnchorPosition, reactionListRef, getReactionInformation} = useBasePopoverReactionList({ emojiName, emojiReactions, accountID: currentUserPersonalDetails.accountID, @@ -21,6 +23,8 @@ function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWit // Get the reaction information const {emojiCodes, reactionCount, hasUserReacted, users} = getReactionInformation(); + useImperativeHandle(ref, () => ({hideReactionList, showReactionList})); + return ( `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, }, - })(BasePopoverReactionList), + })(forwardRef(BasePopoverReactionList)), ); diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx index e7899c814568..2cdcac65feae 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/index.tsx @@ -23,6 +23,10 @@ function PopoverReactionList(props: unknown, ref: ForwardedRef) useImperativeHandle(ref, () => ({showReactionList, hideReactionList, isActiveReportAction})); + if (reactionListReportActionID === '' || reactionListEmojiName === '') { + return null; + } + return ( Date: Fri, 26 Jan 2024 15:47:34 +0100 Subject: [PATCH 18/28] chore: makes hasUserReacted optional with default value --- .../report/ReactionList/BaseReactionList.tsx | 19 +++++++++---------- .../ReactionList/HeaderReactionList.tsx | 14 +++++++------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index a49c4d832f30..09471faf0b42 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -24,7 +24,7 @@ type BaseReactionListProps = ReactionListProps & { /** * Returns true if the current account has reacted to the report action (with the given skin tone). */ - hasUserReacted: boolean; + hasUserReacted?: boolean; /** * Returns true if the reaction list is visible @@ -40,10 +40,10 @@ const getItemLayout = (data: ArrayLike | null | undefined, inde offset: variables.listItemHeightNormal * index, }); -function BaseReactionList(props: BaseReactionListProps) { +function BaseReactionList({hasUserReacted = false, users, isVisible = false, emojiCodes, emojiCount, emojiName, onClose}: BaseReactionListProps) { const {isSmallScreenWidth} = useWindowDimensions(); const {hoveredComponentBG, reactionListContainer, reactionListContainerFixedWidth, pv2} = useThemeStyles(); - if (!props.isVisible) { + if (!isVisible) { return null; } @@ -59,7 +59,7 @@ function BaseReactionList(props: BaseReactionListProps) { style={{maxWidth: variables.mobileResponsiveWidthBreakpoint}} hoverStyle={hoveredComponentBG} onSelectRow={() => { - props.onClose?.(); + onClose?.(); Navigation.navigate(ROUTES.PROFILE.getRoute(item.accountID)); }} @@ -84,14 +84,13 @@ function BaseReactionList(props: BaseReactionListProps) { return ( <> & { /** Returns true if the current account has reacted to the report action (with the given skin tone). */ - hasUserReacted: boolean; + hasUserReacted?: boolean; }; -function HeaderReactionList(props: HeaderReactionListProps) { +function HeaderReactionList({emojiCodes, emojiCount, emojiName, hasUserReacted = false}: HeaderReactionListProps) { const { flexRow, justifyContentBetween, @@ -32,11 +32,11 @@ function HeaderReactionList(props: HeaderReactionListProps) { return ( - - {props.emojiCodes.join('')} - {props.emojiCount} + + {emojiCodes.join('')} + {emojiCount} - {`:${EmojiUtils.getLocalizedEmojiName(props.emojiName, preferredLocale)}:`} + {`:${EmojiUtils.getLocalizedEmojiName(emojiName, preferredLocale)}:`} ); From ce370cd4af2b566b6fcbfca4b4aaf3233f8763d4 Mon Sep 17 00:00:00 2001 From: Vadym Date: Fri, 26 Jan 2024 15:52:42 +0100 Subject: [PATCH 19/28] chore: removes unneeded JSDocs returns and comments --- src/hooks/useBasePopoverReactionList/index.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index 64c5f6d8efc0..ae7afc230bf9 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -11,7 +11,6 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a const [popoverAnchorPosition, setPopoverAnchorPosition] = useState({horizontal: 0, vertical: 0}); const reactionListRef = useRef(null); - // custom methods function getReactionInformation() { const selectedReaction = emojiReactions?.[emojiName]; @@ -41,8 +40,6 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a /** * Get the BasePopoverReactionList anchor position * We calculate the achor coordinates from measureInWindow async method - * - * @returns promise */ function getReactionListMeasuredLocation(): Promise<{x: number; y: number}> { return new Promise((resolve) => { From 3fa2b09f585f5ba2567c4dc33d391802186fa5ac Mon Sep 17 00:00:00 2001 From: Vadym Date: Fri, 26 Jan 2024 15:57:07 +0100 Subject: [PATCH 20/28] fix: reuses the AnchorPosition type --- src/hooks/useBasePopoverReactionList/types.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/types.ts b/src/hooks/useBasePopoverReactionList/types.ts index 49620ccd9370..2993b2b8acb6 100644 --- a/src/hooks/useBasePopoverReactionList/types.ts +++ b/src/hooks/useBasePopoverReactionList/types.ts @@ -2,6 +2,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext'; +import type {AnchorPosition} from '@src/styles'; import type {ReportActionReactions} from '@src/types/onyx'; type BasePopoverReactionListOnyxProps = { @@ -33,16 +34,10 @@ type BasePopoverReactionListState = { isPopoverVisible: boolean; /** The horizontal and vertical position (relative to the screen) where the popover will display. */ - popoverAnchorPosition: { - horizontal: number; - vertical: number; - }; + popoverAnchorPosition: AnchorPosition; /** The horizontal and vertical position (relative to the screen) where the cursor is. */ - cursorRelativePosition: { - horizontal: number; - vertical: number; - }; + cursorRelativePosition: AnchorPosition; }; type ShowReactionList = (event: ReactionListEvent | undefined, reactionListAnchor: ReactionListAnchor) => void; From b1b7451b5e460639040a44194215b3db11291f77 Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Thu, 8 Feb 2024 10:09:36 +0100 Subject: [PATCH 21/28] Update src/hooks/useBasePopoverReactionList/index.ts Co-authored-by: Getabalew Tesfaye <75031127+getusha@users.noreply.github.com> --- src/hooks/useBasePopoverReactionList/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index ae7afc230bf9..5f598d9eb1dd 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -117,7 +117,7 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a } // Hide the list when all reactions are removed - const users = emojiReactions?.[emojiName].users; + const users = emojiReactions?.[emojiName]?.users; const isEmptyList = users && Object.keys(users).length === 0; if (!isEmptyList) { From e9761c395dfd8b46a0b0838103eff055f3baa3b8 Mon Sep 17 00:00:00 2001 From: Vadym Date: Thu, 8 Feb 2024 15:45:03 +0100 Subject: [PATCH 22/28] fix: adds the missing dependencies in the useEffect --- src/hooks/useBasePopoverReactionList/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index 5f598d9eb1dd..3134526e8507 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -125,7 +125,7 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a } hideReactionList(); - }); + }, [emojiReactions, emojiName, isPopoverVisible, reportActionID, preferredLocale]); return {isPopoverVisible, cursorRelativePosition, popoverAnchorPosition, getReactionInformation, hideReactionList, reactionListRef, showReactionList}; } From 1925a110d042671a84ba4613ee1e345894f36711 Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 13 Feb 2024 10:24:31 +0100 Subject: [PATCH 23/28] fix: popover position --- .../PopoverReactionList/BasePopoverReactionList.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx index 6a464c46cce8..9f024ac92846 100644 --- a/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx +++ b/src/pages/home/report/ReactionList/PopoverReactionList/BasePopoverReactionList.tsx @@ -37,10 +37,6 @@ function BasePopoverReactionList(props: BasePopoverReactionListPropsWithLocalWit fullscreen withoutOverlay anchorRef={reactionListRef} - anchorAlignment={{ - horizontal: 'left', - vertical: 'top', - }} > Date: Thu, 15 Feb 2024 16:32:18 +0100 Subject: [PATCH 24/28] chore: adds white space for better readability --- src/pages/home/report/ReactionList/BaseReactionList.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index 09471faf0b42..d1b0d8c4ca84 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -43,6 +43,7 @@ const getItemLayout = (data: ArrayLike | null | undefined, inde function BaseReactionList({hasUserReacted = false, users, isVisible = false, emojiCodes, emojiCount, emojiName, onClose}: BaseReactionListProps) { const {isSmallScreenWidth} = useWindowDimensions(); const {hoveredComponentBG, reactionListContainer, reactionListContainerFixedWidth, pv2} = useThemeStyles(); + if (!isVisible) { return null; } From 2916ba26fb1108d4b2063a5f95897315af41593b Mon Sep 17 00:00:00 2001 From: vadymbokatov <138146362+vadymbokatov@users.noreply.github.com> Date: Mon, 19 Feb 2024 14:58:24 +0100 Subject: [PATCH 25/28] Update src/pages/home/report/ReactionList/types.ts Co-authored-by: Monil Bhavsar --- src/pages/home/report/ReactionList/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReactionList/types.ts b/src/pages/home/report/ReactionList/types.ts index eb5dfe1d998e..66c096067310 100644 --- a/src/pages/home/report/ReactionList/types.ts +++ b/src/pages/home/report/ReactionList/types.ts @@ -8,7 +8,7 @@ type ReactionListProps = { /** The name of the emoji */ emojiName: string; - /** Count of the emoji */ + /** Count of the emoji reactions */ emojiCount: number; }; From 5dbe827779942e3b89dcb50199fb8b885d181867 Mon Sep 17 00:00:00 2001 From: Vadym Date: Mon, 19 Feb 2024 15:53:03 +0100 Subject: [PATCH 26/28] chore: removes unnecessary comments --- src/hooks/useBasePopoverReactionList/index.ts | 1 - src/pages/home/report/ReactionList/BaseReactionList.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index 3134526e8507..8a3a3a123d4b 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -47,7 +47,6 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a if (reactionListAnchor && 'measureInWindow' in reactionListAnchor) { reactionListAnchor.measureInWindow((x, y) => resolve({x, y})); } else { - // If the anchor is not available or does not have the measureInWindow method, we return 0, 0 resolve({x: 0, y: 0}); } }); diff --git a/src/pages/home/report/ReactionList/BaseReactionList.tsx b/src/pages/home/report/ReactionList/BaseReactionList.tsx index d1b0d8c4ca84..aaa8ca132ee0 100755 --- a/src/pages/home/report/ReactionList/BaseReactionList.tsx +++ b/src/pages/home/report/ReactionList/BaseReactionList.tsx @@ -22,7 +22,7 @@ type BaseReactionListProps = ReactionListProps & { users: PersonalDetails[]; /** - * Returns true if the current account has reacted to the report action (with the given skin tone). + * Returns true if the current account has reacted to the report action */ hasUserReacted?: boolean; From e5ded33d22261cd004f3970e2e626f52adaa23c8 Mon Sep 17 00:00:00 2001 From: Vadym Date: Mon, 19 Feb 2024 15:56:47 +0100 Subject: [PATCH 27/28] chore: removes unnecessary variable --- src/hooks/useBasePopoverReactionList/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hooks/useBasePopoverReactionList/index.ts b/src/hooks/useBasePopoverReactionList/index.ts index 8a3a3a123d4b..87c9d23c6e35 100644 --- a/src/hooks/useBasePopoverReactionList/index.ts +++ b/src/hooks/useBasePopoverReactionList/index.ts @@ -117,9 +117,8 @@ export default function useBasePopoverReactionList({emojiName, emojiReactions, a // Hide the list when all reactions are removed const users = emojiReactions?.[emojiName]?.users; - const isEmptyList = users && Object.keys(users).length === 0; - if (!isEmptyList) { + if (!users || Object.keys(users).length > 0) { return; } From e4a843ad405fc9d30053cccf7d1dee5cbd710f2d Mon Sep 17 00:00:00 2001 From: Vadym Date: Tue, 20 Feb 2024 08:31:40 +0100 Subject: [PATCH 28/28] chore: removes unnecessary comments --- src/pages/home/report/ReactionList/HeaderReactionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReactionList/HeaderReactionList.tsx b/src/pages/home/report/ReactionList/HeaderReactionList.tsx index 1822f9712f51..9766894c275b 100644 --- a/src/pages/home/report/ReactionList/HeaderReactionList.tsx +++ b/src/pages/home/report/ReactionList/HeaderReactionList.tsx @@ -9,7 +9,7 @@ import * as EmojiUtils from '@libs/EmojiUtils'; import type ReactionListProps from './types'; type HeaderReactionListProps = Omit & { - /** Returns true if the current account has reacted to the report action (with the given skin tone). */ + /** Returns true if the current account has reacted to the report action */ hasUserReacted?: boolean; };