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:
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>
);
};.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:
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.