Skip to content

Commit 8cf2a25

Browse files
authored
Merge pull request Expensify#90334 from callstack-internal/refactor/option-row-offline-and-tooltip
[No QA] refactor: split OfflineWrapper and ProductTrainingTooltip from OptionRowTooltipLayer
2 parents a444e78 + c2c2617 commit 8cf2a25

4 files changed

Lines changed: 128 additions & 100 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import type {ReactNode} from 'react';
3+
import OfflineWithFeedback from '@components/OfflineWithFeedback';
4+
import type {OptionData} from '@libs/ReportUtils';
5+
6+
type OfflineWrapperProps = {
7+
/** Pending action forwarded to OfflineWithFeedback to drive opacity and strikethrough. */
8+
pendingAction: OptionData['pendingAction'];
9+
10+
/** Errors forwarded to OfflineWithFeedback. Error messages themselves are hidden in the LHN. */
11+
errors: OptionData['allReportErrors'];
12+
13+
/** Row content to wrap. */
14+
children: ReactNode;
15+
};
16+
17+
function OfflineWrapper({pendingAction, errors, children}: OfflineWrapperProps) {
18+
return (
19+
<OfflineWithFeedback
20+
pendingAction={pendingAction}
21+
errors={errors}
22+
shouldShowErrorMessages={false}
23+
needsOffscreenAlphaCompositing
24+
>
25+
{children}
26+
</OfflineWithFeedback>
27+
);
28+
}
29+
30+
OfflineWrapper.displayName = 'OptionRow.OfflineWrapper';
31+
32+
export default OfflineWrapper;

src/components/LHNOptionsList/OptionRowLHN/OptionRowTooltipLayer.tsx renamed to src/components/LHNOptionsList/OptionRowLHN/OptionRow/ProductTrainingTooltip.tsx

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
2+
import type {ReactElement} from 'react';
23
import {useLHNTooltipContext} from '@components/LHNOptionsList/LHNTooltipContext';
3-
import OfflineWithFeedback from '@components/OfflineWithFeedback';
4+
import useLHNRowProductTrainingTooltip from '@components/LHNOptionsList/OptionRowLHN/useLHNRowProductTrainingTooltip';
45
import {useSession} from '@components/OnyxListItemProvider';
56
import EducationalTooltip from '@components/Tooltip/EducationalTooltip';
67
import useOnyx from '@hooks/useOnyx';
@@ -10,21 +11,20 @@ import type {OptionData} from '@libs/ReportUtils';
1011
import variables from '@styles/variables';
1112
import CONST from '@src/CONST';
1213
import ONYXKEYS from '@src/ONYXKEYS';
13-
import useLHNRowProductTrainingTooltip from './useLHNRowProductTrainingTooltip';
1414

