Make the action input auto-grow up to ~4 lines
Swap the single-line action <input> for a <textarea> that grows with its content (capped at ~4 lines, then scrolls) and shrinks back after send. Enter still sends; Shift+Enter inserts a newline. Buttons stay bottom-aligned as it grows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8a1650c0d4
commit
92044a53c4
+10
-1
@@ -392,8 +392,17 @@ button:disabled { opacity: 0.45; cursor: default; transform: none; box-shadow: n
|
|||||||
.turn-buttons button { font-size: 0.78rem; padding: 4px 12px; color: var(--text-dim); }
|
.turn-buttons button { font-size: 0.78rem; padding: 4px 12px; color: var(--text-dim); }
|
||||||
.turn-buttons button:hover:not(:disabled) { color: var(--text); }
|
.turn-buttons button:hover:not(:disabled) { color: var(--text); }
|
||||||
|
|
||||||
.input-bar { display: flex; gap: 8px; align-items: stretch; }
|
.input-bar { display: flex; gap: 8px; align-items: flex-end; }
|
||||||
.input-bar input { flex: 1; }
|
.input-bar input { flex: 1; }
|
||||||
|
/* Auto-growing action box: starts at one line, grows to ~4, then scrolls. */
|
||||||
|
.input-bar textarea {
|
||||||
|
flex: 1;
|
||||||
|
resize: none;
|
||||||
|
min-height: 0;
|
||||||
|
max-height: calc(6em + 20px); /* ~4 lines (1.5em each) + padding/border */
|
||||||
|
overflow-y: auto;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
.mode-select { display: flex; }
|
.mode-select { display: flex; }
|
||||||
.mode-select button {
|
.mode-select button {
|
||||||
|
|||||||
@@ -612,6 +612,16 @@ export default function Play() {
|
|||||||
const storyEndRef = useRef(null)
|
const storyEndRef = useRef(null)
|
||||||
const abortRef = useRef(null)
|
const abortRef = useRef(null)
|
||||||
const pinnedRef = useRef(true) // autoscroll only while the reader is at the bottom
|
const pinnedRef = useRef(true) // autoscroll only while the reader is at the bottom
|
||||||
|
const inputRef = useRef(null)
|
||||||
|
|
||||||
|
// Grow the action box with its content (CSS caps it at ~4 lines, then
|
||||||
|
// scrolls); shrinks back after send() clears the text.
|
||||||
|
useEffect(() => {
|
||||||
|
const el = inputRef.current
|
||||||
|
if (!el) return
|
||||||
|
el.style.height = 'auto'
|
||||||
|
el.style.height = `${el.scrollHeight}px`
|
||||||
|
}, [input])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.getAdventure(id)
|
api.getAdventure(id)
|
||||||
@@ -863,8 +873,9 @@ export default function Play() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<textarea
|
||||||
type="text"
|
ref={inputRef}
|
||||||
|
rows={1}
|
||||||
value={input}
|
value={input}
|
||||||
disabled={busy}
|
disabled={busy}
|
||||||
placeholder={
|
placeholder={
|
||||||
@@ -873,7 +884,10 @@ export default function Play() {
|
|||||||
: 'What happens next?'
|
: 'What happens next?'
|
||||||
}
|
}
|
||||||
onChange={(e) => setInput(e.target.value)}
|
onChange={(e) => setInput(e.target.value)}
|
||||||
onKeyDown={(e) => { if (e.key === 'Enter' && !busy) send() }}
|
// Enter sends; Shift+Enter inserts a newline.
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && !e.shiftKey && !busy) { e.preventDefault(); send() }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{busy ? (
|
{busy ? (
|
||||||
<button className="danger" onClick={stopGeneration}>■ Stop</button>
|
<button className="danger" onClick={stopGeneration}>■ Stop</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user