Skip to content

Commit 398cc6d

Browse files
committed
feat: add group avatar
1 parent 949446b commit 398cc6d

File tree

19 files changed

+727
-149
lines changed

19 files changed

+727
-149
lines changed

docusaurus/docs/React/components/contexts/channel-list-context.mdx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: ChannelListContext
55

66
The context value is provided by `ChannelListContextProvider` which wraps the contents rendered by [`ChannelList`](../core-components/channel-list.mdx). It exposes API that the default and custom components rendered by `ChannelList` can take advantage of. The components that can consume the context are customizable via `ChannelListProps`:
77

8-
- `Avatar` - component used to display channel image
8+
- `ChannelAvatar` - component used to display channel image
99
- `ChannelSearch` - renders channel search input and results
1010
- `EmptyStateIndicator` - rendered when the channels query returns and empty array
1111
- `LoadingErrorIndicator` - rendered when the channels query fails
@@ -24,10 +24,10 @@ import { useChannelListContext } from 'stream-chat-react';
2424
export const CustomComponent = () => {
2525
const { channels, setChannels } = useChannelListContext();
2626
// component logic ...
27-
return(
28-
{/* rendered elements */}
29-
);
30-
}
27+
return {
28+
/* rendered elements */
29+
};
30+
};
3131
```
3232

3333
## Value
@@ -37,7 +37,7 @@ export const CustomComponent = () => {
3737
State representing the array of loaded channels. Channels query is executed by default only within the [`ChannelList` component](../core-components/channel-list.mdx) in the SDK.
3838

3939
| Type |
40-
|-------------|
40+
| ----------- |
4141
| `Channel[]` |
4242

4343
### setChannels
@@ -109,5 +109,5 @@ const Sidebar = () => {
109109
```
110110
111111
| Type |
112-
|---------------------------------------|
112+
| ------------------------------------- |
113113
| `Dispatch<SetStateAction<Channel[]>>` |

docusaurus/docs/React/components/contexts/component-context.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@ The [default `BaseImage` component](../../utility-components/base-image) tries t
131131
| --------- | ----------------------------------------------------------------- |
132132
| component | <GHComponentLink text='BaseImage' path='/Gallery/BaseImage.tsx'/> |
133133

134+
### ChannelAvatar
135+
136+
Custom UI component to display avatar for a channel in ChannelHeader.
137+
138+
| Type | Default |
139+
| --------- | ------------------------------------------------------------------------ |
140+
| component | <GHComponentLink text='ChannelAvatar' path='/Avatar/ChannelAvatar.tsx'/> |
141+
134142
### CooldownTimer
135143

136144
Custom UI component to display the slow mode cooldown timer.

docusaurus/docs/React/components/core-components/channel-list.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,11 @@ list from incrementing the list.
187187

188188
### Avatar
189189

190-
Custom UI component to display the user's avatar.
190+
Custom UI component to display the channel avatar. The default avatar component for `ChannelList` is `ChannelAvatar`.
191191

192-
| Type | Default |
193-
| --------- | ---------------------------------------------------------- |
194-
| component | <GHComponentLink text='Avatar' path='/Avatar/Avatar.tsx'/> |
192+
| Type | Default |
193+
| --------- | ----------------------------------------------------------------- |
194+
| component | <GHComponentLink text='Avatar' path='/Avatar/ChannelAvatar.tsx'/> |
195195

196196
### channelRenderFilterFn
197197

docusaurus/docs/React/components/core-components/channel.mdx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,14 @@ Custom UI component to display a user's avatar.
190190
| --------- | ---------------------------------------------------------- |
191191
| component | <GHComponentLink text='Avatar' path='/Avatar/Avatar.tsx'/> |
192192

193+
### ChannelAvatar
194+
195+
Custom UI component to display avatar for a channel in ChannelHeader.
196+
197+
| Type | Default |
198+
| --------- | ------------------------------------------------------------------------ |
199+
| component | <GHComponentLink text='ChannelAvatar' path='/Avatar/ChannelAvatar.tsx'/> |
200+
193201
### channelQueryOptions
194202

195203
Optional configuration parameters used for the initial channel query. Applied only if the value of `channel.initialized` is false. If the channel instance has already been initialized (channel has been queried), then the channel query will be skipped and channelQueryOptions will not be applied.

docusaurus/docs/React/components/utility-components/avatar.mdx

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,72 @@ id: avatar
33
title: Avatar
44
---
55

