Skip to content

Commit

Permalink
feat(Reactions): adjust ReactionSelector/List to new components
Browse files Browse the repository at this point in the history
  • Loading branch information
arnautov-anton committed Feb 19, 2023
1 parent e4ed3bf commit a1a55b4
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 202 deletions.
132 changes: 55 additions & 77 deletions src/components/Reactions/ReactionSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import React, { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import clsx from 'clsx';

import { isMutableRef } from './utils/utils';

import { AvatarProps, Avatar as DefaultAvatar } from '../Avatar';
import { getStrippedEmojiData, ReactionEmoji } from '../Channel/emojiData';

import { useComponentContext } from '../../context/ComponentContext';
import { useEmojiContext } from '../../context/EmojiContext';
import { useMessageContext } from '../../context/MessageContext';

import type { NimbleEmojiProps } from 'emoji-mart';
import type { ReactionResponse } from 'stream-chat';

import type { DefaultStreamChatGenerics } from '../../types/types';
import { defaultReactionOptions, ReactionOptions } from './reactionOptions';

export type ReactionSelectorProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = {
/** Additional props to be passed to the [NimbleEmoji](https://github.com/missive/emoji-mart/blob/master/src/components/emoji/nimble-emoji.js) component from `emoji-mart` */
additionalEmojiProps?: Partial<NimbleEmojiProps>;
/** Custom UI component to display user avatar, defaults to and accepts same props as: [Avatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/Avatar.tsx) */
Avatar?: React.ElementType<AvatarProps>;
/** If true, shows the user's avatar with the reaction */
Expand All @@ -31,9 +27,9 @@ export type ReactionSelectorProps<
/** An array of the own reaction objects to distinguish own reactions visually */
own_reactions?: ReactionResponse<StreamChatGenerics>[];
/** An object that keeps track of the count of each type of reaction on a message */
reaction_counts?: { [key: string]: number };
reaction_counts?: Record<string, number>;
/** A list of the currently supported reactions on a message */
reactionOptions?: ReactionEmoji[];
reactionOptions?: ReactionOptions;
/** If true, adds a CSS class that reverses the horizontal positioning of the selector */
reverse?: boolean;
};
Expand All @@ -44,38 +40,27 @@ const UnMemoizedReactionSelector = React.forwardRef(
ref: React.ForwardedRef<HTMLDivElement | null>,
) => {
const {
additionalEmojiProps = {},
Avatar: propAvatar,
detailedView = true,
handleReaction: propHandleReaction,
latest_reactions: propLatestReactions,
own_reactions: propOwnReactions,
reaction_counts: propReactionCounts,
reactionOptions: propReactionOptions,
reactionOptions = defaultReactionOptions,
reverse = false,
} = props;

const { Avatar: contextAvatar } = useComponentContext<StreamChatGenerics>('ReactionSelector');
const { Emoji, emojiConfig } = useEmojiContext('ReactionSelector');
const {
handleReaction: contextHandleReaction,
message,
} = useMessageContext<StreamChatGenerics>('ReactionSelector');

const { defaultMinimalEmojis, emojiData: fullEmojiData, emojiSetDef } = emojiConfig || {};

const Avatar = propAvatar || contextAvatar || DefaultAvatar;
const handleReaction = propHandleReaction || contextHandleReaction;
const latestReactions = propLatestReactions || message?.latest_reactions || [];
const ownReactions = propOwnReactions || message?.own_reactions || [];
const reactionCounts = propReactionCounts || message?.reaction_counts || {};
const reactionOptions = propReactionOptions || defaultMinimalEmojis;
const reactionsAreCustom = !!propReactionOptions?.length;

const emojiData = useMemo(
() => (reactionsAreCustom ? fullEmojiData : getStrippedEmojiData(fullEmojiData)),
[fullEmojiData, reactionsAreCustom],
);

const [tooltipReactionType, setTooltipReactionType] = useState<string | null>(null);
const [tooltipPositions, setTooltipPositions] = useState<{
Expand Down Expand Up @@ -165,64 +150,57 @@ const UnMemoizedReactionSelector = React.forwardRef(
</div>
)}
<ul className='str-chat__message-reactions-list str-chat__message-reactions-options'>
{reactionOptions.map((reactionOption: ReactionEmoji) => {
const latestUser = getLatestUserForReactionType(reactionOption.id);
const count = reactionCounts && reactionCounts[reactionOption.id];
return (
<li key={`item-${reactionOption.id}`}>
<button
aria-label={`Select Reaction: ${reactionOption.name}`}
className={clsx(
'str-chat__message-reactions-list-item str-chat__message-reactions-option',
{
'str-chat__message-reactions-option-selected': iHaveReactedWithReaction(
reactionOption.id,
),
},
)}
data-text={reactionOption.id}
onClick={(event) => handleReaction(reactionOption.id, event)}
>
{!!count && detailedView && (
<div
className='latest-user str-chat__message-reactions-last-user'
onClick={hideTooltip}
onMouseEnter={(e) => showTooltip(e, reactionOption.id)}
onMouseLeave={hideTooltip}
>
{latestUser ? (
<Avatar
image={latestUser.image}
name={latestUser.name}
size={20}
user={latestUser}
/>
) : (
<div className='latest-user-not-found' />
)}
</div>
)}
{
<Suspense fallback={null}>
<span className='str-chat__message-reaction-emoji'>
<Emoji
data={emojiData}
emoji={reactionOption}
size={20}
{...(reactionsAreCustom ? additionalEmojiProps : emojiSetDef)}
/>
</span>
</Suspense>
}
{Boolean(count) && detailedView && (
<span className='str-chat__message-reactions-list-item__count'>
{count || ''}
{Object.entries(reactionOptions).map(
([reactionType, { Component, name: reactionName }]) => {
const latestUser = getLatestUserForReactionType(reactionType);
const count = reactionCounts && reactionCounts[reactionType];
return (
<li key={`item-${reactionType}`}>
<button
aria-label={`Select Reaction: ${reactionName || reactionType}`}
className={clsx(
'str-chat__message-reactions-list-item str-chat__message-reactions-option',
{
'str-chat__message-reactions-option-selected': iHaveReactedWithReaction(
reactionType,
),
},
)}
data-text={reactionType}
onClick={(event) => handleReaction(reactionType, event)}
>
{!!count && detailedView && (
<div
className='latest-user str-chat__message-reactions-last-user'
onClick={hideTooltip}
onMouseEnter={(e) => showTooltip(e, reactionType)}
onMouseLeave={hideTooltip}
>
{latestUser ? (
<Avatar
image={latestUser.image}
name={latestUser.name}
size={20}
user={latestUser}
/>
) : (
<div className='latest-user-not-found' />
)}
</div>
)}
<span className='str-chat__message-reaction-emoji'>
<Component />
</span>
)}
</button>
</li>
);
})}
{Boolean(count) && detailedView && (
<span className='str-chat__message-reactions-list-item__count'>
{count || ''}
</span>
)}
</button>
</li>
);
},
)}
</ul>
</div>
);
Expand Down
86 changes: 36 additions & 50 deletions src/components/Reactions/ReactionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,29 @@
import React, { ComponentProps, Suspense, useState } from 'react';
import React, { ComponentProps, useState } from 'react';
import clsx from 'clsx';

import { useEmojiContext } from '../../context/EmojiContext';
import type { ReactionResponse } from 'stream-chat';

import { useMessageContext } from '../../context/MessageContext';
import { useChatContext } from '../../context/ChatContext';
import { useProcessReactions } from './hooks/useProcessReactions';

import type { NimbleEmojiProps } from 'emoji-mart';
import type { ReactionResponse } from 'stream-chat';
import { PopperTooltip } from '../Tooltip';
import { useEnterLeaveHandlers } from '../Tooltip/hooks';

import type { ReactEventHandler } from '../Message/types';

import type { DefaultStreamChatGenerics } from '../../types/types';
import type { ReactionEmoji } from '../Channel/emojiData';

import { PopperTooltip } from '../Tooltip';
import { useEnterLeaveHandlers } from '../Tooltip/hooks';
import type { ReactionOptions } from './reactionOptions';

export type ReactionsListProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = {
/** Additional props to be passed to the [NimbleEmoji](https://github.com/missive/emoji-mart/blob/master/src/components/emoji/nimble-emoji.js) component from `emoji-mart` */
additionalEmojiProps?: Partial<NimbleEmojiProps>;
/** Custom on click handler for an individual reaction, defaults to `onReactionListClick` from the `MessageContext` */
onClick?: ReactEventHandler;
/** An array of the own reaction objects to distinguish own reactions visually */
own_reactions?: ReactionResponse<StreamChatGenerics>[];
/** An object that keeps track of the count of each type of reaction on a message */
reaction_counts?: { [key: string]: number };
reaction_counts?: Record<string, number>;
/** A list of the currently supported reactions on a message */
reactionOptions?: ReactionEmoji[];
reactionOptions?: ReactionOptions;
/** An array of the reaction objects to display in the list */
reactions?: ReactionResponse<StreamChatGenerics>[];
/** Display the reactions in the list in reverse order, defaults to false */
Expand Down Expand Up @@ -77,21 +71,18 @@ const UnMemoizedReactionsList = <
) => {
const { onClick, reverse = false, ...rest } = props;

const { Emoji, emojiConfig } = useEmojiContext('ReactionsList');
const { onReactionListClick } = useMessageContext<StreamChatGenerics>('ReactionsList');

const {
additionalEmojiProps,
aggregatedUserNamesByType,
emojiData,
getEmojiByReactionType,
iHaveReactedWithReaction,
latestReactions,
latestReactionTypes,
reactionCounts,
supportedReactionsArePresent,
totalReactionCount,
} = useProcessReactions({ emojiConfig, ...rest });
} = useProcessReactions(rest);

if (!latestReactions.length) return null;

Expand All @@ -110,42 +101,37 @@ const UnMemoizedReactionsList = <
>
<ul className='str-chat__message-reactions'>
{latestReactionTypes.map((reactionType) => {
const emojiObject = getEmojiByReactionType(reactionType);
const [, Reaction] = getEmojiByReactionType(reactionType) ?? [];
const isOwnReaction = iHaveReactedWithReaction(reactionType);
return emojiObject ? (
<li
className={clsx('str-chat__message-reaction', {
'str-chat__message-reaction-own': isOwnReaction,
})}
key={emojiObject.id}
>
<ButtonWithTooltip
aria-label={`Reactions: ${reactionType}`}
title={aggregatedUserNamesByType[reactionType].join(', ')}
type='button'
return (
Reaction && (
<li
className={clsx('str-chat__message-reaction', {
'str-chat__message-reaction-own': isOwnReaction,
})}
key={reactionType}
>
{
<Suspense fallback={null}>
<ButtonWithTooltip
aria-label={`Reactions: ${reactionType}`}
title={aggregatedUserNamesByType[reactionType].join(', ')}
type='button'
>
{
<span className='str-chat__message-reaction-emoji'>
<Emoji
data={emojiData}
emoji={emojiObject}
size={16}
{...additionalEmojiProps}
/>
<Reaction.Component />
</span>
</Suspense>
}
&nbsp;
<span
className='str-chat__message-reaction-count'
data-testclass='reaction-list-reaction-count'
>
{reactionCounts[reactionType]}
</span>
</ButtonWithTooltip>
</li>
) : null;
}
&nbsp;
<span
className='str-chat__message-reaction-count'
data-testclass='reaction-list-reaction-count'
>
{reactionCounts[reactionType]}
</span>
</ButtonWithTooltip>
</li>
)
);
})}
<li>
<span className='str-chat__reaction-list--counter'>{totalReactionCount}</span>
Expand Down
Loading

0 comments on commit a1a55b4

Please sign in to comment.