Skip to content

Commit 9596290

Browse files
authored
docs: gate list-header content on both workspace navigation lists (#3414)
1 parent ba41e60 commit 9596290

5 files changed

Lines changed: 33 additions & 36 deletions

File tree

‎ai-docs/ai-migration-v14-v15.md‎

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1692,7 +1692,12 @@ Where you pass the props depends on who renders the header:
16921692

16931693
Both headers are exported.
16941694

1695-
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.
1695+
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()`:
1696+
1697+
- `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.
1698+
- 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`).
1699+
- To ask whether a given channel or thread is on screen, use `isChannelActive(cid)` / `isThreadActive(threadId)`, which follow what each panel shows.
1700+
- `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.
16961701

16971702
```tsx
16981703
// v14
@@ -1710,24 +1715,18 @@ import {
17101715
// `SidebarToggle` stands for a control of your own.
17111716
const SidebarToggle = () => <button>Menu</button>;
17121717

1713-
// Shown only while a channel is open, as in v14.
1714-
const SidebarToggleWhileChannelOpen = () => {
1715-
const { openChannels } = useWorkspaceNavigation();
1716-
return openChannels.length > 0 ? <SidebarToggle /> : null;
1717-
};
1718-
1719-
// Shown only while a thread is open, as in v14.
1720-
const SidebarToggleWhileThreadOpen = () => {
1721-
const { openThreads } = useWorkspaceNavigation();
1722-
return openThreads.length > 0 ? <SidebarToggle /> : null;
1718+
// Shown only while a channel or thread is open beside the list.
1719+
const SidebarToggleWhileContentOpen = () => {
1720+
const { openChannels, openThreads } = useWorkspaceNavigation();
1721+
return openChannels.length > 0 || openThreads.length > 0 ? <SidebarToggle /> : null;
17231722
};
17241723

17251724
const ChannelListHeaderWithToggle = () => (
1726-
<ChannelListHeader EndContent={SidebarToggleWhileChannelOpen} />
1725+
<ChannelListHeader EndContent={SidebarToggleWhileContentOpen} />
17271726
);
17281727

17291728
const ThreadListHeaderWithToggle = () => (
1730-
<ThreadListHeader EndContent={SidebarToggleWhileThreadOpen} />
1729+
<ThreadListHeader EndContent={SidebarToggleWhileContentOpen} />
17311730
);
17321731

17331732
// Declared once, so WithComponents gets the same object on every render.

‎ai-docs/v15-breaking-changes.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2315,12 +2315,12 @@ const CustomThreadHeader = () => {
23152315
- `ChannelListHeader` and `ThreadListHeader` take `StartContent` (before the title) and `EndContent` (after the title), as `ChannelHeader` / `ThreadHeader` do. Neither has a default. Types: `ChannelListHeaderProps`, `ThreadListHeaderProps`.
23162316
- 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.
23172317
- 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.
2318-
- 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.
2318+
- 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.
23192319
- Evidence:
23202320
- `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`
23212321
- `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?`
23222322
- ticket: REACT-1449
2323-
- 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`
2323+
- 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`
23242324
- Docs impact:
23252325
- `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`
23262326
- Example needed: yes (the guide section has one)

‎examples/cookbook/src/recipes/layout/collapsible-sidebar/views.tsx‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@ export const layouts: LayoutDescriptor[] = [
2020
{ id: 'channels', slots: ['channel', 'thread'] },
2121
];
2222

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

3032
// The default channel list header, with the toggle at its end.
3133
const ChannelListHeaderWithToggle = () => (
32-
<ChannelListHeader EndContent={SidebarToggleWhileChannelOpen} />
34+
<ChannelListHeader EndContent={SidebarToggleWhileContentOpen} />
3335
);
3436

3537
// Declared once, so WithComponents gets the same object on every render.

‎examples/vite/src/ChatLayout/SwitchableChannelNavigation.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
useStateStore,
2626
} from 'stream-chat-react';
2727

28-
import { SidebarToggleWhileChannelOpen } from '../Sidebar/HeaderContent.tsx';
28+
import { SidebarToggleWhileContentOpen } from '../Sidebar/HeaderContent.tsx';
2929
import { ScrollToActiveChannelButton } from './ScrollToActiveChannelButton';
3030

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

228228
return (
229229
<div className='str-chat__channel-list'>
230-
<ChannelListHeader EndContent={SidebarToggleWhileChannelOpen} />
230+
<ChannelListHeader EndContent={SidebarToggleWhileContentOpen} />
231231
<Search />
232232
{!isActive && activePaginator && (
233233
// Expose the active paginator through ChannelListContext (as the SDK's ChannelLists

‎examples/vite/src/Sidebar/HeaderContent.tsx‎

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,18 @@ export const HeaderStartWithSidebarToggle = () => (
1515
</>
1616
);
1717

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

3228
/** The thread list header, registered as the `ThreadListHeader` override, with the sidebar toggle
3329
* at its end. */
3430
export const ThreadListHeaderWithSidebarToggle = () => (
35-
<ThreadListHeader EndContent={SidebarToggleWhileThreadOpen} />
31+
<ThreadListHeader EndContent={SidebarToggleWhileContentOpen} />
3632
);

0 commit comments

Comments
 (0)