Skip to content

Commit 71421d4

Browse files
refactor: port transaction item row changes to split components
1 parent c11065f commit 71421d4

6 files changed

Lines changed: 222 additions & 23 deletions

File tree

src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ function TransactionListItemNarrow<TItem extends ListItem>({
3838
exportedReportActions,
3939
nonPersonalAndWorkspaceCards,
4040
isAttendeesEnabledForMovingPolicy,
41+
shouldDisableHoverStyle,
42+
onPressRow,
43+
onMouseDownRow,
44+
onHoverInRow,
45+
onEditDate,
46+
onEditMerchant,
47+
onEditDescription,
48+
onEditCategory,
49+
onEditAmount,
50+
onEditTag,
51+
canEditDate,
52+
canEditMerchant,
53+
canEditDescription,
54+
canEditCategory,
55+
canEditAmount,
56+
canEditTag,
4157
}: TransactionListItemNarrowProps<TItem>) {
4258
const styles = useThemeStyles();
4359
const theme = useTheme();
@@ -66,13 +82,14 @@ function TransactionListItemNarrow<TItem extends ListItem>({
6682
<PressableWithFeedback
6783
ref={pressableRef}
6884
onLongPress={() => onLongPressRow?.(item)}
69-
onPress={isDeletedTransaction && !canSelectMultiple ? undefined : () => onSelectRow(item, transactionPreviewData)}
85+
onPress={onPressRow}
7086
disabled={isDisabled && !item.isSelected}
7187
accessibilityLabel={item.text ?? ''}
7288
role={!isDeletedTransaction ? getButtonRole(true) : 'none'}
7389
isNested
74-
onMouseDown={(e) => e.preventDefault()}
75-
hoverStyle={[!item.isDisabled && styles.hoveredComponentBG, item.isSelected && styles.activeComponentBG]}
90+
onMouseDown={onMouseDownRow}
91+
onHoverIn={onHoverInRow}
92+
hoverStyle={[!item.isDisabled && !shouldDisableHoverStyle && styles.hoveredComponentBG, item.isSelected && styles.activeComponentBG]}
7693
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true, [CONST.INNER_BOX_SHADOW_ELEMENT]: false}}
7794
id={item.keyForList ?? ''}
7895
sentryLabel={CONST.SENTRY_LABEL.SEARCH.TRANSACTION_LIST_ITEM}
@@ -126,6 +143,18 @@ function TransactionListItemNarrow<TItem extends ListItem>({
126143
nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards}
127144
reportActions={exportedReportActions}
128145
isAttendeesEnabledForMovingPolicy={isAttendeesEnabledForMovingPolicy}
146+
onEditDate={onEditDate}
147+
onEditMerchant={onEditMerchant}
148+
onEditDescription={onEditDescription}
149+
onEditCategory={onEditCategory}
150+
onEditAmount={onEditAmount}
151+
onEditTag={onEditTag}
152+
canEditDate={canEditDate}
153+
canEditMerchant={canEditMerchant}
154+
canEditDescription={canEditDescription}
155+
canEditCategory={canEditCategory}
156+
canEditAmount={canEditAmount}
157+
canEditTag={canEditTag}
129158
/>
130159
</>
131160
)}

