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

Search menu

This recipe adds an app menu next to the search bar above the channel list: a Menu button that opens a list with Profile, New Group and Sign Out. The search bar is the SearchBar override key, so the recipe wraps the default bar and keeps search working. The search components are explained in Search.

Example

The menu is a plain list. It calls close after an item is picked:

src/AppMenu.tsx
import "./AppMenu.css";

type AppMenuProps = {
  close: () => void;
};

export const AppMenu = ({ close }: AppMenuProps) => {
  const handleSelect = () => {
    // Open the profile, start a group or sign out here.
    close();
  };

  return (
    <div className="app-menu__container">
      <ul className="app-menu__item-list">
        <li>
          <button
            className="app-menu__item"
            onClick={handleSelect}
            type="button"
          >
            Profile
          </button>
        </li>
        <li>
          <button
            className="app-menu__item"
            onClick={handleSelect}
            type="button"
          >
            New Group
          </button>
        </li>
        <li>
          <button
            className="app-menu__item"
            onClick={handleSelect}
            type="button"
          >
            Sign Out
          </button>
        </li>
      </ul>
    </div>
  );
};
src/AppMenu.css
.search-bar-with-menu {
  position: relative;
}

.search-bar-with-menu__button {
  left: 0.75rem;
  position: absolute;
  top: 0.75rem;
  z-index: 2;
}

.search-bar-with-menu .str-chat__search-bar {
  padding-left: 4rem;
}

.app-menu__container {
  background-color: white;
  border-radius: 5px;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.15);
  left: 0.75rem;
  position: absolute;
  top: 3.25rem;
  z-index: 3;
}

.app-menu__item-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.app-menu__item {
  background: none;
  border: 0;
  cursor: pointer;
  font: inherit;
  padding: 0.5rem 1rem;
  text-align: start;
  width: 100%;
}

.app-menu__item:hover {
  background-color: lightgrey;
}

The search bar with the menu renders the default SearchBar beside the button. Register it under the SearchBar key, around ChannelNavigation, which renders the channel list header, the search and the channel lists:

src/App.tsx
import { useState } from "react";
import {
  ChannelHeader,
  ChannelNavigation,
  Chat,
  MessageComposer,
  MessageList,
  SearchBar,
  ThreadHeader,
  WithComponents,
} from "stream-chat-react";
import {
  ChannelSlot,
  ChatView,
  ThreadSlot,
} from "stream-chat-react/slot-layout";
import type { LayoutDescriptor } from "stream-chat-react/slot-layout";
import { AppMenu } from "./AppMenu";

const SearchBarWithMenu = () => {
  const [menuOpen, setMenuOpen] = useState(false);

  return (
    <div className="search-bar-with-menu">
      <button
        aria-expanded={menuOpen}
        className="search-bar-with-menu__button"
        onClick={() => setMenuOpen((open) => !open)}
        type="button"
      >
        Menu
      </button>
      {menuOpen && <AppMenu close={() => setMenuOpen(false)} />}
      <SearchBar />
    </div>
  );
};

// Declared once, so WithComponents gets the same object on every render.
const overrides = { SearchBar: SearchBarWithMenu };

const layouts: LayoutDescriptor[] = [
  { id: "channels", slots: ["channel", "thread"] },
];

const ChannelsView = () => (
  <>
    <WithComponents overrides={overrides}>
      <ChannelNavigation />
    </WithComponents>
    <ChannelSlot slot="channel">
      <ChannelHeader />
      <MessageList />
      <MessageComposer />
    </ChannelSlot>
    <ThreadSlot slot="thread">
      <ThreadHeader />
      <MessageList />
      <MessageComposer />
    </ThreadSlot>
  </>
);

export const App = () => (
  <Chat client={client}>
    <ChatView layouts={layouts} views={{ channels: <ChannelsView /> }} />
  </Chat>
);

SearchBar reads the search from Search, so it keeps working inside the wrapper: typing searches, and the results replace the channel lists while the search is active. The channel lists come from the paginators registered on client.channelManager, as in the Quick start.

This menu closes only when an item is picked or the button is clicked again. For one that also closes on Escape and on clicks outside it, and moves the focus into it, open it as a ContextMenu: see Dialogs.