From 04dfe295e6e3f9845c3175c2f328aa9a81ec1844 Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 10:38:57 +0100 Subject: [PATCH 1/7] feat: show username in metadata only for 3+ members --- src/components/Message/MessageSimple.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 4fcdbeb3d..ded032cfb 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -38,7 +38,11 @@ import { useComponentContext } from '../../context/ComponentContext'; import type { MessageContextValue } from '../../context/MessageContext'; import { useMessageContext } from '../../context/MessageContext'; -import { useChatContext, useTranslationContext } from '../../context'; +import { + useChannelStateContext, + useChatContext, + useTranslationContext, +} from '../../context'; import { MessageEditedTimestamp } from './MessageEditedTimestamp'; import type { MessageUIComponentProps } from './types'; @@ -64,8 +68,10 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { showAvatar = 'incoming', threadList, } = props; - const { client } = useChatContext('MessageSimple'); - const { t } = useTranslationContext('MessageSimple'); + const { channel } = useChannelStateContext(); + const { client } = useChatContext(); + const { t } = useTranslationContext(); + const memberCount = Object.keys(channel?.state?.members ?? {}).length; const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false); const [isEditedTimestampOpen, setEditedTimestampOpen] = useState(false); const reminder = useMessageReminder(message.id); @@ -231,7 +237,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { {showMetadata && (
- {!isMyMessage() && !!message.user && ( + {!isMyMessage() && !!message.user && memberCount > 2 && ( {message.user.name || message.user.id} From 247674559e1d7956df09bf4f0b8393a6f9e4e887 Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 10:39:39 +0100 Subject: [PATCH 2/7] feat: show "Pinned by You" for messages pinned by own user --- src/components/Message/PinIndicator.tsx | 10 ++++++++-- src/i18n/de.json | 1 + src/i18n/en.json | 1 + src/i18n/es.json | 1 + src/i18n/fr.json | 1 + src/i18n/hi.json | 1 + src/i18n/it.json | 1 + src/i18n/ja.json | 1 + src/i18n/ko.json | 1 + src/i18n/nl.json | 1 + src/i18n/pt.json | 1 + src/i18n/ru.json | 1 + src/i18n/tr.json | 1 + 13 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/Message/PinIndicator.tsx b/src/components/Message/PinIndicator.tsx index cc9249d6f..a8e1b1858 100644 --- a/src/components/Message/PinIndicator.tsx +++ b/src/components/Message/PinIndicator.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { IconPin } from '../Icons'; -import { useTranslationContext } from '../../context'; +import { useChatContext, useTranslationContext } from '../../context'; import type { LocalMessage } from 'stream-chat'; export type PinIndicatorProps = { @@ -14,12 +14,18 @@ export type PinIndicatorProps = { */ export const PinIndicator = ({ message }: PinIndicatorProps) => { const { t } = useTranslationContext(); + const { client } = useChatContext(); if (!message) return null; + const isOwnPin = !!message.pinned_by?.id && message.pinned_by.id === client.user?.id; const name = message.pinned_by?.name ?? message.pinned_by?.id ?? ''; - const label = name ? t('Pinned by {{ name }}', { name }) : t('Message pinned'); + const label = isOwnPin + ? t('Pinned by You') + : name + ? t('Pinned by {{ name }}', { name }) + : t('Message pinned'); return (
diff --git a/src/i18n/de.json b/src/i18n/de.json index 2995a44db..404f391d9 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -209,6 +209,7 @@ "Photo": "Foto", "Pin": "Anheften", "Pinned by {{ name }}": "Angeheftet von {{ name }}", + "Pinned by You": "Von Ihnen angeheftet", "Play video": "Video abspielen", "Poll": "Umfrage", "Poll comments": "Umfragekommentare", diff --git a/src/i18n/en.json b/src/i18n/en.json index 449e320a2..94598b4ba 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -209,6 +209,7 @@ "Photo": "Photo", "Pin": "Pin", "Pinned by {{ name }}": "Pinned by {{ name }}", + "Pinned by You": "Pinned by You", "Play video": "Play video", "Poll": "Poll", "Poll comments": "Poll comments", diff --git a/src/i18n/es.json b/src/i18n/es.json index 742831c68..64e17dc41 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -214,6 +214,7 @@ "Photo": "Foto", "Pin": "Fijar", "Pinned by {{ name }}": "Fijado por {{ name }}", + "Pinned by You": "Anclado por ti", "Play video": "Reproducir video", "Poll": "Encuesta", "Poll comments": "Comentarios de la encuesta", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index f31621c2c..6c30a682f 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -214,6 +214,7 @@ "Photo": "Photo", "Pin": "Épingler", "Pinned by {{ name }}": "Épinglé par {{ name }}", + "Pinned by You": "Épinglé par vous", "Play video": "Lire la vidéo", "Poll": "Sondage", "Poll comments": "Commentaires du sondage", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 70c85f70a..c1dbb6d51 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -210,6 +210,7 @@ "Photo": "फ़ोटो", "Pin": "पिन", "Pinned by {{ name }}": "{{ name }} द्वारा पिन किया गया", + "Pinned by You": "आपके द्वारा पिन किया गया", "Play video": "वीडियो चलाएं", "Poll": "मतदान", "Poll comments": "मतदान टिप्पणियाँ", diff --git a/src/i18n/it.json b/src/i18n/it.json index 3d8659da0..e8012052c 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -214,6 +214,7 @@ "Photo": "Foto", "Pin": "Appunta", "Pinned by {{ name }}": "Appuntato da {{ name }}", + "Pinned by You": "Fissato da te", "Play video": "Riproduci video", "Poll": "Sondaggio", "Poll comments": "Commenti del sondaggio", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 813fa5dbd..6ca9d22ea 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -209,6 +209,7 @@ "Photo": "写真", "Pin": "ピン", "Pinned by {{ name }}": "{{ name }}がピンしました", + "Pinned by You": "あなたがピン留めしました", "Play video": "動画を再生", "Poll": "投票", "Poll comments": "投票コメント", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 929529d10..0ec450017 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -209,6 +209,7 @@ "Photo": "사진", "Pin": "핀", "Pinned by {{ name }}": "{{ name }}님이 핀함", + "Pinned by You": "내가 고정함", "Play video": "동영상 재생", "Poll": "투표", "Poll comments": "투표 댓글", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 689960ea8..0bbd930ab 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -209,6 +209,7 @@ "Photo": "Foto", "Pin": "Vastmaken", "Pinned by {{ name }}": "Vastgemaakt door {{ name }}", + "Pinned by You": "Door jou vastgezet", "Play video": "Video afspelen", "Poll": "Peiling", "Poll comments": "Peiling opmerkingen", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 80ef00bad..d76475bc6 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -214,6 +214,7 @@ "Photo": "Foto", "Pin": "Fixar", "Pinned by {{ name }}": "Fixado por {{ name }}", + "Pinned by You": "Fixado por você", "Play video": "Reproduzir vídeo", "Poll": "Enquete", "Poll comments": "Comentários da pesquisa", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index c27665413..04d02ec51 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -219,6 +219,7 @@ "Photo": "Фото", "Pin": "Закрепить", "Pinned by {{ name }}": "Закреплено: {{ name }}", + "Pinned by You": "Закреплено вами", "Play video": "Воспроизвести видео", "Poll": "Опрос", "Poll comments": "Комментарии к опросу", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 63749a679..fb48b7a6d 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -209,6 +209,7 @@ "Photo": "Fotoğraf", "Pin": "Sabitle", "Pinned by {{ name }}": "{{ name }} sabitledi", + "Pinned by You": "Sizin sabitlediğiniz", "Play video": "Videoyu oynat", "Poll": "Anket", "Poll comments": "Anket yorumları", From 4db82a241c11f050964f4949004fed9be9a0be22 Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 10:57:49 +0100 Subject: [PATCH 3/7] feat: set max message width to 400px --- src/components/Message/styling/Message.scss | 22 ++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 452d032a1..78e7b8cfb 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -81,7 +81,7 @@ --str-chat__message-pinned-background-color: var(--background-core-highlight); /* The maximum allowed width of the message component */ - --str-chat__message-max-width: calc(var(--str-chat__spacing-px) * 480); + --str-chat__message-max-width: calc(var(--str-chat__spacing-px) * 400); /* The maximum allowed width of the message component, if it has attachments */ --str-chat__message-with-attachment-max-width: calc(var(--str-chat__spacing-px) * 300); @@ -93,12 +93,12 @@ padding-block-end: var(--spacing-xxxs); } -.str-chat__li--bottom { +.str-chat__li--bottom { padding-block-start: var(--spacing-xxxs); padding-block-end: var(--spacing-xs); } -.str-chat__li--middle { +.str-chat__li--middle { padding-block: var(--spacing-xxxs); } @@ -286,7 +286,9 @@ } } - &.str-chat__message--me .str-chat__message-pin-indicator .str-chat__message-pin-indicator__content { + &.str-chat__message--me + .str-chat__message-pin-indicator + .str-chat__message-pin-indicator__content { justify-content: flex-end; } @@ -372,7 +374,6 @@ } } } - } &.str-chat__message--deleted { @@ -510,11 +511,14 @@ display: flex; flex-wrap: wrap; align-items: center; - margin-block-start: var(--str-chat__spacing-0_5); + height: var(--size-24); color: var(--chat-text-timestamp); - font-size: var(--typography-font-size-xs,); - font-weight: var(--typography-font-weight-regular); - line-height: var(--typography-line-height-tight); + + * { + font-size: var(--typography-font-size-xs,); + font-weight: var(--typography-font-weight-regular); + line-height: var(--typography-line-height-tight); + } .str-chat__message-simple-name { @include utils.prevent-glitch-text-overflow; From 5260f320dcf082b224b63e238401e0d7d84390b9 Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 10:58:05 +0100 Subject: [PATCH 4/7] style: fix linter issues --- src/components/Avatar/styling/Avatar.scss | 2 +- src/components/Dialog/styling/Alert.scss | 3 +-- src/components/Dialog/styling/Prompt.scss | 5 ---- .../MessageBounce/styling/index.scss | 2 +- .../MessageList/styling/MessageList.scss | 25 ++++++++++++++----- .../styling/VirtualizedMessageList.scss | 5 +++- .../Reactions/styling/ReactionsListModal.scss | 2 +- src/components/Reactions/styling/index.scss | 2 +- 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/Avatar/styling/Avatar.scss b/src/components/Avatar/styling/Avatar.scss index c1bb2cb78..91a3b1920 100644 --- a/src/components/Avatar/styling/Avatar.scss +++ b/src/components/Avatar/styling/Avatar.scss @@ -100,7 +100,7 @@ font-size: var(--typography-font-size-md); } - &.str-chat__avatar--size-md { + &.str-chat__avatar--size-md { --avatar-size: 32px; --avatar-online-badge-size: 12px; --avatar-icon-size: var(--icon-size-md); diff --git a/src/components/Dialog/styling/Alert.scss b/src/components/Dialog/styling/Alert.scss index 55a4ac5e9..7a5539f0c 100644 --- a/src/components/Dialog/styling/Alert.scss +++ b/src/components/Dialog/styling/Alert.scss @@ -1,4 +1,3 @@ - @mixin flex-column { display: flex; flex-direction: column; @@ -45,4 +44,4 @@ width: 100%; } } -} \ No newline at end of file +} diff --git a/src/components/Dialog/styling/Prompt.scss b/src/components/Dialog/styling/Prompt.scss index 26ca0f782..25b94246e 100644 --- a/src/components/Dialog/styling/Prompt.scss +++ b/src/components/Dialog/styling/Prompt.scss @@ -1,10 +1,5 @@ @use '../../../styling/utils'; -// todo: once we have designs for dialogs + context menus create base class instead of a mixin -@mixin dialog-base { - -} - .str-chat__dialog-overlay { inset: 0; position: absolute; diff --git a/src/components/MessageBounce/styling/index.scss b/src/components/MessageBounce/styling/index.scss index b9060626b..f4c0360de 100644 --- a/src/components/MessageBounce/styling/index.scss +++ b/src/components/MessageBounce/styling/index.scss @@ -1 +1 @@ -@use "MessageBouncePrompt"; \ No newline at end of file +@use 'MessageBouncePrompt'; diff --git a/src/components/MessageList/styling/MessageList.scss b/src/components/MessageList/styling/MessageList.scss index 91596687d..b0bafdbdd 100644 --- a/src/components/MessageList/styling/MessageList.scss +++ b/src/components/MessageList/styling/MessageList.scss @@ -15,7 +15,10 @@ overscroll-behavior: none; width: 100%; /* Max container 800px, 16px padding → 768px readable content; matches composer width + padding */ - max-width: calc(var(--str-chat__message-composer-max-width) + var(--str-chat__message-composer-padding)); + max-width: calc( + var(--str-chat__message-composer-max-width) + + var(--str-chat__message-composer-padding) + ); height: 100%; max-height: 100%; @include utils.component-layer-overrides('message-list'); @@ -101,13 +104,17 @@ --str-chat__message-list-border-inline-end: none; /* The border radius used for the borders of the jump to latest message button */ - --str-chat__jump-to-latest-message-border-radius: var(--str-chat__circle-fab-border-radius); + --str-chat__jump-to-latest-message-border-radius: var( + --str-chat__circle-fab-border-radius + ); /* The text/icon color of the jump to latest message button */ --str-chat__jump-to-latest-message-color: var(--str-chat__circle-fab-color); /* The background color of the jump to latest message button */ - --str-chat__jump-to-latest-message-background-color: var(--str-chat__circle-fab-background-color); + --str-chat__jump-to-latest-message-background-color: var( + --str-chat__circle-fab-background-color + ); /* The background color of the jump to latest message button in pressed state */ --str-chat__jump-to-latest-message-pressed-background-color: var( @@ -123,7 +130,9 @@ ); /* Bottom border of the jump to latest message button */ - --str-chat__jump-to-latest-message-border-block-end: var(--str-chat__circle-fab-border-block-end); + --str-chat__jump-to-latest-message-border-block-end: var( + --str-chat__circle-fab-border-block-end + ); /* Left (right in RTL layout) border of the jump to latest message button */ --str-chat__jump-to-latest-message-border-inline-start: var( @@ -153,14 +162,18 @@ } .str-chat__jump-to-latest-message { - --str-chat-icon-color: var(--str-chat__jump-to-latest-message-unread-count-background-color); + --str-chat-icon-color: var( + --str-chat__jump-to-latest-message-unread-count-background-color + ); .str-chat__circle-fab { @include utils.component-layer-overrides('jump-to-latest-message'); @include utils.circle-fab-overrides('jump-to-latest-message'); .str-chat__jump-to-latest-unread-count { - background-color: var(--str-chat__jump-to-latest-message-unread-count-background-color); + background-color: var( + --str-chat__jump-to-latest-message-unread-count-background-color + ); color: var(--str-chat__jump-to-latest-message-unread-count-color); border-radius: var(--str-chat__jump-to-latest-message-border-radius); font: var(--str-chat__caption-text); diff --git a/src/components/MessageList/styling/VirtualizedMessageList.scss b/src/components/MessageList/styling/VirtualizedMessageList.scss index bc50fa0fe..41f0985a9 100644 --- a/src/components/MessageList/styling/VirtualizedMessageList.scss +++ b/src/components/MessageList/styling/VirtualizedMessageList.scss @@ -11,7 +11,10 @@ margin: 0; width: 100%; /* Max container = composer width + padding; matches message list */ - max-width: calc(var(--str-chat__message-composer-max-width) + var(--str-chat__message-composer-padding)); + max-width: calc( + var(--str-chat__message-composer-max-width) + + var(--str-chat__message-composer-padding) + ); height: 100%; .str-chat__message-list-scroll { diff --git a/src/components/Reactions/styling/ReactionsListModal.scss b/src/components/Reactions/styling/ReactionsListModal.scss index ca6311e1f..f657aafe6 100644 --- a/src/components/Reactions/styling/ReactionsListModal.scss +++ b/src/components/Reactions/styling/ReactionsListModal.scss @@ -82,4 +82,4 @@ font: var(--str-chat__subtitle-text); } } -} \ No newline at end of file +} diff --git a/src/components/Reactions/styling/index.scss b/src/components/Reactions/styling/index.scss index f7dbd0be0..2ff58635a 100644 --- a/src/components/Reactions/styling/index.scss +++ b/src/components/Reactions/styling/index.scss @@ -1,3 +1,3 @@ @use 'ReactionList'; @use 'ReactionSelector'; -@use 'ReactionsListModal'; \ No newline at end of file +@use 'ReactionsListModal'; From a51b47f2fc585dff8bf96f57eaa3a6c49d708bb7 Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 11:21:14 +0100 Subject: [PATCH 5/7] docs: explain how Attachment prop attachmentActionsDefaultFocus should be used --- src/components/Attachment/Attachment.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/Attachment/Attachment.tsx b/src/components/Attachment/Attachment.tsx index b85e712ae..2f38d3236 100644 --- a/src/components/Attachment/Attachment.tsx +++ b/src/components/Attachment/Attachment.tsx @@ -52,7 +52,13 @@ export type AttachmentProps = { attachments: (StreamAttachment | SharedLocationResponse)[]; /** The handler function to call when an action is performed on an attachment, examples include canceling a \/giphy command or shuffling the results. */ actionHandler?: ActionHandlerReturnType; - /** Which action should be focused on initial render, by attachment type (match by action.value) */ + /** + * Which attachment action button receives focus on initial render, keyed by attachment type. + * Values must match an action's `value` (e.g. `'send'`, `'cancel'`, `'shuffle'` for giphy attachment). + * Default: `{ giphy: 'send' }`. + * To disable auto-focus (e.g. when rendering the Giphy preview above the composer so focus + * stays in the message input), pass an empty object: `attachmentActionsDefaultFocus={{}}`. + */ attachmentActionsDefaultFocus?: AttachmentActionsDefaultFocusByType; /** Custom UI component for displaying attachment actions, defaults to and accepts same props as: [AttachmentActions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Attachment/AttachmentActions.tsx) */ AttachmentActions?: React.ComponentType; From f37116c499aa2af6ee4dbd2e5d6519857829f6b3 Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 13:46:19 +0100 Subject: [PATCH 6/7] feat: redesign ReminderNotification --- src/components/Icons/icons.tsx | 16 +++- .../Message/ReminderNotification.tsx | 95 +++++++++++++++---- .../__tests__/ReminderNotification.test.js | 4 +- .../ReminderNotification.test.js.snap | 22 +++-- src/components/Message/styling/Message.scss | 19 +--- .../Message/styling/ReminderNotification.scss | 58 +++++++++++ src/components/Message/styling/index.scss | 1 + src/i18n/de.json | 3 +- src/i18n/en.json | 3 +- src/i18n/es.json | 3 +- src/i18n/fr.json | 3 +- src/i18n/hi.json | 3 +- src/i18n/it.json | 3 +- src/i18n/ja.json | 3 +- src/i18n/ko.json | 3 +- src/i18n/nl.json | 3 +- src/i18n/pt.json | 3 +- src/i18n/ru.json | 3 +- src/i18n/tr.json | 3 +- src/styling/_global-theme-variables.scss | 4 + 20 files changed, 193 insertions(+), 62 deletions(-) create mode 100644 src/components/Message/styling/ReminderNotification.scss diff --git a/src/components/Icons/icons.tsx b/src/components/Icons/icons.tsx index 7990dae7a..8b0d1f0b3 100644 --- a/src/components/Icons/icons.tsx +++ b/src/components/Icons/icons.tsx @@ -73,7 +73,13 @@ export const IconAtSolid = createIcon( export const IconBellNotification = createIcon( 'IconBellNotification', - , + , ); export const IconBellOff = createIcon( @@ -90,7 +96,13 @@ export const IconBellOff = createIcon( export const IconBookmark = createIcon( 'IconBookmark', - , + , ); export const IconBookmarkRemove = createIcon( diff --git a/src/components/Message/ReminderNotification.tsx b/src/components/Message/ReminderNotification.tsx index 5fbcf6d92..e58920227 100644 --- a/src/components/Message/ReminderNotification.tsx +++ b/src/components/Message/ReminderNotification.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useTranslationContext } from '../../context'; import { useStateStore } from '../../store'; import type { Reminder, ReminderState } from 'stream-chat'; +import { IconBellNotification, IconBookmark } from '../Icons'; export type ReminderNotificationProps = { reminder?: Reminder; @@ -11,40 +12,92 @@ const reminderStateSelector = (state: ReminderState) => ({ timeLeftMs: state.timeLeftMs, }); -export const ReminderNotification = ({ reminder }: ReminderNotificationProps) => { +function SavedForLaterContent() { + const { t } = useTranslationContext(); + return ( +

+ + {t('Saved for later')} +

+ ); +} + +const THRESHOLD_RELATIVE_MINUTES = 59; + +function RemindMeContent({ reminder }: { reminder: Reminder }) { const { t } = useTranslationContext(); const { timeLeftMs } = useStateStore(reminder?.state, reminderStateSelector) ?? {}; const stopRefreshBoundaryMs = reminder?.timer.stopRefreshBoundaryMs; const stopRefreshTimeStamp = reminder?.remindAt && stopRefreshBoundaryMs - ? reminder?.remindAt.getTime() + stopRefreshBoundaryMs + ? reminder.remindAt.getTime() + stopRefreshBoundaryMs : undefined; const isBehindRefreshBoundary = !!stopRefreshTimeStamp && new Date().getTime() > stopRefreshTimeStamp; + if (timeLeftMs === null || !reminder.remindAt) return null; + + const nowMs = Date.now(); + const remindAtMs = reminder.remindAt.getTime(); + const diffMs = remindAtMs - nowMs; + const diffMinutes = Math.abs(diffMs) / (60 * 1000); + const useAbsoluteFormat = diffMinutes > THRESHOLD_RELATIVE_MINUTES; + + const renderTime = () => { + if (isBehindRefreshBoundary) { + // Past: reminder time has passed + if (useAbsoluteFormat) { + // > 59 min ago: calendar + time (same as DateSeparator + HH:mm) + // e.g. "Due since Today at 15:00", "Due since Yesterday at 09:30" + return t('Due since {{ dueSince }}', { + dueSince: t('timestamp/ReminderNotification', { + timestamp: reminder.remindAt, + }), + }); + } + // Within 59 min ago: relative + // e.g. "Due since 5 minutes ago", "Due since a minute ago" + return t('Due since {{ dueSince }}', { + dueSince: t('duration/Message reminder', { + milliseconds: diffMs, + }), + }); + } + // Future: reminder not yet due + if (useAbsoluteFormat) { + // > 59 min from now: calendar + time (no "Due" prefix) + // e.g. "Today at 15:00", "Tomorrow at 09:30" + return t('timestamp/ReminderNotification', { + timestamp: reminder.remindAt, + }); + } + // Within 59 min from now: relative + // e.g. "Due in 30 minutes", "Due in a minute" + return t('Due {{ timeLeft }}', { + timeLeft: t('duration/Message reminder', { + milliseconds: timeLeftMs, + }), + }); + }; + return (

- {t('Saved for later')} - {reminder?.remindAt && timeLeftMs !== null && ( - <> - | - - {isBehindRefreshBoundary - ? t('Due since {{ dueSince }}', { - dueSince: t('timestamp/ReminderNotification', { - timestamp: reminder.remindAt, - }), - }) - : t('Due {{ timeLeft }}', { - timeLeft: t('duration/Message reminder', { - milliseconds: timeLeftMs, - }), - })} - - - )} + + {t('Reminder set')} + · + {renderTime()}

); +} + +export const ReminderNotification = ({ reminder }: ReminderNotificationProps) => { + if (!reminder) return null; + + if (!reminder.remindAt) { + return ; + } + + return ; }; diff --git a/src/components/Message/__tests__/ReminderNotification.test.js b/src/components/Message/__tests__/ReminderNotification.test.js index efc003735..03e0f6ad5 100644 --- a/src/components/Message/__tests__/ReminderNotification.test.js +++ b/src/components/Message/__tests__/ReminderNotification.test.js @@ -21,12 +21,12 @@ const renderComponent = async ({ reminder }) => { }; describe('ReminderNotification', () => { - it('displays text for bookmark notifications', async () => { + it('displays text for bookmark notifications (saved for later)', async () => { const reminder = new Reminder({ data: generateReminderResponse() }); const { container } = await renderComponent({ reminder }); expect(container).toMatchSnapshot(); }); - it('displays text for time due in case of timed reminders', async () => { + it('displays text for time due in case of timed reminders (remind me)', async () => { const reminder = new Reminder({ data: generateReminderResponse({ scheduleOffsetMs: 60 * 1000, diff --git a/src/components/Message/__tests__/__snapshots__/ReminderNotification.test.js.snap b/src/components/Message/__tests__/__snapshots__/ReminderNotification.test.js.snap index 492cbc1c9..22223330c 100644 --- a/src/components/Message/__tests__/__snapshots__/ReminderNotification.test.js.snap +++ b/src/components/Message/__tests__/__snapshots__/ReminderNotification.test.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`ReminderNotification displays text for bookmark notifications 1`] = ` +exports[`ReminderNotification displays text for bookmark notifications (saved for later) 1`] = `

Saved for later @@ -18,30 +18,34 @@ exports[`ReminderNotification displays text for reminder deadline if trespassed class="str-chat__message-reminder" > - Saved for later + Reminder set - | + · - + Due since 01/01/1970

`; -exports[`ReminderNotification displays text for time due in case of timed reminders 1`] = ` +exports[`ReminderNotification displays text for time due in case of timed reminders (remind me) 1`] = `

- Saved for later + Reminder set - | + · - + Due in a minute

diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 78e7b8cfb..c3be86077 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -67,14 +67,6 @@ --str-chat__tertiary-surface-color ); - --str-chat__message-reminder-color: var(--str-chat__primary-color); - --str-chat__message-reminder-background-color: transparent; - --str-chat__message-reminder-border-block-start: none; - --str-chat__message-reminder-border-block-end: none; - --str-chat__message-reminder-border-inline-start: none; - --str-chat__message-reminder-border-inline-end: none; - --str-chat__message-reminder-box-shadow: none; - --str-chat__message-reminder-border-radius: 0; --str-chat__message-reactions-host-offset-x: calc(var(--spacing-xs) * -1); /* Background color for pinned messages (Figma: background/core/highlight) */ @@ -232,16 +224,9 @@ hyphens: auto; overflow-wrap: break-word; - .str-chat__message-reminder { - grid-area: message-reminder; - padding-block: var(--spacing-xxs); - margin: 0; - @include utils.component-layer-overrides('message-reminder'); - font: var(--str-chat__caption-medium-text); - } - @mixin message-grid-no-avatar { grid-template-areas: + 'message-saved-for-later' 'pin-indicator' 'message-reminder' 'message' @@ -253,6 +238,7 @@ @mixin message-grid-other-with-avatar { grid-template-areas: + '. message-saved-for-later' '. pin-indicator' '. message-reminder' 'avatar message' @@ -264,6 +250,7 @@ @mixin message-grid-me-with-avatar { grid-template-areas: + 'message-saved-for-later .' 'pin-indicator .' 'message-reminder .' 'message avatar' diff --git a/src/components/Message/styling/ReminderNotification.scss b/src/components/Message/styling/ReminderNotification.scss new file mode 100644 index 000000000..74c1ec09b --- /dev/null +++ b/src/components/Message/styling/ReminderNotification.scss @@ -0,0 +1,58 @@ +@use '../../../styling/utils'; + +.str-chat { + --str-chat__message-saved-for-later-color: var(--str-chat__primary-color); + --str-chat__message-saved-for-later-background-color: transparent; + --str-chat__message-saved-for-later-border-block-start: none; + --str-chat__message-saved-for-later-border-block-end: none; + --str-chat__message-saved-for-later-border-inline-start: none; + --str-chat__message-saved-for-later-border-inline-end: none; + --str-chat__message-saved-for-later-box-shadow: none; + --str-chat__message-saved-for-later-border-radius: 0; + + --str-chat__message-reminder-color: var(--text-primary); + --str-chat__message-reminder-background-color: transparent; + --str-chat__message-reminder-border-block-start: none; + --str-chat__message-reminder-border-block-end: none; + --str-chat__message-reminder-border-inline-start: none; + --str-chat__message-reminder-border-inline-end: none; + --str-chat__message-reminder-box-shadow: none; + --str-chat__message-reminder-border-radius: 0; +} + +/* Saved for Later: above pin indicator. Font and spacing aligned with pin indicator. */ +.str-chat__message-saved-for-later { + display: flex; + align-items: center; + gap: var(--spacing-xxs); + grid-area: message-saved-for-later; + padding-block: var(--spacing-xxs); + margin: 0; + @include utils.component-layer-overrides('message-saved-for-later'); + font: var(--str-chat__metadata-emphasis-text); + + svg path { + stroke-width: 1.5px; + stroke: var(--str-chat__message-saved-for-later-color); + } +} + +/* Remind Me: below pin indicator. Font and spacing aligned with pin indicator. */ +.str-chat__message-reminder { + display: flex; + align-items: center; + gap: var(--spacing-xxs); + grid-area: message-reminder; + padding-block: var(--spacing-xxs); + margin: 0; + @include utils.component-layer-overrides('message-reminder'); + font: var(--str-chat__metadata-emphasis-text); + + svg path { + stroke-width: 1.5px; + } + + .str-chat__message-reminder__time-left { + font: var(--str-chat__metadata-default-text); + } +} diff --git a/src/components/Message/styling/index.scss b/src/components/Message/styling/index.scss index b70cfa252..5a21ca7ae 100644 --- a/src/components/Message/styling/index.scss +++ b/src/components/Message/styling/index.scss @@ -3,6 +3,7 @@ @use 'MessageStatus'; @use 'MessageSystem'; @use 'QuotedMessage'; +@use 'ReminderNotification'; @use 'UnreadMessageNotification'; @use 'UnreadMessagesSeparator'; @use 'MessageRepliesCountButton'; diff --git a/src/i18n/de.json b/src/i18n/de.json index 404f391d9..78ab5b724 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -223,6 +223,7 @@ "Recording format is not supported and cannot be reproduced": "Aufnahmeformat wird nicht unterstützt und kann nicht wiedergegeben werden", "Remind me": "Erinnern", "Remind Me": "Erinnern", + "Reminder set": "Erinnerung gesetzt", "Remove reminder": "Erinnerung entfernen", "Remove save for later": "„Später ansehen“ entfernen", "Reply": "Antworten", @@ -285,7 +286,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf die Kamera in Ihrem Browser", "To start recording, allow the microphone access in your browser": "Um mit der Aufnahme zu beginnen, erlauben Sie den Zugriff auf das Mikrofon in Ihrem Browser", diff --git a/src/i18n/en.json b/src/i18n/en.json index 94598b4ba..252c13ac7 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -223,6 +223,7 @@ "Recording format is not supported and cannot be reproduced": "Recording format is not supported and cannot be reproduced", "Remind me": "Remind me", "Remind Me": "Remind Me", + "Reminder set": "Reminder set", "Remove reminder": "Remove reminder", "Remove save for later": "Remove save for later", "Reply": "Reply", @@ -285,7 +286,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "To start recording, allow the camera access in your browser", "To start recording, allow the microphone access in your browser": "To start recording, allow the microphone access in your browser", diff --git a/src/i18n/es.json b/src/i18n/es.json index 64e17dc41..debed7510 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -228,6 +228,7 @@ "Recording format is not supported and cannot be reproduced": "El formato de grabación no es compatible y no se puede reproducir", "Remind me": "Recordarme", "Remind Me": "Recordarme", + "Reminder set": "Recordatorio establecido", "Remove reminder": "Eliminar recordatorio", "Remove save for later": "Quitar guardar para después", "Reply": "Responder", @@ -294,7 +295,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Para comenzar a grabar, permita el acceso a la cámara en su navegador", "To start recording, allow the microphone access in your browser": "Para comenzar a grabar, permita el acceso al micrófono en su navegador", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 6c30a682f..728a06acf 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -228,6 +228,7 @@ "Recording format is not supported and cannot be reproduced": "Le format d'enregistrement n'est pas pris en charge et ne peut pas être reproduit", "Remind me": "Me rappeler", "Remind Me": "Me rappeler", + "Reminder set": "Rappel défini", "Remove reminder": "Supprimer le rappel", "Remove save for later": "Supprimer « Enregistrer pour plus tard »", "Reply": "Répondre", @@ -294,7 +295,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Pour commencer l'enregistrement, autorisez l'accès à la caméra dans votre navigateur", "To start recording, allow the microphone access in your browser": "Pour commencer l'enregistrement, autorisez l'accès au microphone dans votre navigateur", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index c1dbb6d51..74d1fa61b 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -224,6 +224,7 @@ "Recording format is not supported and cannot be reproduced": "रेकॉर्डिंग फ़ॉर्मेट समर्थित नहीं है और पुनः उत्पन्न नहीं किया जा सकता", "Remind me": "मुझे याद दिलाएं", "Remind Me": "मुझे याद दिलाएं", + "Reminder set": "अनुस्मारक सेट किया गया", "Remove reminder": "रिमाइंडर हटाएं", "Remove save for later": "बाद में देखें हटाएं", "Reply": "जवाब दे दो", @@ -286,7 +287,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में कैमरा तक पहुँच दें", "To start recording, allow the microphone access in your browser": "रिकॉर्डिंग शुरू करने के लिए, अपने ब्राउज़र में माइक्रोफ़ोन तक पहुँच दें", diff --git a/src/i18n/it.json b/src/i18n/it.json index e8012052c..cc307783c 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -228,6 +228,7 @@ "Recording format is not supported and cannot be reproduced": "Il formato di registrazione non è supportato e non può essere riprodotto", "Remind me": "Promemoria", "Remind Me": "Ricordami", + "Reminder set": "Promemoria impostato", "Remove reminder": "Rimuovi promemoria", "Remove save for later": "Rimuovi Salva per dopo", "Reply": "Rispondi", @@ -294,7 +295,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Per iniziare a registrare, consenti l'accesso alla fotocamera nel tuo browser", "To start recording, allow the microphone access in your browser": "Per iniziare a registrare, consenti l'accesso al microfono nel tuo browser", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 6ca9d22ea..79711fc16 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -223,6 +223,7 @@ "Recording format is not supported and cannot be reproduced": "録音形式はサポートされておらず、再生できません", "Remind me": "リマインド", "Remind Me": "リマインダー", + "Reminder set": "リマインダーを設定しました", "Remove reminder": "リマインダーを削除", "Remove save for later": "「後で見る」を削除", "Reply": "返事", @@ -285,7 +286,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "録音を開始するには、ブラウザーでカメラへのアクセスを許可してください", "To start recording, allow the microphone access in your browser": "録音を開始するには、ブラウザーでマイクロフォンへのアクセスを許可してください", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 0ec450017..0ffe0951e 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -223,6 +223,7 @@ "Recording format is not supported and cannot be reproduced": "녹음 형식이 지원되지 않으므로 재생할 수 없습니다", "Remind me": "알림", "Remind Me": "알림 설정", + "Reminder set": "알림 설정됨", "Remove reminder": "알림 제거", "Remove save for later": "나중에 보기 제거", "Reply": "답장", @@ -285,7 +286,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "브라우저에서 카메라 액세스를 허용하여 녹음을 시작합니다", "To start recording, allow the microphone access in your browser": "브라우저에서 마이크로폰 액세스를 허용하여 녹음을 시작합니다", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 0bbd930ab..d3f89760a 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -223,6 +223,7 @@ "Recording format is not supported and cannot be reproduced": "Opnameformaat wordt niet ondersteund en kan niet worden gereproduceerd", "Remind me": "Herinner me", "Remind Me": "Herinner mij", + "Reminder set": "Herinnering ingesteld", "Remove reminder": "Herinnering verwijderen", "Remove save for later": "Verwijder 'Bewaren voor later'", "Reply": "Antwoord", @@ -287,7 +288,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Om te beginnen met opnemen, sta toegang tot de camera toe in uw browser", "To start recording, allow the microphone access in your browser": "Om te beginnen met opnemen, sta toegang tot de microfoon toe in uw browser", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index d76475bc6..a4b5423cd 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -228,6 +228,7 @@ "Recording format is not supported and cannot be reproduced": "Formato de gravação não é suportado e não pode ser reproduzido", "Remind me": "Lembrar-me", "Remind Me": "Lembrar-me", + "Reminder set": "Lembrete definido", "Remove reminder": "Remover lembrete", "Remove save for later": "Remover Salvar para depois", "Reply": "Responder", @@ -294,7 +295,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Para começar a gravar, permita o acesso à câmera no seu navegador", "To start recording, allow the microphone access in your browser": "Para começar a gravar, permita o acesso ao microfone no seu navegador", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 04d02ec51..98b040aeb 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -233,6 +233,7 @@ "Recording format is not supported and cannot be reproduced": "Формат записи не поддерживается и не может быть воспроизведен", "Remind me": "Напомнить мне", "Remind Me": "Напомнить мне", + "Reminder set": "Напоминание установлено", "Remove reminder": "Удалить напоминание", "Remove save for later": "Удалить «Сохранить на потом»", "Reply": "Ответить", @@ -303,7 +304,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Для начала записи разрешите доступ к камере в вашем браузере", "To start recording, allow the microphone access in your browser": "Для начала записи разрешите доступ к микрофону в вашем браузере", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index fb48b7a6d..de694481d 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -223,6 +223,7 @@ "Recording format is not supported and cannot be reproduced": "Kayıt formatı desteklenmiyor ve çoğaltılamıyor", "Remind me": "Bana hatırlat", "Remind Me": "Hatırlat", + "Reminder set": "Hatırlatıcı ayarlandı", "Remove reminder": "Hatırlatıcıyı kaldır", "Remove save for later": "Sonraya kaydet'i kaldır", "Reply": "Cevapla", @@ -285,7 +286,7 @@ "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", "timestamp/PollVoteTooltip": "{{ timestamp | timestampFormatter(calendar: true) }}", - "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/ReminderNotification": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today] [at] HH:mm\", \"nextDay\": \"[Tomorrow] [at] HH:mm\", \"lastDay\": \"[Yesterday] [at] HH:mm\", \"nextWeek\": \"dddd [at] HH:mm\", \"lastWeek\": \"[Last] dddd [at] HH:mm\", \"sameElse\": \"ddd, D MMM [at] HH:mm\" }) }}", "timestamp/SystemMessage": "{{ timestamp | timestampFormatter(format: dddd L) }}", "To start recording, allow the camera access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda kameraya erişime izin verin", "To start recording, allow the microphone access in your browser": "Kayıt yapmaya başlamak için tarayıcınızda mikrofona erişime izin verin", diff --git a/src/styling/_global-theme-variables.scss b/src/styling/_global-theme-variables.scss index bfc6b0998..540d1d3b3 100644 --- a/src/styling/_global-theme-variables.scss +++ b/src/styling/_global-theme-variables.scss @@ -41,6 +41,10 @@ var(--typography-font-size-xs) / var(--typography-line-height-tight) var(--str-chat__font-family); + --str-chat__metadata-emphasis-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-xs) / var(--typography-line-height-tight) + var(--str-chat__font-family); + --str-chat__caption-default-text: normal var(--typography-font-weight-regular) var(--typography-font-size-sm) / var(--typography-line-height-normal) var(--str-chat__font-family); From 01b8ff59d9199e07518d1b9e03bed182d5790f1d Mon Sep 17 00:00:00 2001 From: martincupela Date: Fri, 20 Feb 2026 17:57:20 +0100 Subject: [PATCH 7/7] feat: add MessageAlsoSentInChannelIndicator --- src/components/Icons/icons.tsx | 11 ++++++++ .../MessageAlsoSentInChannelIndicator.tsx | 22 +++++++++++++++ src/components/Message/MessageSimple.tsx | 3 +++ src/components/Message/index.ts | 1 + src/components/Message/styling/Message.scss | 9 +++++++ .../MessageAlsoSentInChannelIndicator.scss | 27 +++++++++++++++++++ ...IsThreadReplyInChannelButtonIndicator.scss | 0 src/components/Message/styling/index.scss | 2 ++ src/components/Message/utils.tsx | 2 ++ src/context/ComponentContext.tsx | 2 ++ src/i18n/de.json | 1 + src/i18n/en.json | 1 + src/i18n/es.json | 1 + src/i18n/fr.json | 1 + src/i18n/hi.json | 1 + src/i18n/it.json | 1 + src/i18n/ja.json | 1 + src/i18n/ko.json | 1 + src/i18n/nl.json | 1 + src/i18n/pt.json | 1 + src/i18n/ru.json | 1 + src/i18n/tr.json | 1 + 22 files changed, 91 insertions(+) create mode 100644 src/components/Message/MessageAlsoSentInChannelIndicator.tsx create mode 100644 src/components/Message/styling/MessageAlsoSentInChannelIndicator.scss create mode 100644 src/components/Message/styling/MessageIsThreadReplyInChannelButtonIndicator.scss diff --git a/src/components/Icons/icons.tsx b/src/components/Icons/icons.tsx index 8b0d1f0b3..754976cdb 100644 --- a/src/components/Icons/icons.tsx +++ b/src/components/Icons/icons.tsx @@ -32,6 +32,17 @@ export const IconArrowRight = createIcon( , ); +export const IconArrowRightUp = createIcon( + 'IconArrowRightUp', + , +); + export const IconArrowRotateClockwise = createIcon( 'IconArrowRotateClockwise', , diff --git a/src/components/Message/MessageAlsoSentInChannelIndicator.tsx b/src/components/Message/MessageAlsoSentInChannelIndicator.tsx new file mode 100644 index 000000000..6f7929768 --- /dev/null +++ b/src/components/Message/MessageAlsoSentInChannelIndicator.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import { IconArrowRightUp } from '../Icons'; +import { useMessageContext, useTranslationContext } from '../../context'; + +/** + * Indicator shown in thread message lists when the message was also sent to the main channel (show_in_channel === true). + * Only visible inside Thread, not in the main channel list. + */ +export const MessageAlsoSentInChannelIndicator = () => { + const { message, threadList } = useMessageContext('MessageAlsoSentInChannelIndicator'); + const { t } = useTranslationContext(); + + if (!threadList || !message?.show_in_channel) return null; + + return ( +
+ + {t('Also sent in channel')} +
+ ); +}; diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index ded032cfb..ee8dc063a 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -12,6 +12,7 @@ import { MessageText } from './MessageText'; import { MessageTimestamp as DefaultMessageTimestamp } from './MessageTimestamp'; import { StreamedMessageText as DefaultStreamedMessageText } from './StreamedMessageText'; import { isDateSeparatorMessage } from '../MessageList'; +import { MessageAlsoSentInChannelIndicator as DefaultMessageAlsoSentInChannelIndicator } from './MessageAlsoSentInChannelIndicator'; import { MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator } from './MessageIsThreadReplyInChannelButtonIndicator'; import { ReminderNotification as DefaultReminderNotification } from './ReminderNotification'; import { useMessageReminder } from './hooks'; @@ -80,6 +81,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { Attachment = DefaultAttachment, Avatar = DefaultAvatar, MessageActions = DefaultMessageActions, + MessageAlsoSentInChannelIndicator = DefaultMessageAlsoSentInChannelIndicator, MessageBlocked = DefaultMessageBlocked, MessageBouncePrompt = DefaultMessageBouncePrompt, MessageDeleted = DefaultMessageDeleted, @@ -189,6 +191,7 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { {
{message.pinned && } + {threadList && message.show_in_channel && } {!!reminder && } {message.user && ( ; /** Custom UI component for viewing message's image and giphy attachments with option to expand into the Gallery on Modal, defaults to and accepts the same props as [ModalGallery](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Gallery/ModalGallery.tsx) */ ModalGallery?: React.ComponentType; + /** Custom UI component to show "Also sent in channel" in thread message lists when message.show_in_channel is true, defaults to and accepts same props as: [MessageAlsoSentInChannelIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageAlsoSentInChannelIndicator.tsx) */ + MessageAlsoSentInChannelIndicator?: React.ComponentType; /** Custom UI component to override default pinned message indicator, defaults to and accepts same props as: [PinIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/PinIndicator.tsx) */ PinIndicator?: React.ComponentType; /** Custom UI component to override default poll actions rendering in a message, defaults to and accepts same props as: [PollActions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Poll/PollActions/PollActions.tsx) */ diff --git a/src/i18n/de.json b/src/i18n/de.json index 78ab5b724..cbfc29c81 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -35,6 +35,7 @@ "Allow others to add comments": "Anderen das Hinzufügen von Kommentaren erlauben", "Also send as a direct message": "Auch als Direktnachricht senden", "Also send in channel": "Auch im Kanal senden", + "Also sent in channel": "Auch im Kanal gesendet", "An error has occurred during recording": "Ein Fehler ist während der Aufnahme aufgetreten", "An error has occurred during the recording processing": "Ein Fehler ist während der Aufnahmeverarbeitung aufgetreten", "Anonymous": "Anonym", diff --git a/src/i18n/en.json b/src/i18n/en.json index 252c13ac7..c42d3899a 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -35,6 +35,7 @@ "Allow others to add comments": "Allow others to add comments", "Also send as a direct message": "Also send as a direct message", "Also send in channel": "Also send in channel", + "Also sent in channel": "Also sent in channel", "An error has occurred during recording": "An error has occurred during recording", "An error has occurred during the recording processing": "An error has occurred during the recording processing", "Anonymous": "Anonymous", diff --git a/src/i18n/es.json b/src/i18n/es.json index debed7510..f80166494 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -40,6 +40,7 @@ "Allow others to add comments": "Permitir que otros añadan comentarios", "Also send as a direct message": "También enviar como mensaje directo", "Also send in channel": "También enviar en el canal", + "Also sent in channel": "También enviado en el canal", "An error has occurred during recording": "Se ha producido un error durante la grabación", "An error has occurred during the recording processing": "Se ha producido un error durante el procesamiento de la grabación", "Anonymous": "Anónimo", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 728a06acf..e788a2f35 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -40,6 +40,7 @@ "Allow others to add comments": "Permettre à d'autres d'ajouter des commentaires", "Also send as a direct message": "Également envoyer en message direct", "Also send in channel": "Également envoyer dans le canal", + "Also sent in channel": "Également envoyé dans le canal", "An error has occurred during recording": "Une erreur s'est produite pendant l'enregistrement", "An error has occurred during the recording processing": "Une erreur s'est produite pendant le traitement de l'enregistrement", "Anonymous": "Anonyme", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 74d1fa61b..9d6aca93f 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -35,6 +35,7 @@ "Allow others to add comments": "दूसरों को टिप्पणी जोड़ने दें", "Also send as a direct message": "सीधे संदेश के रूप में भी भेजें", "Also send in channel": "चैनल में भी भेजें", + "Also sent in channel": "चैनल में भी भेजा गया", "An error has occurred during recording": "रेकॉर्डिंग के दौरान एक त्रुटि आ गई है", "An error has occurred during the recording processing": "रेकॉर्डिंग प्रोसेसिंग के दौरान एक त्रुटि आ गई है", "Anonymous": "गुमनाम", diff --git a/src/i18n/it.json b/src/i18n/it.json index cc307783c..e4ccba6c4 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -40,6 +40,7 @@ "Allow others to add comments": "Consenti ad altri di aggiungere commenti", "Also send as a direct message": "Invia anche come messaggio diretto", "Also send in channel": "Invia anche nel canale", + "Also sent in channel": "Inviato anche nel canale", "An error has occurred during recording": "Si è verificato un errore durante la registrazione", "An error has occurred during the recording processing": "Si è verificato un errore durante l'elaborazione della registrazione", "Anonymous": "Anonimo", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 79711fc16..173beae86 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -35,6 +35,7 @@ "Allow others to add comments": "他の人にコメントを追加することを許可する", "Also send as a direct message": "ダイレクトメッセージとしても送信", "Also send in channel": "チャンネルにも送信", + "Also sent in channel": "チャンネルにも送信済み", "An error has occurred during recording": "録音中にエラーが発生しました", "An error has occurred during the recording processing": "録音処理中にエラーが発生しました", "Anonymous": "匿名", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 0ffe0951e..924bcb29d 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -35,6 +35,7 @@ "Allow others to add comments": "다른 사람이 댓글을 추가할 수 있도록 허용", "Also send as a direct message": "다이렉트 메시지로도 보내기", "Also send in channel": "채널에도 보내기", + "Also sent in channel": "채널에도 전송됨", "An error has occurred during recording": "녹음 중 오류가 발생했습니다", "An error has occurred during the recording processing": "녹음 처리 중 오류가 발생했습니다", "Anonymous": "익명", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index d3f89760a..7ffc87889 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -35,6 +35,7 @@ "Allow others to add comments": "Sta anderen toe om opmerkingen toe te voegen", "Also send as a direct message": "Ook als direct bericht versturen", "Also send in channel": "Ook in kanaal versturen", + "Also sent in channel": "Ook in kanaal verzonden", "An error has occurred during recording": "Er is een fout opgetreden tijdens het opnemen", "An error has occurred during the recording processing": "Er is een fout opgetreden tijdens de verwerking van de opname", "Anonymous": "Anoniem", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index a4b5423cd..90fc36ab1 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -40,6 +40,7 @@ "Allow others to add comments": "Permitir que outros adicionem comentários", "Also send as a direct message": "Também enviar como mensagem direta", "Also send in channel": "Também enviar no canal", + "Also sent in channel": "Também enviado no canal", "An error has occurred during recording": "Ocorreu um erro durante a gravação", "An error has occurred during the recording processing": "Ocorreu um erro durante o processamento da gravação", "Anonymous": "Anônimo", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 98b040aeb..0bf2a89b9 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -45,6 +45,7 @@ "Allow others to add comments": "Разрешить другим добавлять комментарии", "Also send as a direct message": "Также отправить как личное сообщение", "Also send in channel": "Также отправить в канал", + "Also sent in channel": "Также отправлено в канал", "An error has occurred during recording": "Произошла ошибка во время записи", "An error has occurred during the recording processing": "Произошла ошибка во время обработки записи", "Anonymous": "Аноним", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index de694481d..046d5a707 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -35,6 +35,7 @@ "Allow others to add comments": "Diğerlerinin yorum eklemesine izin ver", "Also send as a direct message": "Ayrıca doğrudan mesaj olarak gönder", "Also send in channel": "Ayrıca kanala gönder", + "Also sent in channel": "Kanala da gönderildi", "An error has occurred during recording": "Kayıt sırasında bir hata oluştu", "An error has occurred during the recording processing": "Kayıt işlemi sırasında bir hata oluştu", "Anonymous": "Anonim",