Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add group avatar #2556

Merged
merged 4 commits into from
Nov 14, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import { useChannelListContext } from 'stream-chat-react';
export const CustomComponent = () => {
const { channels, setChannels } = useChannelListContext();
// component logic ...
return(
{/* rendered elements */}
);
}
return {
/* rendered elements */
};
};
```

## Value
Expand All @@ -37,7 +37,7 @@ export const CustomComponent = () => {
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.

| Type |
|-------------|
| ----------- |
| `Channel[]` |

### setChannels
Expand Down Expand Up @@ -109,5 +109,5 @@ const Sidebar = () => {
```

| Type |
|---------------------------------------|
| ------------------------------------- |
| `Dispatch<SetStateAction<Channel[]>>` |
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ list from incrementing the list.

### Avatar

Custom UI component to display the user's avatar.
Custom UI component to display the channel avatar. The default avatar component for `ChannelList` is `Avatar`.

| Type | Default |
| --------- | ---------------------------------------------------------- |
Expand Down
70 changes: 51 additions & 19 deletions docusaurus/docs/React/components/utility-components/avatar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,64 @@ id: avatar
title: Avatar
---

The `Avatar` component displays an image, with fallback to the first letter of the optional name prop.
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:

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

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

## Customizing avatar component

Passing your custom avatar component to `Channel` prop `Avatar` overrides the avatar for all the `Channel` component's children.

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

```tsx
import { Avatar } from 'stream-chat-react';

const YourCustomChannelPreview = (props) => {
return (
<div>
<Avatar name={props.displayTitle} image={props.displayImage} />
<div> Other channel info needed in the preview </div>
</div>
);
import { Channel } from 'stream-chat-react';
import type { AvatarProps } from 'stream-chat-react';

const Avatar = (props: AvatarProps) => {
return <div>Custom avatar UI</div>;
};

<ChannelList Preview={YourCustomChannelPreview} />;
<Channel Avatar={Avatar} />;
```

## UI Customization
## Customizing channel avatar

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.

An example of overriding just the `Avatar` component in the default `ChannelPreviewMessenger` component.
An example of overriding just the `Avatar` component in the default `ChannelPreview` component.

```tsx
const CustomAvatar = (props) => {
return <Avatar image={props.image} />;
import { ChannelList } from 'stream-chat-react';
import type { ChannelAvatarProps } from 'stream-chat-react';

const CustomChannelAvatar = (props: ChannelAvatarProps) => {
return <div>Custom Channel Avatar</div>;
};

<ChannelList Preview={(props) => <ChannelPreviewMessenger {...props} Avatar={CustomAvatar} />} />;
<ChannelList Avatar={CustomChannelAvatar} />;
```

## Props
To override the channel avatar in `ChannelHeader` we need to provide it prop `Avatar`:

```tsx
import { ChannelHeader } from 'stream-chat-react';
import type { ChannelAvatarProps } from 'stream-chat-react';

const CustomChannelAvatar = (props: ChannelAvatarProps) => {
return <div>Custom Channel Avatar</div>;
};

<ChannelHeader Avatar={CustomChannelAvatar} />;
```

Also, we can take the advantage of existing SDK's `ChannelAvatar` and pass it to both `ChannelHeader` and `ChannelList` as described above.

## Avatar Props

### className

Expand Down Expand Up @@ -89,3 +109,15 @@ The entire user object for the chat user represented by the Avatar component. Th
| Type |
| ------ |
| Object |

## ChannelAvatar Props

Besides the `Avatar` props listed above, the `ChannelAvatar` component accepts the following props.

### groupChannelDisplayInfo

Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load.

