@droppii-org/chat-sdk 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/tsconfig.tsbuildinfo +1 -0
  2. package/package.json +11 -4
  3. package/dist/components/AutoScrollAnchor.d.ts +0 -2
  4. package/dist/components/AutoScrollAnchor.d.ts.map +0 -1
  5. package/dist/components/AutoScrollAnchor.js +0 -12
  6. package/dist/components/AutoScrollAnchor.jsx +0 -11
  7. package/dist/components/ChatBubble.d.ts +0 -2
  8. package/dist/components/ChatBubble.d.ts.map +0 -1
  9. package/dist/components/ChatBubble.js +0 -18
  10. package/dist/components/ChatBubble.jsx +0 -80
  11. package/dist/components/ChatHeader.d.ts +0 -8
  12. package/dist/components/ChatHeader.d.ts.map +0 -1
  13. package/dist/components/ChatHeader.js +0 -32
  14. package/dist/components/ChatHeader.jsx +0 -72
  15. package/dist/components/ChatInput.d.ts +0 -3
  16. package/dist/components/ChatInput.d.ts.map +0 -1
  17. package/dist/components/ChatInput.js +0 -379
  18. package/dist/components/ChatInput.jsx +0 -444
  19. package/dist/components/ChatInputDemo.d.ts +0 -2
  20. package/dist/components/ChatInputDemo.d.ts.map +0 -1
  21. package/dist/components/ChatInputDemo.js +0 -38
  22. package/dist/components/ChatInputDemo.jsx +0 -53
  23. package/dist/components/ChatInputWithCustomIcon.d.ts +0 -16
  24. package/dist/components/ChatInputWithCustomIcon.d.ts.map +0 -1
  25. package/dist/components/ChatInputWithCustomIcon.js +0 -85
  26. package/dist/components/ChatInputWithCustomIcon.jsx +0 -167
  27. package/dist/components/ChatLayout.d.ts +0 -6
  28. package/dist/components/ChatLayout.d.ts.map +0 -1
  29. package/dist/components/ChatLayout.js +0 -48
  30. package/dist/components/ChatLayout.jsx +0 -122
  31. package/dist/components/ConversationItem.d.ts +0 -9
  32. package/dist/components/ConversationItem.d.ts.map +0 -1
  33. package/dist/components/ConversationItem.js +0 -27
  34. package/dist/components/ConversationItem.jsx +0 -51
  35. package/dist/components/ConversationList.d.ts +0 -8
  36. package/dist/components/ConversationList.d.ts.map +0 -1
  37. package/dist/components/ConversationList.js +0 -11
  38. package/dist/components/ConversationList.jsx +0 -22
  39. package/dist/components/DateDivider.d.ts +0 -7
  40. package/dist/components/DateDivider.d.ts.map +0 -1
  41. package/dist/components/DateDivider.js +0 -27
  42. package/dist/components/DateDivider.jsx +0 -28
  43. package/dist/components/EmojiPicker.d.ts +0 -4
  44. package/dist/components/EmojiPicker.d.ts.map +0 -1
  45. package/dist/components/EmojiPicker.js +0 -191
  46. package/dist/components/EmojiPicker.jsx +0 -229
  47. package/dist/components/ImageLightbox.d.ts +0 -8
  48. package/dist/components/ImageLightbox.d.ts.map +0 -1
  49. package/dist/components/ImageLightbox.js +0 -8
  50. package/dist/components/ImageLightbox.jsx +0 -16
  51. package/dist/components/ImagePreviewModal.d.ts +0 -12
  52. package/dist/components/ImagePreviewModal.d.ts.map +0 -1
  53. package/dist/components/ImagePreviewModal.js +0 -55
  54. package/dist/components/ImagePreviewModal.jsx +0 -84
  55. package/dist/components/MessageItem.d.ts +0 -3
  56. package/dist/components/MessageItem.d.ts.map +0 -1
  57. package/dist/components/MessageItem.js +0 -38
  58. package/dist/components/MessageItem.jsx +0 -99
  59. package/dist/components/MessageItemDemo.d.ts +0 -2
  60. package/dist/components/MessageItemDemo.d.ts.map +0 -1
  61. package/dist/components/MessageItemDemo.js +0 -166
  62. package/dist/components/MessageItemDemo.jsx +0 -179
  63. package/dist/components/MessageList.d.ts +0 -15
  64. package/dist/components/MessageList.d.ts.map +0 -1
  65. package/dist/components/MessageList.js +0 -243
  66. package/dist/components/MessageList.jsx +0 -306
  67. package/dist/components/MessageListDemo.d.ts +0 -2
  68. package/dist/components/MessageListDemo.d.ts.map +0 -1
  69. package/dist/components/MessageListDemo.js +0 -165
  70. package/dist/components/MessageListDemo.jsx +0 -183
  71. package/dist/components/StickerPicker.d.ts +0 -4
  72. package/dist/components/StickerPicker.d.ts.map +0 -1
  73. package/dist/components/StickerPicker.js +0 -68
  74. package/dist/components/StickerPicker.jsx +0 -106
  75. package/dist/components/SwipeIndicator.d.ts +0 -9
  76. package/dist/components/SwipeIndicator.d.ts.map +0 -1
  77. package/dist/components/SwipeIndicator.js +0 -24
  78. package/dist/components/SwipeIndicator.jsx +0 -28
  79. package/dist/components/TextFormattingToolbar.d.ts +0 -4
  80. package/dist/components/TextFormattingToolbar.d.ts.map +0 -1
  81. package/dist/components/TextFormattingToolbar.js +0 -29
  82. package/dist/components/TextFormattingToolbar.jsx +0 -52
  83. package/dist/components/TypingIndicator.d.ts +0 -6
  84. package/dist/components/TypingIndicator.d.ts.map +0 -1
  85. package/dist/components/TypingIndicator.js +0 -21
  86. package/dist/components/TypingIndicator.jsx +0 -27
  87. package/dist/components/VoiceWaveIcon.d.ts +0 -7
  88. package/dist/components/VoiceWaveIcon.d.ts.map +0 -1
  89. package/dist/components/VoiceWaveIcon.js +0 -5
  90. package/dist/components/VoiceWaveIcon.jsx +0 -11
  91. package/dist/context/ChatContext.d.ts +0 -72
  92. package/dist/context/ChatContext.d.ts.map +0 -1
  93. package/dist/context/ChatContext.js +0 -347
  94. package/dist/context/ChatContext.jsx +0 -346
  95. package/dist/hooks/useChat.d.ts +0 -5
  96. package/dist/hooks/useChat.d.ts.map +0 -1
  97. package/dist/hooks/useChat.js +0 -73
  98. package/dist/hooks/useConversationList.d.ts +0 -5
  99. package/dist/hooks/useConversationList.d.ts.map +0 -1
  100. package/dist/hooks/useConversationList.js +0 -9
  101. package/dist/hooks/useMessages.d.ts +0 -5
  102. package/dist/hooks/useMessages.d.ts.map +0 -1
  103. package/dist/hooks/useMessages.js +0 -192
  104. package/dist/hooks/useSocket.d.ts +0 -7
  105. package/dist/hooks/useSocket.d.ts.map +0 -1
  106. package/dist/hooks/useSocket.js +0 -120
  107. package/dist/hooks/useSwipeGesture.d.ts +0 -11
  108. package/dist/hooks/useSwipeGesture.d.ts.map +0 -1
  109. package/dist/hooks/useSwipeGesture.js +0 -54
  110. package/dist/hooks/useTextSelection.d.ts +0 -13
  111. package/dist/hooks/useTextSelection.d.ts.map +0 -1
  112. package/dist/hooks/useTextSelection.js +0 -132
  113. package/dist/hooks/useTyping.d.ts +0 -7
  114. package/dist/hooks/useTyping.d.ts.map +0 -1
  115. package/dist/hooks/useTyping.js +0 -64
  116. package/dist/index.d.ts +0 -28
  117. package/dist/index.d.ts.map +0 -1
  118. package/dist/index.js +0 -29
  119. package/dist/types/chat.d.ts +0 -39
  120. package/dist/types/chat.d.ts.map +0 -1
  121. package/dist/types/chat.js +0 -1
  122. package/dist/types/index.d.ts +0 -86
  123. package/dist/types/index.d.ts.map +0 -1
  124. package/dist/types/index.js +0 -1
