Section Navigator
SectionNavigator shows a set of sections, each a navigation button paired with a panel, and moves between them. ChannelDetail is a SectionNavigator with five channel sections. It knows nothing about channels, so you can use it for any panel with sections, such as settings. Moving between channels, threads and messages describes how users move around channel details.
Installation
import {
SectionNavigator,
SectionNavigatorHeader,
useSectionNavigatorContext,
} from "stream-chat-react/channel-detail";
import "stream-chat-react/dist/css/index.css";
import "stream-chat-react/dist/css/channel-detail.css";The navigator's styles are in channel-detail.css.
Defining sections
A section is a { id, NavButton, SectionContent } object. NavButton renders the section's entry in the list of sections, and SectionContent its panel:
import {
SectionNavigator,
SectionNavigatorHeader,
} from "stream-chat-react/channel-detail";
import type {
SectionNavigatorNavButtonProps,
SectionNavigatorSection,
} from "stream-chat-react/channel-detail";
const navButton =
(label: string) =>
({ select, selected }: SectionNavigatorNavButtonProps) => (
<button aria-pressed={selected} onClick={select} type="button">
{label}
</button>
);
const GeneralSettings = () => (
<>
<SectionNavigatorHeader title="General" />
<p>Language and time zone</p>
</>
);
const AdvancedSettings = () => (
<>
<SectionNavigatorHeader title="Advanced" />
<p>Developer options</p>
</>
);
// Declared once: ids identify the sections in the navigation history.
const sections: SectionNavigatorSection[] = [
{
id: "general",
NavButton: navButton("General"),
SectionContent: GeneralSettings,
},
{
id: "advanced",
NavButton: navButton("Advanced"),
SectionContent: AdvancedSettings,
},
];
export const Settings = () => (
<div style={{ height: "100%", width: "100%" }}>
<SectionNavigator sections={sections} />
</div>
);The first section opens first. Pass initialHistory, such as [{ id: "advanced" }], to start on another one.
When sections changes and the open section is no longer in it, the navigator opens the first section.
Layout
The navigator has two layouts, the values of SECTION_NAVIGATOR_LAYOUT:
tabs: the list of sections beside the open one.inline: one section at a time.SectionNavigatorHeadershows a menu button that opens the list as a drawer. Escape, a click outside the drawer or picking a section closes it.
By default, a ResizeObserver measures the navigator's parent element. While it's narrower than tabsLayoutMinWidth, 640px by default, the layout is inline; otherwise it's tabs. A width of 0, such as a hidden parent, changes nothing. Before the first measurement, and where ResizeObserver doesn't exist, the layout is defaultLayout.
The root element carries the layout as data-layout, and the class str-chat__section-navigator--inline in the inline layout. onLayoutChange is called with the layout once on mount and on every change.
const logLayout = (layout: SectionNavigatorLayout) =>
console.log("layout:", layout);
export const MeasuredSettings = () => (
<SectionNavigator
defaultLayout="inline"
onLayoutChange={logLayout}
sections={sections}
tabsLayoutMinWidth={720}
/>
);Controlled layout
Pass layout to fix the layout. While it's set, nothing is measured:
<SectionNavigator layout="tabs" sections={sections} />Custom layout observer
createLayoutObserver replaces the measuring. It receives the navigator's root element, setLayout and tabsLayoutMinWidth, and returns a cleanup function. This one follows the window's width:
const observeWindowWidth: SectionNavigatorLayoutObserverFactory = ({
setLayout,
tabsLayoutMinWidth,
}) => {
const update = () =>
setLayout(window.innerWidth < tabsLayoutMinWidth ? "inline" : "tabs");
update();
window.addEventListener("resize", update);
return () => window.removeEventListener("resize", update);
};
export const WindowSizedSettings = () => (
<SectionNavigator
createLayoutObserver={observeWindowWidth}
sections={sections}
/>
);Declare the factory once. A new function on every render measures again on every render.
Navigation state with useSectionNavigatorContext
Sections and nav buttons read and change the navigation with useSectionNavigatorContext(). Here the General section links to the Advanced one, and Advanced shows a back button when it was opened from another section:
import {
SectionNavigatorHeader,
useSectionNavigatorContext,
} from "stream-chat-react/channel-detail";
export const GeneralSettings = () => {
const { historyPush } = useSectionNavigatorContext();
return (
<>
<SectionNavigatorHeader title="General" />
<button onClick={() => historyPush({ id: "advanced" })} type="button">
Advanced settings
</button>
</>
);
};
export const AdvancedSettings = () => {
const { history, historyPop } = useSectionNavigatorContext();
// Only the inline layout keeps earlier sections in the history.
const canGoBack = history.length > 1;
return (
<>
<SectionNavigatorHeader
goBack={canGoBack ? historyPop : undefined}
title="Advanced"
/>
<p>Developer options</p>
</>
);
};Routes are section ids. A route that matches no section opens the first section, so a view inside a section, such as one member's details, keeps its own state instead: ChannelMembersView and ChannelManagementView do.
| Value | Type | Description |
|---|---|---|
layout |
SectionNavigatorLayout |
The current layout. |
history |
SectionNavigatorRoute[] |
The navigation stack, { id } routes. The last one is open. |
historyPush |
(route: SectionNavigatorRoute) => void |
Opens a route. In tabs, it replaces the history; in inline, it adds to it. Pushing the open route does nothing. |
historyPop |
() => void |
Goes back one route. Does nothing at the first one. |
isNavigationOpen |
boolean |
Whether the drawer is open, in the inline layout. |
openNavigation |
() => void |
Opens the drawer. |
closeNavigation |
() => void |
Closes the drawer. |
Outside a SectionNavigator, the hook doesn't throw. It returns the tabs layout, an empty history and functions that do nothing.
SectionNavigatorHeader
SectionNavigatorHeader is the header for a section's panel. In the inline layout it shows a menu button that opens the drawer, except when goBack is set: a view with a back button shows no menu button.
import { useModalContext } from "stream-chat-react";
import { SectionNavigatorHeader } from "stream-chat-react/channel-detail";
export const NotificationsHeader = () => {
const { close } = useModalContext();
return (
<SectionNavigatorHeader
close={close}
description="Choose what you hear about"
title="Notifications"
/>
);
};| Prop | Type | Default | Description |
|---|---|---|---|
title |
React.ReactNode |
required | The header's title. |
description |
string |
none | A line under the title. |
goBack |
() => void |
none | Shows a back button that calls it, and hides the menu button. |
close |
() => void |
none | Shows a close button that calls it. The channel details views pass the modal's close. |
TrailingContent |
React.ComponentType |
none | Rendered at the end, before the close button. |
className |
string |
none | Added to the header's root element. |
titleId, descriptionId |
string |
ids derived from the modal | The ids of the title and description elements, for aria-labelledby and aria-describedby. |
The menu button's label is the translation key channelDetail.sectionNavigatorHeader.openMenu.ariaLabel, "Open menu".
Props
| Prop | Type | Default | Description |
|---|---|---|---|
sections |
SectionNavigatorSection[] |
required | The sections, in order. |
defaultLayout |
SectionNavigatorLayout |
"tabs" |
The layout before the first measurement. |
layout |
SectionNavigatorLayout |
none | A fixed layout. While it's set, nothing is measured. |
tabsLayoutMinWidth |
number |
640 |
The narrowest width, in pixels, for the tabs layout. |
onLayoutChange |
(layout: SectionNavigatorLayout) => void |
none | Called with the layout on mount and whenever it changes. |
initialHistory |
SectionNavigatorRoute[] |
the first section | The navigation stack at the start. |
createLayoutObserver |
SectionNavigatorLayoutObserverFactory |
a ResizeObserver on the parent element |
Measures the layout. Returns a cleanup function. |
className |
string |
none | Added to the root element, str-chat__section-navigator. |
Other div attributes go to the root element.
SectionNavigatorSection
| Field | Type | Description |
|---|---|---|
id |
string |
The section's id. Keep it unique and stable: the history refers to it. |
NavButton |
ComponentType<SectionNavigatorNavButtonProps> |
The entry in the list of sections. |
SectionContent |
ComponentType<SectionNavigatorSectionContentProps> |
The panel, shown while the section is open. Gets layout. |
SectionNavigatorNavButtonProps are the button props plus sectionId: string, selected: boolean and select: () => void. select opens the section and closes the drawer.
The other types: SectionNavigatorLayout is a string, "tabs" or "inline". SectionNavigatorRoute is { id: string }. SectionNavigatorLayoutObserverFactory is ({ element, setLayout, tabsLayoutMinWidth }) => (() => void) | void.
Related hooks
| Hook | Returns |
|---|---|
useSectionNavigatorContext() |
the layout, the history and the drawer state, as above |
useChannelDetailContext() |
inside ChannelDetail, { channel } |