diff --git a/src/components/Icon/BankIcons/index.native.ts b/src/components/Icon/BankIcons/index.native.ts index 5ad3b0220186..b338a47ae83c 100644 --- a/src/components/Icon/BankIcons/index.native.ts +++ b/src/components/Icon/BankIcons/index.native.ts @@ -9,7 +9,7 @@ import type {BankIcon} from '@src/types/onyx/Bank'; /** * Returns Bank Icon Object that matches to existing bank icons or default icons */ -export default function getBankIcon({styles, bankName, isCard = false}: BankIconParams): BankIcon { +export default function getBankIcon({styles, bankName, isCard = false, maxIconSize}: BankIconParams): BankIcon { const bankIcon: BankIcon = { icon: isCard ? GenericBankCard : GenericBank, }; @@ -23,11 +23,13 @@ export default function getBankIcon({styles, bankName, isCard = false}: BankIcon // For default Credit Card icon the icon size should not be set. if (!isCard) { - bankIcon.iconSize = variables.iconSizeExtraLarge; - bankIcon.iconStyles = [styles.bankIconContainer]; + const defaultSize = variables.iconSizeExtraLarge; + bankIcon.iconSize = maxIconSize ? Math.min(defaultSize, maxIconSize) : defaultSize; + bankIcon.iconStyles = maxIconSize && maxIconSize < defaultSize ? [{...styles.bankIconContainer, width: bankIcon.iconSize, height: bankIcon.iconSize}] : [styles.bankIconContainer]; } else { - bankIcon.iconHeight = variables.cardIconHeight; - bankIcon.iconWidth = variables.cardIconWidth; + const scale = maxIconSize ? Math.min(1, maxIconSize / variables.cardIconWidth) : 1; + bankIcon.iconHeight = Math.round(variables.cardIconHeight * scale); + bankIcon.iconWidth = Math.round(variables.cardIconWidth * scale); bankIcon.iconStyles = [styles.cardIcon]; } diff --git a/src/components/Icon/BankIcons/index.ts b/src/components/Icon/BankIcons/index.ts index 9e06b7696bfa..ae8bb35c5637 100644 --- a/src/components/Icon/BankIcons/index.ts +++ b/src/components/Icon/BankIcons/index.ts @@ -17,7 +17,7 @@ type BankIconAsset = { /** * Returns Bank Icon Object that matches to existing bank icons or default icons */ -export default function getBankIcon({styles, bankName, isCard = false}: BankIconParams): BankIcon { +export default function getBankIcon({styles, bankName, isCard = false, maxIconSize}: BankIconParams): BankIcon { const bankIcon: BankIcon = { icon: isCard ? GenericBankCard : GenericBank, }; @@ -31,11 +31,15 @@ export default function getBankIcon({styles, bankName, isCard = false}: BankIcon // For default Credit Card icon the icon size should not be set. if (!isCard) { - bankIcon.iconSize = variables.iconSizeExtraLarge; - bankIcon.iconStyles = [styles.bankIconContainer]; + const defaultSize: number = variables.iconSizeExtraLarge; + bankIcon.iconSize = maxIconSize ? Math.min(defaultSize, maxIconSize) : defaultSize; + bankIcon.iconStyles = maxIconSize && maxIconSize < defaultSize ? [{...styles.bankIconContainer, width: bankIcon.iconSize, height: bankIcon.iconSize}] : [styles.bankIconContainer]; } else { - bankIcon.iconHeight = variables.cardIconHeight; - bankIcon.iconWidth = variables.cardIconWidth; + const cardWidth: number = variables.cardIconWidth; + const cardHeight: number = variables.cardIconHeight; + const scale = maxIconSize ? Math.min(1, maxIconSize / cardWidth) : 1; + bankIcon.iconHeight = Math.round(cardHeight * scale); + bankIcon.iconWidth = Math.round(cardWidth * scale); bankIcon.iconStyles = [styles.cardIcon]; } diff --git a/src/components/Icon/BankIconsUtils.ts b/src/components/Icon/BankIconsUtils.ts index c12397080a48..483d363ed3ac 100644 --- a/src/components/Icon/BankIconsUtils.ts +++ b/src/components/Icon/BankIconsUtils.ts @@ -7,6 +7,7 @@ type BankIconParams = { styles: ThemeStyles; bankName?: BankName; isCard?: boolean; + maxIconSize?: number; }; /** diff --git a/src/components/ReportActionAvatars/SearchReportAvatar.tsx b/src/components/ReportActionAvatars/SearchReportAvatar.tsx index 89a89d8394aa..9c1126ae2182 100644 --- a/src/components/ReportActionAvatars/SearchReportAvatar.tsx +++ b/src/components/ReportActionAvatars/SearchReportAvatar.tsx @@ -12,9 +12,12 @@ type SearchReportAvatarProps = { shouldShowTooltip: boolean; subscriptAvatarBorderColor: ColorValue; reportID: string; + isLargeScreenWidth?: boolean; }; -function SearchReportAvatar({primaryAvatar, secondaryAvatar, avatarType, shouldShowTooltip, subscriptAvatarBorderColor, reportID}: SearchReportAvatarProps) { +function SearchReportAvatar({primaryAvatar, secondaryAvatar, avatarType, shouldShowTooltip, subscriptAvatarBorderColor, reportID, isLargeScreenWidth}: SearchReportAvatarProps) { + const avatarSize = isLargeScreenWidth ? CONST.AVATAR_SIZE.SMALL : CONST.AVATAR_SIZE.DEFAULT; + if (!primaryAvatar) { return null; } @@ -24,7 +27,7 @@ function SearchReportAvatar({primaryAvatar, secondaryAvatar, avatarType, shouldS ({ return ( - + {!!canSelectMultiple && ( ({ isIndeterminate={isIndeterminate} disabled={!!isDisabled || item.isDisabledCheckbox} accessibilityLabel={translate('common.select')} - style={isLargeScreenWidth && styles.mr1} + containerStyle={styles.m0} sentryLabel={CONST.SENTRY_LABEL.SEARCH.GROUP_SELECT_ALL_CHECKBOX} /> )} diff --git a/src/components/Search/SearchList/ListItem/CardListItem.tsx b/src/components/Search/SearchList/ListItem/CardListItem.tsx index 112eb6823c2a..c908a8e30856 100644 --- a/src/components/Search/SearchList/ListItem/CardListItem.tsx +++ b/src/components/Search/SearchList/ListItem/CardListItem.tsx @@ -174,6 +174,7 @@ function CardListItem({ onPress={handleCheckboxPress} disabled={!!isDisabled} style={styles.ml3} + containerStyle={styles.m0} /> )} diff --git a/src/components/Search/SearchList/ListItem/CardListItemHeader.tsx b/src/components/Search/SearchList/ListItem/CardListItemHeader.tsx index e933fcfcb310..4e78b1e136b7 100644 --- a/src/components/Search/SearchList/ListItem/CardListItemHeader.tsx +++ b/src/components/Search/SearchList/ListItem/CardListItemHeader.tsx @@ -122,7 +122,7 @@ function CardListItemHeader({ return ( - + {!!canSelectMultiple && ( ({ isIndeterminate={isIndeterminate} disabled={!!isDisabled || cardItem.isDisabledCheckbox} accessibilityLabel={translate('common.select')} - style={isLargeScreenWidth && styles.mr1} + containerStyle={styles.m0} /> )} {!isLargeScreenWidth && ( @@ -165,6 +165,7 @@ function CardListItemHeader({ subscriptAvatarBorderColor={backgroundColor} noRightMarginOnSubscriptContainer accountIDs={[cardItem.accountID]} + size={CONST.AVATAR_SIZE.SMALL} /> diff --git a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx index f388be4f7da9..a4f7fee2f37b 100644 --- a/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx +++ b/src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx @@ -17,6 +17,7 @@ import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {handleActionButtonPress} from '@libs/actions/Search'; @@ -46,9 +47,11 @@ function ExpenseReportListItem({ onCheckboxPress, lastPaymentMethod, personalPolicyID, + isLastItem, }: ExpenseReportListItemProps) { const reportItem = item as unknown as ExpenseReportListItemType; const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const theme = useTheme(); const {translate} = useLocalize(); const {isLargeScreenWidth} = useResponsiveLayout(); @@ -170,8 +173,9 @@ function ExpenseReportListItem({ item.isSelected && styles.activeComponentBG, styles.mh0, isPendingDelete && styles.cursorDisabled, + isLargeScreenWidth && StyleUtils.getSearchTableRowPressableStyle(!!isLastItem, item.isSelected, {vertical: variables.tableRowPaddingVertical}), ], - [styles, item.isSelected, isPendingDelete], + [styles, item.isSelected, isLargeScreenWidth, isLastItem, isPendingDelete, StyleUtils], ); const listItemWrapperStyle = useMemo( @@ -184,7 +188,7 @@ function ExpenseReportListItem({ ); const animatedHighlightStyle = useAnimatedHighlightStyle({ - borderRadius: variables.componentBorderRadius, + borderRadius: StyleUtils.getSearchTableHighlightBorderRadius(isLargeScreenWidth), shouldHighlight: item?.shouldAnimateInHighlight ?? false, highlightColor: theme.messageHighlightBG, backgroundColor: theme.highlightBG, @@ -249,7 +253,7 @@ function ExpenseReportListItem({ item={item} pressableStyle={listItemPressableStyle} wrapperStyle={listItemWrapperStyle} - containerStyle={[styles.mb2]} + containerStyle={!isLargeScreenWidth && [styles.mb2]} isFocused={isFocused} showTooltip={showTooltip} canSelectMultiple={canSelectMultiple} @@ -260,7 +264,7 @@ function ExpenseReportListItem({ onLongPressRow={onLongPressRow} shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} - pressableWrapperStyle={[styles.mh5, animatedHighlightStyle, isPendingDelete && styles.cursorDisabled]} + pressableWrapperStyle={[styles.mh5, animatedHighlightStyle, isPendingDelete && styles.cursorDisabled, isLargeScreenWidth && isLastItem && styles.searchTableBottomRadius]} accessible={false} shouldShowRightCaret={false} shouldUseDefaultRightHandSideCheckmark={false} @@ -284,6 +288,7 @@ function ExpenseReportListItem({ isHovered={hovered} isFocused={isFocused} isPendingDelete={isPendingDelete} + isLargeScreenWidth={isLargeScreenWidth} /> {getDescription} diff --git a/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow.tsx b/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow.tsx index 358b05028365..59cd51a4c7ce 100644 --- a/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow.tsx +++ b/src/components/Search/SearchList/ListItem/ExpenseReportListItemRow.tsx @@ -10,7 +10,6 @@ import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -49,6 +48,7 @@ type ExpenseReportListItemRowProps = { isFocused?: boolean; isPendingDelete?: boolean; columns?: SearchColumnType[]; + isLargeScreenWidth?: boolean; }; function ExpenseReportListItemRow({ @@ -67,13 +67,14 @@ function ExpenseReportListItemRow({ isHovered = false, isFocused = false, isPendingDelete = false, + isLargeScreenWidth = false, }: ExpenseReportListItemRowProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); - const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); + const shouldUseNarrowLayout = !isLargeScreenWidth; const policy = usePolicy(item.policyID); const expensifyIcons = useMemoizedLazyExpensifyIcons(['ArrowRight']); @@ -141,6 +142,7 @@ function ExpenseReportListItemRow({ accountID={item.from.accountID} avatar={item.from.avatar} displayName={item.formattedFrom ?? ''} + isLargeScreenWidth={isLargeScreenWidth} /> )} @@ -152,12 +154,17 @@ function ExpenseReportListItemRow({ accountID={item.to.accountID} avatar={item.to.avatar} displayName={item.formattedTo ?? ''} + isLargeScreenWidth={isLargeScreenWidth} /> )} ), [CONST.SEARCH.TABLE_COLUMNS.REIMBURSABLE_TOTAL]: ( - + ), [CONST.SEARCH.TABLE_COLUMNS.NON_REIMBURSABLE_TOTAL]: ( - + ), [CONST.SEARCH.TABLE_COLUMNS.TOTAL]: ( - + - + + {!!canSelectMultiple && ( )} @@ -346,6 +360,7 @@ function ExpenseReportListItemRow({ shouldShowTooltip={showTooltip} subscriptAvatarBorderColor={finalAvatarBorderColor} reportID={item.reportID} + isLargeScreenWidth={isLargeScreenWidth} /> @@ -354,15 +369,13 @@ function ExpenseReportListItemRow({ return {CellComponent}; })} - - - + ); } diff --git a/src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx b/src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx index 8dc066c9838d..bc616975ebb8 100644 --- a/src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx +++ b/src/components/Search/SearchList/ListItem/MemberListItemHeader.tsx @@ -7,7 +7,6 @@ import type {ListItem} from '@components/SelectionList/types'; import TextWithTooltip from '@components/TextWithTooltip'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; @@ -44,6 +43,9 @@ type MemberListItemHeaderProps = { /** The visible columns for the header */ columns?: SearchColumnType[]; + + /** Whether the screen is large */ + isLargeScreenWidth?: boolean; }; function MemberListItemHeader({ @@ -56,10 +58,10 @@ function MemberListItemHeader({ isExpanded, onDownArrowClick, columns, + isLargeScreenWidth, }: MemberListItemHeaderProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const {isLargeScreenWidth} = useResponsiveLayout(); const {translate, formatPhoneNumber} = useLocalize(); const formattedDisplayName = formatPhoneNumber(getDisplayNameOrDefault(memberItem)); const formattedLogin = formatPhoneNumber(memberItem.login ?? ''); @@ -70,7 +72,7 @@ function MemberListItemHeader({ key={CONST.SEARCH.TABLE_COLUMNS.GROUP_FROM} style={StyleUtils.getReportTableColumnStyles(CONST.SEARCH.TABLE_COLUMNS.FROM)} > - + ({ return ( - + {!!canSelectMultiple && ( ({ isIndeterminate={isIndeterminate} disabled={!!isDisabled || memberItem.isDisabledCheckbox} accessibilityLabel={translate('common.select')} - style={isLargeScreenWidth && styles.mr1} + containerStyle={styles.m0} /> )} {!isLargeScreenWidth && ( @@ -129,7 +131,7 @@ function MemberListItemHeader({ /> - + ({ )} - {isLargeScreenWidth && ( + {!!isLargeScreenWidth && ( <> @@ -151,6 +153,7 @@ function MemberListItemHeader({ type={CONST.ICON_TYPE_AVATAR} name={formattedDisplayName} avatarID={memberItem.accountID} + size={CONST.AVATAR_SIZE.SMALL} /> diff --git a/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx b/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx index 89103da7da81..bb0584a44a10 100644 --- a/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx +++ b/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx @@ -135,7 +135,7 @@ function HeaderFirstRow({ onPress={() => onCheckboxPress?.(reportItem as unknown as TItem)} isChecked={isSelectAllChecked} isIndeterminate={isIndeterminate} - containerStyle={[StyleUtils.getCheckboxContainerStyle(20), StyleUtils.getMultiselectListStyles(!!reportItem.isSelected, !!reportItem.isDisabled)]} + containerStyle={[StyleUtils.getCheckboxContainerStyle(20), StyleUtils.getMultiselectListStyles(!!reportItem.isSelected, !!reportItem.isDisabled), styles.m0]} disabled={!!isDisabled || reportItem.isDisabledCheckbox} accessibilityLabel={reportItem.text ?? ''} shouldStopMouseDownPropagation diff --git a/src/components/Search/SearchList/ListItem/TaskListItem.tsx b/src/components/Search/SearchList/ListItem/TaskListItem.tsx index 5fd73e6ea1cc..e4b43812e733 100644 --- a/src/components/Search/SearchList/ListItem/TaskListItem.tsx +++ b/src/components/Search/SearchList/ListItem/TaskListItem.tsx @@ -3,6 +3,7 @@ import BaseListItem from '@components/SelectionList/ListItem/BaseListItem'; import type {ListItem} from '@components/SelectionList/types'; import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import FS from '@libs/Fullstory'; @@ -22,10 +23,12 @@ function TaskListItem({ onLongPressRow, shouldSyncFocus, allReports, + isLastItem, }: TaskListItemProps) { const taskItem = item as unknown as TaskListItemType; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${taskItem?.parentReportID}`]; const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const theme = useTheme(); const {isLargeScreenWidth} = useResponsiveLayout(); @@ -38,6 +41,7 @@ function TaskListItem({ styles.bgTransparent, item.isSelected && styles.activeComponentBG, styles.mh0, + isLargeScreenWidth && StyleUtils.getSearchTableRowPressableStyle(!!isLastItem, item.isSelected, {vertical: variables.tableRowPaddingVertical}), ]; const listItemWrapperStyle = [ @@ -47,7 +51,7 @@ function TaskListItem({ ]; const animatedHighlightStyle = useAnimatedHighlightStyle({ - borderRadius: variables.componentBorderRadius, + borderRadius: StyleUtils.getSearchTableHighlightBorderRadius(isLargeScreenWidth), shouldHighlight: item?.shouldAnimateInHighlight ?? false, highlightColor: theme.messageHighlightBG, backgroundColor: theme.highlightBG, @@ -60,7 +64,7 @@ function TaskListItem({ item={item} pressableStyle={listItemPressableStyle} wrapperStyle={listItemWrapperStyle} - containerStyle={[styles.mb2]} + containerStyle={!isLargeScreenWidth && [styles.mb2]} isFocused={isFocused} isDisabled={isDisabled} showTooltip={showTooltip} @@ -72,7 +76,7 @@ function TaskListItem({ onLongPressRow={onLongPressRow} shouldSyncFocus={shouldSyncFocus} hoverStyle={item.isSelected && styles.activeComponentBG} - pressableWrapperStyle={[styles.mh5, animatedHighlightStyle]} + pressableWrapperStyle={[styles.mh5, animatedHighlightStyle, isLargeScreenWidth && isLastItem && styles.searchTableBottomRadius]} forwardedFSClass={fsClass} > @@ -150,6 +151,7 @@ function TaskListItemRow({item, containerStyle, showTooltip}: TaskListItemRowPro @@ -229,12 +231,14 @@ function TaskListItemRow({item, containerStyle, showTooltip}: TaskListItemRowPro accountID={item.createdBy.accountID} avatar={item.createdBy.avatar} displayName={item.formattedCreatedBy} + isLargeScreenWidth={isLargeScreenWidth} /> @@ -242,6 +246,7 @@ function TaskListItemRow({item, containerStyle, showTooltip}: TaskListItemRowPro accountID={item.assignee.accountID} avatar={item.assignee.avatar} displayName={item.formattedAssignee} + isLargeScreenWidth={isLargeScreenWidth} /> diff --git a/src/components/Search/SearchList/ListItem/TextCell.tsx b/src/components/Search/SearchList/ListItem/TextCell.tsx index a0eca53db729..a1e9176ef8c8 100644 --- a/src/components/Search/SearchList/ListItem/TextCell.tsx +++ b/src/components/Search/SearchList/ListItem/TextCell.tsx @@ -14,8 +14,8 @@ function TextCell({text = '', isLargeScreenWidth = true}: TextCellProps) { ); } diff --git a/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx b/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx index 30771565a0a0..f1e9ff633a65 100644 --- a/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx @@ -191,26 +191,29 @@ function TransactionGroupListExpanded({ const content = ( - {isLargeScreenWidth && ( - - {}} - sortOrder={undefined} - sortBy={undefined} - shouldShowYear={dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE} - isAmountColumnWide={amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE} - isTaxAmountColumnWide={taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE} - shouldShowSorting={false} - columns={currentColumns} - groupBy={groupBy} - isExpenseReportView - /> - + {isLargeScreenWidth && !(isEmpty && shouldDisplayLoadingIndicator) && ( + <> + + {}} + sortOrder={undefined} + sortBy={undefined} + shouldShowYear={dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE} + isAmountColumnWide={amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE} + isTaxAmountColumnWide={taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE} + shouldShowSorting={false} + columns={currentColumns} + groupBy={groupBy} + isExpenseReportView + /> + + + )} {visibleTransactions.map((transaction, index) => { - const shouldShowBottomBorder = !isLastTransaction(index) && !isLargeScreenWidth; + const shouldShowBottomBorder = !isLastTransaction(index); const exportedReportActions = Object.values(transactionsSnapshot?.data?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`] ?? {}); const transactionRow = ( @@ -232,7 +235,7 @@ function TransactionGroupListExpanded({ onButtonPress={() => { openReportInRHP(transaction); }} - style={[styles.noBorderRadius, styles.p3, isLargeScreenWidth && [styles.pv1Half], styles.flex1]} + style={[styles.noBorderRadius, styles.p3, isLargeScreenWidth && [styles.pv2, styles.searchTableRowHeight], styles.flex1]} isReportItemChild isInSingleTransactionReport={isInSingleTransactionReport} shouldShowBottomBorder={shouldShowBottomBorder} diff --git a/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx b/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx index 4f0c54275053..e4bcc59f75c6 100644 --- a/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx @@ -77,6 +77,8 @@ function TransactionGroupListItem({ newTransactionID, lastPaymentMethod, personalPolicyID, + isFirstItem, + isLastItem, }: TransactionGroupListItemProps) { const groupItem = item as unknown as TransactionGroupListItemType; @@ -171,8 +173,10 @@ function TransactionGroupListItem({ }); }; + const StyleUtils = useStyleUtils(); + const animatedHighlightStyle = useAnimatedHighlightStyle({ - borderRadius: variables.componentBorderRadius, + borderRadius: StyleUtils.getSearchTableHighlightBorderRadius(isLargeScreenWidth), shouldHighlight: item?.shouldAnimateInHighlight ?? false, highlightColor: theme.messageHighlightBG, backgroundColor: theme.highlightBG, @@ -180,9 +184,16 @@ function TransactionGroupListItem({ const isItemSelected = isSelectAllChecked || item?.isSelected; - const pressableStyle = [styles.transactionGroupListItemStyle, isItemSelected && styles.activeComponentBG]; - - const StyleUtils = useStyleUtils(); + const pressableStyle = [ + styles.transactionGroupListItemStyle, + isLargeScreenWidth && { + ...styles.searchTableRowHeight, + borderRadius: 0, + paddingVertical: variables.tableGroupRowPaddingVertical, + ...(isLastItem ? styles.searchTableBottomRadius : {}), + }, + isItemSelected && styles.activeComponentBG, + ]; const pressableRef = useRef(null); useEffect(() => { @@ -249,6 +260,7 @@ function TransactionGroupListItem({ isIndeterminate={isIndeterminate} onDownArrowClick={onExpandIconPress} isExpanded={isExpanded} + isLargeScreenWidth={isLargeScreenWidth} /> ), [CONST.SEARCH.GROUP_BY.CARD]: ( @@ -464,7 +476,13 @@ function TransactionGroupListItem({ isFocused && StyleUtils.getItemBackgroundColorStyle(!!isItemSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG), ]} onFocus={onFocus} - wrapperStyle={[styles.mb2, styles.mh5, animatedHighlightStyle, styles.userSelectNone]} + wrapperStyle={[ + !isLargeScreenWidth && styles.mb2, + styles.mh5, + animatedHighlightStyle, + styles.userSelectNone, + isLargeScreenWidth && StyleUtils.getSearchTableGroupRowBorderStyle(isFirstItem, isLastItem, isItemSelected), + ]} > {({hovered}) => ( @@ -472,8 +490,9 @@ function TransactionGroupListItem({ isExpanded={isExpanded} header={getHeader(hovered)} onPress={onExpandIconPress} - expandButtonStyle={styles.pv4Half} + expandButtonStyle={isLargeScreenWidth ? styles.pv2 : styles.pv4Half} shouldShowToggleButton={isLargeScreenWidth} + borderBottomStyle={isLargeScreenWidth && styles.borderNone} sentryLabel={CONST.SENTRY_LABEL.SEARCH.GROUP_EXPAND_TOGGLE} > ({ customCardNames, lastPaymentMethod, personalPolicyID, + isLastItem, }: TransactionListItemProps) { const transactionItem = item as unknown as TransactionListItemType; const styles = useThemeStyles(); const theme = useTheme(); + const StyleUtils = useStyleUtils(); const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const {currentSearchHash, currentSearchKey, currentSearchResults} = useSearchStateContext(); @@ -105,11 +106,18 @@ function TransactionListItem({ styles.transactionListItemStyle, !isLargeScreenWidth && styles.pt3, item.isSelected && styles.activeComponentBG, - isLargeScreenWidth ? {...styles.flexRow, ...styles.justifyContentBetween, ...styles.alignItemsCenter} : {...styles.flexColumn, ...styles.alignItemsStretch}, + isLargeScreenWidth + ? { + ...styles.flexRow, + ...styles.justifyContentBetween, + ...styles.alignItemsCenter, + ...StyleUtils.getSearchTableRowPressableStyle(!!isLastItem, item.isSelected), + } + : {...styles.flexColumn, ...styles.alignItemsStretch}, ]; const animatedHighlightStyle = useAnimatedHighlightStyle({ - borderRadius: variables.componentBorderRadius, + borderRadius: StyleUtils.getSearchTableHighlightBorderRadius(isLargeScreenWidth), shouldHighlight: item?.shouldAnimateInHighlight ?? false, highlightColor: theme.messageHighlightBG, backgroundColor: theme.highlightBG, @@ -172,7 +180,6 @@ function TransactionListItem({ }); }; - const StyleUtils = useStyleUtils(); const pressableRef = useRef(null); useSyncFocus(pressableRef, !!isFocused, shouldSyncFocus); @@ -197,7 +204,14 @@ function TransactionListItem({ isFocused && StyleUtils.getItemBackgroundColorStyle(!!item.isSelected, !!isFocused, !!item.isDisabled, theme.activeComponentBG, theme.hoverComponentBG), ]} onFocus={onFocus} - wrapperStyle={[styles.mb2, styles.mh5, styles.flex1, animatedHighlightStyle, styles.userSelectNone]} + wrapperStyle={[ + !isLargeScreenWidth && styles.mb2, + styles.mh5, + styles.flex1, + animatedHighlightStyle, + styles.userSelectNone, + isLargeScreenWidth && isLastItem && styles.searchTableBottomRadius, + ]} > {({hovered}) => ( <> @@ -218,6 +232,7 @@ function TransactionListItem({ onButtonPress={handleActionButtonPress} onCheckboxPress={() => onCheckboxPress?.(item)} shouldUseNarrowLayout={!isLargeScreenWidth} + isLargeScreenWidth={isLargeScreenWidth} columns={columns} isActionLoading={isLoading ?? isActionLoading} isSelected={!!transactionItem.isSelected} @@ -231,7 +246,7 @@ function TransactionListItem({ taxAmountColumnSize={taxAmountColumnSize} shouldShowCheckbox={!!canSelectMultiple} checkboxSentryLabel={CONST.SENTRY_LABEL.SEARCH.TRANSACTION_LIST_ITEM_CHECKBOX} - style={[styles.p3, styles.pv2, shouldUseNarrowLayout ? styles.pt2 : {}]} + style={[styles.p3, styles.pv2, shouldUseNarrowLayout ? styles.pt2 : isLargeScreenWidth && styles.noBorderRadius]} violations={transactionViolations} onArrowRightPress={() => onSelectRow(item, transactionPreviewData)} isHover={hovered} diff --git a/src/components/Search/SearchList/ListItem/UserInfoAndActionButtonRow.tsx b/src/components/Search/SearchList/ListItem/UserInfoAndActionButtonRow.tsx index 51d4b430081b..5e2671fb465e 100644 --- a/src/components/Search/SearchList/ListItem/UserInfoAndActionButtonRow.tsx +++ b/src/components/Search/SearchList/ListItem/UserInfoAndActionButtonRow.tsx @@ -68,7 +68,7 @@ function UserInfoAndActionButtonRow({ shouldUseArrowIcon={false} /> )} - + ; textStyle?: TextStyle; avatarStyle?: ViewStyle; + isLargeScreenWidth?: boolean; }; -function UserInfoCell({avatar, accountID, displayName, avatarSize, containerStyle, textStyle, avatarStyle}: UserInfoCellProps) { +function UserInfoCell({avatar, accountID, displayName, avatarSize, containerStyle, textStyle, avatarStyle, isLargeScreenWidth}: UserInfoCellProps) { const styles = useThemeStyles(); - const {isLargeScreenWidth} = useResponsiveLayout(); if (!isCorrectSearchUserName(displayName) || !accountID) { return null; diff --git a/src/components/Search/SearchList/ListItem/UserInfoCellsWithArrow.tsx b/src/components/Search/SearchList/ListItem/UserInfoCellsWithArrow.tsx index 3f1fbb04a6c7..be256cf3f69c 100644 --- a/src/components/Search/SearchList/ListItem/UserInfoCellsWithArrow.tsx +++ b/src/components/Search/SearchList/ListItem/UserInfoCellsWithArrow.tsx @@ -24,6 +24,7 @@ function UserInfoCellsWithArrow({ infoCellsAvatarStyle, fromRecipientStyle, shouldUseArrowIcon = true, + isLargeScreenWidth, }: { shouldShowToRecipient: boolean; participantFrom: PersonalDetails; @@ -36,6 +37,7 @@ function UserInfoCellsWithArrow({ infoCellsAvatarStyle?: ViewStyle; fromRecipientStyle?: ViewStyle; shouldUseArrowIcon?: boolean; + isLargeScreenWidth?: boolean; }) { const icons = useMemoizedLazyExpensifyIcons(['ArrowRightLong']); const styles = useThemeStyles(); @@ -56,6 +58,7 @@ function UserInfoCellsWithArrow({ textStyle={infoCellsTextStyle} avatarStyle={infoCellsAvatarStyle} containerStyle={[styles.mw50, styles.flexShrink1, fromRecipientStyle]} + isLargeScreenWidth={isLargeScreenWidth} /> {shouldShowToRecipient && ( <> @@ -83,6 +86,7 @@ function UserInfoCellsWithArrow({ textStyle={infoCellsTextStyle} avatarStyle={infoCellsAvatarStyle} containerStyle={[styles.mw50, styles.flexShrink1, fromRecipientStyle, styles.mlHalf]} + isLargeScreenWidth={isLargeScreenWidth} /> )} diff --git a/src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx b/src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx index 86a883bb03f0..20d28c93bb58 100644 --- a/src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx +++ b/src/components/Search/SearchList/ListItem/WithdrawalIDListItemHeader.tsx @@ -77,7 +77,7 @@ function WithdrawalIDListItemHeader({ const maskedNumber = withdrawalIDItem.accountNumber ? `xx${withdrawalIDItem.accountNumber.slice(-4)}` : ''; const accountLabel = `${formattedBankName} ${maskedNumber}`; - const {icon, iconSize, iconStyles} = getBankIcon({bankName: withdrawalIDItem.bankName, styles}); + const {icon, iconSize, iconStyles} = getBankIcon({bankName: withdrawalIDItem.bankName, styles, maxIconSize: isLargeScreenWidth ? variables.w28 : undefined}); const formattedWithdrawalDate = DateUtils.formatWithUTCTimeZone( withdrawalIDItem.debitPosted, DateUtils.doesDateBelongToAPastYear(withdrawalIDItem.debitPosted) ? CONST.DATE.MONTH_DAY_YEAR_ABBR_FORMAT : CONST.DATE.MONTH_DAY_ABBR_FORMAT, @@ -116,7 +116,9 @@ function WithdrawalIDListItemHeader({ [CONST.SEARCH.TABLE_COLUMNS.GROUP_WITHDRAWN]: ( ({ return ( - + {!!canSelectMultiple && ( ({ disabled={!!isDisabled || withdrawalIDItem.isDisabledCheckbox} accessibilityLabel={translate('common.select')} isIndeterminate={isIndeterminate} + containerStyle={styles.m0} /> )} {!isLargeScreenWidth && ( diff --git a/src/components/Search/SearchList/ListItem/types.ts b/src/components/Search/SearchList/ListItem/types.ts index 4f19c2436a4e..952d0372862a 100644 --- a/src/components/Search/SearchList/ListItem/types.ts +++ b/src/components/Search/SearchList/ListItem/types.ts @@ -253,6 +253,9 @@ type TransactionReportGroupListItemType = TransactionGroupListItemType & {groupe /** Pre-computed reimbursable spend amount */ reimbursableSpend?: number; + /** Whether the amount column should use the wide layout */ + isAmountColumnWide?: boolean; + /** Pre-computed flag indicating whether all transactions are scanning */ isAllScanning?: boolean; @@ -356,6 +359,9 @@ type TransactionMonthGroupListItemType = TransactionGroupListItemType & {grouped type TransactionWithdrawalIDGroupListItemType = TransactionGroupListItemType & {groupedBy: typeof CONST.SEARCH.GROUP_BY.WITHDRAWAL_ID} & SearchWithdrawalIDGroup & { /** Final and formatted "withdrawalID" value used for displaying and sorting */ formattedWithdrawalID?: string; + + /** Whether any withdrawn date in the current results belongs to a past year */ + shouldShowYearWithdrawn?: boolean; }; type TransactionCategoryGroupListItemType = TransactionGroupListItemType & {groupedBy: typeof CONST.SEARCH.GROUP_BY.CATEGORY} & SearchCategoryGroup & { diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index 73fd8a20e432..391a1699a199 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -286,7 +286,7 @@ function SearchList({ // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout here because there is a race condition that causes shouldUseNarrowLayout to change indefinitely in this component // See https://github.com/Expensify/App/issues/48675 for more details // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); + const {isSmallScreenWidth, isLargeScreenWidth} = useResponsiveLayout(); const [isModalVisible, setIsModalVisible] = useState(false); const [longPressedItem, setLongPressedItem] = useState(); @@ -451,6 +451,8 @@ function SearchList({ onFocus={onFocus} newTransactionID={newTransactionID} keyForList={item.keyForList} + isFirstItem={index === 0} + isLastItem={index === data.length - 1 && !ListFooterComponent} /> ); @@ -482,6 +484,7 @@ function SearchList({ personalPolicyID, customCardNames, selectedTransactions, + ListFooterComponent, ], ); @@ -492,7 +495,12 @@ function SearchList({ const content = ( {tableHeaderVisible && ( - + {canSelectMultiple && ( )} diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBarWide.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBarWide.tsx index 97f2d08a7a19..b4e218958039 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBarWide.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBarWide.tsx @@ -49,7 +49,7 @@ function SearchFiltersBarWide({queryJSON, isMobileSelectionModeEnabled}: SearchF } return ( - + {shouldShowSelectedDropdown ? ( ) : ( diff --git a/src/components/Search/SearchTableHeader.tsx b/src/components/Search/SearchTableHeader.tsx index 7ac9cc87beb3..51e7ebd54b86 100644 --- a/src/components/Search/SearchTableHeader.tsx +++ b/src/components/Search/SearchTableHeader.tsx @@ -439,6 +439,7 @@ type SearchTableHeaderProps = { shouldShowYearApproved?: boolean; shouldShowYearPosted?: boolean; shouldShowYearExported?: boolean; + shouldShowYearWithdrawn?: boolean; isAmountColumnWide: boolean; isTaxAmountColumnWide: boolean; shouldShowSorting: boolean; @@ -460,6 +461,7 @@ function SearchTableHeader({ shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported, + shouldShowYearWithdrawn, shouldShowSorting, canSelectMultiple, isAmountColumnWide, @@ -531,15 +533,15 @@ function SearchTableHeader({ approvedColumnSize={shouldShowYearApproved ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} postedColumnSize={shouldShowYearPosted ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} exportedColumnSize={shouldShowYearExported ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} + withdrawnColumnSize={shouldShowYearWithdrawn ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} amountColumnSize={isAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} taxAmountColumnSize={isTaxAmountColumnWide ? CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE : CONST.SEARCH.TABLE_COLUMN_SIZES.NORMAL} shouldShowSorting={shouldShowSorting} sortBy={sortBy} sortOrder={sortOrder} - // In GroupBy views, disable flex expansion for Total columns so Expenses column gets more space - shouldRemoveTotalColumnFlex={!!groupBy && !isExpenseReportView} + shouldRemoveTotalColumnFlex={!!groupBy !== !!isExpenseReportView} // Don't butt up against the 'select all' checkbox if present - containerStyles={canSelectMultiple && [styles.pl4]} + containerStyles={canSelectMultiple && [styles.pl3]} onSortPress={(columnName, order) => { if (columnName === CONST.SEARCH.TABLE_COLUMNS.COMMENTS) { return; diff --git a/src/components/Search/SortableTableHeader.tsx b/src/components/Search/SortableTableHeader.tsx index 67bea394f13a..60e1d7f45b02 100644 --- a/src/components/Search/SortableTableHeader.tsx +++ b/src/components/Search/SortableTableHeader.tsx @@ -28,6 +28,7 @@ type SearchTableHeaderProps = { approvedColumnSize?: TableColumnSize; postedColumnSize?: TableColumnSize; exportedColumnSize?: TableColumnSize; + withdrawnColumnSize?: TableColumnSize; amountColumnSize: TableColumnSize; taxAmountColumnSize: TableColumnSize; containerStyles?: StyleProp; @@ -46,6 +47,7 @@ function SortableTableHeader({ approvedColumnSize, postedColumnSize, exportedColumnSize, + withdrawnColumnSize, containerStyles, shouldShowSorting, onSortPress, @@ -88,6 +90,7 @@ function SortableTableHeader({ isTaxAmountColumnWide: taxAmountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, isAmountColumnWide: amountColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, shouldRemoveTotalColumnFlex, + isWithdrawnColumnWide: withdrawnColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE, }), ]} isSortable={isSortable} diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index d752727e434f..414b475b3b94 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -281,14 +281,12 @@ function Search({ const {isOffline} = useNetwork(); const prevIsOffline = usePrevious(isOffline); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - const styles = useThemeStyles(); - // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout for enabling the selection mode on small screens only // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth, isLargeScreenWidth} = useResponsiveLayout(); + const {shouldUseNarrowLayout, isSmallScreenWidth, isLargeScreenWidth} = useResponsiveLayout(); + const styles = useThemeStyles(); const navigation = useNavigation>(); const isFocused = useIsFocused(); - const {markReportIDAsExpense} = useWideRHPActions(); + const {markReportIDAsExpense, markReportIDAsMultiTransactionExpense, unmarkReportIDAsMultiTransactionExpense} = useWideRHPActions(); const { currentSearchHash, currentSearchKey, @@ -321,7 +319,6 @@ function Search({ const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID); const isExpenseReportType = type === CONST.SEARCH.DATA_TYPES.EXPENSE_REPORT; - const {markReportIDAsMultiTransactionExpense, unmarkReportIDAsMultiTransactionExpense} = useWideRHPActions(); const archivedReportsIdSet = useArchivedReportsIdSet(); @@ -1445,6 +1442,47 @@ function Search({ // dep-free useEffect above — see comment on didBailToFallbackState. didBailToFallbackState.current = false; + const isAnyVisibleActionLoading = useMemo( + () => filteredData.some((item) => 'reportID' in item && item.reportID && isActionLoadingSet.has(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${item.reportID}`)), + [filteredData, isActionLoadingSet], + ); + + const visibleDataLength = useMemo(() => filteredData.filter((item) => item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length, [filteredData, isOffline]); + + const yearIndicators = useMemo( + () => + searchResults?.data + ? shouldShowYearUtil(searchResults.data, isExpenseReportType ?? false, undefined, type === CONST.SEARCH.DATA_TYPES.EXPENSE) + : { + shouldShowYearCreated: false, + shouldShowYearSubmitted: false, + shouldShowYearApproved: false, + shouldShowYearPosted: false, + shouldShowYearExported: false, + shouldShowYearWithdrawn: false, + }, + [searchResults?.data, isExpenseReportType, type], + ); + + const amountIndicators = useMemo( + () => (searchResults?.data ? getWideAmountIndicators(searchResults.data) : {shouldShowAmountInWideColumn: false, shouldShowTaxAmountInWideColumn: false}), + [searchResults?.data], + ); + + const onSortPress = useCallback( + (column: SearchColumnType, order: SortOrder) => { + clearSelectedTransactions(); + const newQuery = buildSearchQueryString({ + ...queryJSON, + sortBy: column, + sortOrder: order, + }); + onSortPressedCallback?.(); + navigation.setParams({q: newQuery, rawQuery: undefined}); + }, + [clearSelectedTransactions, queryJSON, onSortPressedCallback, navigation], + ); + // This is a performance optimization for the submit-expense->search path only. // The SearchPage skeleton (useSearchLoadingState) doesn't cover this case because // Search must mount for its onLayout to flush the deferred CreateMoneyRequest API write, which would block the JS thread causing a slowdown on post expense creation navigation @@ -1495,8 +1533,6 @@ function Search({ ); } - const isAnyVisibleActionLoading = filteredData.some((item) => 'reportID' in item && item.reportID && isActionLoadingSet.has(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${item.reportID}`)); - const visibleDataLength = filteredData.filter((item) => item.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE || isOffline).length; // Guard: don't render the empty view while the data is transiently empty. // - shouldDeferHeavySearchWork: skeleton is still showing, data hasn't been computed yet. // - showPendingExpensePlaceholder: deferred write hasn't produced the optimistic item yet. @@ -1522,23 +1558,8 @@ function Search({ ); } - const onSortPress = (column: SearchColumnType, order: SortOrder) => { - clearSelectedTransactions(); - const newQuery = buildSearchQueryString({ - ...queryJSON, - sortBy: column, - sortOrder: order, - }); - onSortPressedCallback?.(); - // We want to explicitly clear stale rawQuery since it's only used for manually typed-in queries. - navigation.setParams({q: newQuery, rawQuery: undefined}); - }; - - const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYearUtil( - searchResults?.data, - isExpenseReportType ?? false, - ); - const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(searchResults?.data); + const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported, shouldShowYearWithdrawn} = yearIndicators; + const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = amountIndicators; const shouldShowTableHeader = isLargeScreenWidth && !isChat; const tableHeaderVisible = canSelectMultiple || shouldShowTableHeader; @@ -1617,10 +1638,12 @@ function Search({ shouldShowYearApproved={shouldShowYearApproved} shouldShowYearPosted={shouldShowYearPosted} shouldShowYearExported={shouldShowYearExported} + shouldShowYearWithdrawn={shouldShowYearWithdrawn} isAmountColumnWide={shouldShowAmountInWideColumn} isTaxAmountColumnWide={shouldShowTaxAmountInWideColumn} shouldShowSorting groupBy={validGroupBy} + isExpenseReportView={isExpenseReportType} /> ) @@ -1639,6 +1662,7 @@ function Search({ shouldAnimate fixedNumItems={shouldShowLoadingMoreItems ? 5 : 1} reasonAttributes={showPendingExpensePlaceholder ? pendingExpenseReasonAttributes : loadMoreSkeletonReasonAttributes} + isLoadMore /> ) : undefined } diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx index e72ca9ff9555..7e0048d2b546 100644 --- a/src/components/SelectionList/BaseSelectionList.tsx +++ b/src/components/SelectionList/BaseSelectionList.tsx @@ -365,6 +365,7 @@ function BaseSelectionList({ shouldSyncFocus={!isTextInputFocusedRef.current && hasKeyBeenPressed.current} shouldDisableHoverStyle={shouldDisableHoverStyle} shouldShowRightCaret={shouldShowRightCaret} + isLastItem={index === data.length - 1} shouldPreventEnterKeySubmit={!disableKeyboardShortcuts} /> ); diff --git a/src/components/SelectionList/ListItem/ListItemRenderer.tsx b/src/components/SelectionList/ListItem/ListItemRenderer.tsx index 74273fe69189..107d21e67fd8 100644 --- a/src/components/SelectionList/ListItem/ListItemRenderer.tsx +++ b/src/components/SelectionList/ListItem/ListItemRenderer.tsx @@ -16,6 +16,7 @@ type ListItemRendererProps = Omit['singleExecution']; titleStyles?: StyleProp; titleContainerStyles?: StyleProp; + isLastItem?: boolean; shouldHighlightSelectedItem: boolean; shouldPreventEnterKeySubmit?: boolean; }; @@ -54,6 +55,7 @@ function ListItemRenderer({ shouldDisableHoverStyle, shouldShowRightCaret, errorRowStyles, + isLastItem, shouldPreventEnterKeySubmit = true, }: ListItemRendererProps) { const handleOnCheckboxPress = () => { @@ -112,6 +114,7 @@ function ListItemRenderer({ shouldHighlightSelectedItem={shouldHighlightSelectedItem} shouldDisableHoverStyle={shouldDisableHoverStyle} shouldShowRightCaret={shouldShowRightCaret} + isLastItem={isLastItem} /> {item.footerContent && item.footerContent} diff --git a/src/components/SelectionList/ListItem/types.ts b/src/components/SelectionList/ListItem/types.ts index 286759246408..0dbd19174807 100644 --- a/src/components/SelectionList/ListItem/types.ts +++ b/src/components/SelectionList/ListItem/types.ts @@ -294,6 +294,12 @@ type ListItemProps = CommonListItemProps & { /** Whether the network is offline */ isOffline?: boolean; + + /** Whether this is the last item in the list (for border radius on desktop) */ + isLastItem?: boolean; + + /** Whether this is the first item in the list (for border styling on desktop) */ + isFirstItem?: boolean; }; type ValidListItem = diff --git a/src/components/Skeletons/ItemListSkeletonView.tsx b/src/components/Skeletons/ItemListSkeletonView.tsx index 779a18c66956..2d774a3c425b 100644 --- a/src/components/Skeletons/ItemListSkeletonView.tsx +++ b/src/components/Skeletons/ItemListSkeletonView.tsx @@ -12,6 +12,7 @@ type ListItemSkeletonProps = { fixedNumItems?: number; gradientOpacityEnabled?: boolean; itemViewStyle?: StyleProp; + itemContainerStyle?: StyleProp; itemViewHeight?: number; speed?: number; style?: StyleProp; @@ -37,6 +38,7 @@ function ItemListSkeletonView({ fixedNumItems, gradientOpacityEnabled = false, itemViewStyle = {}, + itemContainerStyle, itemViewHeight = CONST.LHN_SKELETON_VIEW_ITEM_HEIGHT, speed, style, @@ -70,7 +72,7 @@ function ItemListSkeletonView({ const items = []; for (let i = 0; i < numItems; i++) { const opacity = gradientOpacityEnabled ? 1 - i / (numItems - 1) : 1; - items.push( + const loader = ( {renderSkeletonItem({itemIndex: i})} - , + ); + + if (itemContainerStyle) { + const isLastItem = i === numItems - 1; + items.push( + + {loader} + , + ); + } else { + items.push(loader); + } } return items; - }, [numItems, shouldAnimate, theme, themeStyles, renderSkeletonItem, gradientOpacityEnabled, itemViewHeight, itemViewStyle, speed]); + }, [numItems, shouldAnimate, theme, themeStyles, renderSkeletonItem, gradientOpacityEnabled, itemViewHeight, itemViewStyle, speed, itemContainerStyle]); return ( ; reasonAttributes: SkeletonSpanReasonAttributes; + isLoadMore?: boolean; onLayout?: (event: LayoutChangeEvent) => void; }; @@ -28,16 +29,19 @@ const leftPaneWidth = variables.sideBarWithLHBWidth + variables.navigationTabBar // 12 is the gap between the element and the right button const gapWidth = 12; -// 80 is the width of the element itself -const rightSideElementWidth = 80; +// 68 is the width of the action button +const rightSideElementWidth = 68; -// 24 is the padding of the central pane summing two sides +// 40 is the padding of the central pane summing two sides const centralPanePadding = 40; -// 80 is the width of the button on the right side -const rightButtonWidth = 80; +// 16 is the width of the right arrow icon + padding +const rightArrowWidth = 28; -function SearchRowSkeleton({shouldAnimate = true, fixedNumItems, gradientOpacityEnabled = false, containerStyle, reasonAttributes, onLayout}: SearchRowSkeletonProps) { +// 68 is the width of the action button +const rightButtonWidth = 68; + +function SearchRowSkeleton({shouldAnimate = true, fixedNumItems, gradientOpacityEnabled = false, containerStyle, reasonAttributes, isLoadMore = false, onLayout}: SearchRowSkeletonProps) { const styles = useThemeStyles(); const {windowWidth} = useWindowDimensions(); const {shouldUseNarrowLayout, isLargeScreenWidth} = useResponsiveLayout(); @@ -123,35 +127,38 @@ function SearchRowSkeleton({shouldAnimate = true, fixedNumItems, gradientOpacity fixedNumItems={fixedNumItems} gradientOpacityEnabled={gradientOpacityEnabled} onLayout={onLayout} - itemViewStyle={[styles.highlightBG, styles.mb2, styles.br3, styles.ml5]} + itemViewStyle={[styles.highlightBG, styles.mr0]} + itemViewHeight={variables.tableRowHeight} + itemContainerStyle={styles.borderBottom} + style={[styles.mh5, styles.overflowHidden, isLoadMore && styles.searchTableBottomRadius, !isLoadMore && styles.searchTableTopRadius]} renderSkeletonItem={() => ( <> {isLargeScreenWidth && ( <> @@ -160,16 +167,19 @@ function SearchRowSkeleton({shouldAnimate = true, fixedNumItems, gradientOpacity diff --git a/src/components/TransactionItemRow/DataCells/CategoryCell.tsx b/src/components/TransactionItemRow/DataCells/CategoryCell.tsx index 6a7cacaa6814..80e8801c9549 100644 --- a/src/components/TransactionItemRow/DataCells/CategoryCell.tsx +++ b/src/components/TransactionItemRow/DataCells/CategoryCell.tsx @@ -23,8 +23,8 @@ function CategoryCell({shouldUseNarrowLayout, shouldShowTooltip, transactionItem ); } diff --git a/src/components/TransactionItemRow/DataCells/MerchantCell.tsx b/src/components/TransactionItemRow/DataCells/MerchantCell.tsx index 7aec8d8fdfda..924aa5168854 100644 --- a/src/components/TransactionItemRow/DataCells/MerchantCell.tsx +++ b/src/components/TransactionItemRow/DataCells/MerchantCell.tsx @@ -27,8 +27,8 @@ function MerchantOrDescriptionCell({ ); } diff --git a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx index b833a4ec6d92..c5f7bddc9ea2 100644 --- a/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx +++ b/src/components/TransactionItemRow/DataCells/ReceiptCell.tsx @@ -16,10 +16,11 @@ import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import variables from '@styles/variables'; import type {Transaction} from '@src/types/onyx'; -function ReceiptCell({transactionItem, isSelected, style}: {transactionItem: Transaction; isSelected: boolean; style?: ViewStyle}) { +function ReceiptCell({transactionItem, isSelected, style, shouldUseNarrowLayout}: {transactionItem: Transaction; isSelected: boolean; style?: ViewStyle; shouldUseNarrowLayout?: boolean}) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const isLargeScreenWidth = !shouldUseNarrowLayout; const icons = useMemoizedLazyExpensifyIcons(['Receipt']); const backgroundStyles = isSelected ? StyleUtils.getBackgroundColorStyle(theme.buttonHoveredBG) : StyleUtils.getBackgroundColorStyle(theme.border); const {hovered, bind} = useHover(); @@ -56,7 +57,7 @@ function ReceiptCell({transactionItem, isSelected, style}: {transactionItem: Tra return ( ); } diff --git a/src/components/TransactionItemRow/DataCells/TypeCell.tsx b/src/components/TransactionItemRow/DataCells/TypeCell.tsx index 03d9ff0cf769..c93aebd28477 100644 --- a/src/components/TransactionItemRow/DataCells/TypeCell.tsx +++ b/src/components/TransactionItemRow/DataCells/TypeCell.tsx @@ -83,8 +83,8 @@ function TypeCell({transactionItem, shouldUseNarrowLayout, shouldShowTooltip}: T diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 9bb8b8b8697a..e57dc00dbf67 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -20,7 +20,6 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -146,6 +145,7 @@ type TransactionItemRowProps = { customCardNames?: Record; reportActions?: ReportAction[]; checkboxSentryLabel?: string; + isLargeScreenWidth?: boolean; }; const EMPTY_ACTIVE_STYLE: StyleProp = []; @@ -198,12 +198,13 @@ function TransactionItemRow({ customCardNames, reportActions, checkboxSentryLabel, + isLargeScreenWidth: isLargeScreenWidthProp, }: TransactionItemRowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); const theme = useTheme(); - const {isLargeScreenWidth} = useResponsiveLayout(); + const isLargeScreenWidth = isLargeScreenWidthProp ?? !shouldUseNarrowLayout; const hasCategoryOrTag = !isCategoryMissing(transactionItem?.category) || !!transactionItem.tag; const createdAt = getTransactionCreated(transactionItem); const expensicons = useMemoizedLazyExpensifyIcons(['ArrowRight']); @@ -309,6 +310,7 @@ function TransactionItemRow({ ); @@ -485,6 +487,7 @@ function TransactionItemRow({ accountID={transactionItem.to.accountID} avatar={transactionItem.to.avatar} displayName={transactionItem.formattedTo ?? transactionItem.to.displayName ?? ''} + isLargeScreenWidth={isLargeScreenWidth} /> )} @@ -500,6 +503,7 @@ function TransactionItemRow({ accountID={transactionItem.from.accountID} avatar={transactionItem.from.avatar} displayName={transactionItem.formattedFrom ?? transactionItem.from.displayName ?? ''} + isLargeScreenWidth={isLargeScreenWidth} /> )} @@ -700,6 +704,7 @@ function TransactionItemRow({ accessibilityLabel={CONST.ROLE.CHECKBOX} isChecked={isSelected} style={styles.mr3} + containerStyle={styles.m0} wrapperStyle={styles.justifyContentCenter} sentryLabel={checkboxSentryLabel} /> @@ -708,6 +713,7 @@ function TransactionItemRow({ transactionItem={transactionItem} isSelected={isSelected} style={styles.mr3} + shouldUseNarrowLayout={shouldUseNarrowLayout} /> @@ -830,7 +836,7 @@ function TransactionItemRow({ }} accessibilityLabel={CONST.ROLE.CHECKBOX} isChecked={isSelected} - style={styles.mr1} + containerStyle={styles.m0} wrapperStyle={styles.justifyContentCenter} sentryLabel={checkboxSentryLabel} /> @@ -851,7 +857,7 @@ function TransactionItemRow({ onArrowRightPress?.()} - style={[styles.p3Half, styles.pl0half, styles.pr0half, styles.justifyContentCenter, styles.alignItemsEnd]} + style={[styles.pv2, styles.justifyContentCenter, styles.alignItemsEnd]} accessibilityRole={CONST.ROLE.BUTTON} accessibilityLabel={CONST.ROLE.BUTTON} sentryLabel={CONST.SENTRY_LABEL.TRANSACTION_ITEM_ROW.ARROW_RIGHT} diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index ad634471850e..d37f6b8a45d6 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -1408,6 +1408,7 @@ type ShouldShowYearResult = { shouldShowYearApproved: boolean; shouldShowYearPosted: boolean; shouldShowYearExported: boolean; + shouldShowYearWithdrawn: boolean; }; /** @@ -1454,6 +1455,7 @@ function shouldShowYear( data: TransactionListItemType[] | TransactionGroupListItemType[] | TaskListItemType[] | OnyxTypes.SearchResults['data'], checkOnlyReports = false, precomputedLastExportedMap?: Map, + skipReportCreatedDate = false, ): ShouldShowYearResult { const result: ShouldShowYearResult = { shouldShowYearCreated: false, @@ -1461,6 +1463,7 @@ function shouldShowYear( shouldShowYearApproved: false, shouldShowYearPosted: false, shouldShowYearExported: false, + shouldShowYearWithdrawn: false, }; const currentYear = new Date().getFullYear(); @@ -1549,7 +1552,7 @@ function shouldShowYear( } else if (isReportEntry(key)) { const item = data[key]; - if (item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { + if (!skipReportCreatedDate && item.created && DateUtils.doesDateBelongToAPastYear(item.created)) { result.shouldShowYearCreated = true; } if (item.submitted && DateUtils.doesDateBelongToAPastYear(item.submitted)) { @@ -1563,10 +1566,22 @@ function shouldShowYear( if (exportedAction?.created && DateUtils.doesDateBelongToAPastYear(exportedAction.created)) { result.shouldShowYearExported = true; } + } else if (!result.shouldShowYearWithdrawn && isGroupEntry(key)) { + const group = data[key]; + if ('debitPosted' in group && group.debitPosted && DateUtils.doesDateBelongToAPastYear(group.debitPosted)) { + result.shouldShowYearWithdrawn = true; + } } // Early exit if all flags are true - if (result.shouldShowYearCreated && result.shouldShowYearSubmitted && result.shouldShowYearApproved && result.shouldShowYearPosted && result.shouldShowYearExported) { + if ( + result.shouldShowYearCreated && + result.shouldShowYearSubmitted && + result.shouldShowYearApproved && + result.shouldShowYearPosted && + result.shouldShowYearExported && + result.shouldShowYearWithdrawn + ) { return result; } } @@ -1733,7 +1748,12 @@ function getTransactionsSections({ }: GetTransactionSectionsParams): [TransactionListItemType[], number] { const shouldShowMerchant = getShouldShowMerchant(data); const lastExportedActionByReportID = buildLastExportedActionByReportIDMap(data); - const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYear(data, false, lastExportedActionByReportID); + const {shouldShowYearCreated, shouldShowYearSubmitted, shouldShowYearApproved, shouldShowYearPosted, shouldShowYearExported} = shouldShowYear( + data, + false, + lastExportedActionByReportID, + true, + ); const {shouldShowAmountInWideColumn, shouldShowTaxAmountInWideColumn} = getWideAmountIndicators(data); // Pre-filter transaction keys to avoid repeated checks @@ -2467,6 +2487,7 @@ function getReportSections({ totalDisplaySpend, nonReimbursableSpend, reimbursableSpend, + isAmountColumnWide: shouldShowAmountInWideColumn, isAllScanning: false, ...avatarProps, }; @@ -2785,6 +2806,7 @@ function getCardSections( * Do not use directly, use only via `getSections()` facade. */ function getWithdrawalIDSections(data: OnyxTypes.SearchResults['data'], queryJSON: SearchQueryJSON | undefined): [TransactionWithdrawalIDGroupListItemType[], number] { + const {shouldShowYearWithdrawn} = shouldShowYear(data); const withdrawalIDSections: Record = {}; for (const key in data) { @@ -2802,6 +2824,7 @@ function getWithdrawalIDSections(data: OnyxTypes.SearchResults['data'], queryJSO transactions: [], transactionsQueryJSON, ...withdrawalIDGroup, + shouldShowYearWithdrawn, formattedWithdrawalID: String(withdrawalIDGroup.entryID), keyForList: key, }; @@ -5104,18 +5127,28 @@ function getTableMinWidth(columns: SearchColumnType[]) { let minWidth = 24; for (const column of columns) { - if (column === CONST.SEARCH.TABLE_COLUMNS.RECEIPT || column === CONST.SEARCH.TABLE_COLUMNS.COMMENTS) { + if (column === CONST.SEARCH.TABLE_COLUMNS.COMMENTS) { minWidth += 36; + } else if (column === CONST.SEARCH.TABLE_COLUMNS.RECEIPT) { + minWidth += 28; } else if (column === CONST.SEARCH.TABLE_COLUMNS.AVATAR) { - minWidth += 40; - } else if (column === CONST.SEARCH.TABLE_COLUMNS.STATUS || column === CONST.SEARCH.TABLE_COLUMNS.ACTION) { + minWidth += 28; + } else if (column === CONST.SEARCH.TABLE_COLUMNS.STATUS) { minWidth += 80; + } else if (column === CONST.SEARCH.TABLE_COLUMNS.ACTION) { + minWidth += 68; } else if (column === CONST.SEARCH.TABLE_COLUMNS.DATE) { minWidth += 48; - } else if (column === CONST.SEARCH.TABLE_COLUMNS.SUBMITTED || column === CONST.SEARCH.TABLE_COLUMNS.APPROVED || column === CONST.SEARCH.TABLE_COLUMNS.POSTED) { + } else if ( + column === CONST.SEARCH.TABLE_COLUMNS.SUBMITTED || + column === CONST.SEARCH.TABLE_COLUMNS.APPROVED || + column === CONST.SEARCH.TABLE_COLUMNS.POSTED || + column === CONST.SEARCH.TABLE_COLUMNS.WITHDRAWN || + column === CONST.SEARCH.TABLE_COLUMNS.GROUP_WITHDRAWN + ) { minWidth += 72; } else if (column === CONST.SEARCH.TABLE_COLUMNS.TYPE) { - minWidth += 20; + minWidth += 16; } else if (column === CONST.SEARCH.TABLE_COLUMNS.REIMBURSABLE || column === CONST.SEARCH.TABLE_COLUMNS.BILLABLE) { minWidth += 80; } else { diff --git a/src/styles/index.ts b/src/styles/index.ts index 68633f5a5418..92b007e95afd 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2489,6 +2489,20 @@ const staticStyles = (theme: ThemeColors) => borderBottomRightRadius: variables.componentBorderRadiusNormal, }, + searchTableTopRadius: { + borderTopLeftRadius: variables.componentBorderRadius, + borderTopRightRadius: variables.componentBorderRadius, + }, + + searchTableBottomRadius: { + borderBottomLeftRadius: variables.componentBorderRadius, + borderBottomRightRadius: variables.componentBorderRadius, + }, + + searchTableRowHeight: { + minHeight: variables.tableRowHeight, + }, + borderBottom: { borderBottomWidth: 1, borderColor: theme.border, @@ -3504,15 +3518,23 @@ const staticStyles = (theme: ThemeColors) => }, searchListHeaderContainerStyle: { - width: '100%', flexDirection: 'row', alignItems: 'center', ...userSelect.userSelectNone, paddingBottom: 12, - backgroundColor: theme.appBG, justifyContent: 'flex-start', }, + searchListHeaderTableStyle: { + backgroundColor: theme.highlightBG, + borderTopLeftRadius: variables.componentBorderRadius, + borderTopRightRadius: variables.componentBorderRadius, + borderBottomWidth: 1, + borderColor: theme.border, + minHeight: 36, + paddingBottom: 8, + }, + groupSearchListTableContainerStyle: { minHeight: variables.h28, paddingBottom: 0, @@ -4782,6 +4804,11 @@ const staticStyles = (theme: ThemeColors) => paddingHorizontal: 32, }, + listTableHeaderCompact: { + paddingVertical: 8, + paddingHorizontal: 12, + }, + tableHeaderIconSpacing: { marginRight: variables.iconSizeExtraSmall, marginBottom: 1, diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index 2d3ac64f1ab3..2a60f3218a52 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -62,6 +62,7 @@ type GetReportTableColumnStylesParams = { isPostedColumnWide?: boolean; isExportedColumnWide?: boolean; shouldRemoveTotalColumnFlex?: boolean; + isWithdrawnColumnWide?: boolean; }; const workspaceColorOptions: SVGAvatarColorStyle[] = LETTER_AVATAR_COLOR_OPTIONS.map(({backgroundColor, fillColor}) => ({backgroundColor, fill: fillColor})); @@ -1804,6 +1805,32 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ return isDragging ? styles.cursorGrabbing : styles.cursorZoomOut; }, + getSearchTableRowBorderStyle: (isLastItem?: boolean, isSelected?: boolean): ViewStyle => ({ + borderRadius: 0, + borderBottomWidth: isLastItem ? 0 : 1, + borderColor: isSelected ? theme.buttonHoveredBG : theme.border, + ...(isLastItem ? styles.searchTableBottomRadius : {}), + }), + + getSearchTableGroupRowBorderStyle: (isFirstItem?: boolean, isLastItem?: boolean, isSelected?: boolean): ViewStyle => ({ + borderRadius: 0, + borderTopWidth: isFirstItem ? 0 : 1, + borderColor: isSelected ? theme.buttonHoveredBG : theme.border, + ...(isLastItem ? styles.searchTableBottomRadius : {}), + }), + + getSearchTableRowPressableStyle: (isLastItem?: boolean, isSelected?: boolean, padding?: {vertical?: number; horizontal?: number}): ViewStyle => ({ + minHeight: variables.tableRowHeight, + borderRadius: 0, + borderBottomWidth: isLastItem ? 0 : 1, + borderColor: isSelected ? theme.buttonHoveredBG : theme.border, + ...(isLastItem ? styles.searchTableBottomRadius : {}), + ...(padding?.vertical !== undefined && {paddingVertical: padding.vertical}), + ...(padding?.horizontal !== undefined && {paddingHorizontal: padding.horizontal}), + }), + + getSearchTableHighlightBorderRadius: (isLargeScreenWidth: boolean): number => (isLargeScreenWidth ? 0 : variables.componentBorderRadius), + getReportTableColumnStyles: (columnName: string, options: GetReportTableColumnStylesParams = {}): ViewStyle => { const { isSubmittedColumnWide, @@ -1814,16 +1841,19 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ isTaxAmountColumnWide, isAmountColumnWide, shouldRemoveTotalColumnFlex, + isWithdrawnColumnWide, } = options; let columnWidth; switch (columnName) { case CONST.SEARCH.TABLE_COLUMNS.COMMENTS: - case CONST.SEARCH.TABLE_COLUMNS.RECEIPT: columnWidth = {...getWidthStyle(variables.w36), ...styles.alignItemsCenter}; break; + case CONST.SEARCH.TABLE_COLUMNS.RECEIPT: + columnWidth = {...getWidthStyle(variables.w28), ...styles.alignItemsCenter}; + break; case CONST.SEARCH.TABLE_COLUMNS.AVATAR: - columnWidth = {...getWidthStyle(variables.w40), ...styles.alignItemsCenter}; + columnWidth = {...getWidthStyle(variables.w28), ...styles.alignItemsCenter}; break; case CONST.SEARCH.TABLE_COLUMNS.STATUS: columnWidth = {...getWidthStyle(variables.w80), ...styles.alignItemsCenter}; @@ -1848,7 +1878,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ break; case CONST.SEARCH.TABLE_COLUMNS.WITHDRAWN: case CONST.SEARCH.TABLE_COLUMNS.GROUP_WITHDRAWN: - columnWidth = {...getWidthStyle(variables.w96)}; + columnWidth = {...getWidthStyle(isWithdrawnColumnWide ? variables.w92 : variables.w72)}; break; case CONST.SEARCH.TABLE_COLUMNS.CATEGORY: case CONST.SEARCH.TABLE_COLUMNS.GROUP_CATEGORY: @@ -1877,7 +1907,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ columnWidth = {...getWidthStyle(isAmountColumnWide ? variables.w130 : variables.w96), ...(!shouldRemoveTotalColumnFlex && styles.flex1), ...styles.alignItemsEnd}; break; case CONST.SEARCH.TABLE_COLUMNS.TYPE: - columnWidth = {...getWidthStyle(variables.w20), ...styles.alignItemsCenter}; + columnWidth = {...getWidthStyle(variables.w16), ...styles.alignItemsCenter}; break; case CONST.SEARCH.TABLE_COLUMNS.REIMBURSABLE: case CONST.SEARCH.TABLE_COLUMNS.BILLABLE: @@ -1887,7 +1917,7 @@ const createStyleUtils = (theme: ThemeColors, styles: ThemeStyles) => ({ columnWidth = {...getWidthStyle(variables.w92), ...styles.flex1}; break; case CONST.SEARCH.TABLE_COLUMNS.ACTION: - columnWidth = {...getWidthStyle(variables.w80), ...styles.alignItemsCenter}; + columnWidth = {...getWidthStyle(variables.w68), ...styles.alignItemsCenter}; break; case CONST.SEARCH.TABLE_COLUMNS.EXPORTED_TO: columnWidth = {...getWidthStyle(variables.w72), ...styles.alignItemsCenter}; diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 854f3cfdba02..0270e262ab76 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -120,6 +120,10 @@ export default { gutterWidth: 12, optionRowHeight: 64, optionRowHeightCompact: 52, + tableRowHeight: 56, + tableRowPaddingVertical: 8, + tableRowPaddingHorizontal: 12, + tableGroupRowPaddingVertical: 4, sectionMenuItemHeight: 52, sectionMenuItemHeightCompact: 44, optionsListSectionHeaderHeight: getValueUsingPixelRatio(32, 38), @@ -354,6 +358,7 @@ export default { h20: 20, h28: 28, + h32: 32, h36: 36, h40: 40, h70: 70, @@ -368,6 +373,7 @@ export default { w44: 44, w46: 46, w52: 52, + w68: 68, w72: 72, w80: 80, w92: 92, diff --git a/tests/unit/Search/SearchUIUtilsTest.ts b/tests/unit/Search/SearchUIUtilsTest.ts index 52073fbad279..7cb2e4cd8f7e 100644 --- a/tests/unit/Search/SearchUIUtilsTest.ts +++ b/tests/unit/Search/SearchUIUtilsTest.ts @@ -1126,6 +1126,7 @@ const transactionReportGroupListItems = [ nonReimbursableSpend: -0, reimbursableSpend: 5000, isAllScanning: false, + isAmountColumnWide: false, primaryAvatar: adminAvatarIcon, secondaryAvatar: policyWorkspaceIcon, avatarType: CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT, @@ -1240,6 +1241,7 @@ const transactionReportGroupListItems = [ nonReimbursableSpend: -0, reimbursableSpend: 5000, isAllScanning: false, + isAmountColumnWide: false, primaryAvatar: adminAvatarIcon, secondaryAvatar: policyWorkspaceIcon, avatarType: CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT, @@ -1353,6 +1355,7 @@ const transactionReportGroupListItems = [ nonReimbursableSpend: 0, reimbursableSpend: 4400, isAllScanning: false, + isAmountColumnWide: false, primaryAvatar: approverAvatarIcon, secondaryAvatar: adminAvatarIcon, avatarType: CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT, @@ -1541,6 +1544,7 @@ const transactionReportGroupListItems = [ nonReimbursableSpend: 0, reimbursableSpend: 0, isAllScanning: false, + isAmountColumnWide: false, primaryAvatar: adminAvatarIcon, secondaryAvatar: policyWorkspaceIcon, avatarType: CONST.REPORT_ACTION_AVATARS.TYPE.SUBSCRIPT, @@ -1710,6 +1714,7 @@ const transactionWithdrawalIDGroupListItems: TransactionWithdrawalIDGroupListIte transactions: [], transactionsQueryJSON: undefined, keyForList: 'group_5', + shouldShowYearWithdrawn: true, }, { bankName: CONST.BANK_NAMES.CITIBANK, @@ -1725,6 +1730,7 @@ const transactionWithdrawalIDGroupListItems: TransactionWithdrawalIDGroupListIte transactions: [], transactionsQueryJSON: undefined, keyForList: 'group_30303030', + shouldShowYearWithdrawn: true, }, ]; @@ -1743,6 +1749,7 @@ const transactionWithdrawalIDGroupListItemsSorted: TransactionWithdrawalIDGroupL transactions: [], transactionsQueryJSON: undefined, keyForList: 'group_5', + shouldShowYearWithdrawn: true, }, { bankName: CONST.BANK_NAMES.CITIBANK, @@ -1758,6 +1765,7 @@ const transactionWithdrawalIDGroupListItemsSorted: TransactionWithdrawalIDGroupL transactions: [], transactionsQueryJSON: undefined, keyForList: 'group_30303030', + shouldShowYearWithdrawn: true, }, ];