From 5df89bd4df8018f5f11d92938410797e0b31e87b Mon Sep 17 00:00:00 2001 From: martincupela Date: Tue, 10 Feb 2026 10:02:33 +0100 Subject: [PATCH 01/20] feat: add new DateSeparator styles --- .../DateSeparator/DateSeparator.tsx | 13 ++--------- .../DateSeparator/styling/DateSeparator.scss | 23 +++++++++++++++++++ .../DateSeparator/styling/index.scss | 1 + src/i18n/de.json | 2 +- src/i18n/en.json | 2 +- src/i18n/es.json | 2 +- src/i18n/fr.json | 2 +- src/i18n/hi.json | 2 +- src/i18n/it.json | 2 +- src/i18n/ja.json | 2 +- src/i18n/ko.json | 2 +- src/i18n/nl.json | 2 +- src/i18n/pt.json | 2 +- src/i18n/ru.json | 2 +- src/i18n/tr.json | 2 +- src/styling/index.scss | 3 ++- 16 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 src/components/DateSeparator/styling/DateSeparator.scss create mode 100644 src/components/DateSeparator/styling/index.scss diff --git a/src/components/DateSeparator/DateSeparator.tsx b/src/components/DateSeparator/DateSeparator.tsx index daf75a6cb2..148426bd54 100644 --- a/src/components/DateSeparator/DateSeparator.tsx +++ b/src/components/DateSeparator/DateSeparator.tsx @@ -10,6 +10,7 @@ export type DateSeparatorProps = TimestampFormatterOptions & { date: Date; /** Override the default formatting of the date. This is a function that has access to the original date object. */ formatDate?: (date: Date) => string; + // todo: position and unread are not necessary anymore /** Set the position of the date in the separator, options are 'left', 'center', 'right', @default right */ position?: 'left' | 'center' | 'right'; /** If following messages are not new */ @@ -21,8 +22,6 @@ const UnMemoizedDateSeparator = (props: DateSeparatorProps) => { calendar, date: messageCreatedAt, formatDate, - position = 'right', - unread, ...restTimestampFormatterOptions } = props; @@ -40,15 +39,7 @@ const UnMemoizedDateSeparator = (props: DateSeparatorProps) => { return (
- {(position === 'right' || position === 'center') && ( -
- )} -
- {unread ? `${t('New')} - ${formattedDate}` : formattedDate} -
- {(position === 'left' || position === 'center') && ( -
- )} +
{formattedDate}
); }; diff --git a/src/components/DateSeparator/styling/DateSeparator.scss b/src/components/DateSeparator/styling/DateSeparator.scss new file mode 100644 index 0000000000..e3afd5e74c --- /dev/null +++ b/src/components/DateSeparator/styling/DateSeparator.scss @@ -0,0 +1,23 @@ +.str-chat__date-separator { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: var(--spacing-xs) 0; + + + .str-chat__date-separator-date { + display: flex; + padding: var(--spacing-xs) var(--spacing-sm); + justify-content: center; + align-items: center; + color: var(--chat-text-system); + background-color: var(--background-core-surface-subtle); + border-radius: var(--radius-max); + + /* metadata/emphasis */ + font-size: var(--typography-font-size-xs); + font-weight: var(--typography-font-weight-semi-bold); + line-height: var(--typography-line-height-tight); + } +} \ No newline at end of file diff --git a/src/components/DateSeparator/styling/index.scss b/src/components/DateSeparator/styling/index.scss new file mode 100644 index 0000000000..fcc3033fa8 --- /dev/null +++ b/src/components/DateSeparator/styling/index.scss @@ -0,0 +1 @@ +@use 'DateSeparator'; \ No newline at end of file diff --git a/src/i18n/de.json b/src/i18n/de.json index 7e3a9d17aa..f271495c1b 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -240,7 +240,7 @@ "Thread": "Thread", "Thread has not been found": "Thread wurde nicht gefunden", "Thread reply": "Thread-Antwort", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/en.json b/src/i18n/en.json index 2fd89fbd0d..dec8c88832 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -240,7 +240,7 @@ "Thread": "Thread", "Thread has not been found": "Thread has not been found", "Thread reply": "Thread reply", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/es.json b/src/i18n/es.json index b84dd98268..2ce535ea62 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -249,7 +249,7 @@ "Thread": "Hilo", "Thread has not been found": "No se ha encontrado el hilo", "Thread reply": "Respuesta en hilo", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 9d2ce8d8fd..d537424da0 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -249,7 +249,7 @@ "Thread": "Fil de discussion", "Thread has not been found": "Le fil de discussion n'a pas été trouvé", "Thread reply": "Réponse dans le fil", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index cd7f25a79a..23cda2a84d 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -241,7 +241,7 @@ "Thread": "रिप्लाई थ्रेड", "Thread has not been found": "थ्रेड नहीं मिला", "Thread reply": "थ्रेड में उत्तर", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/it.json b/src/i18n/it.json index 56fbe98d95..d6ffb31415 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -249,7 +249,7 @@ "Thread": "Discussione", "Thread has not been found": "Discussione non trovata", "Thread reply": "Risposta nella discussione", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index 3b5438d7b4..4a62681ca4 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -240,7 +240,7 @@ "Thread": "スレッド", "Thread has not been found": "スレッドが見つかりませんでした", "Thread reply": "スレッドの返信", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 08866c3346..8ca049962a 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -240,7 +240,7 @@ "Thread": "스레드", "Thread has not been found": "스레드를 찾을 수 없습니다", "Thread reply": "스레드 답장", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index 486e7814c4..d00cd6f040 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -242,7 +242,7 @@ "Thread": "Draadje", "Thread has not been found": "Draadje niet gevonden", "Thread reply": "Draadje antwoord", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 1c208d3cf8..0b7cdf5365 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -249,7 +249,7 @@ "Thread": "Fio", "Thread has not been found": "Fio não encontrado", "Thread reply": "Resposta no fio", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index b25969db9c..5a8d87af33 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -258,7 +258,7 @@ "Thread": "Ветка", "Thread has not been found": "Ветка не найдена", "Thread reply": "Ответ в ветке", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index 64b5ed3354..68a13d1c0f 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -240,7 +240,7 @@ "Thread": "Konu", "Thread has not been found": "Konu bulunamadı", "Thread reply": "Konu yanıtı", - "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true) }}", + "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/PollVote": "{{ timestamp | timestampFormatter(format: MMM D [at] HH:mm) }}", diff --git a/src/styling/index.scss b/src/styling/index.scss index b0d2169d01..42abd4c53f 100644 --- a/src/styling/index.scss +++ b/src/styling/index.scss @@ -22,12 +22,13 @@ @use "../components/Attachment/styling" as Attachment; @use "../components/AudioPlayback/styling" as AudioPlayback; @use "../components/Avatar/styling/Avatar" as Avatar; +@use "../components/DateSeparator/styling" as DateSeparator; @use "../components/MediaRecorder/AudioRecorder/styling" as AudioRecorder; @use "../components/Message/styling" as Message; @use "../components/MessageActions/styling" as MessageActions; @use "../components/MessageInput/styling" as MessageComposer; @use "../components/Reactions/styling/ReactionSelector" as ReactionSelector; -@use "../components/TextareaComposer/styling/" as TextareaComposer; +@use "../components/TextareaComposer/styling" as TextareaComposer; // Layers have to be kept the last @import 'modern-normalize' layer(css-reset); From b8c1dbe62161774ea0e9d95e2ab4881d59044c97 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 11:34:20 +0100 Subject: [PATCH 02/20] feat: add new DateSeparator styles --- .../DateSeparator/styling/DateSeparator.scss | 15 ++++++++ .../Message/styling/DateSeparator.scss | 34 ------------------- src/components/Message/styling/index.scss | 1 - 3 files changed, 15 insertions(+), 35 deletions(-) delete mode 100644 src/components/Message/styling/DateSeparator.scss diff --git a/src/components/DateSeparator/styling/DateSeparator.scss b/src/components/DateSeparator/styling/DateSeparator.scss index e3afd5e74c..09fe4e41f0 100644 --- a/src/components/DateSeparator/styling/DateSeparator.scss +++ b/src/components/DateSeparator/styling/DateSeparator.scss @@ -1,4 +1,19 @@ +@use '../../../styling/utils'; + +.str-chat { + --str-chat__date-separator-color: var(--str-chat__text-low-emphasis-color); + --str-chat__date-separator-line-color: var(--str-chat__disabled-color); + --str-chat__date-separator-border-radius: 0; + --str-chat__date-separator-background-color: transparent; + --str-chat__date-separator-border-block-start: none; + --str-chat__date-separator-border-block-end: none; + --str-chat__date-separator-border-inline-start: none; + --str-chat__date-separator-border-inline-end: none; + --str-chat__date-separator-box-shadow: none; +} + .str-chat__date-separator { + @include utils.component-layer-overrides('date-separator'); display: flex; justify-content: center; align-items: center; diff --git a/src/components/Message/styling/DateSeparator.scss b/src/components/Message/styling/DateSeparator.scss deleted file mode 100644 index 6cdb97b929..0000000000 --- a/src/components/Message/styling/DateSeparator.scss +++ /dev/null @@ -1,34 +0,0 @@ -@use '../../../styling/utils'; - -.str-chat { - --str-chat__date-separator-color: var(--str-chat__text-low-emphasis-color); - --str-chat__date-separator-line-color: var(--str-chat__disabled-color); - --str-chat__date-separator-border-radius: 0; - --str-chat__date-separator-background-color: transparent; - --str-chat__date-separator-border-block-start: none; - --str-chat__date-separator-border-block-end: none; - --str-chat__date-separator-border-inline-start: none; - --str-chat__date-separator-border-inline-end: none; - --str-chat__date-separator-box-shadow: none; -} - -.str-chat__date-separator { - display: flex; - padding: 2rem; - align-items: center; - @include utils.component-layer-overrides('date-separator'); - font: var(--str-chat__body-text); - - &-line { - flex: 1; - height: 1px; - background-color: var(--str-chat__date-separator-line-color); - border: none; - } - - > * { - &:not(:last-child) { - margin-right: 1rem; - } - } -} diff --git a/src/components/Message/styling/index.scss b/src/components/Message/styling/index.scss index 0b1cd05bec..b16e49e8da 100644 --- a/src/components/Message/styling/index.scss +++ b/src/components/Message/styling/index.scss @@ -1,4 +1,3 @@ -@use 'DateSeparator'; @use 'Message'; @use 'MessageEditedTimestamp'; @use 'MessageStatus'; From e17afb27d96ae91c6e4aa40847655cc80e24a887 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 11:34:55 +0100 Subject: [PATCH 03/20] fix: provide min width for voice recording wave bar in Attachment --- src/components/Attachment/styling/Attachment.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Attachment/styling/Attachment.scss b/src/components/Attachment/styling/Attachment.scss index 2e402e45a0..4a22be57fe 100644 --- a/src/components/Attachment/styling/Attachment.scss +++ b/src/components/Attachment/styling/Attachment.scss @@ -709,6 +709,7 @@ min-height: 60px; .str-chat__message-attachment__voice-recording-widget__metadata { + min-width: 180px; padding-inline: var(--spacing-xs); a { From 66a4b60ac510cb0c7bd0ceedbba1a943ba7c2f89 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 11:35:35 +0100 Subject: [PATCH 04/20] feat: add avatars to own messages and show avatars only at the bottom or single message --- src/components/Avatar/styling/Avatar.scss | 19 ++++++++---- src/components/Message/MessageSimple.tsx | 6 ++-- src/components/Message/styling/Message.scss | 32 ++++++++++++++------- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/components/Avatar/styling/Avatar.scss b/src/components/Avatar/styling/Avatar.scss index 3ccf4505b0..df79ee09dc 100644 --- a/src/components/Avatar/styling/Avatar.scss +++ b/src/components/Avatar/styling/Avatar.scss @@ -8,13 +8,8 @@ background: var(--avatar-bg-default, #e3edff); color: var(--avatar-text-default, #142f63); text-align: center; - font-feature-settings: - 'liga' off, - 'clig' off; - font-family: var(--typography-font-family-sans, 'SF Pro'); - font-style: normal; - font-weight: var(--typography-font-weight-semi-bold, 600); + font-weight: var(--typography-font-weight-semi-bold); line-height: 1; text-transform: uppercase; width: var(--avatar-size); @@ -132,3 +127,15 @@ } } } + +// Display in message list +.str-chat__li--top, +.str-chat__li--middle { + .str-chat__avatar { + // space above the message group + background: transparent; + pointer-events: none; + color: transparent; + } +} + diff --git a/src/components/Message/MessageSimple.tsx b/src/components/Message/MessageSimple.tsx index 1227730daa..301ddcd913 100644 --- a/src/components/Message/MessageSimple.tsx +++ b/src/components/Message/MessageSimple.tsx @@ -177,13 +177,13 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
{PinIndicator && } {!!reminder && } - {message.user && !isMyMessage() && ( + {message.user && ( )} @@ -210,8 +210,8 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => { ) : ( )} -
+ {showReplyCountButton && ( Date: Wed, 11 Feb 2026 11:36:05 +0100 Subject: [PATCH 05/20] fix: wrap message composer into a container and align it to the middle --- .../MessageInput/MessageInputFlat.tsx | 39 +++++++++++-------- .../MessageInput/styling/MessageComposer.scss | 6 +++ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/components/MessageInput/MessageInputFlat.tsx b/src/components/MessageInput/MessageInputFlat.tsx index c1e03e6d87..153b2c8e0c 100644 --- a/src/components/MessageInput/MessageInputFlat.tsx +++ b/src/components/MessageInput/MessageInputFlat.tsx @@ -117,24 +117,29 @@ export const MessageInputFlat = () => { } = useComponentContext(); return ( - - {recordingController.recordingState ? ( - - ) : ( - <> - -
- -
- - - - + +
+ {recordingController.recordingState ? ( + + ) : ( + <> + +
+ +
+ + + + +
-
- - - )} + + + )} +
); }; diff --git a/src/components/MessageInput/styling/MessageComposer.scss b/src/components/MessageInput/styling/MessageComposer.scss index fcc0543f86..4261c41e30 100644 --- a/src/components/MessageInput/styling/MessageComposer.scss +++ b/src/components/MessageInput/styling/MessageComposer.scss @@ -36,6 +36,12 @@ --str-chat__cooldown-border-inline-end: 0; --str-chat__cooldown-box-shadow: none; + .str-chat__message-composer-container { + width: 100%; + display: flex; + justify-content: center; + min-width: 0; + } /* Styles for floating like composer */ From a59fd691ac7ecc7d704e42f4809797ca01107dc6 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 11:36:27 +0100 Subject: [PATCH 06/20] fix(demo): provide responsive app styling --- examples/vite/src/index.scss | 87 +++++++++++++++++++++++++++--------- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/examples/vite/src/index.scss b/examples/vite/src/index.scss index 85cb002865..9636c26853 100644 --- a/examples/vite/src/index.scss +++ b/examples/vite/src/index.scss @@ -25,51 +25,94 @@ body { @layer stream-overrides { .str-chat { - --max-content-width: 800px; + height: 100%; + width: 100%; } - .str-chat__channel-list, - .str-chat__thread-list-container { - flex-basis: 350px; - flex-shrink: 0; + .str-chat__chat-view, + .str-chat__chat-view-channels, + .str-chat__channel, + .str-chat__window { + min-height: 0; } - .str-chat__channel { - width: 100%; + .str-chat__chat-view { + height: 100%; + container-type: inline-size; } - .str-chat__main-panel, - .str-chat__thread-container { - align-items: center; + .str-chat__chat-view-channels { + height: 100%; + gap: 0; + } + + .str-chat__channel-list { + flex: 0 0 300px; + max-width: 300px; + height: 100%; + } + + .str-chat__main-panel { + height: 100%; + align-items: stretch; } + .str-chat__main-panel-inner { width: 100%; + height: 100%; + max-width: none; } - .str-chat__message-list-scroll { - max-width: var(--max-content-width); - width: 100%; + .str-chat__window { + height: 100%; } .str-chat__channel-header, .str-chat__thread-header { - max-width: var(--max-content-width); width: 100%; + max-width: none; } - .str-chat__message-input { - max-width: var(--max-content-width); - // scrollbar-gutter: stable; - // scrollbar-width: thin; - // overflow-y: hidden; - // flex-shrink: 0; + .str-chat__message-list-scroll { + width: 100%; + max-width: none; } .str-chat__list, .str-chat__virtual-list { - display: flex; - justify-content: center; + display: block; scrollbar-gutter: stable; scrollbar-width: thin; } + + .str-chat__message-input { + width: 100%; + //max-width: none; + } + + .str-chat__thread-list-container, + .str-chat__thread-container { + flex: 0 0 360px; + max-width: 360px; + } + + @media (max-width: 960px) { + .str-chat__channel-list { + flex-basis: 260px; + max-width: 260px; + } + + .str-chat__thread-list-container, + .str-chat__thread-container { + flex-basis: 320px; + max-width: 320px; + } + } + + @container (max-width: 1025px) { + .str-chat__channel-list, + .str-chat__chat-view__selector { + display: none; + } + } } From 98ace85fd2646722760eda1e64cbb1be8159dc0f Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 11:36:55 +0100 Subject: [PATCH 07/20] fix(demo): allow to specify own user token from URL --- examples/vite/src/App.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index 3f1cd2b6e9..0a4bc1e910 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -47,7 +47,9 @@ import { humanId } from 'human-id'; init({ data }); const apiKey = import.meta.env.VITE_STREAM_API_KEY; -const token = import.meta.env.VITE_USER_TOKEN; +const token = + new URLSearchParams(window.location.search).get('token') || + import.meta.env.VITE_USER_TOKEN; if (!apiKey) { throw new Error('VITE_STREAM_API_KEY is not defined'); @@ -214,7 +216,7 @@ const App = () => { })) } > - Toggle Visual Style + Toggle Msg Reactions Visual Style
From f416c6c05436f364ae684ba34a1704190d2be2a4 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 11:38:24 +0100 Subject: [PATCH 08/20] refactor: make DateSeparator block padding smaller --- src/components/DateSeparator/styling/DateSeparator.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/DateSeparator/styling/DateSeparator.scss b/src/components/DateSeparator/styling/DateSeparator.scss index 09fe4e41f0..72834ac3cd 100644 --- a/src/components/DateSeparator/styling/DateSeparator.scss +++ b/src/components/DateSeparator/styling/DateSeparator.scss @@ -23,7 +23,7 @@ .str-chat__date-separator-date { display: flex; - padding: var(--spacing-xs) var(--spacing-sm); + padding: var(--spacing-xxs) var(--spacing-sm); justify-content: center; align-items: center; color: var(--chat-text-system); From 8f1aa383d0a9182b25421bbeff5995a9e23f85fc Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 13:13:20 +0100 Subject: [PATCH 09/20] refactor: hide own avatar in message list by default --- src/components/Avatar/styling/Avatar.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Avatar/styling/Avatar.scss b/src/components/Avatar/styling/Avatar.scss index df79ee09dc..6a4a4efe1b 100644 --- a/src/components/Avatar/styling/Avatar.scss +++ b/src/components/Avatar/styling/Avatar.scss @@ -139,3 +139,9 @@ } } +.str-chat__message--me { + .str-chat__avatar { + display: none; + } +} + From 3e4427b26cc60b46e29000c1953e224bb8a6f109 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 13:15:07 +0100 Subject: [PATCH 10/20] feat: change message status icons --- src/components/Icons/IconDoubleCheckmark.tsx | 13 ++++ src/components/Icons/IconSingleCheckmark.tsx | 13 ++++ src/components/Icons/index.ts | 2 + .../Icons/styling/IconDoubleCheckmark.scss | 14 +++++ .../Icons/styling/IconSingleCheckmark.scss | 11 ++++ src/components/Icons/styling/index.scss | 2 + src/components/Message/MessageStatus.tsx | 25 ++------ src/components/Message/icons.tsx | 31 --------- src/components/Message/styling/Message.scss | 7 ++- .../Message/styling/MessageStatus.scss | 63 +++++-------------- 10 files changed, 80 insertions(+), 101 deletions(-) create mode 100644 src/components/Icons/IconDoubleCheckmark.tsx create mode 100644 src/components/Icons/IconSingleCheckmark.tsx create mode 100644 src/components/Icons/styling/IconDoubleCheckmark.scss create mode 100644 src/components/Icons/styling/IconSingleCheckmark.scss diff --git a/src/components/Icons/IconDoubleCheckmark.tsx b/src/components/Icons/IconDoubleCheckmark.tsx new file mode 100644 index 0000000000..c5d376efe1 --- /dev/null +++ b/src/components/Icons/IconDoubleCheckmark.tsx @@ -0,0 +1,13 @@ +import type { ComponentProps } from 'react'; +import { BaseIcon } from './BaseIcon'; +import clsx from 'clsx'; + +export const IconDoubleCheckmark = ({ className, ...props }: ComponentProps<'svg'>) => ( + + + +); diff --git a/src/components/Icons/IconSingleCheckmark.tsx b/src/components/Icons/IconSingleCheckmark.tsx new file mode 100644 index 0000000000..53ae81daa1 --- /dev/null +++ b/src/components/Icons/IconSingleCheckmark.tsx @@ -0,0 +1,13 @@ +import type { ComponentProps } from 'react'; +import clsx from 'clsx'; +import { BaseIcon } from './BaseIcon'; + +export const IconSingleCheckmark = ({ className, ...props }: ComponentProps<'svg'>) => ( + + + +); diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts index fb737b4854..adf4e9b2bb 100644 --- a/src/components/Icons/index.ts +++ b/src/components/Icons/index.ts @@ -7,6 +7,7 @@ export * from './IconChevronRight'; export * from './IconClose'; export * from './IconCommand'; export * from './IconCross'; +export * from './IconDoubleCheckmark'; export * from './IconExclamationCircle'; export * from './IconExclamationTriangle'; export * from './IconEyeOpen'; @@ -26,6 +27,7 @@ export * from './IconPeopleRemove'; export * from './IconPlaySolid'; export * from './IconPlus'; export * from './IconPoll'; +export * from './IconSingleCheckmark'; export * from './IconVideoCamera'; export * from './IconVideoCameraOutline'; export * from './IconVolumeFull'; diff --git a/src/components/Icons/styling/IconDoubleCheckmark.scss b/src/components/Icons/styling/IconDoubleCheckmark.scss new file mode 100644 index 0000000000..95b0573432 --- /dev/null +++ b/src/components/Icons/styling/IconDoubleCheckmark.scss @@ -0,0 +1,14 @@ +.str-chat { + .str-chat__icon--double-checkmark { + fill: none; + height: 16px; + width: 16px; + + path { + fill: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 1.5; + } + } +} \ No newline at end of file diff --git a/src/components/Icons/styling/IconSingleCheckmark.scss b/src/components/Icons/styling/IconSingleCheckmark.scss new file mode 100644 index 0000000000..f389203907 --- /dev/null +++ b/src/components/Icons/styling/IconSingleCheckmark.scss @@ -0,0 +1,11 @@ +.str-chat { + .str-chat__icon--single-checkmark { + fill: none; + height: 16px; + width: 16px; + + path { + fill: currentColor; + } + } +} \ No newline at end of file diff --git a/src/components/Icons/styling/index.scss b/src/components/Icons/styling/index.scss index 2134f5189b..a6b1b5cb62 100644 --- a/src/components/Icons/styling/index.scss +++ b/src/components/Icons/styling/index.scss @@ -8,6 +8,7 @@ @use 'IconClose'; @use 'IconCommand'; @use 'IconCross'; +@use 'IconDoubleCheckmark'; @use 'IconExclamationCircle'; @use 'IconExclamationTriangle'; @use 'IconEyeOpen'; @@ -27,5 +28,6 @@ @use 'IconPlaySolid'; @use 'IconPlus'; @use 'IconPoll'; +@use 'IconSingleCheckmark'; @use 'IconVideoCameraOutline'; @use 'IconVolumeFull'; diff --git a/src/components/Message/MessageStatus.tsx b/src/components/Message/MessageStatus.tsx index a4b4a518b6..a529ecdd22 100644 --- a/src/components/Message/MessageStatus.tsx +++ b/src/components/Message/MessageStatus.tsx @@ -1,24 +1,17 @@ import React, { useState } from 'react'; import clsx from 'clsx'; - -import { MessageDeliveredIcon, MessageSentIcon } from './icons'; import type { TooltipUsernameMapper } from './utils'; import { getReadByTooltipText, mapToUserNameOrId } from './utils'; - -import type { AvatarProps } from '../Avatar'; -import { Avatar as DefaultAvatar } from '../Avatar'; import { LoadingIndicator } from '../Loading'; import { PopperTooltip } from '../Tooltip'; import { useEnterLeaveHandlers } from '../Tooltip/hooks'; import { useChatContext } from '../../context/ChatContext'; -import { useComponentContext } from '../../context/ComponentContext'; import { useMessageContext } from '../../context/MessageContext'; import { useTranslationContext } from '../../context/TranslationContext'; +import { IconDoubleCheckmark, IconSingleCheckmark } from '../Icons'; export type MessageStatusProps = { - /* Custom UI component to display a user's avatar (overrides the value from `ComponentContext`) */ - Avatar?: React.ComponentType; /* Custom component to render when message is considered delivered, not read. The default UI renders MessageDeliveredIcon and a tooltip with string 'Delivered'. */ MessageDeliveredStatus?: React.ComponentType; /* Custom component to render when message is considered delivered and read. The default UI renders the last reader's Avatar and a tooltip with string readers' names. */ @@ -35,7 +28,6 @@ export type MessageStatusProps = { const UnMemoizedMessageStatus = (props: MessageStatusProps) => { const { - Avatar: propAvatar, MessageDeliveredStatus, MessageReadStatus, MessageSendingStatus, @@ -48,7 +40,6 @@ const UnMemoizedMessageStatus = (props: MessageStatusProps) => { useEnterLeaveHandlers(); const { client } = useChatContext('MessageStatus'); - const { Avatar: contextAvatar } = useComponentContext('MessageStatus'); const { deliveredTo, isMyMessage, @@ -61,8 +52,6 @@ const UnMemoizedMessageStatus = (props: MessageStatusProps) => { const { t } = useTranslationContext('MessageStatus'); const [referenceElement, setReferenceElement] = useState(null); - const Avatar = propAvatar || contextAvatar || DefaultAvatar; - if (!isMyMessage() || message.type === 'error') return null; const justReadByMe = readBy?.length === 1 && readBy[0].id === client.user?.id; @@ -81,7 +70,6 @@ const UnMemoizedMessageStatus = (props: MessageStatusProps) => { const readersWithoutOwnUser = read ? readBy.filter((item) => item.id !== client.user?.id) : []; - const [lastReadUser] = readersWithoutOwnUser; return ( { > {t('Sent')} - + ))} @@ -148,7 +136,7 @@ const UnMemoizedMessageStatus = (props: MessageStatusProps) => { > {t('Delivered')} - + ))} @@ -165,12 +153,7 @@ const UnMemoizedMessageStatus = (props: MessageStatusProps) => { {getReadByTooltipText(readBy, t, client, tooltipUserNameMapper)} - + {readersWithoutOwnUser.length > 1 && ( { ); }; -export const MessageSentIcon = () => ( - - - -); - -export const MessageDeliveredIcon = () => ( - - - -); - export const MessageErrorIcon = () => (
Date: Wed, 11 Feb 2026 13:16:06 +0100 Subject: [PATCH 11/20] feat: provide QuickMessageActionButton to unify quick message action display --- src/components/MessageActions/MessageActions.tsx | 13 +++++++++---- .../MessageActions/QuickMessageActionButton.tsx | 16 ++++++++++++++++ src/components/MessageActions/defaults.tsx | 7 ++++--- src/components/MessageActions/index.ts | 1 + .../Reactions/ReactionSelectorWithButton.tsx | 5 +++-- 5 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 src/components/MessageActions/QuickMessageActionButton.tsx diff --git a/src/components/MessageActions/MessageActions.tsx b/src/components/MessageActions/MessageActions.tsx index 2522bd94b7..cff1ffc5f2 100644 --- a/src/components/MessageActions/MessageActions.tsx +++ b/src/components/MessageActions/MessageActions.tsx @@ -4,7 +4,6 @@ import React, { useMemo, useState } from 'react'; import { useChatContext, useMessageContext, useTranslationContext } from '../../context'; import { ContextMenu, - ContextMenuButton, type ContextMenuItemComponent, type ContextMenuItemProps, DialogAnchor, @@ -14,6 +13,7 @@ import { import { useBaseMessageActionSetFilter, useSplitMessageActionSet } from './hooks'; import { defaultMessageActionSet } from './defaults'; import { ActionsIcon, type MESSAGE_ACTIONS } from '../Message'; +import { Button } from '../Button'; type BaseMessageActionSetItem = { placement: 'quick' | 'dropdown'; @@ -98,11 +98,16 @@ export const MessageActions = ({ > {dropdownActionSet.length > 0 && ( <> - { dialog?.toggle(); @@ -110,7 +115,7 @@ export const MessageActions = ({ ref={setActionsBoxButtonElement} > - + ( + +); diff --git a/src/components/MessageActions/defaults.tsx b/src/components/MessageActions/defaults.tsx index 575365e65c..2406572a06 100644 --- a/src/components/MessageActions/defaults.tsx +++ b/src/components/MessageActions/defaults.tsx @@ -13,9 +13,10 @@ import { RemindMeSubmenu, RemindMeSubmenuHeader, } from '../../components/MessageActions/RemindMeSubmenu'; -import { ContextMenuButton } from '../../components/Dialog'; import type { ContextMenuItemProps } from '../../components/Dialog'; +import { ContextMenuButton } from '../../components/Dialog'; import type { MessageActionSetItem } from './MessageActions'; +import { QuickMessageActionsButton } from './QuickMessageActionButton'; const msgActionsBoxButtonClassName = 'str-chat__message-actions-list-item-button' as const; @@ -200,14 +201,14 @@ const DefaultMessageActionComponents = { const { t } = useTranslationContext(); return ( - + ); }, }, diff --git a/src/components/MessageActions/index.ts b/src/components/MessageActions/index.ts index 1e0b5c79f0..3cbbb95b7e 100644 --- a/src/components/MessageActions/index.ts +++ b/src/components/MessageActions/index.ts @@ -1,3 +1,4 @@ export * from './MessageActions'; +export * from './QuickMessageActionButton'; export * from './defaults'; export * from './hooks'; diff --git a/src/components/Reactions/ReactionSelectorWithButton.tsx b/src/components/Reactions/ReactionSelectorWithButton.tsx index 62e28d946a..13f6e65ea4 100644 --- a/src/components/Reactions/ReactionSelectorWithButton.tsx +++ b/src/components/Reactions/ReactionSelectorWithButton.tsx @@ -9,6 +9,7 @@ import { } from '../../context'; import type { IconProps } from '../../types/types'; +import { QuickMessageActionsButton } from '../MessageActions'; type ReactionSelectorWithButtonProps = { /* Custom component rendering the icon used in a button invoking reactions selector for a given message. */ @@ -43,7 +44,7 @@ export const ReactionSelectorWithButton = ({ > - + ); }; From 700e612d313b8f2e45a61ec06603d4b82d76b0f0 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 13:28:46 +0100 Subject: [PATCH 12/20] feat: adjust styling of Edited flag on message --- src/components/Message/styling/Message.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 4428d658b8..142005c655 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -394,9 +394,8 @@ font: var(--str-chat__caption-medium-text); } - .str-chat__message-simple-timestamp + .str-chat__mesage-simple-edited::before { - content: '•'; - margin-right: var(--str-chat__spacing-1); + .str-chat__mesage-simple-edited { + margin-left: var(--spacing-xs); } } From 01165e5378408a8dcbadfe4e7df15c98c09aba80 Mon Sep 17 00:00:00 2001 From: martincupela Date: Wed, 11 Feb 2026 13:31:48 +0100 Subject: [PATCH 13/20] feat: adjust message metadata vertical size --- src/components/Message/styling/Message.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 142005c655..9bcfc3da21 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -370,9 +370,9 @@ .str-chat__custom-message-metadata { grid-area: custom-metadata; - margin-block-start: var(--str-chat__spacing-0_5); + height: var(--size-24); + margin-block-start: var(--spacing-xxs); color: var(--str-chat__message-secondary-color); - font: var(--str-chat__caption-text); } .str-chat__message-metadata { From 76316a5480d2d332e14925a0701cae1fc53df980 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:20:05 +0100 Subject: [PATCH 14/20] feat: convert ChatView into a general purpose sidebar --- examples/vite/src/stream-imports-layout.scss | 2 +- examples/vite/src/stream-imports-theme.scss | 2 +- src/components/ChatView/ChatView.tsx | 144 +++++++++++++----- src/components/ChatView/styling/ChatView.scss | 65 ++++++++ src/components/ChatView/styling/index.scss | 1 + src/i18n/de.json | 2 + src/i18n/en.json | 2 + src/i18n/es.json | 2 + src/i18n/fr.json | 2 + src/i18n/hi.json | 2 + src/i18n/it.json | 2 + src/i18n/ja.json | 2 + src/i18n/ko.json | 2 + src/i18n/nl.json | 2 + src/i18n/pt.json | 2 + src/i18n/ru.json | 2 + src/i18n/tr.json | 2 + src/styling/index.scss | 1 + 18 files changed, 201 insertions(+), 38 deletions(-) create mode 100644 src/components/ChatView/styling/ChatView.scss create mode 100644 src/components/ChatView/styling/index.scss diff --git a/examples/vite/src/stream-imports-layout.scss b/examples/vite/src/stream-imports-layout.scss index 3868f2a722..84456cc381 100644 --- a/examples/vite/src/stream-imports-layout.scss +++ b/examples/vite/src/stream-imports-layout.scss @@ -44,6 +44,6 @@ @use 'stream-chat-react/dist/scss/v2/Tooltip/Tooltip-layout'; @use 'stream-chat-react/dist/scss/v2/TypingIndicator/TypingIndicator-layout'; @use 'stream-chat-react/dist/scss/v2/ThreadList/ThreadList-layout'; -@use 'stream-chat-react/dist/scss/v2/ChatView/ChatView-layout'; +//@use 'stream-chat-react/dist/scss/v2/ChatView/ChatView-layout'; @use 'stream-chat-react/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-layout'; @use 'stream-chat-react/dist/scss/v2/AIStateIndicator/AIStateIndicator-layout'; diff --git a/examples/vite/src/stream-imports-theme.scss b/examples/vite/src/stream-imports-theme.scss index e917177908..727c02e92b 100644 --- a/examples/vite/src/stream-imports-theme.scss +++ b/examples/vite/src/stream-imports-theme.scss @@ -38,6 +38,6 @@ @use 'stream-chat-react/dist/scss/v2/Tooltip/Tooltip-theme'; @use 'stream-chat-react/dist/scss/v2/TypingIndicator/TypingIndicator-theme'; @use 'stream-chat-react/dist/scss/v2/ThreadList/ThreadList-theme'; -@use 'stream-chat-react/dist/scss/v2/ChatView/ChatView-theme'; +//@use 'stream-chat-react/dist/scss/v2/ChatView/ChatView-theme'; @use 'stream-chat-react/dist/scss/v2/UnreadCountBadge/UnreadCountBadge-theme'; @use 'stream-chat-react/dist/scss/v2/AIStateIndicator/AIStateIndicator-theme'; diff --git a/src/components/ChatView/ChatView.tsx b/src/components/ChatView/ChatView.tsx index 014731c87c..51053d7126 100644 --- a/src/components/ChatView/ChatView.tsx +++ b/src/components/ChatView/ChatView.tsx @@ -1,20 +1,28 @@ -import React, { createContext, useContext, useEffect, useMemo, useState } from 'react'; - +import clsx from 'clsx'; +import React, { + type ComponentType, + createContext, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; + +import { Button, type ButtonProps } from '../Button'; import { ThreadProvider } from '../Threads'; import { Icon } from '../Threads/icons'; import { UnreadCountBadge } from '../Threads/UnreadCountBadge'; -import { useChatContext } from '../../context'; +import { useChatContext, useTranslationContext } from '../../context'; import { useStateStore } from '../../store'; import type { PropsWithChildren } from 'react'; import type { Thread, ThreadManagerState } from 'stream-chat'; -import clsx from 'clsx'; -type ChatView = 'channels' | 'threads'; +export type ChatView = 'channels' | 'threads'; type ChatViewContextValue = { activeChatView: ChatView; - setActiveChatView: (cv: ChatViewContextValue['activeChatView']) => void; + setActiveChatView: (cv: ChatView) => void; }; const ChatViewContext = createContext({ @@ -22,9 +30,10 @@ const ChatViewContext = createContext({ setActiveChatView: () => undefined, }); +export const useChatViewContext = () => useContext(ChatViewContext); + export const ChatView = ({ children }: PropsWithChildren) => { - const [activeChatView, setActiveChatView] = - useState('channels'); + const [activeChatView, setActiveChatView] = useState('channels'); const { theme } = useChatContext(); @@ -38,7 +47,7 @@ export const ChatView = ({ children }: PropsWithChildren) => { }; const ChannelsView = ({ children }: PropsWithChildren) => { - const { activeChatView } = useContext(ChatViewContext); + const { activeChatView } = useChatViewContext(); if (activeChatView !== 'channels') return null; @@ -58,7 +67,7 @@ const ThreadsViewContext = createContext({ export const useThreadsViewContext = () => useContext(ThreadsViewContext); const ThreadsView = ({ children }: PropsWithChildren) => { - const { activeChatView } = useContext(ChatViewContext); + const { activeChatView } = useChatViewContext(); const [activeThread, setActiveThread] = useState(undefined); @@ -125,42 +134,105 @@ const ThreadAdapter = ({ children }: PropsWithChildren) => { return {children}; }; +export const ChatViewSelectorButton = ({ + children, + className, + Icon, + text, + ...props +}: ButtonProps & { Icon?: ComponentType; text?: string }) => ( +
{text}
+ + ) : ( + children + )} + +); + const selector = ({ unreadThreadCount }: ThreadManagerState) => ({ unreadThreadCount, }); -const ChatViewSelector = () => { - const { client } = useChatContext(); - const { unreadThreadCount } = useStateStore(client.threads.state, selector); +export const ChatViewChannelsSelectorButton = () => { + const { activeChatView, setActiveChatView } = useChatViewContext(); + const { t } = useTranslationContext(); - const { activeChatView, setActiveChatView } = useContext(ChatViewContext); + return ( + setActiveChatView('channels')} + text={t('Channels')} + /> + ); +}; + +export const ChatViewThreadsSelectorButton = () => { + const { client } = useChatContext(); + const { unreadThreadCount } = useStateStore(client.threads.state, selector) ?? { + unreadThreadCount: 0, + }; + const { activeChatView, setActiveChatView } = useChatViewContext(); + const { t } = useTranslationContext(); return ( -
- - -
+ setActiveChatView('threads')} + > + + + +
{t('Threads')}
+
); }; +export type ChatViewSelectorItem = { + Component: React.ComponentType; + type: string & {}; +}; + +export type ChatViewSelectorEntry = ChatViewSelectorItem; + +export type ChatViewSelectorProps = { + itemSet?: ChatViewSelectorEntry[]; +}; + +export const defaultChatViewSelectorItemSet: ChatViewSelectorEntry[] = [ + { + Component: ChatViewChannelsSelectorButton, + type: 'channels' as string & {}, + }, + { + Component: ChatViewThreadsSelectorButton, + type: 'threads' as string & {}, + }, +]; + +const ChatViewSelector = ({ + itemSet = defaultChatViewSelectorItemSet, +}: ChatViewSelectorProps) => ( +
+ {itemSet.map(({ Component, type }) => ( + + ))} +
+); + ChatView.Channels = ChannelsView; ChatView.Threads = ThreadsView; ChatView.ThreadAdapter = ThreadAdapter; diff --git a/src/components/ChatView/styling/ChatView.scss b/src/components/ChatView/styling/ChatView.scss new file mode 100644 index 0000000000..3cfd31ef93 --- /dev/null +++ b/src/components/ChatView/styling/ChatView.scss @@ -0,0 +1,65 @@ +.str-chat { + --str-chat-selector-background-color: var(--str-chat__secondary-background-color); + --str-chat-selector-border-color: var(--str-chat__surface-color); + + --str-chat-selector-button-color-default: var(--str-chat__text-low-emphasis-color); + --str-chat-selector-button-color-selected: var(--str-chat__text-color); + --str-chat-selector-button-background-color-default: transparent; + --str-chat-selector-button-background-color-selected: var(--str-chat__surface-color); +} + +.str-chat__chat-view { + display: flex; + width: 100%; + height: 100%; + + .str-chat__chat-view__selector { + display: flex; + flex-direction: column; + padding-inline: 8px; + padding-block: 16px; + gap: 20px; + border-right: 1px solid var(--str-chat-selector-border-color); + background-color: var(--str-chat-selector-background-color); + + .str-chat__chat-view__selector-button { + --str-chat-icon-height: 20px; + --str-chat-icon-width: 20px; + --str-chat-unread-count-badge-absolute-offset-vertical: 25%; + --str-chat-icon-color: var(--str-chat-selector-button-color-default); + + flex-direction: column; + gap: 4px; + padding-inline: 10px; + padding-block: 16px; + border-radius: 8px; + font-size: 12px; + line-height: 1; + position: relative; + + background: var(--str-chat-selector-button-background-color-default); + color: var(--str-chat-selector-button-color-default); + + &[aria-selected='true'] { + --str-chat-icon-color: var(--str-chat-selector-button-color-selected); + color: var(--str-chat-selector-button-color-selected); + background: var(--str-chat-selector-button-background-color-selected); + } + + svg { + height: var(--str-chat-icon-height); + width: var(--str-chat-icon-height); + } + } + } + + .str-chat__chat-view__channels { + display: flex; + flex-grow: 1; + } + + .str-chat__chat-view__threads { + display: flex; + flex-grow: 1; + } +} diff --git a/src/components/ChatView/styling/index.scss b/src/components/ChatView/styling/index.scss new file mode 100644 index 0000000000..a90bfa8923 --- /dev/null +++ b/src/components/ChatView/styling/index.scss @@ -0,0 +1 @@ +@use 'ChatView'; diff --git a/src/i18n/de.json b/src/i18n/de.json index 2ad648346b..5da8de3460 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -79,6 +79,7 @@ "Cancel": "Abbrechen", "Cannot seek in the recording": "In der Aufnahme kann nicht gesucht werden", "Channel Missing": "Kanal fehlt", + "Channels": "Kanäle", "Close": "Schließen", "Close emoji picker": "Emoji-Auswahl schließen", "Commands": "Befehle", @@ -245,6 +246,7 @@ "Thread": "Thread", "Thread has not been found": "Thread wurde nicht gefunden", "Thread reply": "Thread-Antwort", + "Threads": "Diskussionen", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/en.json b/src/i18n/en.json index 153b319eaf..97b4b28c57 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -79,6 +79,7 @@ "Cancel": "Cancel", "Cannot seek in the recording": "Cannot seek in the recording", "Channel Missing": "Channel Missing", + "Channels": "Channels", "Close": "Close", "Close emoji picker": "Close emoji picker", "Commands": "Commands", @@ -245,6 +246,7 @@ "Thread": "Thread", "Thread has not been found": "Thread has not been found", "Thread reply": "Thread reply", + "Threads": "Threads", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/es.json b/src/i18n/es.json index ba5d77b61c..7281a54896 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -84,6 +84,7 @@ "Cancel": "Cancelar", "Cannot seek in the recording": "No se puede buscar en la grabación", "Channel Missing": "Falta canal", + "Channels": "Canales", "Close": "Cerrar", "Close emoji picker": "Cerrar el selector de emojis", "Commands": "Comandos", @@ -254,6 +255,7 @@ "Thread": "Hilo", "Thread has not been found": "No se ha encontrado el hilo", "Thread reply": "Respuesta en hilo", + "Threads": "Hilos", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index cdec2052b6..06630dc5ec 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -84,6 +84,7 @@ "Cancel": "Annuler", "Cannot seek in the recording": "Impossible de rechercher dans l'enregistrement", "Channel Missing": "Canal Manquant", + "Channels": "Canaux", "Close": "Fermer", "Close emoji picker": "Fermer le sélecteur d'émojis", "Commands": "Commandes", @@ -254,6 +255,7 @@ "Thread": "Fil de discussion", "Thread has not been found": "Le fil de discussion n'a pas été trouvé", "Thread reply": "Réponse dans le fil", + "Threads": "Fils", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/hi.json b/src/i18n/hi.json index 50d5ebbda7..b8d732dae2 100644 --- a/src/i18n/hi.json +++ b/src/i18n/hi.json @@ -79,6 +79,7 @@ "Cancel": "रद्द करें", "Cannot seek in the recording": "रेकॉर्डिंग में खोज नहीं की जा सकती", "Channel Missing": "चैनल उपलब्ध नहीं है", + "Channels": "चैनल", "Close": "बंद करे", "Close emoji picker": "इमोजी पिकर बंद करें", "Commands": "कमांड", @@ -246,6 +247,7 @@ "Thread": "रिप्लाई थ्रेड", "Thread has not been found": "थ्रेड नहीं मिला", "Thread reply": "थ्रेड में उत्तर", + "Threads": "थ्रेड्स", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/it.json b/src/i18n/it.json index 5995341e56..ad9c70f9f7 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -84,6 +84,7 @@ "Cancel": "Annulla", "Cannot seek in the recording": "Impossibile cercare nella registrazione", "Channel Missing": "Il canale non esiste", + "Channels": "Canali", "Close": "Chiudi", "Close emoji picker": "Chiudi il selettore di emoji", "Commands": "Comandi", @@ -254,6 +255,7 @@ "Thread": "Discussione", "Thread has not been found": "Discussione non trovata", "Thread reply": "Risposta nella discussione", + "Threads": "Thread", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/ja.json b/src/i18n/ja.json index a5b73a7b71..9f4c4e58a4 100644 --- a/src/i18n/ja.json +++ b/src/i18n/ja.json @@ -79,6 +79,7 @@ "Cancel": "キャンセル", "Cannot seek in the recording": "録音中にシークできません", "Channel Missing": "チャネルがありません", + "Channels": "チャンネル", "Close": "閉める", "Close emoji picker": "絵文字ピッカーを閉める", "Commands": "コマンド", @@ -245,6 +246,7 @@ "Thread": "スレッド", "Thread has not been found": "スレッドが見つかりませんでした", "Thread reply": "スレッドの返信", + "Threads": "スレッド", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/ko.json b/src/i18n/ko.json index 9bfcc346a1..9e37a9b42a 100644 --- a/src/i18n/ko.json +++ b/src/i18n/ko.json @@ -79,6 +79,7 @@ "Cancel": "취소", "Cannot seek in the recording": "녹음에서 찾을 수 없습니다", "Channel Missing": "채널 누락", + "Channels": "채널", "Close": "닫기", "Close emoji picker": "이모티콘 선택기 닫기", "Commands": "명령어", @@ -245,6 +246,7 @@ "Thread": "스레드", "Thread has not been found": "스레드를 찾을 수 없습니다", "Thread reply": "스레드 답장", + "Threads": "스레드", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/nl.json b/src/i18n/nl.json index e6f6eb332a..3bf733df6f 100644 --- a/src/i18n/nl.json +++ b/src/i18n/nl.json @@ -79,6 +79,7 @@ "Cancel": "Annuleer", "Cannot seek in the recording": "Kan niet zoeken in de opname", "Channel Missing": "Kanaal niet gevonden", + "Channels": "Kanalen", "Close": "Sluit", "Close emoji picker": "Sluit de emoji-kiezer", "Commands": "Commando's", @@ -247,6 +248,7 @@ "Thread": "Draadje", "Thread has not been found": "Draadje niet gevonden", "Thread reply": "Draadje antwoord", + "Threads": "Discussies", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/pt.json b/src/i18n/pt.json index 63a1cbe752..ec268017f0 100644 --- a/src/i18n/pt.json +++ b/src/i18n/pt.json @@ -84,6 +84,7 @@ "Cancel": "Cancelar", "Cannot seek in the recording": "Não é possível buscar na gravação", "Channel Missing": "Canal ausente", + "Channels": "Canais", "Close": "Fechar", "Close emoji picker": "Fechar seletor de emoji", "Commands": "Comandos", @@ -254,6 +255,7 @@ "Thread": "Fio", "Thread has not been found": "Fio não encontrado", "Thread reply": "Resposta no fio", + "Threads": "Tópicos", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/ru.json b/src/i18n/ru.json index 87bfa009b6..38c76dee72 100644 --- a/src/i18n/ru.json +++ b/src/i18n/ru.json @@ -89,6 +89,7 @@ "Cancel": "Отмена", "Cannot seek in the recording": "Невозможно осуществить поиск в записи", "Channel Missing": "Канал не найден", + "Channels": "Каналы", "Close": "Закрыть", "Close emoji picker": "Закрыть окно выбора смайлов", "Commands": "Команды", @@ -263,6 +264,7 @@ "Thread": "Ветка", "Thread has not been found": "Ветка не найдена", "Thread reply": "Ответ в ветке", + "Threads": "Треды", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/i18n/tr.json b/src/i18n/tr.json index f40a285b2b..24c2de5beb 100644 --- a/src/i18n/tr.json +++ b/src/i18n/tr.json @@ -79,6 +79,7 @@ "Cancel": "İptal", "Cannot seek in the recording": "Kayıtta arama yapılamıyor", "Channel Missing": "Kanal bulunamıyor", + "Channels": "Kanallar", "Close": "Kapat", "Close emoji picker": "Emoji seçiciyi kapat", "Commands": "Komutlar", @@ -245,6 +246,7 @@ "Thread": "Konu", "Thread has not been found": "Konu bulunamadı", "Thread reply": "Konu yanıtı", + "Threads": "İleti dizileri", "timestamp/DateSeparator": "{{ timestamp | timestampFormatter(calendar: true; calendarFormats: { \"sameDay\": \"[Today]\", \"nextDay\": \"[Tomorrow]\", \"lastDay\": \"[Yesterday]\", \"nextWeek\": \"dddd\", \"lastWeek\": \"[Last] dddd\", \"sameElse\": \"ddd, D MMM\" }) }}", "timestamp/LiveLocation": "{{ timestamp | timestampFormatter(calendar: true) }}", "timestamp/MessageTimestamp": "{{ timestamp | timestampFormatter(calendar: true) }}", diff --git a/src/styling/index.scss b/src/styling/index.scss index 15911223f1..7fb2622621 100644 --- a/src/styling/index.scss +++ b/src/styling/index.scss @@ -23,6 +23,7 @@ @use '../components/AudioPlayback/styling' as AudioPlayback; @use '../components/Avatar/styling/Avatar' as Avatar; @use '../components/Avatar/styling/GroupAvatar' as GroupAvatar; +@use '../components/ChatView/styling' as ChatView; @use '../components/DateSeparator/styling' as DateSeparator; @use '../components/Gallery/styling' as Gallery; @use '../components/MediaRecorder/AudioRecorder/styling' as AudioRecorder; From fca697d5d2cfe8b310a385b3319bd0a9e2054141 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:21:14 +0100 Subject: [PATCH 15/20] feat(demo): add app settings with reactions configuration tab --- examples/vite/src/App.tsx | 168 ++++++----------- .../vite/src/AppSettings/AppSettings.scss | 150 ++++++++++++++++ examples/vite/src/AppSettings/AppSettings.tsx | 68 +++++++ examples/vite/src/AppSettings/index.ts | 2 + examples/vite/src/AppSettings/state.ts | 26 +++ .../tabs/Reactions/ReactionsTab.tsx | 133 ++++++++++++++ .../src/AppSettings/tabs/Reactions/index.ts | 1 + .../tabs/Reactions/reactionsExampleData.ts | 170 ++++++++++++++++++ .../src/Sidebar/ChatViewSelectorItemSet.tsx | 10 ++ examples/vite/src/index.scss | 5 +- src/components/Icons/IconCog.tsx | 13 ++ src/components/Icons/IconCross.tsx | 1 - src/components/Icons/IconEmoji.tsx | 16 ++ src/components/Icons/index.ts | 2 + src/components/Icons/styling/IconCog.scss | 9 + .../Icons/styling/IconDoubleCheckmark.scss | 2 +- src/components/Icons/styling/IconEmoji.scss | 11 ++ .../Icons/styling/IconSingleCheckmark.scss | 2 +- src/components/Icons/styling/index.scss | 2 + 19 files changed, 674 insertions(+), 117 deletions(-) create mode 100644 examples/vite/src/AppSettings/AppSettings.scss create mode 100644 examples/vite/src/AppSettings/AppSettings.tsx create mode 100644 examples/vite/src/AppSettings/index.ts create mode 100644 examples/vite/src/AppSettings/state.ts create mode 100644 examples/vite/src/AppSettings/tabs/Reactions/ReactionsTab.tsx create mode 100644 examples/vite/src/AppSettings/tabs/Reactions/index.ts create mode 100644 examples/vite/src/AppSettings/tabs/Reactions/reactionsExampleData.ts create mode 100644 examples/vite/src/Sidebar/ChatViewSelectorItemSet.tsx create mode 100644 src/components/Icons/IconCog.tsx create mode 100644 src/components/Icons/IconEmoji.tsx create mode 100644 src/components/Icons/styling/IconCog.scss create mode 100644 src/components/Icons/styling/IconEmoji.scss diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index 0a4bc1e910..32f8352f23 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -1,12 +1,4 @@ -import { - createContext, - type PropsWithChildren, - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; +import { useCallback, useEffect, useMemo } from 'react'; import { ChannelFilters, ChannelOptions, @@ -35,14 +27,14 @@ import { MessageList, Window, WithComponents, - GroupAvatar, ReactionsList, - useMessageContext, } from 'stream-chat-react'; import { createTextComposerEmojiMiddleware, EmojiPicker } from 'stream-chat-react/emojis'; import { init, SearchIndex } from 'emoji-mart'; import data from '@emoji-mart/data'; import { humanId } from 'human-id'; +import { chatViewSelectorItemSet } from './Sidebar/ChatViewSelectorItemSet.tsx'; +import { useAppSettingsState } from './AppSettings'; init({ data }); @@ -56,7 +48,8 @@ if (!apiKey) { } const options: ChannelOptions = { limit: 5, presence: true, state: true }; -const sort: ChannelSort = { pinned_at: 1, last_message_at: -1, updated_at: -1 }; +// pinned_at param leads to BE not returning (empty) channels +const sort: ChannelSort = { last_message_at: -1, updated_at: -1 }; // @ts-ignore const isMessageAIGenerated = (message: LocalMessage) => !!message?.ai_generated; @@ -93,7 +86,8 @@ const useUser = () => { }; const CustomMessageReactions = (props: React.ComponentProps) => { - const { visualStyle, verticalPosition, flipHorizontalPosition } = useContext(TempCtx); + const { reactions } = useAppSettingsState(); + const { visualStyle, verticalPosition, flipHorizontalPosition } = reactions; return ( ({ - visualStyle: 'clustered', - verticalPosition: 'top', - flipHorizontalPosition: false, -}); - const App = () => { const { userId, tokenProvider } = useUser(); - const [tempCtxValue, setTempCtxValue] = useState({ - visualStyle: 'clustered', - verticalPosition: 'top', - flipHorizontalPosition: false, - }); - const chatClient = useCreateChatClient({ apiKey, tokenOrProvider: tokenProvider, @@ -133,9 +110,12 @@ const App = () => { const filters: ChannelFilters = useMemo( () => ({ - members: { $in: [userId] }, - type: 'messaging', - archived: false, + $or: [ + { + members: { $in: [userId] }, + }, + { type: 'public' }, + ], }), [userId], ); @@ -184,85 +164,49 @@ const App = () => { if (!chatClient) return <>Loading...; return ( - - - - - - - - - -
- - - -
- - - - -
- -
-
- - - - - - -
-
-
-
+ + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/examples/vite/src/AppSettings/AppSettings.scss b/examples/vite/src/AppSettings/AppSettings.scss new file mode 100644 index 0000000000..963215f0ee --- /dev/null +++ b/examples/vite/src/AppSettings/AppSettings.scss @@ -0,0 +1,150 @@ +@layer stream-app-overrides { + .app__settings-group { + height: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + gap: 16px; + + .app__settings-group_button { + color: var(--text-secondary); + + svg { + height: 2rem; + width: 2rem; + } + } + } + + .app__settings-modal { + display: flex; + flex-direction: column; + width: min(920px, 90vw); + max-height: min(80vh, 760px); + min-height: min(520px, 72vh); + background: #fff; + border-radius: 14px; + } + + .app__settings-modal__header { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 16px 20px; + font-size: 1.5rem; + font-weight: 700; + border-bottom: 1px solid #dfe5ef; + + svg.str-chat__icon--cog { + height: 1.75rem; + width: 1.75rem; + } + } + + .app__settings-modal__body { + display: grid; + grid-template-columns: minmax(180px, 240px) minmax(0, 1fr); + min-height: 0; + height: 100%; + } + + .app__settings-modal__tabs { + overflow-y: auto; + border-right: 1px solid #dfe5ef; + padding: 10px; + } + + .app__settings-modal__tab { + width: 100%; + white-space: nowrap; + justify-content: flex-start; + font-weight: 500; + margin-bottom: 6px; + } + + .app__settings-modal__tab[aria-selected='true'], + .app__settings-modal__tab.app__settings-modal__tab--active { + background: #e9f0ff; + border-color: #3167f6; + font-weight: 600; + } + + .app__settings-modal__content { + overflow-y: auto; + padding: 20px 24px; + } + + .app__settings-modal__content-stack { + display: flex; + flex-direction: column; + gap: 20px; + } + + .app__settings-modal__field { + display: flex; + flex-direction: column; + gap: 10px; + } + + .app__settings-modal__field-label { + font-weight: 600; + color: #2f3550; + } + + .app__settings-modal__options-row { + display: flex; + gap: 10px; + flex-wrap: wrap; + } + + .app__settings-modal__option-button[aria-pressed='true'] { + border-color: #3167f6; + background: #e9f0ff; + font-weight: 600; + } + + .app__settings-modal__checkbox-label { + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + } + + .app__settings-modal__preview { + border: 1px solid var(--border); + border-radius: 12px; + padding: 12px; + background: var(--bg-surface); + + .str-chat__li--single { + list-style: none; + margin: 0; + padding: 0; + } + + .str-chat__message-options, + .str-chat__message-actions-box-button { + display: none; + } + } + + @media (max-width: 760px) { + .app__settings-modal { + width: min(92vw, 680px); + } + + .app__settings-modal__body { + grid-template-columns: 1fr; + } + + .app__settings-modal__tabs { + border-right: 0; + border-bottom: 1px solid #dfe5ef; + display: flex; + gap: 8px; + padding: 10px 12px; + overflow-x: auto; + overflow-y: hidden; + } + } +} diff --git a/examples/vite/src/AppSettings/AppSettings.tsx b/examples/vite/src/AppSettings/AppSettings.tsx new file mode 100644 index 0000000000..39b163b6ea --- /dev/null +++ b/examples/vite/src/AppSettings/AppSettings.tsx @@ -0,0 +1,68 @@ +import { + Button, + ChatViewSelectorButton, + GlobalModal, + IconCog, + IconEmoji, +} from 'stream-chat-react'; +import { type ComponentType, useState } from 'react'; +import { ReactionsTab } from './tabs/Reactions'; + +type TabId = 'reactions'; + +const tabConfig: { Icon: ComponentType; id: TabId; title: string }[] = [ + { Icon: IconEmoji, id: 'reactions', title: 'Reactions' }, +]; + +export const AppSettings = () => { + const [activeTab, setActiveTab] = useState('reactions'); + const [open, setOpen] = useState(false); + + return ( +
+ setOpen(true)} + text='Settings' + /> + setOpen(false)}> +
+
+ + Settings +
+
+ +
+ {activeTab === 'reactions' && } +
+
+
+
+
+ ); +}; diff --git a/examples/vite/src/AppSettings/index.ts b/examples/vite/src/AppSettings/index.ts new file mode 100644 index 0000000000..5126fea671 --- /dev/null +++ b/examples/vite/src/AppSettings/index.ts @@ -0,0 +1,2 @@ +export * from './AppSettings'; +export * from './state'; diff --git a/examples/vite/src/AppSettings/state.ts b/examples/vite/src/AppSettings/state.ts new file mode 100644 index 0000000000..01971c3165 --- /dev/null +++ b/examples/vite/src/AppSettings/state.ts @@ -0,0 +1,26 @@ +import { StateStore } from 'stream-chat'; +import { useStateStore } from 'stream-chat-react'; + +export type ReactionsSettingsState = { + flipHorizontalPosition: boolean; + verticalPosition: 'top' | 'bottom'; + visualStyle: 'clustered' | 'segmented'; +}; + +export type AppSettingsState = { + reactions: ReactionsSettingsState; +}; + +const defaultAppSettingsState: AppSettingsState = { + reactions: { + flipHorizontalPosition: false, + verticalPosition: 'top', + visualStyle: 'clustered', + }, +}; + +export const appSettingsStore = new StateStore(defaultAppSettingsState); + +export const useAppSettingsState = () => + useStateStore(appSettingsStore, (nextValue: AppSettingsState) => nextValue) ?? + defaultAppSettingsState; diff --git a/examples/vite/src/AppSettings/tabs/Reactions/ReactionsTab.tsx b/examples/vite/src/AppSettings/tabs/Reactions/ReactionsTab.tsx new file mode 100644 index 0000000000..f572518964 --- /dev/null +++ b/examples/vite/src/AppSettings/tabs/Reactions/ReactionsTab.tsx @@ -0,0 +1,133 @@ +import { + Button, + ChannelActionProvider, + ChannelStateProvider, + ComponentProvider, + Message, + useComponentContext, +} from 'stream-chat-react'; +import { appSettingsStore, useAppSettingsState } from '../../state'; +import { + reactionsPreviewChannelActions, + reactionsPreviewChannelState, + reactionsPreviewMessage, + reactionsPreviewOptions, +} from './reactionsExampleData'; + +export const ReactionsTab = () => { + const state = useAppSettingsState(); + const { reactions } = state; + const componentContext = useComponentContext(); + + return ( +
+
+
Visual style
+
+ + +
+
+ +
+
Vertical position
+
+ + +
+
+ +
+
Horizontal alignment
+
+ + +
+
+ +
+
Preview
+
+ + + +
  • + +
  • +
    +
    +
    +
    +
    +
    + ); +}; diff --git a/examples/vite/src/AppSettings/tabs/Reactions/index.ts b/examples/vite/src/AppSettings/tabs/Reactions/index.ts new file mode 100644 index 0000000000..d003e2af00 --- /dev/null +++ b/examples/vite/src/AppSettings/tabs/Reactions/index.ts @@ -0,0 +1 @@ +export * from './ReactionsTab'; diff --git a/examples/vite/src/AppSettings/tabs/Reactions/reactionsExampleData.ts b/examples/vite/src/AppSettings/tabs/Reactions/reactionsExampleData.ts new file mode 100644 index 0000000000..836df32e7c --- /dev/null +++ b/examples/vite/src/AppSettings/tabs/Reactions/reactionsExampleData.ts @@ -0,0 +1,170 @@ +import type { LocalMessage } from 'stream-chat'; + +const fireReactionTimestamp = '2026-02-12T06:39:57.188362Z'; +const firstLikeReactionTimestamp = '2026-02-12T06:39:56.237389Z'; +const secondLikeReactionTimestamp = '2026-02-12T06:39:52.237389Z'; +const heartReactionTimestamp = '2026-02-12T06:35:58.021196Z'; + +export const reactionsPreviewMessage: LocalMessage = { + created_at: new Date('2026-02-12T06:34:40.000000Z'), + deleted_at: null, + id: 'settings-preview-message-id', + latest_reactions: [ + { + created_at: fireReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'fire', + updated_at: fireReactionTimestamp, + user: { + id: 'test-user', + language: '', + role: 'user', + teams: [], + }, + user_id: 'test-user', + }, + { + created_at: firstLikeReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'like', + updated_at: firstLikeReactionTimestamp, + user: { + id: 'test-user', + language: '', + role: 'user', + teams: [], + }, + user_id: 'test-user', + }, + { + created_at: secondLikeReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'like', + updated_at: secondLikeReactionTimestamp, + user: { + id: 'test-user-2', + language: '', + role: 'user', + teams: [], + }, + user_id: 'test-user-2', + }, + { + created_at: heartReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'heart', + updated_at: heartReactionTimestamp, + user: { id: 'test-user-2' }, + user_id: 'test-user-2', + }, + ] as LocalMessage['latest_reactions'], + own_reactions: [ + { + created_at: fireReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'fire', + updated_at: fireReactionTimestamp, + user: { id: 'test-user' }, + user_id: 'test-user', + }, + { + created_at: firstLikeReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'like', + updated_at: firstLikeReactionTimestamp, + user: { id: 'test-user' }, + user_id: 'test-user', + }, + { + created_at: heartReactionTimestamp, + message_id: 'settings-preview-message-id', + score: 1, + type: 'heart', + updated_at: heartReactionTimestamp, + user: { id: 'test-user' }, + user_id: 'test-user', + }, + ] as LocalMessage['own_reactions'], + pinned_at: null, + reaction_counts: { fire: 1, heart: 1, like: 2 }, + reaction_groups: { + fire: { + count: 1, + first_reaction_at: fireReactionTimestamp, + last_reaction_at: fireReactionTimestamp, + sum_scores: 1, + }, + heart: { + count: 1, + first_reaction_at: heartReactionTimestamp, + last_reaction_at: heartReactionTimestamp, + sum_scores: 1, + }, + like: { + count: 2, + first_reaction_at: secondLikeReactionTimestamp, + last_reaction_at: firstLikeReactionTimestamp, + sum_scores: 2, + }, + } as LocalMessage['reaction_groups'], + reaction_scores: { fire: 1, heart: 1, like: 2 }, + status: 'received', + text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed lectus nibh, rutrum in risus eget, dictum commodo dolor. Donec augue nisi, sollicitudin sed magna ut, tincidunt pretium lorem. ', + type: 'regular', + updated_at: new Date('2026-02-12T06:40:00.000000Z'), + user: { + id: 'settings-preview-user', + image: 'https://getstream.io/random_svg/?id=preview-user&name=Preview+User', + name: 'Preview User', + }, +}; + +export const reactionsPreviewChannelState = { + channel: { + state: { + membership: { + channel_role: 'channel_member', + is_moderator: false, + role: 'member', + }, + }, + }, + channelCapabilities: {}, + channelConfig: undefined, + imageAttachmentSizeHandler: () => ({ url: '' }), + notifications: [], + shouldGenerateVideoThumbnail: false, + videoAttachmentSizeHandler: () => ({ url: '' }), +}; + +export const reactionsPreviewChannelActions = { + addNotification: () => undefined, + closeThread: () => undefined, + onMentionsClick: () => undefined, + onMentionsHover: () => undefined, + openThread: () => undefined, +}; + +export const reactionsPreviewOptions = [ + { + Component: () => '🔥', + name: 'Fire', + type: 'fire', + }, + { + Component: () => '👍', + name: 'Thumbs up', + type: 'like', + }, + { + Component: () => '❤️', + name: 'Heart', + type: 'heart', + }, +]; diff --git a/examples/vite/src/Sidebar/ChatViewSelectorItemSet.tsx b/examples/vite/src/Sidebar/ChatViewSelectorItemSet.tsx new file mode 100644 index 0000000000..c28cc05392 --- /dev/null +++ b/examples/vite/src/Sidebar/ChatViewSelectorItemSet.tsx @@ -0,0 +1,10 @@ +import { + type ChatViewSelectorEntry, + defaultChatViewSelectorItemSet, +} from 'stream-chat-react'; +import { AppSettings } from '../AppSettings'; + +export const chatViewSelectorItemSet: ChatViewSelectorEntry[] = [ + ...defaultChatViewSelectorItemSet, + { Component: AppSettings, type: 'settings' }, +]; diff --git a/examples/vite/src/index.scss b/examples/vite/src/index.scss index 9636c26853..e5ad46e401 100644 --- a/examples/vite/src/index.scss +++ b/examples/vite/src/index.scss @@ -1,4 +1,4 @@ -@layer stream, stream-new, stream-overrides; +@layer stream, stream-new, stream-overrides, stream-app-overrides; @import url('./stream-imports-theme.scss') layer(stream); @import url('./stream-imports-layout.scss') layer(stream); @@ -6,6 +6,7 @@ // v3 CSS import @import url('stream-chat-react/dist/css/index.css') layer(stream-new); @import url('stream-chat-react/dist/css/emojis.css') layer(stream-new); +@import url('./AppSettings/AppSettings.scss') layer(stream-app-overrides); :root { font-synthesis: none; @@ -109,7 +110,7 @@ body { } } - @container (max-width: 1025px) { + @container (max-width: 760px) { .str-chat__channel-list, .str-chat__chat-view__selector { display: none; diff --git a/src/components/Icons/IconCog.tsx b/src/components/Icons/IconCog.tsx new file mode 100644 index 0000000000..e9b548f2a8 --- /dev/null +++ b/src/components/Icons/IconCog.tsx @@ -0,0 +1,13 @@ +import { BaseIcon } from './BaseIcon'; +import type { ComponentProps } from 'react'; +import clsx from 'clsx'; + +export const IconCog = ({ className, ...props }: ComponentProps<'svg'>) => ( + + + +); diff --git a/src/components/Icons/IconCross.tsx b/src/components/Icons/IconCross.tsx index 715131d91e..523588587b 100644 --- a/src/components/Icons/IconCross.tsx +++ b/src/components/Icons/IconCross.tsx @@ -7,7 +7,6 @@ export const IconCross = ({ className, ...props }: ComponentProps<'svg'>) => ( {...props} className={clsx('str-chat__icon--cross', className)} viewBox='0 0 10 10' - xmlns='http://www.w3.org/2000/svg' > diff --git a/src/components/Icons/IconEmoji.tsx b/src/components/Icons/IconEmoji.tsx new file mode 100644 index 0000000000..55b866265a --- /dev/null +++ b/src/components/Icons/IconEmoji.tsx @@ -0,0 +1,16 @@ +import { BaseIcon } from './BaseIcon'; +import type { ComponentProps } from 'react'; +import clsx from 'clsx'; + +export const IconEmoji = ({ className, ...props }: ComponentProps<'svg'>) => ( + + + + + + +); diff --git a/src/components/Icons/index.ts b/src/components/Icons/index.ts index adf4e9b2bb..9b1da8717d 100644 --- a/src/components/Icons/index.ts +++ b/src/components/Icons/index.ts @@ -5,9 +5,11 @@ export * from './IconChainLink'; export * from './IconCheckmark'; export * from './IconChevronRight'; export * from './IconClose'; +export * from './IconCog'; export * from './IconCommand'; export * from './IconCross'; export * from './IconDoubleCheckmark'; +export * from './IconEmoji'; export * from './IconExclamationCircle'; export * from './IconExclamationTriangle'; export * from './IconEyeOpen'; diff --git a/src/components/Icons/styling/IconCog.scss b/src/components/Icons/styling/IconCog.scss new file mode 100644 index 0000000000..e093d88996 --- /dev/null +++ b/src/components/Icons/styling/IconCog.scss @@ -0,0 +1,9 @@ +.str-chat__icon--cog { + fill: none; + height: 16px; + width: 16px; + + path { + fill: currentColor; + } +} diff --git a/src/components/Icons/styling/IconDoubleCheckmark.scss b/src/components/Icons/styling/IconDoubleCheckmark.scss index 95b0573432..a096747a8a 100644 --- a/src/components/Icons/styling/IconDoubleCheckmark.scss +++ b/src/components/Icons/styling/IconDoubleCheckmark.scss @@ -11,4 +11,4 @@ stroke-width: 1.5; } } -} \ No newline at end of file +} diff --git a/src/components/Icons/styling/IconEmoji.scss b/src/components/Icons/styling/IconEmoji.scss new file mode 100644 index 0000000000..8ac07e2faf --- /dev/null +++ b/src/components/Icons/styling/IconEmoji.scss @@ -0,0 +1,11 @@ +.str-chat { + .str-chat__icon--emoji { + fill: none; + height: 16px; + width: 16px; + + path { + fill: currentColor; + } + } +} diff --git a/src/components/Icons/styling/IconSingleCheckmark.scss b/src/components/Icons/styling/IconSingleCheckmark.scss index f389203907..0c38bbb246 100644 --- a/src/components/Icons/styling/IconSingleCheckmark.scss +++ b/src/components/Icons/styling/IconSingleCheckmark.scss @@ -8,4 +8,4 @@ fill: currentColor; } } -} \ No newline at end of file +} diff --git a/src/components/Icons/styling/index.scss b/src/components/Icons/styling/index.scss index a6b1b5cb62..6f49ddeb85 100644 --- a/src/components/Icons/styling/index.scss +++ b/src/components/Icons/styling/index.scss @@ -6,9 +6,11 @@ @use 'IconCheckmark'; @use 'IconChevronRight'; @use 'IconClose'; +@use 'IconCog'; @use 'IconCommand'; @use 'IconCross'; @use 'IconDoubleCheckmark'; +@use 'IconEmoji'; @use 'IconExclamationCircle'; @use 'IconExclamationTriangle'; @use 'IconEyeOpen'; From 250f080c8a4424a39e6ca1abc640e752e9a67d3d Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:23:23 +0100 Subject: [PATCH 16/20] fix: fix lint issues --- src/components/Avatar/styling/Avatar.scss | 1 - src/components/DateSeparator/styling/DateSeparator.scss | 3 +-- src/components/DateSeparator/styling/index.scss | 2 +- src/components/Gallery/styling/index.scss | 2 +- src/components/Modal/styling/Modal.scss | 2 +- src/components/Modal/styling/index.scss | 2 +- src/components/Reactions/styling/ReactionList.scss | 1 - src/components/VideoPlayer/styling/VideoThumbnail.scss | 2 +- src/components/VideoPlayer/styling/index.scss | 2 +- 9 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/components/Avatar/styling/Avatar.scss b/src/components/Avatar/styling/Avatar.scss index 6a4a4efe1b..cd364c5f4e 100644 --- a/src/components/Avatar/styling/Avatar.scss +++ b/src/components/Avatar/styling/Avatar.scss @@ -144,4 +144,3 @@ display: none; } } - diff --git a/src/components/DateSeparator/styling/DateSeparator.scss b/src/components/DateSeparator/styling/DateSeparator.scss index 72834ac3cd..2b83e822fa 100644 --- a/src/components/DateSeparator/styling/DateSeparator.scss +++ b/src/components/DateSeparator/styling/DateSeparator.scss @@ -20,7 +20,6 @@ width: 100%; padding: var(--spacing-xs) 0; - .str-chat__date-separator-date { display: flex; padding: var(--spacing-xxs) var(--spacing-sm); @@ -35,4 +34,4 @@ font-weight: var(--typography-font-weight-semi-bold); line-height: var(--typography-line-height-tight); } -} \ No newline at end of file +} diff --git a/src/components/DateSeparator/styling/index.scss b/src/components/DateSeparator/styling/index.scss index fcc3033fa8..7dfdc24840 100644 --- a/src/components/DateSeparator/styling/index.scss +++ b/src/components/DateSeparator/styling/index.scss @@ -1 +1 @@ -@use 'DateSeparator'; \ No newline at end of file +@use 'DateSeparator'; diff --git a/src/components/Gallery/styling/index.scss b/src/components/Gallery/styling/index.scss index 40df39d161..96de974cfd 100644 --- a/src/components/Gallery/styling/index.scss +++ b/src/components/Gallery/styling/index.scss @@ -1 +1 @@ -@use 'Gallery'; \ No newline at end of file +@use 'Gallery'; diff --git a/src/components/Modal/styling/Modal.scss b/src/components/Modal/styling/Modal.scss index 892b3cd08d..ae46f68655 100644 --- a/src/components/Modal/styling/Modal.scss +++ b/src/components/Modal/styling/Modal.scss @@ -144,4 +144,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/Modal/styling/index.scss b/src/components/Modal/styling/index.scss index 21d1deb317..d0efde238b 100644 --- a/src/components/Modal/styling/index.scss +++ b/src/components/Modal/styling/index.scss @@ -1 +1 @@ -@use 'Modal'; \ No newline at end of file +@use 'Modal'; diff --git a/src/components/Reactions/styling/ReactionList.scss b/src/components/Reactions/styling/ReactionList.scss index ba30a428ca..c4b82ca924 100644 --- a/src/components/Reactions/styling/ReactionList.scss +++ b/src/components/Reactions/styling/ReactionList.scss @@ -13,7 +13,6 @@ font-weight: var(--typography-font-weight-bold); line-height: 1; - .str-chat__message-reactions__list { list-style: none; margin: 0; diff --git a/src/components/VideoPlayer/styling/VideoThumbnail.scss b/src/components/VideoPlayer/styling/VideoThumbnail.scss index fd6092c16e..bf21d2e646 100644 --- a/src/components/VideoPlayer/styling/VideoThumbnail.scss +++ b/src/components/VideoPlayer/styling/VideoThumbnail.scss @@ -34,4 +34,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/VideoPlayer/styling/index.scss b/src/components/VideoPlayer/styling/index.scss index ecf1b0f340..366a25a95d 100644 --- a/src/components/VideoPlayer/styling/index.scss +++ b/src/components/VideoPlayer/styling/index.scss @@ -1 +1 @@ -@use "VideoThumbnail"; \ No newline at end of file +@use 'VideoThumbnail'; From 4626ccf31a27082f6bd449fab364e598322f2171 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:24:02 +0100 Subject: [PATCH 17/20] fix: avoid race condition when closing global modal by pressing Escape --- src/components/Modal/GlobalModal.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Modal/GlobalModal.tsx b/src/components/Modal/GlobalModal.tsx index 5d75302278..9b43e34f5a 100644 --- a/src/components/Modal/GlobalModal.tsx +++ b/src/components/Modal/GlobalModal.tsx @@ -53,7 +53,7 @@ export const GlobalModal = ({ }; useEffect(() => { - if (!isOpen) return; + if (!open || !isOpen) return; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') maybeClose('escape', event); @@ -61,13 +61,13 @@ export const GlobalModal = ({ document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); - }, [isOpen, maybeClose]); + }, [isOpen, maybeClose, open]); useEffect(() => { - if (open && !dialog.isOpen) { + if (open && !isOpen) { dialog.open(); } - }, [dialog, open]); + }, [dialog, isOpen, open]); if (!open || !isOpen) return null; From a0fb55fbd03bba5655881b7eff14a95f289abc52 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:24:31 +0100 Subject: [PATCH 18/20] fix: add space after message author name in message metadata --- src/components/Message/styling/Message.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Message/styling/Message.scss b/src/components/Message/styling/Message.scss index 9bcfc3da21..f68193ba65 100644 --- a/src/components/Message/styling/Message.scss +++ b/src/components/Message/styling/Message.scss @@ -388,6 +388,7 @@ .str-chat__message-simple-name { @include utils.prevent-glitch-text-overflow; + margin-inline-end: var(--spacing-xxs); } .str-chat__message-sender-name { From 3389410f44a65e48d8cccf5234c81e424e76c683 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:24:59 +0100 Subject: [PATCH 19/20] fix: fix the modal gallery placeholder overlay styles --- .../Attachment/styling/ModalGallery.scss | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/components/Attachment/styling/ModalGallery.scss b/src/components/Attachment/styling/ModalGallery.scss index 8172f4f3ae..6ebd665a94 100644 --- a/src/components/Attachment/styling/ModalGallery.scss +++ b/src/components/Attachment/styling/ModalGallery.scss @@ -39,8 +39,7 @@ } } - .str-chat__modal-gallery-placeholder { - position: relative; + .str-chat__modal-gallery__placeholder { display: flex; align-items: center; justify-content: center; @@ -57,21 +56,9 @@ font-weight: var(--typography-font-weight-medium); line-height: var(--typography-line-height-relaxed); - p { - position: relative; - z-index: 1; - } - - &::after { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 0; - background-color: var(--background-core-scrim); - } + inset: 0; + position: absolute; + background-color: var(--background-core-scrim); } } } @@ -110,4 +97,4 @@ } } } -} \ No newline at end of file +} From 040ff54e510a1540b4d7d2a82b61e5f946e91b11 Mon Sep 17 00:00:00 2001 From: martincupela Date: Thu, 12 Feb 2026 09:42:03 +0100 Subject: [PATCH 20/20] refactor: remove emoji icon from emojis plugin and use default emoji icon from stream-chat-react --- src/plugins/Emojis/EmojiPicker.tsx | 13 ++++++++----- src/plugins/Emojis/icons.tsx | 29 ----------------------------- src/plugins/Emojis/index.ts | 1 - 3 files changed, 8 insertions(+), 35 deletions(-) delete mode 100644 src/plugins/Emojis/icons.tsx diff --git a/src/plugins/Emojis/EmojiPicker.tsx b/src/plugins/Emojis/EmojiPicker.tsx index 8ed412e1cf..fc2ef66d9b 100644 --- a/src/plugins/Emojis/EmojiPicker.tsx +++ b/src/plugins/Emojis/EmojiPicker.tsx @@ -1,10 +1,13 @@ import React, { useEffect, useState } from 'react'; import Picker from '@emoji-mart/react'; -import { EmojiPickerIcon } from './icons'; import { useMessageInputContext, useTranslationContext } from '../../context'; -import type { PopperLikePlacement } from '../../components'; -import { Button, useMessageComposer } from '../../components'; +import { + Button, + IconEmoji, + type PopperLikePlacement, + useMessageComposer, +} from '../../components'; import { usePopoverPosition } from '../../components/Dialog/hooks/usePopoverPosition'; import clsx from 'clsx'; import { useIsCooldownActive } from '../../components/MessageInput/hooks/useIsCooldownActive'; @@ -67,7 +70,7 @@ export const EmojiPicker = (props: EmojiPickerProps) => { const { buttonClassName, pickerContainerClassName, wrapperClassName } = classNames; - const { ButtonIconComponent = EmojiPickerIcon } = props; + const { ButtonIconComponent = IconEmoji } = props; useEffect(() => { if (!popperElement || !referenceElement) return; @@ -118,7 +121,7 @@ export const EmojiPicker = (props: EmojiPickerProps) => { aria-expanded={displayPicker} aria-label={t('aria/Emoji picker')} className={props.buttonClassName ?? buttonClassName} - disabled={!!isCooldownActive} + disabled={isCooldownActive} onClick={() => setDisplayPicker((cv) => !cv)} ref={setReferenceElement} type='button' diff --git a/src/plugins/Emojis/icons.tsx b/src/plugins/Emojis/icons.tsx deleted file mode 100644 index e30d12bef4..0000000000 --- a/src/plugins/Emojis/icons.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { type ComponentProps } from 'react'; -import clsx from 'clsx'; - -export const EmojiPickerIcon = ({ className, ...props }: ComponentProps<'svg'>) => ( - - - - - - -); diff --git a/src/plugins/Emojis/index.ts b/src/plugins/Emojis/index.ts index 016578681c..17d0119d3c 100644 --- a/src/plugins/Emojis/index.ts +++ b/src/plugins/Emojis/index.ts @@ -1,3 +1,2 @@ export * from './EmojiPicker'; export * from './middleware'; -export { EmojiPickerIcon } from './icons';