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 centralized dialog management #2489

Merged
merged 33 commits into from
Sep 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9751e15
feat: add centralized dialog management
MartinCupela Sep 4, 2024
eb6ab99
Merge branch 'release-v11' into feat/dialogs-manager
MartinCupela Sep 5, 2024
143555b
feat: use own anchor root for DialogAnchor
MartinCupela Sep 5, 2024
c3ed317
feat: forward custom DialogsManager id via DialogsManagerProvider
MartinCupela Sep 5, 2024
71c7685
feat: apply dialogs manager to message lists only
MartinCupela Sep 5, 2024
8e48860
fix: do not forward prop mine to MessageActionsBox root div
MartinCupela Sep 5, 2024
223ddea
test: fix MessageActions tests
MartinCupela Sep 5, 2024
d45459b
test: fix tests rendering message actions
MartinCupela Sep 5, 2024
ba89493
feat: assign static id to DialogsManager inside MessageList
MartinCupela Sep 5, 2024
993358f
docs: fix todo comment
MartinCupela Sep 5, 2024
b557e25
feat: handle focus within dialog
MartinCupela Sep 5, 2024
0a14548
feat: prevent rendering dialog contents if not open
MartinCupela Sep 6, 2024
07eb261
refactor: do not register event listeners by MessageActions component
MartinCupela Sep 6, 2024
d9f3709
test: open MessageActionsBox first in MessageActionsBox.test.js
MartinCupela Sep 6, 2024
af6b94b
feat: control ReactionsSelector dialog display
MartinCupela Sep 6, 2024
8d1c98a
fix: close MessageActionsBox on click inside
MartinCupela Sep 9, 2024
9f071e4
test: add DialogManager tests
MartinCupela Sep 9, 2024
9d1ee9d
refactor: unmound popper element if closed in useDialogAnchor
MartinCupela Sep 9, 2024
86b4cb0
Merge branch 'feat/dialogs-manager'
MartinCupela Sep 9, 2024
d1e4df4
refactor: use StateStore to handle DialogsManager subscriptions
MartinCupela Sep 10, 2024
f76cc75
refactor: rename DialogsManager to DialogManager
MartinCupela Sep 11, 2024
cb52d15
refactor: rename DialogsManager.open param single to closeRest
MartinCupela Sep 11, 2024
45c5eb7
refactor: rename DialogsManager.state.dialogs to DialogsManager.state…
MartinCupela Sep 11, 2024
401af81
refactor: move useStateStore to src/store/hooks
MartinCupela Sep 11, 2024
4f120a4
refactor: remove openDialogCount from DialogManager
MartinCupela Sep 11, 2024
a344533
refactor: apply suggestions about declaring state selector
MartinCupela Sep 11, 2024
1447022
Merge branch 'master' into feat/dialog-manager
MartinCupela Sep 11, 2024
a212d27
fix: remove unsupported onClick prop from ReactionListProps
MartinCupela Sep 11, 2024
9932001
docs: remove references to removed props
MartinCupela Sep 11, 2024
7b32ea3
docs: add dialog management guide and migration guide
MartinCupela Sep 11, 2024
f649140
chore(docs): bump stream-chat-css to v5.0.0-rc.6
MartinCupela Sep 13, 2024
7a67c03
refactor: merge Dialog.toggleOpen and Dialog.toggleOpenSingle into Di…
MartinCupela Sep 16, 2024
580b2bf
Merge branch 'master' into feat/dialog-manager
MartinCupela Sep 16, 2024
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
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
Loading