The message box: grows with the text, sends on Enter and turns into stop while the agent answers.
Enter sends. Shift+Enter starts a new line.
'use client';
import { useRef, useState } from 'react';
import { Attachment, ChatComposer, DictationButton } from '@brand-studio/ui';
export default function ChatComposerDemo() {
const [sent, setSent] = useState<string>();
const [busy, setBusy] = useState(false);
const [file, setFile] = useState(true);
const timer = useRef<ReturnType<typeof setTimeout>>(undefined);
return (
<div style={{ width: '100%', maxWidth: 560, display: 'grid', gap: 16 }}>
<p style={{ margin: 0, minHeight: 24, color: 'var(--bs-muted)' }}>{sent ? `Sent: ${sent}` : 'Enter sends. Shift+Enter starts a new line.'}</p>
<ChatComposer
placeholder="Ask about your coffee"
busy={busy}
onSubmit={text => { setSent(text); setBusy(true); timer.current = setTimeout(() => setBusy(false), 2500); }}
onStop={() => { clearTimeout(timer.current); setBusy(false); }}
attachments={file && <Attachment name="roast-log.csv" size="18 KB" onRemove={() => setFile(false)} />}
start={<DictationButton />}
/>
</div>
);
}Installation
pnpm add @brand-studio/uinpm install @brand-studio/uiyarn add @brand-studio/uibun add @brand-studio/uiInstall the peer dependencies
npm install react react-domImport the stylesheet once
import '@brand-studio/ui/styles.css';Wrap your app in a brand theme
import { BrandTheme, ChatComposer } from '@brand-studio/ui'; import brand from './brand.json'; <BrandTheme palette={brand.tokens} mode="system"> {/* your app */} </BrandTheme>
Usage
import { ChatComposer } from '@brand-studio/ui';<ChatComposer
busy={answering}
onSubmit={text => ask(text)}
onStop={() => cancel()}
attachments={files.map(file => <Attachment key={file.name} name={file.name} />)}
start={<DictationButton />}
/>Enter sends and Shift+Enter starts a new line. Enter never sends in the middle of typing with an input method, such as Japanese or Vietnamese. The box grows with the text up to about eight lines, then scrolls. Send stays disabled until there is text. While busy, send becomes a stop button.
start and end hold tools either side of the send button. attachments shows above the text. Leave value unset and the composer keeps its own text; set it with onValueChange to control it.
Accessibility
- The text box has a label for screen readers, set with
label. - Send and stop are labelled buttons. Send reports disabled while the box is empty.
API Reference
| Prop | Type | Default |
|---|---|---|
value? | string | – |
defaultValue? | string | '' |
onValueChange? | ((value: string) => void) | – |
onSubmit? | ((text: string) => void) | – |
onStop? | (() => void) | – |
busy? | boolean | false |
disabled? | boolean | false |
label? | string | 'Message' |
placeholder? | string | 'Ask anything' |
sendLabel? | string | 'Send' |
stopLabel? | string | 'Stop' |
attachments? | React.ReactNode | – |
start? | React.ReactNode | – |
end? | React.ReactNode | – |
above? | React.ReactNode | – |
inputRef? | React.RefObject<HTMLTextAreaElement | null> | – |
inputProps? | React.TextareaHTMLAttributes<HTMLTextAreaElement> | – |
className? | string | '' |