diff --git a/src/components/ChatInput.tsx b/src/components/ChatInput.tsx index 658f7f37d..818644766 100644 --- a/src/components/ChatInput.tsx +++ b/src/components/ChatInput.tsx @@ -58,6 +58,44 @@ const InputControls = styled.div` align-items: flex-end; `; +const AttachButton = styled.button` + width: 32px; + height: 32px; + flex-shrink: 0; + border-radius: 4px; + background: #3e3e42; + color: #cccccc; + border: none; + cursor: pointer; + 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; + } + + &:active { + background: #5a5a5a; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +`; + +const HiddenFileInput = styled.input` + display: none; +`; + // Input now rendered by VimTextArea; styles moved there const ModeToggles = styled.div` @@ -407,6 +445,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 +893,21 @@ export const ChatInput: React.FC = ({ } aria-expanded={showCommandSuggestions && commandSuggestions.length > 0} /> + + + 📎 +