diff --git a/src/CONST/index.ts b/src/CONST/index.ts index ededd81cf509..11dc881dc7dc 100755 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -2071,6 +2071,7 @@ const CONST = { LHN_SKELETON_VIEW_ITEM_HEIGHT: 64, LHN_VIEWPORT_ITEM_COUNT: 20, SEARCH_SKELETON_VIEW_ITEM_HEIGHT: 108, + SEARCH_SKELETON_VIEW_ITEM_HEIGHT_SMALL: 96, EXPENSIFY_PARTNER_NAME: 'expensify.com', EXPENSIFY_MERCHANT: 'Expensify', EMAIL, @@ -5178,6 +5179,11 @@ const CONST = { EXPENSIFY_LOGO_MARGIN_RATIO: 0.03, }, + ACCESSIBILITY_LABELS: { + COLLAPSE: 'Collapse', + EXPAND: 'Expand', + }, + /** * Acceptable values for the `role` attribute on react native components. * @@ -5332,6 +5338,7 @@ const CONST = { STATUS_TEXT_MAX_LENGTH: 100, DROPDOWN_BUTTON_SIZE: { + EXTRA_SMALL: 'extra-small', LARGE: 'large', MEDIUM: 'medium', SMALL: 'small', diff --git a/src/components/AnimatedCollapsible/index.tsx b/src/components/AnimatedCollapsible/index.tsx index b7d085013c25..d2b5930ebf2a 100644 --- a/src/components/AnimatedCollapsible/index.tsx +++ b/src/components/AnimatedCollapsible/index.tsx @@ -41,9 +41,28 @@ type AnimatedCollapsibleProps = { /** Callback for when the toggle button is pressed */ onPress: () => void; + + /** Whether to show the toggle button */ + shouldShowToggleButton?: boolean; + + /** Style for the border bottom */ + borderBottomStyle?: StyleProp; }; -function AnimatedCollapsible({isExpanded, children, header, duration = 300, style, headerStyle, contentStyle, expandButtonStyle, onPress, disabled = false}: AnimatedCollapsibleProps) { +function AnimatedCollapsible({ + isExpanded, + children, + header, + duration = 300, + style, + headerStyle, + contentStyle, + expandButtonStyle, + onPress, + disabled = false, + shouldShowToggleButton = true, + borderBottomStyle, +}: AnimatedCollapsibleProps) { const theme = useTheme(); const styles = useThemeStyles(); const contentHeight = useSharedValue(0); @@ -91,21 +110,24 @@ function AnimatedCollapsible({isExpanded, children, header, duration = 300, styl {header} - - {({hovered}) => ( - - )} - + {shouldShowToggleButton && ( + + {({hovered}) => ( + + )} + + )} {isExpanded || isRendered ? ( @@ -119,8 +141,8 @@ function AnimatedCollapsible({isExpanded, children, header, duration = 300, styl } }} > - - + + {children} diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 00ff5326241f..3ff23c88a985 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,7 +1,7 @@ import reportsSelector from '@selectors/Attributes'; import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import type {ColorValue, TextStyle} from 'react-native'; +import type {ColorValue, StyleProp, TextStyle, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import useLocalize from '@hooks/useLocalize'; @@ -72,6 +72,15 @@ type AvatarWithDisplayNameProps = { /** Color of the secondary avatar border, usually should match the container background */ avatarBorderColor?: ColorValue; + + /** The style of the custom display name text */ + customDisplayNameStyle?: TextStyle; + + /** The style of the parent navigation subtitle text */ + parentNavigationSubtitleTextStyles?: StyleProp; + + /** The style of the parent navigation status container */ + parentNavigationStatusContainerStyles?: StyleProp; }; function getCustomDisplayName( @@ -161,6 +170,9 @@ function AvatarWithDisplayName({ openParentReportInCurrentTab = false, avatarBorderColor: avatarBorderColorProp, shouldDisplayStatus = false, + customDisplayNameStyle = {}, + parentNavigationSubtitleTextStyles, + parentNavigationStatusContainerStyles = {}, }: AvatarWithDisplayNameProps) { const {localeCompare} = useLocalize(); const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report?.parentReportID}`, {canEvict: false, canBeMissing: !report?.parentReportID}); @@ -269,7 +281,7 @@ function AvatarWithDisplayName({ displayNamesWithTooltips, transactions, shouldUseFullTitle, - [styles.headerText, styles.pre], + [styles.headerText, styles.pre, customDisplayNameStyle], [isAnonymous ? styles.headerAnonymousFooter : styles.headerText, styles.pre], isAnonymous, isMoneyRequestOrReport, @@ -282,6 +294,8 @@ function AvatarWithDisplayName({ pressableStyles={[styles.alignSelfStart, styles.mw100]} openParentReportInCurrentTab={openParentReportInCurrentTab} statusText={statusText} + textStyles={parentNavigationSubtitleTextStyles} + statusTextContainerStyles={parentNavigationStatusContainerStyles} statusTextColor={reportStatusColorStyle?.textColor} statusTextBackgroundColor={reportStatusColorStyle?.backgroundColor} /> diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index b38ae23017ab..b55f4ad6d5bb 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -43,6 +43,9 @@ type BadgeProps = { /** Additional styles from OfflineWithFeedback applied to the row */ style?: StyleProp; + + /** Whether to use XXSmall icon size */ + shouldUseXXSmallIcon?: boolean; }; function Badge({ @@ -57,6 +60,7 @@ function Badge({ icon, iconStyles = [], style, + shouldUseXXSmallIcon = false, }: BadgeProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -89,8 +93,8 @@ function Badge({ {!!icon && ( diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 8ee72401966e..d26295d247cc 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -44,6 +44,9 @@ type ButtonProps = Partial & { /** Any additional styles to pass to the icon wrapper container. */ iconWrapperStyles?: StyleProp; + /** Extra-small sized button */ + extraSmall?: boolean; + /** Small sized button */ small?: boolean; @@ -233,9 +236,10 @@ function Button({ iconWrapperStyles = [], text = '', + extraSmall = false, small = false, large = false, - medium = !small && !large, + medium = !extraSmall && !small && !large, isLoading = false, isDisabled = false, @@ -298,6 +302,7 @@ function Button({ isLoading && styles.opacity0, styles.pointerEventsNone, styles.buttonText, + extraSmall && styles.buttonExtraSmallText, small && styles.buttonSmallText, medium && styles.buttonMediumText, large && styles.buttonLargeText, @@ -348,10 +353,11 @@ function Button({ {!!icon && ( - + >( () => [ styles.button, - StyleUtils.getButtonStyleWithIcon(styles, small, medium, large, !!icon, !!(text?.length > 0), shouldShowRightIcon), + StyleUtils.getButtonStyleWithIcon(styles, extraSmall, small, medium, large, !!icon, !!(text?.length > 0), shouldShowRightIcon), success ? styles.buttonSuccess : undefined, danger ? styles.buttonDanger : undefined, isDisabled && !shouldStayNormalOnDisable ? styles.buttonOpacityDisabled : undefined, @@ -418,6 +426,7 @@ function Button({ shouldRemoveRightBorderRadius, shouldShowRightIcon, small, + extraSmall, styles, success, text, @@ -525,6 +534,7 @@ function Button({ )} diff --git a/src/components/ButtonWithDropdownMenu/index.tsx b/src/components/ButtonWithDropdownMenu/index.tsx index 0915d68c7580..8033ac51c1fd 100644 --- a/src/components/ButtonWithDropdownMenu/index.tsx +++ b/src/components/ButtonWithDropdownMenu/index.tsx @@ -80,6 +80,7 @@ function ButtonWithDropdownMenu({ref, ...props}: ButtonWithDropdownM const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize); const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; const isButtonSizeSmall = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.SMALL; + const isButtonSizeExtraSmall = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.EXTRA_SMALL; const nullCheckRef = (refParam: RefObject) => refParam ?? null; const shouldShowButtonRightIcon = !!options.at(0)?.shouldShowButtonRightIcon; @@ -171,6 +172,7 @@ function ButtonWithDropdownMenu({ref, ...props}: ButtonWithDropdownM isLoading={isLoading} shouldRemoveRightBorderRadius style={isSplitButton ? [styles.flex1, styles.pr0] : {}} + extraSmall={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.EXTRA_SMALL} large={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE} medium={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} small={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.SMALL} @@ -193,6 +195,7 @@ function ButtonWithDropdownMenu({ref, ...props}: ButtonWithDropdownM style={[styles.pl0]} onPress={() => setIsMenuVisible(!isMenuVisible)} shouldRemoveLeftBorderRadius + extraSmall={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.EXTRA_SMALL} large={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE} medium={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} small={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.SMALL} @@ -205,6 +208,7 @@ function ButtonWithDropdownMenu({ref, ...props}: ButtonWithDropdownM style={[ isButtonSizeLarge && styles.dropDownLargeButtonArrowContain, isButtonSizeSmall && shouldUseShortForm ? styles.dropDownSmallButtonArrowContain : styles.dropDownMediumButtonArrowContain, + isButtonSizeExtraSmall && styles.dropDownSmallButtonArrowContain, ]} > ({ref, ...props}: ButtonWithDropdownM isLoading={isLoading} text={selectedItem?.text} onPress={handleSingleOptionPress} + extraSmall={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.EXTRA_SMALL} large={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE} medium={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.MEDIUM} small={buttonSize === CONST.DROPDOWN_BUTTON_SIZE.SMALL} diff --git a/src/components/ButtonWithDropdownMenu/types.ts b/src/components/ButtonWithDropdownMenu/types.ts index 4877c60b4161..ffcdb76f10ad 100644 --- a/src/components/ButtonWithDropdownMenu/types.ts +++ b/src/components/ButtonWithDropdownMenu/types.ts @@ -79,6 +79,9 @@ type ButtonWithDropdownMenuProps = { /** The size of button size */ buttonSize?: ValueOf; + /** Render button in extra-small size */ + extraSmall?: boolean; + /** Should the confirmation button be disabled? */ isDisabled?: boolean; diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 44dc2d71badc..89a0d74b03b5 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -28,6 +28,7 @@ type IconProps = { fill?: string; /** Is small icon */ + extraSmall?: boolean; small?: boolean; /** Is large icon */ @@ -66,6 +67,7 @@ function Icon({ width = variables.iconSizeNormal, height = variables.iconSizeNormal, fill = undefined, + extraSmall = false, small = false, large = false, medium = false, @@ -80,7 +82,7 @@ function Icon({ }: IconProps) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); - const {width: iconWidth, height: iconHeight} = StyleUtils.getIconWidthAndHeightStyle(small, medium, large, width, height, isButtonIcon); + const {width: iconWidth, height: iconHeight} = StyleUtils.getIconWidthAndHeightStyle(extraSmall, small, medium, large, width, height, isButtonIcon); const iconStyles = [StyleUtils.getWidthAndHeightStyle(width ?? 0, height), IconWrapperStyles, styles.pAbsolute, additionalStyles]; const contentSize: Dimensions = {width: iconWidth as number, height: iconHeight as number}; const [canvasSize, setCanvasSize] = useState(); diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx index eed8ffd54f0c..eddf35edd28d 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionItem.tsx @@ -82,7 +82,7 @@ function MoneyRequestReportTransactionItem({ const {translate} = useLocalize(); const styles = useThemeStyles(); // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth, isMediumScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); + const {isSmallScreenWidth, isMediumScreenWidth, isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); const theme = useTheme(); const isPendingDelete = isTransactionPendingDelete(transaction); const pendingAction = getTransactionPendingAction(transaction); @@ -150,7 +150,10 @@ function MoneyRequestReportTransactionItem({ columns={columns} areAllOptionalColumnsHidden={areAllOptionalColumnsHidden} isDisabled={isPendingDelete} - style={[styles.p3]} + style={[styles.p3, isLargeScreenWidth && styles.pr0]} + onButtonPress={() => { + handleOnPress(transaction.transactionID); + }} /> diff --git a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx index fd96771dac87..ae1b3b363e6c 100644 --- a/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx +++ b/src/components/MoneyRequestReportView/MoneyRequestReportTransactionList.tsx @@ -336,7 +336,7 @@ function MoneyRequestReportTransactionList({ return ( <> {!shouldUseNarrowLayout && ( - + { diff --git a/src/components/ParentNavigationSubtitle.tsx b/src/components/ParentNavigationSubtitle.tsx index 201e448a6583..8df332f00d7b 100644 --- a/src/components/ParentNavigationSubtitle.tsx +++ b/src/components/ParentNavigationSubtitle.tsx @@ -1,6 +1,6 @@ import {useRoute} from '@react-navigation/native'; import React from 'react'; -import type {ColorValue, StyleProp, TextStyle} from 'react-native'; +import type {ColorValue, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {View} from 'react-native'; import useHover from '@hooks/useHover'; import useLocalize from '@hooks/useLocalize'; @@ -42,11 +42,17 @@ type ParentNavigationSubtitleProps = { /** The status text of the expense report */ statusText?: string; + /** The style of the text */ + textStyles?: StyleProp; + /** The background color for the status text */ statusTextBackgroundColor?: ColorValue; /** The text color for the status text */ statusTextColor?: ColorValue; + + /** The style of the status text container */ + statusTextContainerStyles?: StyleProp; }; function ParentNavigationSubtitle({ @@ -56,8 +62,10 @@ function ParentNavigationSubtitle({ pressableStyles, openParentReportInCurrentTab = false, statusText, + textStyles, statusTextBackgroundColor, statusTextColor, + statusTextContainerStyles, }: ParentNavigationSubtitleProps) { const currentRoute = useRoute(); const styles = useThemeStyles(); @@ -126,31 +134,32 @@ function ParentNavigationSubtitle({ { backgroundColor: statusTextBackgroundColor, }, + statusTextContainerStyles, ]} > {statusText} )} {!!reportName && ( <> - {`${translate('threads.from')} `} + {`${translate('threads.from')} `} {reportName} )} {!!workspaceName && workspaceName !== reportName && ( - {` ${translate('threads.in')} ${workspaceName}`} + {` ${translate('threads.in')} ${workspaceName}`} )} diff --git a/src/components/ReportSearchHeader/index.tsx b/src/components/ReportSearchHeader/index.tsx index f8fe2ff2d06a..3e44067a098a 100755 --- a/src/components/ReportSearchHeader/index.tsx +++ b/src/components/ReportSearchHeader/index.tsx @@ -1,11 +1,13 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import AvatarWithDisplayName from '@components/AvatarWithDisplayName'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import type ReportSearchHeaderProps from './types'; function ReportSearchHeader({report, style, transactions, avatarBorderColor}: ReportSearchHeaderProps) { const styles = useThemeStyles(); + const {isLargeScreenWidth} = useResponsiveLayout(); const middleContent = useMemo(() => { return ( @@ -17,9 +19,12 @@ function ReportSearchHeader({report, style, transactions, avatarBorderColor}: Re shouldEnableDetailPageNavigation={false} shouldEnableAvatarNavigation={false} avatarBorderColor={avatarBorderColor} + customDisplayNameStyle={styles.fontWeightNormal} + parentNavigationSubtitleTextStyles={[styles.textLineHeightNormal, styles.minHeight4, styles.mt1, !isLargeScreenWidth && styles.textMicro]} + parentNavigationStatusContainerStyles={isLargeScreenWidth ? styles.mt1 : styles.mt0Half} /> ); - }, [report, transactions, avatarBorderColor]); + }, [report, transactions, avatarBorderColor, styles.fontWeightNormal, styles.textLineHeightNormal, styles.minHeight4, styles.mt1, isLargeScreenWidth, styles.textMicro, styles.mt0Half]); return ( - {middleContent} + {middleContent} ); } diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index e846170a0257..838032476bbd 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -918,20 +918,22 @@ function Search({ onDEWModalOpen={handleDEWModalOpen} SearchTableHeader={ !shouldShowTableHeader ? undefined : ( - + + + ) } contentContainerStyle={[styles.pb3, contentContainerStyle]} diff --git a/src/components/SelectionListWithSections/Search/ActionCell.tsx b/src/components/SelectionListWithSections/Search/ActionCell.tsx index 557a8e442ff3..e53a40dd7d88 100644 --- a/src/components/SelectionListWithSections/Search/ActionCell.tsx +++ b/src/components/SelectionListWithSections/Search/ActionCell.tsx @@ -49,6 +49,7 @@ type ActionCellProps = { reportID?: string; hash?: number; amount?: number; + extraSmall?: boolean; }; function ActionCell({ @@ -63,6 +64,7 @@ function ActionCell({ reportID = '', hash, amount, + extraSmall = false, }: ActionCellProps) { const {translate} = useLocalize(); const theme = useTheme(); @@ -102,7 +104,7 @@ function ActionCell({ if (!isChildListItem && ((parentAction !== CONST.SEARCH.ACTION_TYPES.PAID && action === CONST.SEARCH.ACTION_TYPES.PAID) || action === CONST.SEARCH.ACTION_TYPES.DONE)) { return ( - + ); @@ -131,7 +134,8 @@ function ActionCell({ testID={ActionCell.displayName} text={text} onPress={goToItem} - small + small={!extraSmall} + extraSmall={extraSmall} style={[styles.w100]} innerStyles={buttonInnerStyles} link={isChildListItem} @@ -148,7 +152,7 @@ function ActionCell({ return ( = { /** Whether only some transactions are selected */ isIndeterminate?: boolean; + + /** Callback for when the down arrow is clicked */ + onDownArrowClick?: () => void; + + /** Whether the down arrow is expanded */ + isExpanded?: boolean; }; function CardListItemHeader({ @@ -44,9 +54,12 @@ function CardListItemHeader({ canSelectMultiple, isSelectAllChecked, isIndeterminate, + onDownArrowClick, + isExpanded, }: CardListItemHeaderProps) { const theme = useTheme(); const styles = useThemeStyles(); + const {isLargeScreenWidth} = useResponsiveLayout(); const StyleUtils = useStyleUtils(); const {translate, formatPhoneNumber} = useLocalize(); const formattedDisplayName = useMemo(() => formatPhoneNumber(getDisplayNameOrDefault(cardItem)), [cardItem, formatPhoneNumber]); @@ -76,7 +89,7 @@ function CardListItemHeader({ ({ - + + {!isLargeScreenWidth && !!onDownArrowClick && ( + + + {({hovered}) => ( + + )} + + + )} diff --git a/src/components/SelectionListWithSections/Search/MemberListItemHeader.tsx b/src/components/SelectionListWithSections/Search/MemberListItemHeader.tsx index 9fbb385fc889..78133c07812c 100644 --- a/src/components/SelectionListWithSections/Search/MemberListItemHeader.tsx +++ b/src/components/SelectionListWithSections/Search/MemberListItemHeader.tsx @@ -2,10 +2,15 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import Avatar from '@components/Avatar'; import Checkbox from '@components/Checkbox'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import {PressableWithFeedback} from '@components/Pressable'; import type {ListItem, TransactionMemberGroupListItemType} from '@components/SelectionListWithSections/types'; import TextWithTooltip from '@components/TextWithTooltip'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils'; import CONST from '@src/CONST'; @@ -29,6 +34,12 @@ type MemberListItemHeaderProps = { /** Whether only some transactions are selected */ isIndeterminate?: boolean; + + /** Callback for when the down arrow is clicked */ + onDownArrowClick?: () => void; + + /** Whether the down arrow is expanded */ + isExpanded?: boolean; }; function MemberListItemHeader({ @@ -38,8 +49,12 @@ function MemberListItemHeader({ canSelectMultiple, isSelectAllChecked, isIndeterminate, + isExpanded, + onDownArrowClick, }: MemberListItemHeaderProps) { + const theme = useTheme(); const styles = useThemeStyles(); + const {isLargeScreenWidth} = useResponsiveLayout(); const {translate, formatPhoneNumber} = useLocalize(); const [formattedDisplayName, formattedLogin] = useMemo( () => [formatPhoneNumber(getDisplayNameOrDefault(memberItem)), formatPhoneNumber(memberItem.login ?? '')], @@ -70,10 +85,10 @@ function MemberListItemHeader({ /> - + ({ - + + {!isLargeScreenWidth && !!onDownArrowClick && ( + + + {({hovered}) => ( + + )} + + + )} diff --git a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx index 0206bfbc76e8..12a04595e54d 100644 --- a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx +++ b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx @@ -2,6 +2,9 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {ColorValue} from 'react-native'; import Checkbox from '@components/Checkbox'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import {PressableWithFeedback} from '@components/Pressable'; import ReportSearchHeader from '@components/ReportSearchHeader'; import {useSearchContext} from '@components/Search/SearchContext'; import type {ListItem, TransactionReportGroupListItemType} from '@components/SelectionListWithSections/types'; @@ -43,6 +46,12 @@ type ReportListItemHeaderProps = { /** Whether only some transactions are selected */ isIndeterminate?: boolean; + /** Callback for when the down arrow is clicked */ + onDownArrowClick?: () => void; + + /** Whether the down arrow is expanded */ + isExpanded?: boolean; + /** Whether the item is hovered */ isHovered?: boolean; @@ -66,9 +75,6 @@ type FirstRowReportHeaderProps = { /** Callback passed as goToItem in actionCell, triggered by clicking actionButton */ handleOnButtonPress?: () => void; - /** Whether the action button should be displayed */ - shouldShowAction?: boolean; - /** Color of the secondary avatar border, usually should match the container background */ avatarBorderColor?: ColorValue; @@ -77,6 +83,12 @@ type FirstRowReportHeaderProps = { /** Whether only some transactions are selected */ isIndeterminate?: boolean; + + /** Callback for when the down arrow is clicked */ + onDownArrowClick?: () => void; + + /** Whether the down arrow is expanded */ + isExpanded?: boolean; }; function HeaderFirstRow({ @@ -85,13 +97,16 @@ function HeaderFirstRow({ isDisabled, canSelectMultiple, handleOnButtonPress = () => {}, - shouldShowAction = false, avatarBorderColor, isSelectAllChecked, isIndeterminate, + onDownArrowClick, + isExpanded, }: FirstRowReportHeaderProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {isLargeScreenWidth} = useResponsiveLayout(); + const theme = useTheme(); const {total, currency} = useMemo(() => { let reportTotal = reportItem.total ?? 0; @@ -133,13 +148,32 @@ function HeaderFirstRow({ /> - + + {!isLargeScreenWidth && !!onDownArrowClick && ( + + + {({hovered}) => ( + + )} + + + )} - {shouldShowAction && ( + {isLargeScreenWidth && ( ({ reportID={reportItem.reportID} hash={reportItem.hash} amount={reportItem.total} + extraSmall={!isLargeScreenWidth} /> )} @@ -166,6 +201,8 @@ function ReportListItemHeader({ canSelectMultiple, isSelectAllChecked, isIndeterminate, + onDownArrowClick, + isExpanded, isHovered, onDEWModalOpen, }: ReportListItemHeaderProps) { @@ -202,7 +239,13 @@ function ReportListItemHeader({ ); }; return !isLargeScreenWidth ? ( - + + ({ avatarBorderColor={avatarBorderColor} isSelectAllChecked={isSelectAllChecked} isIndeterminate={isIndeterminate} - /> - ) : ( @@ -226,11 +265,12 @@ function ReportListItemHeader({ onCheckboxPress={onCheckboxPress} isDisabled={isDisabled} canSelectMultiple={canSelectMultiple} - shouldShowAction handleOnButtonPress={handleOnButtonPress} avatarBorderColor={avatarBorderColor} isSelectAllChecked={isSelectAllChecked} isIndeterminate={isIndeterminate} + onDownArrowClick={onDownArrowClick} + isExpanded={isExpanded} /> ); diff --git a/src/components/SelectionListWithSections/Search/TotalCell.tsx b/src/components/SelectionListWithSections/Search/TotalCell.tsx index 66b1a2be3108..0f4491809c68 100644 --- a/src/components/SelectionListWithSections/Search/TotalCell.tsx +++ b/src/components/SelectionListWithSections/Search/TotalCell.tsx @@ -16,7 +16,7 @@ function TotalCell({total, currency}: TotalCellProps) { testID={TotalCell.displayName} shouldShowTooltip text={convertToDisplayString(total, currency)} - style={[styles.optionDisplayName, styles.pre, styles.justifyContentCenter, styles.textBold, styles.textAlignRight]} + style={[styles.optionDisplayName, styles.pre, styles.justifyContentCenter, styles.textBold, styles.textAlignRight, styles.fontWeightNormal]} /> ); } diff --git a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx index ae75bf14589c..86e175db0f14 100644 --- a/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionGroupListExpanded.tsx @@ -60,6 +60,13 @@ function TransactionGroupListExpanded({ return transactions; }, [transactions, transactionsVisibleLimit, isExpenseReportType]); + const isLastTransaction = useCallback( + (index: number) => { + return index === visibleTransactions.length - 1; + }, + [visibleTransactions], + ); + const currentColumns = useMemo(() => { if (isExpenseReportType) { return columns ?? []; @@ -87,7 +94,7 @@ function TransactionGroupListExpanded({ const currentOffset = transactionsSnapshotMetadata?.offset ?? 0; const shouldShowLoadingOnSearch = !!(!transactions?.length && transactionsSnapshotMetadata?.isLoading) || currentOffset > 0; const shouldDisplayLoadingIndicator = !isExpenseReportType && !!transactionsSnapshotMetadata?.isLoading && shouldShowLoadingOnSearch; - const {isLargeScreenWidth, shouldUseNarrowLayout} = useResponsiveLayout(); + const {isLargeScreenWidth} = useResponsiveLayout(); const {amountColumnSize, dateColumnSize, taxAmountColumnSize} = useMemo(() => { const isAmountColumnWide = transactions.some((transaction) => transaction.isAmountColumnWide); @@ -156,15 +163,7 @@ function TransactionGroupListExpanded({ return ( <> {isLargeScreenWidth && ( - + ({ /> )} - {visibleTransactions.map((transaction) => ( - - onCheckboxPress?.(transaction as unknown as TItem)} - columns={currentColumns} - onButtonPress={() => { - openReportInRHP(transaction); - }} - style={[styles.noBorderRadius, shouldUseNarrowLayout ? [styles.p3, styles.pt2] : [styles.ph3, styles.pv1Half], isExpenseReportType && styles.pr10]} - isReportItemChild - isInSingleTransactionReport={isInSingleTransactionReport} - areAllOptionalColumnsHidden={areAllOptionalColumnsHidden} - /> - - ))} + {visibleTransactions.map((transaction, index) => { + const shouldShowBottomBorder = !isLastTransaction(index) && !isLargeScreenWidth; + return ( + + onCheckboxPress?.(transaction as unknown as TItem)} + columns={currentColumns} + onButtonPress={() => { + openReportInRHP(transaction); + }} + style={[styles.noBorderRadius, !isLargeScreenWidth ? [styles.p3, styles.pt3] : [styles.pl3, styles.pv1Half], styles.flex1]} + isReportItemChild + isInSingleTransactionReport={isInSingleTransactionReport} + areAllOptionalColumnsHidden={areAllOptionalColumnsHidden} + shouldShowBottomBorder={shouldShowBottomBorder} + /> + + ); + })} {shouldDisplayShowMoreButton && !shouldDisplayLoadingIndicator && (