From 71f69fb3b8c49eaf46061dcb8b616cde8f4dad78 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Sat, 20 Dec 2025 16:33:28 -0800 Subject: [PATCH 1/3] refactoring: isPercentageMode to generic prop --- .../BaseSelectionListWithSections.tsx | 6 +++--- src/components/SelectionListWithSections/index.tsx | 5 ++--- src/components/SelectionListWithSections/types.ts | 4 ++-- src/pages/iou/SplitPercentageList.tsx | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx b/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx index 356eaf5e68a3b..1bccd9a5d0e1f 100644 --- a/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx +++ b/src/components/SelectionListWithSections/BaseSelectionListWithSections.tsx @@ -145,7 +145,7 @@ function BaseSelectionListWithSections({ shouldHighlightSelectedItem = true, shouldDisableHoverStyle = false, setShouldDisableHoverStyle = () => {}, - isPercentageMode, + shouldSkipContentHeaderHeightOffset, ref, }: SelectionListProps) { const styles = useThemeStyles(); @@ -357,8 +357,8 @@ function BaseSelectionListWithSections({ } let viewOffset = variables.contentHeaderHeight - viewOffsetToKeepFocusedItemAtTopOfViewableArea; - // Remove contentHeaderHeight from viewOffset calculation if isPercentageMode (for scroll offset calculation on native) - if (isPercentageMode) { + // Skip contentHeaderHeight for native split expense tabs scroll correction + if (shouldSkipContentHeaderHeightOffset) { viewOffset = viewOffsetToKeepFocusedItemAtTopOfViewableArea; } listRef.current.scrollToLocation({sectionIndex, itemIndex, animated, viewOffset}); diff --git a/src/components/SelectionListWithSections/index.tsx b/src/components/SelectionListWithSections/index.tsx index 5c2cbe9346df8..f1374774545cb 100644 --- a/src/components/SelectionListWithSections/index.tsx +++ b/src/components/SelectionListWithSections/index.tsx @@ -107,9 +107,8 @@ function SelectionListWithSections({onScroll, shouldHide isRowMultilineSupported shouldDisableHoverStyle={shouldDisableHoverStyle} setShouldDisableHoverStyle={setShouldDisableHoverStyle} - // We only allow the prop to pass on Native (for scroll offset calculation) - // web should be false by default since there are no issues on web - isPercentageMode={false} + // Web doesn't require scroll offset correction, only native does + shouldSkipContentHeaderHeightOffset={false} /> ); } diff --git a/src/components/SelectionListWithSections/types.ts b/src/components/SelectionListWithSections/types.ts index 9ad24ea2a7da7..2112c85045207 100644 --- a/src/components/SelectionListWithSections/types.ts +++ b/src/components/SelectionListWithSections/types.ts @@ -1089,8 +1089,8 @@ type SelectionListProps = Partial & { shouldDisableHoverStyle?: boolean; setShouldDisableHoverStyle?: React.Dispatch>; - /** Whether the list is percentage mode (for scroll offset calculation) */ - isPercentageMode?: boolean; + /** When true, skips the contentHeaderHeight from the viewOffset calculation during scroll-to-index. Only needed on native platforms for split expense tabs (Amount/Percentage/Date) scroll correction. Web should always pass false. */ + shouldSkipContentHeaderHeightOffset?: boolean; } & TRightHandSideComponent; type SelectionListHandle = { diff --git a/src/pages/iou/SplitPercentageList.tsx b/src/pages/iou/SplitPercentageList.tsx index af796b288ed03..9a3281b2636b5 100644 --- a/src/pages/iou/SplitPercentageList.tsx +++ b/src/pages/iou/SplitPercentageList.tsx @@ -59,7 +59,7 @@ function SplitPercentageList({sections, initiallyFocusedOptionKey, onSelectRow, shouldPreventDefaultFocusOnSelectRow removeClippedSubviews={false} shouldHideListOnInitialRender={false} - isPercentageMode + shouldSkipContentHeaderHeightOffset /> ); } From 517eb0294cbddfe296cdba7f99b29e213cfa7224 Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Sat, 20 Dec 2025 16:36:19 -0800 Subject: [PATCH 2/3] refactoring: remove displayName references --- .../SplitExpense/SplitAmountDisplay.tsx | 2 -- .../SelectionListWithSections/SplitExpense/SplitAmountInput.tsx | 2 -- .../SplitExpense/SplitPercentageDisplay.tsx | 2 -- .../SplitExpense/SplitPercentageInput.tsx | 2 -- src/components/SelectionListWithSections/SplitListItemInput.tsx | 2 -- 5 files changed, 10 deletions(-) diff --git a/src/components/SelectionListWithSections/SplitExpense/SplitAmountDisplay.tsx b/src/components/SelectionListWithSections/SplitExpense/SplitAmountDisplay.tsx index 1035934ab507f..c781fd39effce 100644 --- a/src/components/SelectionListWithSections/SplitExpense/SplitAmountDisplay.tsx +++ b/src/components/SelectionListWithSections/SplitExpense/SplitAmountDisplay.tsx @@ -42,6 +42,4 @@ function SplitAmountDisplay({splitItem, contentWidth = '100%', shouldRemoveSpaci ); } -SplitAmountDisplay.displayName = 'SplitAmountDisplay'; - export default SplitAmountDisplay; diff --git a/src/components/SelectionListWithSections/SplitExpense/SplitAmountInput.tsx b/src/components/SelectionListWithSections/SplitExpense/SplitAmountInput.tsx index 73a9ef73308ee..79a322e41ecba 100644 --- a/src/components/SelectionListWithSections/SplitExpense/SplitAmountInput.tsx +++ b/src/components/SelectionListWithSections/SplitExpense/SplitAmountInput.tsx @@ -65,6 +65,4 @@ function SplitAmountInput({splitItem, formattedOriginalAmount, contentWidth, onS ); } -SplitAmountInput.displayName = 'SplitAmountInput'; - export default SplitAmountInput; diff --git a/src/components/SelectionListWithSections/SplitExpense/SplitPercentageDisplay.tsx b/src/components/SelectionListWithSections/SplitExpense/SplitPercentageDisplay.tsx index cbd05b5ba34b2..532296ecbc5d3 100644 --- a/src/components/SelectionListWithSections/SplitExpense/SplitPercentageDisplay.tsx +++ b/src/components/SelectionListWithSections/SplitExpense/SplitPercentageDisplay.tsx @@ -27,6 +27,4 @@ function SplitPercentageDisplay({splitItem, contentWidth}: SplitPercentageDispla ); } -SplitPercentageDisplay.displayName = 'SplitPercentageDisplay'; - export default SplitPercentageDisplay; diff --git a/src/components/SelectionListWithSections/SplitExpense/SplitPercentageInput.tsx b/src/components/SelectionListWithSections/SplitExpense/SplitPercentageInput.tsx index 397e34337c17a..6f2ac3efebcaf 100644 --- a/src/components/SelectionListWithSections/SplitExpense/SplitPercentageInput.tsx +++ b/src/components/SelectionListWithSections/SplitExpense/SplitPercentageInput.tsx @@ -60,6 +60,4 @@ function SplitPercentageInput({splitItem, contentWidth, percentageDraft, onSplit ); } -SplitPercentageInput.displayName = 'SplitPercentageInput'; - export default SplitPercentageInput; diff --git a/src/components/SelectionListWithSections/SplitListItemInput.tsx b/src/components/SelectionListWithSections/SplitListItemInput.tsx index edeb303f2f2a6..5eebfe960ca73 100644 --- a/src/components/SelectionListWithSections/SplitListItemInput.tsx +++ b/src/components/SelectionListWithSections/SplitListItemInput.tsx @@ -66,6 +66,4 @@ function SplitListItemInput({ ); } -SplitListItemInput.displayName = 'SplitListItemInput'; - export default SplitListItemInput; From 1f4746342d910b9e5b2af464c57c22443c148b6a Mon Sep 17 00:00:00 2001 From: Kevin Brian Bader Date: Sat, 20 Dec 2025 16:41:54 -0800 Subject: [PATCH 3/3] refactoring: merge SplitAmountList and SplitPercentageList --- src/pages/iou/SplitExpensePage.tsx | 15 ++-- .../{SplitAmountList.tsx => SplitList.tsx} | 27 +++++--- src/pages/iou/SplitPercentageList.tsx | 69 ------------------- 3 files changed, 25 insertions(+), 86 deletions(-) rename src/pages/iou/{SplitAmountList.tsx => SplitList.tsx} (74%) delete mode 100644 src/pages/iou/SplitPercentageList.tsx diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 16014b39df8bf..1302493d36cbc 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -54,8 +54,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import SplitAmountList from './SplitAmountList'; -import SplitPercentageList from './SplitPercentageList'; +import SplitList from './SplitList'; type SplitExpensePageProps = PlatformStackScreenProps; @@ -481,11 +480,12 @@ function SplitExpensePage({route}: SplitExpensePageProps) { {() => ( - {footerContent} @@ -496,11 +496,12 @@ function SplitExpensePage({route}: SplitExpensePageProps) { {() => ( - {footerContent} @@ -512,11 +513,12 @@ function SplitExpensePage({route}: SplitExpensePageProps) { {headerDateContent} - } + mode={CONST.TAB.SPLIT.DATE} /> {footerContent} @@ -527,11 +529,12 @@ function SplitExpensePage({route}: SplitExpensePageProps) { ) : ( - {footerContent} diff --git a/src/pages/iou/SplitAmountList.tsx b/src/pages/iou/SplitList.tsx similarity index 74% rename from src/pages/iou/SplitAmountList.tsx rename to src/pages/iou/SplitList.tsx index d8ccea36f5713..6c3393b6f7fa1 100644 --- a/src/pages/iou/SplitAmountList.tsx +++ b/src/pages/iou/SplitList.tsx @@ -1,5 +1,6 @@ import React, {useMemo} from 'react'; import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'; +import type {ValueOf} from 'type-fest'; // eslint-disable-next-line no-restricted-imports import SelectionList from '@components/SelectionListWithSections'; import type {SectionListDataType, SplitListItemType} from '@components/SelectionListWithSections/types'; @@ -7,7 +8,7 @@ import useDisplayFocusedInputUnderKeyboard from '@hooks/useDisplayFocusedInputUn import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -type SplitAmountListProps = { +type SplitListProps = { /** The split expense sections data. */ sections: Array>; /** The initially focused option key. */ @@ -16,25 +17,30 @@ type SplitAmountListProps = { onSelectRow: (item: SplitListItemType) => void; /** Footer content to render at the bottom of the list. */ listFooterContent?: React.JSX.Element | null; + /** The split mode to use (amount, percentage, or date). */ + mode: ValueOf; }; /** - * Dedicated component for the Amount tab in Split Expense flow. - * Renders split items with amount inputs, managing its own scroll/height state. + * Unified component for split expense tabs (Amount, Percentage, Date). + * Renders split items with the appropriate input type based on mode, + * managing its own scroll/height state. */ -function SplitAmountList({sections, initiallyFocusedOptionKey, onSelectRow, listFooterContent}: SplitAmountListProps) { +function SplitList({sections, initiallyFocusedOptionKey, onSelectRow, listFooterContent, mode}: SplitListProps) { const styles = useThemeStyles(); const {listRef, bottomOffset, scrollToFocusedInput, SplitListItem} = useDisplayFocusedInputUnderKeyboard(); - const amountSections = useMemo(() => { + const splitSections = useMemo(() => { return sections.map((section) => ({ ...section, data: section.data.map((item) => ({ ...item, - mode: CONST.TAB.SPLIT.AMOUNT, + mode, })), })); - }, [sections]); + }, [sections, mode]); + + const isPercentageMode = mode === CONST.TAB.SPLIT.PERCENTAGE; return ( ); } -SplitAmountList.displayName = 'SplitAmountList'; - -export default SplitAmountList; +export default SplitList; diff --git a/src/pages/iou/SplitPercentageList.tsx b/src/pages/iou/SplitPercentageList.tsx deleted file mode 100644 index 9a3281b2636b5..0000000000000 --- a/src/pages/iou/SplitPercentageList.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, {useMemo} from 'react'; -import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'; -// eslint-disable-next-line no-restricted-imports -import SelectionList from '@components/SelectionListWithSections'; -import type {SectionListDataType, SplitListItemType} from '@components/SelectionListWithSections/types'; -import useDisplayFocusedInputUnderKeyboard from '@hooks/useDisplayFocusedInputUnderKeyboard'; -import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; - -type SplitPercentageListProps = { - /** The split expense sections data. */ - sections: Array>; - /** The initially focused option key. */ - initiallyFocusedOptionKey: string | undefined; - /** Callback when a row is selected. */ - onSelectRow: (item: SplitListItemType) => void; - /** Footer content to render at the bottom of the list. */ - listFooterContent?: React.JSX.Element | null; -}; - -/** - * Dedicated component for the Percentage tab in Split Expense flow. - * Renders split items with percentage inputs, managing its own scroll/height state. - */ -function SplitPercentageList({sections, initiallyFocusedOptionKey, onSelectRow, listFooterContent}: SplitPercentageListProps) { - const styles = useThemeStyles(); - const {listRef, bottomOffset, scrollToFocusedInput, SplitListItem} = useDisplayFocusedInputUnderKeyboard(); - - const percentageSections = useMemo(() => { - return sections.map((section) => ({ - ...section, - data: section.data.map((item) => ({ - ...item, - mode: CONST.TAB.SPLIT.PERCENTAGE, - })), - })); - }, [sections]); - - return ( - ( - scrollToFocusedInput()} - /> - )} - onSelectRow={onSelectRow} - ref={listRef} - sections={percentageSections} - initiallyFocusedOptionKey={initiallyFocusedOptionKey} - ListItem={SplitListItem} - containerStyle={[styles.flexBasisAuto]} - listFooterContent={listFooterContent} - disableKeyboardShortcuts - shouldSingleExecuteRowSelect - canSelectMultiple={false} - shouldPreventDefaultFocusOnSelectRow - removeClippedSubviews={false} - shouldHideListOnInitialRender={false} - shouldSkipContentHeaderHeightOffset - /> - ); -} - -SplitPercentageList.displayName = 'SplitPercentageList'; - -export default SplitPercentageList;