@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
@@ -1,2 +1,2 @@
1
- export declare function AutoScrollAnchor(): import("react").JSX.Element;
1
+ export declare function AutoScrollAnchor(): import("react/jsx-runtime").JSX.Element;
2
2
  //# sourceMappingURL=AutoScrollAnchor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AutoScrollAnchor.d.ts","sourceRoot":"","sources":["../../src/components/AutoScrollAnchor.tsx"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,gCAU/B"}
1
+ {"version":3,"file":"AutoScrollAnchor.d.ts","sourceRoot":"","sources":["../../src/components/AutoScrollAnchor.tsx"],"names":[],"mappings":"AAIA,wBAAgB,gBAAgB,4CAU/B"}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ export function AutoScrollAnchor() {
5
+ const anchorRef = useRef(null);
6
+ useEffect(() => {
7
+ if (anchorRef.current) {
8
+ anchorRef.current.scrollIntoView({ behavior: "smooth" });
9
+ }
10
+ });
11
+ return _jsx("div", { ref: anchorRef });
12
+ }
@@ -1,2 +1,2 @@
1
- export declare function ChatBubble(): import("react").JSX.Element;
1
+ export declare function ChatBubble(): import("react/jsx-runtime").JSX.Element;
2
2
  //# sourceMappingURL=ChatBubble.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatBubble.d.ts","sourceRoot":"","sources":["../../src/components/ChatBubble.tsx"],"names":[],"mappings":"AAWA,wBAAgB,UAAU,gCA0GzB"}
1
+ {"version":3,"file":"ChatBubble.d.ts","sourceRoot":"","sources":["../../src/components/ChatBubble.tsx"],"names":[],"mappings":"AAWA,wBAAgB,UAAU,4CA0GzB"}
@@ -0,0 +1,18 @@
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 { MessageCircle, X } from "lucide-react";
5
+ import { ChatHeader } from "./ChatHeader";
6
+ import { MessageList } from "./MessageList";
7
+ import { ChatInput } from "./ChatInput";
8
+ import { useMessages } from "../hooks/useMessages";
9
+ export function ChatBubble() {
10
+ const [isOpen, setIsOpen] = useState(false);
11
+ const [selectedConversationId, setSelectedConversationId] = useState('conv-1');
12
+ console.log("🚀 ~ ChatBubble ~ isOpen:", isOpen);
13
+ const messagesHook = useMessages(selectedConversationId || "");
14
+ return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: () => setIsOpen(true), className: `fixed bottom-6 right-6 w-14 h-14 bg-blue-600 hover:bg-blue-700 text-white rounded-full shadow-lg transition-all duration-200 flex items-center justify-center z-40 ${isOpen ? "scale-0" : "scale-100"}`, "aria-label": "Open chat", children: _jsx(MessageCircle, { className: "w-6 h-6" }) }), isOpen && (_jsxs("div", { className: "fixed bottom-6 right-6 w-96 h-[700px] max-h-[80%] bg-white rounded-lg shadow-2xl border z-50 flex flex-col overflow-hidden", children: [_jsxs("div", { className: "flex items-center justify-between p-4 border-b bg-blue-600 text-white", children: [_jsx("h3", { className: "font-semibold", children: "Chat Support" }), _jsx("button", { onClick: () => setIsOpen(false), className: "p-1 hover:bg-blue-700 rounded", "aria-label": "Close chat", children: _jsx(X, { className: "w-4 h-4" }) })] }), _jsx("div", { className: `
15
+ flex-1 flex flex-col relative h-[calc(100%-50px)]
16
+ ${selectedConversationId ? "flex" : "hidden md:flex"}
17
+ `, 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: () => setIsOpen(false), onMenuClick: () => setIsOpen(false) }) }), _jsxs("div", { className: "flex-1 min-h-0", children: [" ", _jsx(MessageList, { messages: messagesHook.messages, currentUserId: "current-user", 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" }), _jsx("button", { onClick: () => setIsOpen(false), 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" })] }) })) })] })), isOpen && _jsx("div", { className: "fixed inset-0 bg-black bg-opacity-20 z-30", onClick: () => setIsOpen(false) })] }));
18
+ }
@@ -3,6 +3,6 @@ interface ChatHeaderProps {
3
3
  onBackClick?: () => void;
4
4
  onMenuClick?: () => void;
5
5
  }
6
- export declare function ChatHeader({ conversationId, onBackClick, onMenuClick }: ChatHeaderProps): import("react").JSX.Element | null;
6
+ export declare function ChatHeader({ conversationId, onBackClick, onMenuClick }: ChatHeaderProps): import("react/jsx-runtime").JSX.Element | null;
7
7
  export {};
8
8
  //# sourceMappingURL=ChatHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatHeader.d.ts","sourceRoot":"","sources":["../../src/components/ChatHeader.tsx"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;CACzB;AAED,wBAAgB,UAAU,CAAC,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,eAAe,sCAgGvF"}
