@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@banbox/chat",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Banbox Chat UI components — reusable across all Banbox React/Next.js projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
"typescript"
|
|
58
58
|
],
|
|
59
59
|
"peerDependencies": {
|
|
60
|
+
"framer-motion": ">=10",
|
|
60
61
|
"react": ">=18",
|
|
61
|
-
"react-dom": ">=18"
|
|
62
|
-
"framer-motion": ">=10"
|
|
62
|
+
"react-dom": ">=18"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"framer-motion": {
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"clsx": "^2.1.1",
|
|
71
|
+
"lottie-react": "^2.4.1",
|
|
71
72
|
"tailwind-merge": "^3.6.0"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
package/src/chat/InboxPopup.tsx
CHANGED
|
@@ -144,36 +144,41 @@ const InboxPopup: React.FC<InboxPopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
144
144
|
};
|
|
145
145
|
|
|
146
146
|
return (
|
|
147
|
-
<div className="fixed bottom-4 right-
|
|
147
|
+
<div className="fixed bottom-4 right-4 z-[10002]">
|
|
148
148
|
{/* Backdrop */}
|
|
149
149
|
<motion.button
|
|
150
150
|
aria-label="Close chat"
|
|
151
151
|
onClick={close}
|
|
152
|
-
className="fixed inset-0
|
|
152
|
+
className="fixed inset-0"
|
|
153
153
|
initial={{ opacity: 0 }}
|
|
154
154
|
animate={{ opacity: 1 }}
|
|
155
155
|
exit={{ opacity: 0 }}
|
|
156
|
-
transition={{ duration: 0.
|
|
156
|
+
transition={{ type: "tween", duration: 0.25 }}
|
|
157
157
|
/>
|
|
158
158
|
|
|
159
159
|
{/* Popup wrapper */}
|
|
160
160
|
<motion.div
|
|
161
161
|
role="dialog"
|
|
162
162
|
aria-modal="true"
|
|
163
|
-
className="relative rounded-[
|
|
164
|
-
style={{
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
className="relative rounded-[20px] p-[3px]"
|
|
164
|
+
style={{
|
|
165
|
+
width: 800,
|
|
166
|
+
height: 650,
|
|
167
|
+
boxShadow: "0px 2px 12px 0px #3B33331A",
|
|
168
|
+
background:
|
|
169
|
+
"linear-gradient(236.83deg, rgba(51, 201, 212, 0.3) 0.4%, rgba(39, 83, 251, 0.3) 30.28%, rgba(39, 83, 251, 0.3) 50.2%, rgba(39, 83, 251, 0.3) 65.14%, rgba(235, 67, 255, 0.3) 100%)",
|
|
170
|
+
}}
|
|
171
|
+
initial={{ x: "110%" }}
|
|
172
|
+
animate={{ x: 0 }}
|
|
173
|
+
exit={{ x: "110%" }}
|
|
174
|
+
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
|
|
169
175
|
>
|
|
170
176
|
<div
|
|
171
|
-
className="relative h-full w-full overflow-hidden rounded-[
|
|
172
|
-
style={{
|
|
173
|
-
border: "2px solid transparent",
|
|
174
|
-
background: "linear-gradient(white, white) padding-box, linear-gradient(236.83deg, rgba(51, 201, 212, 0.3) 0.4%, rgba(39, 83, 251, 0.3) 30.28%, rgba(39, 83, 251, 0.3) 50.2%, rgba(39, 83, 251, 0.3) 65.14%, rgba(235, 67, 255, 0.3) 100%) border-box",
|
|
175
|
-
}}
|
|
177
|
+
className="relative h-full w-full overflow-hidden rounded-[18px] bg-white"
|
|
178
|
+
style={{ overscrollBehavior: "contain" }}
|
|
176
179
|
>
|
|
180
|
+
<div className="pointer-events-none absolute inset-0 rounded-[14px] ring-1 ring-[#2F80ED]/40" />
|
|
181
|
+
|
|
177
182
|
<div className="grid h-full min-h-0 grid-cols-[1fr_350px]">
|
|
178
183
|
{/* LEFT — Message area */}
|
|
179
184
|
<div className="flex h-full min-h-0 flex-col border-r border-[#9BBCCF]">
|
|
@@ -212,39 +217,39 @@ const InboxPopup: React.FC<InboxPopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
212
217
|
|
|
213
218
|
<div className="flex-1 min-h-0">
|
|
214
219
|
<div className="relative h-full min-h-0">
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
220
|
+
<ChatScroll
|
|
221
|
+
className="h-full pb-10"
|
|
222
|
+
bottomAlignWhenShort={false}
|
|
223
|
+
scrollKey={scrollKey}
|
|
224
|
+
>
|
|
225
|
+
{messages.map((m, idx) => {
|
|
226
|
+
const mine = m.author === "you";
|
|
227
|
+
const isLast = idx === messages.length - 1;
|
|
228
|
+
return (
|
|
229
|
+
<ChatMessageItem
|
|
230
|
+
key={m.id}
|
|
231
|
+
id={m.id}
|
|
232
|
+
mine={mine}
|
|
233
|
+
time={m.time ?? ""}
|
|
234
|
+
authorInitial={typeof m.author === "string" ? m.author : "U"}
|
|
235
|
+
avatarBg={avatarBg}
|
|
236
|
+
text={m.text ?? m.content}
|
|
237
|
+
businessCard={m.businessCard as Parameters<typeof ChatMessageItem>[0]["businessCard"]}
|
|
238
|
+
addressCard={m.addressCard as Parameters<typeof ChatMessageItem>[0]["addressCard"]}
|
|
239
|
+
images={m.images}
|
|
240
|
+
files={m.files}
|
|
241
|
+
audio={m.audio}
|
|
242
|
+
replyTo={m.replyTo}
|
|
243
|
+
showStatus={isLast}
|
|
244
|
+
status={activeThread?.status?.kind === "seen" ? "Seen" : "Delivered"}
|
|
245
|
+
onReply={() => setReplyTo(toRef(m))}
|
|
246
|
+
initialSrc={m.avatarSrc}
|
|
247
|
+
/>
|
|
248
|
+
);
|
|
249
|
+
})}
|
|
250
|
+
</ChatScroll>
|
|
246
251
|
|
|
247
|
-
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-center px-4 pb-2 pt-1 bg-white">
|
|
252
|
+
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex items-center justify-start px-4 pb-2 pt-1 bg-white">
|
|
248
253
|
<TypingIndicator className="pointer-events-auto" />
|
|
249
254
|
</div>
|
|
250
255
|
</div>
|
package/src/chat/SinglePopup.tsx
CHANGED
|
@@ -11,7 +11,6 @@ import ChatHeader from "../ui/chat/ChatHeader";
|
|
|
11
11
|
import ChatIdentity from "../ui/chat/ChatIdentity";
|
|
12
12
|
import ChatMessageItem from "../ui/chat/ChatMessageItem";
|
|
13
13
|
import ChatScroll from "../ui/chat/ChatScroll";
|
|
14
|
-
import ChatSpinner from "../ui/chat/ChatSpinner";
|
|
15
14
|
import TypingIndicator from "../ui/chat/TypingIndicator";
|
|
16
15
|
|
|
17
16
|
import type { Thread, Message, MessageRef, Reference } from "../types";
|
|
@@ -82,7 +81,7 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
82
81
|
subtitle?: string;
|
|
83
82
|
};
|
|
84
83
|
|
|
85
|
-
const
|
|
84
|
+
const initialSrc = activeThread?.avatarSrc ?? "/shop/ban-box.png";
|
|
86
85
|
const title = meta.title ?? activeThread?.title ?? "Unknown";
|
|
87
86
|
const online = meta.online ?? activeThread?.online ?? true;
|
|
88
87
|
const subtitle = meta.subtitle ?? "Customer";
|
|
@@ -91,15 +90,8 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
91
90
|
const [messages, setMessages] = React.useState<Message[]>(() =>
|
|
92
91
|
activeId ? adapter.messages.list(activeId) : [],
|
|
93
92
|
);
|
|
94
|
-
const [scrollKey, setScrollKey] = React.useState<number>(
|
|
93
|
+
const [scrollKey, setScrollKey] = React.useState<number>(Date.now());
|
|
95
94
|
const [replyTo, setReplyTo] = React.useState<MessageRef | undefined>(undefined);
|
|
96
|
-
const [isLoading, setIsLoading] = React.useState(true);
|
|
97
|
-
|
|
98
|
-
// Brief loading flash on initial open
|
|
99
|
-
React.useEffect(() => {
|
|
100
|
-
const t = setTimeout(() => setIsLoading(false), 300);
|
|
101
|
-
return () => clearTimeout(t);
|
|
102
|
-
}, []);
|
|
103
95
|
|
|
104
96
|
// Subscribe to real-time updates
|
|
105
97
|
React.useEffect(() => {
|
|
@@ -123,16 +115,16 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
123
115
|
void uiCallbacks;
|
|
124
116
|
|
|
125
117
|
return (
|
|
126
|
-
<div className="fixed bottom-4 right-
|
|
118
|
+
<div className="fixed bottom-4 right-4 z-[10002]">
|
|
127
119
|
{/* Backdrop */}
|
|
128
120
|
<motion.button
|
|
129
121
|
aria-label="Close chat"
|
|
130
122
|
onClick={close}
|
|
131
|
-
className="fixed inset-0
|
|
123
|
+
className="fixed inset-0 cursor-auto!"
|
|
132
124
|
initial={{ opacity: 0 }}
|
|
133
125
|
animate={{ opacity: 1 }}
|
|
134
126
|
exit={{ opacity: 0 }}
|
|
135
|
-
transition={{ duration: 0.
|
|
127
|
+
transition={{ type: "tween", duration: 0.25 }}
|
|
136
128
|
/>
|
|
137
129
|
|
|
138
130
|
{/* Outer gradient wrapper */}
|
|
@@ -142,23 +134,26 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
142
134
|
className="relative h-[650px] w-[450px] rounded-[20px] p-[2px]"
|
|
143
135
|
style={{
|
|
144
136
|
boxShadow: "0px 2px 12px 0px #3B33331A",
|
|
145
|
-
background:
|
|
137
|
+
background:
|
|
138
|
+
"linear-gradient(236.83deg, rgba(51, 201, 212, 0.3) 0.4%, rgba(39, 83, 251, 0.3) 30.28%, rgba(39, 83, 251, 0.3) 50.2%, rgba(39, 83, 251, 0.3) 65.14%, rgba(235, 67, 255, 0.3) 100%)",
|
|
146
139
|
}}
|
|
147
|
-
initial={{ x: "
|
|
148
|
-
animate={{ x: 0
|
|
149
|
-
exit={{ x: "
|
|
150
|
-
transition={{ type: "tween", duration: 0.
|
|
140
|
+
initial={{ x: "110%" }}
|
|
141
|
+
animate={{ x: 0 }}
|
|
142
|
+
exit={{ x: "110%" }}
|
|
143
|
+
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
|
|
151
144
|
>
|
|
152
145
|
{/* Inner card */}
|
|
153
|
-
<div
|
|
146
|
+
<div
|
|
147
|
+
className="flex h-full w-full flex-col overflow-hidden rounded-[18px] bg-white"
|
|
148
|
+
style={{ overscrollBehavior: "contain" }}
|
|
149
|
+
>
|
|
154
150
|
{/* Header */}
|
|
155
151
|
<div className="h-[64px] shrink-0">
|
|
156
152
|
<ChatHeader
|
|
157
153
|
left={
|
|
158
154
|
<ChatIdentity
|
|
159
|
-
variant="
|
|
160
|
-
|
|
161
|
-
bg="#FFE5DA"
|
|
155
|
+
variant="avatar"
|
|
156
|
+
src={initialSrc}
|
|
162
157
|
online={online}
|
|
163
158
|
title={title}
|
|
164
159
|
subtitle={subtitle}
|
|
@@ -179,44 +174,38 @@ const SinglePopup: React.FC<SinglePopupProps> = ({ adapter, uiCallbacks }) => {
|
|
|
179
174
|
</div>
|
|
180
175
|
|
|
181
176
|
{/* Messages */}
|
|
182
|
-
<div className="
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
</ChatScroll>
|
|
212
|
-
)}
|
|
213
|
-
|
|
214
|
-
{/* Typing indicator */}
|
|
215
|
-
<div className="pointer-events-none absolute bottom-0 left-0 w-full bg-white px-4 pb-2">
|
|
216
|
-
<div className="pointer-events-auto flex items-center justify-start">
|
|
177
|
+
<div className="flex-1 min-h-0">
|
|
178
|
+
<ChatScroll className="h-full" bottomAlignWhenShort={false} scrollKey={scrollKey}>
|
|
179
|
+
{messages.map((m, idx) => {
|
|
180
|
+
const mine = m.author === "you";
|
|
181
|
+
const isLast = idx === messages.length - 1;
|
|
182
|
+
return (
|
|
183
|
+
<ChatMessageItem
|
|
184
|
+
key={m.id}
|
|
185
|
+
id={m.id}
|
|
186
|
+
mine={mine}
|
|
187
|
+
time={m.time ?? ""}
|
|
188
|
+
authorInitial={typeof m.author === "string" ? m.author : "U"}
|
|
189
|
+
text={m.text ?? m.content}
|
|
190
|
+
businessCard={m.businessCard as Parameters<typeof ChatMessageItem>[0]["businessCard"]}
|
|
191
|
+
addressCard={m.addressCard as Parameters<typeof ChatMessageItem>[0]["addressCard"]}
|
|
192
|
+
images={m.images}
|
|
193
|
+
files={m.files}
|
|
194
|
+
audio={m.audio}
|
|
195
|
+
replyTo={m.replyTo}
|
|
196
|
+
initialSrc={m.avatarSrc}
|
|
197
|
+
showStatus={isLast}
|
|
198
|
+
status={statusText}
|
|
199
|
+
onReply={() => setReplyTo(toRef(m))}
|
|
200
|
+
/>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
203
|
+
|
|
204
|
+
{/* Typing */}
|
|
205
|
+
<div className="flex items-center justify-start">
|
|
217
206
|
<TypingIndicator />
|
|
218
207
|
</div>
|
|
219
|
-
</
|
|
208
|
+
</ChatScroll>
|
|
220
209
|
</div>
|
|
221
210
|
|
|
222
211
|
{/* Footer */}
|