Skip to content

Commit

Permalink
feat: add centralized dialog management (#2489)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:
- removes the following variables from `MessageContext`: isReactionEnabled, onReactionListClick, showDetailedReactions, reactionSelectorRef
- removes prop `messageWrapperRef` from `MessageOptions` and `MessageActions` props.
  • Loading branch information
MartinCupela authored Sep 16, 2024
1 parent 9fa4806 commit 8235d45
Show file tree
Hide file tree
Showing 56 changed files with 1,768 additions and 1,228 deletions.
24 changes: 0 additions & 24 deletions docusaurus/docs/React/components/contexts/message-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -304,14 +304,6 @@ Function that runs on hover of an @mention in a message.
| ----------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void |

### onReactionListClick

Function that runs on click of the reactions list component.

| Type |
| ----------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void |

### onUserClick

Function that runs on click of a user avatar.
Expand All @@ -336,14 +328,6 @@ The user roles allowed to pin messages in various channel types (deprecated in f
| ------ | ------------------------------------------------------------------------- |
| object | <GHComponentLink text='defaultPinPermissions' path='/Message/utils.tsx'/> |

### reactionSelectorRef

Ref to be placed on the reaction selector component.

| Type |
| --------------------------------------- |
| React.MutableRefObject<HTMLDivElement\> |

### readBy

An array of users that have read the current message.
Expand All @@ -368,14 +352,6 @@ Function to toggle the editing state on a message.
| ----------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void |

### showDetailedReactions

When true, show the reactions list component.

| Type |
| ------- |
| boolean |

### reactionDetailsSort

Sort options to provide to a reactions query. Affects the order of reacted users in the default reactions modal.
Expand Down
24 changes: 0 additions & 24 deletions docusaurus/docs/React/components/message-components/message-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -397,14 +397,6 @@ Function that runs on hover of an @mention in a message (overrides the function
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void | [MessageContextValue['onMentionsHoverMessage']](../contexts/channel-action-context.mdx#onmentionshovermessage) |

### onReactionListClick

Function that runs on click of the reactions list component (overrides the function stored in `MessageContext`).

| Type | Default |
| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void | [MessageContextValue['onReactionListClick']](../contexts/channel-action-context.mdx#onreactionlistclick) |

### onUserClick

Function that runs on click of a user avatar (overrides the function stored in `MessageContext`).
Expand All @@ -429,14 +421,6 @@ The user roles allowed to pin messages in various channel types (deprecated in f
| ------ | -------------------------------------------------------------------------------------------------------------------- |
| object | [defaultPinPermissions](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/utils.tsx) |

### reactionSelectorRef

Ref to be placed on the reaction selector component (overrides the ref stored in `MessageContext`).

| Type |
| --------------------------------------- |
| React.MutableRefObject<HTMLDivElement\> |

### readBy

An array of users that have read the current message (overrides the value stored in `MessageContext`).
Expand All @@ -461,14 +445,6 @@ Function to toggle the editing state on a message (overrides the function stored
| ----------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void |

### showDetailedReactions

When true, show the reactions list component (overrides the value stored in `MessageContext`).

| Type |
| ------- |
| boolean |

### threadList

If true, indicates that the current `MessageList` component is part of a `Thread` (overrides the value stored in `MessageContext`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,6 @@ const MyCustomReactionsList = (props) => {
};
```

### onClick

Custom on click handler for an individual reaction in the list (overrides the function coming from `MessageContext`).

| Type | Default |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void | [MessageContextValue['onReactionListClick']](../contexts/message-context.mdx#onreactionlistclick) |

### own_reactions

An array of the own reaction objects to distinguish own reactions visually (overrides `message.own_reactions` from `MessageContext`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,6 @@ The `StreamChat` message object, which provides necessary data to the underlying
| ------ |
| object |

### messageWrapperRef

React mutable ref placed on the message root `div`. It is forwarded by `MessageOptions` down to `MessageActions` ([see the example](../../guides/theming/message-ui.mdx)).

| Type |
| -------------------------------- |
| React.RefObject<HTMLDivElement\> |

### mine

Function that returns whether the message was sent by the connected user.
Expand Down Expand Up @@ -178,14 +170,6 @@ Function that opens a [`Thread`](../core-components/thread.mdx) on a message (ov
| ----------------------------------------------------------- |
| (event: React.BaseSyntheticEvent) => Promise<void\> \| void |

### messageWrapperRef

React mutable ref that can be placed on the message root `div`. `MessageOptions` component forwards this prop to [`MessageActions`](#messageactions-props) component ([see the example](../../guides/theming/message-ui.mdx)).

| Type |
| -------------------------------- |
| React.RefObject<HTMLDivElement\> |

### ReactionIcon

Custom component rendering the icon used in a message options button invoking reactions selector for a given message.
Expand Down
107 changes: 107 additions & 0 deletions docusaurus/docs/React/guides/dialog-management.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
id: dialog-management
title: Dialog Management
---

This article presents the API the integrators can use to toggle display dialogs in their UIs. The default components that are displayed as dialogs are:

- `ReactionSelector` - allows users to post reactions / emojis to a message
- `MessageActionsBox` - allows user to select from a list of permitted message actions

The dialog management following this guide is enabled within `MessageList` and `VirtualizedMessageList`.

## Setup dialog display

There are two actors in the play. The first one is the component that requests the dialog to be closed or open and the other is the component that renders the dialog. We will start with demonstrating how to properly render a component in a dialog.

### Rendering a dialog

Component we want to be rendered as a floating dialog should be wrapped inside `DialogAnchor`:

```tsx
import React, { ElementRef, useRef } from 'react';
import { DialogAnchor } from 'stream-chat-react';

import { ComponentToDisplayOnDialog } from './ComponentToDisplayOnDialog';
import { generateUniqueId } from './generateUniqueId';

const Container = () => {
// DialogAnchor needs a reference to the element that will toggle the open state. Based on this reference the dialog positioning is calculated
const buttonRef = useRef<ElementRef<'button'>>(null);
// providing the dialog is necessary for the dialog to be retrieved from anywhere in the DialogManagerProviderContext
const dialogId = generateUniqueId();

return (
<>
<DialogAnchor id={dialogId} placement='top' referenceElement={buttonRef.current} trapFocus>
<ComponentToDisplayOnDialog />
</DialogAnchor>
</>
);
};
```

### Controlling a dialog's display

The dialog display is controlled via Dialog API. You can access the API via `useDialog()` hook.

```tsx
import React, { ElementRef, useRef } from 'react';
import { DialogAnchor, useDialog, useDialogIsOpen } from 'stream-chat-react';

import { ComponentToDisplayOnDialog } from './ComponentToDisplayOnDialog';
import { generateUniqueId } from './generateUniqueId';

const Container = () => {
const buttonRef = useRef<ElementRef<'button'>>(null);
const dialogId = generateUniqueId();
// access the dialog controller which provides the dialog API
const dialog = useDialog({ id: dialogId });
// subscribe to dialog open state changes
const dialogIsOpen = useDialogIsOpen(dialogId);

return (
<>
<DialogAnchor id={dialogId} placement='top' referenceElement={buttonRef.current} trapFocus>
<ComponentToDisplayOnDialog />
</DialogAnchor>
<button aria-expanded={dialogIsOpen} onClick={dialog.toggleSingle} ref={buttonRef}>
Toggle
</button>
</>
);
};
```

### Dialog API

Dialog can be controlled via `Dialog` object retrieved using `useDialog()` hook. The hook returns an object with the following API:

- `dialog.open()` - opens the dialog
- `dialog.close()` - closes the dialog
- `dialog.toggle()` - toggles the dialog open state. Accepts boolean argument `closeAll`. If enabled closes any other dialog that would be open.
- `dialog.remove()` - removes the dialog object reference from the state (primarily for cleanup purposes)

Every `Dialog` object carries its own `id` and `isOpen` flag.

### Dialog utility hooks

There are the following utility hooks that can be used to subscribe to state changes or access a given dialog:

- `useDialogIsOpen(id: string)` - allows to observe the open state of a particular `Dialog` instance
- `useDialog({ id }: GetOrCreateDialogParams)` - retrieves a dialog object that exposes API to manage it
- `useOpenedDialogCount()` - allows to observe changes in the open dialog count

### Custom dialog management context

Those who would like to render dialogs outside the `MessageList` and `VirtualizedMessageList`, will need to create a dialog management context using `DialogManagerProvider`.

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

const Container = () => {
return <DialogManagerProvider id='custom-dialog-manager-id'></DialogManagerProvider>;
};
```

Now the children of `DialogAnchor` will be anchored to the parent `DialogManagerProvider`.
2 changes: 1 addition & 1 deletion docusaurus/docs/React/guides/theming/message-ui.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ const CustomMessageUi = () => {

Message grouping is being managed automatically by the SDK and each parent element (which holds our message UI) receives an appropriate class name based on which we can adjust our rules to display metadata elements only when it's appropriate to make our UI look less busy.

{/_ TODO: link to grouping logic (maybe how to adjust it if needed) _/}
[//]: # 'TODO: link to grouping logic (maybe how to adjust it if needed)'

```css
.custom-message-ui__metadata {
Expand Down
32 changes: 32 additions & 0 deletions docusaurus/docs/React/release-guides/upgrade-to-v12.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,38 @@ import { encodeToMp3 } from 'stream-chat-react/mp3-encoder';

:::

## Unified dialog management

Dialogs will be managed centrally. At the moment, this applies to display of `ReactionSelector` and `MessageActionsBox`. They will be displayed on a transparent overlay that prevents users from opening other dialogs in the message list. Once an option from a dialog is selected or the overlay is clicked, the dialog will disappear. This adjust brings new API and removes some properties from `MessageContextValue`.

### Removed properties from MessageContextValue

- `isReactionEnabled` - served to signal the permission to send reactions by the current user in a given channel. With the current permissions implementation, the permission can be determined by doing the following:

```
import { useMessageContext } from 'stream-chat-react';
const { getMessageActions } = useMessageContext();
const messageActions = getMessageActions();
const canReact = messageActions.includes(MESSAGE_ACTIONS.react);
```

- `onReactionListClick` - handler function that toggled the open state of `ReactionSelector` represented by another removed value - `showDetailedReactions`
- `showDetailedReactions` - flag used to decide, whether the reaction selector should be shown or not
- `reactionSelectorRef` - ref to the root of the reaction selector component (served to control the display of the component)

Also prop `messageWrapperRef` was removed as part of the change from `MessageOptions` and `MessageActions` props.

On the other hand, the `Message` prop (configuration parameter) `closeReactionSelectorOnClick` is now available in the `MessageContextValue`.

:::important
If you used any of these values in your customizations, please make sure to adjust your implementation according to the newly recommended use of Dialog API in [Dialog management guide](../../guides/dialog-management).
:::

### New dialog management API

To learn about the new API, please, take a look at our [Dialog management guide](../../guides/dialog-management).

## EmojiPickerIcon extraction to emojis plugin

The default `EmojiPickerIcon` has been moved to emojis plugin from which we already import `EmojiPicker` component.
Expand Down
3 changes: 2 additions & 1 deletion docusaurus/sidebars-react.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@
"guides/video-integration/video-integration-stream",
"guides/sdk-state-management",
"guides/date-time-formatting",
"guides/custom-threads-view"
"guides/custom-threads-view",
"guides/dialog-management"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,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.0.0-rc.5",
"@stream-io/stream-chat-css": "5.0.0-rc.6",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
Expand Down
3 changes: 2 additions & 1 deletion src/components/ChatView/ChatView.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';

import { ThreadProvider, useStateStore } from '../Threads';
import { ThreadProvider } from '../Threads';
import { Icon } from '../Threads/icons';
import { UnreadCountBadge } from '../Threads/UnreadCountBadge';
import { useChatContext } from '../../context';
import { useStateStore } from '../../store';

import type { PropsWithChildren } from 'react';
import type { Thread, ThreadManagerState } from 'stream-chat';
Expand Down
Loading

0 comments on commit 8235d45

Please sign in to comment.