Skip to content
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
25 changes: 12 additions & 13 deletions ai-docs/ai-migration-v14-v15.md
Original file line number Diff line number Diff line change
Expand Up @@ -1690,7 +1690,12 @@ Where you pass the props depends on who renders the header:

Both headers are exported.

To keep the v14 behavior of showing the content only next to an open channel or thread (a sidebar toggle, for example), make the content return `null` otherwise: read `openChannels` / `openThreads` from `useWorkspaceNavigation()`. `ChatView` (or your own `WorkspaceNavigationProvider`, [section 4](#4-selecting-channels-and-opening-threads-workspace-navigation)) fills these lists; without one they stay empty, so gate on your own selection state instead.
To keep showing the content only while something is open beside the list (a sidebar toggle, for example), make it return `null` otherwise. Read the workspace from `useWorkspaceNavigation()`:

- `openChannels` / `openThreads` list the channels and threads that the workspace's panels were opened with. Something opened over another item in the same panel (stacked) is not in them. In a workspace whose panels open with channels and threads only, a stacked item always sits over one that is in the lists, so "something is open" is either list being non-empty; `openThreads` alone misses a thread stacked over a channel.
- A panel opened with another kind, such as a `userProfile` panel, is in neither list, even with a channel stacked over it. If your layout opens such panels, check the layout instead: `useLayoutViewState()` from `stream-chat-react/slot-layout` returns each slot's binding (`slotBindings`) and stacked layers (`slotLayers`).
- To ask whether a given channel or thread is on screen, use `isChannelActive(cid)` / `isThreadActive(threadId)`, which follow what each panel shows.
- `ChatView` (or your own `WorkspaceNavigationProvider`, [section 4](#4-selecting-channels-and-opening-threads-workspace-navigation)) fills all of these. Without one the lists stay empty, so gate on your own selection state instead.
Comment thread
MartinCupela marked this conversation as resolved.
Resolved

```tsx
// v14
Expand All @@ -1708,24 +1713,18 @@ import {
// `SidebarToggle` stands for a control of your own.
const SidebarToggle = () => <button>Menu</button>;

// Shown only while a channel is open, as in v14.
const SidebarToggleWhileChannelOpen = () => {
const { openChannels } = useWorkspaceNavigation();
return openChannels.length > 0 ? <SidebarToggle /> : null;
};

// Shown only while a thread is open, as in v14.
const SidebarToggleWhileThreadOpen = () => {
const { openThreads } = useWorkspaceNavigation();
return openThreads.length > 0 ? <SidebarToggle /> : null;
// Shown only while a channel or thread is open beside the list.
const SidebarToggleWhileContentOpen = () => {
const { openChannels, openThreads } = useWorkspaceNavigation();
return openChannels.length > 0 || openThreads.length > 0 ? <SidebarToggle /> : null;
};

const ChannelListHeaderWithToggle = () => (
<ChannelListHeader EndContent={SidebarToggleWhileChannelOpen} />
<ChannelListHeader EndContent={SidebarToggleWhileContentOpen} />
);

const ThreadListHeaderWithToggle = () => (
<ThreadListHeader EndContent={SidebarToggleWhileThreadOpen} />
<ThreadListHeader EndContent={SidebarToggleWhileContentOpen} />
);

// Declared once, so WithComponents gets the same object on every render.
Expand Down
4 changes: 2 additions & 2 deletions ai-docs/v15-breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2315,12 +2315,12 @@ const CustomThreadHeader = () => {
- `ChannelListHeader` and `ThreadListHeader` take `StartContent` (before the title) and `EndContent` (after the title), as `ChannelHeader` / `ThreadHeader` do. Neither has a default. Types: `ChannelListHeaderProps`, `ThreadListHeaderProps`.
- where the SDK renders the header, the props go through the header's override key: register a component that renders the default header with them. `ChannelListHeader` is read by `ChannelNavigation`; `ThreadListHeader` is a new key, read by `ThreadList`. `ThreadListHeader` is now exported.
- where the app renders the header itself (for example `ChannelListHeader` above a standalone `ChannelList`), it passes the props directly; the override key does not reach that header.
- to keep the v14 gate, read `useWorkspaceNavigation().openChannels` / `.openThreads` in the content component and return `null` while it is empty. `ChatView` or an app's own `WorkspaceNavigationProvider` fills these lists; without one they stay empty, so an app without either gates on its own selection state.
- to show the content only while something is open beside the list, read `useWorkspaceNavigation()` in the content component and return `null` while both `openChannels` and `openThreads` are empty. The lists hold the channels and threads the workspace's panels were opened with: an item stacked over another one in a panel is not in them. Where panels open with channels and threads only, a stacked item always sits over one that is, so checking one list alone misses it (a thread stacked over a channel is not in `openThreads`). A panel opened with another kind (a `userProfile` panel, even with a channel stacked over it) is in neither list; such layouts check `useLayoutViewState()` (`slotBindings`, `slotLayers`) from `stream-chat-react/slot-layout` instead. Whether a given channel or thread is on screen is `isChannelActive(cid)` / `isThreadActive(threadId)`. `ChatView` or an app's own `WorkspaceNavigationProvider` fills these; without one the lists stay empty, so an app without either gates on its own selection state.
- Evidence:
- `release-v14:src/components/ChannelList/ChannelListHeader.tsx:15` `{channel && HeaderEndContent && <HeaderEndContent />}`; `release-v14:src/components/Threads/ThreadList/ThreadListHeader.tsx:12` `{activeThread && HeaderEndContent && <HeaderEndContent />}`; `release-v14:src/context/ComponentContext.tsx:303`
- `master:src/components/ChannelList/ChannelListHeader.tsx` and `master:src/components/Threads/ThreadList/ThreadListHeader.tsx` (`StartContent` / `EndContent` props); `master:src/components/Threads/ThreadList/ThreadList.tsx` reads `ThreadListHeader` from `useComponentContext()`; `master:src/context/ComponentContext.tsx` `ThreadListHeader?`
- ticket: REACT-1449
- examples: `examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx` passes `EndContent={SidebarToggleWhileChannelOpen}` to `ChannelListHeader`; `examples/vite/src/App.tsx` registers `ThreadListHeaderWithSidebarToggle` (`examples/vite/src/Sidebar/HeaderContent.tsx`) as `ThreadListHeader`
- examples: `examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx` passes `EndContent={SidebarToggleWhileContentOpen}` to `ChannelListHeader`; `examples/vite/src/App.tsx` registers `ThreadListHeaderWithSidebarToggle` (`examples/vite/src/Sidebar/HeaderContent.tsx`) as `ThreadListHeader`
- Docs impact:
- `03-ui/03-components/01-channel-list-ui.md`, `03-ui/03-components/08-thread-list-ui.md`, `03-ui/03-components/12-overriding-components.md`, `06-component-reference/02-channel/04-componentcontext.md`, `06-component-reference/07-thread-list/01-threadlist.md`, `05-ui-cookbooks/10-layout/04-collapsible-sidebar.md`, `05-ui-cookbooks/03-search/03-mentions-inbox.md`, `08-release-guides/01-upgrade-to-v15.md`
- Example needed: yes (the guide section has one)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,18 @@ export const layouts: LayoutDescriptor[] = [
{ id: 'channels', slots: ['channel', 'thread'] },
];

// The toggle, only while a channel is open beside the list. With nothing open, collapsing the
// sidebar would leave an empty screen.
const SidebarToggleWhileChannelOpen = () => {
const { openChannels } = useWorkspaceNavigation();
return openChannels.length > 0 ? <SidebarToggle /> : null;
// The toggle, only while a channel or thread is open beside the list. With nothing open,
// collapsing the sidebar would leave an empty screen. `openChannels` / `openThreads` hold the
// channels and threads the panels were opened with, so checking both covers a thread stacked over
// a channel.
const SidebarToggleWhileContentOpen = () => {
const { openChannels, openThreads } = useWorkspaceNavigation();
return openChannels.length > 0 || openThreads.length > 0 ? <SidebarToggle /> : null;
};

// The default channel list header, with the toggle at its end.
const ChannelListHeaderWithToggle = () => (
<ChannelListHeader EndContent={SidebarToggleWhileChannelOpen} />
<ChannelListHeader EndContent={SidebarToggleWhileContentOpen} />
);

// Declared once, so WithComponents gets the same object on every render.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
useStateStore,
} from 'stream-chat-react';

import { SidebarToggleWhileChannelOpen } from '../Sidebar/HeaderContent.tsx';
import { SidebarToggleWhileContentOpen } from '../Sidebar/HeaderContent.tsx';
import { ScrollToActiveChannelButton } from './ScrollToActiveChannelButton';

// Human-readable labels for each channel list, keyed by the paginator id set up in App.tsx.
Expand Down Expand Up @@ -222,7 +222,7 @@ export const SwitchableChannelNavigation = () => {

return (
<div className='str-chat__channel-list'>
<ChannelListHeader EndContent={SidebarToggleWhileChannelOpen} />
<ChannelListHeader EndContent={SidebarToggleWhileContentOpen} />
<Search />
{!isActive && activePaginator && (
// Expose the active paginator through ChannelListContext (as the SDK's ChannelLists
Expand Down
22 changes: 9 additions & 13 deletions examples/vite/src/Sidebar/HeaderContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,18 @@ export const HeaderStartWithSidebarToggle = () => (
</>
);

/** The sidebar toggle while a channel is open beside the channel list. With nothing open,
* collapsing the sidebar would leave an empty screen. */
export const SidebarToggleWhileChannelOpen = () => {
const { openChannels } = useWorkspaceNavigation();
return openChannels.length > 0 ? <SidebarToggle /> : null;
};

/** The sidebar toggle while a thread is open beside the thread list. With nothing open,
* collapsing the sidebar would leave an empty screen. */
export const SidebarToggleWhileThreadOpen = () => {
const { openThreads } = useWorkspaceNavigation();
return openThreads.length > 0 ? <SidebarToggle /> : null;
/** The sidebar toggle while a channel or thread is open beside the list. With nothing open,
* collapsing the sidebar would leave an empty screen. `openChannels` / `openThreads` hold the
* channels and threads the panels were opened with, so checking both covers a thread stacked over
* a channel. A panel opened with a user profile is in neither list, and the toggle stays hidden
* next to it. */
export const SidebarToggleWhileContentOpen = () => {
const { openChannels, openThreads } = useWorkspaceNavigation();
return openChannels.length > 0 || openThreads.length > 0 ? <SidebarToggle /> : null;
};

/** The thread list header, registered as the `ThreadListHeader` override, with the sidebar toggle
* at its end. */
export const ThreadListHeaderWithSidebarToggle = () => (
<ThreadListHeader EndContent={SidebarToggleWhileThreadOpen} />
<ThreadListHeader EndContent={SidebarToggleWhileContentOpen} />
);
Loading