import { useState } from 'react'; type EditableTitleProps = { /** What to show, and what the field opens on. */ value: string; /** Called with the trimmed draft on Enter or blur, only when it actually differs from `value`. */ onCommit: (next: string) => void; /** * Let an emptied field commit as `''`. * * Off by default, because for most titles empty is not a value — the chat rename endpoint 400s on it, * and there is nothing sensible to show. The nav header is the exception: clearing the field is how you * hand the tab back to the route's own name, and it is documented there as the only way out. */ allowEmpty?: boolean; /** No edit affordance — renders as plain text. For a title with nothing behind it yet, like a new chat. */ readOnly?: boolean; ariaLabel: string; /** Tooltip on the resting state. The edit affordance is invisible otherwise, so this is how it is found. */ hint?: string; className?: string; inputClassName?: string; }; /** * Click a title, type a new one. * * The edit is a **draft committed on blur/Enter**, not a live write. Writing every keystroke is fine * while a title is a plain string and stops being fine the moment empty means something — here it means * "keep what you had", so deleting the last character would otherwise snap the field to the old value * underneath the cursor. Escape discards. * * Extracted from the nav header's tab rename, which is now one of two callers. Deliberately not a third * caller: `SessionList`'s row rename is opened by a pencil button and confirmed by a check button, so it * is a different interaction wearing the same styling, and folding it in would mean an `isEditing` prop * that only one caller passes. */ export function EditableTitle({ value, onCommit, allowEmpty, readOnly, ariaLabel, hint, className = '', inputClassName = '', }: EditableTitleProps) { const [draft, setDraft] = useState(null); if (readOnly) return {value}; if (draft !== null) { return ( setDraft(ev.target.value)} onBlur={() => { const next = draft.trim(); if ((next || allowEmpty) && next !== value) onCommit(next); setDraft(null); }} onKeyDown={(ev) => { if (ev.key === 'Enter') ev.currentTarget.blur(); if (ev.key === 'Escape') setDraft(null); // discard, keeping whatever it was called }} aria-label={ariaLabel} placeholder={value} className={inputClassName} /> ); } return ( ); }