Suggestion List
In this example, we will demonstrate how to customize the autocomplete suggestion list items that
appear above the MessageInput
component when one of the supported autocompleteTriggers
is entered into the text input.
The Channel
component accepts three props that adjust the look and feel of the autocomplete
suggestion list:
Below we show how to create custom header and list items, while leaving the list container unchanged.
#
Suggestion HeaderBy default, the component library handles autocomplete suggestions for user mentions @
, commands /
,
and emojis :
. The header component receives the text value
of the MessageInput
via props. The current trigger
is the first character in the value string, so we condition the UI of our header component based on the
currently entered trigger.
const SuggestionHeader: React.FC<SuggestionListHeaderProps> = (props) => {
const { value } = props;
const initialCharacter = value[0];
switch (initialCharacter) {
case '@':
return <div className='suggestion-header'>Mention someone...</div>;
case '/':
return <div className='suggestion-header'>Available commands...</div>;
case ':':
return <div className='suggestion-header'>Choose an emoji...</div>;
default:
return null;
}
};
tip
To customize autocompleteTriggers
, pass your own TriggerProvider
component to Channel
.
#
Suggestion List ItemsSimilar to our header component, we will conditionally render the list items based on the item
type
received by our custom component. The SuggestionItem
type represents a union of type options
for the various autocompleteTriggers
.
Since our component can receive three potential item types, we must construct a few type guards to
ensure proper typing and prevent TypeScript errors. We check if the received item
is an emoji, a mention,
or both. Each case will trigger slightly different UI in our component.
type SuggestionItem = BaseEmoji | SuggestionUser | SuggestionCommand;
const isEmoji = (output: SuggestionItem): output is BaseEmoji =>
(output as BaseEmoji).native != null;
const isMention = (output: SuggestionItem): output is SuggestionUser =>
(output as SuggestionUser).id != null && (output as SuggestionUser).native == null;
const isEmojiOrMention = (output: SuggestionItem): output is BaseEmoji | SuggestionUser =>
(output as BaseEmoji | SuggestionUser).id != null;
Once we've aligned our types, we pull necessary data off props and assemble a component that
on click interacts with the MessageInput
and properly selects data. The type guards are necessary
since the received items do not conform to a common type with shared object keys.
const SuggestionItem = React.forwardRef(
(props: SuggestionItemProps, ref: React.Ref<HTMLDivElement>) => {
const { item, onClickHandler, onSelectHandler, selected } = props;
const selectItem = () => onSelectHandler(item);
const itemName = isEmojiOrMention(item) ? item.name || item.id : item.name;
const displayText = isEmoji(item) ? `${item.native} - ${itemName}` : itemName;
return (
<div
className={`suggestion-item ${selected ? 'selected' : ''}`}
onClick={onClickHandler}
onMouseEnter={selectItem}
ref={ref}
role='button'
tabIndex={0}
>
{isMention(item) && <Avatar image={item.image} size={20} />}
{displayText}
</div>
);
},
);
#
ImplementationNow that each individual piece has been constructed, we can assemble all of the snippets into the final code example.
#
The Code.suggestion-header {
font-weight: 700;
font-size: 16px;
}
.suggestion-item {
display: flex;
align-items: center;
height: 32px;
font-size: 18px;
padding-left: 16px;
}
.suggestion-item.selected {
background: var(--white-smoke);
color: var(--primary-color);
font-weight: 700;
cursor: pointer;
}
const SuggestionHeader: React.FC<SuggestionListHeaderProps> = (props) => {
const { value } = props;
const initialCharacter = value[0];
switch (initialCharacter) {
case '@':
return <div className='suggestion-header'>Mention someone...</div>;
case '/':
return <div className='suggestion-header'>Available commands...</div>;
case ':':
return <div className='suggestion-header'>Choose an emoji...</div>;
default:
return null;
}
};
type SuggestionItem = BaseEmoji | SuggestionUser | SuggestionCommand;
const isEmoji = (output: SuggestionItem): output is BaseEmoji =>
(output as BaseEmoji).native != null;
const isMention = (output: SuggestionItem): output is SuggestionUser =>
(output as SuggestionUser).id != null && (output as SuggestionUser).native == null;
const isEmojiOrMention = (output: SuggestionItem): output is BaseEmoji | SuggestionUser =>
(output as BaseEmoji | SuggestionUser).id != null;
const SuggestionItem = React.forwardRef(
(props: SuggestionItemProps, ref: React.Ref<HTMLDivElement>) => {
const { item, onClickHandler, onSelectHandler, selected } = props;
const selectItem = () => onSelectHandler(item);
const itemName = isEmojiOrMention(item) ? item.name || item.id : item.name;
const displayText = isEmoji(item) ? `${item.native} - ${itemName}` : itemName;
return (
<div
className={`suggestion-item ${selected ? 'selected' : ''}`}
onClick={onClickHandler}
onMouseEnter={selectItem}
ref={ref}
role='button'
tabIndex={0}
>
{isMention(item) && <Avatar image={item.image} size={20} />}
{displayText}
</div>
);
},
);
const App = () => (
<Chat client={client}>
<ChannelList />
<Channel
AutocompleteSuggestionHeader={SuggestionHeader}
AutocompleteSuggestionItem={SuggestionItem}
>
<Window>
<ChannelHeader />
<MessageList />
<MessageInput />
</Window>
<Thread />
</Channel>
</Chat>
);
#
The ResultMentions UI:

Commands UI:

Emojis UI:
