feat(ui): add clear action to prompt input

This commit is contained in:
Shantur Rathore
2026-03-26 23:10:02 +00:00
parent 0dc5867fb3
commit b7d4f8f869
9 changed files with 98 additions and 45 deletions

View File

@@ -1,5 +1,5 @@
import { Suspense, createEffect, createSignal, lazy, on, onCleanup, Show } from "solid-js" import { Suspense, createEffect, createSignal, lazy, on, onCleanup, Show } from "solid-js"
import { ArrowBigUp, ArrowBigDown, Loader2, Mic } from "lucide-solid" import { ArrowBigUp, ArrowBigDown, Loader2, Mic, X } from "lucide-solid"
import ExpandButton from "./expand-button" import ExpandButton from "./expand-button"
import { clearAttachments, removeAttachment } from "../stores/attachments" import { clearAttachments, removeAttachment } from "../stores/attachments"
import { resolvePastedPlaceholders } from "../lib/prompt-placeholders" import { resolvePastedPlaceholders } from "../lib/prompt-placeholders"
@@ -351,6 +351,19 @@ export default function PromptInput(props: PromptInputProps) {
textareaRef?.focus() textareaRef?.focus()
} }
function handleClearPrompt() {
clearPrompt()
clearHistoryDraft()
resetHistoryNavigation()
setShowPicker(false)
setPickerMode("mention")
setAtPosition(null)
setSearchQuery("")
setIgnoredAtPositions(new Set<number>())
syncAttachmentCounters("")
textareaRef?.focus()
}
function insertBlockContent(block: string) { function insertBlockContent(block: string) {
const textarea = textareaRef const textarea = textareaRef
const current = prompt() const current = prompt()
@@ -422,6 +435,8 @@ export default function PromptInput(props: PromptInputProps) {
return hasText || attachments().length > 0 return hasText || attachments().length > 0
} }
const canClearPrompt = () => prompt().length > 0
const shellHint = () => const shellHint = () =>
mode() === "shell" mode() === "shell"
? { key: "Esc", text: t("promptInput.hints.shell.exit") } ? { key: "Esc", text: t("promptInput.hints.shell.exit") }
@@ -543,7 +558,7 @@ export default function PromptInput(props: PromptInputProps) {
autocomplete="off" autocomplete="off"
/> />
<div class="prompt-nav-buttons"> <div class="prompt-nav-buttons">
<div class="prompt-nav-top-row"> <div class="prompt-nav-column prompt-nav-column-left">
<Show when={showVoiceInput()}> <Show when={showVoiceInput()}>
<button <button
type="button" type="button"
@@ -586,43 +601,55 @@ export default function PromptInput(props: PromptInputProps) {
</Show> </Show>
</button> </button>
</Show> </Show>
<button
type="button"
class="prompt-clear-button"
onClick={handleClearPrompt}
disabled={!canClearPrompt()}
aria-label={t("promptInput.clear.ariaLabel")}
title={t("promptInput.clear.title")}
>
<X class="h-4 w-4" aria-hidden="true" />
</button>
</div>
<div class="prompt-nav-column prompt-nav-column-right">
<ExpandButton <ExpandButton
expandState={expandState} expandState={expandState}
onToggleExpand={handleExpandToggle} onToggleExpand={handleExpandToggle}
/> />
<Show when={hasHistory()}>
<button
type="button"
class="prompt-history-button"
onClick={() =>
selectPreviousHistory({
force: true,
isPickerOpen: showPicker(),
getTextarea: () => textareaRef,
})
}
disabled={!canHistoryGoPrevious()}
aria-label={t("promptInput.history.previousAriaLabel")}
>
<ArrowBigUp class="h-5 w-5" aria-hidden="true" />
</button>
<button
type="button"
class="prompt-history-button"
onClick={() =>
selectNextHistory({
force: true,
isPickerOpen: showPicker(),
getTextarea: () => textareaRef,
})
}
disabled={!canHistoryGoNext()}
aria-label={t("promptInput.history.nextAriaLabel")}
>
<ArrowBigDown class="h-5 w-5" aria-hidden="true" />
</button>
</Show>
</div> </div>
<Show when={hasHistory()}>
<button
type="button"
class="prompt-history-button"
onClick={() =>
selectPreviousHistory({
force: true,
isPickerOpen: showPicker(),
getTextarea: () => textareaRef,
})
}
disabled={!canHistoryGoPrevious()}
aria-label={t("promptInput.history.previousAriaLabel")}
>
<ArrowBigUp class="h-5 w-5" aria-hidden="true" />
</button>
<button
type="button"
class="prompt-history-button"
onClick={() =>
selectNextHistory({
force: true,
isPickerOpen: showPicker(),
getTextarea: () => textareaRef,
})
}
disabled={!canHistoryGoNext()}
aria-label={t("promptInput.history.nextAriaLabel")}
>
<ArrowBigDown class="h-5 w-5" aria-hidden="true" />
</button>
</Show>
</div> </div>
<Show when={shouldShowOverlay()}> <Show when={shouldShowOverlay()}>
<div class={`prompt-input-overlay keyboard-hints ${mode() === "shell" ? "shell-mode" : ""}`}> <div class={`prompt-input-overlay keyboard-hints ${mode() === "shell" ? "shell-mode" : ""}`}>

View File

@@ -142,6 +142,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "again to abort session", "promptInput.overlay.againToAbort": "again to abort session",
"promptInput.stopSession.ariaLabel": "Stop session", "promptInput.stopSession.ariaLabel": "Stop session",
"promptInput.stopSession.title": "Stop session", "promptInput.stopSession.title": "Stop session",
"promptInput.clear.ariaLabel": "Clear prompt text",
"promptInput.clear.title": "Clear prompt text",
"promptInput.send.ariaLabel": "Send message", "promptInput.send.ariaLabel": "Send message",
"promptInput.send.errorFallback": "Failed to send message", "promptInput.send.errorFallback": "Failed to send message",
"promptInput.send.errorTitle": "Send failed", "promptInput.send.errorTitle": "Send failed",

View File

@@ -144,6 +144,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "otra vez para abortar la sesión", "promptInput.overlay.againToAbort": "otra vez para abortar la sesión",
"promptInput.stopSession.ariaLabel": "Detener sesión", "promptInput.stopSession.ariaLabel": "Detener sesión",
"promptInput.stopSession.title": "Detener sesión", "promptInput.stopSession.title": "Detener sesión",
"promptInput.clear.ariaLabel": "Borrar el texto del prompt",
"promptInput.clear.title": "Borrar el texto del prompt",
"promptInput.send.ariaLabel": "Enviar mensaje", "promptInput.send.ariaLabel": "Enviar mensaje",
"promptInput.send.errorFallback": "No se pudo enviar el mensaje", "promptInput.send.errorFallback": "No se pudo enviar el mensaje",
"promptInput.send.errorTitle": "Error al enviar", "promptInput.send.errorTitle": "Error al enviar",

View File

@@ -144,6 +144,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "à nouveau pour interrompre la session", "promptInput.overlay.againToAbort": "à nouveau pour interrompre la session",
"promptInput.stopSession.ariaLabel": "Arrêter la session", "promptInput.stopSession.ariaLabel": "Arrêter la session",
"promptInput.stopSession.title": "Arrêter la session", "promptInput.stopSession.title": "Arrêter la session",
"promptInput.clear.ariaLabel": "Effacer le texte du prompt",
"promptInput.clear.title": "Effacer le texte du prompt",
"promptInput.send.ariaLabel": "Envoyer le message", "promptInput.send.ariaLabel": "Envoyer le message",
"promptInput.send.errorFallback": "Impossible d'envoyer le message", "promptInput.send.errorFallback": "Impossible d'envoyer le message",
"promptInput.send.errorTitle": "Échec de l'envoi", "promptInput.send.errorTitle": "Échec de l'envoi",

View File

@@ -142,6 +142,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "שוב כדי לבטל את הסשן", "promptInput.overlay.againToAbort": "שוב כדי לבטל את הסשן",
"promptInput.stopSession.ariaLabel": "עצור סשן", "promptInput.stopSession.ariaLabel": "עצור סשן",
"promptInput.stopSession.title": "עצור סשן", "promptInput.stopSession.title": "עצור סשן",
"promptInput.clear.ariaLabel": "נקה את טקסט הפרומפט",
"promptInput.clear.title": "נקה את טקסט הפרומפט",
"promptInput.send.ariaLabel": "שלח הודעה", "promptInput.send.ariaLabel": "שלח הודעה",
"promptInput.send.errorFallback": "שליחת ההודעה נכשלה", "promptInput.send.errorFallback": "שליחת ההודעה נכשלה",
"promptInput.send.errorTitle": "השליחה נכשלה", "promptInput.send.errorTitle": "השליחה נכשלה",

View File

@@ -144,6 +144,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "もう一度押すとセッションを中断", "promptInput.overlay.againToAbort": "もう一度押すとセッションを中断",
"promptInput.stopSession.ariaLabel": "セッションを停止", "promptInput.stopSession.ariaLabel": "セッションを停止",
"promptInput.stopSession.title": "セッションを停止", "promptInput.stopSession.title": "セッションを停止",
"promptInput.clear.ariaLabel": "プロンプトのテキストをクリア",
"promptInput.clear.title": "プロンプトのテキストをクリア",
"promptInput.send.ariaLabel": "メッセージを送信", "promptInput.send.ariaLabel": "メッセージを送信",
"promptInput.send.errorFallback": "メッセージの送信に失敗しました", "promptInput.send.errorFallback": "メッセージの送信に失敗しました",
"promptInput.send.errorTitle": "送信に失敗", "promptInput.send.errorTitle": "送信に失敗",

View File

@@ -144,6 +144,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "еще раз, чтобы прервать сессию", "promptInput.overlay.againToAbort": "еще раз, чтобы прервать сессию",
"promptInput.stopSession.ariaLabel": "Остановить сессию", "promptInput.stopSession.ariaLabel": "Остановить сессию",
"promptInput.stopSession.title": "Остановить сессию", "promptInput.stopSession.title": "Остановить сессию",
"promptInput.clear.ariaLabel": "Очистить текст prompt",
"promptInput.clear.title": "Очистить текст prompt",
"promptInput.send.ariaLabel": "Отправить сообщение", "promptInput.send.ariaLabel": "Отправить сообщение",
"promptInput.send.errorFallback": "Не удалось отправить сообщение", "promptInput.send.errorFallback": "Не удалось отправить сообщение",
"promptInput.send.errorTitle": "Не удалось отправить", "promptInput.send.errorTitle": "Не удалось отправить",

View File

@@ -144,6 +144,8 @@ export const messagingMessages = {
"promptInput.overlay.againToAbort": "再次按下以中止会话", "promptInput.overlay.againToAbort": "再次按下以中止会话",
"promptInput.stopSession.ariaLabel": "停止会话", "promptInput.stopSession.ariaLabel": "停止会话",
"promptInput.stopSession.title": "停止会话", "promptInput.stopSession.title": "停止会话",
"promptInput.clear.ariaLabel": "清除输入框文本",
"promptInput.clear.title": "清除输入框文本",
"promptInput.send.ariaLabel": "发送消息", "promptInput.send.ariaLabel": "发送消息",
"promptInput.send.errorFallback": "发送消息失败", "promptInput.send.errorFallback": "发送消息失败",
"promptInput.send.errorTitle": "发送失败", "promptInput.send.errorTitle": "发送失败",

View File

@@ -37,7 +37,7 @@
.prompt-input { .prompt-input {
@apply w-full pt-2.5 border text-sm resize-none outline-none transition-colors; @apply w-full pt-2.5 border text-sm resize-none outline-none transition-colors;
padding-inline-start: 0.75rem; padding-inline-start: 0.75rem;
padding-inline-end: 5.5rem; padding-inline-end: 7.5rem;
font-family: inherit; font-family: inherit;
background-color: var(--surface-base); background-color: var(--surface-base);
color: var(--text-primary); color: var(--text-primary);
@@ -90,22 +90,32 @@
inset-inline-end: 0.25rem; inset-inline-end: 0.25rem;
bottom: 0.25rem; bottom: 0.25rem;
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: flex-end; align-items: flex-start;
justify-content: flex-start; justify-content: flex-end;
gap: 0.125rem; gap: 0.125rem;
z-index: 2; z-index: 2;
} }
.prompt-nav-top-row { .prompt-nav-column {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: flex-end; align-items: flex-end;
justify-content: flex-start;
gap: 0.125rem; gap: 0.125rem;
} }
.prompt-nav-column-left {
min-width: 1.75rem;
}
.prompt-nav-column-right {
min-width: 1.75rem;
}
.prompt-expand-button, .prompt-expand-button,
.prompt-history-button { .prompt-history-button,
.prompt-clear-button {
@apply w-7 h-7 flex items-center justify-center rounded-md; @apply w-7 h-7 flex items-center justify-center rounded-md;
color: var(--text-muted); color: var(--text-muted);
background-color: var(--control-ghost-bg); background-color: var(--control-ghost-bg);
@@ -115,7 +125,8 @@
} }
.prompt-expand-button:hover:not(:disabled), .prompt-expand-button:hover:not(:disabled),
.prompt-history-button:hover:not(:disabled) { .prompt-history-button:hover:not(:disabled),
.prompt-clear-button:hover:not(:disabled) {
background-color: var(--surface-secondary); background-color: var(--surface-secondary);
color: var(--text-primary); color: var(--text-primary);
} }
@@ -127,7 +138,8 @@
} }
.prompt-expand-button:disabled, .prompt-expand-button:disabled,
.prompt-history-button:disabled { .prompt-history-button:disabled,
.prompt-clear-button:disabled {
opacity: 0.4; opacity: 0.4;
cursor: not-allowed; cursor: not-allowed;
} }
@@ -397,7 +409,7 @@
.prompt-input { .prompt-input {
min-height: 0; min-height: 0;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
padding-inline-end: 5.5rem; padding-inline-end: 7.5rem;
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }