From d87e80a2b5f42565bfe148e1c790845a35165196 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 21 Oct 2025 13:47:43 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=A4=96=20Add=20image=20attachment=20b?= =?UTF-8?q?utton=20for=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add attach button (📎) positioned in bottom-right of input area - Hidden file input with accept='image/*' and multiple selection - Clicking button opens native file picker - Reuses existing processImageFiles logic for consistent handling - Button disabled when chat input is disabled --- src/components/ChatInput.tsx | 73 ++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 658f7f37d..0dbea0834 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -56,6 +56,42 @@ const InputControls = styled.div` display: flex; gap: 10px; align-items: flex-end; + position: relative; +`; + +const AttachButton = styled.button` + position: absolute; + right: 8px; + bottom: 8px; + width: 32px; + height: 32px; + border-radius: 4px; + background: #3e3e42; + color: #cccccc; + border: none; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + transition: background 0.2s; + + &:hover { + background: #505050; + } + + &:active { + background: #5a5a5a; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +`; + +const HiddenFileInput = styled.input` + display: none; `; // Input now rendered by VimTextArea; styles moved there @@ -407,6 +443,28 @@ export const ChatInput: React.FC = ({ setImageAttachments((prev) => prev.filter((img) => img.id !== id)); }, []); + // Handle file selection from file input (for mobile) + const fileInputRef = useRef(null); + + const handleFileSelect = useCallback((e: React.ChangeEvent) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + const fileArray = Array.from(files); + void processImageFiles(fileArray).then((attachments) => { + setImageAttachments((prev) => [...prev, ...attachments]); + }); + + // Reset input so same file can be selected again + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }, []); + + const handleAttachClick = useCallback(() => { + fileInputRef.current?.click(); + }, []); + // Handle drag over to allow drop const handleDragOver = useCallback((e: React.DragEvent) => { // Check if drag contains files @@ -833,6 +891,21 @@ export const ChatInput: React.FC = ({ } aria-expanded={showCommandSuggestions && commandSuggestions.length > 0} /> + + + 📎 + From c84ad00d4bd1f954465549c2d66385e53185f78f Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 21 Oct 2025 13:49:53 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A4=96=20Show=20attach=20button=20onl?= =?UTF-8?q?y=20on=20mobile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hide the attach button on desktop (>768px width) since desktop users can paste or drag-and-drop images. Mobile users need the button to access the file picker. --- src/components/ChatInput.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 0dbea0834..77b4c844f 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -70,12 +70,17 @@ const AttachButton = styled.button` color: #cccccc; border: none; cursor: pointer; - display: flex; + display: none; align-items: center; justify-content: center; font-size: 18px; transition: background 0.2s; + /* Show only on mobile (viewport width <= 768px) */ + @media (max-width: 768px) { + display: flex; + } + &:hover { background: #505050; } From 0e27375d4049846a9f3f4b90fad994a10f3065b3 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 21 Oct 2025 13:52:04 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=A4=96=20Fix=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ChatInput.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 77b4c844f..3637b6e91 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -450,7 +450,7 @@ export const ChatInput: React.FC = ({ // Handle file selection from file input (for mobile) const fileInputRef = useRef(null); - + const handleFileSelect = useCallback((e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; From 4b61a8551a891aeee94820bb3a7b73b4f7c7c215 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 21 Oct 2025 13:52:47 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A4=96=20Fix=20attach=20button=20layo?= =?UTF-8?q?ut=20to=20avoid=20overlapping=20textarea?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove absolute positioning from attach button - Make it a flex sibling of VimTextArea instead - Add flex-shrink: 0 to maintain button size - Button now sits beside the input instead of overlaying it --- src/components/ChatInput.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 3637b6e91..818644766 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -56,15 +56,12 @@ const InputControls = styled.div` display: flex; gap: 10px; align-items: flex-end; - position: relative; `; const AttachButton = styled.button` - position: absolute; - right: 8px; - bottom: 8px; width: 32px; height: 32px; + flex-shrink: 0; border-radius: 4px; background: #3e3e42; color: #cccccc;