Skip to content

Commit 5ffd0f6

Browse files
authored
Merge pull request #97961 from software-mansion-labs/chore/refactor-avatar-tooltips
Refactor avatar tooltips to remove shouldShowTooltip
2 parents 5e89148 + c821559 commit 5ffd0f6

35 files changed

Lines changed: 613 additions & 426 deletions

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {SafeAreaProvider} from 'react-native-safe-area-context';
1111
import '../wdyr';
1212
import {ActionSheetAwareScrollViewProvider} from './components/ActionSheetAwareScrollView';
1313
import ActiveElementRoleProvider from './components/ActiveElementRoleProvider';
14+
import {AvatarTooltipsProvider} from './components/Avatar/tooltips/AvatarTooltipContext';
1415
import ColorSchemeWrapper from './components/ColorSchemeWrapper';
1516
import ComposeProviders from './components/ComposeProviders';
1617
import {CurrencyListContextProvider} from './components/CurrencyListContextProvider';
@@ -127,6 +128,7 @@ function App() {
127128
ModalProvider,
128129
SidePanelContextProvider,
129130
EditingCellProvider,
131+
AvatarTooltipsProvider,
130132
]}
131133
>
132134
<CustomStatusBarAndBackground />

src/components/Avatar/connected/AccountAvatar.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ type AccountAvatarProps = {
2020
/** Size of the avatar */
2121
size?: ValueOf<typeof CONST.AVATAR_SIZE>;
2222

23-
/** Whether to show the tooltip on hover */
24-
shouldShowTooltip?: boolean;
25-
2623
/** Display name used as a fallback for the avatar tooltip */
2724
fallbackDisplayName?: string;
2825

@@ -34,7 +31,7 @@ type AccountAvatarProps = {
3431
* Renders a single account's avatar, resolving the icon from the personal-details context (zero Onyx subscriptions).
3532
* Use whenever exactly one account is rendered. Pass `Avatar/UserAvatar` a `source` instead when the avatar is already resolved.
3633
*/
37-
function AccountAvatar({accountID, size = CONST.AVATAR_SIZE.DEFAULT, shouldShowTooltip = true, fallbackDisplayName, containerStyle}: AccountAvatarProps) {
34+
function AccountAvatar({accountID, size = CONST.AVATAR_SIZE.DEFAULT, fallbackDisplayName, containerStyle}: AccountAvatarProps) {
3835
const personalDetails = usePersonalDetails();
3936
const defaultAvatars = useDefaultAvatars();
4037
const StyleUtils = useStyleUtils();
@@ -46,7 +43,6 @@ function AccountAvatar({accountID, size = CONST.AVATAR_SIZE.DEFAULT, shouldShowT
4643
avatar={icon}
4744
size={size}
4845
containerStyles={containerStyle ?? StyleUtils.getContainerStyles(size)}
49-
shouldShowTooltip={shouldShowTooltip}
5046
fallbackDisplayName={fallbackDisplayName}
5147
/>
5248
);

src/components/Avatar/layouts/DiagonalAvatars.tsx

Lines changed: 35 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1+
import AvatarNamesTooltip from '@components/Avatar/tooltips/AvatarNamesTooltip';
2+
import AvatarTooltip from '@components/Avatar/tooltips/AvatarTooltip';
13
import Text from '@components/Text';
2-
import Tooltip from '@components/Tooltip';
3-
import UserDetailsTooltip from '@components/UserDetailsTooltip';
44

5-
import useLocalize from '@hooks/useLocalize';
65
import useStyleUtils from '@hooks/useStyleUtils';
76
import useTheme from '@hooks/useTheme';
87
import useThemeStyles from '@hooks/useThemeStyles';
98

10-
import {getUserDetailTooltipText} from '@libs/ReportUtils';
11-
129
import CONST from '@src/CONST';
1310

1411
import type {StyleProp, ViewStyle} from 'react-native';
@@ -33,17 +30,14 @@ type DiagonalAvatarsProps = MultipleAvatarsProps & {
3330
/** `DiagonalAvatars` renders two avatars stacked diagonally — the primary in the top-left and the secondary in the bottom-right.
3431
* When more than two `icons` are passed, the secondary slot shows a "+N" overflow count instead of the second avatar.
3532
*/
36-
function DiagonalAvatars({size, shouldShowTooltip, icons, isInReportAction, secondaryAvatarContainerStyle, isHovered = false, fallbackDisplayName}: DiagonalAvatarsProps) {
33+
function DiagonalAvatars({size, icons, isInReportAction, secondaryAvatarContainerStyle, isHovered = false, fallbackDisplayName}: DiagonalAvatarsProps) {
3734
const theme = useTheme();
3835
const styles = useThemeStyles();
3936
const StyleUtils = useStyleUtils();
40-
const {formatPhoneNumber, translate} = useLocalize();
4137

4238
const primaryIcon = icons.at(0);
4339
const secondaryIcon = icons.at(1);
4440

45-
const tooltipTexts = shouldShowTooltip ? icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, translate, icon.name)) : [''];
46-
4741
const {avatarSize, singleAvatarStyleKey} = getDiagonalAvatarSizing(size);
4842
const secondaryAvatarContainerStyles = secondaryAvatarContainerStyle ?? [StyleUtils.getBackgroundAndBorderStyle(isHovered ? theme.activeComponentBG : theme.componentBG)];
4943

@@ -55,61 +49,43 @@ function DiagonalAvatars({size, shouldShowTooltip, icons, isInReportAction, seco
5549
primaryContainerStyle={primaryIcon?.type === CONST.ICON_TYPE_WORKSPACE && StyleUtils.getAvatarBorderRadius(size, primaryIcon.type)}
5650
secondaryContainerStyle={[secondaryAvatarContainerStyles, secondaryIcon?.type === CONST.ICON_TYPE_WORKSPACE && StyleUtils.getAvatarBorderRadius(size, secondaryIcon.type)]}
5751
primary={
58-
<UserDetailsTooltip
59-
accountID={Number(primaryIcon?.id)}
60-
icon={primaryIcon}
61-
fallbackUserDetails={{
62-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
63-
displayName: fallbackDisplayName || primaryIcon?.name,
64-
}}
65-
shouldRender={shouldShowTooltip}
52+
<AvatarTooltip
53+
avatar={primaryIcon}
54+
fallbackDisplayName={fallbackDisplayName}
6655
>
67-
{/* View is necessary for tooltip to show for multiple avatars in LHN */}
68-
<View>
69-
<Avatar
70-
type={primaryIcon?.type ?? CONST.ICON_TYPE_AVATAR}
71-
source={primaryIcon?.source}
72-
name={primaryIcon?.name ?? ''}
73-
avatarID={primaryIcon?.id ?? CONST.DEFAULT_NUMBER_ID}
74-
fallbackIcon={primaryIcon?.fallbackIcon}
75-
fill={primaryIcon?.fill}
76-
size={avatarSize}
77-
imageStyles={styles[singleAvatarStyleKey]}
78-
testID="ReportActionAvatars-MultipleAvatars-MainAvatar"
79-
/>
80-
</View>
81-
</UserDetailsTooltip>
56+
<Avatar
57+
type={primaryIcon?.type ?? CONST.ICON_TYPE_AVATAR}
58+
source={primaryIcon?.source}
59+
name={primaryIcon?.name ?? ''}
60+
avatarID={primaryIcon?.id ?? CONST.DEFAULT_NUMBER_ID}
61+
fallbackIcon={primaryIcon?.fallbackIcon}
62+
fill={primaryIcon?.fill}
63+
size={avatarSize}
64+
imageStyles={styles[singleAvatarStyleKey]}
65+
testID="ReportActionAvatars-MultipleAvatars-MainAvatar"
66+
/>
67+
</AvatarTooltip>
8268
}
8369
secondary={
8470
icons.length === 2 ? (
85-
<UserDetailsTooltip
86-
accountID={Number(secondaryIcon?.id)}
87-
icon={secondaryIcon}
88-
fallbackUserDetails={{
89-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
90-
displayName: fallbackDisplayName || secondaryIcon?.name,
91-
}}
92-
shouldRender={shouldShowTooltip}
71+
<AvatarTooltip
72+
avatar={secondaryIcon}
73+
fallbackDisplayName={fallbackDisplayName}
9374
>
94-
<View>
95-
<Avatar
96-
type={secondaryIcon?.type ?? CONST.ICON_TYPE_AVATAR}
97-
source={secondaryIcon?.source}
98-
name={secondaryIcon?.name ?? ''}
99-
avatarID={secondaryIcon?.id ?? CONST.DEFAULT_NUMBER_ID}
100-
fallbackIcon={secondaryIcon?.fallbackIcon}
101-
fill={secondaryIcon?.fill}
102-
size={avatarSize}
103-
imageStyles={styles[singleAvatarStyleKey]}
104-
testID="ReportActionAvatars-MultipleAvatars-SecondaryAvatar"
105-
/>
106-
</View>
107-
</UserDetailsTooltip>
75+
<Avatar
76+
type={secondaryIcon?.type ?? CONST.ICON_TYPE_AVATAR}
77+
source={secondaryIcon?.source}
78+
name={secondaryIcon?.name ?? ''}
79+
avatarID={secondaryIcon?.id ?? CONST.DEFAULT_NUMBER_ID}
80+
fallbackIcon={secondaryIcon?.fallbackIcon}
81+
fill={secondaryIcon?.fill}
82+
size={avatarSize}
83+
imageStyles={styles[singleAvatarStyleKey]}
84+
testID="ReportActionAvatars-MultipleAvatars-SecondaryAvatar"
85+
/>
86+
</AvatarTooltip>
10887
) : (
109-
<Tooltip
110-
text={tooltipTexts.slice(1).join(', ')}
111-
shouldRender={shouldShowTooltip}
112-
>
88+
<AvatarNamesTooltip avatars={icons.slice(1)}>
11389
<View
11490
style={[styles[singleAvatarStyleKey], styles.alignItemsCenter, styles.justifyContentCenter]}
11591
testID="ReportActionAvatars-MultipleAvatars-LimitReached"
@@ -121,7 +97,7 @@ function DiagonalAvatars({size, shouldShowTooltip, icons, isInReportAction, seco
12197
{`+${icons.length - 1}`}
12298
</Text>
12399
</View>
124-
</Tooltip>
100+
</AvatarNamesTooltip>
125101
)
126102
}
127103
/>

src/components/Avatar/layouts/HorizontalAvatars.tsx

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1+
import AvatarNamesTooltip from '@components/Avatar/tooltips/AvatarNamesTooltip';
2+
import AvatarTooltip from '@components/Avatar/tooltips/AvatarTooltip';
13
import Text from '@components/Text';
2-
import Tooltip from '@components/Tooltip';
3-
import UserDetailsTooltip from '@components/UserDetailsTooltip';
44

5-
import useLocalize from '@hooks/useLocalize';
65
import useStyleUtils from '@hooks/useStyleUtils';
76
import useTheme from '@hooks/useTheme';
87
import useThemeStyles from '@hooks/useThemeStyles';
98

10-
import {getUserDetailTooltipText} from '@libs/ReportUtils';
11-
129
import variables from '@styles/variables';
1310

1411
import CONST from '@src/CONST';
@@ -47,15 +44,13 @@ function HorizontalAvatars({
4744
avatarBorderColor,
4845
overlapDivider = 3,
4946
size,
50-
shouldShowTooltip,
5147
icons,
5248
isInReportAction,
5349
fallbackDisplayName,
5450
}: HorizontalAvatarsProps) {
5551
const theme = useTheme();
5652
const styles = useThemeStyles();
5753
const StyleUtils = useStyleUtils();
58-
const {formatPhoneNumber, translate} = useLocalize();
5954

6055
const oneAvatarSize = StyleUtils.getAvatarStyle(size);
6156
const overlapSize = oneAvatarSize.width / overlapDivider;
@@ -70,8 +65,6 @@ function HorizontalAvatars({
7065
rowIndex === rowCount - 1 ? icons.slice(rowIndex * rowSize) : icons.slice(rowIndex * rowSize, (rowIndex + 1) * rowSize),
7166
);
7267

73-
const tooltipTexts = shouldShowTooltip ? icons.map((icon) => getUserDetailTooltipText(Number(icon.id), formatPhoneNumber, translate, icon.name)) : [''];
74-
7568
return avatarRows.map((avatars, rowIndex) => (
7669
<View
7770
style={avatarContainerStyles}
@@ -80,46 +73,37 @@ function HorizontalAvatars({
8073
testID="ReportActionAvatars-MultipleAvatars-StackedHorizontally-Row"
8174
>
8275
{avatars.slice(0, maxAvatarsPerRow).map((icon, index) => (
83-
<UserDetailsTooltip
76+
<AvatarTooltip
8477
key={`stackedAvatars-${icon.id}`}
85-
accountID={Number(icon.id)}
86-
icon={icon}
87-
fallbackUserDetails={{
88-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
89-
displayName: fallbackDisplayName || icon.name,
90-
}}
91-
shouldRender={shouldShowTooltip}
78+
avatar={icon}
79+
fallbackDisplayName={fallbackDisplayName}
80+
style={[StyleUtils.getHorizontalStackedAvatarStyle(index, overlapSize), StyleUtils.getAvatarBorderRadius(size, icon.type)]}
9281
>
93-
<View style={[StyleUtils.getHorizontalStackedAvatarStyle(index, overlapSize), StyleUtils.getAvatarBorderRadius(size, icon.type)]}>
94-
<Avatar
95-
iconAdditionalStyles={[
96-
StyleUtils.getHorizontalStackedAvatarBorderStyle({
97-
theme,
98-
isHovered,
99-
isPressed,
100-
isInReportAction,
101-
avatarBorderColor,
102-
isActive,
103-
}),
104-
StyleUtils.getAvatarBorderWidth(size),
105-
]}
106-
type={icon.type}
107-
source={icon.source}
108-
name={icon.name ?? ''}
109-
avatarID={icon.id ?? CONST.DEFAULT_NUMBER_ID}
110-
fallbackIcon={icon.fallbackIcon}
111-
fill={icon.fill}
112-
size={size}
113-
testID="ReportActionAvatars-MultipleAvatars-StackedHorizontally-Avatar"
114-
/>
115-
</View>
116-
</UserDetailsTooltip>
82+
<Avatar
83+
iconAdditionalStyles={[
84+
StyleUtils.getHorizontalStackedAvatarBorderStyle({
85+
theme,
86+
isHovered,
87+
isPressed,
88+
isInReportAction,
89+
avatarBorderColor,
90+
isActive,
91+
}),
92+
StyleUtils.getAvatarBorderWidth(size),
93+
]}
94+
type={icon.type}
95+
source={icon.source}
96+
name={icon.name ?? ''}
97+
avatarID={icon.id ?? CONST.DEFAULT_NUMBER_ID}
98+
fallbackIcon={icon.fallbackIcon}
99+
fill={icon.fill}
100+
size={size}
101+
testID="ReportActionAvatars-MultipleAvatars-StackedHorizontally-Avatar"
102+
/>
103+
</AvatarTooltip>
117104
))}
118105
{avatars.length > maxAvatarsPerRow && (
119-
<Tooltip
120-
text={tooltipTexts.slice(avatarRows.length * maxAvatarsPerRow - 1, avatarRows.length * maxAvatarsPerRow + 9).join(', ')}
121-
shouldRender={shouldShowTooltip}
122-
>
106+
<AvatarNamesTooltip avatars={icons.slice(avatarRows.length * maxAvatarsPerRow - 1, avatarRows.length * maxAvatarsPerRow + 9)}>
123107
<View
124108
testID="ReportActionAvatars-MultipleAvatars-StackedHorizontally-LimitReached"
125109
style={[
@@ -144,7 +128,7 @@ function HorizontalAvatars({
144128
>{`+${avatars.length - maxAvatarsPerRow}`}</Text>
145129
</View>
146130
</View>
147-
</Tooltip>
131+
</AvatarNamesTooltip>
148132
)}
149133
</View>
150134
));

src/components/Avatar/layouts/PressableDiagonalAvatars.tsx

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import AvatarTooltip from '@components/Avatar/tooltips/AvatarTooltip';
12
import PressableWithoutFocus from '@components/Pressable/PressableWithoutFocus';
2-
import UserDetailsTooltip from '@components/UserDetailsTooltip';
33

44
import useLocalize from '@hooks/useLocalize';
55
import useStyleUtils from '@hooks/useStyleUtils';
@@ -12,7 +12,6 @@ import type {Icon as IconType} from '@src/types/onyx/OnyxCommon';
1212
import type {ValueOf} from 'type-fest';
1313

1414
import React from 'react';
15-
import {View} from 'react-native';
1615

1716
import Avatar from '..';
1817
import DiagonalAvatarsFrame from './DiagonalAvatarsFrame';
@@ -50,35 +49,26 @@ function PressableDiagonalAvatars({size, primaryAvatar, secondaryAvatar, iconCou
5049
const getAccessibilityLabel = (avatar: IconType) => translate(avatar.type === CONST.ICON_TYPE_WORKSPACE ? 'common.workspaces' : 'common.profile');
5150

5251
const renderPressableAvatar = (avatar: IconType, testID: string) => (
53-
<UserDetailsTooltip
54-
shouldRender
55-
accountID={Number(avatar.id ?? CONST.DEFAULT_NUMBER_ID)}
56-
icon={avatar}
57-
fallbackUserDetails={{
58-
displayName: avatar.name,
59-
}}
60-
>
61-
<View>
62-
<PressableWithoutFocus
63-
onPress={() => onAvatarPress(avatar)}
64-
accessibilityLabel={getAccessibilityLabel(avatar)}
65-
accessibilityRole={CONST.ROLE.BUTTON}
66-
sentryLabel={sentryLabel}
67-
>
68-
<Avatar
69-
type={avatar.type}
70-
source={avatar.source}
71-
name={avatar.name ?? ''}
72-
avatarID={avatar.id ?? CONST.DEFAULT_NUMBER_ID}
73-
fallbackIcon={avatar.fallbackIcon}
74-
fill={avatar.fill}
75-
size={avatarSize}
76-
imageStyles={styles[singleAvatarStyleKey]}
77-
testID={testID}
78-
/>
79-
</PressableWithoutFocus>
80-
</View>
81-
</UserDetailsTooltip>
52+
<AvatarTooltip avatar={avatar}>
53+
<PressableWithoutFocus
54+
onPress={() => onAvatarPress(avatar)}
55+
accessibilityLabel={getAccessibilityLabel(avatar)}
56+
accessibilityRole={CONST.ROLE.BUTTON}
57+
sentryLabel={sentryLabel}
58+
>
59+
<Avatar
60+
type={avatar.type}
61+
source={avatar.source}
62+
name={avatar.name ?? ''}
63+
avatarID={avatar.id ?? CONST.DEFAULT_NUMBER_ID}
64+
fallbackIcon={avatar.fallbackIcon}
65+
fill={avatar.fill}
66+
size={avatarSize}
67+
imageStyles={styles[singleAvatarStyleKey]}
68+
testID={testID}
69+
/>
70+
</PressableWithoutFocus>
71+
</AvatarTooltip>
8272
);
8373

8474
return (

0 commit comments

Comments
 (0)