Composer
import { Composer, ComposerTool, ASK } from '@hanzo/ui/chat'
The stacked composer
A field over a toolbar, the send control at the end of it.
Enter to send
export function Stacked() {const [draft, setDraft] = useState('')return <Composer value={draft} onChange={setDraft} onSend={() => setDraft('')} hint="Enter to send" />}
One line, with the context above and the controls below
A coding composer.
Default
cloud
main
Build
Zen 5
export function Coding() {const [draft, setDraft] = useState('')const [repo, setRepo] = useState({ owner: 'hanzo-inc', name: 'cloud' })const [branch, setBranch] = useState('main')return (<YStack width="100%" maxW={768} pt={260}><Composer inline value={draft} onChange={setDraft} onSend={() => setDraft('')} placeholder="Describe a task or ask a question" head={ <> <ChipSelect name="Environment" label="Default" onChange={() => {}} items={[{ id: 'd', label: 'Default' }]} /> <RepoSelect value={repo} onChange={setRepo} load={async () => ({ repos: [repo] })} /> <BranchSelect value={branch} onChange={setBranch} load={async () => ({ branches: ['main', 'dev'] })} /> </> } foot={ <> <ComposerTool label="Attach" icon={<Plus size={14} />} /> <ComposerTool label="Voice" icon={<Mic size={14} />} /> <ComposerTool label="Mode" text="Build" caret /> <XStack flex={1} /> <ChipSelect name="Model" label="Zen 5" onChange={() => {}} items={[{ id: 'z', label: 'Zen 5' }]} quiet /> </> } /></YStack>)}
Busy
The send mark becomes Stop while a turn is in flight.
export function Busy() {return <Composer inline value="" busy onChange={() => {}} onSend={() => {}} onStop={() => {}} />}
Types
export interface ComposerProps extends Stack {value: stringonChange: (value: string) => void/** Called when the draft is submitted. Clearing `value` is the host's job. */onSend: () => void/** Called instead of `onSend` while `busy`. Omit to disable stopping. */onStop?: () => void/** A turn is in flight: the send control becomes stop. */busy?: booleandisabled?: booleanplaceholder?: string/** Row floor for the field. It grows with the draft from here. */rows?: number/** Ceiling for that growth, px. Past it the field scrolls instead. */maxHeight?: number/** Toolbar slot, rendered at the start of the footer row. */children?: ReactNode/** Hint shown at the end of the footer row. */hint?: string/*** The accessible name of the field, independent of `placeholder`.** A placeholder that rotates renames the control on every tick and gets the* field re-announced mid-sentence. The name is stable; the placeholder is* decoration.*/label?: string/** The field's own props — a ref, a testid, focus handlers, a paste handler. */field?: FieldProps/** Replaces the send control. `children` is the START of the footer row, so a* second commit mode has nowhere else to go. */send?: ReactNode/*** Drawn ABOVE the frame — the row of context a draft is sent with (where it* runs, which repository, which branch). Outside the frame on purpose: it* says what the draft is about, and the field says what it is.*/head?: ReactNode/*** Drawn UNDER the frame — attach, voice, mode at its start and the model at* its end, as the host arranges them. Unlike `children`, which lives inside* the frame beside the send control.*/foot?: ReactNode/*** One line: the field and the send control share a single row inside the* frame, and the send control is a glyph at the field's end — Enter's own* mark while idle, Stop while busy. `children` and `hint` join that row* before it. Unset, the frame is the stacked field-over-toolbar it has* always been.*/inline?: boolean}
export interface ComposerToolProps extends Row {/** The control's name — what a screen reader says and the tooltip shows. */label: stringicon?: ReactNode/** Words drawn after the icon — a mode, a model. Absent, the tool is icon-only. */text?: string/** A trailing caret: this opens a menu. */caret?: booleanonPress?: () => voiddisabled?: boolean}
Exports
ComposerComposerToolASKtype ComposerPropstype ComposerToolProps