src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ function TransactionListItemWide<TItem extends ListItem>({
3636
exportedReportActions,
3737
nonPersonalAndWorkspaceCards,
3838
isAttendeesEnabledForMovingPolicy,
39+
shouldDisableHoverStyle,
40+
onPressRow,
41+
onMouseDownRow,
42+
onHoverInRow,
43+
onEditDate,
44+
onEditMerchant,
45+
onEditDescription,
46+
onEditCategory,
47+
onEditAmount,
48+
onEditTag,
49+
canEditDate,
50+
canEditMerchant,
51+
canEditDescription,
52+
canEditCategory,
53+
canEditAmount,
54+
canEditTag,
3955
}: TransactionListItemWideProps<TItem>) {
4056
const styles = useThemeStyles();
4157
const theme = useTheme();
@@ -75,13 +91,14 @@ function TransactionListItemWide<TItem extends ListItem>({
7591
<PressableWithFeedback
7692
ref={pressableRef}
7793
onLongPress={() => onLongPressRow?.(item)}
78-
onPress={isDeletedTransaction && !canSelectMultiple ? undefined : () => onSelectRow(item, transactionPreviewData)}
94+
onPress={onPressRow}
7995
disabled={isDisabled && !item.isSelected}
8096
accessibilityLabel={item.text ?? ''}
8197
role={!isDeletedTransaction ? getButtonRole(true) : 'none'}
8298
isNested
83-
onMouseDown={(e) => e.preventDefault()}
84-
hoverStyle={[!item.isDisabled && styles.hoveredComponentBG, item.isSelected && styles.activeComponentBG]}
99+
onMouseDown={onMouseDownRow}
100+
onHoverIn={onHoverInRow}
101+
hoverStyle={[!item.isDisabled && !shouldDisableHoverStyle && styles.hoveredComponentBG, item.isSelected && styles.activeComponentBG]}
85102
dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true, [CONST.INNER_BOX_SHADOW_ELEMENT]: false}}
86103
id={item.keyForList ?? ''}
87104
sentryLabel={CONST.SENTRY_LABEL.SEARCH.TRANSACTION_LIST_ITEM}
@@ -124,6 +141,18 @@ function TransactionListItemWide<TItem extends ListItem>({
124141
nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards}
125142
reportActions={exportedReportActions}
126143
isAttendeesEnabledForMovingPolicy={isAttendeesEnabledForMovingPolicy}
144+
onEditDate={onEditDate}
145+
onEditMerchant={onEditMerchant}
146+
onEditDescription={onEditDescription}
147+
onEditCategory={onEditCategory}
148+
onEditAmount={onEditAmount}
149+
onEditTag={onEditTag}
150+
canEditDate={canEditDate}
151+
canEditMerchant={canEditMerchant}
152+
canEditDescription={canEditDescription}
153+
canEditCategory={canEditCategory}
154+
canEditAmount={canEditAmount}
155+
canEditTag={canEditTag}
127156
/>
128157
)}
129158
</PressableWithFeedback>

