@droppii-org/chat-sdk 0.0.3 → 0.0.4

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 (64) hide show
  1. package/dist/components/AutoScrollAnchor.d.ts +1 -1
  2. package/dist/components/AutoScrollAnchor.d.ts.map +1 -1
  3. package/dist/components/AutoScrollAnchor.js +12 -0
  4. package/dist/components/ChatBubble.d.ts +1 -1
  5. package/dist/components/ChatBubble.d.ts.map +1 -1
  6. package/dist/components/ChatBubble.js +18 -0
  7. package/dist/components/ChatHeader.d.ts +1 -1
  8. package/dist/components/ChatHeader.d.ts.map +1 -1
  9. package/dist/components/ChatHeader.js +32 -0
  10. package/dist/components/ChatInput.d.ts +1 -2
  11. package/dist/components/ChatInput.d.ts.map +1 -1
  12. package/dist/components/ChatInput.js +379 -0
  13. package/dist/components/ChatInputDemo.d.ts +1 -1
  14. package/dist/components/ChatInputDemo.d.ts.map +1 -1
  15. package/dist/components/ChatInputDemo.js +38 -0
  16. package/dist/components/ChatInputWithCustomIcon.d.ts +1 -2
  17. package/dist/components/ChatInputWithCustomIcon.d.ts.map +1 -1
  18. package/dist/components/ChatInputWithCustomIcon.js +85 -0
  19. package/dist/components/ChatLayout.d.ts +1 -1
  20. package/dist/components/ChatLayout.d.ts.map +1 -1
  21. package/dist/components/ChatLayout.js +48 -0
  22. package/dist/components/ConversationItem.d.ts +1 -1
  23. package/dist/components/ConversationItem.d.ts.map +1 -1
  24. package/dist/components/ConversationItem.js +27 -0
  25. package/dist/components/ConversationList.d.ts +1 -1
  26. package/dist/components/ConversationList.d.ts.map +1 -1
  27. package/dist/components/ConversationList.js +11 -0
  28. package/dist/components/DateDivider.d.ts +1 -1
  29. package/dist/components/DateDivider.d.ts.map +1 -1
  30. package/dist/components/DateDivider.js +27 -0
  31. package/dist/components/EmojiPicker.js +191 -0
  32. package/dist/components/ImageLightbox.d.ts +1 -1
  33. package/dist/components/ImageLightbox.d.ts.map +1 -1
  34. package/dist/components/ImageLightbox.js +8 -0
  35. package/dist/components/ImagePreviewModal.d.ts +1 -1
  36. package/dist/components/ImagePreviewModal.d.ts.map +1 -1
  37. package/dist/components/ImagePreviewModal.js +55 -0
  38. package/dist/components/MessageItem.d.ts +1 -1
  39. package/dist/components/MessageItem.d.ts.map +1 -1
  40. package/dist/components/MessageItem.js +38 -0
  41. package/dist/components/MessageItemDemo.d.ts +1 -1
  42. package/dist/components/MessageItemDemo.d.ts.map +1 -1
  43. package/dist/components/MessageItemDemo.js +166 -0
  44. package/dist/components/MessageList.d.ts +1 -1
  45. package/dist/components/MessageList.d.ts.map +1 -1
  46. package/dist/components/MessageList.js +243 -0
  47. package/dist/components/MessageListDemo.d.ts +1 -1
  48. package/dist/components/MessageListDemo.d.ts.map +1 -1
  49. package/dist/components/MessageListDemo.js +165 -0
  50. package/dist/components/StickerPicker.js +68 -0
  51. package/dist/components/SwipeIndicator.d.ts +1 -1
  52. package/dist/components/SwipeIndicator.d.ts.map +1 -1
  53. package/dist/components/SwipeIndicator.js +24 -0
  54. package/dist/components/TextFormattingToolbar.js +29 -0
  55. package/dist/components/TypingIndicator.d.ts +1 -1
  56. package/dist/components/TypingIndicator.d.ts.map +1 -1
  57. package/dist/components/TypingIndicator.js +21 -0
  58. package/dist/components/VoiceWaveIcon.d.ts +1 -1
  59. package/dist/components/VoiceWaveIcon.d.ts.map +1 -1
  60. package/dist/components/VoiceWaveIcon.js +5 -0
  61. package/dist/context/ChatContext.d.ts +1 -1
  62. package/dist/context/ChatContext.d.ts.map +1 -1
  63. package/dist/context/ChatContext.js +347 -0
  64. package/package.json +1 -1
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useRef, useCallback } from "react";
4
+ import { Smile, ImageIcon, Paperclip, Mic, ThumbsUp, Send, Type, Sticker, User } from "lucide-react";
5
+ import { VoiceWaveIcon } from "./VoiceWaveIcon";
6
+ export function ChatInputWithCustomIcon({ onSendMessage, onEmojiClick, onFileUpload, onImageUpload, onContactShare, onVoiceRecord, onVoiceMessage, onQuickReact, placeholder = "Nhập tin nhắn", disabled = false, className = "", }) {
7
+ const [message, setMessage] = useState("");
8
+ const [isRecording, setIsRecording] = useState(false);
9
+ const [showVoiceWave, setShowVoiceWave] = useState(false);
10
+ const textareaRef = useRef(null);
11
+ const fileInputRef = useRef(null);
12
+ const imageInputRef = useRef(null);
13
+ const handleSubmit = useCallback((e) => {
14
+ e.preventDefault();
15
+ if (!message.trim() || disabled)
16
+ return;
17
+ onSendMessage === null || onSendMessage === void 0 ? void 0 : onSendMessage(message.trim());
18
+ setMessage("");
19
+ // Reset textarea height
20
+ if (textareaRef.current) {
21
+ textareaRef.current.style.height = "auto";
22
+ }
23
+ }, [message, disabled, onSendMessage]);
24
+ const handleInputChange = useCallback((e) => {
25
+ const value = e.target.value;
26
+ setMessage(value);
27
+ // Auto-resize textarea
28
+ const textarea = e.target;
29
+ textarea.style.height = "auto";
30
+ textarea.style.height = Math.min(textarea.scrollHeight, 120) + "px";
31
+ }, []);
32
+ const handleKeyPress = useCallback((e) => {
33
+ if (e.key === "Enter" && !e.shiftKey) {
34
+ e.preventDefault();
35
+ handleSubmit(e);
36
+ }
37
+ }, [handleSubmit]);
38
+ const handleFileClick = useCallback(() => {
39
+ var _a;
40
+ (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
41
+ }, []);
42
+ const handleImageClick = useCallback(() => {
43
+ var _a;
44
+ (_a = imageInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
45
+ }, []);
46
+ const handleFileChange = useCallback((e) => {
47
+ var _a;
48
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
49
+ if (file) {
50
+ onFileUpload === null || onFileUpload === void 0 ? void 0 : onFileUpload(file);
51
+ e.target.value = ""; // Reset input
52
+ }
53
+ }, [onFileUpload]);
54
+ const handleImageChange = useCallback((e) => {
55
+ var _a;
56
+ const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
57
+ if (file) {
58
+ onImageUpload === null || onImageUpload === void 0 ? void 0 : onImageUpload(file);
59
+ e.target.value = ""; // Reset input
60
+ }
61
+ }, [onImageUpload]);
62
+ const handleVoiceRecord = useCallback(() => {
63
+ setIsRecording(!isRecording);
64
+ onVoiceRecord === null || onVoiceRecord === void 0 ? void 0 : onVoiceRecord();
65
+ }, [isRecording, onVoiceRecord]);
66
+ const handleVoiceMessage = useCallback(() => {
67
+ setShowVoiceWave(!showVoiceWave);
68
+ onVoiceMessage === null || onVoiceMessage === void 0 ? void 0 : onVoiceMessage();
69
+ }, [showVoiceWave, onVoiceMessage]);
70
+ return (_jsxs("div", { className: `bg-white border-t border-gray-200 p-3 sm:p-4 ${className}`, children: [_jsxs("div", { className: "bg-gray-50 rounded-2xl border border-gray-200 shadow-sm hover:shadow-md transition-shadow duration-200", children: [_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, onKeyPress: handleKeyPress, placeholder: placeholder, disabled: disabled, 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: {
71
+ minHeight: "20px",
72
+ maxHeight: "120px",
73
+ } }) }), message.trim() ? (_jsx("button", { type: "submit", disabled: disabled, 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": "Send message", children: _jsx(Send, { className: "w-4 h-4" }) })) : (_jsx("button", { type: "button", onClick: onQuickReact, className: "flex-shrink-0 p-2 text-blue-500 hover:bg-blue-50 rounded-full transition-colors duration-200", "aria-label": "Quick react", children: _jsx(ThumbsUp, { className: "w-4 h-4" }) }))] }) }), _jsx("div", { className: "px-4 pb-3 border-t border-gray-100", children: _jsxs("div", { className: "flex items-center justify-between space-x-1", children: [_jsx("button", { onClick: () => console.log("Text style clicked"), disabled: disabled, className: "p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100 active:scale-95 text-gray-500 hover:text-blue-500", "aria-label": "Text style", title: "Text style", children: _jsx(Type, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: () => onEmojiClick === null || onEmojiClick === void 0 ? void 0 : onEmojiClick("😊"), disabled: disabled, className: "p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100 active:scale-95 text-gray-500 hover:text-yellow-500", "aria-label": "Emoji", title: "Emoji", children: _jsx(Smile, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: () => console.log("Sticker clicked"), disabled: disabled, className: "p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100 active:scale-95 text-gray-500 hover:text-purple-500", "aria-label": "Sticker", title: "Sticker", children: _jsx(Sticker, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: handleImageClick, disabled: disabled, className: "p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100 active:scale-95 text-gray-500 hover:text-green-500", "aria-label": "Image upload", title: "Image upload", children: _jsx(ImageIcon, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: handleFileClick, disabled: disabled, className: "p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100 active:scale-95 text-gray-500 hover:text-blue-500", "aria-label": "File attachment", title: "File attachment", children: _jsx(Paperclip, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: onContactShare, disabled: disabled, className: "p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:bg-gray-100 active:scale-95 text-gray-500 hover:text-indigo-500", "aria-label": "Contact sharing", title: "Contact sharing", children: _jsx(User, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: handleVoiceRecord, disabled: disabled, className: `
74
+ p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed
75
+ hover:bg-gray-100 active:scale-95
76
+ ${isRecording ? "text-red-500 bg-red-50" : "text-gray-500 hover:text-red-500"}
77
+ `, "aria-label": "Record audio", title: "Record audio", children: _jsx(Mic, { className: "w-4 h-4 sm:w-5 sm:h-5" }) }), _jsx("button", { onClick: handleVoiceMessage, disabled: disabled, className: `
78
+ p-2 rounded-full transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed
79
+ hover:bg-gray-100 active:scale-95
80
+ ${showVoiceWave ? "text-blue-500 bg-blue-50" : "text-gray-500 hover:text-blue-500"}
81
+ `, "aria-label": "Voice message", title: "Voice message", children: _jsx(VoiceWaveIcon, { className: "w-4 h-4 sm:w-5 sm:h-5", animated: showVoiceWave }) })] }) }), 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: "Recording..." })] }) })), 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: {
82
+ height: `${Math.random() * 20 + 10}px`,
83
+ animationDelay: `${i * 0.1}s`,
84
+ } }, i))) }) }))] }), _jsx("input", { ref: fileInputRef, type: "file", onChange: handleFileChange, className: "hidden", accept: ".pdf,.doc,.docx,.txt,.zip,.rar" }), _jsx("input", { ref: imageInputRef, type: "file", onChange: handleImageChange, className: "hidden", accept: "image/*" })] }));
85
+ }
@@ -1,6 +1,6 @@
1
1
  interface ChatLayoutProps {
2
2
  className?: string;
3
3
  }
4
- export declare function ChatLayout({ className }: ChatLayoutProps): import("react").JSX.Element;
4
+ export declare function ChatLayout({ className }: ChatLayoutProps): import("react/jsx-runtime").JSX.Element;
5
5
  export {};
6
6
  //# sourceMappingURL=ChatLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatLayout.d.ts","sourceRoot":"","sources":["../../src/components/ChatLayout.tsx"],"names":[],"mappings":"AAWA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EAAE,SAAc,EAAE,EAAE,eAAe,+BA2J7D"}
1
+ {"version":3,"file":"ChatLayout.d.ts","sourceRoot":"","sources":["../../src/components/ChatLayout.tsx"],"names":[],"mappings":"AAWA,UAAU,eAAe;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EAAE,SAAc,EAAE,EAAE,eAAe,2CA2J7D"}
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { ConversationList } from "./ConversationList";
5
+ import { ChatHeader } from "./ChatHeader";
6
+ import { MessageList } from "./MessageList";
7
+ import { ChatInput } from "./ChatInput";
8
+ import { useChatContext } from "../context/ChatContext";
9
+ import { useSwipeGesture } from "../hooks/useSwipeGesture";
10
+ import { useMessages } from "../hooks/useMessages";
11
+ export function ChatLayout({ className = "" }) {
12
+ var _a;
13
+ const [selectedConversationId, setSelectedConversationId] = useState('conv-1');
14
+ const [showSidebar, setShowSidebar] = useState(false);
15
+ const { state } = useChatContext();
16
+ const messagesHook = useMessages(selectedConversationId || "");
17
+ const handleConversationSelect = (conversationId) => {
18
+ setSelectedConversationId(conversationId);
19
+ setShowSidebar(false); // Hide sidebar on mobile after selection
20
+ };
21
+ const handleBackToList = () => {
22
+ setSelectedConversationId(null);
23
+ setShowSidebar(true);
24
+ };
25
+ // Swipe gesture for going back to conversation list
26
+ const swipeRef = useSwipeGesture({
27
+ onSwipeRight: () => {
28
+ // Only trigger on mobile when a conversation is selected
29
+ if (selectedConversationId && window.innerWidth < 768) {
30
+ handleBackToList();
31
+ }
32
+ },
33
+ threshold: 100, // Minimum swipe distance
34
+ restraint: 100, // Maximum vertical movement allowed
35
+ allowedTime: 300, // Maximum time for swipe
36
+ enabled: !!selectedConversationId, // Only enable when conversation is selected
37
+ });
38
+ return (_jsxs("div", { className: `flex h-screen bg-white ${className}`, ref: swipeRef, children: [showSidebar && (_jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 z-40 md:hidden", onClick: () => setShowSidebar(false) })), _jsxs("div", { className: `
39
+ fixed inset-y-0 left-0 z-50 w-full bg-white transform transition-transform duration-300 ease-in-out
40
+ md:relative md:translate-x-0 md:w-80 md:border-r md:border-gray-200
41
+ ${showSidebar ? "translate-x-0" : "-translate-x-full"}
42
+ ${selectedConversationId ? "hidden md:flex" : "flex"}
43
+ flex-col h-full
44
+ `, children: [_jsx("div", { className: "flex-shrink-0 p-3 sm:p-4 border-b border-gray-200 bg-white", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h2", { className: "text-lg font-semibold text-gray-900", children: "Messages" }), _jsxs("div", { className: "flex items-center space-x-2", children: [!state.isConnected && (_jsxs("div", { className: "flex items-center text-xs text-gray-500", children: [_jsx("div", { className: "w-2 h-2 bg-gray-400 rounded-full mr-2" }), _jsx("span", { className: "hidden sm:inline", children: "Demo Mode" })] })), _jsx("button", { onClick: () => setShowSidebar(false), className: "md:hidden p-2 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100", children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] })] }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: _jsx(ConversationList, { selectedConversationId: selectedConversationId || undefined, onConversationSelect: handleConversationSelect, className: "h-full" }) })] }), _jsx("div", { className: `
45
+ flex-1 flex flex-col relative h-full
46
+ ${selectedConversationId ? "flex" : "hidden md:flex"}
47
+ `, children: selectedConversationId ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "md:hidden absolute top-2 left-2 z-10 pointer-events-none", children: _jsx("div", { className: "bg-black bg-opacity-20 text-white text-xs px-2 py-1 rounded-full opacity-0 transition-opacity duration-200 swipe-hint", children: "\u2190 Swipe to go back" }) }), _jsx("div", { className: "flex-shrink-0", children: _jsx(ChatHeader, { conversationId: selectedConversationId, onBackClick: handleBackToList, onMenuClick: () => setShowSidebar(true) }) }), _jsxs("div", { className: "flex-1 min-h-0", children: [" ", _jsx(MessageList, { messages: messagesHook.messages, currentUserId: ((_a = state.config) === null || _a === void 0 ? void 0 : _a.userId) || "", conversationId: selectedConversationId, className: "h-full" })] }), _jsx("div", { className: "flex-shrink-0", children: _jsx(ChatInput, {}) })] })) : (_jsx("div", { className: "flex-1 flex items-center justify-center text-gray-500 p-4", children: _jsxs("div", { className: "text-center max-w-sm", children: [_jsx("div", { className: "w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center", children: _jsx("svg", { className: "w-8 h-8 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" }) }) }), _jsx("h3", { className: "text-lg font-medium text-gray-900 mb-2", children: "Select a conversation" }), _jsx("p", { className: "text-gray-500 text-sm sm:text-base", children: "Choose a conversation from the sidebar to start messaging" }), !state.isConnected && (_jsx("p", { className: "text-xs text-gray-400 mt-2", children: "Running in demo mode - WebSocket disabled" })), _jsx("button", { onClick: () => setShowSidebar(true), className: "mt-4 md:hidden px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors", children: "View Conversations" })] }) })) })] }));
48
+ }
@@ -4,6 +4,6 @@ interface ConversationItemProps {
4
4
  isSelected?: boolean;
5
5
  onClick?: () => void;
6
6
  }
7
- export declare function ConversationItem({ conversation, isSelected, onClick }: ConversationItemProps): import("react").JSX.Element;
7
+ export declare function ConversationItem({ conversation, isSelected, onClick }: ConversationItemProps): import("react/jsx-runtime").JSX.Element;
8
8
  export {};
9
9
  //# sourceMappingURL=ConversationItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationItem.d.ts","sourceRoot":"","sources":["../../src/components/ConversationItem.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAG5C,UAAU,qBAAqB;IAC7B,YAAY,EAAE,YAAY,CAAA;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,qBAAqB,+BAiE5F"}
1
+ {"version":3,"file":"ConversationItem.d.ts","sourceRoot":"","sources":["../../src/components/ConversationItem.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAG5C,UAAU,qBAAqB;IAC7B,YAAY,EAAE,YAAY,CAAA;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;CACrB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,qBAAqB,2CAiE5F"}
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useChatContext } from "../context/ChatContext";
4
+ export function ConversationItem({ conversation, isSelected, onClick }) {
5
+ var _a;
6
+ const { state } = useChatContext();
7
+ // Get the other participant (not the current user)
8
+ const otherParticipant = conversation.participants.find((p) => { var _a; return p.id !== ((_a = state.config) === null || _a === void 0 ? void 0 : _a.userId); });
9
+ const formatTime = (date) => {
10
+ const now = new Date();
11
+ const diff = now.getTime() - date.getTime();
12
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24));
13
+ if (days === 0) {
14
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
15
+ }
16
+ else if (days === 1) {
17
+ return "Yesterday";
18
+ }
19
+ else if (days < 7) {
20
+ return date.toLocaleDateString([], { weekday: "short" });
21
+ }
22
+ else {
23
+ return date.toLocaleDateString([], { month: "short", day: "numeric" });
24
+ }
25
+ };
26
+ return (_jsxs("div", { className: `flex items-center p-3 sm:p-4 cursor-pointer hover:bg-gray-50 active:bg-gray-100 transition-colors ${isSelected ? "bg-blue-50 border-r-2 border-blue-500" : ""}`, onClick: onClick, children: [_jsxs("div", { className: "relative flex-shrink-0", children: [_jsx("img", { src: (otherParticipant === null || otherParticipant === void 0 ? void 0 : otherParticipant.avatar) || "/placeholder.svg?height=48&width=48&query=user", alt: (otherParticipant === null || otherParticipant === void 0 ? void 0 : otherParticipant.name) || "User", className: "w-10 h-10 sm:w-12 sm:h-12 rounded-full object-cover" }), (otherParticipant === null || otherParticipant === void 0 ? void 0 : otherParticipant.status) === "online" && (_jsx("div", { className: "absolute bottom-0 right-0 w-2.5 h-2.5 sm:w-3 sm:h-3 bg-green-500 rounded-full border-2 border-white" }))] }), _jsxs("div", { className: "ml-3 flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center justify-between mb-1", children: [_jsx("h3", { className: "text-sm sm:text-base font-medium text-gray-900 truncate pr-2", children: (otherParticipant === null || otherParticipant === void 0 ? void 0 : otherParticipant.name) || "Unknown User" }), conversation.lastMessage && (_jsx("span", { className: "text-xs text-gray-500 flex-shrink-0", children: formatTime(conversation.lastMessage.timestamp) }))] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsx("p", { className: "text-xs sm:text-sm text-gray-600 truncate pr-2", children: ((_a = conversation.lastMessage) === null || _a === void 0 ? void 0 : _a.content) || "No messages yet" }), conversation.unreadCount > 0 && (_jsx("span", { className: "inline-flex items-center justify-center px-2 py-1 text-xs font-bold leading-none text-white bg-blue-500 rounded-full flex-shrink-0 min-w-[20px] h-5", children: conversation.unreadCount > 99 ? "99+" : conversation.unreadCount }))] })] })] }));
27
+ }
@@ -3,6 +3,6 @@ interface ConversationListProps {
3
3
  selectedConversationId?: string;
4
4
  className?: string;
5
5
  }
6
- export declare function ConversationList({ onConversationSelect, selectedConversationId, className, }: ConversationListProps): import("react").JSX.Element;
6
+ export declare function ConversationList({ onConversationSelect, selectedConversationId, className, }: ConversationListProps): import("react/jsx-runtime").JSX.Element;
7
7
  export {};
8
8
  //# sourceMappingURL=ConversationList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/components/ConversationList.tsx"],"names":[],"mappings":"AAIA,UAAU,qBAAqB;IAC7B,oBAAoB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,oBAAoB,EACpB,sBAAsB,EACtB,SAAc,GACf,EAAE,qBAAqB,+BAiCvB"}
1
+ {"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/components/ConversationList.tsx"],"names":[],"mappings":"AAIA,UAAU,qBAAqB;IAC7B,oBAAoB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAA;IACvD,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,oBAAoB,EACpB,sBAAsB,EACtB,SAAc,GACf,EAAE,qBAAqB,2CAiCvB"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useConversationList } from "../hooks/useConversationList";
4
+ import { ConversationItem } from "./ConversationItem";
5
+ export function ConversationList({ onConversationSelect, selectedConversationId, className = "", }) {
6
+ const { conversations, isLoading } = useConversationList();
7
+ if (isLoading) {
8
+ return (_jsx("div", { className: `flex flex-col space-y-2 p-3 sm:p-4 ${className}`, children: [...Array(5)].map((_, i) => (_jsx("div", { className: "animate-pulse", children: _jsxs("div", { className: "flex items-center space-x-3 p-3", children: [_jsx("div", { className: "w-10 h-10 sm:w-12 sm:h-12 bg-gray-300 rounded-full" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "h-4 bg-gray-300 rounded w-3/4 mb-2" }), _jsx("div", { className: "h-3 bg-gray-300 rounded w-1/2" })] })] }) }, i))) }));
9
+ }
10
+ return (_jsx("div", { className: `flex flex-col ${className}`, children: conversations.map((conversation) => (_jsx(ConversationItem, { conversation: conversation, isSelected: selectedConversationId === conversation.id, onClick: () => onConversationSelect === null || onConversationSelect === void 0 ? void 0 : onConversationSelect(conversation.id) }, conversation.id))) }));
11
+ }
@@ -2,6 +2,6 @@ interface DateDividerProps {
2
2
  date: Date;
3
3
  customLabel?: string;
4
4
  }
5
- export declare function DateDivider({ date, customLabel }: DateDividerProps): import("react").JSX.Element;
5
+ export declare function DateDivider({ date, customLabel }: DateDividerProps): import("react/jsx-runtime").JSX.Element;
6
6
  export {};
7
7
  //# sourceMappingURL=DateDivider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DateDivider.d.ts","sourceRoot":"","sources":["../../src/components/DateDivider.tsx"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAA;IACV,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,gBAAgB,+BA4BlE"}
1
+ {"version":3,"file":"DateDivider.d.ts","sourceRoot":"","sources":["../../src/components/DateDivider.tsx"],"names":[],"mappings":"AAEA,UAAU,gBAAgB;IACxB,IAAI,EAAE,IAAI,CAAA;IACV,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,gBAAgB,2CA4BlE"}
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ export function DateDivider({ date, customLabel }) {
4
+ const formatDate = (date) => {
5
+ if (customLabel)
6
+ return customLabel;
7
+ const now = new Date();
8
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
9
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
10
+ const messageDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
11
+ if (messageDate.getTime() === today.getTime()) {
12
+ return "Hôm nay";
13
+ }
14
+ else if (messageDate.getTime() === yesterday.getTime()) {
15
+ return "Hôm qua";
16
+ }
17
+ else {
18
+ return date.toLocaleDateString("vi-VN", {
19
+ weekday: "long",
20
+ year: "numeric",
21
+ month: "long",
22
+ day: "numeric",
23
+ });
24
+ }
25
+ };
26
+ return (_jsx("div", { className: "flex items-center justify-center my-3 sm:my-4", children: _jsx("div", { className: "bg-gray-100 text-gray-600 text-xs px-2 py-1 sm:px-3 sm:py-1 rounded-full", children: formatDate(date) }) }));
27
+ }
@@ -0,0 +1,191 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { useState } from "react";
5
+ import { X, Search, Clock, Smile, Heart, Hand, Car, Lightbulb, Coffee, Flag } from "lucide-react";
6
+ const emojiCategories = {
7
+ recent: {
8
+ label: "Thường xuyên sử dụng",
9
+ icon: Clock,
10
+ emojis: ["😊", "😂", "❤️", "👍", "😢", "😮", "😡", "🎉", "👏"],
11
+ },
12
+ smileys: {
13
+ label: "Mặt cười & con người",
14
+ icon: Smile,
15
+ emojis: [
16
+ "😀",
17
+ "😃",
18
+ "😄",
19
+ "😁",
20
+ "😆",
21
+ "😅",
22
+ "😂",
23
+ "🤣",
24
+ "😊",
25
+ "😇",
26
+ "🙂",
27
+ "🙃",
28
+ "😉",
29
+ "😌",
30
+ "😍",
31
+ "🥰",
32
+ "😘",
33
+ "😗",
34
+ "😙",
35
+ "😚",
36
+ "😋",
37
+ "😛",
38
+ "😝",
39
+ "😜",
40
+ "🤪",
41
+ "🤨",
42
+ "🧐",
43
+ "🤓",
44
+ "😎",
45
+ "🤩",
46
+ "🥳",
47
+ "😏",
48
+ "😒",
49
+ "😞",
50
+ "😔",
51
+ "😟",
52
+ "😕",
53
+ "🙁",
54
+ "☹️",
55
+ "😣",
56
+ "😖",
57
+ "😫",
58
+ "😩",
59
+ "🥺",
60
+ "😢",
61
+ "😭",
62
+ "😤",
63
+ "😠",
64
+ "😡",
65
+ "🤬",
66
+ "🤯",
67
+ "😳",
68
+ "🥵",
69
+ "🥶",
70
+ "😱",
71
+ "😨",
72
+ "😰",
73
+ "😥",
74
+ "😓",
75
+ "🤗",
76
+ "🤔",
77
+ "🤭",
78
+ "🤫",
79
+ "🤥",
80
+ "😶",
81
+ "😐",
82
+ "😑",
83
+ "😬",
84
+ "🙄",
85
+ "😯",
86
+ "😦",
87
+ "😧",
88
+ "😮",
89
+ "😲",
90
+ "🥱",
91
+ "😴",
92
+ "🤤",
93
+ "😪",
94
+ "😵",
95
+ "🤐",
96
+ ],
97
+ },
98
+ gestures: {
99
+ label: "Cử chỉ",
100
+ icon: Hand,
101
+ emojis: [
102
+ "👍",
103
+ "👎",
104
+ "👌",
105
+ "✌️",
106
+ "🤞",
107
+ "🤟",
108
+ "🤘",
109
+ "🤙",
110
+ "👈",
111
+ "👉",
112
+ "👆",
113
+ "🖕",
114
+ "👇",
115
+ "☝️",
116
+ "👏",
117
+ "🙌",
118
+ "👐",
119
+ "🤲",
120
+ "🤝",
121
+ "🙏",
122
+ ],
123
+ },
124
+ hearts: {
125
+ label: "Trái tim",
126
+ icon: Heart,
127
+ emojis: ["❤️", "🧡", "💛", "💚", "💙", "💜", "🖤", "🤍", "🤎", "💔", "❣️", "💕", "💞", "💓", "💗", "💖", "💘", "💝"],
128
+ },
129
+ objects: {
130
+ label: "Đồ vật",
131
+ icon: Coffee,
132
+ emojis: ["📱", "💻", "⌨️", "🖥️", "🖨️", "📷", "📹", "🎥", "📞", "☎️", "📠", "📺", "📻", "🎵", "🎶", "🎤", "🎧", "📢"],
133
+ },
134
+ travel: {
135
+ label: "Du lịch",
136
+ icon: Car,
137
+ emojis: ["🚗", "🚕", "🚙", "🚌", "🚎", "🏎️", "🚓", "🚑", "🚒", "🚐", "🛻", "🚚", "🚛", "🚜", "🏍️", "🛵", "🚲", "🛴"],
138
+ },
139
+ activities: {
140
+ label: "Hoạt động",
141
+ icon: Lightbulb,
142
+ emojis: [
143
+ "⚽",
144
+ "🏀",
145
+ "🏈",
146
+ "⚾",
147
+ "🥎",
148
+ "🎾",
149
+ "🏐",
150
+ "🏉",
151
+ "🥏",
152
+ "🎱",
153
+ "🪀",
154
+ "🏓",
155
+ "🏸",
156
+ "🏒",
157
+ "🏑",
158
+ "🥍",
159
+ "🏏",
160
+ "🪃",
161
+ ],
162
+ },
163
+ flags: {
164
+ label: "Cờ",
165
+ icon: Flag,
166
+ emojis: ["🏳️", "🏴", "🏁", "🚩", "🏳️‍🌈", "🏳️‍⚧️", "🇺🇳", "🇻🇳", "🇺🇸", "🇬🇧", "🇫🇷", "🇩🇪", "🇯🇵", "🇰🇷", "🇨🇳", "🇮🇳"],
167
+ },
168
+ };
169
+ export const EmojiPicker = React.forwardRef(({ onEmojiSelect, onClose, isOpen = false, style }, ref) => {
170
+ const [activeCategory, setActiveCategory] = useState("recent");
171
+ const [searchTerm, setSearchTerm] = useState("");
172
+ if (!isOpen)
173
+ return null;
174
+ const handleEmojiClick = (emoji) => {
175
+ onEmojiSelect === null || onEmojiSelect === void 0 ? void 0 : onEmojiSelect(emoji);
176
+ };
177
+ const filteredEmojis = searchTerm
178
+ ? emojiCategories[activeCategory].emojis.filter((emoji) =>
179
+ // Simple search - in a real app you'd have emoji names/keywords
180
+ emoji.includes(searchTerm))
181
+ : emojiCategories[activeCategory].emojis;
182
+ return (_jsx("div", { ref: ref, className: "absolute bottom-full mb-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 w-full sm:max-w-xs md:w-80", style: style, children: _jsxs("div", { className: "p-2", children: [_jsxs("div", { className: "flex items-center justify-between mb-2 pb-1 border-b border-gray-100", children: [Object.entries(emojiCategories).map(([key, category]) => {
183
+ const Icon = category.icon;
184
+ const isActive = activeCategory === key;
185
+ return (_jsx("button", { onClick: () => setActiveCategory(key), className: `
186
+ p-1.5 rounded-full transition-all duration-200 hover:bg-gray-100
187
+ ${isActive ? "text-blue-500 bg-blue-50" : "text-gray-500"}
188
+ `, title: category.label, children: _jsx(Icon, { className: "w-4 h-4" }) }, key));
189
+ }), _jsx("button", { onClick: onClose, className: "p-1.5 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100", children: _jsx(X, { className: "w-4 h-4" }) })] }), _jsxs("div", { className: "relative mb-2", children: [_jsx(Search, { className: "absolute left-2 top-1/2 transform -translate-y-1/2 w-3 h-3 text-gray-400" }), _jsx("input", { type: "text", placeholder: "T\u00ECm ki\u1EBFm", value: searchTerm, onChange: (e) => setSearchTerm(e.target.value), className: "w-full pl-7 pr-3 py-1.5 text-xs bg-gray-50 border border-gray-200 rounded-md focus:outline-none focus:ring-1 focus:ring-blue-500 focus:border-transparent" })] }), _jsx("div", { className: "mb-2", children: _jsx("h3", { className: "text-xs font-medium text-gray-900", children: emojiCategories[activeCategory].label }) }), _jsx("div", { className: "grid grid-cols-9 gap-0.5 max-h-48 overflow-y-auto", children: filteredEmojis.map((emoji, index) => (_jsx("button", { onClick: () => handleEmojiClick(emoji), className: "p-1.5 text-lg hover:bg-gray-100 rounded transition-all duration-200 hover:scale-110 active:scale-95", title: emoji, children: emoji }, index))) }), filteredEmojis.length === 0 && (_jsxs("div", { className: "text-center py-4 text-gray-500", children: [_jsx("div", { className: "text-lg mb-1", children: "\uD83D\uDD0D" }), _jsx("p", { className: "text-xs", children: "Kh\u00F4ng t\u00ECm th\u1EA5y emoji n\u00E0o" })] }))] }) }));
190
+ });
191
+ EmojiPicker.displayName = "EmojiPicker";
@@ -3,6 +3,6 @@ interface ImageLightboxProps {
3
3
  alt: string;
4
4
  onClose: () => void;
5
5
  }
6
- export declare function ImageLightbox({ src, alt, onClose }: ImageLightboxProps): import("react").JSX.Element;
6
+ export declare function ImageLightbox({ src, alt, onClose }: ImageLightboxProps): import("react/jsx-runtime").JSX.Element;
7
7
  export {};
8
8
  //# sourceMappingURL=ImageLightbox.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImageLightbox.d.ts","sourceRoot":"","sources":["../../src/components/ImageLightbox.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,kBAAkB,+BA0BtE"}
1
+ {"version":3,"file":"ImageLightbox.d.ts","sourceRoot":"","sources":["../../src/components/ImageLightbox.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,aAAa,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,kBAAkB,2CA0BtE"}
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { X } from "lucide-react";
4
+ import Image from "next/image";
5
+ export function ImageLightbox({ src, alt, onClose }) {
6
+ return (_jsxs("div", { className: "fixed inset-0 z-[9999] bg-black bg-opacity-80 flex items-center justify-center p-4", onClick: onClose, children: [_jsx("button", { onClick: onClose, className: "absolute top-4 right-4 text-white p-2 rounded-full bg-gray-800/50 hover:bg-gray-700/70 transition-colors z-50", "aria-label": "Close image", children: _jsx(X, { className: "w-6 h-6" }) }), _jsx("div", { className: "relative max-w-full max-h-full", onClick: (e) => e.stopPropagation(), children: _jsx(Image, { src: src || "/placeholder.svg", alt: alt, width: 1200, height: 800, style: { width: "auto", height: "auto", maxWidth: "90vw", maxHeight: "90vh" }, className: "rounded-lg shadow-xl object-contain", priority // Load immediately
7
+ : true }) })] }));
8
+ }
@@ -7,6 +7,6 @@ interface ImagePreviewModalProps {
7
7
  initialImageId: string;
8
8
  onClose: () => void;
9
9
  }
10
- export declare function ImagePreviewModal({ images, initialImageId, onClose }: ImagePreviewModalProps): import("react").JSX.Element | null;
10
+ export declare function ImagePreviewModal({ images, initialImageId, onClose }: ImagePreviewModalProps): import("react/jsx-runtime").JSX.Element | null;
11
11
  export {};
12
12
  //# sourceMappingURL=ImagePreviewModal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePreviewModal.d.ts","sourceRoot":"","sources":["../../src/components/ImagePreviewModal.tsx"],"names":[],"mappings":"AAOA,UAAU,sBAAsB;IAC9B,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAA;QACV,GAAG,EAAE,MAAM,CAAA;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,EAAE,CAAA;IACH,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,sBAAsB,sCAmH5F"}
1
+ {"version":3,"file":"ImagePreviewModal.d.ts","sourceRoot":"","sources":["../../src/components/ImagePreviewModal.tsx"],"names":[],"mappings":"AAOA,UAAU,sBAAsB;IAC9B,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAA;QACV,GAAG,EAAE,MAAM,CAAA;QACX,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,EAAE,CAAA;IACH,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,sBAAsB,kDAmH5F"}
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import Image from "next/image";
5
+ import { X, ChevronLeft, ChevronRight } from "lucide-react";
6
+ export function ImagePreviewModal({ images, initialImageId, onClose }) {
7
+ // Find the initial index based on initialImageId
8
+ const initialIndex = images.findIndex((img) => img.id === initialImageId);
9
+ const [currentImageIndex, setCurrentImageIndex] = useState(initialIndex !== -1 ? initialIndex : 0);
10
+ // Ensure currentImageIndex is valid if initialImageId wasn't found or images array is empty
11
+ useEffect(() => {
12
+ if (initialIndex === -1 && images.length > 0) {
13
+ setCurrentImageIndex(0);
14
+ }
15
+ else if (images.length === 0) {
16
+ onClose(); // Close if no images are provided
17
+ }
18
+ }, [initialImageId, images, initialIndex, onClose]);
19
+ const currentImage = images[currentImageIndex];
20
+ const handlePrev = useCallback(() => {
21
+ setCurrentImageIndex((prevIndex) => Math.max(0, prevIndex - 1));
22
+ }, []);
23
+ const handleNext = useCallback(() => {
24
+ setCurrentImageIndex((prevIndex) => Math.min(images.length - 1, prevIndex + 1));
25
+ }, [images.length]);
26
+ const handleKeyDown = useCallback((event) => {
27
+ if (event.key === "Escape") {
28
+ onClose();
29
+ }
30
+ else if (event.key === "ArrowLeft") {
31
+ handlePrev();
32
+ }
33
+ else if (event.key === "ArrowRight") {
34
+ handleNext();
35
+ }
36
+ }, [onClose, handlePrev, handleNext]);
37
+ // Add and remove keyboard event listener
38
+ useEffect(() => {
39
+ document.addEventListener("keydown", handleKeyDown);
40
+ return () => {
41
+ document.removeEventListener("keydown", handleKeyDown);
42
+ };
43
+ }, [handleKeyDown]);
44
+ if (!currentImage) {
45
+ return null; // Or render a loading/error state
46
+ }
47
+ const isFirstImage = currentImageIndex === 0;
48
+ const isLastImage = currentImageIndex === images.length - 1;
49
+ return (_jsxs("div", { className: "fixed inset-0 z-[9999] bg-black bg-opacity-90 flex items-center justify-center p-4 sm:p-8", onClick: onClose, children: [_jsx("button", { onClick: onClose, className: "absolute top-4 right-4 text-white p-2 rounded-full bg-gray-800/50 hover:bg-gray-700/70 transition-colors z-50", "aria-label": "\u0110\u00F3ng", title: "\u0110\u00F3ng (Esc)", children: _jsx(X, { className: "w-6 h-6" }) }), _jsxs("div", { className: "relative flex items-center justify-center w-full h-full max-w-screen-xl max-h-screen-xl", onClick: (e) => e.stopPropagation(), children: [_jsx("button", { onClick: handlePrev, disabled: isFirstImage, className: `absolute left-2 sm:left-4 p-3 rounded-full bg-gray-800/50 text-white hover:bg-gray-700/70 transition-colors z-40
50
+ ${isFirstImage ? "opacity-50 cursor-not-allowed" : ""}`, "aria-label": "\u1EA2nh tr\u01B0\u1EDBc", title: "\u1EA2nh tr\u01B0\u1EDBc (M\u0169i t\u00EAn tr\u00E1i)", children: _jsx(ChevronLeft, { className: "w-6 h-6 sm:w-8 sm:h-8" }) }), _jsx("div", { className: "relative w-full h-full flex items-center justify-center", children: _jsx(Image, { src: currentImage.url || "/placeholder.svg", alt: currentImage.name || "Xem trước hình ảnh", layout: "fill" // Use fill to make it responsive within its parent
51
+ , objectFit: "contain" // Ensure the image fits within the container without cropping
52
+ , className: "rounded-lg shadow-xl", priority // Load immediately for better UX
53
+ : true }) }), _jsx("button", { onClick: handleNext, disabled: isLastImage, className: `absolute right-2 sm:right-4 p-3 rounded-full bg-gray-800/50 text-white hover:bg-gray-700/70 transition-colors z-40
54
+ ${isLastImage ? "opacity-50 cursor-not-allowed" : ""}`, "aria-label": "\u1EA2nh ti\u1EBFp theo", title: "\u1EA2nh ti\u1EBFp theo (M\u0169i t\u00EAn ph\u1EA3i)", children: _jsx(ChevronRight, { className: "w-6 h-6 sm:w-8 sm:h-8" }) })] }), _jsxs("div", { className: "absolute bottom-4 text-white text-sm sm:text-base bg-black/50 px-4 py-2 rounded-full", children: [currentImage.name || "Hình ảnh", " (", currentImageIndex + 1, " / ", images.length, ")"] })] }));
55
+ }
@@ -1,3 +1,3 @@
1
1
  import type { MessageItemProps } from "../types";
2
- export declare function MessageItem({ message, isGrouped, onImageClick }: MessageItemProps): import("react").JSX.Element;
2
+ export declare function MessageItem({ message, isGrouped, onImageClick }: MessageItemProps): import("react/jsx-runtime").JSX.Element;
3
3
  //# sourceMappingURL=MessageItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItem.d.ts","sourceRoot":"","sources":["../../src/components/MessageItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,UAAU,CAAA;AAIhE,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,gBAAgB,+BAwLjF"}
1
+ {"version":3,"file":"MessageItem.d.ts","sourceRoot":"","sources":["../../src/components/MessageItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,UAAU,CAAA;AAIhE,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,gBAAgB,2CAwLjF"}