6-
The `Avatar` component displays an image, with fallback to the first letter of the optional name prop.
6+
The SDK supports variety of avatar component types. Different approach is taken to display a channel avatar and an avatar of a message author.
77

8-
## Basic Usage
8+
The channel avatar accounts for the fact that the channel may contain more than two members and thus become a group channel. Therefore, it renders a `GroupAvatar` component in case of more than two channel members and `Avatar` in case of two or less channel members.
9+
On the other hand, messages use the avatars for a specific single user and thus using the `Avatar` component exclusively.
910

10-
A typical use case for the `Avatar` component would be to import and use in your custom components that will completely override a header component, preview component, or similar.
11+
The `Avatar` component displays an image, with fallback to the first letter of the optional name prop. The `GroupAvatar` displays up to four `Avatar` components in a 2x2 grid.
12+
13+
## Customizing Avatar component
14+
15+
The SDK's default `Avatar` component is used by the following components:
16+
17+
- `ChannelSearch` results for users
18+
- `Message`
19+
- `QuotesMessage`
20+
- `MesageStatus`
21+
- `QuotedMessagePreview` in message composer
22+
- Suggestion items for user mentions
23+
- `Poll`
24+
- Message `Reactions`
25+
- `ThreadList`
26+
27+
Passing your custom avatar component to `Channel` prop `Avatar` overrides the avatar for all the above components.
1128

1229
Here's an example of using the `Avatar` component within a custom preview component:
1330

1431
```tsx
15-
import { Avatar } from 'stream-chat-react';
16-
17-
const YourCustomChannelPreview = (props) => {
18-
return (
19-
<div>
20-
<Avatar name={props.displayTitle} image={props.displayImage} />
21-
<div> Other channel info needed in the preview </div>
22-
</div>
23-
);
32+
import { Channel } from 'stream-chat-react';
33+
import type { AvatarProps } from 'stream-chat-react';
34+
35+
const Avatar = (props: AvatarProps) => {
36+
return <div>Custom avatar UI</div>;
2437
};
2538

26-
<ChannelList Preview={YourCustomChannelPreview} />;
39+
<Channel Avatar={Avatar} />;
2740
```
2841

29-
## UI Customization
42+
## Customizing Channel Avatar
3043

3144
You can also take advantage of the `Avatar` prop on the `ChannelHeader` and `ChannelList` components to override just that aspect of these components specifically, see the example below.
3245

33-
An example of overriding just the `Avatar` component in the default `ChannelPreviewMessenger` component.
46+
An example of overriding just the `Avatar` component in the default `ChannelPreview` component.
47+
48+
```tsx
49+
import type { ChannelAvatarProps } from 'stream-chat-react';
50+
51+
const CustomChannelAvatar = (props: ChannelAvatarProps) => {
52+
return <div>Custom Channel Avatar</div>;
53+
};
54+
55+
<ChannelList Avatar={CustomChannelAvatar} />;
56+
```
57+
58+
To override channel avatar in `ChannelHeader` we need to provide our component to `Channel` prop `ChannelAvatar`:
3459

3560
```tsx
36-
const CustomAvatar = (props) => {
37-
return <Avatar image={props.image} />;
61+
import { Channel } from 'stream-chat-react';
62+
import type { ChannelAvatarProps } from 'stream-chat-react';
63+
64+
const CustomChannelAvatar = (props: ChannelAvatarProps) => {
65+
return <div>Custom Channel Avatar</div>;
3866
};
3967

40-
<ChannelList Preview={(props) => <ChannelPreviewMessenger {...props} Avatar={CustomAvatar} />} />;
68+
<Channel ChannelAvatar={CustomChannelAvatar} />;
4169
```
4270

43-
## Props
71+
## Avatar Props
4472

4573
### className
4674

@@ -89,3 +117,15 @@ The entire user object for the chat user represented by the Avatar component. Th
89117
| Type |
90118
| ------ |
91119
| Object |
120+
121+
## ChannelAvatar Props
122+
123+
Besides the `Avatar` props listed above, the `ChannelAvatar` component accepts the following props.
124+
125+
### groupChannelDisplayInfo
126+
127+
Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load.
128+
129+
| Type |
130+
| ------------------------------------- |
131+
| `{ image?: string; name?: string }[]` |

