Skip to content

Commit 632b4eb

Browse files
perf: decompose OptionRowLHN avatar/delegate logic into a separate component
1 parent 4cfb6a6 commit 632b4eb

2 files changed

Lines changed: 100 additions & 59 deletions

File tree

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import React, {useMemo} from 'react';
2+
import type {ColorValue, StyleProp, ViewStyle} from 'react-native';
3+
import type {OnyxEntry} from 'react-native-onyx';
4+
import {usePersonalDetails} from '@components/OnyxListItemProvider';
5+
import {shouldOptionShowTooltip} from '@libs/OptionsListUtils';
6+
import {getDelegateAccountIDFromReportAction} from '@libs/ReportActionsUtils';
7+
import type {OptionData} from '@libs/ReportUtils';
8+
import CONST from '@src/CONST';
9+
import type {Report} from '@src/types/onyx';
10+
import LHNAvatar from './LHNAvatar';
11+
12+
type OptionRowAvatarProps = {
13+
optionItem: OptionData;
14+
report: OnyxEntry<Report>;
15+
isInFocusMode: boolean;
16+
subscriptAvatarBorderColor: ColorValue;
17+
secondaryAvatarBackgroundColor: ColorValue;
18+
singleAvatarContainerStyle: StyleProp<ViewStyle>;
19+
};
20+
21+
function OptionRowAvatar({optionItem, report, isInFocusMode, subscriptAvatarBorderColor, secondaryAvatarBackgroundColor, singleAvatarContainerStyle}: OptionRowAvatarProps) {
22+
const personalDetails = usePersonalDetails();
23+
24+
const delegateAccountID = useMemo(
25+
() => getDelegateAccountIDFromReportAction(optionItem?.parentReportAction),
26+
// eslint-disable-next-line react-hooks/exhaustive-deps -- getDelegateAccountIDFromReportAction is a stable import; only parentReportAction determines the result
27+
[optionItem?.parentReportAction],
28+
);
29+
30+
// Match the header's delegate avatar logic: when a delegate exists on the
31+
// parent report action, the header (useReportActionAvatars) shows the
32+
// delegate's avatar as primary instead of the report owner's.
33+
const skipDelegate = report?.type === CONST.REPORT.TYPE.INVOICE || (optionItem?.isTaskReport && !report?.chatReportID);
34+
35+
const icons = useMemo(() => {
36+
let result = optionItem?.icons ?? [];
37+
if (!skipDelegate && delegateAccountID && personalDetails && result.length > 0) {
38+
const delegateDetails = personalDetails[delegateAccountID];
39+
if (delegateDetails) {
40+
const updatedIcons = [...result];
41+
const firstIcon = updatedIcons.at(0);
42+
if (firstIcon) {
43+
updatedIcons[0] = {
44+
...firstIcon,
45+
source: delegateDetails.avatar ?? '',
46+
name: delegateDetails.displayName ?? '',
47+
id: delegateAccountID,
48+
};
49+
}
50+
result = updatedIcons;
51+
}
52+
}
53+
54+
return result;
55+
}, [optionItem?.icons, skipDelegate, delegateAccountID, personalDetails]);
56+
57+
const delegateTooltipAccountID = useMemo(() => {
58+
if (!skipDelegate && delegateAccountID && personalDetails?.[delegateAccountID] && optionItem?.icons?.length) {
59+
return Number(optionItem.icons.at(0)?.id ?? CONST.DEFAULT_NUMBER_ID);
60+
}
61+
return undefined;
62+
}, [skipDelegate, delegateAccountID, personalDetails, optionItem?.icons]);
63+
64+
const firstIcon = optionItem.icons?.at(0);
65+
66+
if (!optionItem.icons?.length || !firstIcon) {
67+
return null;
68+
}
69+
70+
return (
71+
<LHNAvatar
72+
icons={icons}
73+
shouldShowSubscript={!!optionItem.shouldShowSubscript}
74+
size={isInFocusMode ? CONST.AVATAR_SIZE.SMALL : CONST.AVATAR_SIZE.DEFAULT}
75+
subscriptAvatarBorderColor={subscriptAvatarBorderColor}
76+
useMidSubscriptSize={isInFocusMode}
77+
secondaryAvatarBackgroundColor={secondaryAvatarBackgroundColor}
78+
singleAvatarContainerStyle={singleAvatarContainerStyle}
79+
shouldShowTooltip={shouldOptionShowTooltip(optionItem)}
80+
delegateAccountID={skipDelegate ? undefined : delegateAccountID}
81+
delegateTooltipAccountID={delegateTooltipAccountID}
82+
/>
83+
);
84+
}
85+
86+
OptionRowAvatar.displayName = 'OptionRowAvatar';
87+
88+
export default OptionRowAvatar;

