@banbox/chat 1.0.1 → 1.0.2
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/index.cjs +564 -576
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -41
- package/dist/index.d.ts +43 -41
- package/dist/index.js +486 -499
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/chat/InboxPopup.tsx +51 -46
- package/src/chat/SinglePopup.tsx +47 -58
- package/src/lottie/banbox-chat-globe.json +1 -0
- package/src/ui/chat/AttachmentPreviewStrip.tsx +63 -112
- package/src/ui/chat/ChatComposerBar.tsx +22 -38
- package/src/ui/chat/ChatFooter.tsx +1 -1
- package/src/ui/chat/ChatIdentity.tsx +148 -145
- package/src/ui/chat/ChatListHeader.tsx +60 -83
- package/src/ui/chat/ChatMessageItem.tsx +193 -214
- package/src/ui/chat/ChatThreadItem.tsx +133 -140
- package/src/ui/chat/MessageHoverActions.tsx +136 -120
- package/src/ui/chat/TypingIndicator.tsx +23 -43
- package/src/ui/chat/drop-up/BusinessCardDropup.tsx +9 -1
- package/src/ui/chat/types.ts +42 -37
|
@@ -1,214 +1,193 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import {
|
|
15
|
-
|
|
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
|
-
export type
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
onTranslate={handleTranslateClick}
|
|
195
|
-
isItemButton={["replay", "translate"]}
|
|
196
|
-
activeButtons={translated ? ["translate"] : []}
|
|
197
|
-
>
|
|
198
|
-
<ChatBubbleText mine={mine} text={displayText} />
|
|
199
|
-
</MessageHoverActions>
|
|
200
|
-
) : null}
|
|
201
|
-
|
|
202
|
-
{showStatus ? (
|
|
203
|
-
<div className={cn("text-xs text-[#9AA1A9]", mine ? "text-right" : "text-left")}>
|
|
204
|
-
{status}
|
|
205
|
-
</div>
|
|
206
|
-
) : null}
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
);
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
export default ChatMessageItem;
|
|
1
|
+
// components/ui/chat/ChatMessageItem.tsx
|
|
2
|
+
"use client";
|
|
3
|
+
|
|
4
|
+
import clsx from "clsx";
|
|
5
|
+
import React from "react";
|
|
6
|
+
import ChatAddressCard from "./message-items/ChatAddressCard";
|
|
7
|
+
import ChatBubbleAudio from "./message-items/ChatBubbleAudio";
|
|
8
|
+
import ChatBubbleFiles from "./message-items/ChatBubbleFiles";
|
|
9
|
+
import ChatBubbleImages from "./message-items/ChatBubbleImages";
|
|
10
|
+
import ChatBubbleText from "./message-items/ChatBubbleText";
|
|
11
|
+
import ChatBusinessCard from "./message-items/ChatBusinessCard";
|
|
12
|
+
import MessageHoverActions from "./MessageHoverActions";
|
|
13
|
+
import ReplyCard from "./ReplyCard";
|
|
14
|
+
import type { AddressCard, BusinessCard, MessageRef } from "./types";
|
|
15
|
+
|
|
16
|
+
/** super-simple demo translator for your seed data */
|
|
17
|
+
const toBanglaDemo = (s: string) => {
|
|
18
|
+
const map: Record<string, string> = {
|
|
19
|
+
Hi: "হাই",
|
|
20
|
+
"Do you have a freight forwarder in China?": "আপনার কি চীনে কোনো ফ্রেইট ফরওয়ার্ডার আছে?",
|
|
21
|
+
"This conversation is empty. Say hi 👋": "এই কথোপকথনটি খালি । হাই হাই হাই বলুন 👋",
|
|
22
|
+
"Can we schedule a call for tomorrow?": "আমরা কি আগামীকাল একটি কল নির্ধারণ করতে পারি?",
|
|
23
|
+
"Sure, what time suits you?": "অবশ্যই, আপনার জন্য কোন সময়টি সুবিধাজনক?",
|
|
24
|
+
"Welcome to Global Marketplace": "গ্লোবাল মার্কেটপ্লেসে আপনাকে স্বাগতম 🎉",
|
|
25
|
+
"Happy to be here!": "এখানে থাকতে পেরে আনন্দিত!",
|
|
26
|
+
};
|
|
27
|
+
if (map[s]) {
|
|
28
|
+
return map[s];
|
|
29
|
+
}
|
|
30
|
+
return `বাংলা ${s}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type ChatAudio = { src?: string; duration?: string };
|
|
34
|
+
export type ChatFile = {
|
|
35
|
+
name: string;
|
|
36
|
+
sizeMB: number;
|
|
37
|
+
ext: string;
|
|
38
|
+
href?: string;
|
|
39
|
+
downloadName?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ChatMessageItemProps = {
|
|
43
|
+
id: string;
|
|
44
|
+
mine?: boolean;
|
|
45
|
+
time: string;
|
|
46
|
+
authorInitial?: string;
|
|
47
|
+
avatarBg?: string;
|
|
48
|
+
|
|
49
|
+
text?: string;
|
|
50
|
+
businessCard?: BusinessCard;
|
|
51
|
+
addressCard?: AddressCard;
|
|
52
|
+
images?: string[];
|
|
53
|
+
files?: ChatFile[];
|
|
54
|
+
audio?: ChatAudio;
|
|
55
|
+
|
|
56
|
+
replyTo?: MessageRef;
|
|
57
|
+
showStatus?: boolean;
|
|
58
|
+
status?: string;
|
|
59
|
+
className?: string;
|
|
60
|
+
|
|
61
|
+
onReply?: () => void;
|
|
62
|
+
onTranslate?: () => void; // optional external hook
|
|
63
|
+
|
|
64
|
+
initialSrc?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const ChatMessageItem: React.FC<ChatMessageItemProps> = ({
|
|
68
|
+
id,
|
|
69
|
+
mine = false,
|
|
70
|
+
time,
|
|
71
|
+
authorInitial = "U",
|
|
72
|
+
avatarBg = "#ffffff",
|
|
73
|
+
text,
|
|
74
|
+
businessCard,
|
|
75
|
+
addressCard,
|
|
76
|
+
images,
|
|
77
|
+
files,
|
|
78
|
+
audio,
|
|
79
|
+
replyTo,
|
|
80
|
+
showStatus = false,
|
|
81
|
+
status = "Seen",
|
|
82
|
+
className,
|
|
83
|
+
onReply,
|
|
84
|
+
onTranslate,
|
|
85
|
+
initialSrc,
|
|
86
|
+
}) => {
|
|
87
|
+
// translation state only affects the text bubble
|
|
88
|
+
const originalTextRef = React.useRef(text ?? "");
|
|
89
|
+
const [translated, setTranslated] = React.useState(false);
|
|
90
|
+
const displayText = translated ? toBanglaDemo(originalTextRef.current) : originalTextRef.current;
|
|
91
|
+
|
|
92
|
+
const handleTranslateClick = () => {
|
|
93
|
+
setTranslated((v) => !v); // toggle EN ⇄ BN
|
|
94
|
+
onTranslate?.(); // still let parent know if needed
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const isOnline = true;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div className={clsx("mb-4", className)} data-msg-id={id}>
|
|
101
|
+
<div className={clsx("flex items-end gap-3", mine && "justify-end")}>
|
|
102
|
+
{!mine && (
|
|
103
|
+
<div className={`${showStatus ? "mb-5" : "mb-0"}`}>
|
|
104
|
+
{initialSrc ? (
|
|
105
|
+
<div className="relative h-10 w-10 shrink-0 rounded-full border border-[#F1F1F1]">
|
|
106
|
+
<img
|
|
107
|
+
src={initialSrc}
|
|
108
|
+
alt="avatar image"
|
|
109
|
+
className="h-full w-full rounded-full object-cover"
|
|
110
|
+
/>
|
|
111
|
+
|
|
112
|
+
{isOnline && (
|
|
113
|
+
<span className="absolute bottom-0 right-0 h-[11.25px] w-[11.25px] rounded-full bg-[#328545] ring-1 ring-white" />
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
) : (
|
|
117
|
+
<div
|
|
118
|
+
className="relative grid h-10 w-10 shrink-0 place-items-center rounded-full border border-[#f1f1f1] font-semibold text-[#2c2c2c]"
|
|
119
|
+
style={{ backgroundColor: avatarBg }}
|
|
120
|
+
>
|
|
121
|
+
{authorInitial}
|
|
122
|
+
|
|
123
|
+
{isOnline && (
|
|
124
|
+
<span className="absolute bottom-0 right-0 h-[11.25px] w-[11.25px] rounded-full bg-[#328545] ring-1 ring-white" />
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
<div className={clsx("flex flex-col gap-1 w-full")}>
|
|
132
|
+
<div
|
|
133
|
+
className={clsx(
|
|
134
|
+
" text-xs font-light text-[#636363]",
|
|
135
|
+
mine ? "text-right" : "text-left",
|
|
136
|
+
)}
|
|
137
|
+
>
|
|
138
|
+
{time}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div className={clsx("flex flex-col gap-1 w-full", mine ? "items-end" : "items-start")}>
|
|
142
|
+
{/* reply-to preview */}
|
|
143
|
+
<div className={clsx("flex w-full", mine ? "justify-end" : "justify-start")}>
|
|
144
|
+
{replyTo && <ReplyCard jumpOnClick refMsg={replyTo} compact className="mb-1" />}
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
{businessCard ? <ChatBusinessCard mine={mine} card={businessCard} /> : null}
|
|
148
|
+
{addressCard ? <ChatAddressCard mine={mine} card={addressCard} /> : null}
|
|
149
|
+
|
|
150
|
+
{images?.length ? (
|
|
151
|
+
<MessageHoverActions mine={mine} onReply={onReply} isItemButton={["replay"]}>
|
|
152
|
+
<ChatBubbleImages images={images} />
|
|
153
|
+
</MessageHoverActions>
|
|
154
|
+
) : null}
|
|
155
|
+
|
|
156
|
+
{files?.length ? (
|
|
157
|
+
<MessageHoverActions mine={mine} onReply={onReply} isItemButton={["replay"]}>
|
|
158
|
+
<ChatBubbleFiles files={files} />
|
|
159
|
+
</MessageHoverActions>
|
|
160
|
+
) : null}
|
|
161
|
+
|
|
162
|
+
{audio ? (
|
|
163
|
+
<MessageHoverActions mine={mine} onReply={onReply} isItemButton={["replay"]}>
|
|
164
|
+
<ChatBubbleAudio mine={mine} audio={audio} />
|
|
165
|
+
</MessageHoverActions>
|
|
166
|
+
) : null}
|
|
167
|
+
|
|
168
|
+
{typeof text === "string" && text.length > 0 ? (
|
|
169
|
+
<MessageHoverActions
|
|
170
|
+
mine={mine}
|
|
171
|
+
onReply={onReply}
|
|
172
|
+
onTranslate={handleTranslateClick}
|
|
173
|
+
// show both buttons for text; mark Translate active when toggled
|
|
174
|
+
isItemButton={["replay", "translate"]}
|
|
175
|
+
activeButtons={translated ? ["translate"] : []}
|
|
176
|
+
>
|
|
177
|
+
<ChatBubbleText mine={mine} text={displayText} />
|
|
178
|
+
</MessageHoverActions>
|
|
179
|
+
) : null}
|
|
180
|
+
|
|
181
|
+
{showStatus && (
|
|
182
|
+
<div className={clsx("text-xs text-[#9AA1A9]", mine ? "text-right" : "text-left")}>
|
|
183
|
+
{status}
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export default ChatMessageItem;
|