@@ -1,379 +0,0 @@
1
- "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useState, useCallback, useEffect, useRef } from "react";
4
- import { Smile, ImageIcon, Paperclip, Mic, Send, Type, Sticker, User, X, Loader2, FileText } from "lucide-react"; // Import FileText icon
5
- import { useChat } from "../hooks/useChat";
6
- import { useTyping } from "../hooks/useTyping";
7
- import { useTextSelection } from "../hooks/useTextSelection";
8
- import { TextFormattingToolbar } from "./TextFormattingToolbar";
9
- import { EmojiPicker } from "./EmojiPicker";
10
- import { StickerPicker } from "./StickerPicker";
11
- export function ChatInput({ conversationId, onSendMessage, onEmojiClick, onStickerClick, onFileUpload, onImageUpload, onContactShare, onVoiceRecord, onVoiceMessage, onQuickReact, placeholder = "Nhập tin nhắn", disabled = false, className = "", }) {
12
- const [message, setMessage] = useState("");
13
- const [isRecording, setIsRecording] = useState(false);
14
- const [showVoiceWave, setShowVoiceWave] = useState(false);
15
- const [showTextToolbar, setShowTextToolbar] = useState(false);
16
- const [showEmojiPicker, setShowEmojiPicker] = useState(false);
17
- const [showStickerPicker, setShowStickerPicker] = useState(false);
18
- const [selectedFormats, setSelectedFormats] = useState([]);
19
- const [selectedFiles, setSelectedFiles] = useState([]); // State for selected files
20
- const [isUploading, setIsUploading] = useState(false); // State for uploading status
21
- const fileInputRef = useRef(null);
22
- const imageInputRef = useRef(null);
23
- // Refs for popups
24
- const textToolbarRef = useRef(null);
25
- const emojiPickerRef = useRef(null);
26
- const stickerPickerRef = useRef(null);
27
- // Refs for toggle buttons
28
- const textButtonRef = useRef(null);
29
- const emojiButtonRef = useRef(null);
30
- const stickerButtonRef = useRef(null);
31
- // Ref for the relative container that wraps the popups
32
- const popupWrapperRef = useRef(null);
33
- // State for dynamic popup styles
34
- const [textToolbarStyle, setTextToolbarStyle] = useState({});
35
- const [emojiPickerStyle, setEmojiPickerStyle] = useState({});
36
- const [stickerPickerStyle, setStickerPickerStyle] = useState({});
37
- const { sendMessage: sendChatMessage } = useChat(conversationId || "");
38
- const { startTyping, stopTyping } = useTyping(conversationId || "");
39
- const { textareaRef, applyFormat } = useTextSelection();
40
- const handleSubmit = useCallback(async (e) => {
41
- e.preventDefault();
42
- if (disabled || isUploading)
43
- return;
44
- if (selectedFiles.length > 0) {
45
- setIsUploading(true);
46
- // Simulate file upload
47
- await Promise.all(selectedFiles.map(async (file) => {
48
- await new Promise((resolve) => setTimeout(resolve, 1000)); // Simulate network delay
49
- if (file.type.startsWith("image/")) {
50
- onImageUpload === null || onImageUpload === void 0 ? void 0 : onImageUpload(file);
51
- }
52
- else {
53
- onFileUpload === null || onFileUpload === void 0 ? void 0 : onFileUpload(file);
54
- }
55
- }));
56
- setSelectedFiles([]);
57
- setIsUploading(false);
58
- }
59
- if (message.trim()) {
60
- // Use integrated chat system if available, otherwise use callback
61
- if (sendChatMessage) {
62
- sendChatMessage(message.trim());
63
- }
64
- else {
65
- onSendMessage === null || onSendMessage === void 0 ? void 0 : onSendMessage(message.trim());
66
- }
67
- setMessage("");
68
- setSelectedFormats([]);
69
- stopTyping();
70
- // Reset textarea height
71
- if (textareaRef.current) {
72
- textareaRef.current.style.height = "auto";
73
- }
74
- }
75
- }, [
76
- message,
77
- disabled,
78
- isUploading,
79
- selectedFiles,
80
- sendChatMessage,
81
- onSendMessage,
82
- onFileUpload,
83
- onImageUpload,
84
- stopTyping,
85
- textareaRef,
86
- ]);
87
- const handleInputChange = useCallback((e) => {
88
- const value = e.target.value;
89
- setMessage(value);
90
- // Trigger typing indicator
91
- if (value.trim()) {
92
- startTyping();
93
- }
94
- else {
95
- stopTyping();
96
- }
97
- // Auto-resize textarea
98
- const textarea = e.target;
99
- textarea.style.height = "auto";
100
- textarea.style.height = Math.min(textarea.scrollHeight, 120) + "px";
101
- }, [startTyping, stopTyping]);
102
- const handleKeyPress = useCallback((e) => {
103
- if (e.key === "Enter" && !e.shiftKey) {
104
- e.preventDefault();
105
- handleSubmit(e);
106
- }
107
- // Handle keyboard shortcuts
108
- if (e.ctrlKey || e.metaKey) {
109
- switch (e.key) {
110
- case "b":
111
- e.preventDefault();
112
- applyFormat("bold");
113
- break;
114
- case "i":
115
- e.preventDefault();
116
- applyFormat("italic");
117
- break;
118
- case "e":
119
- e.preventDefault();
120
- applyFormat("code");
121
- break;
122
- case "k":
123
- e.preventDefault();
124
- applyFormat("link");
125
- break;
126
- }
127
- }
128
- }, [handleSubmit, applyFormat]);
129
- const handleFileClick = useCallback(() => {
130
- var _a;
131
- (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
132
- }, []);
133
- const handleImageClick = useCallback(() => {
134
- var _a;
135
- (_a = imageInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
136
- }, []);
137
- const handleFileChange = useCallback((e) => {
138
- const files = Array.from(e.target.files || []);
139
- if (files.length > 0) {
140
- setSelectedFiles((prev) => [...prev, ...files]);
141
- e.target.value = ""; // Reset input to allow selecting same file again
142
- }
143
- }, []);
144
- const handleImageChange = useCallback((e) => {
145
- const files = Array.from(e.target.files || []);
146
- if (files.length > 0) {
147
- setSelectedFiles((prev) => [...prev, ...files]);
148
- e.target.value = ""; // Reset input to allow selecting same file again
149
- }
150
- }, []);
151
- const handleRemoveFile = useCallback((fileToRemove) => {
152
- setSelectedFiles((prev) => prev.filter((file) => file !== fileToRemove));
153
- }, []);
154
- const handleVoiceRecord = useCallback(() => {
155
- setIsRecording(!isRecording);
156
- onVoiceRecord === null || onVoiceRecord === void 0 ? void 0 : onVoiceRecord();
157
- }, [isRecording, onVoiceRecord]);
158
- const handleTextToolbarToggle = useCallback(() => {
159
- setShowTextToolbar((prev) => !prev);
160
- setShowEmojiPicker(false);
161
- setShowStickerPicker(false);
162
- }, []);
163
- const handleEmojiToggle = useCallback(() => {
164
- setShowEmojiPicker((prev) => !prev);
165
- setShowTextToolbar(false);
166
- setShowStickerPicker(false);
167
- }, []);
168
- const handleStickerToggle = useCallback(() => {
169
- setShowStickerPicker((prev) => !prev);
170
- setShowTextToolbar(false);
171
- setShowEmojiPicker(false);
172
- }, []);
173
- const handleFormatSelect = useCallback((format) => {
174
- applyFormat(format);
175
- setSelectedFormats((prev) => {
176
- if (prev.includes(format)) {
177
- return prev.filter((f) => f !== format);
178
- }
179
- else {
180
- return [...prev, format];
181
- }
182
- });
183
- }, [applyFormat]);
184
- const handleEmojiSelect = useCallback((emoji) => {
185
- const textarea = textareaRef.current;
186
- if (textarea) {
187
- const start = textarea.selectionStart;
188
- const end = textarea.selectionEnd;
189
- const newMessage = message.slice(0, start) + emoji + message.slice(end);
190
- setMessage(newMessage);
191
- setTimeout(() => {
192
- textarea.selectionStart = textarea.selectionEnd = start + emoji.length;
193
- textarea.focus();
194
- }, 0);
195
- }
196
- onEmojiClick === null || onEmojiClick === void 0 ? void 0 : onEmojiClick(emoji);
197
- }, [message, onEmojiClick, textareaRef]);
198
- const handleStickerSelect = useCallback((sticker) => {
199
- // Send sticker immediately (like a message)
200
- if (sendChatMessage) {
201
- sendChatMessage(sticker);
202
- }
203
- else {
204
- onSendMessage === null || onSendMessage === void 0 ? void 0 : onSendMessage(sticker);
205
- }
206
- onStickerClick === null || onStickerClick === void 0 ? void 0 : onStickerClick(sticker);
207
- setShowStickerPicker(false); // Close picker after selection
208
- }, [sendChatMessage, onSendMessage, onStickerClick]);
209
- // Click outside logic
210
- useEffect(() => {
211
- const handleClickOutside = (event) => {
212
- const target = event.target;
213
- // Text Formatting Toolbar
214
- if (showTextToolbar &&
215
- textToolbarRef.current &&
216
- !textToolbarRef.current.contains(target) &&
217
- textButtonRef.current &&
218
- !textButtonRef.current.contains(target)) {
219
- setShowTextToolbar(false);
220
- }
221
- // Emoji Picker
222
- if (showEmojiPicker &&
223
- emojiPickerRef.current &&
224
- !emojiPickerRef.current.contains(target) &&
225
- emojiButtonRef.current &&
226
- !emojiButtonRef.current.contains(target)) {
227
- setShowEmojiPicker(false);
228
- }
229
- // Sticker Picker
230
- if (showStickerPicker &&
231
- stickerPickerRef.current &&
232
- !stickerPickerRef.current.contains(target) &&
233
- stickerButtonRef.current &&
234
- !stickerButtonRef.current.contains(target)) {
235
- setShowStickerPicker(false);
236
- }
237
- };
238
- document.addEventListener("mousedown", handleClickOutside);
239
- return () => {
240
- document.removeEventListener("mousedown", handleClickOutside);
241
- };
242
- }, [showTextToolbar, showEmojiPicker, showStickerPicker]);
243
- // Dynamic positioning logic for popups
244
- const calculatePopupPosition = useCallback((buttonRef, popupRef, setPopupStyle) => {
245
- const wrapper = popupWrapperRef.current;
246
- const button = buttonRef.current;
247
- const popup = popupRef.current;
248
- if (!wrapper || !button || !popup)
249
- return;
250
- const wrapperRect = wrapper.getBoundingClientRect();
251
- const buttonRect = button.getBoundingClientRect();
252
- const popupRect = popup.getBoundingClientRect();
253
- const viewportWidth = window.innerWidth;
254
- let desiredLeft = buttonRect.left - wrapperRect.left;
255
- // Add a small margin (e.g., 8px) from the right edge of the viewport
256
- const rightViewportMargin = 8;
257
- if (wrapperRect.left + desiredLeft + popupRect.width > viewportWidth - rightViewportMargin) {
258
- desiredLeft = viewportWidth - wrapperRect.left - popupRect.width - rightViewportMargin;
259
- // Ensure it doesn't go off the left edge (e.g., 8px margin from left)
260
- desiredLeft = Math.max(8, desiredLeft);
261
- }
262
- setPopupStyle({ left: `${desiredLeft}px` });
263
- }, []);
264
- useEffect(() => {
265
- if (showTextToolbar) {
266
- calculatePopupPosition(textButtonRef, textToolbarRef, setTextToolbarStyle);
267
- window.addEventListener("resize", () => calculatePopupPosition(textButtonRef, textToolbarRef, setTextToolbarStyle));
268
- }
269
- else {
270
- setTextToolbarStyle({}); // Reset style when closed
271
- }
272
- return () => window.removeEventListener("resize", () => calculatePopupPosition(textButtonRef, textToolbarRef, setTextToolbarStyle));
273
- }, [showTextToolbar, calculatePopupPosition]);
274
- useEffect(() => {
275
- if (showEmojiPicker) {
276
- calculatePopupPosition(emojiButtonRef, emojiPickerRef, setEmojiPickerStyle);
277
- window.addEventListener("resize", () => calculatePopupPosition(emojiButtonRef, emojiPickerRef, setEmojiPickerStyle));
278
- }
279
- else {
280
- setEmojiPickerStyle({}); // Reset style when closed
281
- }
282
- return () => window.removeEventListener("resize", () => calculatePopupPosition(emojiButtonRef, emojiPickerRef, setEmojiPickerStyle));
283
- }, [showEmojiPicker, calculatePopupPosition]);
284
- useEffect(() => {
285
- if (showStickerPicker) {
286
- calculatePopupPosition(stickerButtonRef, stickerPickerRef, setStickerPickerStyle);
287
- window.addEventListener("resize", () => calculatePopupPosition(stickerButtonRef, stickerPickerRef, setStickerPickerStyle));
288
- }
289
- else {
290
- setStickerPickerStyle({}); // Reset style when closed
291
- }
292
- return () => window.removeEventListener("resize", () => calculatePopupPosition(stickerButtonRef, stickerPickerRef, setStickerPickerStyle));
293
- }, [showStickerPicker, calculatePopupPosition]);
294
- // Cleanup object URLs when selectedFiles change or component unmounts
295
- useEffect(() => {
296
- const objectUrls = [];
297
- selectedFiles.forEach((file) => {
298
- if (file.type.startsWith("image/")) {
299
- objectUrls.push(URL.createObjectURL(file));
300
- }
301
- });
302
- return () => {
303
- objectUrls.forEach((url) => URL.revokeObjectURL(url));
304
- };
305
- }, [selectedFiles]);
306
- // Small, left-aligned action buttons
307
- const actionButtons = [
308
- {
309
- icon: Type,
310
- label: "Định dạng văn bản",
311
- onClick: handleTextToolbarToggle,
312
- color: showTextToolbar ? "text-blue-500 bg-blue-50" : "text-gray-500 hover:text-blue-500",
313
- active: showTextToolbar,
314
- ref: textButtonRef,
315
- },
316
- {
317
- icon: Smile,
318
- label: "Emoji",
319
- onClick: handleEmojiToggle,
320
- color: showEmojiPicker ? "text-yellow-500 bg-yellow-50" : "text-gray-500 hover:text-yellow-500",
321
- active: showEmojiPicker,
322
- ref: emojiButtonRef,
323
- },
324
- {
325
- icon: Sticker,
326
- label: "Sticker",
327
- onClick: handleStickerToggle,
328
- color: showStickerPicker ? "text-purple-500 bg-purple-50" : "text-gray-500 hover:text-purple-500",
329
- active: showStickerPicker,
330
- ref: stickerButtonRef,
331
- },
332
- {
333
- icon: ImageIcon,
334
- label: "Hình ảnh",
335
- onClick: handleImageClick,
336
- color: "text-gray-500 hover:text-green-500",
337
- },
338
- {
339
- icon: Paperclip,
340
- label: "Tệp đính kèm",
341
- onClick: handleFileClick,
342
- color: "text-gray-500 hover:text-blue-500",
343
- },
344
- {
345
- icon: User,
346
- label: "Chia sẻ liên hệ",
347
- onClick: onContactShare,
348
- color: "text-gray-500 hover:text-indigo-500",
349
- },
350
- {
351
- icon: Mic,
352
- label: "Ghi âm",
353
- onClick: handleVoiceRecord,
354
- color: isRecording ? "text-red-500 bg-red-50" : "text-gray-500 hover:text-red-500",
355
- active: isRecording,
356
- },
357
- ];
358
- const isSendButtonDisabled = disabled || isUploading || (message.trim() === "" && selectedFiles.length === 0);
359
- return (_jsxs("div", { className: `bg-white border-t border-gray-200 p-3 sm:p-4 ${className}`, children: [_jsxs("div", { className: "relative", ref: popupWrapperRef, children: [_jsx(TextFormattingToolbar, { ref: textToolbarRef, isOpen: showTextToolbar, onClose: () => setShowTextToolbar(false), onFormatSelect: handleFormatSelect, selectedFormats: selectedFormats, style: textToolbarStyle }), _jsx(EmojiPicker, { ref: emojiPickerRef, isOpen: showEmojiPicker, onEmojiSelect: handleEmojiSelect, onClose: () => setShowEmojiPicker(false), style: emojiPickerStyle }), _jsx(StickerPicker, { ref: stickerPickerRef, isOpen: showStickerPicker, onStickerSelect: handleStickerSelect, onClose: () => setShowStickerPicker(false), style: stickerPickerStyle })] }), _jsxs("div", { className: "bg-gray-50 rounded-2xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow duration-200", children: [selectedFiles.length > 0 && (_jsx("div", { className: "px-4 pt-3 pb-2 border-b border-gray-100 flex flex-wrap gap-2", children: selectedFiles.map((file, index) => {
360
- const isImage = file.type.startsWith("image/");
361
- const fileUrl = isImage ? URL.createObjectURL(file) : null;
362
- return (_jsxs("div", { className: "flex items-center bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-1 rounded-lg relative overflow-hidden", children: [isImage ? (_jsx("img", { src: fileUrl || "", alt: file.name, className: "h-12 w-12 object-cover rounded-md mr-2" })) : (_jsxs("div", { className: "flex items-center space-x-1", children: [_jsx(FileText, { className: "w-4 h-4 text-blue-600" }), " ", _jsx("span", { className: "truncate max-w-[100px] sm:max-w-[150px]", children: file.name })] })), _jsx("button", { type: "button", onClick: () => handleRemoveFile(file), className: "ml-1 p-0.5 rounded-full hover:bg-blue-200 text-blue-600 absolute top-0.5 right-0.5 bg-white/70 backdrop-blur-sm", "aria-label": `Remove ${file.name}`, children: _jsx(X, { className: "w-3 h-3" }) })] }, index));
363
- }) })), _jsx("div", { className: "px-4 py-3", children: _jsxs("form", { onSubmit: handleSubmit, className: "flex items-end space-x-3", children: [_jsx("div", { className: "flex-1 relative", children: _jsx("textarea", { ref: textareaRef, value: message, onChange: handleInputChange, onKeyDown: handleKeyPress, placeholder: placeholder, disabled: disabled || isUploading, className: "w-full bg-transparent border-none outline-none resize-none text-sm sm:text-base text-gray-900 placeholder-gray-500 leading-5", rows: 1, style: {
364
- minHeight: "20px",
365
- maxHeight: "120px",
366
- } }) }), _jsx("button", { type: "submit", disabled: isSendButtonDisabled, className: "flex-shrink-0 p-2 bg-blue-500 text-white rounded-full hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200", "aria-label": isUploading ? "Đang tải lên" : "Gửi tin nhắn", children: isUploading ? _jsx(Loader2, { className: "w-4 h-4 animate-spin" }) : _jsx(Send, { className: "w-4 h-4" }) })] }) }), _jsx("div", { className: "px-4 pb-3 border-t border-gray-100", children: _jsx("div", { className: "flex items-center space-x-2 justify-start", children: actionButtons.map((button, index) => {
367
- const Icon = button.icon;
368
- return (_jsx("button", { ref: button.ref, onClick: button.onClick, disabled: disabled || isUploading, className: `
369
- p-1.5 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed
370
- hover:bg-gray-100 active:scale-95
371
- ${button.color}
372
- `, "aria-label": button.label, title: button.label, children: _jsx(Icon, { className: "w-4 h-4" }) }, index));
373
- }) }) }), isRecording && (_jsx("div", { className: "px-4 pb-2", children: _jsxs("div", { className: "flex items-center space-x-2 text-red-500 text-sm", children: [_jsx("div", { className: "w-2 h-2 bg-red-500 rounded-full animate-pulse" }), _jsx("span", { children: "\u0110ang ghi \u00E2m..." })] }) })), showVoiceWave && (_jsx("div", { className: "px-4 pb-2", children: _jsx("div", { className: "flex items-center space-x-1", children: [...Array(8)].map((_, i) => (_jsx("div", { className: "w-1 bg-blue-500 rounded-full animate-pulse", style: {
374
- height: `${Math.random() * 20 + 10}px`,
375
- animationDelay: `${i * 0.1}s`,
376
- } }, i))) }) }))] }), _jsx("input", { ref: fileInputRef, type: "file", onChange: handleFileChange, className: "hidden", accept: ".pdf,.doc,.docx,.txt,.zip,.rar", multiple // Allow multiple file selection
377
- : true }), _jsx("input", { ref: imageInputRef, type: "file", onChange: handleImageChange, className: "hidden", accept: "image/*", multiple // Allow multiple image selection
378
- : true })] }));
379
- }