diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index daf02a6f48a8..ea915c25a6ab 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -463,7 +463,7 @@ type OnyxValues = {
[ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags;
[ONYXKEYS.COLLECTION.SELECTED_TAB]: string;
- [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations;
+ [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[];
[ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string;
[ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep;
diff --git a/src/components/DisplayNames/types.ts b/src/components/DisplayNames/types.ts
index b0959d43aa96..2e6f36d5cc07 100644
--- a/src/components/DisplayNames/types.ts
+++ b/src/components/DisplayNames/types.ts
@@ -12,7 +12,7 @@ type DisplayNameWithTooltip = {
login?: string;
/** The avatar for the tooltip fallback */
- avatar: AvatarSource;
+ avatar?: AvatarSource;
};
type DisplayNamesProps = {
diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js
deleted file mode 100644
index 4c8b0e1102b9..000000000000
--- a/src/components/LHNOptionsList/LHNOptionsList.js
+++ /dev/null
@@ -1,222 +0,0 @@
-import {FlashList} from '@shopify/flash-list';
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
-import React, {useCallback} from 'react';
-import {View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
-import _ from 'underscore';
-import participantPropTypes from '@components/participantPropTypes';
-import transactionPropTypes from '@components/transactionPropTypes';
-import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID';
-import usePermissions from '@hooks/usePermissions';
-import useThemeStyles from '@hooks/useThemeStyles';
-import compose from '@libs/compose';
-import * as OptionsListUtils from '@libs/OptionsListUtils';
-import * as ReportUtils from '@libs/ReportUtils';
-import {transactionViolationsPropType} from '@libs/Violations/propTypes';
-import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
-import reportPropTypes from '@pages/reportPropTypes';
-import stylePropTypes from '@styles/stylePropTypes';
-import variables from '@styles/variables';
-import CONST from '@src/CONST';
-import ONYXKEYS from '@src/ONYXKEYS';
-import OptionRowLHNData from './OptionRowLHNData';
-
-const propTypes = {
- /** Wrapper style for the section list */
- style: stylePropTypes,
-
- /** Extra styles for the section list container */
- contentContainerStyles: stylePropTypes.isRequired,
-
- /** Sections for the section list */
- data: PropTypes.arrayOf(PropTypes.string).isRequired,
-
- /** Callback to fire when a row is selected */
- onSelectRow: PropTypes.func.isRequired,
-
- /** Toggle between compact and default view of the option */
- optionMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)).isRequired,
-
- /** Whether to allow option focus or not */
- shouldDisableFocusOptions: PropTypes.bool,
-
- /** The policy which the user has access to and which the report could be tied to */
- policy: PropTypes.shape({
- /** The ID of the policy */
- id: PropTypes.string,
- /** Name of the policy */
- name: PropTypes.string,
- /** Avatar of the policy */
- avatar: PropTypes.string,
- }),
-
- /** All reports shared with the user */
- reports: PropTypes.objectOf(reportPropTypes),
-
- /** Array of report actions for this report */
- reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),
-
- /** Indicates which locale the user currently has selected */
- preferredLocale: PropTypes.string,
-
- /** List of users' personal details */
- personalDetails: PropTypes.objectOf(participantPropTypes),
-
- /** The transaction from the parent report action */
- transactions: PropTypes.objectOf(transactionPropTypes),
-
- /** List of draft comments */
- draftComments: PropTypes.objectOf(PropTypes.string),
-
- /** The list of transaction violations */
- transactionViolations: transactionViolationsPropType,
-
- ...withCurrentReportIDPropTypes,
-};
-
-const defaultProps = {
- style: undefined,
- shouldDisableFocusOptions: false,
- reportActions: {},
- reports: {},
- policy: {},
- preferredLocale: CONST.LOCALES.DEFAULT,
- personalDetails: {},
- transactions: {},
- draftComments: {},
- transactionViolations: {},
- ...withCurrentReportIDDefaultProps,
-};
-
-const keyExtractor = (item) => `report_${item}`;
-
-function LHNOptionsList({
- style,
- contentContainerStyles,
- data,
- onSelectRow,
- optionMode,
- shouldDisableFocusOptions,
- reports,
- reportActions,
- policy,
- preferredLocale,
- personalDetails,
- transactions,
- draftComments,
- currentReportID,
- transactionViolations,
-}) {
- const styles = useThemeStyles();
- const {canUseViolations} = usePermissions();
- /**
- * Function which renders a row in the list
- *
- * @param {Object} params
- * @param {Object} params.item
- *
- * @return {Component}
- */
- const renderItem = useCallback(
- ({item: reportID}) => {
- const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] || {};
- const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`];
- const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`] || {};
- const itemParentReportAction = itemParentReportActions[itemFullReport.parentReportActionID] || {};
- const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`] || {};
- const transactionID = lodashGet(itemParentReportAction, ['originalMessage', 'IOUTransactionID'], '');
- const itemTransaction = transactionID ? transactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {};
- const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || '';
- const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport.ownerAccountID, itemParentReportAction.actorAccountID];
-
- const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails);
-
- return (
-
- );
- },
- [
- currentReportID,
- draftComments,
- onSelectRow,
- optionMode,
- personalDetails,
- policy,
- preferredLocale,
- reportActions,
- reports,
- shouldDisableFocusOptions,
- transactions,
- transactionViolations,
- canUseViolations,
- ],
- );
-
- return (
-
-
-
- );
-}
-
-LHNOptionsList.propTypes = propTypes;
-LHNOptionsList.defaultProps = defaultProps;
-LHNOptionsList.displayName = 'LHNOptionsList';
-
-export default compose(
- withCurrentReportID,
- withOnyx({
- reports: {
- key: ONYXKEYS.COLLECTION.REPORT,
- },
- reportActions: {
- key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
- },
- policy: {
- key: ONYXKEYS.COLLECTION.POLICY,
- },
- preferredLocale: {
- key: ONYXKEYS.NVP_PREFERRED_LOCALE,
- },
- personalDetails: {
- key: ONYXKEYS.PERSONAL_DETAILS_LIST,
- },
- transactions: {
- key: ONYXKEYS.COLLECTION.TRANSACTION,
- },
- draftComments: {
- key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
- },
- transactionViolations: {
- key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
- },
- }),
-)(LHNOptionsList);
diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx
new file mode 100644
index 000000000000..ecf320807b48
--- /dev/null
+++ b/src/components/LHNOptionsList/LHNOptionsList.tsx
@@ -0,0 +1,141 @@
+import {FlashList} from '@shopify/flash-list';
+import type {ReactElement} from 'react';
+import React, {useCallback} from 'react';
+import {StyleSheet, View} from 'react-native';
+import {withOnyx} from 'react-native-onyx';
+import withCurrentReportID from '@components/withCurrentReportID';
+import usePermissions from '@hooks/usePermissions';
+import useThemeStyles from '@hooks/useThemeStyles';
+import * as OptionsListUtils from '@libs/OptionsListUtils';
+import * as ReportUtils from '@libs/ReportUtils';
+import variables from '@styles/variables';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import OptionRowLHNData from './OptionRowLHNData';
+import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types';
+
+const keyExtractor = (item: string) => `report_${item}`;
+
+function LHNOptionsList({
+ style,
+ contentContainerStyles,
+ data,
+ onSelectRow,
+ optionMode,
+ shouldDisableFocusOptions = false,
+ reports = {},
+ reportActions = {},
+ policy = {},
+ preferredLocale = CONST.LOCALES.DEFAULT,
+ personalDetails = {},
+ transactions = {},
+ currentReportID = '',
+ draftComments = {},
+ transactionViolations = {},
+}: LHNOptionsListProps) {
+ const styles = useThemeStyles();
+ const {canUseViolations} = usePermissions();
+ /**
+ * Function which renders a row in the list
+ */
+ const renderItem = useCallback(
+ ({item: reportID}: RenderItemProps): ReactElement => {
+ const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null;
+ const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null;
+ const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null;
+ const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null;
+ const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null;
+ const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : '';
+ const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null;
+ const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? '';
+ const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport?.ownerAccountID, itemParentReportAction?.actorAccountID];
+ const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails);
+
+ return (
+
+ );
+ },
+ [
+ currentReportID,
+ draftComments,
+ onSelectRow,
+ optionMode,
+ personalDetails,
+ policy,
+ preferredLocale,
+ reportActions,
+ reports,
+ shouldDisableFocusOptions,
+ transactions,
+ transactionViolations,
+ canUseViolations,
+ ],
+ );
+
+ return (
+
+
+
+ );
+}
+
+LHNOptionsList.displayName = 'LHNOptionsList';
+
+export default withCurrentReportID(
+ withOnyx({
+ reports: {
+ key: ONYXKEYS.COLLECTION.REPORT,
+ },
+ reportActions: {
+ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
+ },
+ policy: {
+ key: ONYXKEYS.COLLECTION.POLICY,
+ },
+ preferredLocale: {
+ key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+ },
+ personalDetails: {
+ key: ONYXKEYS.PERSONAL_DETAILS_LIST,
+ },
+ transactions: {
+ key: ONYXKEYS.COLLECTION.TRANSACTION,
+ },
+ draftComments: {
+ key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT,
+ },
+ transactionViolations: {
+ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
+ },
+ })(LHNOptionsList),
+);
+
+export type {LHNOptionsListProps};
diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.tsx
similarity index 68%
rename from src/components/LHNOptionsList/OptionRowLHN.js
rename to src/components/LHNOptionsList/OptionRowLHN.tsx
index fc4f05eefd22..1932cf6c6b7f 100644
--- a/src/components/LHNOptionsList/OptionRowLHN.js
+++ b/src/components/LHNOptionsList/OptionRowLHN.tsx
@@ -1,9 +1,7 @@
import {useFocusEffect} from '@react-navigation/native';
-import lodashGet from 'lodash/get';
-import PropTypes from 'prop-types';
import React, {useCallback, useRef, useState} from 'react';
+import type {GestureResponderEvent, ViewStyle} from 'react-native';
import {StyleSheet, View} from 'react-native';
-import _ from 'underscore';
import DisplayNames from '@components/DisplayNames';
import Hoverable from '@components/Hoverable';
import Icon from '@components/Icon';
@@ -27,51 +25,18 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag
import * as ReportUtils from '@libs/ReportUtils';
import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import CONST from '@src/CONST';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
+import type {OptionRowLHNProps} from './types';
-const propTypes = {
- /** Style for hovered state */
- // eslint-disable-next-line react/forbid-prop-types
- hoverStyle: PropTypes.object,
-
- /** The ID of the report that the option is for */
- reportID: PropTypes.string.isRequired,
-
- /** Whether this option is currently in focus so we can modify its style */
- isFocused: PropTypes.bool,
-
- /** A function that is called when an option is selected. Selected option is passed as a param */
- onSelectRow: PropTypes.func,
-
- /** Toggle between compact and default view */
- viewMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)),
-
- style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]),
-
- /** The item that should be rendered */
- // eslint-disable-next-line react/forbid-prop-types
- optionItem: PropTypes.object,
-};
-
-const defaultProps = {
- hoverStyle: undefined,
- viewMode: 'default',
- onSelectRow: () => {},
- style: null,
- optionItem: null,
- isFocused: false,
-};
-
-function OptionRowLHN(props) {
+function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) {
const theme = useTheme();
const styles = useThemeStyles();
+ const popoverAnchor = useRef(null);
const StyleUtils = useStyleUtils();
- const popoverAnchor = useRef(null);
const isFocusedRef = useRef(true);
const {isSmallScreenWidth} = useWindowDimensions();
const {translate} = useLocalize();
-
- const optionItem = props.optionItem;
const [isContextMenuActive, setIsContextMenuActive] = useState(false);
useFocusEffect(
@@ -87,42 +52,37 @@ function OptionRowLHN(props) {
return null;
}
- const isHidden = optionItem.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
- if (isHidden && !props.isFocused && !optionItem.isPinned) {
+ const isHidden = optionItem?.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
+ if (isHidden && !isFocused && !optionItem?.isPinned) {
return null;
}
- const textStyle = props.isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText;
- const textUnreadStyle = optionItem.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle];
- const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], props.style);
- const alternateTextStyle = StyleUtils.combineStyles(
- props.viewMode === CONST.OPTION_MODE.COMPACT
- ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2]
- : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting],
- props.style,
- );
+ const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText;
+ const textUnreadStyle = optionItem?.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle];
+ const displayNameStyle = [styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, textUnreadStyle, style];
+ const alternateTextStyle =
+ viewMode === CONST.OPTION_MODE.COMPACT
+ ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2, style]
+ : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, style];
+
const contentContainerStyles =
- props.viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1];
- const sidebarInnerRowStyle = StyleSheet.flatten(
- props.viewMode === CONST.OPTION_MODE.COMPACT
+ viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1];
+ const sidebarInnerRowStyle = StyleSheet.flatten(
+ viewMode === CONST.OPTION_MODE.COMPACT
? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter]
: [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter],
);
- const hoveredBackgroundColor =
- (props.hoverStyle || styles.sidebarLinkHover) && (props.hoverStyle || styles.sidebarLinkHover).backgroundColor
- ? (props.hoverStyle || styles.sidebarLinkHover).backgroundColor
- : theme.sidebar;
+ const hoveredBackgroundColor = !!styles.sidebarLinkHover && 'backgroundColor' in styles.sidebarLinkHover ? styles.sidebarLinkHover.backgroundColor : theme.sidebar;
const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor;
const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR;
const shouldShowGreenDotIndicator = !hasBrickError && ReportUtils.requiresAttentionFromCurrentUser(optionItem, optionItem.parentReportAction);
-
/**
* Show the ReportActionContextMenu modal popover.
*
- * @param {Object} [event] - A press event.
+ * @param [event] - A press event.
*/
- const showPopover = (event) => {
+ const showPopover = (event: MouseEvent | GestureResponderEvent) => {
if (!isFocusedRef.current && isSmallScreenWidth) {
return;
}
@@ -131,33 +91,32 @@ function OptionRowLHN(props) {
CONST.CONTEXT_MENU_TYPES.REPORT,
event,
'',
- popoverAnchor,
- props.reportID,
+ popoverAnchor.current,
+ reportID,
'0',
- props.reportID,
+ reportID,
undefined,
() => {},
() => setIsContextMenuActive(false),
false,
false,
optionItem.isPinned,
- optionItem.isUnread,
+ !!optionItem.isUnread,
);
};
- const emojiCode = lodashGet(optionItem, 'status.emojiCode', '');
- const statusText = lodashGet(optionItem, 'status.text', '');
- const statusClearAfterDate = lodashGet(optionItem, 'status.clearAfter', '');
+ const emojiCode = optionItem.status?.emojiCode ?? '';
+ const statusText = optionItem.status?.text ?? '';
+ const statusClearAfterDate = optionItem.status?.clearAfter ?? '';
const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate);
const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText;
- const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID));
+ const report = ReportUtils.getReport(optionItem.reportID ?? '');
+ const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(!isEmptyObject(report) ? report : null);
- const isGroupChat =
- optionItem.type === CONST.REPORT.TYPE.CHAT && _.isEmpty(optionItem.chatType) && !optionItem.isThread && lodashGet(optionItem, 'displayNamesWithTooltips.length', 0) > 2;
- const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text;
-
- const subscriptAvatarBorderColor = props.isFocused ? focusedBackgroundColor : theme.sidebar;
+ const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips?.length ?? 0) > 2;
+ const fullTitle = isGroupChat ? getGroupChatName(!isEmptyObject(report) ? report : null) : optionItem.text;
+ const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar;
return (
(
{
- if (e) {
- e.preventDefault();
- }
+ onPress={(event) => {
+ event?.preventDefault();
// Enable Composer to focus on clicking the same chat after opening the context menu.
ReportActionComposeFocusManager.focus();
- props.onSelectRow(optionItem, popoverAnchor);
+ onSelectRow(optionItem, popoverAnchor);
}}
- onMouseDown={(e) => {
+ onMouseDown={(event) => {
// Allow composer blur on right click
- if (!e) {
+ if (!event) {
return;
}
// Prevent composer blur on left click
- e.preventDefault();
+ event.preventDefault();
}}
testID={optionItem.reportID}
- onSecondaryInteraction={(e) => {
- showPopover(e);
+ onSecondaryInteraction={(event) => {
+ showPopover(event);
// Ensure that we blur the composer when opening context menu, so that only one component is focused at a time
if (DomUtils.getActiveElement()) {
- DomUtils.getActiveElement().blur();
+ (DomUtils.getActiveElement() as HTMLElement | null)?.blur();
}
}}
withoutFocusOnSecondaryInteraction
@@ -203,32 +160,32 @@ function OptionRowLHN(props) {
styles.sidebarLink,
styles.sidebarLinkInnerLHN,
StyleUtils.getBackgroundColorStyle(theme.sidebar),
- props.isFocused ? styles.sidebarLinkActive : null,
- (hovered || isContextMenuActive) && !props.isFocused ? props.hoverStyle || styles.sidebarLinkHover : null,
+ isFocused ? styles.sidebarLinkActive : null,
+ (hovered || isContextMenuActive) && !isFocused ? styles.sidebarLinkHover : null,
]}
role={CONST.ROLE.BUTTON}
accessibilityLabel={translate('accessibilityHints.navigatesToChat')}
- needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2}
+ needsOffscreenAlphaCompositing={(optionItem?.icons?.length ?? 0) >= 2}
>
- {!_.isEmpty(optionItem.icons) &&
+ {(optionItem.icons?.length ?? 0) > 0 &&
(optionItem.shouldShowSubscript ? (
) : (
@@ -237,13 +194,17 @@ function OptionRowLHN(props) {
{isStatusVisible && (
@@ -265,7 +226,7 @@ function OptionRowLHN(props) {
) : null}
- {optionItem.descriptiveText ? (
+ {optionItem?.descriptiveText ? (
{optionItem.descriptiveText}
@@ -324,10 +285,6 @@ function OptionRowLHN(props) {
);
}
-OptionRowLHN.propTypes = propTypes;
-OptionRowLHN.defaultProps = defaultProps;
OptionRowLHN.displayName = 'OptionRowLHN';
export default React.memo(OptionRowLHN);
-
-export {propTypes, defaultProps};
diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.tsx
similarity index 56%
rename from src/components/LHNOptionsList/OptionRowLHNData.js
rename to src/components/LHNOptionsList/OptionRowLHNData.tsx
index 8bdf065a94fd..dca74e880169 100644
--- a/src/components/LHNOptionsList/OptionRowLHNData.js
+++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx
@@ -1,65 +1,14 @@
import {deepEqual} from 'fast-equals';
-import PropTypes from 'prop-types';
import React, {useEffect, useMemo, useRef} from 'react';
-import _ from 'underscore';
-import participantPropTypes from '@components/participantPropTypes';
-import transactionPropTypes from '@components/transactionPropTypes';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import SidebarUtils from '@libs/SidebarUtils';
import * as TransactionUtils from '@libs/TransactionUtils';
-import {transactionViolationsPropType} from '@libs/Violations/propTypes';
-import reportActionPropTypes from '@pages/home/report/reportActionPropTypes';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
-import OptionRowLHN, {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './OptionRowLHN';
-
-const propTypes = {
- /** Whether row should be focused */
- isFocused: PropTypes.bool,
-
- /** List of users' personal details */
- personalDetails: PropTypes.objectOf(participantPropTypes),
-
- /** The preferred language for the app */
- preferredLocale: PropTypes.string,
-
- /** The full data of the report */
- // eslint-disable-next-line react/forbid-prop-types
- fullReport: PropTypes.object,
-
- /** The policy which the user has access to and which the report could be tied to */
- policy: PropTypes.shape({
- /** The ID of the policy */
- id: PropTypes.string,
- /** Name of the policy */
- name: PropTypes.string,
- /** Avatar of the policy */
- avatar: PropTypes.string,
- }),
-
- /** The action from the parent report */
- parentReportAction: PropTypes.shape(reportActionPropTypes),
-
- /** The transaction from the parent report action */
- transaction: transactionPropTypes,
-
- /** Any violations associated with the transaction */
- transactionViolations: transactionViolationsPropType,
-
- ...basePropTypes,
-};
-
-const defaultProps = {
- isFocused: false,
- personalDetails: {},
- fullReport: {},
- policy: {},
- parentReportAction: {},
- transaction: {},
- preferredLocale: CONST.LOCALES.DEFAULT,
- ...baseDefaultProps,
-};
+import type {OptionData} from '@src/libs/ReportUtils';
+import OptionRowLHN from './OptionRowLHN';
+import type {OptionRowLHNDataProps} from './types';
/*
* This component gets the data from onyx for the actual
@@ -68,11 +17,11 @@ const defaultProps = {
* re-render if the data really changed.
*/
function OptionRowLHNData({
- isFocused,
+ isFocused = false,
fullReport,
reportActions,
- personalDetails,
- preferredLocale,
+ personalDetails = {},
+ preferredLocale = CONST.LOCALES.DEFAULT,
comment,
policy,
receiptTransactions,
@@ -81,18 +30,18 @@ function OptionRowLHNData({
transactionViolations,
canUseViolations,
...propsToForward
-}) {
+}: OptionRowLHNDataProps) {
const reportID = propsToForward.reportID;
- const optionItemRef = useRef();
+ const optionItemRef = useRef();
const linkedTransaction = useMemo(() => {
const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions);
- const lastReportAction = _.first(sortedReportActions);
+ const lastReportAction = sortedReportActions[0];
return TransactionUtils.getLinkedTransaction(lastReportAction);
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [fullReport.reportID, receiptTransactions, reportActions]);
+ }, [fullReport?.reportID, receiptTransactions, reportActions]);
- const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction);
+ const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null);
const optionItem = useMemo(() => {
// Note: ideally we'd have this as a dependent selector in onyx!
@@ -100,15 +49,17 @@ function OptionRowLHNData({
report: fullReport,
reportActions,
personalDetails,
- preferredLocale,
+ preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT,
policy,
parentReportAction,
- hasViolations,
+ hasViolations: !!hasViolations,
});
if (deepEqual(item, optionItemRef.current)) {
return optionItemRef.current;
}
+
optionItemRef.current = item;
+
return item;
// Listen parentReportAction to update title of thread report when parentReportAction changed
// Listen to transaction to update title of transaction report when transaction changed
@@ -116,7 +67,7 @@ function OptionRowLHNData({
}, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction, transactionViolations, canUseViolations]);
useEffect(() => {
- if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) {
+ if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) {
return;
}
Report.setReportWithDraft(reportID, true);
@@ -133,8 +84,6 @@ function OptionRowLHNData({
);
}
-OptionRowLHNData.propTypes = propTypes;
-OptionRowLHNData.defaultProps = defaultProps;
OptionRowLHNData.displayName = 'OptionRowLHNData';
/**
diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts
new file mode 100644
index 000000000000..24cebb8e3da2
--- /dev/null
+++ b/src/components/LHNOptionsList/types.ts
@@ -0,0 +1,124 @@
+import type {ContentStyle} from '@shopify/flash-list';
+import type {RefObject} from 'react';
+import type {StyleProp, TextStyle, View, ViewStyle} from 'react-native';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
+import type {ValueOf} from 'type-fest';
+import type {CurrentReportIDContextValue} from '@components/withCurrentReportID';
+import type CONST from '@src/CONST';
+import type {OptionData} from '@src/libs/ReportUtils';
+import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx';
+
+type OptionMode = ValueOf;
+
+type LHNOptionsListOnyxProps = {
+ /** The policy which the user has access to and which the report could be tied to */
+ policy: OnyxCollection;
+
+ /** All reports shared with the user */
+ reports: OnyxCollection;
+
+ /** Array of report actions for this report */
+ reportActions: OnyxCollection;
+
+ /** Indicates which locale the user currently has selected */
+ preferredLocale: OnyxEntry;
+
+ /** List of users' personal details */
+ personalDetails: OnyxEntry;
+
+ /** The transaction from the parent report action */
+ transactions: OnyxCollection;
+
+ /** List of draft comments */
+ draftComments: OnyxCollection;
+
+ /** The list of transaction violations */
+ transactionViolations: OnyxCollection;
+};
+
+type CustomLHNOptionsListProps = {
+ /** Wrapper style for the section list */
+ style?: StyleProp;
+
+ /** Extra styles for the section list container */
+ contentContainerStyles?: StyleProp;
+
+ /** Sections for the section list */
+ data: string[];
+
+ /** Callback to fire when a row is selected */
+ onSelectRow: (reportID: string) => void;
+
+ /** Toggle between compact and default view of the option */
+ optionMode: OptionMode;
+
+ /** Whether to allow option focus or not */
+ shouldDisableFocusOptions?: boolean;
+};
+
+type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps;
+
+type OptionRowLHNDataProps = {
+ /** Whether row should be focused */
+ isFocused?: boolean;
+
+ /** List of users' personal details */
+ personalDetails?: PersonalDetailsList;
+
+ /** The preferred language for the app */
+ preferredLocale?: OnyxEntry;
+
+ /** The full data of the report */
+ fullReport: OnyxEntry;
+
+ /** The policy which the user has access to and which the report could be tied to */
+ policy?: OnyxEntry;
+
+ /** The action from the parent report */
+ parentReportAction?: OnyxEntry;
+
+ /** The transaction from the parent report action */
+ transaction: OnyxEntry;
+
+ /** Comment added to report */
+ comment: string;
+
+ /** The receipt transaction from the parent report action */
+ receiptTransactions: OnyxCollection;
+
+ /** The reportID of the report */
+ reportID: string;
+
+ /** Array of report actions for this report */
+ reportActions: OnyxEntry;
+
+ /** List of transaction violation */
+ transactionViolations: OnyxCollection;
+
+ /** Whether the user can use violations */
+ canUseViolations: boolean | undefined;
+};
+
+type OptionRowLHNProps = {
+ /** The ID of the report that the option is for */
+ reportID: string;
+
+ /** Whether this option is currently in focus so we can modify its style */
+ isFocused?: boolean;
+
+ /** A function that is called when an option is selected. Selected option is passed as a param */
+ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void;
+
+ /** Toggle between compact and default view */
+ viewMode?: OptionMode;
+
+ /** Additional style props */
+ style?: StyleProp;
+
+ /** The item that should be rendered */
+ optionItem?: OptionData;
+};
+
+type RenderItemProps = {item: string};
+
+export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps};
diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx
index 00cf248ad838..24a647f900a7 100644
--- a/src/components/SubscriptAvatar.tsx
+++ b/src/components/SubscriptAvatar.tsx
@@ -4,35 +4,17 @@ import type {ValueOf} from 'type-fest';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
-import type {AvatarSource} from '@libs/UserUtils';
import CONST from '@src/CONST';
-import type {AvatarType} from '@src/types/onyx/OnyxCommon';
+import type {Icon} from '@src/types/onyx/OnyxCommon';
import Avatar from './Avatar';
import UserDetailsTooltip from './UserDetailsTooltip';
-type SubAvatar = {
- /** Avatar source to display */
- source?: AvatarSource;
-
- /** Denotes whether it is an avatar or a workspace avatar */
- type?: AvatarType;
-
- /** Owner of the avatar. If user, displayName. If workspace, policy name */
- name?: string;
-
- /** Avatar id */
- id?: number | string;
-
- /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */
- fallbackIcon?: AvatarSource;
-};
-
type SubscriptAvatarProps = {
/** Avatar URL or icon */
- mainAvatar?: SubAvatar;
+ mainAvatar?: Icon;
/** Subscript avatar URL or icon */
- secondaryAvatar?: SubAvatar;
+ secondaryAvatar?: Icon;
/** Set the size of avatars */
size?: ValueOf;
@@ -47,7 +29,7 @@ type SubscriptAvatarProps = {
showTooltip?: boolean;
};
-function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) {
+function SubscriptAvatar({mainAvatar, secondaryAvatar, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
@@ -59,23 +41,23 @@ function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AV
diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts
index 26b3665ca4ce..5d925ae1c684 100644
--- a/src/libs/GroupChatUtils.ts
+++ b/src/libs/GroupChatUtils.ts
@@ -1,11 +1,12 @@
+import type {OnyxEntry} from 'react-native-onyx';
import type {Report} from '@src/types/onyx';
import * as ReportUtils from './ReportUtils';
/**
* Returns the report name if the report is a group chat
*/
-function getGroupChatName(report: Report): string | undefined {
- const participants = report.participantAccountIDs ?? [];
+function getGroupChatName(report: OnyxEntry): string | undefined {
+ const participants = report?.participantAccountIDs ?? [];
const isMultipleParticipantReport = participants.length > 1;
return participants
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 91fa7b35e824..5b0464c58898 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -368,13 +368,12 @@ type CustomIcon = {
type OptionData = {
text: string;
alternateText?: string | null;
- allReportErrors?: Errors | null;
+ allReportErrors?: Errors;
brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null;
tooltipText?: string | null;
alternateTextMaxLines?: number;
boldStyle?: boolean;
customIcon?: CustomIcon;
- descriptiveText?: string;
subtitle?: string | null;
login?: string | null;
accountID?: number | null;
@@ -395,8 +394,9 @@ type OptionData = {
isAllowedToComment?: boolean | null;
isThread?: boolean | null;
isTaskReport?: boolean | null;
- parentReportAction?: ReportAction;
+ parentReportAction?: OnyxEntry;
displayNamesWithTooltips?: DisplayNameWithTooltips | null;
+ descriptiveText?: string;
isDisabled?: boolean | null;
name?: string | null;
} & Report;
@@ -3548,8 +3548,8 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b
/**
* Checks to see if a report's parentAction is a money request that contains a violation
*/
-function doesTransactionThreadHaveViolations(report: Report, transactionViolations: OnyxCollection, parentReportAction: ReportAction): boolean {
- if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) {
+function doesTransactionThreadHaveViolations(report: OnyxEntry, transactionViolations: OnyxCollection, parentReportAction: OnyxEntry): boolean {
+ if (parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) {
return false;
}
const {IOUTransactionID, IOUReportID} = parentReportAction.originalMessage ?? {};
@@ -3559,7 +3559,7 @@ function doesTransactionThreadHaveViolations(report: Report, transactionViolatio
if (!isCurrentUserSubmitter(IOUReportID)) {
return false;
}
- if (report.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) {
+ if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) {
return false;
}
return TransactionUtils.hasViolation(IOUTransactionID, transactionViolations);
@@ -4549,7 +4549,7 @@ function shouldDisplayThreadReplies(reportAction: OnyxEntry, repor
* - The action is a whisper action and it's neither a report preview nor IOU action
* - The action is the thread's first chat
*/
-function shouldDisableThread(reportAction: OnyxEntry, reportID: string) {
+function shouldDisableThread(reportAction: OnyxEntry, reportID: string): boolean {
const isSplitBillAction = ReportActionsUtils.isSplitBillAction(reportAction);
const isDeletedAction = ReportActionsUtils.isDeletedAction(reportAction);
const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction);
diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index 445d9dc30dd8..4a2c4a2da22a 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -1,17 +1,17 @@
/* eslint-disable rulesdir/prefer-underscore-method */
import Str from 'expensify-common/lib/str';
-import type {OnyxCollection} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {PersonalDetails, TransactionViolation} from '@src/types/onyx';
+import type {PersonalDetails, PersonalDetailsList, TransactionViolation} from '@src/types/onyx';
import type Beta from '@src/types/onyx/Beta';
-import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
import type Policy from '@src/types/onyx/Policy';
import type Report from '@src/types/onyx/Report';
import type {ReportActions} from '@src/types/onyx/ReportAction';
import type ReportAction from '@src/types/onyx/ReportAction';
+import type DeepValueOf from '@src/types/utils/DeepValueOf';
import * as CollectionUtils from './CollectionUtils';
import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
@@ -257,12 +257,12 @@ function getOptionData({
parentReportAction,
hasViolations,
}: {
- report: Report;
- reportActions: Record;
- personalDetails: Record;
- preferredLocale: ValueOf;
- policy: Policy;
- parentReportAction: ReportAction;
+ report: OnyxEntry;
+ reportActions: OnyxEntry;
+ personalDetails: OnyxEntry;
+ preferredLocale: DeepValueOf;
+ policy: OnyxEntry | undefined;
+ parentReportAction: OnyxEntry | undefined;
hasViolations: boolean;
}): ReportUtils.OptionData | undefined {
// When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for
@@ -275,7 +275,7 @@ function getOptionData({
const result: ReportUtils.OptionData = {
text: '',
alternateText: null,
- allReportErrors: null,
+ allReportErrors: undefined,
brickRoadIndicator: null,
tooltipText: null,
subtitle: null,
@@ -317,7 +317,8 @@ function getOptionData({
result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report);
result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined;
- result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors;
+ // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript.
+ result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions);
result.brickRoadIndicator = hasErrors || hasViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : '';
result.ownerAccountID = report.ownerAccountID;
result.managerID = report.managerID;
diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts
index 8b040dd8d72c..ea8b3f258c89 100644
--- a/src/styles/utils/index.ts
+++ b/src/styles/utils/index.ts
@@ -1,5 +1,5 @@
import {StyleSheet} from 'react-native';
-import type {Animated, DimensionValue, ImageStyle, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native';
+import type {Animated, ColorValue, DimensionValue, ImageStyle, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import type {EdgeInsets} from 'react-native-safe-area-context';
import type {ValueOf} from 'type-fest';
@@ -388,7 +388,7 @@ function getWidthStyle(width: number): ViewStyle {
/**
* Returns a style with backgroundColor and borderColor set to the same color
*/
-function getBackgroundAndBorderStyle(backgroundColor: string | undefined): ViewStyle {
+function getBackgroundAndBorderStyle(backgroundColor: ColorValue | undefined): ViewStyle {
return {
backgroundColor,
borderColor: backgroundColor,
@@ -398,7 +398,7 @@ function getBackgroundAndBorderStyle(backgroundColor: string | undefined): ViewS
/**
* Returns a style with the specified backgroundColor
*/
-function getBackgroundColorStyle(backgroundColor: string): ViewStyle {
+function getBackgroundColorStyle(backgroundColor: ColorValue): ViewStyle {
return {
backgroundColor,
};