Skip to content

Commit bbde9c6

Browse files
authored
🤖 Add image attachment button for mobile (#374)
1 parent 6de4c75 commit bbde9c6

File tree

1 file changed

+75
-0
lines changed

1 file changed

+75
-0
lines changed

src/components/ChatInput.tsx

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,44 @@ const InputControls = styled.div`
5858
align-items: flex-end;
5959
`;
6060

61+
const AttachButton = styled.button`
62+
width: 32px;
63+
height: 32px;
64+
flex-shrink: 0;
65+
border-radius: 4px;
66+
background: #3e3e42;
67+
color: #cccccc;
68+
border: none;
69+
cursor: pointer;
70+
display: none;
71+
align-items: center;
72+
justify-content: center;
73+
font-size: 18px;
74+
transition: background 0.2s;
75+
76+
/* Show only on mobile (viewport width <= 768px) */
77+
@media (max-width: 768px) {
78+
display: flex;
79+
}
80+
81+
&:hover {
82+
background: #505050;
83+
}
84+
85+
&:active {
86+
background: #5a5a5a;
87+
}
88+
89+
&:disabled {
90+
opacity: 0.5;
91+
cursor: not-allowed;
92+
}
93+
`;
94+
95+
const HiddenFileInput = styled.input`
96+
display: none;
97+
`;
98+
6199
// Input now rendered by VimTextArea; styles moved there
62100

63101
const ModeToggles = styled.div`
@@ -407,6 +445,28 @@ export const ChatInput: React.FC<ChatInputProps> = ({
407445
setImageAttachments((prev) => prev.filter((img) => img.id !== id));
408446
}, []);
409447

448+
// Handle file selection from file input (for mobile)
449+
const fileInputRef = useRef<HTMLInputElement>(null);
450+
451+
const handleFileSelect = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
452+
const files = e.target.files;
453+
if (!files || files.length === 0) return;
454+
455+
const fileArray = Array.from(files);
456+
void processImageFiles(fileArray).then((attachments) => {
457+
setImageAttachments((prev) => [...prev, ...attachments]);
458+
});
459+
460+
// Reset input so same file can be selected again
461+
if (fileInputRef.current) {
462+
fileInputRef.current.value = "";
463+
}
464+
}, []);
465+
466+
const handleAttachClick = useCallback(() => {
467+
fileInputRef.current?.click();
468+
}, []);
469+
410470
// Handle drag over to allow drop
411471
const handleDragOver = useCallback((e: React.DragEvent<HTMLTextAreaElement>) => {
412472
// Check if drag contains files
@@ -833,6 +893,21 @@ export const ChatInput: React.FC<ChatInputProps> = ({
833893
}
834894
aria-expanded={showCommandSuggestions && commandSuggestions.length > 0}
835895
/>
896+
<HiddenFileInput
897+
ref={fileInputRef}
898+
type="file"
899+
accept="image/*"
900+
multiple
901+
onChange={handleFileSelect}
902+
/>
903+
<AttachButton
904+
onClick={handleAttachClick}
905+
disabled={!editingMessage && (disabled || isSending || isCompacting)}
906+
title="Attach images"
907+
aria-label="Attach images"
908+
>
909+
📎
910+
</AttachButton>
836911
</InputControls>
837912
<ImageAttachments images={imageAttachments} onRemove={handleRemoveImage} />
838913
<ModeToggles data-component="ChatModeToggles">

0 commit comments

Comments
 (0)