Skip to content

Commit 414745d

Browse files
authored
feat: add group avatar (#2556)
1 parent 41b23db commit 414745d

File tree

18 files changed

+716
-151
lines changed

18 files changed

+716
-151
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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/core-components/channel-list.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ 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 `Avatar`.
191191

192192
| Type | Default |
193193
| --------- | ---------------------------------------------------------- |

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

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,64 @@ 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+
Semantically we can speak about two types of avatars in the SDK. One type is the avatar that represents the channel and the other representing another user. The SDK exports the follwing avatar components:
77

8-
## Basic Usage
8+
- `Avatar` - displays single image or name initials in case image is not available
9+
- `GroupAvatar` - displays images or name initials as a fallback in a 2x2 grid
10+
- `ChannelAvatar` - renders `GroupAvatar` in case a channel has more than two members and `Avatar` otherwise
911

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.
12+
By default, all the SDK components use `Avatar` to display channel resp. user avatar. However, it makes sense to override the default in `ChannelList` resp. `ChannelPreview` and `ChannelHeader` as those avatars may represent a group of users .
13+
14+
## Customizing avatar component
15+
16+
Passing your custom avatar component to `Channel` prop `Avatar` overrides the avatar for all the `Channel` component's children.
1117

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

1420
```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-
);
21+
import { Channel } from 'stream-chat-react';
22+
import type { AvatarProps } from 'stream-chat-react';
23+
24+
const Avatar = (props: AvatarProps) => {
25+
return <div>Custom avatar UI</div>;
2426
};
2527

26-
<ChannelList Preview={YourCustomChannelPreview} />;
28+
<Channel Avatar={Avatar} />;
2729
```
2830

29-
## UI Customization
31+
## Customizing channel avatar
3032

3133
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.
3234

33-
An example of overriding just the `Avatar` component in the default `ChannelPreviewMessenger` component.
35+
An example of overriding just the `Avatar` component in the default `ChannelPreview` component.
3436

3537
```tsx
36-
const CustomAvatar = (props) => {
37-
return <Avatar image={props.image} />;
38+
import { ChannelList } from 'stream-chat-react';
39+
import type { ChannelAvatarProps } from 'stream-chat-react';
40+
41+
const CustomChannelAvatar = (props: ChannelAvatarProps) => {
42+
return <div>Custom Channel Avatar</div>;
3843
};
3944

40-
<ChannelList Preview={(props) => <ChannelPreviewMessenger {...props} Avatar={CustomAvatar} />} />;
45+
<ChannelList Avatar={CustomChannelAvatar} />;
4146
```
4247

43-
## Props
48+
To override the channel avatar in `ChannelHeader` we need to provide it prop `Avatar`:
49+
50+
```tsx
51+
import { ChannelHeader } from 'stream-chat-react';
52+
import type { ChannelAvatarProps } from 'stream-chat-react';
53+
54+
const CustomChannelAvatar = (props: ChannelAvatarProps) => {
55+
return <div>Custom Channel Avatar</div>;
56+
};
57+
58+
<ChannelHeader Avatar={CustomChannelAvatar} />;
59+
```
60+
61+
Also, we can take the advantage of existing SDK's `ChannelAvatar` and pass it to both `ChannelHeader` and `ChannelList` as described above.
62+
63+
## Avatar Props
4464

4565
### className
4666

@@ -89,3 +109,15 @@ The entire user object for the chat user represented by the Avatar component. Th
89109
| Type |
90110
| ------ |
91111
| Object |
112+
113+
## ChannelAvatar Props
114+
115+
Besides the `Avatar` props listed above, the `ChannelAvatar` component accepts the following props.
116+
117+
### groupChannelDisplayInfo
118+
119+
Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load.
120+
121+
| Type |
122+
| ------------------------------------- |
123+
| `{ image?: string; name?: string }[]` |

examples/vite/src/App.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
22
import {
33
Channel,
4+
ChannelAvatar,
45
ChannelHeader,
56
ChannelList,
67
Chat,
@@ -75,10 +76,17 @@ const App = () => {
7576
<ChatView>
7677
<ChatView.Selector />
7778
<ChatView.Channels>
78-
<ChannelList filters={filters} options={options} sort={sort} />
79+
<ChannelList
80+
Avatar={ChannelAvatar}
81+
filters={filters}
82+
options={options}
83+
sort={sort}
84+
showChannelSearch
85+
additionalChannelSearchProps={{ searchForChannels: true }}
86+
/>
7987
<Channel>
8088
<Window>
81-
<ChannelHeader />
89+
<ChannelHeader Avatar={ChannelAvatar} />
8290
<MessageList returnAllReadData />
8391
<MessageInput focus />
8492
</Window>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
"@semantic-release/changelog": "^6.0.2",
187187
"@semantic-release/git": "^10.0.1",
188188
"@stream-io/rollup-plugin-node-builtins": "^2.1.5",
189-
"@stream-io/stream-chat-css": "^5.2.0",
189+
"@stream-io/stream-chat-css": "^5.4.0",
190190
"@testing-library/jest-dom": "^6.1.4",
191191
"@testing-library/react": "^13.1.1",
192192
"@testing-library/react-hooks": "^8.0.0",

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/ChannelHeader/ChannelHeader.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

33
import { MenuIcon as DefaultMenuIcon } from './icons';
44

5-
import { AvatarProps, Avatar as DefaultAvatar } from '../Avatar';
5+
import { ChannelAvatarProps, Avatar as DefaultAvatar } from '../Avatar';
66
import { useChannelPreviewInfo } from '../ChannelPreview/hooks/useChannelPreviewInfo';
77

88
import { useChannelStateContext } from '../../context/ChannelStateContext';
@@ -12,8 +12,8 @@ import { useTranslationContext } from '../../context/TranslationContext';
1212
import type { DefaultStreamChatGenerics } from '../../types/types';
1313

1414
export type ChannelHeaderProps = {
15-
/** UI component to display a user's avatar, defaults to and accepts same props as: [Avatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/Avatar.tsx) */
16-
Avatar?: React.ComponentType<AvatarProps>;
15+
/** UI component to display an avatar, defaults to [Avatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/Avatar.tsx) component and accepts the same props as: [ChannelAvatar](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Avatar/ChannelAvatar.tsx) */
16+
Avatar?: React.ComponentType<ChannelAvatarProps>;
1717
/** Manually set the image to render, defaults to the Channel image */
1818
image?: string;
1919
/** Show a little indicator that the Channel is live right now */
@@ -43,7 +43,7 @@ export const ChannelHeader = <
4343
const { channel, watcher_count } = useChannelStateContext<StreamChatGenerics>('ChannelHeader');
4444
const { openMobileNav } = useChatContext<StreamChatGenerics>('ChannelHeader');
4545
const { t } = useTranslationContext('ChannelHeader');
46-
const { displayImage, displayTitle } = useChannelPreviewInfo({
46+
const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({
4747
channel,
4848
overrideImage,
4949
overrideTitle,
@@ -62,6 +62,7 @@ export const ChannelHeader = <
6262
</button>
6363
<Avatar
6464
className='str-chat__avatar--channel-header'
65+
groupChannelDisplayInfo={groupChannelDisplayInfo}
6566
image={displayImage}
6667
name={displayTitle}
6768
/>

0 commit comments

Comments
 (0)