@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.
- package/dist/components/AutoScrollAnchor.d.ts +1 -1
- package/dist/components/AutoScrollAnchor.d.ts.map +1 -1
- package/dist/components/AutoScrollAnchor.js +12 -0
- package/dist/components/ChatBubble.d.ts +1 -1
- package/dist/components/ChatBubble.d.ts.map +1 -1
- package/dist/components/ChatBubble.js +18 -0
- package/dist/components/ChatHeader.d.ts +1 -1
- package/dist/components/ChatHeader.d.ts.map +1 -1
- package/dist/components/ChatHeader.js +32 -0
- package/dist/components/ChatInput.d.ts +1 -2
- package/dist/components/ChatInput.d.ts.map +1 -1
- package/dist/components/ChatInput.js +379 -0
- package/dist/components/ChatInputDemo.d.ts +1 -1
- package/dist/components/ChatInputDemo.d.ts.map +1 -1
- package/dist/components/ChatInputDemo.js +38 -0
- package/dist/components/ChatInputWithCustomIcon.d.ts +1 -2
- package/dist/components/ChatInputWithCustomIcon.d.ts.map +1 -1
- package/dist/components/ChatInputWithCustomIcon.js +85 -0
- package/dist/components/ChatLayout.d.ts +1 -1
- package/dist/components/ChatLayout.d.ts.map +1 -1
- package/dist/components/ChatLayout.js +48 -0
- package/dist/components/ConversationItem.d.ts +1 -1
- package/dist/components/ConversationItem.d.ts.map +1 -1
- package/dist/components/ConversationItem.js +27 -0
- package/dist/components/ConversationList.d.ts +1 -1
- package/dist/components/ConversationList.d.ts.map +1 -1
- package/dist/components/ConversationList.js +11 -0
- package/dist/components/DateDivider.d.ts +1 -1
- package/dist/components/DateDivider.d.ts.map +1 -1
- package/dist/components/DateDivider.js +27 -0
- package/dist/components/EmojiPicker.js +191 -0
- package/dist/components/ImageLightbox.d.ts +1 -1
- package/dist/components/ImageLightbox.d.ts.map +1 -1
- package/dist/components/ImageLightbox.js +8 -0
- package/dist/components/ImagePreviewModal.d.ts +1 -1
- package/dist/components/ImagePreviewModal.d.ts.map +1 -1
- package/dist/components/ImagePreviewModal.js +55 -0
- package/dist/components/MessageItem.d.ts +1 -1
- package/dist/components/MessageItem.d.ts.map +1 -1
- package/dist/components/MessageItem.js +38 -0
- package/dist/components/MessageItemDemo.d.ts +1 -1
- package/dist/components/MessageItemDemo.d.ts.map +1 -1
- package/dist/components/MessageItemDemo.js +166 -0
- package/dist/components/MessageList.d.ts +1 -1
- package/dist/components/MessageList.d.ts.map +1 -1
- package/dist/components/MessageList.js +243 -0
- package/dist/components/MessageListDemo.d.ts +1 -1
- package/dist/components/MessageListDemo.d.ts.map +1 -1
- package/dist/components/MessageListDemo.js +165 -0
- package/dist/components/StickerPicker.js +68 -0
- package/dist/components/SwipeIndicator.d.ts +1 -1
- package/dist/components/SwipeIndicator.d.ts.map +1 -1
- package/dist/components/SwipeIndicator.js +24 -0
- package/dist/components/TextFormattingToolbar.js +29 -0
- package/dist/components/TypingIndicator.d.ts +1 -1
- package/dist/components/TypingIndicator.d.ts.map +1 -1
- package/dist/components/TypingIndicator.js +21 -0
- package/dist/components/VoiceWaveIcon.d.ts +1 -1
- package/dist/components/VoiceWaveIcon.d.ts.map +1 -1
- package/dist/components/VoiceWaveIcon.js +5 -0
- package/dist/context/ChatContext.d.ts +1 -1
- package/dist/context/ChatContext.d.ts.map +1 -1
- package/dist/context/ChatContext.js +347 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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"}
|