feat(FN-4189): lift chat input height cap and add autosize behavior
Lifts the chat input height cap and enables auto-resize as the user types, improving the chat UX in `@fusion/dashboard`. Includes test coverage for the autosize behavior and a changeset for the `@runfusion/fusion` package. Fusion-Task-Id: FN-4189
This commit is contained in:
5
.changeset/fn-4189-chat-entry-grows.md
Normal file
5
.changeset/fn-4189-chat-entry-grows.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Raise the dashboard chat composer autosize cap so longer drafts stay readable before scrolling.
|
||||||
@@ -1302,8 +1302,11 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
max-height: 120px;
|
max-height: 320px;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
|
/* Mobile keeps the desktop cap because the surrounding chat-input-area
|
||||||
|
and thread flex bounds already prevent the composer from overtaking
|
||||||
|
the message pane before textarea scrolling engages. */
|
||||||
/* touch-action: manipulation allows tap-to-focus (so iOS doesn't
|
/* touch-action: manipulation allows tap-to-focus (so iOS doesn't
|
||||||
auto-dismiss the keyboard between gesture frames) and blocks
|
auto-dismiss the keyboard between gesture frames) and blocks
|
||||||
double-tap zoom + pan/zoom gestures. We previously tried `none`
|
double-tap zoom + pan/zoom gestures. We previously tried `none`
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ export interface ChatViewProps {
|
|||||||
experimentalFeatures?: Record<string, boolean>;
|
experimentalFeatures?: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CHAT_INPUT_MAX_HEIGHT_PX = 320;
|
||||||
|
|
||||||
|
export function clampChatInputHeight(scrollHeight: number): number {
|
||||||
|
return Math.min(scrollHeight, CHAT_INPUT_MAX_HEIGHT_PX);
|
||||||
|
}
|
||||||
|
|
||||||
function formatRelativeTime(dateStr: string): string {
|
function formatRelativeTime(dateStr: string): string {
|
||||||
const date = new Date(dateStr);
|
const date = new Date(dateStr);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -1526,7 +1532,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
|||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
if (!inputRef.current) return;
|
if (!inputRef.current) return;
|
||||||
inputRef.current.style.height = "auto";
|
inputRef.current.style.height = "auto";
|
||||||
inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`;
|
inputRef.current.style.height = `${clampChatInputHeight(inputRef.current.scrollHeight)}px`;
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1565,7 +1571,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
|||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
if (!inputRef.current) return;
|
if (!inputRef.current) return;
|
||||||
inputRef.current.style.height = "auto";
|
inputRef.current.style.height = "auto";
|
||||||
inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`;
|
inputRef.current.style.height = `${clampChatInputHeight(inputRef.current.scrollHeight)}px`;
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
inputRef.current.setSelectionRange(nextCursorPos, nextCursorPos);
|
inputRef.current.setSelectionRange(nextCursorPos, nextCursorPos);
|
||||||
});
|
});
|
||||||
@@ -1725,7 +1731,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
|||||||
}
|
}
|
||||||
|
|
||||||
textarea.style.height = "auto";
|
textarea.style.height = "auto";
|
||||||
textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
|
textarea.style.height = `${clampChatInputHeight(textarea.scrollHeight)}px`;
|
||||||
}, [updateMentionState]);
|
}, [updateMentionState]);
|
||||||
|
|
||||||
const handleInputSelectionChange = useCallback(
|
const handleInputSelectionChange = useCallback(
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { clampChatInputHeight } from "../ChatView";
|
||||||
|
|
||||||
|
const chatViewCss = readFileSync(resolve(__dirname, "../ChatView.css"), "utf8");
|
||||||
|
|
||||||
|
describe("ChatView chat input autosize", () => {
|
||||||
|
it("keeps the textarea CSS max-height aligned with the raised autosize cap", () => {
|
||||||
|
const textareaRule = chatViewCss.match(/\.chat-input-textarea\s*\{[^}]*\}/);
|
||||||
|
|
||||||
|
expect(textareaRule).not.toBeNull();
|
||||||
|
expect(textareaRule?.[0]).toContain("max-height: 320px");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("clamps oversized textarea growth to the new max height", () => {
|
||||||
|
expect(clampChatInputHeight(600)).toBe(320);
|
||||||
|
expect(clampChatInputHeight(600)).not.toBe(120);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves smaller textarea heights below the cap", () => {
|
||||||
|
expect(clampChatInputHeight(80)).toBe(80);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user