15-
type OptionRowTooltipLayerProps = {
16-
/** Option data, drives onboarding eligibility checks and forwards pendingAction/errors to OfflineWithFeedback */
15+
type ProductTrainingTooltipProps = {
16+
/** Option data, drives onboarding eligibility checks for the educational tooltip. */
1717
optionItem: OptionData;
1818

19-
/** Renders the row content. */
20-
renderChildren: () => React.ReactNode;
19+
/** Row content the tooltip anchors to. */
20+
children: ReactElement;
2121
};
2222

23-
type OptionRowTooltipLayerInnerProps = {
24-
renderChildren: () => React.ReactNode;
23+
type ProductTrainingTooltipInnerProps = {
24+
children: ReactElement;
2525
};
2626

27-
function OptionRowTooltipLayerInner({renderChildren}: OptionRowTooltipLayerInnerProps) {
27+
function ProductTrainingTooltipInner({children}: ProductTrainingTooltipInnerProps) {
2828
const styles = useThemeStyles();
2929
const {shouldShowProductTrainingTooltip, renderProductTrainingTooltip, hideProductTrainingTooltip} = useLHNRowProductTrainingTooltip();
3030

@@ -42,14 +42,14 @@ function OptionRowTooltipLayerInner({renderChildren}: OptionRowTooltipLayerInner
4242
onTooltipPress={hideProductTrainingTooltip}
4343
shouldHideOnScroll
4444
>
45-
{renderChildren()}
45+
{children}
4646
</EducationalTooltip>
4747
);
4848
}
4949

50-
OptionRowTooltipLayerInner.displayName = 'OptionRowTooltipLayerInner';
50+
ProductTrainingTooltipInner.displayName = 'OptionRow.ProductTrainingTooltipInner';
5151

52-
function OptionRowTooltipLayer({optionItem, renderChildren}: OptionRowTooltipLayerProps) {
52+
function ProductTrainingTooltip({optionItem, children}: ProductTrainingTooltipProps) {
5353
const {firstReportIDWithGBRorRBR, onboardingPurpose, onboarding} = useLHNTooltipContext();
5454
const session = useSession();
5555
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
@@ -62,18 +62,13 @@ function OptionRowTooltipLayer({optionItem, renderChildren}: OptionRowTooltipLay
6262
// Skip the inner component (and its heavy hooks) entirely when the row can never show a tooltip.
6363
const shouldEvaluateTooltip = shouldShowRBRorGBRTooltip || shouldShowGetStartedTooltip;
6464

65-
return (
66-
<OfflineWithFeedback
67-
pendingAction={optionItem.pendingAction}
68-
errors={optionItem.allReportErrors}
69-
shouldShowErrorMessages={false}
70-
needsOffscreenAlphaCompositing
71-
>
72-
{shouldEvaluateTooltip ? <OptionRowTooltipLayerInner renderChildren={renderChildren} /> : renderChildren()}
73-
</OfflineWithFeedback>
74-
);
65+
if (!shouldEvaluateTooltip) {
66+
return children;
67+
}
68+
69+
return <ProductTrainingTooltipInner>{children}</ProductTrainingTooltipInner>;
7570
}
7671

77-
OptionRowTooltipLayer.displayName = 'OptionRowTooltipLayer';
72+
ProductTrainingTooltip.displayName = 'OptionRow.ProductTrainingTooltip';
7873

79-
export default OptionRowTooltipLayer;
74+
export default ProductTrainingTooltip;

src/components/LHNOptionsList/OptionRowLHN/OptionRowLHNCore.tsx

Lines changed: 76 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ import DescriptiveText from './OptionRow/DescriptiveText';
1616
import DraftIndicator from './OptionRow/DraftIndicator';
1717
import ErrorBadge from './OptionRow/ErrorBadge';
1818
import InfoBadge from './OptionRow/InfoBadge';
19+
import OfflineWrapper from './OptionRow/OfflineWrapper';
1920
import OnboardingBadge from './OptionRow/OnboardingBadge';
2021
import PinIndicator from './OptionRow/PinIndicator';
22+
import ProductTrainingTooltip from './OptionRow/ProductTrainingTooltip';
2123
import Status from './OptionRow/Status';
2224
import Subtitle from './OptionRow/Subtitle';
2325
import Title from './OptionRow/Title';
2426
import OptionRowAvatar from './OptionRowAvatar';
2527
import OptionRowPressable from './OptionRowPressable';
26-
import OptionRowTooltipLayer from './OptionRowTooltipLayer';
2728

2829
function OptionRowLHN({isOptionFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style, onLayout = () => {}, hasDraftComment, testID}: OptionRowLHNProps) {
2930
const {isProduction} = useEnvironment();
@@ -77,86 +78,86 @@ function OptionRowLHN({isOptionFocused = false, onSelectRow = () => {}, optionIt
7778
contextMenuHint,
7879
});
7980

80-
const renderPressableRow = () => (
81-
<OptionRowPressable
82-
optionItem={optionItem}
83-
isOptionFocused={isOptionFocused}
84-
isScreenFocused={isScreenFocused}
85-
popoverAnchor={popoverAnchor}
86-
onSelectRow={onSelectRow}
87-
onLayout={onLayout}
88-
accessibilityLabel={accessibilityLabelWithContextMenuHint}
89-
accessibilityHint={accessibilityHint}
90-
// reportID may be a number contrary to the type definition
91-
testID={typeof optionItem.reportID === 'number' ? String(optionItem.reportID) : optionItem.reportID}
81+
return (
82+
<OfflineWrapper
83+
pendingAction={optionItem.pendingAction}
84+
errors={optionItem.allReportErrors}
9285
>
93-
{(hovered) => {
94-
let secondaryAvatarBgColor = theme.sidebar;
95-
if (isOptionFocused) {
96-
secondaryAvatarBgColor = focusedBackgroundColor;
97-
} else if (hovered) {
98-
secondaryAvatarBgColor = hoveredBackgroundColor;
99-
}
100-
return (
101-
<>
102-
<View style={sidebarInnerRowStyle}>
103-
<View style={[styles.flexRow, styles.alignItemsCenter]}>
104-
<OptionRowAvatar
105-
optionItem={optionItem}
106-
isInFocusMode={isInFocusMode}
107-
subscriptAvatarBorderColor={hovered && !isOptionFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
108-
secondaryAvatarBackgroundColor={secondaryAvatarBgColor}
109-
singleAvatarContainerStyle={singleAvatarContainerStyle}
110-
/>
111-
<View style={contentContainerStyles}>
112-
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mw100, styles.overflowHidden]}>
113-
<Title
86+
<ProductTrainingTooltip optionItem={optionItem}>
87+
<OptionRowPressable
88+
optionItem={optionItem}
89+
isOptionFocused={isOptionFocused}
90+
isScreenFocused={isScreenFocused}
91+
popoverAnchor={popoverAnchor}
92+
onSelectRow={onSelectRow}
93+
onLayout={onLayout}
94+
accessibilityLabel={accessibilityLabelWithContextMenuHint}
95+
accessibilityHint={accessibilityHint}
96+
// reportID may be a number contrary to the type definition
97+
testID={typeof optionItem.reportID === 'number' ? String(optionItem.reportID) : optionItem.reportID}
98+
>
99+
{(hovered) => {
100+
let secondaryAvatarBgColor = theme.sidebar;
101+
if (isOptionFocused) {
102+
secondaryAvatarBgColor = focusedBackgroundColor;
103+
} else if (hovered) {
104+
secondaryAvatarBgColor = hoveredBackgroundColor;
105+
}
106+
return (
107+
<>
108+
<View style={sidebarInnerRowStyle}>
109+
<View style={[styles.flexRow, styles.alignItemsCenter]}>
110+
<OptionRowAvatar
114111
optionItem={optionItem}
115-
displayNameStyle={displayNameStyle}
116-
testID={testID}
112+
isInFocusMode={isInFocusMode}
113+
subscriptAvatarBorderColor={hovered && !isOptionFocused ? hoveredBackgroundColor : subscriptAvatarBorderColor}
114+
secondaryAvatarBackgroundColor={secondaryAvatarBgColor}
115+
singleAvatarContainerStyle={singleAvatarContainerStyle}
116+
/>
117+
<View style={contentContainerStyles}>
118+
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mw100, styles.overflowHidden]}>
119+
<Title
120+
optionItem={optionItem}
121+
displayNameStyle={displayNameStyle}
122+
testID={testID}
123+
/>
124+
<OnboardingBadge optionItem={optionItem} />
125+
<Status optionItem={optionItem} />
126+
</View>
127+
<Subtitle
128+
optionItem={optionItem}
129+
viewMode={viewMode}
130+
isOptionFocused={isOptionFocused}
131+
style={style}
132+
/>
133+
</View>
134+
<DescriptiveText optionItem={optionItem} />
135+
<ErrorBadge
136+
brickRoadIndicator={brickRoadIndicator}
137+
actionBadge={optionItem.actionBadge}
117138
/>
118-
<OnboardingBadge optionItem={optionItem} />
119-
<Status optionItem={optionItem} />
120139
</View>
121-
<Subtitle
122-
optionItem={optionItem}
123-
viewMode={viewMode}
124-
isOptionFocused={isOptionFocused}
125-
style={style}
140+
</View>
141+
<View style={[styles.flexRow, styles.alignItemsCenter]}>
142+
<InfoBadge
143+
brickRoadIndicator={brickRoadIndicator}
144+
actionBadge={optionItem.actionBadge}
145+
/>
146+
<DraftIndicator
147+
hasDraftComment={hasDraftComment}
148+
isAllowedToComment={optionItem.isAllowedToComment}
149+
/>
150+
<PinIndicator
151+
isPinned={optionItem.isPinned}
152+
brickRoadIndicator={brickRoadIndicator}
126153
/>
127154
</View>
128-
<DescriptiveText optionItem={optionItem} />
129-
<ErrorBadge
130-
brickRoadIndicator={brickRoadIndicator}
131-
actionBadge={optionItem.actionBadge}
132-
/>
133-
</View>
134-
</View>
135-
<View style={[styles.flexRow, styles.alignItemsCenter]}>
136-
<InfoBadge
137-
brickRoadIndicator={brickRoadIndicator}
138-
actionBadge={optionItem.actionBadge}
139-
/>
140-
<DraftIndicator
141-
hasDraftComment={hasDraftComment}
142-
isAllowedToComment={optionItem.isAllowedToComment}
143-
/>
144-
<PinIndicator
145-
isPinned={optionItem.isPinned}
146-
brickRoadIndicator={brickRoadIndicator}
147-
/>
148-
</View>
149-
</>
150-
);
151-
}}
152-
</OptionRowPressable>
153-
);
154-
155-
return (
156-
<OptionRowTooltipLayer
157-
optionItem={optionItem}
158-
renderChildren={renderPressableRow}
159-
/>
155+
</>
156+
);
157+
}}
158+
</OptionRowPressable>
159+
</ProductTrainingTooltip>
160+
</OfflineWrapper>
160161
);
161162
}
162163

src/components/LHNOptionsList/OptionRowLHN/useLHNRowProductTrainingTooltip.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import CONST from '@src/CONST';
66

77
/**
88
* Resolves the product-training tooltip state (CONCIERGE_LHN_GBR) for an LHN row.
9-
* Used by both OptionRowTooltipLayerInner (render the tooltip) and OptionRowPressable (hide on press).
9+
* Used by both ProductTrainingTooltipInner (render the tooltip) and OptionRowPressable (hide on press).
1010
*/
1111
function useLHNRowProductTrainingTooltip() {
1212
const {shouldUseNarrowLayout} = useResponsiveLayout();

0 commit comments

Comments
 (0)