1
+ {"version":3,"file":"ChatHeader.d.ts","sourceRoot":"","sources":["../../src/components/ChatHeader.tsx"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;IACxB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAA;CACzB;AAED,wBAAgB,UAAU,CAAC,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,eAAe,kDAgGvF"}
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useChatContext } from "../context/ChatContext";
4
+ export function ChatHeader({ conversationId, onBackClick, onMenuClick }) {
5
+ const { state } = useChatContext();
6
+ const conversation = state.conversations.find((c) => c.id === conversationId);
7
+ const otherParticipant = conversation === null || conversation === void 0 ? void 0 : conversation.participants.find((p) => { var _a; return p.id !== ((_a = state.config) === null || _a === void 0 ? void 0 : _a.userId); });
8
+ if (!conversation || !otherParticipant) {
9
+ return null;
10
+ }
11
+ const getStatusText = () => {
12
+ if (otherParticipant.status === "online") {
13
+ return "Online";
14
+ }
15
+ else if (otherParticipant.lastSeen) {
16
+ const now = new Date();
17
+ const diff = now.getTime() - otherParticipant.lastSeen.getTime();
18
+ const minutes = Math.floor(diff / (1000 * 60));
19
+ const hours = Math.floor(minutes / 60);
20
+ const days = Math.floor(hours / 24);
21
+ if (minutes < 1)
22
+ return "Last seen just now";
23
+ if (minutes < 60)
24
+ return `Last seen ${minutes}m ago`;
25
+ if (hours < 24)
26
+ return `Last seen ${hours}h ago`;
27
+ return `Last seen ${days}d ago`;
28
+ }
29
+ return "Offline";
30
+ };
31
+ return (_jsxs("div", { className: "flex items-center p-3 sm:p-4 border-b border-gray-200 bg-white relative", children: [_jsx("button", { onClick: onBackClick, className: "md:hidden p-3 mr-1 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 -ml-3 transition-colors", "aria-label": "Go back to conversations", 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: "M15 19l-7-7 7-7" }) }) }), _jsxs("div", { className: "relative", children: [_jsx("img", { src: otherParticipant.avatar || "/placeholder.svg?height=40&width=40&query=user", alt: otherParticipant.name, className: "w-8 h-8 sm:w-10 sm:h-10 rounded-full object-cover" }), 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: [_jsx("h2", { className: "text-base sm:text-lg font-semibold text-gray-900 truncate", children: otherParticipant.name }), _jsx("p", { className: "text-xs sm:text-sm text-gray-500 truncate", children: getStatusText() })] }), _jsx("div", { className: "md:hidden absolute top-full left-0 right-0 bg-blue-50 border-b border-blue-100 px-4 py-2 text-xs text-blue-600 text-center animate-pulse swipe-tutorial", children: "\uD83D\uDCA1 Tip: Swipe right anywhere to go back" }), _jsxs("div", { className: "flex items-center space-x-1 sm:space-x-2", children: [_jsx("button", { className: "p-2 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors", children: _jsx("svg", { className: "w-4 h-4 sm:w-5 sm:h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" }) }) }), _jsx("button", { className: "p-2 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors", children: _jsx("svg", { className: "w-4 h-4 sm:w-5 sm:h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) }) }), _jsx("button", { onClick: onMenuClick, className: "md:hidden p-2 text-gray-400 hover:text-gray-600 rounded-full hover:bg-gray-100 transition-colors", "aria-label": "Open menu", children: _jsx("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 6h16M4 12h16M4 18h16" }) }) })] })] }));
32
+ }
@@ -1,4 +1,3 @@
1
- import type React from "react";
2
1
  import type { ChatInputProps } from "../types/chat";
3
- export declare function ChatInput({ conversationId, onSendMessage, onEmojiClick, onStickerClick, onFileUpload, onImageUpload, onContactShare, onVoiceRecord, onVoiceMessage, onQuickReact, placeholder, disabled, className, }: ChatInputProps): React.JSX.Element;
2
+ export declare function ChatInput({ conversationId, onSendMessage, onEmojiClick, onStickerClick, onFileUpload, onImageUpload, onContactShare, onVoiceRecord, onVoiceMessage, onQuickReact, placeholder, disabled, className, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
4
3
  //# sourceMappingURL=ChatInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInput.d.ts","sourceRoot":"","sources":["../../src/components/ChatInput.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAQ9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,wBAAgB,SAAS,CAAC,EACxB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAA6B,EAC7B,QAAgB,EAChB,SAAc,GACf,EAAE,cAAc,qBA4lBhB"}
1
+ {"version":3,"file":"ChatInput.d.ts","sourceRoot":"","sources":["../../src/components/ChatInput.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,wBAAgB,SAAS,CAAC,EACxB,cAAc,EACd,aAAa,EACb,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAA6B,EAC7B,QAAgB,EAChB,SAAc,GACf,EAAE,cAAc,2CA4lBhB"}
@@ -0,0 +1,379 @@
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
+ }
@@ -1,2 +1,2 @@
1
- export declare function ChatInputDemo(): import("react").JSX.Element;
1
+ export declare function ChatInputDemo(): import("react/jsx-runtime").JSX.Element;
2
2
  //# sourceMappingURL=ChatInputDemo.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInputDemo.d.ts","sourceRoot":"","sources":["../../src/components/ChatInputDemo.tsx"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,gCA0E5B"}
1
+ {"version":3,"file":"ChatInputDemo.d.ts","sourceRoot":"","sources":["../../src/components/ChatInputDemo.tsx"],"names":[],"mappings":"AAKA,wBAAgB,aAAa,4CA0E5B"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { ChatInput } from "./ChatInput";
5
+ export function ChatInputDemo() {
6
+ const [messages, setMessages] = useState([]);
7
+ const handleSendMessage = (message) => {
8
+ setMessages((prev) => [...prev, message]);
9
+ console.log("Message sent:", message);
10
+ };
11
+ const handleEmojiClick = (emoji) => {
12
+ console.log("Emoji clicked:", emoji);
13
+ };
14
+ const handleStickerClick = (sticker) => {
15
+ console.log("Sticker clicked:", sticker);
16
+ setMessages((prev) => [...prev, `Sticker: ${sticker}`]);
17
+ };
18
+ const handleFileUpload = (file) => {
19
+ console.log("File uploaded:", file.name);
20
+ setMessages((prev) => [...prev, `📎 File: ${file.name}`]);
21
+ };
22
+ const handleImageUpload = (file) => {
23
+ console.log("Image uploaded:", file.name);
24
+ setMessages((prev) => [...prev, `🖼️ Image: ${file.name}`]);
25
+ };
26
+ const handleContactShare = () => {
27
+ console.log("Contact share clicked");
28
+ setMessages((prev) => [...prev, "👤 Contact shared"]);
29
+ };
30
+ const handleVoiceRecord = () => {
31
+ console.log("Voice record clicked");
32
+ };
33
+ const handleQuickReact = () => {
34
+ console.log("Quick react clicked");
35
+ setMessages((prev) => [...prev, "👍"]);
36
+ };
37
+ return (_jsxs("div", { className: "max-w-2xl mx-auto bg-white rounded-lg shadow-lg overflow-hidden", children: [_jsx("div", { className: "h-96 p-4 bg-gray-50 overflow-y-auto", children: _jsxs("div", { className: "space-y-3", children: [_jsx("div", { className: "text-center text-gray-500 text-sm mb-4", children: "Demo Chat Interface" }), messages.map((message, index) => (_jsx("div", { className: "flex justify-end", children: _jsx("div", { className: "bg-blue-500 text-white px-4 py-2 rounded-2xl rounded-tr-md max-w-xs", children: message }) }, index))), messages.length === 0 && (_jsx("div", { className: "text-center text-gray-400 text-sm", children: "Type a message, select emoji, or send sticker to test the ChatInput component" }))] }) }), _jsx(ChatInput, { onSendMessage: handleSendMessage, onEmojiClick: handleEmojiClick, onStickerClick: handleStickerClick, onFileUpload: handleFileUpload, onImageUpload: handleImageUpload, onContactShare: handleContactShare, onVoiceRecord: handleVoiceRecord, onQuickReact: handleQuickReact, placeholder: "Nh\u1EADp tin nh\u1EAFn" })] }));
38
+ }
@@ -1,4 +1,3 @@
1
- import type React from "react";
2
1
  interface ChatInputProps {
3
2
  onSendMessage?: (message: string) => void;
4
3
  onEmojiClick?: (emoji: string) => void;
@@ -12,6 +11,6 @@ interface ChatInputProps {
12
11
  disabled?: boolean;
13
12
  className?: string;
14
13
  }
15
- export declare function ChatInputWithCustomIcon({ onSendMessage, onEmojiClick, onFileUpload, onImageUpload, onContactShare, onVoiceRecord, onVoiceMessage, onQuickReact, placeholder, disabled, className, }: ChatInputProps): React.JSX.Element;
14
+ export declare function ChatInputWithCustomIcon({ onSendMessage, onEmojiClick, onFileUpload, onImageUpload, onContactShare, onVoiceRecord, onVoiceMessage, onQuickReact, placeholder, disabled, className, }: ChatInputProps): import("react/jsx-runtime").JSX.Element;
16
15
  export {};
17
16
  //# sourceMappingURL=ChatInputWithCustomIcon.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ChatInputWithCustomIcon.d.ts","sourceRoot":"","sources":["../../src/components/ChatInputWithCustomIcon.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,UAAU,cAAc;IACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACnC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACpC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,uBAAuB,CAAC,EACtC,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAA6B,EAC7B,QAAgB,EAChB,SAAc,GACf,EAAE,cAAc,qBAgRhB"}
1
+ {"version":3,"file":"ChatInputWithCustomIcon.d.ts","sourceRoot":"","sources":["../../src/components/ChatInputWithCustomIcon.tsx"],"names":[],"mappings":"AAOA,UAAU,cAAc;IACtB,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACnC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IACpC,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,uBAAuB,CAAC,EACtC,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,EACb,cAAc,EACd,YAAY,EACZ,WAA6B,EAC7B,QAAgB,EAChB,SAAc,GACf,EAAE,cAAc,2CAgRhB"}