Vitron-Front/app/components/thread/MessageInput.tsx
2026-04-29 01:44:16 +03:30

174 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ArrowRight, Loader2, Reply, SmilePlus, X } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { useRef, useState } from "react";
import EmojiPicker from "./EmojiPicker";
import type { MessageList } from "../../../src/api/types/models";
// Message input component
const MessageInput = ({
onSend,
replyToMessage,
onCancelReply,
messageText,
setMessageText,
isSendingMessage,
setReplyToMessage,
}: {
onSend: (text: string) => void;
replyToMessage: MessageList | undefined;
onCancelReply: () => void;
inputRef: React.RefObject<HTMLInputElement>;
messageText: string;
setMessageText: React.Dispatch<React.SetStateAction<string>>;
isSendingMessage: boolean;
setReplyToMessage: (message: MessageList | undefined) => void;
}) => {
const [caretPosition, setCaretPosition] = useState<number | null>(null);
// Track current textarea height
const handleSend = () => {
if (!messageText.trim() || isSendingMessage) return;
onSend(messageText.trim());
};
// Handle emoji selection
const handleSelectEmoji = (selectedEmoji: string) => {
if (textareaRef.current) {
// Get current cursor position
const cursorPosition =
caretPosition !== null
? caretPosition
: textareaRef.current.selectionStart;
// Insert emoji at cursor position
const newText =
messageText.substring(0, cursorPosition) +
selectedEmoji +
messageText.substring(cursorPosition);
setMessageText(newText);
// After state update, set cursor position after the inserted emoji
setTimeout(() => {
if (textareaRef.current) {
const newPosition = cursorPosition + selectedEmoji.length;
textareaRef.current.focus();
textareaRef.current.setSelectionRange(newPosition, newPosition);
}
}, 0);
} else {
// Fallback if ref not available
setMessageText((prev) => prev + selectedEmoji);
}
};
// Store caret position on selection change
const handleTextareaSelect = () => {
if (textareaRef.current) {
setCaretPosition(textareaRef.current.selectionStart);
}
};
// Auto-growing textarea ref
const textareaRef = useRef<HTMLTextAreaElement>(null);
// Handle text change and update height
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setMessageText(e.target.value);
};
return (
<>
{/* Reply indicator */}
{replyToMessage && (
<div className="max-w-md mx-auto bg-GRAY1 p-2 mb-2 rounded-lg flex items-center">
<div className="flex-1 text-GRAY overflow-hidden">
<p className="text-R12">در پاسخ به:</p>
<p className="text-R14 truncate">{replyToMessage.content}</p>
</div>
<button onClick={() => setReplyToMessage(undefined)} className="ml-2">
<X className="h-5 w-5 text-GRAY" />
</button>
</div>
)}
<div className="max-w-md mx-auto fixed px-2 bottom-0 py-2 w-full bg-WHITE">
{replyToMessage && (
<div className="bg-GRAY3 rounded-lg p-2 flex justify-between items-center mb-2">
<div className="flex items-center">
<Reply className="w-4 h-4 mr-2" />
<span className="text-sm truncate max-w-[250px]">
{replyToMessage.content}
</span>
</div>
<button
onClick={onCancelReply}
className="text-GRAY hover:text-BLACK2"
aria-label="حذف پاسخ"
>
<X className="w-4 h-4" />
</button>
</div>
)}
<div className="relative rounded-[12px] bg-WHITE3 flex items-end">
<Popover>
<PopoverTrigger asChild>
<button
className="absolute right-3 bottom-3 w-6 h-6 flex items-center justify-center"
aria-label="انتخاب ایموجی"
>
<SmilePlus className="text-GRAY hover:text-BLACK" size={20} />
</button>
</PopoverTrigger>
<PopoverContent
align="start"
side="top"
className="w-[280px] p-0"
sideOffset={5}
>
<EmojiPicker onSelectEmoji={handleSelectEmoji} />
</PopoverContent>
</Popover>
<textarea
ref={textareaRef}
value={messageText}
onChange={handleTextChange}
onSelect={handleTextareaSelect}
placeholder={
replyToMessage
? "پاسخ خود را بنویسید..."
: "پیام خود را بنویسید..."
}
className="hide-scrollbar overflow-y-auto h-12 flex w-full rounded-[12px] border border-input bg-transparent px-3 pr-10 py-2 text-base shadow-sm resize-none outline-none transition-height duration-100"
style={{
paddingRight: "40px",
paddingLeft: "48px",
direction: "rtl",
}}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSend();
}
}}
/>
<button
onClick={handleSend}
disabled={!messageText.trim()}
className="absolute left-3 bottom-2 p-2 bg-BLUE2 text-WHITE rounded-full flex items-center justify-center disabled:opacity-50"
aria-label="ارسال پیام"
>
{isSendingMessage ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<ArrowRight className="w-4 h-4 transform rotate-180" />
)}
</button>
</div>
</div>
</>
);
};
export default MessageInput;