src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SearchStaticList (src/components/Search/SearchStaticList.tsx) used for fast
33
// perceived performance. If you change the narrow-layout UI here, verify the
44
// static version still looks visually identical.
5-
import React from 'react';
5+
import React, {useEffect, useState} from 'react';
66
import type {OnyxEntry} from 'react-native-onyx';
77
// Use the original useOnyx hook to get the real-time data from Onyx and not from the snapshot
88
// eslint-disable-next-line no-restricted-imports
@@ -11,9 +11,11 @@ import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/
1111
import {useSearchStateContext} from '@components/Search/SearchContext';
1212
import type {TransactionListItemProps, TransactionListItemType} from '@components/Search/SearchList/ListItem/types';
1313
import type {ListItem} from '@components/SelectionList/types';
14+
import {useEditingCellState} from '@components/Table/EditableCell';
1415
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
1516
import useOnyx from '@hooks/useOnyx';
1617
import useResponsiveLayout from '@hooks/useResponsiveLayout';
18+
import useTransactionInlineEdit from '@hooks/useTransactionInlineEdit';
1719
import type {TransactionPreviewData} from '@libs/actions/Search';
1820
import {handleActionButtonPress as handleActionButtonPressUtil} from '@libs/actions/Search';
1921
import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
@@ -127,6 +129,66 @@ function TransactionListItem<TItem extends ListItem>({
127129

128130
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
129131
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
132+
const {isEditingCell, wasRecentlyEditingCell} = useEditingCellState();
133+
const [shouldDisableHoverStyle, setShouldDisableHoverStyle] = useState(false);
134+
135+
// When a popover is opened during inline editing, onHoverOut never fires after editing ends, leaving the hover style stuck.
136+
// Disable it until the next intentional hover (onHoverIn).
137+
// See: https://github.com/Expensify/App/pull/83127#issuecomment-4114490080
138+
useEffect(() => {
139+
if (!wasRecentlyEditingCell) {
140+
return;
141+
}
142+
queueMicrotask(() => setShouldDisableHoverStyle(true));
143+
}, [wasRecentlyEditingCell]);
144+
145+
const {
146+
canEditDate,
147+
canEditMerchant,
148+
canEditDescription,
149+
canEditCategory,
150+
canEditAmount,
151+
canEditTag,
152+
onEditDate,
153+
onEditMerchant,
154+
onEditDescription,
155+
onEditCategory,
156+
onEditAmount,
157+
onEditTag,
158+
wasEditingOnMouseDownRef,
159+
} = useTransactionInlineEdit({
160+
transactionID: transactionItem.transactionID,
161+
hash: currentSearchHash,
162+
linkedReportAction: transactionItem.reportAction,
163+
});
164+
165+
const handleOnPress = () => {
166+
// Consume the tap that dismissed an editing cell — a second tap will open the row.
167+
// We check the ref rather than isEditingCell because blur fires before onPress and resets the state.
168+
if (wasEditingOnMouseDownRef.current) {
169+
wasEditingOnMouseDownRef.current = false;
170+
return;
171+
}
172+
// react-native-web fires onPress on Space for role="button" elements; suppress it while a cell is being edited.
173+
if (isEditingCell) {
174+
return;
175+
}
176+
if (isDeletedTransaction && !canSelectMultiple) {
177+
return;
178+
}
179+
onSelectRow(item, transactionPreviewData);
180+
};
181+
182+
const handleOnMouseDown = (e?: React.MouseEvent) => {
183+
wasEditingOnMouseDownRef.current = isEditingCell;
184+
185+
// Skip preventDefault when editing so the browser naturally blurs the input (triggering save/cancel).
186+
if (!isEditingCell) {
187+
e?.preventDefault();
188+
}
189+
};
190+
191+
const handleOnHoverIn = () => setShouldDisableHoverStyle(false);
130192

131193
const handleActionButtonPress = () => {
132194
handleActionButtonPressUtil({
@@ -170,6 +232,22 @@ function TransactionListItem<TItem extends ListItem>({
170232
exportedReportActions,
171233
nonPersonalAndWorkspaceCards,
172234
isAttendeesEnabledForMovingPolicy,
235+
shouldDisableHoverStyle,
236+
onPressRow: handleOnPress,
237+
onMouseDownRow: handleOnMouseDown,
238+
onHoverInRow: handleOnHoverIn,
239+
onEditDate,
240+
onEditMerchant,
241+
onEditDescription,
242+
onEditCategory,
243+
onEditAmount,
244+
onEditTag,
245+
canEditDate,
246+
canEditMerchant,
247+
canEditDescription,
248+
canEditCategory,
249+
canEditAmount,
250+
canEditTag,
173251
};
174252

175253
if (!isLargeScreenWidth) {

src/components/Search/SearchList/ListItem/TransactionListItem/types.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,30 @@
1+
import type {MouseEvent} from 'react';
12
import type {TransactionListItemType} from '@components/Search/SearchList/ListItem/types';
23
import type {SearchColumnType} from '@components/Search/types';
34
import type {ListItemFocusEventHandler} from '@components/SelectionList/ListItem/types';
45
import type {ListItem} from '@components/SelectionList/types';
56
import type {TransactionPreviewData} from '@libs/actions/Search';
67
import type {CardList, ReportAction, TransactionViolation} from '@src/types/onyx';
78

9+
type TransactionListItemInlineEditProps = {
10+
shouldDisableHoverStyle: boolean;
11+
onPressRow: () => void;
12+
onMouseDownRow: (e?: MouseEvent) => void;
13+
onHoverInRow: () => void;
14+
onEditDate: (newDate: string) => void;
15+
onEditMerchant: (newMerchant: string) => void;
16+
onEditDescription: (newDescription: string) => void;
17+
onEditCategory: (newCategory: string) => void;
18+
onEditAmount: (newAmount: number) => void;
19+
onEditTag: (newTag: string) => void;
20+
canEditDate: boolean;
21+
canEditMerchant: boolean;
22+
canEditDescription: boolean;
23+
canEditCategory: boolean;
24+
canEditAmount: boolean;
25+
canEditTag: boolean;
26+
};
27+
828
type TransactionListItemWideProps<TItem extends ListItem> = {
929
item: TItem;
1030
transactionItem: TransactionListItemType;
@@ -28,7 +48,7 @@ type TransactionListItemWideProps<TItem extends ListItem> = {
2848
exportedReportActions: ReportAction[];
2949
nonPersonalAndWorkspaceCards?: CardList;
3050
isAttendeesEnabledForMovingPolicy?: boolean;
31-
};
51+
} & TransactionListItemInlineEditProps;
3252

3353
type TransactionListItemNarrowProps<TItem extends ListItem> = TransactionListItemWideProps<TItem> & {
3454
isFirstItem?: boolean;

0 commit comments

Comments
 (0)