| Type |
| ------------------------------------- |
| `{ image?: string; name?: string }[]` |
12 changes: 10 additions & 2 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
import {
Channel,
ChannelAvatar,
ChannelHeader,
ChannelList,
Chat,
Expand Down Expand Up @@ -73,10 +74,17 @@ const App = () => {
<ChatView>
<ChatView.Selector />
<ChatView.Channels>
<ChannelList filters={filters} options={options} sort={sort} />
<ChannelList
Avatar={ChannelAvatar}
filters={filters}
options={options}
sort={sort}
showChannelSearch
additionalChannelSearchProps={{ searchForChannels: true }}
/>
<Channel>
<Window>
<ChannelHeader />
<ChannelHeader Avatar={ChannelAvatar} />
<MessageList returnAllReadData />
<MessageInput focus />
</Window>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"@stream-io/rollup-plugin-node-builtins": "^2.1.5",
"@stream-io/stream-chat-css": "^5.2.0",
"@stream-io/stream-chat-css": "^5.4.0",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
Expand Down
6 changes: 3 additions & 3 deletions src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import type { DefaultStreamChatGenerics } from '../../types/types';
export type AvatarProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = {
/** Custom class that will be merged with the default class */
/** Custom root element class that will be merged with the default class */
className?: string;
/** Image URL or default is an image of the first initial of the name if there is one */
image?: string | null;
/** Name of the image, used for title tag fallback */
name?: string;
/** click event handler */
/** click event handler attached to the component root element */
onClick?: (event: React.BaseSyntheticEvent) => void;
/** mouseOver event handler */
/** mouseOver event handler attached to the component root element */
onMouseOver?: (event: React.BaseSyntheticEvent) => void;
/** The entire user object for the chat user displayed in the component */
user?: UserResponse<StreamChatGenerics>;
Expand Down
22 changes: 22 additions & 0 deletions src/components/Avatar/ChannelAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Avatar, AvatarProps, GroupAvatar, GroupAvatarProps } from './index';
import type { DefaultStreamChatGenerics } from '../../types';

export type ChannelAvatarProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = Partial<GroupAvatarProps> & AvatarProps<StreamChatGenerics>;

export const ChannelAvatar = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
>({
groupChannelDisplayInfo,
image,
name,
user,
...sharedProps
}: ChannelAvatarProps<StreamChatGenerics>) => {
if (groupChannelDisplayInfo) {
return <GroupAvatar groupChannelDisplayInfo={groupChannelDisplayInfo} {...sharedProps} />;
}
return <Avatar image={image} name={name} user={user} {...sharedProps} />;

Check warning on line 21 in src/components/Avatar/ChannelAvatar.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/Avatar/ChannelAvatar.tsx#L21

Added line #L21 was not covered by tests
};
39 changes: 39 additions & 0 deletions src/components/Avatar/GroupAvatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import clsx from 'clsx';
import React from 'react';
import { Avatar, AvatarProps } from './Avatar';
import { GroupChannelDisplayInfo } from '../ChannelPreview';

export type GroupAvatarProps = Pick<AvatarProps, 'className' | 'onClick' | 'onMouseOver'> & {
/** Mapping of image URLs to names which initials will be used as fallbacks in case image assets fail to load. */
groupChannelDisplayInfo: GroupChannelDisplayInfo;
};

export const GroupAvatar = ({
className,
groupChannelDisplayInfo,
onClick,
onMouseOver,
}: GroupAvatarProps) => (
<div
className={clsx(
`str-chat__avatar-group`,
{ 'str-chat__avatar-group--three-part': groupChannelDisplayInfo.length === 3 },
className,
)}
data-testid='group-avatar'
onClick={onClick}
onMouseOver={onMouseOver}
role='button'
>
{groupChannelDisplayInfo.slice(0, 4).map(({ image, name }, i) => (
<Avatar
className={clsx({
'str-chat__avatar--single': groupChannelDisplayInfo.length === 3 && i === 0,
})}
image={image}
key={`${name}-${image}-${i}`}
name={name}
/>
))}
</div>
);
2 changes: 2 additions & 0 deletions src/components/Avatar/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './Avatar';
export * from './ChannelAvatar';
export * from './GroupAvatar';
9 changes: 5 additions & 4 deletions src/components/ChannelHeader/ChannelHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';

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

import { AvatarProps, Avatar as DefaultAvatar } from '../Avatar';
import { ChannelAvatarProps, Avatar as DefaultAvatar } from '../Avatar';
import { useChannelPreviewInfo } from '../ChannelPreview/hooks/useChannelPreviewInfo';

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

export type ChannelHeaderProps = {
/** 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) */
Avatar?: React.ComponentType<AvatarProps>;
/** 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) */
Avatar?: React.ComponentType<ChannelAvatarProps>;
/** Manually set the image to render, defaults to the Channel image */
image?: string;
/** Show a little indicator that the Channel is live right now */
Expand Down Expand Up @@ -43,7 +43,7 @@ export const ChannelHeader = <
const { channel, watcher_count } = useChannelStateContext<StreamChatGenerics>('ChannelHeader');
const { openMobileNav } = useChatContext<StreamChatGenerics>('ChannelHeader');
const { t } = useTranslationContext('ChannelHeader');
const { displayImage, displayTitle } = useChannelPreviewInfo({
const { displayImage, displayTitle, groupChannelDisplayInfo } = useChannelPreviewInfo({
channel,
overrideImage,
overrideTitle,
Expand All @@ -62,6 +62,7 @@ export const ChannelHeader = <
</button>
<Avatar
className='str-chat__avatar--channel-header'
groupChannelDisplayInfo={groupChannelDisplayInfo}
image={displayImage}
name={displayTitle}
/>
Expand Down
Loading
Loading