@banbox/chat 1.0.12 → 1.0.14
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/README.md +403 -88
- package/dist/index.cjs +47 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +47 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/chat/SinglePopup.tsx +49 -12
package/README.md
CHANGED
|
@@ -1,37 +1,72 @@
|
|
|
1
1
|
# @banbox/chat
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Current version:** `1.0.12`
|
|
4
|
+
|
|
5
|
+
Banbox Chat UI package — plug-and-play chat popup for any React or Next.js project.
|
|
6
|
+
Data-agnostic: bring your own adapter (demo, REST API, or WebSocket).
|
|
7
|
+
|
|
8
|
+
---
|
|
4
9
|
|
|
5
10
|
## Installation
|
|
6
11
|
|
|
7
12
|
```bash
|
|
8
|
-
# From GitHub private registry
|
|
9
13
|
npm install @banbox/chat
|
|
10
|
-
|
|
11
|
-
# Or local development link
|
|
12
|
-
npm install ../banbox-chat
|
|
13
14
|
```
|
|
14
15
|
|
|
16
|
+
CSS is **auto-injected** — no manual import needed.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
15
20
|
## Requirements
|
|
16
21
|
|
|
17
22
|
| Peer dep | Version |
|
|
18
|
-
|
|
19
|
-
| react | ≥ 18 |
|
|
20
|
-
| react-dom | ≥ 18 |
|
|
21
|
-
| framer-motion | ≥ 10 |
|
|
23
|
+
|---|---|
|
|
24
|
+
| `react` | ≥ 18 |
|
|
25
|
+
| `react-dom` | ≥ 18 |
|
|
26
|
+
| `framer-motion` | ≥ 10 |
|
|
27
|
+
| `lottie-react` | ≥ 2 |
|
|
22
28
|
|
|
23
29
|
---
|
|
24
30
|
|
|
25
31
|
## Quick Start (Vite / React)
|
|
26
32
|
|
|
27
|
-
### 1.
|
|
33
|
+
### 1. Create your adapter
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// src/components/chat/demoData.ts
|
|
37
|
+
import type { ChatAdapter } from "@banbox/chat";
|
|
38
|
+
|
|
39
|
+
export const createDemoChatAdapter = (): ChatAdapter => ({
|
|
40
|
+
threads: {
|
|
41
|
+
list: () => cache.threads,
|
|
42
|
+
subscribe: (cb) => {
|
|
43
|
+
socket.on("threads:update", cb);
|
|
44
|
+
return () => socket.off("threads:update", cb);
|
|
45
|
+
},
|
|
46
|
+
pin: (id, pinned) => api.patch(`/threads/${id}`, { pinned }),
|
|
47
|
+
delete: (id) => api.delete(`/threads/${id}`),
|
|
48
|
+
markRead: (id) => api.post(`/threads/${id}/read`),
|
|
49
|
+
},
|
|
50
|
+
messages: {
|
|
51
|
+
list: (tid) => cache.messages[tid] ?? [],
|
|
52
|
+
subscribe: (tid, cb) => {
|
|
53
|
+
socket.on(`messages:${tid}`, cb);
|
|
54
|
+
return () => socket.off(`messages:${tid}`, cb);
|
|
55
|
+
},
|
|
56
|
+
send: (tid, payload) => api.post(`/threads/${tid}/messages`, payload),
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Mount `ChatRoot` once at the app root
|
|
28
62
|
|
|
29
63
|
```tsx
|
|
30
|
-
// main.tsx
|
|
64
|
+
// src/main.tsx
|
|
31
65
|
import { ChatUIProvider, ChatRoot } from "@banbox/chat";
|
|
32
66
|
import { createDemoChatAdapter } from "./components/chat/demoData";
|
|
33
|
-
import "
|
|
67
|
+
import { showToast } from "./utils/toast";
|
|
34
68
|
|
|
69
|
+
// Create adapter once — outside render
|
|
35
70
|
const adapter = createDemoChatAdapter();
|
|
36
71
|
|
|
37
72
|
createRoot(document.getElementById("root")!).render(
|
|
@@ -39,8 +74,17 @@ createRoot(document.getElementById("root")!).render(
|
|
|
39
74
|
<App />
|
|
40
75
|
<ChatRoot
|
|
41
76
|
adapter={adapter}
|
|
77
|
+
theme="marketplace"
|
|
78
|
+
|
|
79
|
+
// ── Footer toolbar: allow-list per popup ──────────────────────────
|
|
80
|
+
// Default (when omitted): ["attachment", "emoji", "translate"]
|
|
81
|
+
// Available keys: "attachment" | "emoji" | "businessCard" | "addressCard" | "translate"
|
|
82
|
+
inboxFooterActions={["attachment", "emoji", "translate"]}
|
|
83
|
+
singleFooterActions={["attachment", "emoji", "translate"]}
|
|
84
|
+
// ─────────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
42
86
|
uiCallbacks={{
|
|
43
|
-
showToast
|
|
87
|
+
showToast,
|
|
44
88
|
onNavigate: ({ type, id }) => navigate(`/${type}s/${id}`),
|
|
45
89
|
}}
|
|
46
90
|
/>
|
|
@@ -48,17 +92,30 @@ createRoot(document.getElementById("root")!).render(
|
|
|
48
92
|
);
|
|
49
93
|
```
|
|
50
94
|
|
|
51
|
-
###
|
|
95
|
+
### 3. Open the chat from anywhere in the app
|
|
52
96
|
|
|
53
97
|
```tsx
|
|
54
98
|
import { useChatUI } from "@banbox/chat";
|
|
55
99
|
|
|
56
|
-
function OrderPage() {
|
|
57
|
-
const { openSingle } = useChatUI();
|
|
100
|
+
function OrderPage({ orderId }: { orderId: string }) {
|
|
101
|
+
const { openSingle, openInbox } = useChatUI();
|
|
102
|
+
|
|
58
103
|
return (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
104
|
+
<>
|
|
105
|
+
{/* Open inbox (all conversations) */}
|
|
106
|
+
<button onClick={() => openInbox()}>Messages</button>
|
|
107
|
+
|
|
108
|
+
{/* Open single chat linked to this order */}
|
|
109
|
+
<button
|
|
110
|
+
onClick={() =>
|
|
111
|
+
openSingle({
|
|
112
|
+
reference: { kind: "order", id: orderId, title: `Order #${orderId}` },
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
>
|
|
116
|
+
Chat with Buyer
|
|
117
|
+
</button>
|
|
118
|
+
</>
|
|
62
119
|
);
|
|
63
120
|
}
|
|
64
121
|
```
|
|
@@ -67,27 +124,32 @@ function OrderPage() {
|
|
|
67
124
|
|
|
68
125
|
## Quick Start (Next.js App Router)
|
|
69
126
|
|
|
70
|
-
### 1. Create a client wrapper
|
|
127
|
+
### 1. Create a client wrapper
|
|
71
128
|
|
|
72
129
|
```tsx
|
|
73
130
|
// components/ChatWrapper.tsx
|
|
74
131
|
"use client";
|
|
75
132
|
import { ChatUIProvider, ChatRoot } from "@banbox/chat";
|
|
76
|
-
import {
|
|
133
|
+
import { createApiChatAdapter } from "@/lib/chat/apiAdapter";
|
|
77
134
|
|
|
78
|
-
const adapter =
|
|
135
|
+
const adapter = createApiChatAdapter();
|
|
79
136
|
|
|
80
137
|
export function ChatWrapper({ children }: { children: React.ReactNode }) {
|
|
81
138
|
return (
|
|
82
139
|
<ChatUIProvider>
|
|
83
140
|
{children}
|
|
84
|
-
<ChatRoot
|
|
141
|
+
<ChatRoot
|
|
142
|
+
adapter={adapter}
|
|
143
|
+
theme="marketplace"
|
|
144
|
+
inboxFooterActions={["attachment", "emoji", "translate"]}
|
|
145
|
+
singleFooterActions={["attachment", "emoji", "translate"]}
|
|
146
|
+
/>
|
|
85
147
|
</ChatUIProvider>
|
|
86
148
|
);
|
|
87
149
|
}
|
|
88
150
|
```
|
|
89
151
|
|
|
90
|
-
### 2. Add to
|
|
152
|
+
### 2. Add to root layout
|
|
91
153
|
|
|
92
154
|
```tsx
|
|
93
155
|
// app/layout.tsx
|
|
@@ -104,104 +166,331 @@ export default function RootLayout({ children }) {
|
|
|
104
166
|
}
|
|
105
167
|
```
|
|
106
168
|
|
|
107
|
-
### 3. Open from any Client Component
|
|
108
|
-
|
|
109
|
-
```tsx
|
|
110
|
-
"use client";
|
|
111
|
-
import { useChatUI } from "@banbox/chat";
|
|
112
|
-
|
|
113
|
-
export function ChatButton() {
|
|
114
|
-
const { openInbox } = useChatUI();
|
|
115
|
-
return <button onClick={() => openInbox()}>Messages</button>;
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
169
|
---
|
|
120
170
|
|
|
121
171
|
## Tailwind CSS Setup
|
|
122
172
|
|
|
123
|
-
The package uses Tailwind utility classes. Add the package source to your Tailwind content config so the classes are included in your build:
|
|
124
|
-
|
|
125
173
|
```ts
|
|
126
|
-
//
|
|
174
|
+
// vite.config.ts (or tailwind.config.ts)
|
|
127
175
|
export default {
|
|
128
176
|
content: [
|
|
129
177
|
"./src/**/*.{ts,tsx}",
|
|
130
|
-
//
|
|
178
|
+
// Include @banbox/chat source for Tailwind class scanning
|
|
131
179
|
"./node_modules/@banbox/chat/src/**/*.{ts,tsx}",
|
|
132
180
|
],
|
|
133
|
-
}
|
|
181
|
+
};
|
|
134
182
|
```
|
|
135
183
|
|
|
136
184
|
---
|
|
137
185
|
|
|
138
|
-
##
|
|
186
|
+
## Local Development (Hybrid Mode)
|
|
139
187
|
|
|
140
|
-
The
|
|
188
|
+
The seller / host app automatically detects the local `banbox-chat` folder and uses it during development, while using the published npm package in production builds.
|
|
141
189
|
|
|
142
190
|
```ts
|
|
143
|
-
//
|
|
144
|
-
import
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
},
|
|
157
|
-
messages: {
|
|
158
|
-
list: (tid) => cache.messages[tid] ?? [],
|
|
159
|
-
subscribe: (tid, cb) => {
|
|
160
|
-
socket.on(`messages:${tid}`, cb);
|
|
161
|
-
return () => socket.off(`messages:${tid}`, cb);
|
|
162
|
-
},
|
|
163
|
-
send: (tid, payload) => api.post(`/threads/${tid}/messages`, payload),
|
|
191
|
+
// vite.config.ts — hybrid alias (already set up in banbox-seller-react)
|
|
192
|
+
import fs from "fs";
|
|
193
|
+
import path from "path";
|
|
194
|
+
|
|
195
|
+
const localChatPath = path.resolve(__dirname, "../banbox-chat");
|
|
196
|
+
const useLocalChat = fs.existsSync(localChatPath);
|
|
197
|
+
|
|
198
|
+
export default defineConfig({
|
|
199
|
+
resolve: {
|
|
200
|
+
alias: useLocalChat
|
|
201
|
+
? { "@banbox/chat": path.join(localChatPath, "dist/index.js") }
|
|
202
|
+
: {},
|
|
203
|
+
dedupe: ["react", "react-dom", "framer-motion", "lottie-react"],
|
|
164
204
|
},
|
|
165
205
|
});
|
|
166
206
|
```
|
|
167
207
|
|
|
208
|
+
| Mode | Source |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `npm run dev` (local folder exists) | `../banbox-chat/dist/` |
|
|
211
|
+
| Production build / CI | `node_modules/@banbox/chat` |
|
|
212
|
+
|
|
168
213
|
---
|
|
169
214
|
|
|
170
|
-
##
|
|
215
|
+
## `ChatRoot` Props
|
|
171
216
|
|
|
172
|
-
|
|
217
|
+
```ts
|
|
218
|
+
<ChatRoot
|
|
219
|
+
adapter={adapter} // Required — your ChatAdapter implementation
|
|
220
|
+
theme="marketplace" // Optional — "marketplace" | "admin" | custom object
|
|
221
|
+
uiCallbacks={...} // Optional — toast, navigate, kebab menu
|
|
222
|
+
inboxFooterActions={[...]} // Optional — allow-list for InboxPopup toolbar
|
|
223
|
+
singleFooterActions={[...]} // Optional — allow-list for SinglePopup toolbar
|
|
224
|
+
/>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### `theme` prop
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
// Named themes
|
|
231
|
+
<ChatRoot theme="marketplace" /> // orange primary (#ff5300)
|
|
232
|
+
<ChatRoot theme="admin" /> // black primary (#1a1a1a)
|
|
233
|
+
|
|
234
|
+
// Custom theme object
|
|
235
|
+
<ChatRoot theme={{ primary: "#7C3AED", primaryActive: "#6D28D9" }} />
|
|
236
|
+
```
|
|
173
237
|
|
|
174
|
-
|
|
175
|
-
|-----------|-------------|
|
|
176
|
-
| `<ChatRoot adapter uiCallbacks? />` | Main entry point — mounts the chat popup |
|
|
177
|
-
| `<ChatUIProvider>` | Context provider — wrap your app with this |
|
|
238
|
+
### `inboxFooterActions` / `singleFooterActions`
|
|
178
239
|
|
|
179
|
-
|
|
240
|
+
Controls which toolbar buttons appear in the footer of each popup variant.
|
|
180
241
|
|
|
181
|
-
|
|
|
182
|
-
|
|
183
|
-
| `
|
|
242
|
+
| Key | Button | Default |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| `"attachment"` | 📎 Attach file / image | ✅ Yes |
|
|
245
|
+
| `"emoji"` | 😊 Emoji picker | ✅ Yes |
|
|
246
|
+
| `"translate"` | 🌐 Translation settings | ✅ Yes |
|
|
247
|
+
| `"businessCard"` | 👤 Share business card | ❌ Opt-in |
|
|
248
|
+
| `"addressCard"` | 📍 Share delivery address | ❌ Opt-in |
|
|
184
249
|
|
|
185
|
-
|
|
250
|
+
```tsx
|
|
251
|
+
// Default (omit the prop — shows attachment, emoji, translate only)
|
|
252
|
+
<ChatRoot adapter={adapter} />
|
|
253
|
+
|
|
254
|
+
// Add location sharing to SinglePopup only
|
|
255
|
+
<ChatRoot
|
|
256
|
+
inboxFooterActions={["attachment", "emoji", "translate"]}
|
|
257
|
+
singleFooterActions={["attachment", "emoji", "translate", "addressCard"]}
|
|
258
|
+
/>
|
|
259
|
+
|
|
260
|
+
// Full toolbar (all buttons)
|
|
261
|
+
<ChatRoot
|
|
262
|
+
inboxFooterActions={["attachment", "emoji", "businessCard", "addressCard", "translate"]}
|
|
263
|
+
singleFooterActions={["attachment", "emoji", "businessCard", "addressCard", "translate"]}
|
|
264
|
+
/>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### `uiCallbacks` prop
|
|
186
268
|
|
|
187
269
|
```ts
|
|
270
|
+
uiCallbacks={{
|
|
271
|
+
// Show a toast from your app's existing toast system
|
|
272
|
+
showToast: ({ type, title, message }) => toast[type](title),
|
|
273
|
+
|
|
274
|
+
// Navigate when user clicks "View Order" / "View Inquiry"
|
|
275
|
+
onNavigate: ({ type, id }) => navigate(`/${type}s/${id}`),
|
|
276
|
+
|
|
277
|
+
// (Optional) Replace the default ⋮ kebab menu with your own component
|
|
278
|
+
renderKebabMenu: ({ pinned, onPinToggle, onDelete }) => (
|
|
279
|
+
<MyDropdownMenu pinned={pinned} onPin={onPinToggle} onDelete={onDelete} />
|
|
280
|
+
),
|
|
281
|
+
}}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## `useChatUI()` Hook
|
|
287
|
+
|
|
288
|
+
```tsx
|
|
188
289
|
const {
|
|
189
|
-
openInbox,
|
|
190
|
-
openSingle,
|
|
191
|
-
close,
|
|
192
|
-
selectThread,
|
|
193
|
-
isOpen,
|
|
194
|
-
variant,
|
|
290
|
+
openInbox, // () => void — open inbox (all threads)
|
|
291
|
+
openSingle, // (opts?) => void — open single chat
|
|
292
|
+
close, // () => void — close popup
|
|
293
|
+
selectThread, // (id: string | null) => void
|
|
294
|
+
isOpen, // boolean
|
|
295
|
+
variant, // "inbox" | "single"
|
|
296
|
+
reference, // Reference | undefined
|
|
297
|
+
selectedThreadId, // string | null
|
|
195
298
|
} = useChatUI();
|
|
196
299
|
```
|
|
197
300
|
|
|
198
|
-
### `
|
|
301
|
+
### `openInbox(opts?)`
|
|
199
302
|
|
|
200
303
|
```ts
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
304
|
+
// Open inbox with all threads
|
|
305
|
+
openInbox();
|
|
306
|
+
|
|
307
|
+
// Open inbox pre-filtered to a reference kind
|
|
308
|
+
openInbox({ reference: { kind: "order" } });
|
|
309
|
+
|
|
310
|
+
// Open inbox with a specific thread pre-selected
|
|
311
|
+
openInbox({ threadId: "t4" });
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### `openSingle(opts?)`
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
// Plain single chat
|
|
318
|
+
openSingle();
|
|
319
|
+
|
|
320
|
+
// Linked to an order — shows "View Order" bar in header
|
|
321
|
+
openSingle({ reference: { kind: "order", id: "ORD-123", title: "Order #123" } });
|
|
322
|
+
|
|
323
|
+
// Linked to an inquiry
|
|
324
|
+
openSingle({ reference: { kind: "inquiry", id: "INQ-456" } });
|
|
325
|
+
|
|
326
|
+
// Linked to a quotation
|
|
327
|
+
openSingle({ reference: { kind: "quotation", id: "QUOT-789" } });
|
|
328
|
+
|
|
329
|
+
// Product inquiry
|
|
204
330
|
openSingle({ reference: { kind: "productInquiry", id: "PI-101" } });
|
|
331
|
+
|
|
332
|
+
// With metadata for the header (title, subtitle, online status)
|
|
333
|
+
openSingle({
|
|
334
|
+
reference: {
|
|
335
|
+
kind: "order",
|
|
336
|
+
id: "ORD-123",
|
|
337
|
+
meta: { title: "Emon Hasan", subtitle: "Customer", online: true },
|
|
338
|
+
},
|
|
339
|
+
});
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## `ChatAdapter` Interface
|
|
345
|
+
|
|
346
|
+
Full interface contract:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
import type { ChatAdapter } from "@banbox/chat";
|
|
350
|
+
|
|
351
|
+
const adapter: ChatAdapter = {
|
|
352
|
+
threads: {
|
|
353
|
+
// Returns current thread list (sync — keep a local cache)
|
|
354
|
+
list: (reference?) => Thread[],
|
|
355
|
+
|
|
356
|
+
// Subscribe to thread changes — returns unsubscribe function
|
|
357
|
+
subscribe: (cb: () => void) => () => void,
|
|
358
|
+
|
|
359
|
+
// Pin / unpin a thread
|
|
360
|
+
pin: (id: string, pinned: boolean) => Promise<void> | void,
|
|
361
|
+
|
|
362
|
+
// Delete a thread
|
|
363
|
+
delete: (id: string) => Promise<void> | void,
|
|
364
|
+
|
|
365
|
+
// Mark thread as read (optional)
|
|
366
|
+
markRead?: (id: string) => Promise<void> | void,
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
messages: {
|
|
370
|
+
// Returns messages for a thread (sync — keep a local cache)
|
|
371
|
+
list: (threadId: string) => Message[],
|
|
372
|
+
|
|
373
|
+
// Subscribe to new messages for a thread (optional)
|
|
374
|
+
subscribe?: (threadId: string, cb: () => void) => () => void,
|
|
375
|
+
|
|
376
|
+
// Send a message — handles all payload types
|
|
377
|
+
send: (threadId: string, payload: SendPayload) => Promise<void> | void,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### `SendPayload` — all message types
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
// Text message
|
|
386
|
+
{ type: "text"; text: string; replyTo?: MessageRef }
|
|
387
|
+
|
|
388
|
+
// Voice/audio message
|
|
389
|
+
{ type: "voice"; src?: string; durationSec: number; durationText: string; replyTo?: MessageRef }
|
|
390
|
+
|
|
391
|
+
// Images and/or files only
|
|
392
|
+
{ type: "attachments"; images: string[]; files: MessageFile[]; replyTo?: MessageRef }
|
|
393
|
+
|
|
394
|
+
// Text + images/files combined
|
|
395
|
+
{ type: "combined"; text: string; images: string[]; files: MessageFile[]; replyTo?: MessageRef }
|
|
396
|
+
|
|
397
|
+
// Business card
|
|
398
|
+
{ type: "businessCard"; card: BusinessCard; replyTo?: MessageRef }
|
|
399
|
+
|
|
400
|
+
// Address / delivery card
|
|
401
|
+
{ type: "addressCard"; card: AddressCard; replyTo?: MessageRef }
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### Adding a new message type
|
|
405
|
+
|
|
406
|
+
1. Add a new variant to `SendPayload` in `banbox-chat/src/types/index.ts`
|
|
407
|
+
2. Handle it in your adapter's `messages.send()` implementation
|
|
408
|
+
3. Optionally add a new UI component in `banbox-chat/src/ui/message-items/`
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Domain Types
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import type {
|
|
416
|
+
Thread, // Conversation thread
|
|
417
|
+
Message, // A single chat message
|
|
418
|
+
SendPayload, // Discriminated union of all sendable message types
|
|
419
|
+
MessageFile, // File attachment
|
|
420
|
+
MessageAudio, // Audio clip
|
|
421
|
+
MessageRef, // Reply-to reference
|
|
422
|
+
BusinessCard, // Business card data
|
|
423
|
+
AddressCard, // Delivery address data
|
|
424
|
+
ThreadStatus, // "seen" | "delivered" | { kind: "new", count: number }
|
|
425
|
+
Reference, // Context link: order | inquiry | quotation | productInquiry
|
|
426
|
+
} from "@banbox/chat";
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Individual UI Components (Advanced)
|
|
432
|
+
|
|
433
|
+
Export individual components for custom layouts:
|
|
434
|
+
|
|
435
|
+
```tsx
|
|
436
|
+
import {
|
|
437
|
+
ChatFooter,
|
|
438
|
+
ChatHeader,
|
|
439
|
+
ChatIdentity,
|
|
440
|
+
ChatMessageItem,
|
|
441
|
+
ChatScroll,
|
|
442
|
+
ChatThreadItem,
|
|
443
|
+
ChatListHeader,
|
|
444
|
+
ChatInquiryBar,
|
|
445
|
+
TypingIndicator,
|
|
446
|
+
ReplyCard,
|
|
447
|
+
ChatSpinner,
|
|
448
|
+
ChatKebabMenu,
|
|
449
|
+
} from "@banbox/chat";
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### `ChatFooter` standalone
|
|
453
|
+
|
|
454
|
+
```tsx
|
|
455
|
+
import { ChatFooter, DEFAULT_FOOTER_ACTIONS } from "@banbox/chat";
|
|
456
|
+
|
|
457
|
+
<ChatFooter
|
|
458
|
+
onSend={(payload) => adapter.messages.send(threadId, payload)}
|
|
459
|
+
onAfterSend={() => setRev(v => v + 1)}
|
|
460
|
+
|
|
461
|
+
// Allow-list — only these buttons appear
|
|
462
|
+
enabledActions={["attachment", "emoji", "translate"]}
|
|
463
|
+
|
|
464
|
+
replyTo={replyTo}
|
|
465
|
+
clearReply={() => setReplyTo(undefined)}
|
|
466
|
+
/>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
---
|
|
470
|
+
|
|
471
|
+
## Translation Support
|
|
472
|
+
|
|
473
|
+
The translate button (🌐) in the footer opens a Translation Settings modal where users can configure language preferences. To integrate your own translation API, pass `onTranslate` to `ChatMessageItem`:
|
|
474
|
+
|
|
475
|
+
```tsx
|
|
476
|
+
<ChatMessageItem
|
|
477
|
+
// ...
|
|
478
|
+
onTranslate={(originalText) => {
|
|
479
|
+
// Return translated string or undefined (will keep original if undefined)
|
|
480
|
+
return myTranslationService.translate(originalText, "bn");
|
|
481
|
+
}}
|
|
482
|
+
/>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
> When `onTranslate` is omitted, the translate button is still visible but acts as a no-op toggle (good for demo mode).
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## Exported Constants
|
|
490
|
+
|
|
491
|
+
```ts
|
|
492
|
+
import { DEFAULT_FOOTER_ACTIONS } from "@banbox/chat";
|
|
493
|
+
// → ["attachment", "emoji", "translate"]
|
|
205
494
|
```
|
|
206
495
|
|
|
207
496
|
---
|
|
@@ -209,7 +498,33 @@ openSingle({ reference: { kind: "productInquiry", id: "PI-101" } });
|
|
|
209
498
|
## Build
|
|
210
499
|
|
|
211
500
|
```bash
|
|
212
|
-
npm run build
|
|
213
|
-
npm run build:watch
|
|
214
|
-
npm run typecheck
|
|
501
|
+
npm run build # compile to dist/
|
|
502
|
+
npm run build:watch # watch mode
|
|
503
|
+
npm run typecheck # tsc --noEmit
|
|
215
504
|
```
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## Changelog
|
|
509
|
+
|
|
510
|
+
### v1.0.12
|
|
511
|
+
- ♻️ `hiddenActionKeys` → `enabledActions` allow-list in `ChatFooter` (breaking rename in package internals, not host-facing)
|
|
512
|
+
- ✨ `inboxFooterActions` and `singleFooterActions` props on `ChatRoot` — per-popup toolbar control
|
|
513
|
+
- `businessCard` and `addressCard` are now **opt-in** (not shown by default)
|
|
514
|
+
|
|
515
|
+
### v1.0.11
|
|
516
|
+
- Added `hiddenActionKeys` prop threading through `ChatRoot` → `InboxPopup/SinglePopup` → `ChatFooter`
|
|
517
|
+
|
|
518
|
+
### v1.0.10
|
|
519
|
+
- 🐛 **Critical:** `SinglePopup` now subscribes to thread list (was fetched once on mount — real-time updates were missing)
|
|
520
|
+
- 🐛 `coalesceThreadId` no longer has hardcoded `"t4"` demo dependency — uses pure reference matching
|
|
521
|
+
- 🐛 `isOnline = true` hardcoded removed from `ChatMessageItem` avatar
|
|
522
|
+
- ✨ `onTranslate` prop on `ChatMessageItem` — replaces internal `toBanglaDemo` hardcoded translator
|
|
523
|
+
- 🐛 `markRead` now fires on **initial popup open**, not only on thread switch
|
|
524
|
+
- ♻️ Shared `utils/theme.ts` — `GRADIENT_BORDER`, `getThemeAttr`, `getThemeVars` extracted from `InboxPopup`/`SinglePopup`
|
|
525
|
+
|
|
526
|
+
### v1.0.9
|
|
527
|
+
- Initial public release
|
|
528
|
+
- Full adapter pattern, pub/sub threads/messages
|
|
529
|
+
- InboxPopup + SinglePopup
|
|
530
|
+
- All message types: text, voice, images, files, businessCard, addressCard
|
package/dist/index.cjs
CHANGED
|
@@ -4088,6 +4088,34 @@ function toRef(m) {
|
|
|
4088
4088
|
audio: m.audio
|
|
4089
4089
|
};
|
|
4090
4090
|
}
|
|
4091
|
+
var avatarBgByInitial2 = {
|
|
4092
|
+
a: "#FFE4E4",
|
|
4093
|
+
b: "#E4F0FF",
|
|
4094
|
+
c: "#E4FFE9",
|
|
4095
|
+
d: "#FFF4E4",
|
|
4096
|
+
e: "#F4E4FF",
|
|
4097
|
+
f: "#FFE4F4",
|
|
4098
|
+
g: "#E4FFFF",
|
|
4099
|
+
h: "#FFFFE4",
|
|
4100
|
+
i: "#E4E4FF",
|
|
4101
|
+
j: "#FFE9E4",
|
|
4102
|
+
k: "#E4FFE4",
|
|
4103
|
+
l: "#FFE4EA",
|
|
4104
|
+
m: "#E8E4FF",
|
|
4105
|
+
n: "#E4F8FF",
|
|
4106
|
+
o: "#FFF0E4",
|
|
4107
|
+
p: "#F0FFE4",
|
|
4108
|
+
q: "#FFE4F8",
|
|
4109
|
+
r: "#E4FFEC",
|
|
4110
|
+
s: "#FFEEE4",
|
|
4111
|
+
t: "#E4EAFF",
|
|
4112
|
+
u: "#F8FFE4",
|
|
4113
|
+
v: "#FFE4EE",
|
|
4114
|
+
w: "#E4FFFA",
|
|
4115
|
+
x: "#FFF8E4",
|
|
4116
|
+
y: "#EAE4FF",
|
|
4117
|
+
z: "#E4FFF0"
|
|
4118
|
+
};
|
|
4091
4119
|
var SinglePopup = ({ adapter, uiCallbacks, theme, footerActions }) => {
|
|
4092
4120
|
const { close, reference } = useChatUI();
|
|
4093
4121
|
const { isOpen: isGalleryOpen, closeGallery } = useGallery();
|
|
@@ -4108,10 +4136,12 @@ var SinglePopup = ({ adapter, uiCallbacks, theme, footerActions }) => {
|
|
|
4108
4136
|
const activeThread = threads.find((t) => t.id === activeId);
|
|
4109
4137
|
const isVerified = activeThread?.badge === true;
|
|
4110
4138
|
const meta = reference?.meta ?? {};
|
|
4111
|
-
const
|
|
4139
|
+
const avatarSrc = meta.avatarSrc ?? activeThread?.avatarSrc;
|
|
4140
|
+
const initial = meta.initial ?? activeThread?.avatarText ?? (activeThread?.title ?? meta.title ?? "?").charAt(0).toUpperCase();
|
|
4141
|
+
const avatarBg = avatarBgByInitial2[initial.toLowerCase()] ?? "#E4F0FF";
|
|
4112
4142
|
const title = meta.title ?? activeThread?.title ?? "Unknown";
|
|
4113
|
-
const online = meta.online ?? activeThread?.online ??
|
|
4114
|
-
const subtitle = meta.subtitle ?? "Customer";
|
|
4143
|
+
const online = meta.online ?? activeThread?.online ?? false;
|
|
4144
|
+
const subtitle = meta.subtitle ?? activeThread?.subTitle ?? "Customer";
|
|
4115
4145
|
const [messages, setMessages] = React16__default.default.useState(
|
|
4116
4146
|
() => activeId ? adapter.messages.list(activeId) : []
|
|
4117
4147
|
);
|
|
@@ -4184,11 +4214,23 @@ var SinglePopup = ({ adapter, uiCallbacks, theme, footerActions }) => {
|
|
|
4184
4214
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-[64px] shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4185
4215
|
ChatHeader,
|
|
4186
4216
|
{
|
|
4187
|
-
left: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4217
|
+
left: avatarSrc ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4188
4218
|
ChatIdentity_default,
|
|
4189
4219
|
{
|
|
4190
4220
|
variant: "avatar",
|
|
4191
|
-
src:
|
|
4221
|
+
src: avatarSrc,
|
|
4222
|
+
online,
|
|
4223
|
+
title,
|
|
4224
|
+
subtitle,
|
|
4225
|
+
verified: isVerified,
|
|
4226
|
+
subtitleVariant: "muted"
|
|
4227
|
+
}
|
|
4228
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4229
|
+
ChatIdentity_default,
|
|
4230
|
+
{
|
|
4231
|
+
variant: "initial",
|
|
4232
|
+
initial,
|
|
4233
|
+
bg: avatarBg,
|
|
4192
4234
|
online,
|
|
4193
4235
|
title,
|
|
4194
4236
|
subtitle,
|