Skip to content

Commit 17f3d2e

Browse files
authored
Merge pull request Expensify#64971 from s77rt/add-CardListItemHeader
[No QA] Search: Add CardListItemHeader
2 parents f2bb17c + 7832a53 commit 17f3d2e

8 files changed

Lines changed: 161 additions & 34 deletions

File tree

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React, {useMemo} from 'react';
2+
import {View} from 'react-native';
3+
import Checkbox from '@components/Checkbox';
4+
import type {ListItem, TransactionCardGroupListItemType} from '@components/SelectionList/types';
5+
import SubscriptAvatar from '@components/SubscriptAvatar';
6+
import type {SubIcon} from '@components/SubscriptAvatar';
7+
import TextWithTooltip from '@components/TextWithTooltip';
8+
import useLocalize from '@hooks/useLocalize';
9+
import useStyleUtils from '@hooks/useStyleUtils';
10+
import useTheme from '@hooks/useTheme';
11+
import useThemeIllustrations from '@hooks/useThemeIllustrations';
12+
import useThemeStyles from '@hooks/useThemeStyles';
13+
import {getCardFeedIcon} from '@libs/CardUtils';
14+
import {formatPhoneNumber} from '@libs/LocalePhoneNumber';
15+
import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
16+
import variables from '@styles/variables';
17+
import CONST from '@src/CONST';
18+
import type {CompanyCardFeed} from '@src/types/onyx/CardFeeds';
19+
import type {Icon} from '@src/types/onyx/OnyxCommon';
20+
21+
type CardListItemHeaderProps<TItem extends ListItem> = {
22+
/** The card currently being looked at */
23+
card: TransactionCardGroupListItemType;
24+
25+
/** Callback to fire when a checkbox is pressed */
26+
onCheckboxPress?: (item: TItem) => void;
27+
28+
/** Whether this section items disabled for selection */
29+
isDisabled?: boolean | null;
30+
31+
/** Whether the item is hovered */
32+
isHovered?: boolean;
33+
34+
/** Whether the item is focused */
35+
isFocused?: boolean;
36+
37+
/** Whether selecting multiple transactions at once is allowed */
38+
canSelectMultiple: boolean | undefined;
39+
};
40+
41+
function CardListItemHeader<TItem extends ListItem>({card: cardItem, onCheckboxPress, isDisabled, isHovered, isFocused, canSelectMultiple}: CardListItemHeaderProps<TItem>) {
42+
const theme = useTheme();
43+
const styles = useThemeStyles();
44+
const StyleUtils = useStyleUtils();
45+
const {translate} = useLocalize();
46+
const illustrations = useThemeIllustrations();
47+
48+
const formattedDisplayName = useMemo(() => formatPhoneNumber(getDisplayNameOrDefault(cardItem)), [cardItem]);
49+
50+
const [memberAvatar, cardIcon] = useMemo(() => {
51+
const avatar: Icon = {
52+
source: cardItem.avatar,
53+
type: CONST.ICON_TYPE_AVATAR,
54+
name: formattedDisplayName,
55+
id: cardItem.accountID,
56+
};
57+
58+
const icon: SubIcon = {
59+
source: getCardFeedIcon(cardItem.bank as CompanyCardFeed, illustrations),
60+
width: variables.cardAvatarWidth,
61+
height: variables.cardAvatarHeight,
62+
};
63+
64+
return [avatar, icon];
65+
}, [formattedDisplayName, illustrations, cardItem]);
66+
67+
const backgroundColor =
68+
StyleUtils.getItemBackgroundColorStyle(!!cardItem.isSelected, !!isFocused || !!isHovered, !!isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ??
69+
theme.highlightBG;
70+
71+
// s77rt add total cell, action cell and collapse/expand button
72+
73+
return (
74+
<View>
75+
<View style={[styles.pv1Half, styles.ph3, styles.flexRow, styles.alignItemsCenter, styles.justifyContentStart]}>
76+
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mnh40, styles.flex1, styles.gap3]}>
77+
{!!canSelectMultiple && (
78+
<Checkbox
79+
onPress={() => onCheckboxPress?.(cardItem as unknown as TItem)}
80+
isChecked={cardItem.isSelected}
81+
disabled={!!isDisabled || cardItem.isDisabledCheckbox}
82+
accessibilityLabel={translate('common.select')}
83+
/>
84+
)}
85+
<View style={[styles.flexRow, styles.gap3]}>
86+
<SubscriptAvatar
87+
mainAvatar={memberAvatar}
88+
subscriptIcon={cardIcon}
89+
backgroundColor={backgroundColor}
90+
noMargin
91+
/>
92+
<View style={[styles.gapHalf]}>
93+
<TextWithTooltip
94+
text={formattedDisplayName}
95+
style={[styles.optionDisplayName, styles.sidebarLinkTextBold, styles.pre]}
96+
/>
97+
<TextWithTooltip
98+
text={`${cardItem.cardName}${cardItem.lastFourPAN}`}
99+
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
100+
/>
101+
</View>
102+
</View>
103+
</View>
104+
</View>
105+
<View style={[styles.pv2, styles.ph3]}>
106+
<View style={[styles.borderBottom]} />
107+
</View>
108+
</View>
109+
);
110+
}
111+
112+
CardListItemHeader.displayName = 'CardListItemHeader';
113+
114+
export default CardListItemHeader;