src/components/LHNOptionsList/OptionRowLHN.tsx

Lines changed: 12 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import DisplayNames from '@components/DisplayNames';
66
import Hoverable from '@components/Hoverable';
77
import Icon from '@components/Icon';
88
import OfflineWithFeedback from '@components/OfflineWithFeedback';
9-
import {usePersonalDetails, useSession} from '@components/OnyxListItemProvider';
9+
import {useSession} from '@components/OnyxListItemProvider';
1010
import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction';
1111
import {useProductTrainingContext} from '@components/ProductTrainingContext';
1212
import Text from '@components/Text';
@@ -26,9 +26,8 @@ import DateUtils from '@libs/DateUtils';
2626
import DomUtils from '@libs/DomUtils';
2727
import {containsCustomEmoji as containsCustomEmojiUtils, containsOnlyCustomEmoji} from '@libs/EmojiUtils';
2828
import FS from '@libs/Fullstory';
29-
import {shouldOptionShowTooltip, shouldUseBoldText} from '@libs/OptionsListUtils';
29+
import {shouldUseBoldText} from '@libs/OptionsListUtils';
3030
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
31-
import {getDelegateAccountIDFromReportAction} from '@libs/ReportActionsUtils';
3231
import {isAdminRoom, isChatUsedForOnboarding as isChatUsedForOnboardingReportUtils, isConciergeChatReport, isGroupChat, isOneOnOneChat, isSystemChat} from '@libs/ReportUtils';
3332
import {startSpan} from '@libs/telemetry/activeSpans';
3433
import TextWithEmojiFragment from '@pages/inbox/report/comment/TextWithEmojiFragment';
@@ -37,8 +36,8 @@ import FreeTrial from '@pages/settings/Subscription/FreeTrial';
3736
import variables from '@styles/variables';
3837
import CONST from '@src/CONST';
3938
import {isEmptyObject} from '@src/types/utils/EmptyObject';
40-
import LHNAvatar from './LHNAvatar';
4139
import {useLHNTooltipContext} from './LHNTooltipContext';
40+
import OptionRowAvatar from './OptionRowAvatar';
4241
import type {OptionRowLHNProps} from './types';
4342

