Fix ChatTextArea layout

This commit is contained in:
Matt Rubens
2025-01-14 23:45:43 -05:00
parent b75e105fab
commit 84a0063b99
3 changed files with 306 additions and 261 deletions

View File

@@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Fix chat text input layout issues

View File

@@ -15,6 +15,7 @@ import Thumbnails from "../common/Thumbnails"
import { vscode } from "../../utils/vscode" import { vscode } from "../../utils/vscode"
import { WebviewMessage } from "../../../../src/shared/WebviewMessage" import { WebviewMessage } from "../../../../src/shared/WebviewMessage"
import { Mode } from "../../../../src/core/prompts/types" import { Mode } from "../../../../src/core/prompts/types"
import { CaretIcon } from "../common/CaretIcon"
interface ChatTextAreaProps { interface ChatTextAreaProps {
inputValue: string inputValue: string
@@ -50,7 +51,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
ref, ref,
) => { ) => {
const { filePaths, currentApiConfigName, listApiConfigMeta } = useExtensionState() const { filePaths, currentApiConfigName, listApiConfigMeta } = useExtensionState()
const [isTextAreaFocused, setIsTextAreaFocused] = useState(false)
const [gitCommits, setGitCommits] = useState<any[]>([]) const [gitCommits, setGitCommits] = useState<any[]>([])
const [showDropdown, setShowDropdown] = useState(false) const [showDropdown, setShowDropdown] = useState(false)
@@ -376,7 +376,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
if (!isMouseDownOnMenu) { if (!isMouseDownOnMenu) {
setShowContextMenu(false) setShowContextMenu(false)
} }
setIsTextAreaFocused(false)
}, [isMouseDownOnMenu]) }, [isMouseDownOnMenu])
const handlePaste = useCallback( const handlePaste = useCallback(
@@ -494,12 +493,43 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
[updateCursorPosition], [updateCursorPosition],
) )
const selectStyle = {
fontSize: "11px",
cursor: textAreaDisabled ? "not-allowed" : "pointer",
backgroundColor: "transparent",
border: "none",
color: "var(--vscode-foreground)",
opacity: textAreaDisabled ? 0.5 : 0.8,
outline: "none",
paddingLeft: "20px",
paddingRight: "6px",
WebkitAppearance: "none" as const,
MozAppearance: "none" as const,
appearance: "none" as const
}
const caretContainerStyle = {
position: "absolute" as const,
left: 6,
top: "50%",
transform: "translateY(-45%)",
pointerEvents: "none" as const,
opacity: textAreaDisabled ? 0.5 : 0.8
}
return ( return (
<div style={{ <div
padding: "10px 15px", className="chat-text-area"
style={{
opacity: textAreaDisabled ? 0.5 : 1, opacity: textAreaDisabled ? 0.5 : 1,
position: "relative", position: "relative",
display: "flex", display: "flex",
flexDirection: "column",
gap: "8px",
backgroundColor: "var(--vscode-input-background)",
minHeight: "100px",
margin: "10px 15px",
padding: "8px"
}} }}
onDrop={async (e) => { onDrop={async (e) => {
e.preventDefault() e.preventDefault()
@@ -552,7 +582,8 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}} }}
onDragOver={(e) => { onDragOver={(e) => {
e.preventDefault() e.preventDefault()
}}> }}
>
{showContextMenu && ( {showContextMenu && (
<div ref={contextMenuContainerRef}> <div ref={contextMenuContainerRef}>
<ContextMenu <ContextMenu
@@ -566,42 +597,31 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
/> />
</div> </div>
)} )}
{!isTextAreaFocused && (
<div <div style={{
style={{ position: "relative",
position: "absolute", flex: "1 1 auto",
inset: "10px 15px", display: "flex",
border: "1px solid var(--vscode-input-border)", flexDirection: "column-reverse",
borderRadius: 2, minHeight: 0,
pointerEvents: "none", overflow: "hidden"
zIndex: 5, }}>
}}
/>
)}
<div <div
ref={highlightLayerRef} ref={highlightLayerRef}
style={{ style={{
position: "absolute", position: "absolute",
top: 10, inset: 0,
left: 15,
right: 15,
bottom: 10,
pointerEvents: "none", pointerEvents: "none",
whiteSpace: "pre-wrap", whiteSpace: "pre-wrap",
wordWrap: "break-word", wordWrap: "break-word",
color: "transparent", color: "transparent",
overflow: "hidden", overflow: "hidden",
backgroundColor: "var(--vscode-input-background)",
fontFamily: "var(--vscode-font-family)", fontFamily: "var(--vscode-font-family)",
fontSize: "var(--vscode-editor-font-size)", fontSize: "var(--vscode-editor-font-size)",
lineHeight: "var(--vscode-editor-line-height)", lineHeight: "var(--vscode-editor-line-height)",
borderRadius: 2, padding: "8px",
borderLeft: 0, marginBottom: thumbnailsHeight > 0 ? `${thumbnailsHeight + 16}px` : 0,
borderRight: 0, zIndex: 1
borderTop: 0,
borderColor: "transparent",
borderBottom: `${thumbnailsHeight + 6}px solid transparent`,
padding: "9px 9px 25px 9px",
}} }}
/> />
<DynamicTextArea <DynamicTextArea
@@ -621,7 +641,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
}} }}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp} onKeyUp={handleKeyUp}
onFocus={() => setIsTextAreaFocused(true)}
onBlur={handleBlur} onBlur={handleBlur}
onPaste={handlePaste} onPaste={handlePaste}
onSelect={updateCursorPosition} onSelect={updateCursorPosition}
@@ -633,7 +652,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onHeightChange?.(height) onHeightChange?.(height)
}} }}
placeholder={placeholderText} placeholder={placeholderText}
minRows={2} minRows={4}
maxRows={20} maxRows={20}
autoFocus={true} autoFocus={true}
style={{ style={{
@@ -647,20 +666,18 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
lineHeight: "var(--vscode-editor-line-height)", lineHeight: "var(--vscode-editor-line-height)",
resize: "none", resize: "none",
overflowX: "hidden", overflowX: "hidden",
overflowY: "scroll", overflowY: "auto",
borderLeft: 0, border: "none",
borderRight: 0, padding: "8px",
borderTop: 0, marginBottom: thumbnailsHeight > 0 ? `${thumbnailsHeight + 16}px` : 0,
borderBottom: `${thumbnailsHeight + 6}px solid transparent`,
borderColor: "transparent",
padding: "9px 9px 25px 9px",
marginBottom: "15px",
cursor: textAreaDisabled ? "not-allowed" : undefined, cursor: textAreaDisabled ? "not-allowed" : undefined,
flex: 1, flex: "0 1 auto",
zIndex: 1, zIndex: 2
}} }}
onScroll={() => updateHighlights()} onScroll={() => updateHighlights()}
/> />
</div>
{selectedImages.length > 0 && ( {selectedImages.length > 0 && (
<Thumbnails <Thumbnails
images={selectedImages} images={selectedImages}
@@ -668,53 +685,43 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
onHeightChange={handleThumbnailsHeightChange} onHeightChange={handleThumbnailsHeightChange}
style={{ style={{
position: "absolute", position: "absolute",
paddingTop: 4, bottom: "36px",
bottom: 36, left: "16px",
left: 22,
right: 67,
zIndex: 2, zIndex: 2,
marginBottom: "8px"
}} }}
/> />
)} )}
<div
style={{ <div style={{
position: "absolute", display: "flex",
left: 25, justifyContent: "space-between",
bottom: 20, alignItems: "center",
zIndex: 3, marginTop: "auto",
paddingTop: "8px"
}}>
<div style={{
display: "flex", display: "flex",
gap: 8,
alignItems: "center" alignItems: "center"
}} }}>
> <div style={{ position: "relative", display: "inline-block" }}>
<select <select
value={mode} value={mode}
disabled={textAreaDisabled} disabled={textAreaDisabled}
onChange={(e) => { onChange={(e) => {
const newMode = e.target.value as Mode; const newMode = e.target.value as Mode
setMode(newMode); setMode(newMode)
vscode.postMessage({ vscode.postMessage({
type: "mode", type: "mode",
text: newMode text: newMode
}); })
}} }}
style={{ style={{
fontSize: "11px", ...selectStyle,
cursor: textAreaDisabled ? "not-allowed" : "pointer", minWidth: "70px",
backgroundColor: "transparent", flex: "0 0 auto"
border: "none", }}
color: "var(--vscode-input-foreground)", >
opacity: textAreaDisabled ? 0.5 : 0.6,
outline: "none",
paddingLeft: 14,
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e\")",
backgroundRepeat: "no-repeat",
backgroundPosition: "left 0px center",
backgroundSize: "10px"
}}>
<option value="code" style={{ <option value="code" style={{
backgroundColor: "var(--vscode-dropdown-background)", backgroundColor: "var(--vscode-dropdown-background)",
color: "var(--vscode-dropdown-foreground)" color: "var(--vscode-dropdown-foreground)"
@@ -728,6 +735,19 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
color: "var(--vscode-dropdown-foreground)" color: "var(--vscode-dropdown-foreground)"
}}>Ask</option> }}>Ask</option>
</select> </select>
<div style={caretContainerStyle}>
<CaretIcon />
</div>
</div>
<div style={{
position: "relative",
display: "inline-block",
flex: "1 1 auto",
minWidth: 0,
maxWidth: "150px",
overflow: "hidden"
}}>
<select <select
value={currentApiConfigName} value={currentApiConfigName}
disabled={textAreaDisabled} disabled={textAreaDisabled}
@@ -736,21 +756,9 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
text: e.target.value text: e.target.value
})} })}
style={{ style={{
fontSize: "11px", ...selectStyle,
cursor: textAreaDisabled ? "not-allowed" : "pointer", width: "100%",
backgroundColor: "transparent", textOverflow: "ellipsis"
border: "none",
color: "var(--vscode-input-foreground)",
opacity: textAreaDisabled ? 0.5 : 0.6,
outline: "none",
paddingLeft: 14,
WebkitAppearance: "none",
MozAppearance: "none",
appearance: "none",
backgroundImage: "url(\"data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='rgba(255,255,255,0.5)' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e\")",
backgroundRepeat: "no-repeat",
backgroundPosition: "left 0px center",
backgroundSize: "10px"
}} }}
> >
{(listApiConfigMeta || [])?.map((config) => ( {(listApiConfigMeta || [])?.map((config) => (
@@ -766,9 +774,17 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
</option> </option>
))} ))}
</select> </select>
<div style={caretContainerStyle}>
<CaretIcon />
</div> </div>
<div className="button-row" style={{ position: "absolute", right: 16, display: "flex", alignItems: "center", height: 31, bottom: 11, zIndex: 3, padding: "0 8px", justifyContent: "flex-end", backgroundColor: "var(--vscode-input-background)", }}> </div>
<span style={{ display: "flex", alignItems: "center", gap: 12 }}> </div>
<div style={{
display: "flex",
alignItems: "center",
gap: "12px"
}}>
<div style={{ display: "flex", alignItems: "center" }}> <div style={{ display: "flex", alignItems: "center" }}>
{isEnhancingPrompt ? ( {isEnhancingPrompt ? (
<span className="codicon codicon-loading codicon-modifier-spin" style={{ <span className="codicon codicon-loading codicon-modifier-spin" style={{
@@ -776,7 +792,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
opacity: 0.5, opacity: 0.5,
fontSize: 16.5, fontSize: 16.5,
marginRight: 10 marginRight: 10
}}></span> }} />
) : ( ) : (
<span <span
role="button" role="button"
@@ -788,9 +804,17 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
/> />
)} )}
</div> </div>
<span className={`input-icon-button ${shouldDisableImages ? "disabled" : ""} codicon codicon-device-camera`} onClick={() => !shouldDisableImages && onSelectImages()} style={{ fontSize: 16.5 }} /> <span
<span className={`input-icon-button ${textAreaDisabled ? "disabled" : ""} codicon codicon-send`} onClick={() => !textAreaDisabled && onSend()} style={{ fontSize: 15 }} /> className={`input-icon-button ${shouldDisableImages ? "disabled" : ""} codicon codicon-device-camera`}
</span> onClick={() => !shouldDisableImages && onSelectImages()}
style={{ fontSize: 16.5 }}
/>
<span
className={`input-icon-button ${textAreaDisabled ? "disabled" : ""} codicon codicon-send`}
onClick={() => !textAreaDisabled && onSend()}
style={{ fontSize: 15 }}
/>
</div>
</div> </div>
</div> </div>
) )

View File

@@ -0,0 +1,16 @@
import React from 'react'
export const CaretIcon = () => (
<svg
width="10"
height="10"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="6 9 12 15 18 9" />
</svg>
)