Skip to content

Commit 6c0b195

Browse files
authored
fix: online indicator misaligned for 2xl avatar (#3678)
## 🎯 Goal The online indicator was misaligned for `2xl` avatars. The issue was that we used a static offset, not a dynamic computed from avatar and online indicator size. ## 🛠 Implementation details Moving from static offset to computed offset affects all avatar sizes with online indicators. The below list contains the changes for other sizes. They're pretty small, but I checked all occurrences, and they still look good: <img width="458" height="201" alt="Screenshot 2026-06-26 at 8 24 43" src="https://github.com/user-attachments/assets/ee387560-a6aa-4785-b3d1-9e8d969b8c7f" /> ## 🎨 UI Changes Before: <img width="337" height="663" alt="Screenshot 2026-06-26 at 7 52 54" src="https://github.com/user-attachments/assets/21b399c9-dc40-42cf-a293-d03e01d79076" /> After: <img width="564" height="1240" alt="Screenshot 2026-06-26 at 8 16 32" src="https://github.com/user-attachments/assets/4c4f93d9-d5d8-451b-8e6c-32558fb765b2" /> ## 🧪 Testing <!-- Explain how this change can be tested (or why it can't be tested) --> ## ☑️ Checklist - [ ] I have signed the [Stream CLA](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) (required) - [ ] PR targets the `develop` branch - [ ] Documentation is updated - [ ] New code is tested in main example apps, including all possible scenarios - [ ] SampleApp iOS and Android - [ ] Expo iOS and Android
1 parent 631ac82 commit 6c0b195

3 files changed

Lines changed: 66 additions & 29 deletions

File tree

package/src/components/ui/Avatar/UserAvatar.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native';
55
import { UserResponse } from 'stream-chat';
66

77
import { Avatar, AvatarProps } from './Avatar';
8-
import { fontSizes, iconSizes, indicatorSizes, numberOfInitials } from './constants';
8+
import {
9+
fontSizes,
10+
iconSizes,
11+
indicatorPositions,
12+
indicatorSizes,
13+
numberOfInitials,
14+
} from './constants';
915

1016
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
1117
import { PeopleIcon } from '../../../icons/users';
@@ -57,7 +63,7 @@ export const UserAvatar = (props: UserAvatarProps) => {
5763
style={style}
5864
/>
5965
{showOnlineIndicator ? (
60-
<View style={styles.onlineIndicatorWrapper}>
66+
<View style={[styles.onlineIndicatorWrapper, indicatorPositions[size]]}>
6167
<OnlineIndicator online={true} size={indicatorSizes[size]} />
6268
</View>
6369
) : null}
@@ -71,8 +77,6 @@ const useStyles = () => {
7177
StyleSheet.create({
7278
onlineIndicatorWrapper: {
7379
position: 'absolute',
74-
right: -2,
75-
top: -2,
7680
},
7781
}),
7882
[],

package/src/components/ui/Avatar/constants.ts

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,45 @@ const indicatorSizes: Record<UserAvatarProps['size'], OnlineIndicatorProps['size
4141
xs: 'sm',
4242
};
4343

44+
const onlineIndicatorSizes: Record<
45+
OnlineIndicatorProps['size'],
46+
{ borderWidth: number; height: number; width: number }
47+
> = {
48+
xl: {
49+
borderWidth: 2,
50+
height: 16,
51+
width: 16,
52+
},
53+
lg: {
54+
borderWidth: 2,
55+
height: 14,
56+
width: 14,
57+
},
58+
md: {
59+
borderWidth: 2,
60+
height: 12,
61+
width: 12,
62+
},
63+
sm: {
64+
borderWidth: 1,
65+
height: 8,
66+
width: 8,
67+
},
68+
};
69+
70+
// Anchors the presence dot on the avatar's circular edge at 45°:
71+
// offset = avatarWidth / 2 * (1 - Math.SQRT1_2) - indicatorDiameter / 2 (rounded to px)
72+
const indicatorPositions = (Object.keys(avatarSizes) as UserAvatarProps['size'][]).reduce(
73+
(acc, size) => {
74+
const avatarDiameter = avatarSizes[size].width;
75+
const indicatorDiameter = onlineIndicatorSizes[indicatorSizes[size]].width;
76+
const offset = Math.round((avatarDiameter / 2) * (1 - Math.SQRT1_2) - indicatorDiameter / 2);
77+
acc[size] = { right: offset, top: offset };
78+
return acc;
79+
},
80+
{} as Record<UserAvatarProps['size'], { right: number; top: number }>,
81+
);
82+
4483
const iconSizes: Record<UserAvatarProps['size'], number> = {
4584
xs: 10,
4685
sm: 12,
@@ -99,4 +138,12 @@ const numberOfInitials: Record<UserAvatarProps['size'], number> = {
99138
'2xl': 2,
100139
};
101140

102-
export { indicatorSizes, iconSizes, fontSizes, numberOfInitials, avatarSizes };
141+
export {
142+
indicatorSizes,
143+
onlineIndicatorSizes,
144+
indicatorPositions,
145+
iconSizes,
146+
fontSizes,
147+
numberOfInitials,
148+
avatarSizes,
149+
};

package/src/components/ui/Badge/OnlineIndicator.tsx

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,24 @@ import { StyleSheet, View } from 'react-native';
33

44
import { useTheme } from '../../../contexts/themeContext/ThemeContext';
55
import { primitives } from '../../../theme';
6+
import { onlineIndicatorSizes } from '../Avatar/constants';
67

78
export type OnlineIndicatorProps = {
89
online: boolean;
910
size: 'xl' | 'lg' | 'sm' | 'md';
1011
};
1112

12-
const sizes = {
13-
xl: {
14-
borderWidth: 2,
15-
height: 16,
16-
width: 16,
17-
},
18-
lg: {
19-
borderWidth: 2,
20-
height: 14,
21-
width: 14,
22-
},
23-
md: {
24-
borderWidth: 2,
25-
height: 12,
26-
width: 12,
27-
},
28-
sm: {
29-
borderWidth: 1,
30-
height: 8,
31-
width: 8,
32-
},
33-
};
34-
3513
export const OnlineIndicator = ({ online, size = 'md' }: OnlineIndicatorProps) => {
3614
const styles = useStyles();
37-
return <View style={[styles.indicator, sizes[size], online ? styles.online : styles.offline]} />;
15+
return (
16+
<View
17+
style={[
18+
styles.indicator,
19+
onlineIndicatorSizes[size],
20+
online ? styles.online : styles.offline,
21+
]}
22+
/>
23+
);
3824
};
3925

4026
const useStyles = () => {

0 commit comments

Comments
 (0)