4443
function OptionRowLHN({
@@ -65,7 +64,6 @@ function OptionRowLHN({
6564
const {onboardingPurpose, onboarding, isFullscreenVisible, firstReportIDWithGBRorRBR, isScreenFocused, isReportsSplitNavigatorLast} = useLHNTooltipContext();
6665
const shouldShowRBRorGBRTooltip = firstReportIDWithGBRorRBR === reportID;
6766

68-
const personalDetails = usePersonalDetails();
6967
const session = useSession();
7068
const isOnboardingGuideAssigned = onboardingPurpose === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && !session?.email?.includes('+');
7169
const isChatUsedForOnboarding = isChatUsedForOnboardingReportUtils(report, onboarding, conciergeReportID, onboardingPurpose);
@@ -101,45 +99,6 @@ function OptionRowLHN({
10199
[optionItem?.alternateText],
102100
);
103101

104-
const delegateAccountID = useMemo(
105-
() => getDelegateAccountIDFromReportAction(optionItem?.parentReportAction),
106-
// eslint-disable-next-line react-hooks/exhaustive-deps -- getDelegateAccountIDFromReportAction is a stable import; only parentReportAction determines the result
107-
[optionItem?.parentReportAction],
108-
);
109-
110-
// Match the header's delegate avatar logic: when a delegate exists on the
111-
// parent report action, the header (useReportActionAvatars) shows the
112-
// delegate's avatar as primary instead of the report owner's.
113-
const skipDelegate = report?.type === CONST.REPORT.TYPE.INVOICE || (optionItem?.isTaskReport && !report?.chatReportID);
114-
const icons = useMemo(() => {
115-
let result = optionItem?.icons ?? [];
116-
if (!skipDelegate && delegateAccountID && personalDetails && result.length > 0) {
117-
const delegateDetails = personalDetails[delegateAccountID];
118-
if (delegateDetails) {
119-
const updatedIcons = [...result];
120-
const firstIcon = updatedIcons.at(0);
121-
if (firstIcon) {
122-
updatedIcons[0] = {
123-
...firstIcon,
124-
source: delegateDetails.avatar ?? '',
125-
name: delegateDetails.displayName ?? '',
126-
id: delegateAccountID,
127-
};
128-
}
129-
result = updatedIcons;
130-
}
131-
}
132-
133-
return result;
134-
}, [optionItem?.icons, skipDelegate, delegateAccountID, personalDetails]);
135-
136-
const delegateTooltipAccountID = useMemo(() => {
137-
if (!skipDelegate && delegateAccountID && personalDetails?.[delegateAccountID] && optionItem?.icons?.length) {
138-
return Number(optionItem.icons.at(0)?.id ?? CONST.DEFAULT_NUMBER_ID);
139-
}
140-
return undefined;
141-
}, [skipDelegate, delegateAccountID, personalDetails, optionItem?.icons]);
142-
143102
const singleAvatarContainerStyle = [styles.actionAvatar, styles.mr3];
144103

145104
if (!optionItem && !isOptionFocused) {
@@ -219,7 +178,6 @@ function OptionRowLHN({
219178
const isStatusVisible = !!emojiCode && isOneOnOneChat(!isEmptyObject(report) ? report : undefined);
220179

221180
const subscriptAvatarBorderColor = isOptionFocused ? focusedBackgroundColor : theme.sidebar;
222-
const firstIcon = optionItem.icons?.at(0);
223181

224182
// This is used to ensure that we display the text exactly as the user entered it when displaying LHN title, instead of parsing their text to HTML.
225183
const shouldParseFullTitle = optionItem?.parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT && !isGroupChat(report);
@@ -325,20 +283,15 @@ function OptionRowLHN({
325283
>
326284
<View style={sidebarInnerRowStyle}>
327285
<View style={[styles.flexRow, styles.alignItemsCenter]}>
328-
{!!optionItem.icons?.length && !!firstIcon && (
329-
<LHNAvatar
330-
icons={icons}
331-
shouldShowSubscript={!!optionItem.shouldShowSubscript}
332-
size={isInFocusMode ? CONST.AVATAR_SIZE.SMALL : CONST.AVATAR_SIZE.DEFAULT}
333-
subscriptAvatarBorderColor={hovered && !isOptionFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
334-
useMidSubscriptSize={isInFocusMode}
335-
secondaryAvatarBackgroundColor={secondaryAvatarBgColor}
336-
singleAvatarContainerStyle={singleAvatarContainerStyle}
337-
shouldShowTooltip={shouldOptionShowTooltip(optionItem)}
338-
delegateAccountID={skipDelegate ? undefined : delegateAccountID}
339-
delegateTooltipAccountID={delegateTooltipAccountID}
340-
/>
341-
)}
286+
<OptionRowAvatar
287+
optionItem={optionItem}
288+
report={report}
289+
isInFocusMode={isInFocusMode}
290+
subscriptAvatarBorderColor={hovered && !isOptionFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
291+
secondaryAvatarBackgroundColor={secondaryAvatarBgColor}
292+
singleAvatarContainerStyle={singleAvatarContainerStyle}
293+
/>
294+
342295
<View style={contentContainerStyles}>
343296
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mw100, styles.overflowHidden]}>
344297
<DisplayNames

0 commit comments

Comments
 (0)