src/components/Avatar/Avatar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import type { DefaultStreamChatGenerics } from '../../types/types';
1111
export type AvatarProps<
1212
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
1313
> = {
14-
/** Custom class that will be merged with the default class */
14+
/** Custom root element class that will be merged with the default class */
1515
className?: string;
1616
/** Image URL or default is an image of the first initial of the name if there is one */
1717
image?: string | null;
1818
/** Name of the image, used for title tag fallback */
1919
name?: string;
20-
/** click event handler */
20+
/** click event handler attached to the component root element */
2121
onClick?: (event: React.BaseSyntheticEvent) => void;
22-
/** mouseOver event handler */
22+
/** mouseOver event handler attached to the component root element */
2323
onMouseOver?: (event: React.BaseSyntheticEvent) => void;
2424
/** The entire user object for the chat user displayed in the component */
2525
user?: UserResponse<StreamChatGenerics>;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React from 'react';
2+
import { Avatar, AvatarProps, GroupAvatar, GroupAvatarProps } from './index';
3+
import type { DefaultStreamChatGenerics } from '../../types';
4+
5+
export type ChannelAvatarProps<
6+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
7+
> = Partial<GroupAvatarProps> & AvatarProps<StreamChatGenerics>;
8+
9+
export const ChannelAvatar = <
10+
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
11+
>({
12+
groupChannelDisplayInfo,
13+
image,
14+
name,
15+
user,
16+
...sharedProps
17+
}: ChannelAvatarProps<StreamChatGenerics>) => {
18+
if (groupChannelDisplayInfo) {
19+
return <GroupAvatar groupChannelDisplayInfo={groupChannelDisplayInfo} {...sharedProps} />;
20+
}
21+
return <Avatar image={image} name={name} user={user} {...sharedProps} />;
22+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import clsx from 'clsx';
2+
import React from 'react';
3+
import { Avatar, AvatarProps } from './Avatar';
4+
import { GroupChannelDisplayInfo } from '../ChannelPreview';
5+
6+
export type GroupAvatarProps = Pick<AvatarProps, 'className' | 'onClick' | 'onMouseOver'> & {
7+
/** Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load. */
8+
groupChannelDisplayInfo: GroupChannelDisplayInfo;
9+
};
10+
11+
export const GroupAvatar = ({
12+
className,
13+
groupChannelDisplayInfo,
14+
onClick,
15+
onMouseOver,
16+
}: GroupAvatarProps) => (
17+
<div
18+
className={clsx(
19+
`str-chat__avatar-group`,
20+
{ 'str-chat__avatar-group--three-part': groupChannelDisplayInfo.length === 3 },
21+
className,
22+
)}
23+
data-testid='group-avatar'
24+
onClick={onClick}
25+
onMouseOver={onMouseOver}
26+
role='button'
27+
>
28+
{groupChannelDisplayInfo.slice(0, 4).map(({ image, name }, i) => (
29+
<Avatar
30+
className={clsx({
31+
'str-chat__avatar--single': groupChannelDisplayInfo.length === 3 && i === 0,
32+
})}
33+
image={image}
34+
key={`${name}-${image}-${i}`}
35+
name={name}
36+
/>
37+
))}
38+
</div>
39+
);

src/components/Avatar/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './Avatar';
2+
export * from './ChannelAvatar';
3+
export * from './GroupAvatar';

src/components/Channel/Channel.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ type ChannelPropsForwardedToComponentContext<
112112
| 'AutocompleteSuggestionList'
113113
| 'Avatar'
114114
| 'BaseImage'
115+
| 'ChannelAvatar'
115116
| 'CooldownTimer'
116117
| 'CustomMessageActionsList'
117118
| 'DateSeparator'
@@ -1234,6 +1235,7 @@ const ChannelInner = <
12341235
AutocompleteSuggestionList: props.AutocompleteSuggestionList,
12351236
Avatar: props.Avatar,
12361237
BaseImage: props.BaseImage,
1238+
ChannelAvatar: props.ChannelAvatar,
12371239
CooldownTimer: props.CooldownTimer,
12381240
CustomMessageActionsList: props.CustomMessageActionsList,
12391241
DateSeparator: props.DateSeparator,
@@ -1293,6 +1295,7 @@ const ChannelInner = <
12931295
props.AutocompleteSuggestionList,
12941296
props.Avatar,
12951297
props.BaseImage,
1298+
props.ChannelAvatar,
12961299
props.CooldownTimer,
12971300
props.CustomMessageActionsList,
12981301
props.DateSeparator,

0 commit comments

Comments
 (0)