Skip to content

Commit 51298fc

Browse files
committed
chore: apply details
1 parent 9223b43 commit 51298fc

File tree

7 files changed

+75
-36
lines changed

7 files changed

+75
-36
lines changed

packages/uikit-react-native-foundation/src/ui/Avatar/AvatarStack.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type Props = React.PropsWithChildren<{
1818
styles?: {
1919
borderWidth?: number;
2020
borderColor?: string;
21+
remainsTextColor?: string;
22+
remainsBackgroundColor?: string;
2123
};
2224
}>;
2325

@@ -29,20 +31,26 @@ const AvatarStack = ({
2931
size = DEFAULT_AVATAR_SIZE,
3032
avatarGap = DEFAULT_AVATAR_GAP,
3133
}: Props) => {
32-
const { colors, palette } = useUIKitTheme();
33-
const defaultStyles = { borderWidth: DEFAULT_BORDER_WIDTH, borderColor: colors.background };
34+
const { colors, palette, select } = useUIKitTheme();
35+
const defaultStyles = {
36+
borderWidth: DEFAULT_BORDER_WIDTH,
37+
borderColor: colors.background,
38+
remainsTextColor: colors.onBackground02,
39+
remainsBackgroundColor: select({ light: palette.background100, dark: palette.background600 }),
40+
};
3441
const avatarStyles = { ...defaultStyles, ...styles };
3542

3643
const childrenArray = React.Children.toArray(children).filter((it) => React.isValidElement(it));
3744
const remains = childrenArray.length - maxAvatar;
3845
const shouldRenderRemains = remains > 0;
3946

47+
const actualSize = size + avatarStyles.borderWidth * 2;
4048
const actualGap = avatarGap - avatarStyles.borderWidth;
4149

4250
const renderAvatars = () => {
4351
return childrenArray.slice(0, maxAvatar).map((child, index) =>
4452
React.cloneElement(child as ReactElement, {
45-
size,
53+
size: actualSize,
4654
containerStyle: {
4755
left: actualGap * index,
4856
borderWidth: avatarStyles.borderWidth,
@@ -60,32 +68,32 @@ const AvatarStack = ({
6068
avatarStyles,
6169
{
6270
left: actualGap * maxAvatar,
63-
width: size,
64-
height: size,
65-
borderRadius: size / 2,
71+
width: actualSize,
72+
height: actualSize,
73+
borderRadius: actualSize / 2,
6674
alignItems: 'center',
6775
justifyContent: 'center',
68-
backgroundColor: palette.background100,
76+
backgroundColor: avatarStyles.remainsBackgroundColor,
6977
},
7078
]}
7179
>
72-
<Text style={{ color: colors.onBackground02, fontSize: 8 }} caption4>
80+
<Text style={{ color: avatarStyles.remainsTextColor, fontSize: 8 }} caption4>
7381
{`+${Math.min(remains, DEFAULT_REMAINS_MAX)}`}
7482
</Text>
7583
</View>
7684
);
7785
};
7886

7987
const calculateWidth = () => {
80-
const widthEach = size + actualGap;
88+
const widthEach = actualSize + actualGap;
8189
const avatarCountOffset = shouldRenderRemains ? 1 : 0;
8290
const avatarCount = shouldRenderRemains ? maxAvatar : childrenArray.length;
8391
const count = avatarCount + avatarCountOffset;
84-
return widthEach * count - actualGap;
92+
return widthEach * count + avatarStyles.borderWidth;
8593
};
8694

8795
return (
88-
<View style={[containerStyle, { flexDirection: 'row', width: calculateWidth() }]}>
96+
<View style={[containerStyle, { left: -avatarStyles.borderWidth, flexDirection: 'row', width: calculateWidth() }]}>
8997
{renderAvatars()}
9098
{renderRemainsCount()}
9199
</View>

packages/uikit-react-native-foundation/src/ui/MessageTypingBubble/index.tsx

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,42 @@ import Avatar from '../Avatar';
1111
type Props = {
1212
typingUsers: SendbirdUser[];
1313
containerStyle?: StyleProp<ViewStyle>;
14-
styles?: {};
15-
1614
maxAvatar?: number;
1715
};
1816

1917
const MessageTypingBubble = ({ typingUsers, containerStyle, maxAvatar }: Props) => {
18+
const { select, palette, colors } = useUIKitTheme();
19+
2020
if (typingUsers.length === 0) return null;
2121

2222
return (
2323
<Box flexDirection={'row'} justifyContent={'flex-start'} alignItems={'center'} style={containerStyle}>
24-
<Avatar.Stack size={26} maxAvatar={maxAvatar} containerStyle={{ marginRight: 12 }}>
24+
<Avatar.Stack
25+
size={26}
26+
maxAvatar={maxAvatar}
27+
styles={{
28+
remainsTextColor: colors.onBackground02,
29+
remainsBackgroundColor: select({ light: palette.background100, dark: palette.background400 }),
30+
}}
31+
containerStyle={{ marginRight: 12 }}
32+
>
2533
{typingUsers.map((user, index) => (
2634
<Avatar key={index} uri={user.profileUrl} />
2735
))}
2836
</Avatar.Stack>
29-
<TypingDots />
37+
<TypingDots
38+
dotColor={select({ light: palette.background100, dark: palette.background400 })}
39+
backgroundColor={colors.onBackground02}
40+
/>
3041
</Box>
3142
);
3243
};
3344

34-
const TypingDots = () => {
35-
const { select, palette, colors } = useUIKitTheme();
45+
type TypingDotsProps = {
46+
dotColor: string;
47+
backgroundColor: string;
48+
};
49+
const TypingDots = ({ dotColor, backgroundColor }: TypingDotsProps) => {
3650
const animation = useRef(new Animated.Value(0)).current;
3751
const dots = matrix.map(([timeline, scale, opacity]) => [
3852
animation.interpolate({ inputRange: timeline, outputRange: scale, extrapolate: 'clamp' }),
@@ -55,7 +69,7 @@ const TypingDots = () => {
5569
borderRadius={16}
5670
paddingHorizontal={12}
5771
height={34}
58-
backgroundColor={select({ light: palette.background100, dark: palette.background600 })}
72+
backgroundColor={dotColor}
5973
>
6074
{dots.map(([scale, opacity], index) => {
6175
return (
@@ -67,7 +81,7 @@ const TypingDots = () => {
6781
marginRight: index === dots.length - 1 ? 0 : 6,
6882
opacity: opacity,
6983
transform: [{ scale: scale }],
70-
backgroundColor: colors.onBackground02,
84+
backgroundColor: backgroundColor,
7185
},
7286
]}
7387
/>

packages/uikit-react-native/src/components/GroupChannelMessageRenderer/index.tsx

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
4444
focused,
4545
prevMessage,
4646
nextMessage,
47-
isFirstItem,
4847
}) => {
49-
const { typingUsers } = useContext(GroupChannelContexts.TypingIndicator);
5048
const playerUnsubscribes = useRef<(() => void)[]>([]);
5149
const { palette } = useUIKitTheme();
5250
const { sbOptions, currentUser, mentionManager } = useSendbirdChat();
@@ -293,19 +291,25 @@ const GroupChannelMessageRenderer: GroupChannelProps['Fragment']['renderMessage'
293291
}
294292
});
295293

296-
const renderTypingBubble = () => {
297-
if (!isFirstItem) return null;
298-
if (!sbOptions.uikit.groupChannel.channel.enableTypingIndicator) return null;
299-
if (!sbOptions.uikit.groupChannel.channel.typingIndicatorTypes.has('bubble')) return null;
300-
301-
return <MessageTypingBubble typingUsers={typingUsers} containerStyle={{ marginTop: 20 }} />;
302-
};
303-
304294
return (
305295
<Box paddingHorizontal={16} marginBottom={messageGap}>
306296
<GroupChannelMessageDateSeparator message={message} prevMessage={prevMessage} />
307297
<GroupChannelMessageFocusAnimation focused={focused}>{renderMessage()}</GroupChannelMessageFocusAnimation>
308-
{renderTypingBubble()}
298+
</Box>
299+
);
300+
};
301+
302+
export const GroupChannelMessageTypingBubble = () => {
303+
const { sbOptions } = useSendbirdChat();
304+
const { typingUsers } = useContext(GroupChannelContexts.TypingIndicator);
305+
306+
if (typingUsers.length === 0) return null;
307+
if (!sbOptions.uikit.groupChannel.channel.enableTypingIndicator) return null;
308+
if (!sbOptions.uikit.groupChannel.channel.typingIndicatorTypes.has('bubble')) return null;
309+
310+
return (
311+
<Box paddingHorizontal={16} marginTop={4} marginBottom={16}>
312+
<MessageTypingBubble typingUsers={typingUsers} />
309313
</Box>
310314
);
311315
};

packages/uikit-react-native/src/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ export const MESSAGE_FOCUS_ANIMATION_DELAY = 250;
55
export const UNKNOWN_USER_ID = '##__USER_ID_IS_NOT_PROVIDED__##';
66
export const VOICE_MESSAGE_META_ARRAY_DURATION_KEY = 'KEY_VOICE_MESSAGE_DURATION';
77
export const VOICE_MESSAGE_META_ARRAY_MESSAGE_TYPE_KEY = 'KEY_INTERNAL_MESSAGE_TYPE';
8+
9+
export enum TypingIndicatorType {
10+
Text = 'text',
11+
Bubble = 'bubble',
12+
}

packages/uikit-react-native/src/fragments/createGroupChannelFragment.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
22

33
import { ReplyType } from '@sendbird/chat/message';
44
import { useGroupChannelMessages } from '@sendbird/uikit-chat-hooks';
5+
import { Box } from '@sendbird/uikit-react-native-foundation';
56
import {
67
NOOP,
78
PASS,
@@ -14,7 +15,9 @@ import {
1415
useRefTracker,
1516
} from '@sendbird/uikit-utils';
1617

17-
import GroupChannelMessageRenderer from '../components/GroupChannelMessageRenderer';
18+
import GroupChannelMessageRenderer, {
19+
GroupChannelMessageTypingBubble,
20+
} from '../components/GroupChannelMessageRenderer';
1821
import NewMessagesButton from '../components/NewMessagesButton';
1922
import ScrollToBottomButton from '../components/ScrollToBottomButton';
2023
import StatusComposition from '../components/StatusComposition';
@@ -123,8 +126,13 @@ const createGroupChannelFragment = (initModule?: Partial<GroupChannelModule>): G
123126
}, []);
124127

125128
const renderItem: GroupChannelProps['MessageList']['renderMessage'] = useFreshCallback((props) => {
126-
if (renderMessage) return renderMessage(props);
127-
return <GroupChannelMessageRenderer {...props} />;
129+
const content = renderMessage ? renderMessage(props) : <GroupChannelMessageRenderer {...props} />;
130+
return (
131+
<Box>
132+
{content}
133+
{props.isFirstItem && !hasNext() && <GroupChannelMessageTypingBubble />}
134+
</Box>
135+
);
128136
});
129137

130138
const memoizedFlatListProps = useMemo(

packages/uikit-react-native/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export { default as SendbirdUIKitContainer, SendbirdUIKit } from './containers/S
134134
export type { SendbirdUIKitContainerProps } from './containers/SendbirdUIKitContainer';
135135
export { default as SBUError } from './libs/SBUError';
136136
export { default as SBUUtils } from './libs/SBUUtils';
137-
137+
export { TypingIndicatorType } from './constants';
138138
export * from './types';
139139

140140
Logger.setLogLevel(__DEV__ ? 'warn' : 'none');

sample/src/App.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { DarkTheme, DefaultTheme, NavigationContainer } from '@react-navigation/
44
import React, { useEffect } from 'react';
55
import { AppState } from 'react-native';
66

7-
import { SendbirdUIKitContainer, useSendbirdChat } from '@sendbird/uikit-react-native';
7+
import { SendbirdUIKitContainer, TypingIndicatorType, useSendbirdChat } from '@sendbird/uikit-react-native';
88
import { DarkUIKitTheme, LightUIKitTheme } from '@sendbird/uikit-react-native-foundation';
99

1010
// import LogView from './components/LogView';
@@ -60,7 +60,7 @@ const App = () => {
6060
},
6161
groupChannel: {
6262
enableMention: true,
63-
typingIndicatorTypes: new Set(['bubble']),
63+
typingIndicatorTypes: new Set([TypingIndicatorType.Text, TypingIndicatorType.Bubble]),
6464
},
6565
groupChannelList: {
6666
enableTypingIndicator: true,

0 commit comments

Comments
 (0)