Skip to content

Commit

Permalink
docs: message actions & mentions cookbook adjustments (#2348)
Browse files Browse the repository at this point in the history
Cherry-picked commits from #2334.
  • Loading branch information
arnautov-anton authored Mar 29, 2024
1 parent 137c5f5 commit 011edb2
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 240 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,8 @@ client/dist
/.virtualgo
.vscode/
coverage.out

# stream-chat-css/docusaurus files
docusaurus/docs/React/theming
docusaurus/docs/React/assets/stream-chat-css*
shared
Binary file removed docusaurus/docs/React/assets/CustomMessageAction.png
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 29 additions & 45 deletions docusaurus/docs/React/components/message-components/render-text.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id: render-text
title: renderText function
---

The `renderText` function is a core piece of functionality, which handles how the text of our message is going to be formatted/look like. The default [`renderText`](#render-text) function parses a markdown string and outputs a `ReactElement`. Under the hood, the output is generated by the `ReactMarkdown` component from [react-markdown library](https://github.com/remarkjs/react-markdown). The component transforms the markdown to `ReactElement` by using [`remark` parser](https://github.com/remarkjs/remark/tree/main) and [`remark`](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) and [`rehype`](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) plugins.
The `renderText` function is a core piece of functionality, which handles how the text of our message is going to be formatted/look like. The default `renderText` function parses a markdown string and outputs a `ReactElement`. Under the hood, the output is generated by the `ReactMarkdown` component from [react-markdown library](https://github.com/remarkjs/react-markdown). The component transforms the markdown to `ReactElement` by using [`remark` parser](https://github.com/remarkjs/remark/tree/main) and [`remark`](https://github.com/remarkjs/remark/blob/main/doc/plugins.md) and [`rehype`](https://github.com/rehypejs/rehype/blob/main/doc/plugins.md) plugins.

The default `remark` plugins used by SDK are:

Expand All @@ -14,53 +14,41 @@ The default `rehype` plugins (both specific to this SDK) are:
1. plugin to render user mentions
2. plugin to render emojis

### Overriding defaults
## Overriding Defaults

#### Custom `renderText` function
### Custom `renderText` Function

If you don't want your chat implementation to support markdown syntax by default you can override the default behaviour by creating a custom `renderText` function which returns a React node and passing it down to the `MessageList` or `MessageSimple` component via `renderText` property.
If you don't want your chat implementation to support markdown syntax by default you can override the default behaviour by creating a custom `renderText` function which returns a React node and passing it down to the [`MessageList`](../core-components/message-list.mdx) or [`MessageSimple`](../message-components/message-ui.mdx) component via `renderText` property.

For this particular example we'll create a very primitive one which takes the message text passed down to it as a first argument and returns it wrapped in `span` element:

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

const customRenderText = (text) => {
return <span>{text}</span>;
};

const App = () => (
<Chat client={client}>
<Channel>
<Window>
<MessageList renderText={customRenderText} />
</Window>
</Channel>
</Chat>
);
export const WrappedMessageList = () => <MessageList renderText={customRenderText} />;
```

Here's also an example with `VirtualizedMessageList` which currently does not accept `renderText` directly:
Here's also an example with [`VirtualizedMessageList`](../core-components/virtualized-list.mdx) which currently does not accept `renderText` directly:

```tsx
import { MessageSimple } from 'stream-chat-react';
import { VirtualizedMessageList, MessageSimple } from 'stream-chat-react';

const customRenderText = (text) => {
return <span>{text}</span>;
};

const CustomMessage = (props) => <MessageSimple {...props} renderText={customRenderText} />;

const App = () => (
<Chat client={client}>
<Channel>
<Window>
<VirtualizedMessageList Message={CustomMessage} />
</Window>
</Channel>
</Chat>
export const WrappedVirtualizedMessageList = () => (
<VirtualizedMessageList Message={CustomMessage} />
);
```

#### Custom element rendering
### Custom Element Rendering

If you feel like the default output is sufficient, but you'd like to adjust how certain [ReactMarkdown components](https://github.com/remarkjs/react-markdown#appendix-b-components) look like (like `strong` element generated by typing \*\*strong\*\*) you can do so by passing down options to a third argument of the default `renderText` function:

Expand All @@ -71,37 +59,33 @@ Types `mention` and `emoji` are special case component types generated by our SD
```tsx
import { renderText } from 'stream-chat-react';

const StrongComponent = ({ children }) => <b className='custom-strong-class-name'>{children}</b>;
const CustomStrongComponent = ({ children }) => (
<b className='custom-strong-class-name'>{children}</b>
);

const MentionComponent = ({ children, node: { mentionedUser } }) => (
const CustomMentionComponent = ({ children, node: { mentionedUser } }) => (
<a data-user-id={mentionedUser.id} href={`/user-profile/${mentionedUser.id}`}>
{children}
</a>
);

const App = () => (
<Chat client={client}>
<Channel>
<Window>
<MessageList
renderText={(text, mentionedUsers) =>
renderText(text, mentionedUsers, {
customMarkDownRenderers: { strong: StrongComponent, mention: MentionComponent },
})
}
/>
</Window>
</Channel>
</Chat>
export const WrappedMessageList = () => (
<MessageList
renderText={(text, mentionedUsers) =>
renderText(text, mentionedUsers, {
customMarkDownRenderers: { strong: CustomStrongComponent, mention: CustomMentionComponent },
})
}
/>
);
```

#### Custom remark and rehype plugins
### Custom Remark and Rehype Plugins

If you would like to extend the array of plugins used to parse the markdown, you can provide your own lists of remark resp. rehype plugins. The logic that determines what plugins are used and in which order can be specified in custom `getRehypePlugins` and `getRemarkPlugins` functions. These receive the default array of rehype and remark plugins for further customization. Both custom functions ought to be passed to the third `renderText()` parameter. An example follows:
If you would like to extend the array of plugins used to parse the markdown, you can provide your own lists of remark resp. rehype plugins. The logic that determines what plugins are used and in which order can be specified in custom `getRehypePlugins` and `getRemarkPlugins` functions. These receive the default array of rehype and remark plugins for further customization. Both custom functions ought to be passed to the third `renderText` parameter. An example follows:

:::note
It is important to understand what constitutes a rehype or remark plugin. A good start is to learn about the library called [`react-remark`](https://github.com/remarkjs/react-remark) which is used under the hood in our `renderText()` function.
It is important to understand what constitutes a rehype or remark plugin. A good start is to learn about the library called [`react-remark`](https://github.com/remarkjs/react-remark) which is used under the hood in our `renderText` function.
:::

```tsx
Expand All @@ -122,7 +106,7 @@ const customRenderText = (text, mentionedUsers) =>
getRemarkPlugins,
});

const CustomMessageList = () => <MessageList renderText={customRenderText} />;
const WrappedMessageList = () => <MessageList renderText={customRenderText} />;
```

It is also possible to define your custom set of allowed tag names for the elements rendered from the parsed markdown. To perform the tree transformations, you will need to use libraries like [`unist-builder`](https://github.com/syntax-tree/unist-builder) to build the trees and [`unist-util-visit`](https://github.com/syntax-tree/unist-util-visit-parents) or [`hast-util-find-and-replace`](https://github.com/syntax-tree/hast-util-find-and-replace) to traverse the tree:
Expand Down Expand Up @@ -151,5 +135,5 @@ const customRenderText = (text, mentionedUsers) =>
getRehypePlugins,
});

const CustomMessageList = () => <MessageList renderText={customRenderText} />;
const WrappedMessageList = () => <MessageList renderText={customRenderText} />;
```
202 changes: 28 additions & 174 deletions docusaurus/docs/React/guides/theming/actions/mentions-hover-click.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,198 +3,52 @@ id: mentions_actions
title: Mentions Actions
---

import MentionClick from '../../../assets/MentionClick.png';
import MentionHover from '../../../assets/MentionHover.png';
In this example, we will demonstrate how to utilize custom functions that run on hover and click events of a mentioned user in a message. We pass `onMentionsHover` and `onMentionsClick` functions as props to either the [`Channel`](../../../components/core-components/channel.mdx), [`MessageList`](../../../components/core-components/message-list.mdx) or [`VirtualizedMessagelist`](../../../components/core-components/virtualized-list.mdx) components to achieve the desired result.

In this example, we will demonstrate how to utilize custom functions that run on hover and click of a mentioned user in a message.
We pass `onMentionsHover` and `onMentionsClick` functions as props to the `Channel` component to achieve the desired result.

## Mention Hover Action

For a simple on hover example, we will randomly change the color of the mentioned user text. Our custom function receives the
`event` object as a parameter. Through DOM manipulation, we can use the `target` field on the `event` to change the color.

### The Code

```tsx
const onMentionsHover = (event: React.BaseSyntheticEvent) => {
const randomColor = Math.floor(Math.random() * 16777215).toString(16);
event.target.style.color = `#${randomColor}`;
};
```

From here, all we need to do is override the default component in `Channel`:

```tsx
<Channel onMentionsHover={onMentionsHover}>{/* children of Channel component */}</Channel>
```

### The Result

<img src={MentionHover} alt='Mention Hover' width='700' />
Both of the event handler functions receive apropriate event object as their first argument and an array of users mentioned within targeted message as their second argument. To target specific user we will need to acess [`event.target.dataset`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) and look for `userId`.

## Mention Click Action

For this example, we will go more in-depth to show how an on click function can be used to navigate to a user details component.

### The Code

To start, we will create a state variable to hold our clicked user object. Our custom mention click function will set this user in state.
In this example, we'll show how to properly target clicked user and display their name through `window.alert` dialog. Click handler can be helpful for when you need to navigate to user's profile.

```tsx
const [clickedUser, setClickedUser] = useState<UserResponse | undefined>();
import { Channel } from 'stream-chat-react';
import type { CustomMentionHandler } from 'stream-chat-react';

const onMentionsClick = (event: React.BaseSyntheticEvent, user: UserResponse) => {
event.stopPropagation();
setClickedUser(user);
};
```
const handleMentionsClick: CustomMentionHandler = (event, mentionedUsers) => {
const userId = event.target.dataset.userId;
if (!userId) return;

We can now create a custom component that takes `clickedUser` and `setClickedUser` as props and renders the user's info in a sidebar. The user object passed to
our function will allow us to show things such as the user's Avatar and online status.
const user = mentionedUsers.find((user) => user.id === userId);

```tsx
type ClickedUserProps = {
clickedUser: UserResponse;
setClickedUser: React.Dispatch<React.SetStateAction<UserResponse | undefined>>;
window.alert(`Mentioned user: ${user.name || user.id}`);
};

export const ClickedUser: React.FC<ClickedUserProps> = (props) => {
const { clickedUser, setClickedUser } = props;

return (
<div className='wrapper'>
<div className='header'>
{clickedUser.online ? (
<div className='online'>Online</div>
) : (
<div className='offline'>Offline</div>
)}
<button className='str-chat__square-button' onClick={() => setClickedUser(undefined)}>
<svg height='10' width='10' xmlns='http://www.w3.org/2000/svg'>
<path
d='M9.916 1.027L8.973.084 5 4.058 1.027.084l-.943.943L4.058 5 .084 8.973l.943.943L5 5.942l3.973 3.974.943-.943L5.942 5z'
fillRule='evenodd'
/>
</svg>
</button>
</div>
<div className='inner'>
{clickedUser.image && <Avatar image={clickedUser.image} size={240} />}
<div className='name'>Name: {<p>{clickedUser.name}</p>}</div>
<div className='id'>ID: {<p>{clickedUser.id}</p>}</div>
<div className='role'>Role: {<p>{clickedUser.role}</p>}</div>
</div>
</div>
);
export const WrappedChannel = ({ children }) => {
return <Channel onMentionsClick={handleMentionsClick}>{children}</Channel>;
};
```

```css
.wrapper {
width: fit-content;
margin-top: var(--sm-m);
margin-right: var(--xs-m);
background: var(--white);
border-radius: var(--border-radius-md) var(--border-radius-md) 0 0;
}

.header {
height: 70px;
display: flex;
padding: 0 var(--sm-p);
justify-content: space-between;
align-items: center;
box-shadow: 0 7px 9px 0 var(--border), 0 1px 0 0 var(--border);
}

.inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-weight: 700;
margin-top: var(--lg-m);
padding: var(--sm-p);
}

.inner .str-chat__avatar {
margin: 0;
}

.online {
font-weight: var(--font-weight-bold);
color: var(--green);
}

.offline {
font-weight: var(--font-weight-bold);
color: var(--grey);
}

.name {
display: flex;
align-items: center;
font-size: var(--xl-font);
}

.name p {
color: var(--grey);
font-size: var(--xl-font);
margin: 0;
margin: var(--xs-m);
}

.id {
display: flex;
align-items: center;
}

.id p {
color: var(--grey);
margin: 0;
margin: var(--xs-m);
}

.role {
display: flex;
align-items: center;
}

.role p {
color: var(--grey);
margin: 0;
margin: var(--xs-m);
}
```
## Mention Hover Action

Lastly, we plug our custom function and component into our app to tie it all together:
For a simple hover example, we will randomly change the color of the mentioned user text. Through DOM manipulation, we can use the `target` field on the `event` to change the color.

```tsx
const App = () => {
const [clickedUser, setClickedUser] = useState<UserResponse | undefined>();

const onMentionsClick = (event: React.BaseSyntheticEvent, user: UserResponse) => {
setClickedUser(user);
};

return (
<Chat client={client}>
<ChannelList />
<Channel onMentionsClick={onMentionsClick}>
<Window>
<ChannelHeader />
<MessageList />
<MessageInput />
</Window>
<Thread />
{clickedUser && <ClickedUser clickedUser={clickedUser} setClickedUser={setClickedUser} />}
</Channel>
</Chat>
);
import { Channel } from 'stream-chat-react';
import type { CustomMentionHandler } from 'stream-chat-react';

const handleMentionsHover: CustomMentionHandler = (event) => {
if (!event.target.dataset.userId) return;

const randomColor = Math.floor(Math.random() * 16777215).toString(16);
event.target.style.color = `#${randomColor}`;
};

export const WrappedChannel = ({ children }) => {
return <Channel onMentionsHover={handleMentionsHover}>{children}</Channel>;
};
```

### The Result
## Custom Mention Component

<img src={MentionClick} alt='Mention Click' width='500' />
If you wish to access certain contexts (like [`MessageContext`](../../../components/contexts/message-context.mdx)) and have more control over what is being rendered and what other events you'd want to attach to specific mention elements then you'd use a custom [`Mention`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/renderText/componentRenderers/Mention.tsx) component, see [_Custom Element Rendering_](../../../components/message-components/render-text.mdx#custom-element-rendering) for more information.
Loading

0 comments on commit 011edb2

Please sign in to comment.