From b57bbc9fad9c10c6e6d87089fef73d7dc95dcab7 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 24 Feb 2026 15:08:36 +0100 Subject: [PATCH 1/6] fix: add proper dark mode support and fix odd cases --- .../SampleApp/src/hooks/useStreamChatTheme.ts | 10 -- .../MessageSimple/utils/renderText.tsx | 14 +- .../MessageList/MessageFlashList.tsx | 144 +++++++++-------- .../components/MessageList/MessageList.tsx | 147 ++++++++++-------- .../contexts/themeContext/ThemeContext.tsx | 23 ++- .../src/contexts/themeContext/utils/theme.ts | 14 +- package/src/theme/index.ts | 10 +- 7 files changed, 207 insertions(+), 155 deletions(-) diff --git a/examples/SampleApp/src/hooks/useStreamChatTheme.ts b/examples/SampleApp/src/hooks/useStreamChatTheme.ts index 8a0ad4f44f..bf195a9b57 100644 --- a/examples/SampleApp/src/hooks/useStreamChatTheme.ts +++ b/examples/SampleApp/src/hooks/useStreamChatTheme.ts @@ -61,16 +61,6 @@ const getChatStyle = (colorScheme: ColorSchemeName): DeepPartial => ({ white_smoke: '#F2F2F2', white_snow: '#FCFCFC', }, - ...(colorScheme === 'dark' - ? { - messageSimple: { - content: { - receiverMessageBackgroundColor: '#2D2F2F', - senderMessageBackgroundColor: '#101418', - }, - }, - } - : {}), }); export const useStreamChatTheme = () => { diff --git a/package/src/components/Message/MessageSimple/utils/renderText.tsx b/package/src/components/Message/MessageSimple/utils/renderText.tsx index cf30b522af..7ea8207814 100644 --- a/package/src/components/Message/MessageSimple/utils/renderText.tsx +++ b/package/src/components/Message/MessageSimple/utils/renderText.tsx @@ -34,7 +34,7 @@ import type { MessageContextValue } from '../../../../contexts/messageContext/Me import type { Colors, MarkdownStyle } from '../../../../contexts/themeContext/utils/theme'; import { primitives } from '../../../../theme'; -import { escapeRegExp } from '../../../../utils/utils'; +import { escapeRegExp, hasOnlyEmojis } from '../../../../utils/utils'; type ReactNodeOutput = NodeOutput; type ReactOutput = Output; @@ -125,14 +125,14 @@ const defaultMarkdownStyles: MarkdownStyle = { }, paragraph: { marginBottom: 8, - fontSize: primitives.typographyFontSizeMd, - lineHeight: primitives.typographyLineHeightNormal, + // fontSize: primitives.typographyFontSizeMd, + // lineHeight: primitives.typographyLineHeightNormal, marginTop: 8, }, paragraphCenter: { marginBottom: 8, - fontSize: primitives.typographyFontSizeMd, - lineHeight: primitives.typographyLineHeightNormal, + // fontSize: primitives.typographyFontSizeMd, + // lineHeight: primitives.typographyLineHeightNormal, marginTop: 8, }, paragraphWithImage: { @@ -279,7 +279,9 @@ export const renderText = (params: RenderTextParams) => { }, text: { fontSize: primitives.typographyFontSizeMd, - lineHeight: primitives.typographyLineHeightNormal, + ...(markdownText && hasOnlyEmojis(markdownText) + ? {} + : { lineHeight: primitives.typographyLineHeightNormal }), ...defaultMarkdownStyles.text, color: colors.black, ...markdownStyles?.text, diff --git a/package/src/components/MessageList/MessageFlashList.tsx b/package/src/components/MessageList/MessageFlashList.tsx index 2ece7e68f5..76ecceb7e1 100644 --- a/package/src/components/MessageList/MessageFlashList.tsx +++ b/package/src/components/MessageList/MessageFlashList.tsx @@ -340,18 +340,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => const channelResyncScrollSet = useRef(true); const { theme } = useTheme(); - - const { - colors: { white_snow }, - messageList: { - container, - contentContainer, - listContainer, - scrollToBottomButtonContainer, - stickyHeaderContainer, - unreadMessagesNotificationContainer, - }, - } = theme; + const styles = useStyles(); const myMessageThemeString = useMemo(() => JSON.stringify(myMessageTheme), [myMessageTheme]); @@ -979,20 +968,19 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => } const flatListStyle = useMemo( - () => [styles.listContainer, listContainer, additionalFlashListProps?.style], - [additionalFlashListProps?.style, listContainer], + () => [styles.listContainer, additionalFlashListProps?.style], + [additionalFlashListProps?.style, styles.listContainer], ); const flatListContentContainerStyle = useMemo( () => [ styles.contentContainer, { paddingBottom: messageInputFloating ? messageInputHeight : 0 }, - contentContainer, additionalFlashListProps?.contentContainerStyle, ], [ additionalFlashListProps?.contentContainerStyle, - contentContainer, + styles.contentContainer, messageInputFloating, messageInputHeight, ], @@ -1016,7 +1004,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => if (loading) { return ( - + ); @@ -1029,13 +1017,9 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => } return ( - + {processedMessageList.length === 0 && !thread ? ( - + {EmptyStateIndicator ? : null} ) : ( @@ -1071,7 +1055,7 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => /> )} - + {messageListLengthAfterUpdate && StickyHeader ? ( ) : null} @@ -1086,7 +1070,6 @@ const MessageFlashListWithContext = (props: MessageFlashListPropsWithContext) => style={[ styles.scrollToBottomButtonContainer, { bottom: messageInputFloating ? messageInputHeight : 16 }, - scrollToBottomButtonContainer, ]} > {isUnreadNotificationOpen && !threadList ? ( - + ) : null} @@ -1219,38 +1200,75 @@ export const MessageFlashList = (props: MessageFlashListProps) => { ); }; -const styles = StyleSheet.create({ - container: { - flex: 1, - width: '100%', - }, - contentContainer: { - /** - * paddingBottom is set to 4 to account for the default date - * header and inline indicator alignment. The top margin is 8 - * on the header but 4 on the inline date, this adjusts the spacing - * to allow the "first" inline date to align with the date header. - */ - paddingBottom: 4, - }, - flex: { flex: 1 }, - listContainer: { - flex: 1, - width: '100%', - }, - scrollToBottomButtonContainer: { - position: 'absolute', - right: 16, - }, - stickyHeaderContainer: { - left: 0, - position: 'absolute', - right: 0, - top: primitives.spacingXs, - }, - unreadMessagesNotificationContainer: { - alignSelf: 'center', - position: 'absolute', - top: 8, - }, -}); +const useStyles = () => { + const { + theme: { + semantics, + messageList: { + container, + contentContainer, + listContainer, + stickyHeaderContainer, + scrollToBottomButtonContainer, + unreadMessagesNotificationContainer, + }, + }, + } = useTheme(); + + const { backgroundCoreApp } = semantics; + + return useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + width: '100%', + backgroundColor: backgroundCoreApp, + ...container, + }, + contentContainer: { + /** + * paddingBottom is set to 4 to account for the default date + * header and inline indicator alignment. The top margin is 8 + * on the header but 4 on the inline date, this adjusts the spacing + * to allow the "first" inline date to align with the date header. + */ + paddingBottom: 4, + ...contentContainer, + }, + flex: { flex: 1, backgroundColor: backgroundCoreApp }, + listContainer: { + flex: 1, + width: '100%', + ...listContainer, + }, + scrollToBottomButtonContainer: { + position: 'absolute', + right: 16, + ...scrollToBottomButtonContainer, + }, + stickyHeaderContainer: { + left: 0, + position: 'absolute', + right: 0, + top: primitives.spacingXs, + ...stickyHeaderContainer, + }, + unreadMessagesNotificationContainer: { + alignSelf: 'center', + position: 'absolute', + top: 8, + ...unreadMessagesNotificationContainer, + }, + }), + [ + backgroundCoreApp, + container, + contentContainer, + listContainer, + scrollToBottomButtonContainer, + stickyHeaderContainer, + unreadMessagesNotificationContainer, + ], + ); +}; diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 1d1f475265..868786a789 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -74,41 +74,79 @@ import { MessageWrapper } from '../Message/MessageSimple/MessageWrapper'; // TODO: Think if we really need this and strive to remove it if we can. const WAIT_FOR_SCROLL_TIMEOUT = 0; const MAX_RETRIES_AFTER_SCROLL_FAILURE = 10; -const styles = StyleSheet.create({ - container: { - flex: 1, - width: '100%', - }, - contentContainer: { - /** - * paddingBottom is set to 4 to account for the default date - * header and inline indicator alignment. The top margin is 8 - * on the header but 4 on the inline date, this adjusts the spacing - * to allow the "first" inline date to align with the date header. - */ - paddingBottom: 4, - }, - flex: { flex: 1 }, - listContainer: { - flex: 1, - width: '100%', - }, - scrollToBottomButtonContainer: { - position: 'absolute', - right: 16, - }, - stickyHeaderContainer: { - left: 0, - position: 'absolute', - right: 0, - top: primitives.spacingXs, - }, - unreadMessagesNotificationContainer: { - alignSelf: 'center', - position: 'absolute', - top: 8, - }, -}); + +const useStyles = () => { + const { + theme: { + semantics, + messageList: { + container, + contentContainer, + listContainer, + stickyHeaderContainer, + scrollToBottomButtonContainer, + unreadMessagesNotificationContainer, + }, + }, + } = useTheme(); + + const { backgroundCoreApp } = semantics; + + return useMemo( + () => + StyleSheet.create({ + container: { + flex: 1, + width: '100%', + backgroundColor: backgroundCoreApp, + ...container, + }, + contentContainer: { + /** + * paddingBottom is set to 4 to account for the default date + * header and inline indicator alignment. The top margin is 8 + * on the header but 4 on the inline date, this adjusts the spacing + * to allow the "first" inline date to align with the date header. + */ + paddingBottom: 4, + ...contentContainer, + }, + flex: { flex: 1, backgroundColor: backgroundCoreApp }, + listContainer: { + flex: 1, + width: '100%', + ...listContainer, + }, + scrollToBottomButtonContainer: { + position: 'absolute', + right: 16, + ...scrollToBottomButtonContainer, + }, + stickyHeaderContainer: { + left: 0, + position: 'absolute', + right: 0, + top: primitives.spacingXs, + ...stickyHeaderContainer, + }, + unreadMessagesNotificationContainer: { + alignSelf: 'center', + position: 'absolute', + top: 8, + ...unreadMessagesNotificationContainer, + }, + }), + [ + backgroundCoreApp, + container, + contentContainer, + listContainer, + scrollToBottomButtonContainer, + stickyHeaderContainer, + unreadMessagesNotificationContainer, + ], + ); +}; const keyExtractor = (derivedItem: MessageListItemWithNeighbours) => { const { message: item } = derivedItem; @@ -330,23 +368,12 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { } = props; const [isUnreadNotificationOpen, setIsUnreadNotificationOpen] = useState(false); const { theme } = useTheme(); + const styles = useStyles(); const { height: messageInputHeight } = useStateStore( messageInputHeightStore.store, messageInputHeightStoreSelector, ); - const { - colors: { white_snow }, - messageList: { - container, - contentContainer, - listContainer, - stickyHeaderContainer, - scrollToBottomButtonContainer, - unreadMessagesNotificationContainer, - }, - } = theme; - const myMessageThemeString = useMemo(() => JSON.stringify(myMessageTheme), [myMessageTheme]); const modifiedTheme = useMemo( @@ -1123,19 +1150,19 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { } const flatListStyle = useMemo( - () => [styles.listContainer, listContainer, additionalFlatListProps?.style], - [additionalFlatListProps?.style, listContainer], + () => [styles.listContainer, additionalFlatListProps?.style], + [additionalFlatListProps?.style], ); const flatListContentContainerStyle = useMemo( () => [ { paddingTop: messageInputFloating ? messageInputHeight : 0 }, + styles.contentContainer, additionalFlatListProps?.contentContainerStyle, - contentContainer, ], [ additionalFlatListProps?.contentContainerStyle, - contentContainer, + styles.contentContainer, messageInputHeight, messageInputFloating, ], @@ -1193,7 +1220,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { if (loading) { return ( - + ); @@ -1201,13 +1228,10 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { // TODO: Make sure this is actually overridable as the previous FlatList was. return ( - + {/* Don't show the empty list indicator for Thread messages */} {processedMessageList.length === 0 && !thread ? ( - + {EmptyStateIndicator ? : null} ) : ( @@ -1254,7 +1278,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { /> )} - + {messageListLengthAfterUpdate && StickyHeader ? ( ) : null} @@ -1268,9 +1292,8 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { { {isUnreadNotificationOpen && !threadList ? ( - + ) : null} diff --git a/package/src/contexts/themeContext/ThemeContext.tsx b/package/src/contexts/themeContext/ThemeContext.tsx index 3d7db92b7b..191fa82ee1 100644 --- a/package/src/contexts/themeContext/ThemeContext.tsx +++ b/package/src/contexts/themeContext/ThemeContext.tsx @@ -1,9 +1,13 @@ import React, { PropsWithChildren, useContext, useMemo } from 'react'; +import { ColorSchemeName, useColorScheme } from 'react-native'; + import merge from 'lodash/merge'; import { defaultTheme, Theme } from './utils/theme'; +import { darkSemantics, lightSemantics } from '../../theme'; + import { resolveTokensTopologically } from '../../theme/topologicalResolution'; import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue'; import { isTestEnvironment } from '../utils/isTestEnvironment'; @@ -20,6 +24,7 @@ export type ThemeProviderInputValue = { export type MergedThemesParams = { style?: DeepPartial; theme?: Theme; + scheme?: ColorSchemeName; }; export type ThemeContextValue = { @@ -27,20 +32,22 @@ export type ThemeContextValue = { }; export const mergeThemes = (params: MergedThemesParams) => { - const { style, theme } = params; - const finalTheme = ( + const { style, theme, scheme } = params; + const baseTheme = ( !theme || Object.keys(theme).length === 0 ? JSON.parse(JSON.stringify(defaultTheme)) : JSON.parse(JSON.stringify(theme)) ) as Theme; + const semantics = resolveTokensTopologically(scheme === 'dark' ? darkSemantics : lightSemantics); + + const finalTheme = { ...baseTheme, semantics }; + if (style) { merge(finalTheme, style); } - const semantics = resolveTokensTopologically(finalTheme.semantics); - - return { ...finalTheme, semantics }; + return finalTheme; }; export const ThemeContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as Theme); @@ -50,13 +57,15 @@ export const ThemeProvider = ( ) => { const { children, mergedStyle, style, theme } = props; + const scheme = useColorScheme(); + const modifiedTheme = useMemo(() => { if (mergedStyle) { return mergedStyle; } - return mergeThemes({ style, theme }); - }, [mergedStyle, style, theme]); + return mergeThemes({ style, theme, scheme }); + }, [mergedStyle, style, theme, scheme]); return {children}; }; diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 1354150737..6bf5869596 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -1,8 +1,14 @@ -import { type ColorValue, type ImageStyle, type TextStyle, type ViewStyle } from 'react-native'; +import { + Appearance, + type ColorValue, + type ImageStyle, + type TextStyle, + type ViewStyle, +} from 'react-native'; import type { CircleProps } from 'react-native-svg'; import type { IconProps } from '../../../icons/utils/base'; -import { primitives, semantics } from '../../../theme'; +import { primitives, lightSemantics, darkSemantics } from '../../../theme'; export const DEFAULT_STATUS_ICON_SIZE = 16; // TODO: Handle this better later depending on the size of the avatar used @@ -919,11 +925,11 @@ export type Theme = { thumb: ViewStyle; waveform: ViewStyle; }; - semantics: typeof semantics; + semantics: typeof lightSemantics; // themed semantics have the same type }; export const defaultTheme: Theme = { - semantics, + semantics: Appearance.getColorScheme() === 'light' ? lightSemantics : darkSemantics, aiTypingIndicatorView: { container: {}, text: {}, diff --git a/package/src/theme/index.ts b/package/src/theme/index.ts index 7412b9a202..b051f5f5df 100644 --- a/package/src/theme/index.ts +++ b/package/src/theme/index.ts @@ -1,2 +1,8 @@ -// TODO: Handle color scheme here. -export * from './generated/light/StreamTokens'; +import { semantics as darkSemantics } from './generated/dark/StreamTokens'; +import { semantics as lightSemantics } from './generated/light/StreamTokens'; +// TODO: As these never change across different themes (only per platform), +// it's safe to do this. It should be handled in the generation phase, +// though. +export { primitives, foundations, components } from './generated/light/StreamTokens'; + +export { lightSemantics, darkSemantics }; From e1bf07407b91ed1fc21ad3f9b74d1f6b54917ddc Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 24 Feb 2026 15:56:49 +0100 Subject: [PATCH 2/6] fix: polls color scheme --- .../AttachmentPickerSelectionBar.tsx | 25 ++++++++++++------- .../components/MessageInput/MessageInput.tsx | 16 +++++++++--- .../src/components/Poll/CreatePollContent.tsx | 11 +++++--- .../Poll/components/CreatePollHeader.tsx | 1 + 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/examples/SampleApp/src/components/AttachmentPickerSelectionBar.tsx b/examples/SampleApp/src/components/AttachmentPickerSelectionBar.tsx index c663e500c4..6201074e3e 100644 --- a/examples/SampleApp/src/components/AttachmentPickerSelectionBar.tsx +++ b/examples/SampleApp/src/components/AttachmentPickerSelectionBar.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { StyleSheet, View } from 'react-native'; import { AttachmentTypePickerButton, @@ -10,6 +10,7 @@ import { PollPickerButton, useAttachmentPickerContext, useStableCallback, + useTheme, } from 'stream-chat-react-native'; import { ShareLocationIcon } from '../icons/ShareLocationIcon'; import { LiveLocationCreateModal } from './LocationSharing/CreateLocationModal'; @@ -19,6 +20,8 @@ export const CustomAttachmentPickerSelectionBar = () => { const { attachmentPickerStore } = useAttachmentPickerContext(); const { selectedPicker } = useAttachmentPickerState(); + const styles = useStyles(); + const onRequestClose = () => { setModalVisible(false); }; @@ -47,11 +50,15 @@ export const CustomAttachmentPickerSelectionBar = () => { ); }; -const styles = StyleSheet.create({ - selectionBar: { - flexDirection: 'row', - alignItems: 'center', - paddingHorizontal: 16, - paddingBottom: 12, - }, -}); +const useStyles = () => { + const { theme: { semantics }} = useTheme(); + return useMemo(() => StyleSheet.create({ + selectionBar: { + backgroundColor: semantics.composerBg, + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 16, + paddingBottom: 12, + }, + }), [semantics]) +} diff --git a/package/src/components/MessageInput/MessageInput.tsx b/package/src/components/MessageInput/MessageInput.tsx index bb56b3ad83..4fd757510d 100644 --- a/package/src/components/MessageInput/MessageInput.tsx +++ b/package/src/components/MessageInput/MessageInput.tsx @@ -71,6 +71,16 @@ const useStyles = () => { } = useTheme(); return useMemo(() => { return StyleSheet.create({ + pollModalWrapper: { + alignItems: 'center', + flex: 1, + justifyContent: 'center', + backgroundColor: semantics.backgroundElevationElevation1, + }, + pollSafeArea: { + flex: 1, + backgroundColor: semantics.backgroundElevationElevation1, + }, container: { alignItems: 'center', flexDirection: 'row', @@ -454,14 +464,14 @@ const MessageInputWithContext = (props: MessageInputPropsWithContext) => { {showPollCreationDialog ? ( - + - - + + { const { theme: { - colors: { white }, poll: { createContent: { addComment, anonymousPoll, optionCardWrapper, scrollView, suggestOption }, }, @@ -150,7 +149,7 @@ export const CreatePollContent = () => { /> @@ -248,7 +247,11 @@ const useStyles = () => { } = useTheme(); return useMemo(() => { return StyleSheet.create({ - scrollView: { flex: 1, padding: primitives.spacingMd }, + scrollView: { + flex: 1, + padding: primitives.spacingMd, + backgroundColor: semantics.backgroundElevationElevation1, + }, contentContainerStyle: { paddingBottom: 70 }, title: { color: semantics.textPrimary, diff --git a/package/src/components/Poll/components/CreatePollHeader.tsx b/package/src/components/Poll/components/CreatePollHeader.tsx index 30329c7505..0b011f679c 100644 --- a/package/src/components/Poll/components/CreatePollHeader.tsx +++ b/package/src/components/Poll/components/CreatePollHeader.tsx @@ -92,6 +92,7 @@ const useStyles = () => { alignItems: 'center', justifyContent: 'space-between', padding: primitives.spacingMd, + backgroundColor: semantics.backgroundElevationElevation1, }, title: { color: semantics.textPrimary, From 7c0a3aa08ff5709d7704f29f10ec0e5a9dc1f5cd Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 24 Feb 2026 18:47:39 +0100 Subject: [PATCH 3/6] fix: reactivity of BG color --- .../Message/MessageSimple/MessageContent.tsx | 13 ++++++++----- .../Message/MessageSimple/MessageSimple.tsx | 14 +++++--------- package/src/contexts/themeContext/utils/theme.ts | 2 -- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/package/src/components/Message/MessageSimple/MessageContent.tsx b/package/src/components/Message/MessageSimple/MessageContent.tsx index 861f549264..37517f47f9 100644 --- a/package/src/components/Message/MessageSimple/MessageContent.tsx +++ b/package/src/components/Message/MessageSimple/MessageContent.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { AnimatableNumericValue, ColorValue, @@ -117,7 +117,6 @@ export type MessageContentPropsWithContext = Pick< * Child of MessageSimple that displays a message's content */ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { - const [longPressFired, setLongPressFired] = useState(false); const { additionalPressableProps, Attachment, @@ -238,7 +237,6 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { { - setLongPressFired(true); if (onLongPress) { onLongPress({ emitter: 'messageContent', @@ -262,10 +260,9 @@ const MessageContentWithContext = (props: MessageContentPropsWithContext) => { }); } }} - style={({ pressed }) => [{ opacity: pressed && !longPressFired ? 0.5 : 1 }, container]} + style={container} {...additionalPressableProps} onPressOut={(event) => { - setLongPressFired(false); setNativeScrollability(true); if (additionalPressableProps?.onPressOut) { @@ -382,6 +379,7 @@ const areEqual = ( nextProps: MessageContentPropsWithContext, ) => { const { + backgroundColor: prevBackgroundColor, preventPress: prevPreventPress, goToMessage: prevGoToMessage, groupStyles: prevGroupStyles, @@ -393,6 +391,7 @@ const areEqual = ( t: prevT, } = prevProps; const { + backgroundColor: nextBackgroundColor, preventPress: nextPreventPress, goToMessage: nextGoToMessage, groupStyles: nextGroupStyles, @@ -403,6 +402,10 @@ const areEqual = ( t: nextT, } = nextProps; + if (prevBackgroundColor !== nextBackgroundColor) { + return false; + } + if (prevPreventPress !== nextPreventPress) { return false; } diff --git a/package/src/components/Message/MessageSimple/MessageSimple.tsx b/package/src/components/Message/MessageSimple/MessageSimple.tsx index e70ddb6089..b749d5471f 100644 --- a/package/src/components/Message/MessageSimple/MessageSimple.tsx +++ b/package/src/components/Message/MessageSimple/MessageSimple.tsx @@ -120,16 +120,12 @@ const MessageSimpleWithContext = forwardRef const { theme: { - colors: { blue_alice, grey_gainsboro, light_blue, light_gray, transparent }, + semantics, + colors: { blue_alice, grey_gainsboro, transparent }, messageSimple: { container, repliesContainer, - content: { - container: contentContainer, - errorContainer, - receiverMessageBackgroundColor, - senderMessageBackgroundColor, - }, + content: { container: contentContainer, errorContainer }, headerWrapper, lastMessageContainer, messageGroupedSingleOrBottomContainer, @@ -168,7 +164,7 @@ const MessageSimpleWithContext = forwardRef } } - let backgroundColor = senderMessageBackgroundColor ?? light_blue; + let backgroundColor = semantics.chatBgOutgoing; if (onlyEmojis && !message.quoted_message) { backgroundColor = transparent; } else if (otherAttachments.length) { @@ -178,7 +174,7 @@ const MessageSimpleWithContext = forwardRef backgroundColor = blue_alice; } } else if (isMessageReceivedOrErrorType) { - backgroundColor = receiverMessageBackgroundColor ?? light_gray; + backgroundColor = semantics.chatBgIncoming; } const onSwipeActionHandler = useStableCallback(() => { diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 6bf5869596..bf1287892c 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -596,8 +596,6 @@ export type Theme = { onlyEmojiMarkdown: MarkdownStyle; }; wrapper: ViewStyle; - receiverMessageBackgroundColor?: ColorValue; - senderMessageBackgroundColor?: ColorValue; timestampText?: TextStyle; }; deleted: { From 707be0ff32ea4fbf78d199f21c7ff58f53563404 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 24 Feb 2026 18:51:17 +0100 Subject: [PATCH 4/6] fix: lint errors and tests --- .../__snapshots__/MessageAvatar.test.js.snap | 4 +- .../MessageTextContainer.test.tsx.snap | 2 - .../components/MessageList/MessageList.tsx | 2 +- .../src/components/Poll/CreatePollContent.tsx | 2 +- .../__snapshots__/Thread.test.js.snap | 87 +++++-------------- 5 files changed, 26 insertions(+), 71 deletions(-) diff --git a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap index a048ebfaaf..3356718c66 100644 --- a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap +++ b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageAvatar.test.js.snap @@ -22,10 +22,10 @@ exports[`MessageAvatar should render message avatar 1`] = ` "width": 32, }, { - "backgroundColor": "#d1f3f6", + "backgroundColor": "#003a3f", }, { - "borderColor": "rgba(0, 0, 0, 0.1)", + "borderColor": "rgba(255, 255, 255, 0.2)", "borderWidth": 1, }, undefined, diff --git a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap index 3f3a5a8efc..ea2f951811 100644 --- a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap +++ b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap @@ -29,9 +29,7 @@ exports[`MessageTextContainer should render message text container 1`] = ` "alignItems": "flex-start", "flexDirection": "row", "flexWrap": "wrap", - "fontSize": 15, "justifyContent": "flex-start", - "lineHeight": 20, "marginBottom": 8, "marginTop": 8, } diff --git a/package/src/components/MessageList/MessageList.tsx b/package/src/components/MessageList/MessageList.tsx index 868786a789..f3aa00f349 100644 --- a/package/src/components/MessageList/MessageList.tsx +++ b/package/src/components/MessageList/MessageList.tsx @@ -1151,7 +1151,7 @@ const MessageListWithContext = (props: MessageListPropsWithContext) => { const flatListStyle = useMemo( () => [styles.listContainer, additionalFlatListProps?.style], - [additionalFlatListProps?.style], + [additionalFlatListProps?.style, styles.listContainer], ); const flatListContentContainerStyle = useMemo( diff --git a/package/src/components/Poll/CreatePollContent.tsx b/package/src/components/Poll/CreatePollContent.tsx index 067886620e..6e2c9e532d 100644 --- a/package/src/components/Poll/CreatePollContent.tsx +++ b/package/src/components/Poll/CreatePollContent.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { StyleSheet, Switch, Text, useColorScheme, View } from 'react-native'; +import { StyleSheet, Switch, Text, View } from 'react-native'; import { ScrollView } from 'react-native-gesture-handler'; import { useSharedValue } from 'react-native-reanimated'; diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index 6b54211ad1..db571a9798 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -19,16 +19,11 @@ exports[`Thread should match thread snapshot 1`] = ` > @@ -40,8 +35,10 @@ exports[`Thread should match thread snapshot 1`] = ` { "paddingTop": 0, }, + { + "paddingBottom": 4, + }, undefined, - {}, ] } data={ @@ -297,7 +294,6 @@ exports[`Thread should match thread snapshot 1`] = ` "flex": 1, "width": "100%", }, - {}, undefined, ], ] @@ -528,14 +524,7 @@ exports[`Thread should match thread snapshot 1`] = ` onResponderTerminate={[Function]} onResponderTerminationRequest={[Function]} onStartShouldSetResponder={[Function]} - style={ - [ - { - "opacity": 1, - }, - {}, - ] - } + style={{}} > From 7eb0d0538c4831bce14ebdf2f38b4119d4e24ae5 Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Tue, 24 Feb 2026 18:57:25 +0100 Subject: [PATCH 5/6] fix: only emoji rendering --- .../MessageSimple/utils/renderText.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/package/src/components/Message/MessageSimple/utils/renderText.tsx b/package/src/components/Message/MessageSimple/utils/renderText.tsx index 7ea8207814..a4c8fe7f1d 100644 --- a/package/src/components/Message/MessageSimple/utils/renderText.tsx +++ b/package/src/components/Message/MessageSimple/utils/renderText.tsx @@ -34,7 +34,7 @@ import type { MessageContextValue } from '../../../../contexts/messageContext/Me import type { Colors, MarkdownStyle } from '../../../../contexts/themeContext/utils/theme'; import { primitives } from '../../../../theme'; -import { escapeRegExp, hasOnlyEmojis } from '../../../../utils/utils'; +import { escapeRegExp } from '../../../../utils/utils'; type ReactNodeOutput = NodeOutput; type ReactOutput = Output; @@ -125,14 +125,12 @@ const defaultMarkdownStyles: MarkdownStyle = { }, paragraph: { marginBottom: 8, - // fontSize: primitives.typographyFontSizeMd, - // lineHeight: primitives.typographyLineHeightNormal, + fontSize: primitives.typographyFontSizeMd, marginTop: 8, }, paragraphCenter: { marginBottom: 8, - // fontSize: primitives.typographyFontSizeMd, - // lineHeight: primitives.typographyLineHeightNormal, + fontSize: primitives.typographyFontSizeMd, marginTop: 8, }, paragraphWithImage: { @@ -201,6 +199,16 @@ export const renderText = (params: RenderTextParams) => { const styles: MarkdownStyle = { ...defaultMarkdownStyles, ...markdownStyles, + paragraph: { + ...(onlyEmojis ? {} : { lineHeight: primitives.typographyLineHeightNormal }), + ...defaultMarkdownStyles.paragraph, + ...markdownStyles?.paragraph, + }, + paragraphCenter: { + ...(onlyEmojis ? {} : { lineHeight: primitives.typographyLineHeightNormal }), + ...defaultMarkdownStyles.paragraphCenter, + ...markdownStyles?.paragraphCenter, + }, autolink: { fontSize: primitives.typographyFontSizeMd, lineHeight: primitives.typographyLineHeightNormal, @@ -279,9 +287,7 @@ export const renderText = (params: RenderTextParams) => { }, text: { fontSize: primitives.typographyFontSizeMd, - ...(markdownText && hasOnlyEmojis(markdownText) - ? {} - : { lineHeight: primitives.typographyLineHeightNormal }), + ...(onlyEmojis ? {} : { lineHeight: primitives.typographyLineHeightNormal }), ...defaultMarkdownStyles.text, color: colors.black, ...markdownStyles?.text, From 16a16aaad83f431ada5a86589ea095700b0cc68f Mon Sep 17 00:00:00 2001 From: Ivan Sekovanikj Date: Wed, 25 Feb 2026 09:09:50 +0100 Subject: [PATCH 6/6] fix: snapshots --- .../__snapshots__/MessageTextContainer.test.tsx.snap | 2 ++ .../Thread/__tests__/__snapshots__/Thread.test.js.snap | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap index ea2f951811..3f3a5a8efc 100644 --- a/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap +++ b/package/src/components/Message/MessageSimple/__tests__/__snapshots__/MessageTextContainer.test.tsx.snap @@ -29,7 +29,9 @@ exports[`MessageTextContainer should render message text container 1`] = ` "alignItems": "flex-start", "flexDirection": "row", "flexWrap": "wrap", + "fontSize": 15, "justifyContent": "flex-start", + "lineHeight": 20, "marginBottom": 8, "marginTop": 8, } diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index db571a9798..d309122ace 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -603,7 +603,9 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-start", "flexDirection": "row", "flexWrap": "wrap", + "fontSize": 15, "justifyContent": "flex-start", + "lineHeight": 20, "marginBottom": 8, "marginTop": 8, } @@ -954,7 +956,9 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-start", "flexDirection": "row", "flexWrap": "wrap", + "fontSize": 15, "justifyContent": "flex-start", + "lineHeight": 20, "marginBottom": 8, "marginTop": 8, } @@ -1330,7 +1334,9 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-start", "flexDirection": "row", "flexWrap": "wrap", + "fontSize": 15, "justifyContent": "flex-start", + "lineHeight": 20, "marginBottom": 8, "marginTop": 8, } @@ -1687,7 +1693,9 @@ exports[`Thread should match thread snapshot 1`] = ` "alignItems": "flex-start", "flexDirection": "row", "flexWrap": "wrap", + "fontSize": 15, "justifyContent": "flex-start", + "lineHeight": 20, "marginBottom": 8, "marginTop": 8, }