Attachment Selector
<AttachmentSelector/> is the button before the composer's input. It opens a menu to upload a file, create a poll, share a location or pick a command. The menu is an action set: a list of actions, each with a type and a component. Attachments and uploads explains uploads, and Actions explains how action sets work.
Best practices
- Change the menu by passing your own
attachmentSelectorActionSet, built fromdefaultAttachmentSelectorActionSet. Register the selector that renders it under theAttachmentSelectorkey. - Decide who can upload, create polls and share locations on the server and in
client.config. The selector offers a built-in action only when the user can use it, and that filter can't be turned off. - Give a custom action a
typeof its own. The four built-in types are filtered, whatever component you give them. - Set
getModalPortalDestinationwhen the selector renders outside a<Channel/>, or when the channel's container clips its dialogs.
Default behavior
The default menu has four actions, in this order:
| Type | Label | What it does | Offered when |
|---|---|---|---|
uploadFile |
File | Opens the file picker | The user can upload files, as Enabling features explains |
createPoll |
Poll | Opens the poll creation dialog | Polls are turned on, the user has the send-poll capability, and it's not a thread |
addLocation |
Location | Opens the location sharing dialog | Location sharing is turned on, and it's not a thread |
selectCommand |
Commands | Opens a submenu of the channel's commands | The channel type has commands |
<AttachmentSelector/> applies this filter itself, every time it renders, to the default set and to any set you pass. An action of any other type is always offered.
The File item stays in the menu, disabled, once the message reaches the file limit (messageComposer.attachments.maxNumberOfFilesPerMessage in client.config). Uploads says which files count.
What the selector renders depends on the actions it offers:
- No actions: nothing.
- One action that has a
StandaloneButton: that button alone, with no menu. TheuploadFileaction hasFileUploadButtonby default, so File alone renders a plain upload button. Standalone buttons explains them. - Otherwise: the button and its menu. A lone action without a
StandaloneButtonis the only item of the menu.
The button that opens the menu is disabled during a slow-mode cooldown. While a command is selected, the selector is hidden with a CSS transition and taken out of the tab order. A dialog opened from the menu closes once its action is no longer offered, or once a standalone button replaces the menu.
While the composer edits a message (it renders inside that message's MessageContext), the default layout renders <SimpleAttachmentSelector/> in place of <AttachmentSelector/>, which shows FileUploadButton alone, under the File action's rule and file limit.
Enabling features
Each action follows the settings below. Configuration explains how the server and client.config combine.
- File: offered while both of these hold. See Attachments and uploads.
- Attachments are enabled. That needs
messageComposer.attachments.enabledinclient.config, and the channel type'suploadssetting, set in the Stream Dashboard or through the server API. Theuploadssetting doesn't count whenmessageComposer.attachments.customCdninclient.configistrue. messageComposer.attachments.customCdninclient.configistrue, or the user has theupload-filecapability.
- Attachments are enabled. That needs
- Poll:
messageComposer.polls.enabledinclient.config, the channel type'spollssetting (set in the Stream Dashboard or through the server API), and thesend-pollcapability. See Polls. - Location:
messageComposer.location.enabledinclient.config, and the channel type'sshared_locationssetting, set in the Stream Dashboard or through the server API. See Location sharing. - Commands: the channel type's
commands, set in the Stream Dashboard or through the server API. See Commands and Giphy.
The filter decides only what the selector offers. It doesn't grant what an action needs:
- An action with a
typeof its own is offered to every user, such as an upload action for a user without theupload-filecapability. AttachmentManager.uploadFiles()still drops files whileattachmentManager.isUploadEnabledisfalse, so an upload action that goes through the file input orAttachmentManager.uploadFiles()uploads nothing.- Stream's server still refuses a poll from a user without the
send-pollcapability.
Custom action sets
<AttachmentSelector/> props:
| Prop | Type | Default | Description |
|---|---|---|---|
attachmentSelectorActionSet |
AttachmentSelectorAction[] |
defaultAttachmentSelectorActionSet |
The actions of the menu, top to bottom. The filter in Default behavior still applies. |
buttonProps |
Omit<ButtonProps, "onClick"> |
none | Props for the button that opens the menu, or for a standalone button. The disabled you pass is ignored: the menu button and a standalone button are disabled during a slow-mode cooldown. |
getModalPortalDestination |
() => HTMLElement | null |
the <Channel/> container |
The element the action dialogs render into. See Custom modal portal destination. |
An AttachmentSelectorAction has these fields:
| Field | Type | Description |
|---|---|---|
type |
"uploadFile" | "createPoll" | "addLocation" | "selectCommand" | string |
What the action is. Also the key passed to openModalForAction. |
ActionButton |
ComponentType<AttachmentSelectorActionProps> |
The menu item. It gets openModalForAction(type), and submenuItems and submenuHeader when the action has a submenu. |
ModalContent |
ComponentType<AttachmentSelectorModalContentProps> |
What openModalForAction(type) opens in a modal. It gets close. |
Submenu |
ContextMenuSubmenu |
A submenu, as the commands action has. |
Header |
ContextMenuHeaderComponent |
The header of the submenu. |
id |
string |
An identifier of your own. The selector doesn't read it. |
StandaloneButton |
ComponentType<AttachmentSelectorStandaloneButtonProps> |
What renders in place of the menu when this is the only action offered. uploadFile defaults to FileUploadButton. See Standalone buttons. |
A menu item closes the menu with closeMenu() from useContextMenuContext(). The default items are exported as DefaultAttachmentSelectorComponents: File, Poll, Location and Command.
This selector leaves out Location:
import type { PropsWithChildren } from "react";
import {
AttachmentSelector,
defaultAttachmentSelectorActionSet,
WithComponents,
} from "stream-chat-react";
const actionSet = defaultAttachmentSelectorActionSet.filter(
({ type }) => type !== "addLocation",
);
const AttachmentSelectorWithoutLocation = () => (
<AttachmentSelector attachmentSelectorActionSet={actionSet} />
);
const overrides = { AttachmentSelector: AttachmentSelectorWithoutLocation };
export const WithoutLocationAction = ({ children }: PropsWithChildren) => (
<WithComponents overrides={overrides}>{children}</WithComponents>
);Standalone buttons adds an action of its own, and the Product card recipe adds one that opens a picker in a dialog with openModalForAction().
Standalone buttons
When the selector offers only one action and that action has a StandaloneButton, the selector renders that button instead of a menu with one item.
- Where it renders: inside the selector's root element,
.str-chat__attachment-selector, in place of the menu button. It collapses while a command is selected. - Props:
buttonProps, the selector'sbuttonPropswithdisabledset while a slow-mode cooldown runs. The type isAttachmentSelectorStandaloneButtonProps. - No dialog: there's no menu, so it can't call
openModalForAction. - Width: the selector's space beside the input fits a round icon button. A wider button needs the
--str-chat__message-composer-attachment-slot-widthCSS variable.
The uploadFile action's standalone button is FileUploadButton. It opens the file picker, and it's disabled at the file limit. An action with a type of your own has no standalone button by default; an upload action of your own sets StandaloneButton: FileUploadButton to get the same plain button. Enabling features says what such an action doesn't grant.
In this example, the set's only action is one of your own, which inserts a bug report template, so the selector always renders its labeled standalone button. The action still needs an ActionButton for the menu, which shows once the set has more actions:
import type { PropsWithChildren } from "react";
import {
AttachmentSelector,
Button,
ContextMenuButton,
useContextMenuContext,
useMessageComposerController,
WithComponents,
} from "stream-chat-react";
import type {
AttachmentSelectorAction,
AttachmentSelectorStandaloneButtonProps,
} from "stream-chat-react";
const template = "Steps to reproduce:\nExpected:\nActual:";
const BugReportMenuItem = () => {
const { closeMenu } = useContextMenuContext();
const messageComposer = useMessageComposerController();
return (
<ContextMenuButton
onClick={() => {
void messageComposer.textComposer.insertText({ text: template });
closeMenu();
}}
>
Bug report
</ContextMenuButton>
);
};
const BugReportButton = ({
buttonProps,
}: AttachmentSelectorStandaloneButtonProps) => {
const messageComposer = useMessageComposerController();
return (
<Button
{...buttonProps}
appearance="outline"
onClick={() =>
void messageComposer.textComposer.insertText({ text: template })
}
size="lg"
variant="secondary"
>
Bug report
</Button>
);
};
const actionSet: AttachmentSelectorAction[] = [
{
ActionButton: BugReportMenuItem,
StandaloneButton: BugReportButton,
type: "bugReportTemplate",
},
];
const BugReportSelector = () => (
<AttachmentSelector attachmentSelectorActionSet={actionSet} />
);
const overrides = { AttachmentSelector: BugReportSelector };
export const WithBugReportButton = ({ children }: PropsWithChildren) => (
<WithComponents overrides={overrides}>{children}</WithComponents>
);.str-chat__message-composer {
--str-chat__message-composer-attachment-slot-width: 8rem;
}Poll and location dialogs
The createPoll and addLocation actions open the PollCreationDialog and ShareLocationDialog overrides, unless the action has a ModalContent of its own. Both dialogs get close. Opening the poll dialog starts a new poll in the composer's pollComposer.
import type { PropsWithChildren } from "react";
import type { PollCreationDialogProps } from "stream-chat-react";
import { PollCreationDialog, WithComponents } from "stream-chat-react";
const PollDialogWithIntro = (props: PollCreationDialogProps) => (
<div className="poll-dialog">
<p>Polls close after a week.</p>
<PollCreationDialog {...props} />
</div>
);
const overrides = { PollCreationDialog: PollDialogWithIntro };
export const WithPollDialogIntro = ({ children }: PropsWithChildren) => (
<WithComponents overrides={overrides}>{children}</WithComponents>
);The dialogs' own props are on Poll components and Location sharing.
Custom modal portal destination
The action dialogs render into the <Channel/> container, the element with the str-chat__channel id. Outside a <Channel/>, or when that container clips them, pass getModalPortalDestination:
import { AttachmentSelector } from "stream-chat-react";
const getModalPortalDestination = () =>
document.getElementById("chat-modal-root");
export const AttachmentSelectorWithModalRoot = () => (
<AttachmentSelector getModalPortalDestination={getModalPortalDestination} />
);Register it under the AttachmentSelector key. Dialogs explains modals.
AttachmentSelectorContext
The menu items of <AttachmentSelector/> can read AttachmentSelectorContext with useAttachmentSelectorContext():
| Value | Type | Description |
|---|---|---|
fileInput |
HTMLInputElement | null |
The hidden file input of the selector. Call fileInput.click() to open the file picker. null until the input renders, and outside the selector. |
<SimpleAttachmentSelector/> and standalone buttons don't provide this context.
Hooks
useAttachmentManagerState: whether uploads, polls and locations are turned on, and how many files still fit.useCanCreatePoll: whether the poll being composed can be sent.useMessageComposerCommands: the channel's commands, as the commands submenu shows them.useChannelCapabilities: the user's capabilities, such asupload-fileandsend-poll.