Chat Composer

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.

roast-log.csv18 KB
'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/ui

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

PropTypeDefault
value?string
defaultValue?string''
onValueChange?((value: string) => void)
onSubmit?((text: string) => void)
onStop?(() => void)
busy?booleanfalse
disabled?booleanfalse
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''

Still, Deskhand and Hollis are fictional brands. The coffee photographs are original generated artwork. @brand-studio/ui is MIT licensed.