src/components/SelectionList/Search/MemberListItemHeader.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, {useMemo} from 'react';
22
import {View} from 'react-native';
33
import Avatar from '@components/Avatar';
44
import Checkbox from '@components/Checkbox';
@@ -29,8 +29,7 @@ function MemberListItemHeader<TItem extends ListItem>({member: memberItem, onChe
2929
const styles = useThemeStyles();
3030
const {translate} = useLocalize();
3131

32-
const formattedDisplayName = formatPhoneNumber(getDisplayNameOrDefault(memberItem));
33-
const formattedLogin = formatPhoneNumber(memberItem.login ?? '');
32+
const [formattedDisplayName, formattedLogin] = useMemo(() => [formatPhoneNumber(getDisplayNameOrDefault(memberItem)), formatPhoneNumber(memberItem.login ?? '')], [memberItem]);
3433

3534
// s77rt add total cell, action cell and collapse/expand button
3635

src/components/SelectionList/Search/TransactionGroupListItem.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {SearchGroupBy} from '@components/Search/types';
66
import BaseListItem from '@components/SelectionList/BaseListItem';
77
import type {
88
ListItem,
9+
TransactionCardGroupListItemType,
910
TransactionGroupListItemProps,
1011
TransactionGroupListItemType,
1112
TransactionListItemType,
@@ -26,6 +27,7 @@ import {setActiveTransactionThreadIDs} from '@userActions/TransactionThreadNavig
2627
import CONST from '@src/CONST';
2728
import ONYXKEYS from '@src/ONYXKEYS';
2829
import ROUTES from '@src/ROUTES';
30+
import CardListItemHeader from './CardListItemHeader';
2931
import MemberListItemHeader from './MemberListItemHeader';
3032
import ReportListItemHeader from './ReportListItemHeader';
3133

@@ -134,8 +136,14 @@ function TransactionGroupListItem<TItem extends ListItem>({
134136
/>
135137
),
136138
[CONST.SEARCH.GROUP_BY.CARDS]: (
137-
// s77rt CardListItemHeader
138-
<View />
139+
<CardListItemHeader
140+
card={groupItem as TransactionCardGroupListItemType}
141+
onCheckboxPress={onCheckboxPress}
142+
isDisabled={isDisabledOrEmpty}
143+
isHovered={isHovered}
144+
isFocused={isFocused}
145+
canSelectMultiple={canSelectMultiple}
146+
/>
139147
),
140148
};
141149

src/components/SubscriptAvatar.tsx

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, {memo} from 'react';
2-
import type {ColorValue, StyleProp, ViewStyle} from 'react-native';
2+
import type {ColorValue} from 'react-native';
33
import {View} from 'react-native';
44
import type {ValueOf} from 'type-fest';
55
import useStyleUtils from '@hooks/useStyleUtils';
@@ -47,21 +47,9 @@ type SubscriptAvatarProps = {
4747

4848
/** Whether to show the tooltip */
4949
showTooltip?: boolean;
50-
51-
/** Additional style for container of subscription icon */
52-
subscriptionContainerAdditionalStyles?: StyleProp<ViewStyle>;
5350
};
5451

55-
function SubscriptAvatar({
56-
mainAvatar,
57-
secondaryAvatar,
58-
subscriptIcon,
59-
size = CONST.AVATAR_SIZE.DEFAULT,
60-
backgroundColor,
61-
noMargin = false,
62-
showTooltip = true,
63-
subscriptionContainerAdditionalStyles = undefined,
64-
}: SubscriptAvatarProps) {
52+
function SubscriptAvatar({mainAvatar, secondaryAvatar, subscriptIcon, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) {
6553
const theme = useTheme();
6654
const styles = useThemeStyles();
6755
const StyleUtils = useStyleUtils();
@@ -123,15 +111,12 @@ function SubscriptAvatar({
123111
<View
124112
style={[
125113
size === CONST.AVATAR_SIZE.SMALL_NORMAL ? styles.flex1 : {},
126-
StyleUtils.getAvatarBorderStyle(CONST.AVATAR_SIZE.SMALL, CONST.ICON_TYPE_AVATAR),
127-
StyleUtils.getAvatarBorderWidth(CONST.AVATAR_SIZE.SMALL),
128114
// Nullish coalescing thinks that empty strings are truthy, thus I'm using OR operator
129115
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
130116
StyleUtils.getBorderColorStyle(backgroundColor || theme.sidebar),
131-
styles.subscriptIcon,
117+
StyleUtils.getAvatarSubscriptIconContainerStyle(subscriptIcon.width, subscriptIcon.height),
132118
styles.dFlex,
133119
styles.justifyContentCenter,
134-
subscriptionContainerAdditionalStyles,
135120
]}
136121
// Hover on overflowed part of icon will not work on Electron if dragArea is true
137122
// https://stackoverflow.com/questions/56338939/hover-in-css-is-not-working-with-electron
@@ -142,7 +127,7 @@ function SubscriptAvatar({
142127
width={subscriptIcon.width}
143128
height={subscriptIcon.height}
144129
additionalStyles={styles.alignSelfCenter}
145-
fill={subscriptIcon.fill ?? theme.icon}
130+
fill={subscriptIcon.fill}
146131
/>
147132
</View>
148133
)}
@@ -153,4 +138,4 @@ function SubscriptAvatar({
153138
SubscriptAvatar.displayName = 'SubscriptAvatar';
154139

155140
export default memo(SubscriptAvatar);
156-
export type {SubscriptAvatarProps};
141+
export type {SubIcon, SubscriptAvatarProps};

src/styles/index.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2661,15 +2661,6 @@ const styles = (theme: ThemeColors) =>
26612661
marginRight: variables.avatarChatSpacing - 4,
26622662
},
26632663

2664-
subscriptIcon: {
2665-
position: 'absolute',
2666-
bottom: -4,
2667-
right: -4,
2668-
width: 20,
2669-
height: 20,
2670-
backgroundColor: theme.buttonDefaultBG,
2671-
},
2672-
26732664
borderTop: {
26742665
borderTopWidth: variables.borderTopWidth,
26752666
borderColor: theme.border,

src/styles/utils/index.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,29 @@ function getAvatarBorderStyle(size: AvatarSizeName, type: string): ViewStyle {
277277
};
278278
}
279279

280+
/**
281+
* Returns the avatar subscript icon container styles
282+
*/
283+
function getAvatarSubscriptIconContainerStyle(iconWidth = 16, iconHeight = 16): ViewStyle {
284+
const borderWidth = 2;
285+
286+
// The width of the container is the width of the icon + 2x border width (left and right)
287+
const containerWidth = iconWidth + 2 * borderWidth;
288+
// The height of the container is the height of the icon + 2x border width (top and bottom)
289+
const containerHeight = iconHeight + 2 * borderWidth;
290+
291+
return {
292+
overflow: 'hidden',
293+
position: 'absolute',
294+
bottom: -4,
295+
right: -4,
296+
borderWidth,
297+
borderRadius: 2 + borderWidth,
298+
width: containerWidth,
299+
height: containerHeight,
300+
};
301+
}
302+
280303
/**
281304
* Helper method to return workspace avatar color styles
282305
*/
@@ -1219,6 +1242,7 @@ const staticStyleUtils = {
12191242
getAvatarExtraFontSizeStyle,
12201243
getAvatarSize,
12211244
getAvatarWidthStyle,
1245+
getAvatarSubscriptIconContainerStyle,
12221246
getBackgroundAndBorderStyle,
12231247
getBackgroundColorStyle,
12241248
getBackgroundColorWithOpacityStyle,

src/styles/variables.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ export default {
259259
cardMiniatureHeight: 13,
260260
cardMiniatureBorderRadius: 2,
261261

262+
cardAvatarWidth: 20,
263+
cardAvatarHeight: 13,
264+
262265
cardNameWidth: 156,
263266
updateAnimationW: 390,
264267
updateAnimationH: 240,

src/types/onyx/SearchResults.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,9 @@ type SearchTask = {
458458
/** Model of card search result */
459459
// s77rt sync with BE
460460
type SearchCard = {
461+
/** Bank name */
462+
bank: string;
463+
461464
/** Last four Primary Account Number digits */
462465
lastFourPAN: string;
463466

0 commit comments

Comments
 (0)