Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 25 additions & 21 deletions src/components/Message/MessageRepliesCountButton.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { MouseEventHandler } from 'react';
import React from 'react';
import type { UserResponse } from 'stream-chat';
import React, { useMemo } from 'react';

import { useTranslationContext } from '../../context/TranslationContext';
import { useChannelStateContext, useComponentContext } from '../../context';
import { AvatarStack as DefaultAvatarStack } from '../Avatar';
Expand All @@ -13,23 +15,39 @@ export type MessageRepliesCountButtonProps = {
onClick?: MouseEventHandler;
/* The amount of replies (i.e., threaded messages) on a message */
reply_count?: number;
thread_participants?: UserResponse[];
};

function UnMemoizedMessageRepliesCountButton(props: MessageRepliesCountButtonProps) {
const { AvatarStack = DefaultAvatarStack } = useComponentContext(
MessageRepliesCountButton.name,
);
const { labelPlural, labelSingle, onClick, reply_count = 0 } = props;
const {
labelPlural,
labelSingle,
onClick,
reply_count: replyCount = 0,
thread_participants: threadParticipants = [],
} = props;
const { channelCapabilities } = useChannelStateContext();

const { t } = useTranslationContext('MessageRepliesCountButton');

if (!reply_count) return null;
const avatarStackDisplayInfo = useMemo(
() =>
threadParticipants.slice(0, 3).map((participant) => ({
imageUrl: participant.image,
userName: participant.name || participant.id,
})),
[threadParticipants],
);

if (!replyCount) return null;

let replyCountText = t('replyCount', { count: reply_count });
let replyCountText = t('replyCount', { count: replyCount });

if (labelPlural && reply_count > 1) {
replyCountText = `${reply_count} ${labelPlural}`;
if (labelPlural && replyCount > 1) {
replyCountText = `${replyCount} ${labelPlural}`;
} else if (labelSingle) {
replyCountText = `1 ${labelSingle}`;
}
Expand All @@ -44,21 +62,7 @@ function UnMemoizedMessageRepliesCountButton(props: MessageRepliesCountButtonPro
>
{replyCountText}

<AvatarStack
// TODO: figure out place to get this type of data
displayInfo={[
{
id: '0',
imageUrl: 'https://getstream.io/random_png?id=mark&name=Mark',
userName: 'Mark',
},
{
id: '1',
imageUrl: 'https://getstream.io/random_png?id=jane&name=Jane',
userName: 'Jane',
},
]}
/>
<AvatarStack displayInfo={avatarStackDisplayInfo} />
</button>
</div>
);
Expand Down
15 changes: 8 additions & 7 deletions src/components/Message/MessageSimple.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,15 @@ const MessageSimpleWithContext = (props: MessageSimpleWithContextProps) => {
)}
</div>
<MessageErrorIcon />
{showReplyCountButton && (
<MessageRepliesCountButton
onClick={handleOpenThread}
reply_count={message.reply_count}
thread_participants={message.thread_participants}
/>
)}
{showIsReplyInChannel && <MessageIsThreadReplyInChannelButtonIndicator />}
</div>
{showReplyCountButton && (
<MessageRepliesCountButton
onClick={handleOpenThread}
reply_count={message.reply_count}
/>
)}
{showIsReplyInChannel && <MessageIsThreadReplyInChannelButtonIndicator />}
{showMetadata && (
<div className='str-chat__message-metadata'>
<MessageStatus />
Expand Down
36 changes: 27 additions & 9 deletions src/components/Message/styling/Message.scss
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
--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: -6px;

/* The maximum allowed width of the message component */
--str-chat__message-max-width: calc(var(--str-chat__spacing-px) * 480);
Expand Down Expand Up @@ -210,7 +211,6 @@
grid-template-areas:
'. message-reminder'
'avatar message'
'avatar replies'
'avatar translation-notice'
'avatar custom-metadata'
'avatar metadata';
Expand All @@ -220,10 +220,18 @@

.str-chat__message-inner {
.str-chat__message-reactions-host {
justify-content: flex-start;
justify-self: flex-start;

&:has(.str-chat__message-reactions--flipped-horizontally) {
justify-content: flex-end;
justify-self: flex-end;
}

&:has(.str-chat__message-reactions--top) {
margin-left: var(--str-chat__message-reactions-host-offset-x);

&:has(.str-chat__message-reactions--flipped-horizontally) {
margin-right: var(--str-chat__message-reactions-host-offset-x);
}
}
}
}
Expand All @@ -233,7 +241,6 @@
grid-template-areas:
'message-reminder .'
'message avatar'
'replies avatar'
'translation-notice avatar'
'custom-metadata avatar'
'metadata avatar';
Expand All @@ -243,10 +250,18 @@

.str-chat__message-inner {
.str-chat__message-reactions-host {
justify-content: flex-end;
justify-self: flex-end;

&:has(.str-chat__message-reactions--flipped-horizontally) {
justify-content: flex-start;
justify-self: flex-start;
}

&:has(.str-chat__message-reactions--top) {
margin-right: var(--str-chat__message-reactions-host-offset-x);

&:has(.str-chat__message-reactions--flipped-horizontally) {
margin-left: var(--str-chat__message-reactions-host-offset-x);
}
}
}
}
Expand Down Expand Up @@ -279,15 +294,15 @@
display: grid;
grid-template-areas:
'reactions .'
'message-bubble options';
'message-bubble options'
'replies replies';
grid-template-columns: auto 1fr;
column-gap: var(--str-chat__spacing-2);
position: relative;

.str-chat__message-reactions-host {
display: flex;
grid-area: reactions;
min-width: 100%;
z-index: 1;

&:has(.str-chat__message-reactions--top) {
Expand All @@ -306,6 +321,7 @@
&:has(.str-chat__message-reactions--bottom) {
grid-template-areas:
'message-bubble options'
'replies replies'
'reactions .';
}

Expand Down Expand Up @@ -338,7 +354,8 @@
grid-template-areas:
'reminder reminder'
'. reactions'
'options message-bubble';
'options message-bubble'
'replies replies';
grid-template-columns: 1fr auto;

.str-chat__message-options {
Expand All @@ -349,6 +366,7 @@
grid-template-areas:
'reminder reminder'
'options message-bubble'
'replies replies'
'. reactions';
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
// TODO: connector styling should be defined here but applied in Message.scss
.str-chat__message.str-chat__message--me {
.str-chat__message-replies-count-button-wrapper {
justify-self: flex-end;

.str-chat__message-replies-count-button {
flex-direction: row;
}
Expand All @@ -70,6 +72,8 @@

.str-chat__message.str-chat__message--other {
.str-chat__message-replies-count-button-wrapper {
justify-self: flex-start;

.str-chat__message-replies-count-button {
flex-direction: row-reverse;
}
Expand Down
80 changes: 52 additions & 28 deletions src/components/Reactions/ReactionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { type ComponentPropsWithoutRef, useState } from 'react';
import clsx from 'clsx';

import type { ReactionsListModalProps } from './ReactionsListModal';
Expand Down Expand Up @@ -53,6 +53,18 @@ export type ReactionsListProps = Partial<
visualStyle?: 'clustered' | 'segmented' | null;
};

const FragmentOrButton = ({
buttonIf: renderButton = false,
children,
...props
}: ComponentPropsWithoutRef<'button'> & { buttonIf?: boolean }) => {
if (renderButton) {
return <button {...props}>{children}</button>;
}

return <>{children}</>;
};

const UnMemoizedReactionsList = (props: ReactionsListProps) => {
const {
flipHorizontalPosition = false,
Expand Down Expand Up @@ -96,36 +108,48 @@ const UnMemoizedReactionsList = (props: ReactionsListProps) => {
})}
role='figure'
>
<ul className='str-chat__message-reactions__list'>
{existingReactions.map(
({ EmojiComponent, reactionCount, reactionType }) =>
EmojiComponent && (
<li className='str-chat__message-reactions__list-item' key={reactionType}>
<button
className='str-chat__message-reactions__list-item-button'
onClick={() => handleReactionButtonClick(reactionType)}
<FragmentOrButton
buttonIf={visualStyle === 'clustered'}
className='str-chat__message-reactions__list-button'
onClick={() =>
setSelectedReactionType(existingReactions[0]?.reactionType ?? null)
}
>
<ul className='str-chat__message-reactions__list'>
{existingReactions.map(
({ EmojiComponent, reactionCount, reactionType }) =>
EmojiComponent && (
<li
className='str-chat__message-reactions__list-item'
key={reactionType}
>
<span className='str-chat__message-reactions__item-icon'>
<EmojiComponent />
</span>
{visualStyle === 'segmented' && (
<span
className='str-chat__message-reactions__item-count'
data-testclass='message-reactions-item-count'
>
{reactionCount}
<FragmentOrButton
buttonIf={visualStyle === 'segmented'}
className='str-chat__message-reactions__list-item-button'
onClick={() => handleReactionButtonClick(reactionType)}
>
<span className='str-chat__message-reactions__item-icon'>
<EmojiComponent />
</span>
)}
</button>
</li>
),
{visualStyle === 'segmented' && (
<span
className='str-chat__message-reactions__item-count'
data-testclass='message-reactions-item-count'
>
{reactionCount}
</span>
)}
</FragmentOrButton>
</li>
),
)}
</ul>
{visualStyle === 'clustered' && (
<span className='str-chat__message-reactions__total-count'>
{totalReactionCount}
</span>
)}
</ul>
{visualStyle === 'clustered' && (
<span className='str-chat__message-reactions__total-count'>
{totalReactionCount}
</span>
)}
</FragmentOrButton>
</div>
{selectedReactionType !== null && (
<ReactionsListModal
Expand Down
Loading
Loading