Animated placeholder with Prompt suggestion for home chat input (#1706)
<!-- This is an auto-generated description by cubic. --> ## Summary by cubic Adds an animated placeholder with rotating prompt suggestions to the home chat input to guide users. Also introduces a small hook for the typing effect and updates the e2e selector to handle dynamic text. - **New Features** - HomeChatInput shows "Ask Dyad to build" with cycling suggestions (ecommerce store, information page, landing page). - New useTypingPlaceholder hook for type/delete animation with configurable speeds and pause. - E2E test locator updated to match aria-placeholder by prefix to support dynamic content. <sup>Written for commit 6b8133ec9441f60909493cad3b43315348aed2d5. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
committed by
GitHub
parent
fadfbc06be
commit
dd14e67d48
@@ -713,7 +713,7 @@ export class PageObject {
|
|||||||
|
|
||||||
getChatInput() {
|
getChatInput() {
|
||||||
return this.page.locator(
|
return this.page.locator(
|
||||||
'[data-lexical-editor="true"][aria-placeholder="Ask Dyad to build..."]',
|
'[data-lexical-editor="true"][aria-placeholder^="Ask Dyad to build"]',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { HomeSubmitOptions } from "@/pages/home";
|
|||||||
import { ChatInputControls } from "../ChatInputControls";
|
import { ChatInputControls } from "../ChatInputControls";
|
||||||
import { LexicalChatInput } from "./LexicalChatInput";
|
import { LexicalChatInput } from "./LexicalChatInput";
|
||||||
import { useChatModeToggle } from "@/hooks/useChatModeToggle";
|
import { useChatModeToggle } from "@/hooks/useChatModeToggle";
|
||||||
|
import { useTypingPlaceholder } from "@/hooks/useTypingPlaceholder";
|
||||||
export function HomeChatInput({
|
export function HomeChatInput({
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: {
|
}: {
|
||||||
@@ -26,6 +27,13 @@ export function HomeChatInput({
|
|||||||
}); // eslint-disable-line @typescript-eslint/no-unused-vars
|
}); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||||
useChatModeToggle();
|
useChatModeToggle();
|
||||||
|
|
||||||
|
const typingText = useTypingPlaceholder([
|
||||||
|
"an ecommerce store...",
|
||||||
|
"an information page...",
|
||||||
|
"a landing page...",
|
||||||
|
]);
|
||||||
|
const placeholder = `Ask Dyad to build ${typingText ?? ""}`;
|
||||||
|
|
||||||
// Use the attachments hook
|
// Use the attachments hook
|
||||||
const {
|
const {
|
||||||
attachments,
|
attachments,
|
||||||
@@ -83,7 +91,7 @@ export function HomeChatInput({
|
|||||||
onChange={setInputValue}
|
onChange={setInputValue}
|
||||||
onSubmit={handleCustomSubmit}
|
onSubmit={handleCustomSubmit}
|
||||||
onPaste={handlePaste}
|
onPaste={handlePaste}
|
||||||
placeholder="Ask Dyad to build..."
|
placeholder={placeholder}
|
||||||
disabled={isStreaming}
|
disabled={isStreaming}
|
||||||
excludeCurrentApp={false}
|
excludeCurrentApp={false}
|
||||||
disableSendButton={false}
|
disableSendButton={false}
|
||||||
|
|||||||
48
src/hooks/useTypingPlaceholder.ts
Normal file
48
src/hooks/useTypingPlaceholder.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export function useTypingPlaceholder(
|
||||||
|
phrases: string[],
|
||||||
|
typingSpeed = 100,
|
||||||
|
deletingSpeed = 50,
|
||||||
|
pauseTime = 1500,
|
||||||
|
) {
|
||||||
|
const [text, setText] = useState("");
|
||||||
|
const [index, setIndex] = useState(0);
|
||||||
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
const [charIndex, setCharIndex] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const current = phrases[index];
|
||||||
|
const speed = deleting ? deletingSpeed : typingSpeed;
|
||||||
|
let pauseTimer: NodeJS.Timeout;
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (!deleting && charIndex < current.length) {
|
||||||
|
setText((prev) => prev + current.charAt(charIndex));
|
||||||
|
setCharIndex((prev) => prev + 1);
|
||||||
|
} else if (deleting && charIndex > 0) {
|
||||||
|
setText((prev) => prev.slice(0, -1));
|
||||||
|
setCharIndex((prev) => prev - 1);
|
||||||
|
} else if (!deleting && charIndex === current.length) {
|
||||||
|
pauseTimer = setTimeout(() => setDeleting(true), pauseTime);
|
||||||
|
} else if (deleting && charIndex === 0) {
|
||||||
|
setDeleting(false);
|
||||||
|
setIndex((prev) => (prev + 1) % phrases.length);
|
||||||
|
}
|
||||||
|
}, speed);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
clearTimeout(pauseTimer);
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
phrases,
|
||||||
|
index,
|
||||||
|
deleting,
|
||||||
|
charIndex,
|
||||||
|
typingSpeed,
|
||||||
|
deletingSpeed,
|
||||||
|
pauseTime,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user