Info:
This is release candidate documentation for Stream Chat React SDK v15. For the latest stable version, see the latest version (v14).

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. SectionNavigatorHeader shows 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.

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.

Hook Returns
useSectionNavigatorContext() the layout, the history and the drawer state, as above
useChannelDetailContext() inside ChannelDetail, { channel }