diff --git a/examples/SampleApp/src/components/Reminders/MessageReminderHeader.tsx b/examples/SampleApp/src/components/Reminders/MessageReminderHeader.tsx deleted file mode 100644 index f57d827c40..0000000000 --- a/examples/SampleApp/src/components/Reminders/MessageReminderHeader.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { - MessageHeaderProps, - MessagePinnedHeader, - Time, - useMessageContext, - useMessageReminder, - useStateStore, - useTheme, - useTranslationContext, -} from 'stream-chat-react-native'; -import { ReminderState } from 'stream-chat'; -import { StyleSheet, Text, View } from 'react-native'; - -const reminderStateSelector = (state: ReminderState) => ({ - timeLeftMs: state.timeLeftMs, -}); - -export const MessageReminderHeader = ({ message }: MessageHeaderProps) => { - const messageId = message?.id ?? ''; - const reminder = useMessageReminder(messageId); - const { timeLeftMs } = useStateStore(reminder?.state, reminderStateSelector) ?? {}; - const { t } = useTranslationContext(); - - const { - theme: { semantics }, - } = useTheme(); - - const stopRefreshBoundaryMs = reminder?.timer.stopRefreshBoundaryMs; - const stopRefreshTimeStamp = - reminder?.remindAt && stopRefreshBoundaryMs - ? reminder?.remindAt.getTime() + stopRefreshBoundaryMs - : undefined; - - const isBehindRefreshBoundary = - !!stopRefreshTimeStamp && new Date().getTime() > stopRefreshTimeStamp; - - if (!reminder) { - return null; - } - - // This is for "Saved for Later" - if (!reminder.remindAt) { - return ( - - 🔖 Saved for Later - - ); - } - - if (reminder.remindAt && timeLeftMs !== null) { - return ( - - - ); - } -}; - -export const MessageHeader = () => { - const { message } = useMessageContext(); - return ( - <> - - - - ); -}; - -const styles = StyleSheet.create({ - headerContainer: { - flexDirection: 'row', - alignItems: 'center', - }, - headerTitle: { - fontSize: 14, - fontWeight: '500', - marginLeft: 4, - }, -}); diff --git a/examples/SampleApp/src/icons/Bell.tsx b/examples/SampleApp/src/icons/Bell.tsx deleted file mode 100644 index e2e669941a..0000000000 --- a/examples/SampleApp/src/icons/Bell.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import Svg, { Path } from 'react-native-svg'; -import { useTheme } from 'stream-chat-react-native'; - -import { IconProps } from '../utils/base'; - -export const Bell: React.FC = ({ height = 512, width = 512 }) => { - const { - theme: { - semantics - }, - } = useTheme(); - - return ( - - - - ); -}; diff --git a/examples/SampleApp/src/screens/ChannelScreen.tsx b/examples/SampleApp/src/screens/ChannelScreen.tsx index 92c178100a..e3b21e5b97 100644 --- a/examples/SampleApp/src/screens/ChannelScreen.tsx +++ b/examples/SampleApp/src/screens/ChannelScreen.tsx @@ -27,7 +27,6 @@ import { useChannelMembersStatus } from '../hooks/useChannelMembersStatus'; import type { StackNavigatorParamList } from '../types'; import { NetworkDownIndicator } from '../components/NetworkDownIndicator'; import { useCreateDraftFocusEffect } from '../utils/useCreateDraftFocusEffect.tsx'; -import { MessageHeader } from '../components/Reminders/MessageReminderHeader.tsx'; import { channelMessageActions } from '../utils/messageActions.tsx'; import { MessageLocation } from '../components/LocationSharing/MessageLocation.tsx'; import { useStreamChatContext } from '../context/StreamChatContext.tsx'; @@ -176,7 +175,7 @@ export const ChannelScreen: React.FC = ({ }; const onThreadSelect = useCallback( - (thread: LocalMessage | null) => { + (thread: LocalMessage | null, targetedMessageId?: string) => { if (!thread || !channel) { return; } @@ -185,6 +184,7 @@ export const ChannelScreen: React.FC = ({ navigation.navigate('ThreadScreen', { channel, thread, + targetedMessageId, }); }, [channel, navigation, setThread], @@ -233,7 +233,6 @@ export const ChannelScreen: React.FC = ({ initialScrollToFirstUnreadMessage keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300} messageActions={messageActions} - MessageHeader={MessageHeader} MessageLocation={MessageLocation} messageId={messageId} NetworkDownIndicator={() => null} diff --git a/examples/SampleApp/src/screens/ThreadScreen.tsx b/examples/SampleApp/src/screens/ThreadScreen.tsx index 75fd66e7f3..63a7adb0ca 100644 --- a/examples/SampleApp/src/screens/ThreadScreen.tsx +++ b/examples/SampleApp/src/screens/ThreadScreen.tsx @@ -15,12 +15,11 @@ import { useStateStore } from 'stream-chat-react-native'; import { ScreenHeader } from '../components/ScreenHeader'; -import type { RouteProp } from '@react-navigation/native'; +import { type RouteProp } from '@react-navigation/native'; import type { StackNavigatorParamList } from '../types'; import { LocalMessage, ThreadState, UserResponse } from 'stream-chat'; import { useCreateDraftFocusEffect } from '../utils/useCreateDraftFocusEffect.tsx'; -import { MessageReminderHeader } from '../components/Reminders/MessageReminderHeader.tsx'; import { channelMessageActions } from '../utils/messageActions.tsx'; import { useStreamChatContext } from '../context/StreamChatContext.tsx'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -74,7 +73,7 @@ const ThreadHeader: React.FC = ({ thread }) => { export const ThreadScreen: React.FC = ({ navigation, route: { - params: { channel, thread }, + params: { channel, thread, targetedMessageId }, }, }) => { const { @@ -87,7 +86,6 @@ export const ThreadScreen: React.FC = ({ const { t } = useTranslationContext(); const { setThread } = useStreamChatContext(); const { messageInputFloating, messageListImplementation } = useAppContext(); - const onPressMessage: NonNullable['onPressMessage']> = ( payload, ) => { @@ -118,6 +116,13 @@ export const ThreadScreen: React.FC = ({ setThread(null); }, [setThread]); + const onBackPressThread = useCallback( + (messageId?: string) => { + navigation.popTo('ChannelScreen', { messageId: messageId }); + }, + [navigation], + ); + return ( = ({ keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300} messageActions={messageActions} messageInputFloating={messageInputFloating} - MessageHeader={MessageReminderHeader} MessageLocation={MessageLocation} onPressMessage={onPressMessage} thread={thread} threadList + onBackPressThread={onBackPressThread} + messageId={targetedMessageId} > - + ); diff --git a/examples/SampleApp/src/types.ts b/examples/SampleApp/src/types.ts index 1fc11fc7f6..621ffeb907 100644 --- a/examples/SampleApp/src/types.ts +++ b/examples/SampleApp/src/types.ts @@ -40,6 +40,7 @@ export type StackNavigatorParamList = { ThreadScreen: { channel: Channel; thread: LocalMessage | ThreadType; + targetedMessageId?: string; }; }; diff --git a/examples/SampleApp/src/utils/messageActions.tsx b/examples/SampleApp/src/utils/messageActions.tsx index f104737a4c..0c54000ae3 100644 --- a/examples/SampleApp/src/utils/messageActions.tsx +++ b/examples/SampleApp/src/utils/messageActions.tsx @@ -9,8 +9,8 @@ import { MessageActionsParams, Time, TranslationContextValue, + Bell, } from 'stream-chat-react-native'; -import { Bell } from '../icons/Bell'; import { Theme } from 'stream-chat-react-native'; export function channelMessageActions({ @@ -94,7 +94,7 @@ export function channelMessageActions({ }, actionType: reminder ? 'remove-reminder' : 'remind-me', title: reminder ? 'Remove Reminder' : 'Remind Me', - icon: , + icon: , type: 'standard', }); actions.push({ diff --git a/package/src/components/Channel/Channel.tsx b/package/src/components/Channel/Channel.tsx index 7960b7af4d..b6d37ca59a 100644 --- a/package/src/components/Channel/Channel.tsx +++ b/package/src/components/Channel/Channel.tsx @@ -141,6 +141,9 @@ import { } from '../KeyboardCompatibleView/KeyboardControllerAvoidingView'; import { Message as MessageDefault } from '../Message/Message'; import { MessagePinnedHeader as MessagePinnedHeaderDefault } from '../Message/MessageSimple/Headers/MessagePinnedHeader'; +import { MessageReminderHeader as MessageReminderHeaderDefault } from '../Message/MessageSimple/Headers/MessageReminderHeader'; +import { MessageSavedForLaterHeader as MessageSavedForLaterHeaderDefault } from '../Message/MessageSimple/Headers/MessageSavedForLaterHeader'; +import { SentToChannelHeader as SentToChannelHeaderDefault } from '../Message/MessageSimple/Headers/SentToChannelHeader'; import { MessageAvatar as MessageAvatarDefault } from '../Message/MessageSimple/MessageAvatar'; import { MessageBlocked as MessageBlockedDefault } from '../Message/MessageSimple/MessageBlocked'; import { MessageBounce as MessageBounceDefault } from '../Message/MessageSimple/MessageBounce'; @@ -366,6 +369,9 @@ export type ChannelPropsWithContext = Pick & | 'MessageLocation' | 'MessageMenu' | 'MessagePinnedHeader' + | 'MessageReminderHeader' + | 'MessageSavedForLaterHeader' + | 'SentToChannelHeader' | 'MessageReplies' | 'MessageRepliesAvatars' | 'MessageSimple' @@ -404,7 +410,7 @@ export type ChannelPropsWithContext = Pick & > > & Partial> & - Partial> & { + Partial> & { shouldSyncChannel: boolean; thread: ThreadType; /** @@ -683,6 +689,9 @@ const ChannelWithContext = (props: PropsWithChildren) = MessageLocation, MessageMenu = MessageMenuDefault, MessagePinnedHeader = MessagePinnedHeaderDefault, + MessageReminderHeader = MessageReminderHeaderDefault, + MessageSavedForLaterHeader = MessageSavedForLaterHeaderDefault, + SentToChannelHeader = SentToChannelHeaderDefault, MessageReactionPicker = MessageReactionPickerDefault, MessageReplies = MessageRepliesDefault, MessageRepliesAvatars = MessageRepliesAvatarsDefault, @@ -704,6 +713,7 @@ const ChannelWithContext = (props: PropsWithChildren) = onLongPressMessage, onPressInMessage, onPressMessage, + onBackPressThread, openPollCreationDialog, overrideOwnCapabilities, PollContent, @@ -1941,6 +1951,9 @@ const ChannelWithContext = (props: PropsWithChildren) = MessageLocation, MessageMenu, MessagePinnedHeader, + MessageReminderHeader, + MessageSavedForLaterHeader, + SentToChannelHeader, MessageReactionPicker, MessageReplies, MessageRepliesAvatars, @@ -1984,6 +1997,7 @@ const ChannelWithContext = (props: PropsWithChildren) = const threadContext = useCreateThreadContext({ allowThreadMessagesInChannel, + onBackPressThread, closeThread, loadMoreThread, openThread, diff --git a/package/src/components/Channel/hooks/useCreateMessagesContext.ts b/package/src/components/Channel/hooks/useCreateMessagesContext.ts index 2d04824d2e..be64622728 100644 --- a/package/src/components/Channel/hooks/useCreateMessagesContext.ts +++ b/package/src/components/Channel/hooks/useCreateMessagesContext.ts @@ -65,6 +65,9 @@ export const useCreateMessagesContext = ({ MessageLocation, MessageMenu, MessagePinnedHeader, + MessageReminderHeader, + MessageSavedForLaterHeader, + SentToChannelHeader, MessageReactionPicker, MessageReplies, MessageRepliesAvatars, @@ -178,6 +181,9 @@ export const useCreateMessagesContext = ({ MessageLocation, MessageMenu, MessagePinnedHeader, + MessageReminderHeader, + MessageSavedForLaterHeader, + SentToChannelHeader, MessageReactionPicker, MessageReplies, MessageRepliesAvatars, diff --git a/package/src/components/Channel/hooks/useCreateThreadContext.ts b/package/src/components/Channel/hooks/useCreateThreadContext.ts index 43addaafe8..098dbe9596 100644 --- a/package/src/components/Channel/hooks/useCreateThreadContext.ts +++ b/package/src/components/Channel/hooks/useCreateThreadContext.ts @@ -12,6 +12,7 @@ const selector = (nextValue: ThreadState) => export const useCreateThreadContext = ({ allowThreadMessagesInChannel, + onBackPressThread, closeThread, loadMoreThread, openThread, @@ -39,6 +40,7 @@ export const useCreateThreadContext = ({ return { allowThreadMessagesInChannel, + onBackPressThread, closeThread, loadMoreThread, openThread, diff --git a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx index 0c363a29f3..ab2b663db0 100644 --- a/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx +++ b/package/src/components/ChannelPreview/ChannelPreviewMessage.tsx @@ -79,7 +79,7 @@ export const ChannelPreviewMessage = (props: ChannelPreviewMessageProps) => { if (draftMessage) { return ( - {t('Draft:')} + {t('Draft')}: {renderMessagePreview(draftMessage)} ); diff --git a/package/src/components/Message/Message.tsx b/package/src/components/Message/Message.tsx index 17c8b7a662..ecbe268e3a 100644 --- a/package/src/components/Message/Message.tsx +++ b/package/src/components/Message/Message.tsx @@ -773,6 +773,7 @@ const MessageWithContext = (props: MessagePropsWithContext) => { } } : null, + onThreadSelect, otherAttachments: attachments.other, preventPress: overlayActive ? true : preventPress, reactions, diff --git a/package/src/components/Message/MessageSimple/Headers/MessageReminderHeader.tsx b/package/src/components/Message/MessageSimple/Headers/MessageReminderHeader.tsx new file mode 100644 index 0000000000..ef06b07e2f --- /dev/null +++ b/package/src/components/Message/MessageSimple/Headers/MessageReminderHeader.tsx @@ -0,0 +1,108 @@ +import React, { useMemo } from 'react'; + +import { StyleSheet, Text, View } from 'react-native'; + +import { ReminderState } from 'stream-chat'; + +import { useMessageContext } from '../../../../contexts/messageContext/MessageContext'; +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; +import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext'; +import { useMessageReminder } from '../../../../hooks/useMessageReminder'; +import { useStateStore } from '../../../../hooks/useStateStore'; +import { Bell } from '../../../../icons'; +import { primitives } from '../../../../theme'; + +const reminderStateSelector = (state: ReminderState) => ({ + timeLeftMs: state.timeLeftMs, +}); + +type MessageReminderHeaderPropsWithContext = { + timeLeftMs?: number; + isReminderTimeLeft: boolean; +}; + +const MessageReminderHeaderWithContext = (props: MessageReminderHeaderPropsWithContext) => { + const { timeLeftMs, isReminderTimeLeft } = props; + const { + theme: { semantics }, + } = useTheme(); + const { t } = useTranslationContext(); + const styles = useStyles(); + + return ( + + + + {isReminderTimeLeft ? t('Reminder set') : t('Reminder overdue')} + + · + + {t('duration/Message reminder', { + milliseconds: timeLeftMs, + })} + + + ); +}; + +export type MessageReminderHeaderProps = Partial; + +export const MessageReminderHeader = (props: MessageReminderHeaderProps) => { + const { message } = useMessageContext(); + const reminder = useMessageReminder(message.id); + const { timeLeftMs } = useStateStore(reminder?.state, reminderStateSelector) ?? {}; + + const isReminderTimeLeft = !!(timeLeftMs && timeLeftMs > 0); + + return ( + + ); +}; + +const useStyles = () => { + const { + theme: { + semantics, + messageSimple: { + reminderHeader: { container, label, dot, time }, + }, + }, + } = useTheme(); + + return useMemo(() => { + return StyleSheet.create({ + container: { + alignItems: 'center', + flexDirection: 'row', + gap: primitives.spacingXxs, + paddingVertical: primitives.spacingXxs, + ...container, + }, + label: { + color: semantics.textPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightSemiBold, + lineHeight: primitives.typographyLineHeightTight, + ...label, + }, + dot: { + color: semantics.textPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightRegular, + lineHeight: primitives.typographyLineHeightTight, + ...dot, + }, + time: { + color: semantics.textPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightRegular, + lineHeight: primitives.typographyLineHeightTight, + ...time, + }, + }); + }, [container, semantics, label, dot, time]); +}; diff --git a/package/src/components/Message/MessageSimple/Headers/MessageSavedForLaterHeader.tsx b/package/src/components/Message/MessageSimple/Headers/MessageSavedForLaterHeader.tsx new file mode 100644 index 0000000000..96b64554d5 --- /dev/null +++ b/package/src/components/Message/MessageSimple/Headers/MessageSavedForLaterHeader.tsx @@ -0,0 +1,55 @@ +import React, { useMemo } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; + +import { MessageContextValue } from '../../../../contexts/messageContext/MessageContext'; +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; +import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext'; +import { Bookmark } from '../../../../icons/Bookmark'; +import { primitives } from '../../../../theme'; + +export type MessageSavedForLaterHeaderProps = Partial>; + +export const MessageSavedForLaterHeader = () => { + const { + theme: { semantics }, + } = useTheme(); + const styles = useStyles(); + const { t } = useTranslationContext(); + + return ( + + + {t('Saved For Later')} + + ); +}; + +const useStyles = () => { + const { + theme: { + semantics, + messageSimple: { + savedForLaterHeader: { container, label }, + }, + }, + } = useTheme(); + + return useMemo(() => { + return StyleSheet.create({ + container: { + alignItems: 'center', + flexDirection: 'row', + gap: primitives.spacingXxs, + paddingVertical: primitives.spacingXxs, + ...container, + }, + label: { + color: semantics.accentPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightSemiBold, + lineHeight: primitives.typographyLineHeightTight, + ...label, + }, + }); + }, [semantics, container, label]); +}; diff --git a/package/src/components/Message/MessageSimple/Headers/SentToChannelHeader.tsx b/package/src/components/Message/MessageSimple/Headers/SentToChannelHeader.tsx new file mode 100644 index 0000000000..973110ded1 --- /dev/null +++ b/package/src/components/Message/MessageSimple/Headers/SentToChannelHeader.tsx @@ -0,0 +1,141 @@ +import React, { useMemo } from 'react'; + +import { Pressable, StyleSheet, Text, View } from 'react-native'; + +import { formatMessage } from 'stream-chat'; + +import { + ChannelContextValue, + useChannelContext, +} from '../../../../contexts/channelContext/ChannelContext'; +import { useMessageContext } from '../../../../contexts/messageContext/MessageContext'; +import { useTheme } from '../../../../contexts/themeContext/ThemeContext'; +import { useThreadContext } from '../../../../contexts/threadContext/ThreadContext'; +import { useTranslationContext } from '../../../../contexts/translationContext/TranslationContext'; +import { useStableCallback } from '../../../../hooks'; +import { ArrowUpRight } from '../../../../icons/ArrowUpRight'; +import { primitives } from '../../../../theme'; + +type SentToChannelHeaderPropsWithContext = Pick & { + /** + * Function to handle press on the sent to channel header + * @returns void + */ + onPress: () => void; + /** + * Boolean to show the view text + * @default false + */ + showViewText?: boolean; +}; + +const SentToChannelHeaderWithContext = (props: SentToChannelHeaderPropsWithContext) => { + const { threadList, onPress, showViewText } = props; + const { + theme: { semantics }, + } = useTheme(); + const { t } = useTranslationContext(); + const styles = useStyles(); + + return ( + + + + {threadList ? t('Also sent in channel') : t('Replied to a thread')} + + {showViewText ? ( + <> + · + + {t('View')} + + + ) : null} + + ); +}; + +const MemoizedSentToChannelHeader = React.memo( + SentToChannelHeaderWithContext, +) as typeof SentToChannelHeaderWithContext; + +export type SentToChannelHeaderProps = Partial; + +export const SentToChannelHeader = (props: SentToChannelHeaderProps) => { + const { onBackPressThread } = useThreadContext(); + const { threadList, channel, setTargetedMessage } = useChannelContext(); + const { onThreadSelect, message } = useMessageContext(); + + const handleOnPress = useStableCallback(async () => { + if (!threadList) { + return await channel + .getClient() + .search({ cid: channel.cid }, { id: message.parent_id }) + .then(({ results }) => { + if (!results.length) { + return; + } + const targetMessage = formatMessage(results[0].message); + onThreadSelect?.(targetMessage, message.id); + }) + .catch((error) => { + console.error('Error querying parent message:', error); + }); + } else { + setTargetedMessage(message.id); + onBackPressThread?.(message.id); + } + }); + + const showViewText = useMemo(() => { + return !!((!threadList && onThreadSelect) || (threadList && onBackPressThread)); + }, [threadList, onThreadSelect, onBackPressThread]); + + return ( + + ); +}; + +const useStyles = () => { + const { + theme: { + semantics, + messageSimple: { + sentToChannelHeader: { container, label, dot, link }, + }, + }, + } = useTheme(); + + return useMemo(() => { + return StyleSheet.create({ + container: { + alignItems: 'center', + flexDirection: 'row', + gap: primitives.spacingXxs, + paddingVertical: primitives.spacingXxs, + ...container, + }, + label: { + color: semantics.textPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightSemiBold, + lineHeight: primitives.typographyLineHeightTight, + ...label, + }, + dot: { + color: semantics.textPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightSemiBold, + lineHeight: primitives.typographyLineHeightTight, + ...dot, + }, + link: { + color: semantics.accentPrimary, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightRegular, + lineHeight: primitives.typographyLineHeightTight, + ...link, + }, + }); + }, [container, semantics, label, dot, link]); +}; diff --git a/package/src/components/Message/MessageSimple/Headers/index.tsx b/package/src/components/Message/MessageSimple/Headers/index.tsx new file mode 100644 index 0000000000..81b226621e --- /dev/null +++ b/package/src/components/Message/MessageSimple/Headers/index.tsx @@ -0,0 +1,4 @@ +export * from './MessagePinnedHeader'; +export * from './MessageReminderHeader'; +export * from './MessageSavedForLaterHeader'; +export * from './SentToChannelHeader'; diff --git a/package/src/components/Message/MessageSimple/MessageHeader.tsx b/package/src/components/Message/MessageSimple/MessageHeader.tsx index a76f6e2162..2bddd14aad 100644 --- a/package/src/components/Message/MessageSimple/MessageHeader.tsx +++ b/package/src/components/Message/MessageSimple/MessageHeader.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { View } from 'react-native'; + import { MessageContextValue, useMessageContext, @@ -8,26 +10,82 @@ import { MessagesContextValue, useMessagesContext, } from '../../../contexts/messagesContext/MessagesContext'; +import { useMessageReminder } from '../../../hooks/useMessageReminder'; type MessageHeaderPropsWithContext = Pick & - Pick; + Pick< + MessagesContextValue, + | 'MessagePinnedHeader' + | 'MessageReminderHeader' + | 'MessageSavedForLaterHeader' + | 'SentToChannelHeader' + > & { + shouldShowSavedForLaterHeader?: boolean; + shouldShowPinnedHeader: boolean; + shouldShowReminderHeader: boolean; + shouldShowSentToChannelHeader: boolean; + }; const MessageHeaderWithContext = (props: MessageHeaderPropsWithContext) => { - const { message, MessagePinnedHeader } = props; + const { + message, + MessagePinnedHeader, + shouldShowSavedForLaterHeader, + shouldShowPinnedHeader, + shouldShowReminderHeader, + shouldShowSentToChannelHeader, + MessageReminderHeader, + MessageSavedForLaterHeader, + SentToChannelHeader, + } = props; - return ; + return ( + + {shouldShowReminderHeader && } + {shouldShowSavedForLaterHeader && } + {shouldShowPinnedHeader && } + {shouldShowSentToChannelHeader && } + + ); }; const areEqual = ( prevProps: MessageHeaderPropsWithContext, nextProps: MessageHeaderPropsWithContext, ) => { - const { message: prevMessage } = prevProps; - const { message: nextMessage } = nextProps; + const { + shouldShowSavedForLaterHeader: prevShouldShowSavedForLaterHeader, + shouldShowPinnedHeader: prevShouldShowPinnedHeader, + shouldShowReminderHeader: prevShouldShowReminderHeader, + shouldShowSentToChannelHeader: prevShouldShowSentToChannelHeader, + } = prevProps; + const { + shouldShowSavedForLaterHeader: nextShouldShowSavedForLaterHeader, + shouldShowPinnedHeader: nextShouldShowPinnedHeader, + shouldShowReminderHeader: nextShouldShowReminderHeader, + shouldShowSentToChannelHeader: nextShouldShowSentToChannelHeader, + } = nextProps; + + const shouldShowSavedForLaterHeaderEqual = + prevShouldShowSavedForLaterHeader === nextShouldShowSavedForLaterHeader; + if (!shouldShowSavedForLaterHeaderEqual) { + return false; + } - const messageEqual = - prevMessage.id === nextMessage.id && prevMessage.pinned === nextMessage.pinned; - if (!messageEqual) { + const shouldShowPinnedHeaderEqual = prevShouldShowPinnedHeader === nextShouldShowPinnedHeader; + if (!shouldShowPinnedHeaderEqual) { + return false; + } + + const shouldShowReminderHeaderEqual = + prevShouldShowReminderHeader === nextShouldShowReminderHeader; + if (!shouldShowReminderHeaderEqual) { + return false; + } + + const shouldShowSentToChannelHeaderEqual = + prevShouldShowSentToChannelHeader === nextShouldShowSentToChannelHeader; + if (!shouldShowSentToChannelHeaderEqual) { return false; } @@ -43,9 +101,40 @@ export type MessageHeaderProps = Partial>; export const MessageHeader = (props: MessageHeaderProps) => { const { message } = useMessageContext(); - const { MessagePinnedHeader } = useMessagesContext(); + const { + MessagePinnedHeader, + MessageReminderHeader, + MessageSavedForLaterHeader, + SentToChannelHeader, + } = useMessagesContext(); + const reminder = useMessageReminder(message.id); + + const shouldShowSavedForLaterHeader = reminder && !reminder.remindAt; + const shouldShowReminderHeader = reminder && reminder.remindAt; + const shouldShowPinnedHeader = !!message?.pinned; + const shouldShowSentToChannelHeader = !!message?.show_in_channel; + + if ( + !shouldShowPinnedHeader && + !shouldShowSavedForLaterHeader && + !shouldShowReminderHeader && + !shouldShowSentToChannelHeader + ) { + return null; + } return ( - + ); }; diff --git a/package/src/components/Message/hooks/useCreateMessageContext.ts b/package/src/components/Message/hooks/useCreateMessageContext.ts index 0484fb0a2e..6fc3953a83 100644 --- a/package/src/components/Message/hooks/useCreateMessageContext.ts +++ b/package/src/components/Message/hooks/useCreateMessageContext.ts @@ -41,6 +41,7 @@ export const useCreateMessageContext = ({ onOpenThread, onPress, onPressIn, + onThreadSelect, otherAttachments, preventPress, reactions, @@ -92,6 +93,7 @@ export const useCreateMessageContext = ({ onOpenThread, onPress, onPressIn, + onThreadSelect, otherAttachments, preventPress, reactions, diff --git a/package/src/components/index.ts b/package/src/components/index.ts index 81c347a587..a23fc62bac 100644 --- a/package/src/components/index.ts +++ b/package/src/components/index.ts @@ -98,7 +98,7 @@ export * from './Message/MessageSimple/MessageDeleted'; export * from './Message/MessageSimple/MessageError'; export * from './Message/MessageSimple/MessageFooter'; export * from './Message/MessageSimple/MessageHeader'; -export * from './Message/MessageSimple/Headers/MessagePinnedHeader'; +export * from './Message/MessageSimple/Headers'; export * from './Message/MessageSimple/MessageReplies'; export * from './Message/MessageSimple/MessageRepliesAvatars'; export * from './Message/MessageSimple/MessageSimple'; diff --git a/package/src/contexts/messageContext/MessageContext.tsx b/package/src/contexts/messageContext/MessageContext.tsx index 391da93775..043c70b793 100644 --- a/package/src/contexts/messageContext/MessageContext.tsx +++ b/package/src/contexts/messageContext/MessageContext.tsx @@ -119,6 +119,15 @@ export type MessageContextValue = { preventPress?: boolean; /** Whether or not the avatar show show next to Message */ showAvatar?: boolean; + /** + * Function to handle thread select + * @param message - The message to select + * @param targetedMessageId - The id of the targeted message + * @returns void + * + * TODO: V9: Change function params to an object + */ + onThreadSelect?: (message: LocalMessage, targetedMessageId?: string) => void; } & Pick & Pick; diff --git a/package/src/contexts/messagesContext/MessagesContext.tsx b/package/src/contexts/messagesContext/MessagesContext.tsx index 4dbef94bf8..fb15642f58 100644 --- a/package/src/contexts/messagesContext/MessagesContext.tsx +++ b/package/src/contexts/messagesContext/MessagesContext.tsx @@ -30,6 +30,9 @@ import type { MessageProps, } from '../../components/Message/Message'; import type { MessagePinnedHeaderProps } from '../../components/Message/MessageSimple/Headers/MessagePinnedHeader'; +import type { MessageReminderHeaderProps } from '../../components/Message/MessageSimple/Headers/MessageReminderHeader'; +import type { MessageSavedForLaterHeaderProps } from '../../components/Message/MessageSimple/Headers/MessageSavedForLaterHeader'; +import type { SentToChannelHeaderProps } from '../../components/Message/MessageSimple/Headers/SentToChannelHeader'; import type { MessageAvatarProps } from '../../components/Message/MessageSimple/MessageAvatar'; import type { MessageBlockedProps } from '../../components/Message/MessageSimple/MessageBlocked'; import type { MessageBounceProps } from '../../components/Message/MessageSimple/MessageBounce'; @@ -237,6 +240,18 @@ export type MessagesContextValue = Pick; + /** + * Custom message reminder component + */ + MessageReminderHeader: React.ComponentType; + /** + * Custom message saved for later component + */ + MessageSavedForLaterHeader: React.ComponentType; + /** + * Custom message sent to channel component + */ + SentToChannelHeader: React.ComponentType; /** * UI component for MessageReactionPicker */ diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 886e9e4790..73ec7ef853 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -699,6 +699,22 @@ export type Theme = { container: ViewStyle; label: TextStyle; }; + savedForLaterHeader: { + container: ViewStyle; + label: TextStyle; + }; + reminderHeader: { + container: ViewStyle; + label: TextStyle; + dot: TextStyle; + time: TextStyle; + }; + sentToChannelHeader: { + container: ViewStyle; + label: TextStyle; + dot: TextStyle; + link: TextStyle; + }; reactionListBottom: { contentContainer: ViewStyle; columnWrapper: ViewStyle; @@ -1539,6 +1555,22 @@ export const defaultTheme: Theme = { container: {}, label: {}, }, + savedForLaterHeader: { + container: {}, + label: {}, + }, + reminderHeader: { + container: {}, + label: {}, + dot: {}, + time: {}, + }, + sentToChannelHeader: { + container: {}, + label: {}, + dot: {}, + link: {}, + }, reactionListBottom: { contentContainer: {}, columnWrapper: {}, diff --git a/package/src/contexts/threadContext/ThreadContext.tsx b/package/src/contexts/threadContext/ThreadContext.tsx index d60d6b58b1..7270763cb6 100644 --- a/package/src/contexts/threadContext/ThreadContext.tsx +++ b/package/src/contexts/threadContext/ThreadContext.tsx @@ -26,6 +26,12 @@ export type ThreadContextValue = { threadInstance?: Thread | null; threadLoadingMore?: boolean; threadLoadingMoreRecent?: boolean; + /** + * Function to handle back press on thread + * @param messageId - The id of the message to go back to + * @returns void + */ + onBackPressThread?: (messageId?: string) => void; }; export const ThreadContext = React.createContext(DEFAULT_BASE_CONTEXT_VALUE as ThreadContextValue); diff --git a/package/src/i18n/en.json b/package/src/i18n/en.json index e9640e72fa..43f607545d 100644 --- a/package/src/i18n/en.json +++ b/package/src/i18n/en.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Device gallery permissions is used to take photos or videos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Do you want to send a copy of this message to a moderator for further investigation?", "Due since {{ dueSince }}": "Due since {{ dueSince }}", - "Due {{ timeLeft }}": "Due {{ timeLeft }}", "Edit Message": "Edit Message", "Edited": "Edited", "Editing Message": "Editing Message", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} Reactions", "Tap to remove": "Tap to remove", "Draft": "Draft", + "Reminder set": "Reminder set", + "Also sent in channel": "Also sent in channel", + "Replied to a thread": "Replied to a thread", + "View": "View", + "Reminder overdue": "Reminder overdue", "Poll has ended": "Poll has ended" } diff --git a/package/src/i18n/es.json b/package/src/i18n/es.json index acf1fac499..981acfcc62 100644 --- a/package/src/i18n/es.json +++ b/package/src/i18n/es.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Los permisos de la galería del dispositivo se utilizan para tomar fotos o videos.", "Do you want to send a copy of this message to a moderator for further investigation?": "¿Deseas enviar una copia de este mensaje a un moderador para una investigación adicional?", "Due since {{ dueSince }}": "Debido desde {{ dueSince }}", - "Due {{ timeLeft }}": "Debido {{ timeLeft }}", "Edit Message": "Editar mensaje", "Edited": "Editado", "Editing Message": "Editando mensaje", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} reacciones", "Tap to remove": "Toca para quitar", "Draft": "Borrador", + "Reminder set": "Recordatorio establecido", + "Also sent in channel": "También enviado en el canal", + "Replied to a thread": "Respondido a un hilo", + "View": "Ver", + "Reminder overdue": "Recordatorio vencido", "Poll has ended": "Votación finalizada" } diff --git a/package/src/i18n/fr.json b/package/src/i18n/fr.json index 57f3ac6fa6..6527b6d296 100644 --- a/package/src/i18n/fr.json +++ b/package/src/i18n/fr.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Les autorisations de la galerie de l'appareil sont utilisées pour prendre des photos ou des vidéos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Voulez-vous envoyer une copie de ce message à un modérateur pour une enquête plus approfondie?", "Due since {{ dueSince }}": "Échéance depuis {{ dueSince }}", - "Due {{ timeLeft }}": "Échéance {{ timeLeft }}", "Edit Message": "Éditer un message", "Edited": "Édité", "Editing Message": "Édite un message", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} réactions", "Tap to remove": "Appuyez pour retirer", "Draft": "Brouillon", + "Reminder set": "Recordatorio establecido", + "Also sent in channel": "También enviado en el canal", + "Replied to a thread": "Respondido a un hilo", + "View": "Ver", + "Reminder overdue": "Recordatorio vencido", "Poll has ended": "Vote terminé" } diff --git a/package/src/i18n/he.json b/package/src/i18n/he.json index af03c4a446..b104ee010c 100644 --- a/package/src/i18n/he.json +++ b/package/src/i18n/he.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "הרשאות גלריית המכשיר משמשות לצילום תמונות או סרטונים.", "Do you want to send a copy of this message to a moderator for further investigation?": "האם את/ה רוצה לשלוח עותק של הודעה זו למנחה להמשך חקירה?", "Due since {{ dueSince }}": "מועד אחרון מאז {{ dueSince }}", - "Due {{ timeLeft }}": "מועד אחרון {{ timeLeft }}", "Edit Message": "ערוך הודעה", "Edited": "נערך", "Editing Message": "הודעה בעריכה", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} תגובות", "Tap to remove": "הקש כדי להסיר", "Draft": "טיוטה", + "Reminder set": "הזמן הוקם", + "Also sent in channel": "שלח/י גם לשיחה", + "Replied to a thread": "הגב/י בשרשור", + "View": "צפה", + "Reminder overdue": "הזמן פג", "Poll has ended": "ההצבעה הסתיימה" } diff --git a/package/src/i18n/hi.json b/package/src/i18n/hi.json index 380baad4a7..442f144292 100644 --- a/package/src/i18n/hi.json +++ b/package/src/i18n/hi.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "डिवाइस गैलरी की अनुमतियों का उपयोग फोटो या वीडियो लेने के लिए किया जाता है।", "Do you want to send a copy of this message to a moderator for further investigation?": "क्या आप इस संदेश की एक प्रति आगे की जाँच के लिए किसी मॉडरेटर को भेजना चाहते हैं?", "Due since {{ dueSince }}": "{{ dueSince }} से देय है", - "Due {{ timeLeft }}": "{{ timeLeft }} बचा है", "Edit Message": "मैसेज में बदलाव करे", "Edited": "मैसेज बदला गया है", "Editing Message": "मैसेज बदला जा रहा है", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} प्रतिक्रियाएँ", "Tap to remove": "हटाने के लिए टैप करें", "Draft": "ड्राफ्ट", + "Reminder set": "रीमिंडर सेट किया गया", + "Also sent in channel": "चैनल में भी भेजा गया", + "Replied to a thread": "थ्रेड में उत्तर दिया", + "View": "देखें", + "Reminder overdue": "रीमिंडर ओवरडो", "Poll has ended": "वोट समाप्त" } diff --git a/package/src/i18n/it.json b/package/src/i18n/it.json index 52358aa6f3..b41c526707 100644 --- a/package/src/i18n/it.json +++ b/package/src/i18n/it.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Le autorizzazioni della galleria del dispositivo vengono utilizzate per scattare foto o video.", "Do you want to send a copy of this message to a moderator for further investigation?": "Vuoi inviare una copia di questo messaggio a un moderatore per ulteriori indagini?", "Due since {{ dueSince }}": "Scadenza dal {{ dueSince }}", - "Due {{ timeLeft }}": "Scadenza {{ timeLeft }}", "Edit Message": "Modifica Messaggio", "Edited": "Modificato", "Editing Message": "Modificando il Messaggio", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} reazioni", "Tap to remove": "Tocca per rimuovere", "Draft": "Borrador", + "Reminder set": "Recordatorio establecido", + "Also sent in channel": "También enviado en el canal", + "Replied to a thread": "Respondido a un hilo", + "View": "Ver", + "Reminder overdue": "Recordatorio vencido", "Poll has ended": "Votazione terminata" } diff --git a/package/src/i18n/ja.json b/package/src/i18n/ja.json index 3151695cdc..015821c2a8 100644 --- a/package/src/i18n/ja.json +++ b/package/src/i18n/ja.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "デバイスギャラリーの権限は写真やビデオを撮るために使用されます。", "Do you want to send a copy of this message to a moderator for further investigation?": "このメッセージのコピーをモデレーターに送信して、さらに調査しますか?", "Due since {{ dueSince }}": "期限は {{ dueSince }} からです", - "Due {{ timeLeft }}": "期限は {{ timeLeft }} です", "Edit Message": "メッセージを編集", "Edited": "編集済み", "Editing Message": "メッセージを編集中", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}}件のリアクション", "Tap to remove": "タップして削除", "Draft": "下書き", + "Reminder set": "リマインダー設定", + "Also sent in channel": "チャンネルにも送信", + "Replied to a thread": "スレッドに返信", + "View": "表示", + "Reminder overdue": "リマインダー期限切れ", "Poll has ended": "投票終了" } diff --git a/package/src/i18n/ko.json b/package/src/i18n/ko.json index c867997a70..30d39822a4 100644 --- a/package/src/i18n/ko.json +++ b/package/src/i18n/ko.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "장치 갤러리 권한은 사진 또는 비디오를 촬영하는 데 사용됩니다.", "Do you want to send a copy of this message to a moderator for further investigation?": "이 메시지의 복사본을 운영자에게 보내 추가 조사를합니까?", "Due since {{ dueSince }}": "기한은 {{ dueSince }}부터입니다.", - "Due {{ timeLeft }}": "기한은 {{ timeLeft }}입니다.", "Edit Message": "메시지 수정", "Edited": "편집됨", "Editing Message": "메시지 편집중", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}}개의 반응", "Tap to remove": "탭하여 제거", "Draft": "초안", + "Reminder set": "리마인더 설정", + "Also sent in channel": "채널에도 전송", + "Replied to a thread": "스레드에 답장", + "View": "보기", + "Reminder overdue": "리마인더 만료", "Poll has ended": "투표 종료됨" } diff --git a/package/src/i18n/nl.json b/package/src/i18n/nl.json index 110f25055f..d8afc01355 100644 --- a/package/src/i18n/nl.json +++ b/package/src/i18n/nl.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Apparaatgallerijmachtigingen worden gebruikt om foto’s of video’s te maken.", "Do you want to send a copy of this message to a moderator for further investigation?": "Wil je een kopie van dit bericht naar een moderator sturen voor verder onderzoek?", "Due since {{ dueSince }}": "Vervaldatum sinds {{ dueSince }}", - "Due {{ timeLeft }}": "Vervaldatum {{ timeLeft }}", "Edit Message": "Pas bericht aan", "Edited": "Bewerkt", "Editing Message": "Bericht aanpassen", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} reacties", "Tap to remove": "Tik om te verwijderen", "Draft": "Ontwerp", + "Reminder set": "Reminder set", + "Also sent in channel": "Also sent in channel", + "Replied to a thread": "Replied to a thread", + "View": "View", + "Reminder overdue": "Reminder overdue", "Poll has ended": "Stemmen beëindigd" } diff --git a/package/src/i18n/pt-br.json b/package/src/i18n/pt-br.json index d197d1d594..a2f2fa827f 100644 --- a/package/src/i18n/pt-br.json +++ b/package/src/i18n/pt-br.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "As permissões da galeria do dispositivo são usadas para tirar fotos ou vídeos.", "Do you want to send a copy of this message to a moderator for further investigation?": "Deseja enviar uma cópia desta mensagem para um moderador para investigação adicional?", "Due since {{ dueSince }}": "Vencido desde {{ dueSince }}", - "Due {{ timeLeft }}": "Vencido {{ timeLeft }}", "Edit Message": "Editar Mensagem", "Edited": "Editado", "Editing Message": "Editando Mensagem", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} reações", "Tap to remove": "Toque para remover", "Draft": "Rascunho", + "Reminder set": "Recordatorio establecido", + "Also sent in channel": "También enviado en el canal", + "Replied to a thread": "Respondido a un hilo", + "View": "Ver", + "Reminder overdue": "Recordatorio vencido", "Poll has ended": "Votação encerrada" } diff --git a/package/src/i18n/ru.json b/package/src/i18n/ru.json index ba38b33512..bffbac0807 100644 --- a/package/src/i18n/ru.json +++ b/package/src/i18n/ru.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Разрешения галереи устройства используются для съемки фото или видео.", "Do you want to send a copy of this message to a moderator for further investigation?": "Вы хотите отправить копию этого сообщения модератору для дальнейшего изучения?", "Due since {{ dueSince }}": "Срок с {{ dueSince }}", - "Due {{ timeLeft }}": "Срок {{ timeLeft }}", "Edit Message": "Редактировать сообщение", "Edited": "Отредактировано", "Editing Message": "Редактирование сообщения", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} реакций", "Tap to remove": "Нажмите, чтобы удалить", "Draft": "Черновик", + "Reminder set": "Напоминание установлено", + "Also sent in channel": "Также отправлено в канал", + "Replied to a thread": "Ответил на тему", + "View": "Посмотреть", + "Reminder overdue": "Напоминание просрочено", "Poll has ended": "Голосование завершено" } diff --git a/package/src/i18n/tr.json b/package/src/i18n/tr.json index 7acbab569f..167d288c06 100644 --- a/package/src/i18n/tr.json +++ b/package/src/i18n/tr.json @@ -28,7 +28,6 @@ "Device gallery permissions is used to take photos or videos.": "Cihaz galerisi izinleri fotoğraf veya video çekmek için kullanılır.", "Do you want to send a copy of this message to a moderator for further investigation?": "Detaylı inceleme için bu mesajın kopyasını moderatöre göndermek istiyor musunuz?", "Due since {{ dueSince }}": "Son tarihi {{ dueSince }} itibarıyla geçmiştir.", - "Due {{ timeLeft }}": "Son tarihi {{ timeLeft }}'dir.", "Edit Message": "Mesajı Düzenle", "Edited": "Düzenlendi", "Editing Message": "Mesaj Düzenleniyor", @@ -173,5 +172,10 @@ "{{count}} Reactions_other": "{{count}} tepki", "Tap to remove": "Kaldırmak için dokunun", "Draft": "Taslak", + "Reminder set": "Hatırlatıcı ayarlandı", + "Also sent in channel": "Kanala da gönderildi", + "Replied to a thread": "Konuya yanıt verildi", + "View": "Görüntüle", + "Reminder overdue": "Hatırlatıcı süresi doldu", "Poll has ended": "Oylama sona erdi" } diff --git a/package/src/icons/ArrowUpRight.tsx b/package/src/icons/ArrowUpRight.tsx new file mode 100644 index 0000000000..d7cfd21239 --- /dev/null +++ b/package/src/icons/ArrowUpRight.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +export const ArrowUpRight = ({ height = 16, width = 16, ...rest }: IconProps) => { + return ( + + + + ); +}; diff --git a/package/src/icons/Bell.tsx b/package/src/icons/Bell.tsx new file mode 100644 index 0000000000..095472f198 --- /dev/null +++ b/package/src/icons/Bell.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +export const Bell = ({ height = 16, width = 16, ...rest }: IconProps) => { + return ( + + + + ); +}; diff --git a/package/src/icons/Bookmark.tsx b/package/src/icons/Bookmark.tsx new file mode 100644 index 0000000000..b2c6766bf3 --- /dev/null +++ b/package/src/icons/Bookmark.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { Path, Svg } from 'react-native-svg'; + +import { IconProps } from './utils/base'; + +export const Bookmark = ({ height, width, ...rest }: IconProps) => ( + + + +); diff --git a/package/src/icons/index.ts b/package/src/icons/index.ts index 431c7fe51b..a45b453a2c 100644 --- a/package/src/icons/index.ts +++ b/package/src/icons/index.ts @@ -104,3 +104,4 @@ export * from './UnreadIndicator'; export * from './FilePickerIcon'; export * from './CommandsIcon'; export * from './CurveLineLeftUpReply'; +export * from './Bell';