Skip to content

Commit

Permalink
feat: add ThreadList and ThreadProvider (Threads 2.0) (#2407)
Browse files Browse the repository at this point in the history
πŸš‚ GetStream/stream-chat-js#1330

This PR adds components for implementing a thread list view: ThreadList
and ChatView. It also adds support for reactive state stores, like the
ones that handle the thread list logic in the client.

Implements a binding from the reactive state store in the client to
React components.

Implements a thread list. The component itself consists mostly of
binding to the `ThreadManager` instance in the client.

The Thread component was modified to optionally use a reactive `Thread`
instance from the `ThreadContext`. The component checks if
`ThreadContext` is provided, and if so uses the thread instance from
that context. Otherwise, it falls back to the `ChannelContext` as usual.

Currently, we use `ThreadContext` in the thread list view, where it is
provided by the `ChatView.ThreadAdapter` component.

Note that even when `ThreadContext` is used, `ChannelContext` also must
be provided. Most our components, including the ones used in threads,
still expect the channel context to be there.

A set of components implementing a simple in-memory router to switch
between channel view and thread list view. Using it is totally optional.
Most integrations will probably use their own router instead. Still,
nice to have something out-of-the-box.

A new way to provide component overrides. Previously we allowed passing
component overrides as props to `Channel` component, and then in some
nested components as well, although not consistently.

`WithComponents` is a new recommended way to provide component
overrides, which requires far less prop drilling and nests nicely.

BREAKING CHANGE: ComponentContext no longer provides any defaults

---------

Co-authored-by: Matvei Andrienko <[email protected]>
  • Loading branch information
arnautov-anton and myandrienko committed Sep 4, 2024
1 parent a39ceda commit 941707d
Show file tree
Hide file tree
Showing 61 changed files with 3,161 additions and 279 deletions.
29 changes: 29 additions & 0 deletions docusaurus/docs/React/components/contexts/component-context.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,35 @@ Pull values from context with our custom hook:
const { Attachment, Avatar, Message } = useComponentContext();
```

## WithComponents

A component override functionality which utilises `ComponentContext` under the hood. This is direct replacement for a prop-based component overrides which are now slowly being deprecated.

### Basic Usage of WithComponents

In this case, top-level [`MessageInput`](../message-input-components/message-input.mdx) component reaches for the closest overrides and applies `MessageInputUi1`, the [`Thread`](../core-components/thread.mdx) component uses [`MessageInput`](../message-input-components/message-input.mdx) internally and its UI can be also overriden - in this case, the closest one provides override with component `MessageInputUi2`. If we were to remove this `WithComponents` wrapper over [`Thread`](../core-components/thread.mdx) component, the closest override for [`Thread`](../core-components/thread.mdx)'s [`MessageInput`](../message-input-components/message-input.mdx) component would be `MessageInputUi1`.

```tsx
const MessageInputUi1 = () => {
/*...*/
};
const MessageInputUi2 = () => {
/*...*/
};

<Channel>
<WithComponents overrides={{ Input: MessageInputUi1 }}>
<Window>
<MessageList />
<MessageInput focus />
</Window>
<WithComponents overrides={{ Input: MessageInputUi2 }}>
<Thread />
</WithComponents>
</WithComponents>
</Channel>;
```

## Values

### Attachment
Expand Down
22 changes: 22 additions & 0 deletions docusaurus/docs/React/components/contexts/thread-context.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
id: thread-context
title: ThreadContext
---

`ThreadContext` - just like any other React context - is used for dependency injection. What makes it different in this case is `ThreadProvider`.

## ThreadProvider

Is a provider which wraps [`Channel`](../core-components/channel.mdx) component and takes [`Thread` instance]() as a value. The [`Channel`](../core-components/channel.mdx) wrapper acts as a temporary measure to make [`Thread` component](../core-components/thread.mdx) compatible with the new architecture which relies on [`Thread` instance](). The reliance on channel is temporary and will become deprecated in the future.

Thread component newly prioritizes [`Thread` instance]() if rendered under [`ThreadProvider`](../contexts/thread-context.mdx#threadprovider) otherwise falls back to accessing thread from [`Channel` state](../contexts/channel-state-context.mdx).

### Basic Usage

```tsx
import { Thread, ThreadProvider } from 'stream-chat-react';

<ThreadProvider thread={/*...*/}>
<Thread />
</ThreadProvider>;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
id: thread-list-item
title: ThreadListItem
---

An item component rendered within [`ThreadList` component](./thread-list.mdx). The item is divided into two components:

`ThreadListItem` - a component and provider which renders `ThreadListItemUi`
`ThreadListItemUi` - a component which renders the actual UI elements

The goal is that as integrator you can provide a different look to your component while preserving the behavior or you can replace the behavior while keeping the default UI or you can change both if you require so.

## Props

### thread

A thread instance provided by the [`ThreadList`](../core-components/thread-list.mdx).

| Type |
| ------ |
| Thread |
27 changes: 27 additions & 0 deletions docusaurus/docs/React/components/core-components/thread-list.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
id: thread-list
title: ThreadList
---

`ThreadList` is a component which renders individual thread instances ([`Thread`](https://github.com/GetStream/stream-chat-js/blob/master/src/thread.ts)) stored within `ThreadManager`. It handles pagination triggers and virtualization through the help of the [Virtuoso](https://virtuoso.dev) virtualized list component. The rest of the business logic lives within ThreadManager and Thread classes. ThreadManager instance gets activated whenever ThreadList renders - activation is necessary as it tells the SDK that user "sees" this list and can update state accordingly whenever appropriate events arrive.

If used in default form and rendered within `ThreadView` component it also allows to set active thread and handles `Thread` activation (similar to `ThreadManager` activation).

## Basic Usage

```tsx
<Chat client={client}>
{/*...*/}
<ThreadList />
</Chat>
```

## Props

### virtuosoProps

Props to be passed to the underlying [`react-virtuoso` virtualized list dependency](https://virtuoso.dev/virtuoso-api/interfaces/VirtuosoProps).

| Type |
| ------ |
| object |
53 changes: 53 additions & 0 deletions docusaurus/docs/React/components/utility-components/chat-view.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
id: chat-view
title: ChatView
keywords: [example, chat view, channel view, thread view, thread adapter]
---

`ChatView` is component itself and a set of components which allow for a drop-in implementation of different chat views - the channel view and thread view. This drop-in solution allows your users to easily switch between said views without having to implement such mechanism yourself. It consists of:

- `ChatView` - a provider that holds information about the selected view
- `ChatView.Selector` - selector which allows to set the required view
- `ChatView.Channels` - a wrapper that renders its children when `ChatView` value is equal to `channels`
- `ChatView.Threads` - a provider and a wrapper that renders its children when `ChatView` value is equal to `threads`, exposes `ThreadsViewContext` under which `ThreadList` can set an active thread
- `ChatView.ThreadAdapter` - a wrapper which can access an active thread from the `ThreadsViewContext` and forwards it to the [`ThreadProvider`](../contexts/thread-context.mdx)

## Basic Usage

```tsx
import {
Chat,
ChatView,
ChannelList,
Channel,
ThreadList,
Thread,
useCreateChatClient,
} from 'stream-chat-react';

const App = () => {
const chatClient = useCreateChatClient(/*...*/);

if (!chatClient) return null;

return (
<Chat client={chatClient}>
<ChatView>
<ChatView.Selector />
{/* Channel View */}
<ChatView.Channels>
<ChannelList />
<Channel>{/*...*/}</Channel>
</ChatView.Channels>
{/* Thread View */}
<ChatView.Threads>
<ThreadList />
<ChatView.ThreadAdapter>
<Thread />
</ChatView.ThreadAdapter>
</ChatView.Threads>
</ChatView>
</Chat>
);
};
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
id: view-components-and-thread-adapter
title: View Components and Thread Adapter
---

ChatView set of components is a view switching mechanism that can be utilised by integrators to quickly implement switching between thread and channel views.

Available components:

- `ChatView` - wrapper with context holding the information about currently active view (`channels` & `threads`)
- `ChatView.Threads` - view used for thread-focused application structure with `ThreadsViewContext` that _can be_ utilised by `ThreadList` to set active thread
- `ChatView.Channels` - view used for channel-focused application structure
- `ChatView.Selector` - list with buttons with bound actions for switching views
- `ChatView.ThreadAdapter` - utilises `ThreadsViewContext` and wraps `Thread` component in necessary `ThreadProvider`

This set of components is provided as-is and offers very limited customizability as the underlying logic is super simple. Integrators are encouraged to build their own switching system if they require it.

### Usage

```tsx
import {
Chat,
ChatView,
ChannelList,
Channel,
ThreadList,
Thread,
Window,
} from 'stream-chat-react';

// application structure which allows users to switch between views
<Chat client={client}>
<ChatView>
<ChatView.Selector />
{/* channel-focused structure */}
<ChatView.Channels>
<ChannelList filters={filters} options={options} sort={sort} />
<Channel>
<Window>
<ChannelHeader />
<MessageList />
<MessageInput focus />
</Window>
<Thread />
</Channel>
</ChatView.Channels>
{/* thread-focused structure */}
<ChatView.Threads>
<ThreadList />
<ChatView.ThreadAdapter>
<Thread />
</ChatView.ThreadAdapter>
</ChatView.Threads>
</ChatView>
</Chat>;
```

### Custom Thread-focused Structure

To build your custom thread-focused structure you'll need these four baseline components; `Thread`, `ThreadList`, `ThreadProvier` and `WithComponents` for component overrides.

:::note
For presentation purposes our custom `ThreadListItemUi` component is loosely defined within `CustomThreadsView` and thus it isn't stable. To achieve best performance make sure your components
are stable and defined outside other component's scope.
:::

```tsx
import { WithComponents, ThreadListItemUi, ThreadList, ThreadProvider } from 'stream-chat-react';

export const CustomThreadsView = () => {
const [activeThread, setActiveThread] = useState(undefined);

return (
<div className='custom-threads-view'>
<WithComponents
overrides={{
ThreadListItemUi: () => {
const thread = useThreadListItemContext()!;
return (
<ThreadListItemUi
onPointerDown={() => setActiveThread(thread)}
aria-selected={thread === activeThread}
/>
);
},
}}
>
<ThreadList />
</WithComponents>

<ThreadProvider thread={activeThread}>
<Thread />
</ThreadProvider>
</div>
);
};
```
62 changes: 62 additions & 0 deletions docusaurus/docs/React/guides/custom-threads-view.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
id: custom-threads-view
title: Custom Threads View
keywords: [cookbook, threads, view]
---

Our SDK comes with [`ChatView`](../components/utility-components/chat-view.mdx) which allows for an easy integration of different views. In this guide we'll show how to implement custom threads view while utilising core components and hooks.

## Required Components & Hooks

These components and hooks are required for your own implementation to work properly:

- `ThreadList`
- `ThreadListItem` - a provider for `ThreadListItemUi` with thread information, will be used to forward custom click event to the `ThreadListItemUi` button
- `ThreadProvider` - "adapter" for Thread component to work properly with [`Thread` instance](https://github.com/GetStream/stream-chat-js/blob/master/src/thread.ts)
- `Thread` - provides [`MessageList`](../components/core-components/message-list.mdx) with [`MessageInput`](../components/message-input-components/message-input.mdx) adjusted for threads
- `useActiveThread` - takes your selected thread instance and handles its activity state (`Thread.activate()` & `Thread.deactivate()`) based on document focus and visibility

## Building Custom Threads View

```tsx
import {
ThreadList,
ThreadListItem,
ThreadProvider,
Thread,
WithComponents,
useActiveThread,
} from 'stream-chat-react';

export const CustomThreadsView = () => {
const [activeThread, setActiveThread] = useState(undefined);

useActiveThread({ activeThread });

return (
<div className='custom-threads-view'>
<ThreadList
virtuosoProps={{
itemContent: (_, thread) => (
<ThreadListItem
thread={thread}
threadListItemUiProps={{
'aria-selected': thread === activeThread,
onClick: () => {
setActiveThread(thread);
},
}}
/>
),
}}
/>

{activeThread && (
<ThreadProvider thread={activeThread}>
<Thread />
</ThreadProvider>
)}
</div>
);
};
```
Loading

0 comments on commit 941707d

Please sign in to comment.