@realtimexsco/live-chat 1.2.0 → 1.3.1
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/LICENSE +1 -1
- package/README.md +1443 -775
- package/dist/index.cjs +14588 -4815
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +824 -111
- package/dist/index.d.ts +824 -111
- package/dist/index.mjs +14675 -4912
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +578 -11
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,112 +1,200 @@
|
|
|
1
1
|
# @realtimexsco/live-chat
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Complete real-time chat UI for **React** and **Next.js** — DMs, groups, reactions, replies, forwarding, pins, stars, attachments, search, light/dark theme, locale/timezone, and **full UI customization** (replace the whole chat or any single component).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
| | |
|
|
6
|
+
|---|---|
|
|
7
|
+
| **Package name** | `@realtimexsco/live-chat` |
|
|
8
|
+
| **Registry** | [npm](https://www.npmjs.com/) (public — no `.npmrc` required) |
|
|
9
|
+
| **Styles entry** | `@realtimexsco/live-chat/styles.css` |
|
|
10
|
+
| **Requires** | React 17+, Tailwind CSS **v4**, Zustand 4+ |
|
|
11
|
+
| **Current version** | `1.1.0` |
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
`<ChatLayout />` for a composable shell with section toggles, or build your own
|
|
11
|
-
layout from the individual pieces (`<Chat>`, `<ChannelList>`, `<MessageList>`, …).
|
|
13
|
+
Socket.IO client, REST API client, Zustand store, Radix UI, emoji picker, and all chat UI are **bundled inside the package**. Your app only installs a few peer dependencies.
|
|
12
14
|
|
|
13
15
|
---
|
|
14
16
|
|
|
15
|
-
##
|
|
17
|
+
## Start here (5-minute overview)
|
|
18
|
+
|
|
19
|
+
1. **Install** the package + peers (`react`, `react-dom`, `zustand`, `tailwindcss` v4).
|
|
20
|
+
2. **Import CSS** — `@import "@realtimexsco/live-chat/styles.css"` and add Tailwind `@source` for the package `dist` files.
|
|
21
|
+
3. **Create** `chat.config.ts` with `clientId`, `accessToken`, `loggedUserDetails`, `apiUrl`, `socketUrl`.
|
|
22
|
+
4. **Render** `<ChatMain {...chatConfig} />` — the package includes a built-in viewport wrapper. For admin templates use `viewportHeight="calc(100dvh - 4rem)"`; for full-page chat use `viewportHeight="screen"`.
|
|
23
|
+
5. **Optional** — pass `components`, `classNames`, or `features` to customize. **Omit any slot → package default is used automatically.**
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
// Full page
|
|
27
|
+
<ChatMain {...chatConfig} viewportHeight="screen" />
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- [How it works](#how-it-works)
|
|
28
|
-
- [`Chat` provider props](#chat-provider-props)
|
|
29
|
-
- [Layout customization](#layout-customization)
|
|
30
|
-
- [Exported components & hooks](#exported-components--hooks)
|
|
31
|
-
- [Theming](#theming)
|
|
32
|
-
- [Troubleshooting](#troubleshooting)
|
|
33
|
-
- [Checklist](#checklist)
|
|
34
|
-
- [License](#license)
|
|
29
|
+
// Admin shell with 4rem header (Elstar, Metronic, etc.)
|
|
30
|
+
<ChatMain {...chatConfig} viewportHeight="calc(100dvh - 4rem)" />
|
|
31
|
+
|
|
32
|
+
// Inside your own bounded flex column (parent must have height + min-h-0)
|
|
33
|
+
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
34
|
+
<ChatMain {...chatConfig} viewportHeight="full" />
|
|
35
|
+
</div>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Do not** pass empty or placeholder components for slots you want to keep default — simply omit those keys from `components`.
|
|
35
39
|
|
|
36
40
|
---
|
|
37
41
|
|
|
38
|
-
##
|
|
42
|
+
## Table of contents
|
|
43
|
+
|
|
44
|
+
1. [Chat features included](#chat-features-included)
|
|
45
|
+
2. [What you install vs what is bundled](#what-you-install-vs-what-is-bundled)
|
|
46
|
+
3. [Backend credentials](#backend-credentials)
|
|
47
|
+
4. [Installation step by step](#installation-step-by-step)
|
|
48
|
+
5. [Tailwind CSS v4 setup (required)](#tailwind-css-v4-setup-required)
|
|
49
|
+
6. [Project config file](#project-config-file)
|
|
50
|
+
7. [Quick start — minimum working chat](#quick-start--minimum-working-chat)
|
|
51
|
+
8. [Complete ChatMain example](#complete-chatmain-example)
|
|
52
|
+
9. [Customization — three ways](#customization--three-ways)
|
|
53
|
+
10. [Default component fallback (important)](#default-component-fallback-important)
|
|
54
|
+
11. [Which slots are wired](#which-slots-are-wired)
|
|
55
|
+
12. [Production integration pattern (admin templates)](#production-integration-pattern-admin-templates)
|
|
56
|
+
13. [Customization examples (Case 1–3)](#customization-examples-case-13)
|
|
57
|
+
14. [Phone & video calling](#phone--video-calling)
|
|
58
|
+
15. [Theme, light/dark, date/time](#theme-lightdark-datetime)
|
|
59
|
+
16. [Layout sections (`layout` prop)](#layout-sections-layout-prop)
|
|
60
|
+
17. [All component slots + examples](#all-component-slots--examples)
|
|
61
|
+
18. [All classNames keys](#all-classnames-keys)
|
|
62
|
+
19. [Wrapping package defaults (`packageDefaultComponents`)](#wrapping-package-defaults-packagedefaultcomponents)
|
|
63
|
+
20. [API query params](#api-query-params)
|
|
64
|
+
21. [ChatMain props reference](#chatmain-props-reference)
|
|
65
|
+
22. [Chat provider (composable mode)](#chat-provider-composable-mode)
|
|
66
|
+
23. [Hooks & store](#hooks--store)
|
|
67
|
+
24. [React (Vite) — full setup](#react-vite--full-setup)
|
|
68
|
+
25. [Next.js — full setup](#nextjs--full-setup)
|
|
69
|
+
26. [Environment variables](#environment-variables)
|
|
70
|
+
27. [Dev proxy (CORS)](#dev-proxy-cors)
|
|
71
|
+
28. [Architecture](#architecture)
|
|
72
|
+
29. [All exports](#all-exports)
|
|
73
|
+
30. [Troubleshooting](#troubleshooting)
|
|
74
|
+
31. [Setup checklist](#setup-checklist)
|
|
75
|
+
32. [Package development (maintainers)](#package-development-maintainers)
|
|
76
|
+
33. [License](#license)
|
|
77
|
+
|
|
78
|
+
---
|
|
39
79
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
-
|
|
47
|
-
|
|
80
|
+
## Chat features included
|
|
81
|
+
|
|
82
|
+
| Feature | Supported |
|
|
83
|
+
|---------|-----------|
|
|
84
|
+
| Direct messages (1-to-1) | Yes |
|
|
85
|
+
| Group chats | Yes |
|
|
86
|
+
| Real-time messages (Socket.IO) | Yes |
|
|
87
|
+
| Optimistic send (instant UI) | Yes |
|
|
88
|
+
| Reactions, replies, forward | Yes |
|
|
89
|
+
| Edit / delete (me / everyone) | Yes |
|
|
90
|
+
| Pin & star messages | Yes |
|
|
91
|
+
| Attachments (images, files) | Yes |
|
|
92
|
+
| In-chat message search | Yes |
|
|
93
|
+
| Typing indicators | Yes |
|
|
94
|
+
| Read receipts (sent / delivered / read) | Yes |
|
|
95
|
+
| New chat / user list modal | Yes |
|
|
96
|
+
| Group members, admins, permissions | Yes |
|
|
97
|
+
| Light / dark mode | Yes |
|
|
98
|
+
| Locale & timezone formatting | Yes |
|
|
99
|
+
| Phone / video call hooks | Yes (you wire WebRTC) |
|
|
100
|
+
| Full UI customization | Yes |
|
|
48
101
|
|
|
49
102
|
---
|
|
50
103
|
|
|
51
|
-
##
|
|
104
|
+
## What you install vs what is bundled
|
|
105
|
+
|
|
106
|
+
### You must install (peer dependencies)
|
|
107
|
+
|
|
108
|
+
| Package | Version | When | Why |
|
|
109
|
+
|---------|---------|------|-----|
|
|
110
|
+
| `react` | ≥ 17 | Always | UI framework |
|
|
111
|
+
| `react-dom` | ≥ 17 | Always | DOM rendering |
|
|
112
|
+
| `zustand` | ≥ 4 | Always | Chat state store |
|
|
113
|
+
| `tailwindcss` | **v4** | Always | Package uses Tailwind utilities |
|
|
114
|
+
| `@tailwindcss/vite` | v4 | Vite only | Tailwind plugin |
|
|
115
|
+
| `@tailwindcss/postcss` | v4 | Next.js only | Tailwind PostCSS plugin |
|
|
116
|
+
| `next` | ≥ 13 | Next.js only | App Router |
|
|
117
|
+
| `react-hot-toast` | ≥ 2 | Optional | Only if you use `showNotification` helper |
|
|
118
|
+
|
|
119
|
+
### Bundled inside the package (do not install separately)
|
|
120
|
+
|
|
121
|
+
`axios`, `socket.io-client`, `radix-ui`, `lucide-react`, `emoji-picker-react`, `clsx`, `tailwind-merge`, `class-variance-authority`, and all chat components.
|
|
122
|
+
|
|
123
|
+
---
|
|
52
124
|
|
|
53
|
-
|
|
54
|
-
| ----------- | ------- |
|
|
55
|
-
| Node.js | 18+ |
|
|
56
|
-
| React | ≥ 17 (19 recommended) |
|
|
57
|
-
| `react-dom` | ≥ 17 |
|
|
58
|
-
| `zustand` | ≥ 4 |
|
|
59
|
-
| `tailwindcss` | **v4** (required) |
|
|
60
|
-
| Next.js | ≥ 13 App Router (optional — for Next.js projects) |
|
|
125
|
+
## Backend credentials
|
|
61
126
|
|
|
62
|
-
|
|
127
|
+
| Field | Example | Notes |
|
|
128
|
+
|-------|---------|-------|
|
|
129
|
+
| `clientId` | `"6a35339216e46130366cda93"` | RealtimeX tenant / client id |
|
|
130
|
+
| `accessToken` | JWT string | User access token |
|
|
131
|
+
| `loggedUserDetails._id` | `"6a3533c416e46130366cda98"` | **Must match `_id` inside JWT** |
|
|
132
|
+
| `loggedUserDetails.name` | `"chat1"` | Display name |
|
|
133
|
+
| `loggedUserDetails.email` | `"chat1@example.com"` | Used for client-user login |
|
|
134
|
+
| `apiUrl` | `https://realtimex.softwareco.com/api/v1` | REST API — **must end with** `/api/v1` (**required**, pass from your app) |
|
|
135
|
+
| `socketUrl` | `https://realtimex.softwareco.com` | Socket host — **no** `/api/v1`; connects at `/connection`. Optional — derived from `apiUrl` if omitted |
|
|
136
|
+
| `themeColor` | `"#6366f1"` | Brand accent (optional) |
|
|
63
137
|
|
|
64
|
-
|
|
65
|
-
| ----- | ----------- |
|
|
66
|
-
| **Client ID** | Your RealtimeX client / app id |
|
|
67
|
-
| **Access token** | JWT for the logged-in user |
|
|
68
|
-
| **Logged user details** | `{ _id, name, email }` |
|
|
69
|
-
| **API URL** | REST base ending with `/api/v1`, e.g. `https://your-server.com/api/v1` |
|
|
70
|
-
| **Socket URL** | Base host only (no `/api/v1`), e.g. `https://your-server.com` |
|
|
138
|
+
> **Important:** If `loggedUserDetails._id` does not match the JWT `_id`, messages may appear on the wrong side, contacts may be empty, or session sync can fail. Decode your JWT or use the id returned from the login API.
|
|
71
139
|
|
|
72
140
|
---
|
|
73
141
|
|
|
74
|
-
## Installation
|
|
142
|
+
## Installation step by step
|
|
143
|
+
|
|
144
|
+
### 1. Install the package and peers
|
|
145
|
+
|
|
146
|
+
**npm:**
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
npm install @realtimexsco/live-chat react react-dom zustand tailwindcss
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**pnpm:**
|
|
75
153
|
|
|
76
154
|
```bash
|
|
77
|
-
|
|
78
|
-
# install peer dependencies if you don't have them yet
|
|
79
|
-
npm install react react-dom zustand tailwindcss @tailwindcss/vite
|
|
155
|
+
pnpm add @realtimexsco/live-chat react react-dom zustand tailwindcss
|
|
80
156
|
```
|
|
81
157
|
|
|
82
|
-
|
|
158
|
+
### 2. Install Tailwind v4 plugin for your framework
|
|
159
|
+
|
|
160
|
+
**Vite:**
|
|
83
161
|
|
|
84
162
|
```bash
|
|
85
|
-
pnpm add
|
|
163
|
+
pnpm add -D @tailwindcss/vite @vitejs/plugin-react vite
|
|
86
164
|
```
|
|
87
165
|
|
|
88
|
-
|
|
166
|
+
**Next.js:**
|
|
89
167
|
|
|
90
|
-
|
|
168
|
+
```bash
|
|
169
|
+
pnpm add -D @tailwindcss/postcss
|
|
170
|
+
# next is usually already installed
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 3. Verify install
|
|
91
174
|
|
|
92
175
|
```bash
|
|
93
|
-
|
|
176
|
+
npm ls @realtimexsco/live-chat
|
|
177
|
+
ls node_modules/@realtimexsco/live-chat/dist/index.d.ts # TypeScript types must exist
|
|
94
178
|
```
|
|
95
179
|
|
|
180
|
+
### 4. Configure CSS, config file, and chat page (see sections below)
|
|
181
|
+
|
|
182
|
+
### 5. Run your app and open the chat route
|
|
183
|
+
|
|
184
|
+
No `.npmrc` or private token is required — the package is on the public npm registry.
|
|
185
|
+
|
|
96
186
|
---
|
|
97
187
|
|
|
98
188
|
## Tailwind CSS v4 setup (required)
|
|
99
189
|
|
|
100
|
-
The package
|
|
190
|
+
The package will look unstyled without these three steps:
|
|
101
191
|
|
|
102
|
-
1.
|
|
103
|
-
2.
|
|
104
|
-
3. Define `--chat-theme*` CSS variables
|
|
192
|
+
1. `@import` Tailwind + package `styles.css`
|
|
193
|
+
2. `@source` the package `dist` files so Tailwind scans bundled classes
|
|
194
|
+
3. Define `--chat-theme*` CSS variables in `:root`
|
|
105
195
|
|
|
106
196
|
### Vite — `src/index.css`
|
|
107
197
|
|
|
108
|
-
> From `src/index.css`, `@source` uses `../node_modules`.
|
|
109
|
-
|
|
110
198
|
```css
|
|
111
199
|
@import "tailwindcss";
|
|
112
200
|
@import "@realtimexsco/live-chat/styles.css";
|
|
@@ -115,11 +203,6 @@ The package ships Tailwind utility classes, so your app must:
|
|
|
115
203
|
@source "../node_modules/@realtimexsco/live-chat/dist/index.cjs";
|
|
116
204
|
@source "../**/*.{js,jsx,ts,tsx}";
|
|
117
205
|
|
|
118
|
-
.chat-page h1,
|
|
119
|
-
.chat-container-root h1 {
|
|
120
|
-
@apply text-[15px] font-semibold leading-snug text-gray-900;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
206
|
:root {
|
|
124
207
|
--radius: 0.625rem;
|
|
125
208
|
--background: oklch(1 0 0);
|
|
@@ -141,7 +224,7 @@ The package ships Tailwind utility classes, so your app must:
|
|
|
141
224
|
--secondary-foreground: oklch(0.205 0 0);
|
|
142
225
|
--input: oklch(0.922 0 0);
|
|
143
226
|
|
|
144
|
-
/*
|
|
227
|
+
/* Overridden at runtime by ChatMain `themeColor` */
|
|
145
228
|
--chat-theme: #6366f1;
|
|
146
229
|
--chat-theme-5: #6366f10D;
|
|
147
230
|
--chat-theme-10: #6366f11A;
|
|
@@ -178,21 +261,14 @@ The package ships Tailwind utility classes, so your app must:
|
|
|
178
261
|
}
|
|
179
262
|
|
|
180
263
|
@layer base {
|
|
181
|
-
html,
|
|
182
|
-
body
|
|
183
|
-
#root {
|
|
184
|
-
height: 100%;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
body {
|
|
188
|
-
@apply bg-background text-foreground;
|
|
189
|
-
}
|
|
264
|
+
html, body, #root { height: 100%; }
|
|
265
|
+
body { @apply bg-background text-foreground; }
|
|
190
266
|
}
|
|
191
267
|
```
|
|
192
268
|
|
|
193
269
|
### Next.js — `src/app/globals.css`
|
|
194
270
|
|
|
195
|
-
>
|
|
271
|
+
> `globals.css` is inside `src/app/`, so `@source` uses **`../../node_modules`** (not `../`).
|
|
196
272
|
|
|
197
273
|
```css
|
|
198
274
|
@import "tailwindcss";
|
|
@@ -201,22 +277,21 @@ The package ships Tailwind utility classes, so your app must:
|
|
|
201
277
|
@source "../../node_modules/@realtimexsco/live-chat/dist/index.mjs";
|
|
202
278
|
@source "../../node_modules/@realtimexsco/live-chat/dist/index.cjs";
|
|
203
279
|
|
|
204
|
-
/*
|
|
280
|
+
/* Copy :root, @theme inline, and @layer base from the Vite example above */
|
|
205
281
|
```
|
|
206
282
|
|
|
207
|
-
### `@source` path
|
|
283
|
+
### `@source` path cheat sheet
|
|
208
284
|
|
|
209
|
-
| CSS file location |
|
|
210
|
-
|
|
211
|
-
| `src/index.css` | `../node_modules/...` |
|
|
212
|
-
| `src/app/globals.css` | `../../node_modules/...` |
|
|
285
|
+
| CSS file location | `@source` prefix |
|
|
286
|
+
|-------------------|------------------|
|
|
287
|
+
| `src/index.css` (Vite) | `../node_modules/...` |
|
|
288
|
+
| `src/app/globals.css` (Next.js) | `../../node_modules/...` |
|
|
213
289
|
|
|
214
|
-
Wrong path
|
|
290
|
+
Wrong path = broken or unstyled chat UI.
|
|
215
291
|
|
|
216
|
-
### Vite config
|
|
292
|
+
### Vite — `vite.config.ts`
|
|
217
293
|
|
|
218
294
|
```ts
|
|
219
|
-
// vite.config.ts
|
|
220
295
|
import path from "path";
|
|
221
296
|
import { fileURLToPath } from "url";
|
|
222
297
|
import { defineConfig } from "vite";
|
|
@@ -232,117 +307,79 @@ export default defineConfig({
|
|
|
232
307
|
dedupe: ["react", "react-dom", "zustand", "socket.io-client", "@realtimexsco/live-chat"],
|
|
233
308
|
},
|
|
234
309
|
optimizeDeps: {
|
|
235
|
-
// Required: stale .vite/deps cache shows OLD UI after package upgrade
|
|
236
310
|
exclude: ["@realtimexsco/live-chat"],
|
|
237
311
|
include: ["zustand", "socket.io-client"],
|
|
238
312
|
},
|
|
239
|
-
server: {
|
|
240
|
-
proxy: {
|
|
241
|
-
"/chat-api": {
|
|
242
|
-
target: "http://YOUR_API_HOST",
|
|
243
|
-
changeOrigin: true,
|
|
244
|
-
rewrite: (p) => p.replace(/^\/chat-api/, "/api/v1"),
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
313
|
});
|
|
249
314
|
```
|
|
250
315
|
|
|
251
|
-
### Next.js config
|
|
316
|
+
### Next.js — `next.config.ts`
|
|
252
317
|
|
|
253
318
|
```ts
|
|
254
|
-
// next.config.ts
|
|
255
319
|
import type { NextConfig } from "next";
|
|
256
320
|
|
|
257
321
|
const nextConfig: NextConfig = {
|
|
258
322
|
transpilePackages: ["@realtimexsco/live-chat"],
|
|
259
|
-
async rewrites() {
|
|
260
|
-
return [
|
|
261
|
-
{
|
|
262
|
-
source: "/chat-api/:path*",
|
|
263
|
-
destination: "http://YOUR_API_HOST/api/v1/:path*",
|
|
264
|
-
},
|
|
265
|
-
];
|
|
266
|
-
},
|
|
267
323
|
};
|
|
268
324
|
|
|
269
325
|
export default nextConfig;
|
|
270
326
|
```
|
|
271
327
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
### PostCSS (Next.js)
|
|
328
|
+
### Next.js — `postcss.config.mjs`
|
|
275
329
|
|
|
276
330
|
```js
|
|
277
|
-
|
|
278
|
-
const config = {
|
|
279
|
-
plugins: { "@tailwindcss/postcss": {} },
|
|
280
|
-
};
|
|
331
|
+
const config = { plugins: { "@tailwindcss/postcss": {} } };
|
|
281
332
|
export default config;
|
|
282
333
|
```
|
|
283
334
|
|
|
284
335
|
---
|
|
285
336
|
|
|
286
|
-
##
|
|
287
|
-
|
|
288
|
-
Create a shared config file and replace `YOUR_*` placeholders with real credentials.
|
|
337
|
+
## Project config file
|
|
289
338
|
|
|
290
|
-
|
|
339
|
+
Create `src/configs/chat.config.ts`:
|
|
291
340
|
|
|
292
341
|
```ts
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
} as const;
|
|
305
|
-
```
|
|
342
|
+
/** Optional: read _id from JWT so it always matches the token */
|
|
343
|
+
function userIdFromToken(token: string): string | undefined {
|
|
344
|
+
try {
|
|
345
|
+
const payload = JSON.parse(
|
|
346
|
+
atob(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"))
|
|
347
|
+
);
|
|
348
|
+
return payload._id || payload.id;
|
|
349
|
+
} catch {
|
|
350
|
+
return undefined;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
306
353
|
|
|
307
|
-
|
|
354
|
+
const accessToken = "YOUR_JWT_ACCESS_TOKEN";
|
|
308
355
|
|
|
309
|
-
```js
|
|
310
356
|
export const chatConfig = {
|
|
311
357
|
clientId: "YOUR_CLIENT_ID",
|
|
312
|
-
accessToken
|
|
358
|
+
accessToken,
|
|
313
359
|
loggedUserDetails: {
|
|
314
|
-
_id: "YOUR_USER_ID",
|
|
360
|
+
_id: userIdFromToken(accessToken) ?? "YOUR_USER_ID",
|
|
315
361
|
name: "Your Name",
|
|
316
362
|
email: "you@example.com",
|
|
317
363
|
},
|
|
318
|
-
apiUrl: "
|
|
319
|
-
socketUrl: "
|
|
364
|
+
apiUrl: "https://realtimex.softwareco.com/api/v1",
|
|
365
|
+
socketUrl: "https://realtimex.softwareco.com", // optional — auto-derived from apiUrl
|
|
320
366
|
themeColor: "#6366f1",
|
|
321
|
-
};
|
|
367
|
+
} as const;
|
|
322
368
|
```
|
|
323
369
|
|
|
324
|
-
| Field | Description |
|
|
325
|
-
| ----- | ----------- |
|
|
326
|
-
| `clientId` | Chat client ID from backend |
|
|
327
|
-
| `accessToken` | JWT token for authenticated user |
|
|
328
|
-
| `loggedUserDetails._id` | Current user MongoDB / API id |
|
|
329
|
-
| `apiUrl` | REST API base URL ending with `/api/v1` |
|
|
330
|
-
| `socketUrl` | Socket server base URL (no `/api/v1`) |
|
|
331
|
-
| `themeColor` | Primary chat color (hex) |
|
|
332
|
-
|
|
333
370
|
---
|
|
334
371
|
|
|
335
|
-
## Quick start —
|
|
336
|
-
|
|
337
|
-
The fastest path. `ChatMain` renders the sidebar, chat area, and modals in one component.
|
|
372
|
+
## Quick start — minimum working chat
|
|
338
373
|
|
|
339
374
|
```tsx
|
|
375
|
+
"use client"; // Next.js only — required on any file that imports chat
|
|
376
|
+
|
|
340
377
|
import ChatMain from "@realtimexsco/live-chat";
|
|
341
378
|
import { chatConfig } from "@/configs/chat.config";
|
|
342
379
|
|
|
343
|
-
export default function
|
|
380
|
+
export default function ChatPage() {
|
|
344
381
|
return (
|
|
345
|
-
<div className="
|
|
382
|
+
<div className="h-dvh min-h-0 overflow-hidden">
|
|
346
383
|
<ChatMain
|
|
347
384
|
clientId={chatConfig.clientId}
|
|
348
385
|
accessToken={chatConfig.accessToken}
|
|
@@ -356,543 +393,993 @@ export default function App() {
|
|
|
356
393
|
}
|
|
357
394
|
```
|
|
358
395
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
```tsx
|
|
362
|
-
<ChatMain
|
|
363
|
-
{...chatConfig}
|
|
364
|
-
clientId={chatConfig.clientId}
|
|
365
|
-
layout={{ input: false }} // read-only chat
|
|
366
|
-
/>
|
|
367
|
-
```
|
|
396
|
+
**Container height is required.** `ChatMain` includes a built-in viewport (`chat-package-viewport`). Set `viewportHeight="screen"` for full page, or `viewportHeight="calc(100dvh - 4rem)"` below a fixed header. Alternatively wrap with `h-dvh min-h-0 overflow-hidden` and use default `viewportHeight="full"`.
|
|
368
397
|
|
|
369
398
|
---
|
|
370
399
|
|
|
371
|
-
##
|
|
400
|
+
## Complete ChatMain example
|
|
372
401
|
|
|
373
|
-
|
|
374
|
-
detail. `ChatLayout` is shipped inside the package — no need to copy a large
|
|
375
|
-
layout file.
|
|
402
|
+
All common props: theme, locale, layout, customization, calls, alerts.
|
|
376
403
|
|
|
377
404
|
```tsx
|
|
378
|
-
|
|
405
|
+
"use client";
|
|
406
|
+
|
|
407
|
+
import { useState } from "react";
|
|
408
|
+
import ChatMain, {
|
|
409
|
+
type ChatColorMode,
|
|
410
|
+
type ChatComponents,
|
|
411
|
+
type ChatClassNames,
|
|
412
|
+
type ChatFeatures,
|
|
413
|
+
type AvatarSlotProps,
|
|
414
|
+
defaultChatLayoutConfig,
|
|
415
|
+
} from "@realtimexsco/live-chat";
|
|
379
416
|
import { chatConfig } from "@/configs/chat.config";
|
|
380
|
-
import { chatLayoutConfig } from "@/configs/chat.layout.config";
|
|
381
417
|
|
|
382
|
-
|
|
418
|
+
const MyAvatar = ({ src, name, size }: AvatarSlotProps) => (
|
|
419
|
+
<img
|
|
420
|
+
src={src || undefined}
|
|
421
|
+
alt={name}
|
|
422
|
+
className={`rounded-full object-cover avatar--${size}`}
|
|
423
|
+
style={{ width: 36, height: 36 }}
|
|
424
|
+
/>
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const components: ChatComponents = {
|
|
428
|
+
Avatar: MyAvatar,
|
|
429
|
+
// ChatLayout: MyFullCustomUI, // Case 1 — replace entire UI
|
|
430
|
+
// MessageItem: MyMessageBubble, // Case 2 — single slot
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const classNames: ChatClassNames = {
|
|
434
|
+
sidebar: "bg-slate-950",
|
|
435
|
+
channelHeader: "border-b-2 border-indigo-500",
|
|
436
|
+
messageBubbleSender: "rounded-3xl shadow-md",
|
|
437
|
+
messageBubbleReceiver: "rounded-3xl",
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
const features: ChatFeatures = {
|
|
441
|
+
showPhoneCall: true,
|
|
442
|
+
showVideoCall: true,
|
|
443
|
+
onPhoneCall: ({ activeChannel, conversationId }) => {
|
|
444
|
+
console.log("Voice call", activeChannel.name, conversationId);
|
|
445
|
+
},
|
|
446
|
+
onVideoCall: ({ activeChannel, conversationId }) => {
|
|
447
|
+
console.log("Video call", activeChannel.name, conversationId);
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
export default function ChatPage() {
|
|
452
|
+
const [colorMode, setColorMode] = useState<ChatColorMode>("light");
|
|
453
|
+
const [error, setError] = useState<string | null>(null);
|
|
454
|
+
|
|
383
455
|
return (
|
|
384
|
-
<div className="
|
|
385
|
-
<
|
|
386
|
-
|
|
456
|
+
<div className="h-dvh min-h-0 overflow-hidden">
|
|
457
|
+
<ChatMain
|
|
458
|
+
clientId={chatConfig.clientId}
|
|
387
459
|
accessToken={chatConfig.accessToken}
|
|
388
460
|
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
389
461
|
apiUrl={chatConfig.apiUrl}
|
|
390
462
|
socketUrl={chatConfig.socketUrl}
|
|
391
463
|
themeColor={chatConfig.themeColor}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
464
|
+
colorMode={colorMode}
|
|
465
|
+
onColorModeChange={setColorMode}
|
|
466
|
+
showColorModeToggle={false}
|
|
467
|
+
locale="en-US"
|
|
468
|
+
timeZone="Asia/Kolkata"
|
|
469
|
+
hour12={false}
|
|
470
|
+
showDateTimeSettings
|
|
471
|
+
layout={{ ...defaultChatLayoutConfig }}
|
|
472
|
+
components={components}
|
|
473
|
+
classNames={classNames}
|
|
474
|
+
features={features}
|
|
475
|
+
error={error}
|
|
476
|
+
onErrorDismiss={() => setError(null)}
|
|
477
|
+
/>
|
|
395
478
|
</div>
|
|
396
479
|
);
|
|
397
480
|
}
|
|
398
481
|
```
|
|
399
482
|
|
|
400
|
-
|
|
483
|
+
---
|
|
401
484
|
|
|
402
|
-
|
|
403
|
-
import type { ChatLayoutConfig } from "@realtimexsco/live-chat";
|
|
404
|
-
|
|
405
|
-
export const chatLayoutConfig: ChatLayoutConfig = {
|
|
406
|
-
sidebar: true,
|
|
407
|
-
loggedUserDetails: true,
|
|
408
|
-
channelList: true,
|
|
409
|
-
header: true,
|
|
410
|
-
messageList: true,
|
|
411
|
-
input: true,
|
|
412
|
-
forwardModal: true,
|
|
413
|
-
};
|
|
414
|
-
```
|
|
485
|
+
## Customization — three ways
|
|
415
486
|
|
|
416
|
-
|
|
487
|
+
You can combine all three.
|
|
417
488
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
} from "@realtimexsco/live-chat";
|
|
489
|
+
| Case | Prop | What it does |
|
|
490
|
+
|------|------|--------------|
|
|
491
|
+
| **1** | `components.ChatLayout` | Replace the **entire** chat UI shell |
|
|
492
|
+
| **2** | `components.*` | Replace **one** component (avatar, header, message bubble, drawer, etc.) |
|
|
493
|
+
| **3** | `classNames.*` | Override **Tailwind classes** per region — no new React components |
|
|
424
494
|
|
|
425
|
-
|
|
495
|
+
Omit any slot or className key → **package default is used automatically**.
|
|
426
496
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Default component fallback (important)
|
|
500
|
+
|
|
501
|
+
Customization is **opt-in per slot**. You never need to register every component.
|
|
502
|
+
|
|
503
|
+
### How it works internally
|
|
504
|
+
|
|
505
|
+
```
|
|
506
|
+
ChatMain components={{ MembersDrawer: MyDrawer }}
|
|
507
|
+
↓
|
|
508
|
+
ChatCustomizationProvider merges your config
|
|
509
|
+
↓
|
|
510
|
+
At render: resolveComponent("MembersDrawer", PackageMembersDrawer)
|
|
511
|
+
↓
|
|
512
|
+
Custom passed? → use MyDrawer
|
|
513
|
+
Not passed? → use PackageMembersDrawer (built-in default)
|
|
430
514
|
```
|
|
431
515
|
|
|
432
|
-
|
|
516
|
+
Rules:
|
|
517
|
+
|
|
518
|
+
| You pass | Result |
|
|
519
|
+
|----------|--------|
|
|
520
|
+
| No `components` prop at all | **Full default chat UI** |
|
|
521
|
+
| `components={{ Avatar: MyAvatar }}` | Custom avatar only; **all other slots use defaults** |
|
|
522
|
+
| `components={{ MembersDrawer: undefined }}` | Treated as not passed → **default drawer** |
|
|
523
|
+
| `classNames={{ sidebar: "..." }}` | Your classes **merge on top of** package defaults |
|
|
433
524
|
|
|
434
|
-
|
|
525
|
+
### Code pattern inside the package
|
|
435
526
|
|
|
436
|
-
|
|
437
|
-
`<Channel>` — it resolves the conversation id, fetches messages, joins the socket
|
|
438
|
-
room, and marks messages read. **Do not** manually emit `group_join` /
|
|
439
|
-
`group_message_read` yourself.
|
|
527
|
+
Every wired slot follows this pattern:
|
|
440
528
|
|
|
441
|
-
|
|
442
|
-
|
|
529
|
+
```tsx
|
|
530
|
+
const MembersDrawerSlot = resolveComponent("MembersDrawer", MembersDrawer);
|
|
531
|
+
return <MembersDrawerSlot isOpen={...} onOpenChange={...} ... />;
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Your custom component receives the **same props** as the package default (`MembersDrawerSlotProps`, etc.). Import the prop type from the package for TypeScript.
|
|
535
|
+
|
|
536
|
+
### Wrapping instead of rebuilding
|
|
443
537
|
|
|
444
|
-
|
|
538
|
+
Use `packageDefaultComponents` when you only need to restyle or add a wrapper:
|
|
445
539
|
|
|
446
540
|
```tsx
|
|
447
|
-
|
|
541
|
+
import { packageDefaultComponents, type MembersDrawerSlotProps } from "@realtimexsco/live-chat";
|
|
448
542
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
LoggedUserDetails,
|
|
456
|
-
MessageInput,
|
|
457
|
-
MessageList,
|
|
458
|
-
useChatStore,
|
|
459
|
-
mergeChatLayoutConfig,
|
|
460
|
-
type ChatLayoutConfig,
|
|
461
|
-
} from "@realtimexsco/live-chat";
|
|
462
|
-
import classNames from "classnames";
|
|
543
|
+
const MyMembersDrawer = (props: MembersDrawerSlotProps) => (
|
|
544
|
+
<div className="my-drawer-shell">
|
|
545
|
+
<packageDefaultComponents.MembersDrawer! {...props} />
|
|
546
|
+
</div>
|
|
547
|
+
);
|
|
548
|
+
```
|
|
463
549
|
|
|
464
|
-
|
|
465
|
-
layout?: ChatLayoutConfig;
|
|
466
|
-
};
|
|
550
|
+
---
|
|
467
551
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
552
|
+
## Which slots are wired
|
|
553
|
+
|
|
554
|
+
All slots below are **honored at runtime** when you pass them on `ChatMain` `components` (or `Chat` `uiConfig.components`). Slots not listed as wired may still be typed for future use or require replacing a parent slot (e.g. `ChannelHeader`).
|
|
555
|
+
|
|
556
|
+
| Slot | Wired in | Fallback when omitted |
|
|
557
|
+
|------|----------|------------------------|
|
|
558
|
+
| `ChatLayout` | `ChatLayout.tsx` | `DefaultChatLayout` |
|
|
559
|
+
| `LoggedUserDetails` | `DefaultChatLayout` | Package default |
|
|
560
|
+
| `ChannelList` | `DefaultChatLayout` | Package default |
|
|
561
|
+
| `ChannelHeader` | `DefaultChatLayout` | Package default |
|
|
562
|
+
| `MessageList` | `DefaultChatLayout` | Package default |
|
|
563
|
+
| `MessageInput` | `DefaultChatLayout` | Package default |
|
|
564
|
+
| `ForwardModal` | `DefaultChatLayout` | Package default |
|
|
565
|
+
| `ChatPanelAlerts` | `DefaultChatLayout` | Package default |
|
|
566
|
+
| `ChannelListItem` | `ChannelListItem` wrapper | `DefaultChannelListItem` |
|
|
567
|
+
| `Avatar` | `ChatAvatar` | `DefaultChatAvatar` |
|
|
568
|
+
| `HeaderCallActions` | `HeaderCallActions` | Built-in phone/video group |
|
|
569
|
+
| `PhoneCallButton` | `HeaderCallActions` | Built-in button |
|
|
570
|
+
| `VideoCallButton` | `HeaderCallActions` | Built-in button |
|
|
571
|
+
| `HeaderLeftSide` | `ChannelHeaderView` | Package default |
|
|
572
|
+
| `HeaderRightSide` | `ChannelHeaderView` | Package default |
|
|
573
|
+
| `ChatActionModal` | `ChannelHeaderView` | Package default |
|
|
574
|
+
| `ChannelDetailsDrawer` | `ChannelHeaderView` | Package default |
|
|
575
|
+
| `MembersDrawer` | `ChannelHeaderView` | Package default |
|
|
576
|
+
| `AdminsDrawer` | `ChannelHeaderView` | Package default |
|
|
577
|
+
| `PermissionDrawer` | `ChannelHeaderView` | Package default |
|
|
578
|
+
| `AttachmentsDrawer` | `ChannelHeaderView` | Package default |
|
|
579
|
+
| `PinnedMessagesDrawer` | `ChannelHeaderView` | Package default |
|
|
580
|
+
| `StarredMessagesDrawer` | `ChannelHeaderView` | Package default |
|
|
581
|
+
|
|
582
|
+
> **Replace entire header:** Pass `components.ChannelHeader` with your own component (use `useChannelHeader` hook for state/handlers). That replaces the full header **and** all drawers inside it.
|
|
583
|
+
|
|
584
|
+
### Advanced slots (replace parent component)
|
|
585
|
+
|
|
586
|
+
These slots are **typed and exported** but used inside inner components. To customize them, either replace the parent slot or copy the parent and wire your component:
|
|
587
|
+
|
|
588
|
+
| Slot | Customize by replacing |
|
|
589
|
+
|------|------------------------|
|
|
590
|
+
| `MessageItem` | `MessageList` (pass your list that renders custom items) |
|
|
591
|
+
| `MessageToolbar`, `MessageStatus`, `MessageAttachments`, `MessageReactions`, `MessageReplySnippet` | `MessageItem` or `MessageList` |
|
|
592
|
+
| `HeaderSearchOverlay` | `HeaderRightSide` or `ChannelHeader` |
|
|
593
|
+
| `ChatThemeToggle`, `ChatDateTimeSettings`, `ChatSocketStatus` | Used inside header/sidebar — replace parent or `ChannelHeader` |
|
|
594
|
+
|
|
595
|
+
For most apps, `components` + `classNames` on the [wired slots](#which-slots-are-wired) table is enough without touching advanced slots.
|
|
472
596
|
|
|
473
|
-
|
|
474
|
-
const setActiveChannel = useChatStore((s) => s.setActiveChannel);
|
|
475
|
-
const conversationId = useChatStore((s) => s.activeConversationId);
|
|
476
|
-
const rateLimitResetAt = useChatStore((s) => s.rateLimitResetAt);
|
|
477
|
-
const isFetchingMessages = useChatStore((s) => s.isFetchingMessages);
|
|
478
|
-
const loggedUserDetails = useChatStore((s) => s.loggedUserDetails);
|
|
479
|
-
const conversationInfos = useChatStore((s) => s.conversationInfos);
|
|
480
|
-
const messagesByConversation = useChatStore((s) => s.messagesByConversation);
|
|
481
|
-
const emitDmSend = useChatStore((s) => s.emitDmSend);
|
|
482
|
-
const emitGroupMessageSend = useChatStore((s) => s.emitGroupMessageSend);
|
|
483
|
-
const fetchAllUsers = useChatStore((s) => s.fetchAllUsers);
|
|
484
|
-
const isSessionReady = useChatStore((s) => s.isSessionReady);
|
|
485
|
-
const accessToken = useChatStore((s) => s.accessToken);
|
|
486
|
-
|
|
487
|
-
const conversationMessages = conversationId
|
|
488
|
-
? messagesByConversation[conversationId] || []
|
|
489
|
-
: [];
|
|
490
|
-
|
|
491
|
-
const [searchQuery, setSearchQuery] = useState("");
|
|
492
|
-
const [debouncedSearch, setDebouncedSearch] = useState("");
|
|
493
|
-
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
|
494
|
-
const [replyTo, setReplyTo] = useState<Parameters<typeof MessageInput>[0]["replyTo"]>(null);
|
|
495
|
-
const [forwardMessage, setForwardMessage] = useState<Parameters<typeof ForwardModal>[0]["message"]>(null);
|
|
496
|
-
const [isForwardModalOpen, setIsForwardModalOpen] = useState(false);
|
|
497
|
-
|
|
498
|
-
useEffect(() => {
|
|
499
|
-
const timer = setTimeout(() => setDebouncedSearch(searchQuery), 400);
|
|
500
|
-
return () => clearTimeout(timer);
|
|
501
|
-
}, [searchQuery]);
|
|
502
|
-
|
|
503
|
-
useEffect(() => {
|
|
504
|
-
if (isSessionReady && accessToken) fetchAllUsers(true);
|
|
505
|
-
}, [isSessionReady, accessToken, fetchAllUsers]);
|
|
506
|
-
|
|
507
|
-
useEffect(() => {
|
|
508
|
-
if (isCreateModalOpen && isSessionReady && accessToken) fetchAllUsers(true);
|
|
509
|
-
}, [isCreateModalOpen, isSessionReady, accessToken, fetchAllUsers]);
|
|
510
|
-
|
|
511
|
-
const handleChannelSelect = useCallback(
|
|
512
|
-
(channel: Parameters<typeof setActiveChannel>[0]) => {
|
|
513
|
-
setActiveChannel(channel);
|
|
514
|
-
setReplyTo(null);
|
|
515
|
-
},
|
|
516
|
-
[setActiveChannel],
|
|
517
|
-
);
|
|
597
|
+
---
|
|
518
598
|
|
|
519
|
-
|
|
520
|
-
(channel: Parameters<typeof setActiveChannel>[0]) => {
|
|
521
|
-
setActiveChannel(channel);
|
|
522
|
-
setIsForwardModalOpen(false);
|
|
523
|
-
setForwardMessage(null);
|
|
524
|
-
},
|
|
525
|
-
[setActiveChannel],
|
|
526
|
-
);
|
|
599
|
+
## Production integration pattern (admin templates)
|
|
527
600
|
|
|
528
|
-
|
|
529
|
-
(
|
|
530
|
-
content: string,
|
|
531
|
-
attachments: Parameters<NonNullable<Parameters<typeof MessageInput>[0]["onSendMessage"]>>[1],
|
|
532
|
-
replyToMessage?: Parameters<typeof MessageInput>[0]["replyTo"],
|
|
533
|
-
) => {
|
|
534
|
-
if (!activeChannel || !loggedUserDetails) return;
|
|
535
|
-
|
|
536
|
-
const isGroup = !!activeChannel.isGroup;
|
|
537
|
-
const dmReceiverId = isGroup ? undefined : String(activeChannel.id);
|
|
538
|
-
|
|
539
|
-
const payload = {
|
|
540
|
-
tempId: `temp-${Date.now()}`,
|
|
541
|
-
sender: loggedUserDetails._id,
|
|
542
|
-
receiver: dmReceiverId,
|
|
543
|
-
content,
|
|
544
|
-
type: "text",
|
|
545
|
-
conversationId,
|
|
546
|
-
replyTo: replyToMessage
|
|
547
|
-
? {
|
|
548
|
-
_id: replyToMessage._id || replyToMessage.id,
|
|
549
|
-
content: replyToMessage.content,
|
|
550
|
-
attachments: replyToMessage.attachments || [],
|
|
551
|
-
sender: {
|
|
552
|
-
_id: (replyToMessage.sender as { _id?: string })?._id || replyToMessage.sender,
|
|
553
|
-
name: (replyToMessage.sender as { name?: string })?.name || "",
|
|
554
|
-
},
|
|
555
|
-
}
|
|
556
|
-
: undefined,
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
if (attachments.length === 0) {
|
|
560
|
-
if (isGroup) emitGroupMessageSend(payload);
|
|
561
|
-
else emitDmSend(payload);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
setReplyTo(null);
|
|
565
|
-
},
|
|
566
|
-
[activeChannel, conversationId, emitDmSend, emitGroupMessageSend, loggedUserDetails],
|
|
567
|
-
);
|
|
601
|
+
Recommended structure for apps like **Elstar**, **Metronic**, or any admin shell:
|
|
568
602
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
<LoggedUserDetails
|
|
582
|
-
onUserSelect={handleChannelSelect}
|
|
583
|
-
searchQuery={searchQuery}
|
|
584
|
-
setSearchQuery={setSearchQuery}
|
|
585
|
-
isCreateModalOpen={isCreateModalOpen}
|
|
586
|
-
setIsCreateModalOpen={setIsCreateModalOpen}
|
|
587
|
-
/>
|
|
588
|
-
)}
|
|
589
|
-
{sections.channelList && (
|
|
590
|
-
<ChannelList
|
|
591
|
-
activeChannel={activeChannel}
|
|
592
|
-
onChannelSelect={handleChannelSelect}
|
|
593
|
-
onNewChatClick={() => setIsCreateModalOpen(true)}
|
|
594
|
-
debouncedSearch={debouncedSearch}
|
|
595
|
-
/>
|
|
596
|
-
)}
|
|
597
|
-
</aside>
|
|
598
|
-
)}
|
|
599
|
-
|
|
600
|
-
<Channel>
|
|
601
|
-
{rateLimitResetAt > Date.now() && (
|
|
602
|
-
<div className="shrink-0 border-b border-amber-200/80 bg-amber-50 px-4 py-2 text-center text-xs font-medium text-amber-800">
|
|
603
|
-
Too many requests — please wait before trying again.
|
|
604
|
-
</div>
|
|
605
|
-
)}
|
|
606
|
-
|
|
607
|
-
{sections.header && (
|
|
608
|
-
<div className="shrink-0">
|
|
609
|
-
<ChannelHeader
|
|
610
|
-
activeChannel={activeChannel}
|
|
611
|
-
conversationId={conversationId}
|
|
612
|
-
onBack={() => setActiveChannel(null)}
|
|
613
|
-
/>
|
|
614
|
-
</div>
|
|
615
|
-
)}
|
|
616
|
-
|
|
617
|
-
{sections.messageList && (
|
|
618
|
-
<div className="relative min-h-0 flex-1 overflow-hidden">
|
|
619
|
-
<MessageList
|
|
620
|
-
activeChannel={activeChannel}
|
|
621
|
-
loggedUserId={loggedUserDetails?._id}
|
|
622
|
-
conversationMessages={conversationMessages}
|
|
623
|
-
isLoading={isFetchingMessages}
|
|
624
|
-
conversationId={conversationId}
|
|
625
|
-
conversationInfo={
|
|
626
|
-
conversationId ? conversationInfos[conversationId] || null : null
|
|
627
|
-
}
|
|
628
|
-
onReply={setReplyTo}
|
|
629
|
-
onForward={(msg) => {
|
|
630
|
-
setForwardMessage(msg);
|
|
631
|
-
setIsForwardModalOpen(true);
|
|
632
|
-
}}
|
|
633
|
-
/>
|
|
634
|
-
</div>
|
|
635
|
-
)}
|
|
636
|
-
|
|
637
|
-
{sections.input && (
|
|
638
|
-
<div className="shrink-0 border-t border-black/[0.06] bg-white">
|
|
639
|
-
<MessageInput
|
|
640
|
-
activeChannel={activeChannel}
|
|
641
|
-
conversationId={conversationId}
|
|
642
|
-
onSendMessage={handleSendMessage}
|
|
643
|
-
replyTo={replyTo}
|
|
644
|
-
onCancelReply={() => setReplyTo(null)}
|
|
645
|
-
/>
|
|
646
|
-
</div>
|
|
647
|
-
)}
|
|
648
|
-
</Channel>
|
|
649
|
-
</div>
|
|
603
|
+
```
|
|
604
|
+
src/
|
|
605
|
+
├── configs/
|
|
606
|
+
│ ├── chat.config.ts # credentials only
|
|
607
|
+
│ ├── chat.customization.config.ts # components + classNames
|
|
608
|
+
│ └── chat.layout.config.ts # layout section toggles
|
|
609
|
+
├── components/chat/custom/
|
|
610
|
+
│ ├── ElstarMembersDrawer.tsx # custom drawer example
|
|
611
|
+
│ ├── ElstarChatAvatar.tsx
|
|
612
|
+
│ └── index.ts
|
|
613
|
+
└── views/Chat.tsx # wires ChatMain
|
|
614
|
+
```
|
|
650
615
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
616
|
+
### `chat.config.ts` — credentials only
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
export const chatConfig = {
|
|
620
|
+
clientId: "YOUR_CLIENT_ID",
|
|
621
|
+
accessToken: "YOUR_JWT",
|
|
622
|
+
loggedUserDetails: { _id: "...", name: "...", email: "..." },
|
|
623
|
+
apiUrl: "https://api.example.com/api/v1",
|
|
624
|
+
socketUrl: "https://api.example.com",
|
|
625
|
+
themeColor: "#6366f1",
|
|
626
|
+
} as const;
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
### `chat.customization.config.ts` — UI overrides
|
|
630
|
+
|
|
631
|
+
```ts
|
|
632
|
+
import type { ChatClassNames, ChatComponents } from "@realtimexsco/live-chat";
|
|
633
|
+
import {
|
|
634
|
+
ElstarChatAvatar,
|
|
635
|
+
ElstarHeaderCallActions,
|
|
636
|
+
ElstarMembersDrawer,
|
|
637
|
+
ElstarPhoneCallButton,
|
|
638
|
+
ElstarVideoCallButton,
|
|
639
|
+
} from "@/components/chat/custom";
|
|
640
|
+
|
|
641
|
+
/** Only register slots you want to customize. All others use package defaults. */
|
|
642
|
+
export const chatComponents: ChatComponents = {
|
|
643
|
+
Avatar: ElstarChatAvatar,
|
|
644
|
+
HeaderCallActions: ElstarHeaderCallActions,
|
|
645
|
+
PhoneCallButton: ElstarPhoneCallButton,
|
|
646
|
+
VideoCallButton: ElstarVideoCallButton,
|
|
647
|
+
MembersDrawer: ElstarMembersDrawer,
|
|
664
648
|
};
|
|
665
649
|
|
|
666
|
-
export
|
|
650
|
+
export const chatClassNames: ChatClassNames = {
|
|
651
|
+
shell: "chat-container-root",
|
|
652
|
+
channelListItemActive: "bg-indigo-50 ring-1 ring-indigo-200",
|
|
653
|
+
messageBubbleSender: "rounded-2xl shadow-sm",
|
|
654
|
+
drawer: "z-[60]",
|
|
655
|
+
drawerContent: "rounded-l-2xl",
|
|
656
|
+
};
|
|
667
657
|
```
|
|
668
658
|
|
|
669
|
-
|
|
659
|
+
### `views/Chat.tsx` — single entry
|
|
670
660
|
|
|
671
661
|
```tsx
|
|
672
|
-
import {
|
|
673
|
-
import ModularChatLayout from "@/components/chat/ModularChatLayout";
|
|
662
|
+
import ChatMain, { type ChatColorMode } from "@realtimexsco/live-chat";
|
|
674
663
|
import { chatConfig } from "@/configs/chat.config";
|
|
664
|
+
import { chatComponents, chatClassNames } from "@/configs/chat.customization.config";
|
|
675
665
|
import { chatLayoutConfig } from "@/configs/chat.layout.config";
|
|
676
666
|
|
|
677
|
-
export default function
|
|
667
|
+
export default function ChatPage() {
|
|
678
668
|
return (
|
|
679
|
-
<div className="
|
|
680
|
-
<
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
onSocketConnected={(id) => console.log("Socket connected:", id)}
|
|
689
|
-
onSocketError={(err) => console.error("Socket error:", err)}
|
|
690
|
-
>
|
|
691
|
-
<ModularChatLayout layout={chatLayoutConfig} />
|
|
692
|
-
</Chat>
|
|
669
|
+
<div className="h-dvh min-h-0 overflow-hidden">
|
|
670
|
+
<ChatMain
|
|
671
|
+
{...chatConfig}
|
|
672
|
+
layout={chatLayoutConfig}
|
|
673
|
+
components={chatComponents}
|
|
674
|
+
classNames={chatClassNames}
|
|
675
|
+
showColorModeToggle={false}
|
|
676
|
+
colorMode="light" // sync with your app theme
|
|
677
|
+
/>
|
|
693
678
|
</div>
|
|
694
679
|
);
|
|
695
680
|
}
|
|
696
681
|
```
|
|
697
682
|
|
|
698
|
-
|
|
699
|
-
> the **top** of the component, before any conditional `return`.
|
|
683
|
+
### Full custom `MembersDrawer` example
|
|
700
684
|
|
|
701
|
-
|
|
685
|
+
Use the package store inside your drawer — same data/actions as the default drawer:
|
|
686
|
+
|
|
687
|
+
```tsx
|
|
688
|
+
import { useState, useMemo } from "react";
|
|
689
|
+
import {
|
|
690
|
+
useChatStore,
|
|
691
|
+
type MembersDrawerSlotProps,
|
|
692
|
+
} from "@realtimexsco/live-chat";
|
|
693
|
+
import Drawer from "@/components/ui/Drawer"; // your design system
|
|
694
|
+
|
|
695
|
+
const MyMembersDrawer = ({
|
|
696
|
+
isOpen,
|
|
697
|
+
onOpenChange,
|
|
698
|
+
conversationId,
|
|
699
|
+
conversationInfo,
|
|
700
|
+
}: MembersDrawerSlotProps) => {
|
|
701
|
+
const allUsers = useChatStore((s) => s.allUsers);
|
|
702
|
+
const manageGroupMembers = useChatStore((s) => s.manageGroupMembers);
|
|
703
|
+
const loggedUserDetails = useChatStore((s) => s.loggedUserDetails);
|
|
702
704
|
|
|
703
|
-
|
|
705
|
+
const participants = conversationInfo?.participants ?? [];
|
|
704
706
|
|
|
705
|
-
|
|
707
|
+
return (
|
|
708
|
+
<Drawer isOpen={isOpen} onClose={() => onOpenChange(false)} title="Members">
|
|
709
|
+
{/* Your UI — add/remove members via manageGroupMembers */}
|
|
710
|
+
<ul>
|
|
711
|
+
{participants.map((p) => (
|
|
712
|
+
<li key={typeof p === "string" ? p : p._id}>{typeof p === "string" ? p : p.name}</li>
|
|
713
|
+
))}
|
|
714
|
+
</ul>
|
|
715
|
+
</Drawer>
|
|
716
|
+
);
|
|
717
|
+
};
|
|
706
718
|
|
|
707
|
-
|
|
708
|
-
my-app/
|
|
709
|
-
├── package.json
|
|
710
|
-
├── vite.config.ts
|
|
711
|
-
├── jsconfig.json # or tsconfig.json
|
|
712
|
-
├── index.html
|
|
713
|
-
└── src/
|
|
714
|
-
├── main.jsx
|
|
715
|
-
├── App.jsx
|
|
716
|
-
├── index.css
|
|
717
|
-
├── configs/
|
|
718
|
-
│ ├── chat.config.js
|
|
719
|
-
│ └── chat.layout.config.js
|
|
720
|
-
└── components/
|
|
721
|
-
└── chat/
|
|
722
|
-
└── ModularChatLayout.jsx # optional — or use ChatLayout
|
|
719
|
+
export default MyMembersDrawer;
|
|
723
720
|
```
|
|
724
721
|
|
|
725
|
-
|
|
722
|
+
Register it: `components={{ MembersDrawer: MyMembersDrawer }}`. If you **remove** that line, the package default members drawer appears again.
|
|
726
723
|
|
|
727
|
-
|
|
728
|
-
pnpm create vite my-app --template react
|
|
729
|
-
cd my-app
|
|
730
|
-
```
|
|
724
|
+
---
|
|
731
725
|
|
|
732
|
-
|
|
726
|
+
## Customization examples (Case 1–3)
|
|
733
727
|
|
|
734
|
-
|
|
735
|
-
{
|
|
736
|
-
"dependencies": {
|
|
737
|
-
"@realtimexsco/live-chat": "^1.0.0",
|
|
738
|
-
"classnames": "^2.5.1",
|
|
739
|
-
"react": "^19.2.7",
|
|
740
|
-
"react-dom": "^19.2.7",
|
|
741
|
-
"zustand": "^5.0.14"
|
|
742
|
-
},
|
|
743
|
-
"devDependencies": {
|
|
744
|
-
"@tailwindcss/vite": "^4.3.1",
|
|
745
|
-
"@vitejs/plugin-react": "^4.7.0",
|
|
746
|
-
"tailwindcss": "^4.3.1",
|
|
747
|
-
"vite": "^6.3.5"
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
```
|
|
728
|
+
### Case 1 — Replace entire chat UI
|
|
751
729
|
|
|
752
|
-
|
|
730
|
+
```tsx
|
|
731
|
+
import ChatMain, {
|
|
732
|
+
useChatController,
|
|
733
|
+
DefaultChatLayout,
|
|
734
|
+
type ChatLayoutSlotProps,
|
|
735
|
+
} from "@realtimexsco/live-chat";
|
|
753
736
|
|
|
754
|
-
|
|
755
|
-
{
|
|
756
|
-
|
|
757
|
-
"
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
737
|
+
// Option A: wrap the default layout
|
|
738
|
+
function MyChatShell(props: ChatLayoutSlotProps) {
|
|
739
|
+
return (
|
|
740
|
+
<div className="flex h-full flex-col">
|
|
741
|
+
<header className="shrink-0 border-b px-4 py-2 font-semibold">My App Chat</header>
|
|
742
|
+
<div className="min-h-0 flex-1">
|
|
743
|
+
<DefaultChatLayout {...props} shellClassName="rounded-none shadow-none" />
|
|
744
|
+
</div>
|
|
745
|
+
</div>
|
|
746
|
+
);
|
|
761
747
|
}
|
|
762
|
-
```
|
|
763
748
|
|
|
764
|
-
|
|
749
|
+
// Option B: build from scratch with useChatController()
|
|
750
|
+
function MyFullChatUI(_props: ChatLayoutSlotProps) {
|
|
751
|
+
const {
|
|
752
|
+
activeChannel,
|
|
753
|
+
conversationId,
|
|
754
|
+
conversationMessages,
|
|
755
|
+
handleChannelSelect,
|
|
756
|
+
handleSendMessage,
|
|
757
|
+
setActiveChannel,
|
|
758
|
+
state,
|
|
759
|
+
updateState,
|
|
760
|
+
} = useChatController();
|
|
765
761
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
762
|
+
return (
|
|
763
|
+
<div className="flex h-full">
|
|
764
|
+
{/* Your sidebar, header, messages, input — full control */}
|
|
765
|
+
</div>
|
|
766
|
+
);
|
|
767
|
+
}
|
|
770
768
|
|
|
771
|
-
|
|
769
|
+
<ChatMain
|
|
770
|
+
{...chatConfig}
|
|
771
|
+
clientId={chatConfig.clientId}
|
|
772
|
+
components={{ ChatLayout: MyChatShell }}
|
|
773
|
+
/>;
|
|
772
774
|
```
|
|
773
775
|
|
|
774
|
-
###
|
|
776
|
+
### Case 2 — Replace individual components
|
|
775
777
|
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
|
|
778
|
+
```tsx
|
|
779
|
+
<ChatMain
|
|
780
|
+
{...chatConfig}
|
|
781
|
+
clientId={chatConfig.clientId}
|
|
782
|
+
components={{
|
|
783
|
+
Avatar: MyAvatar,
|
|
784
|
+
ChannelListItem: MyConversationRow,
|
|
785
|
+
MessageItem: MyMessageBubble,
|
|
786
|
+
MessageInput: MyComposer,
|
|
787
|
+
PhoneCallButton: MyPhoneBtn,
|
|
788
|
+
MembersDrawer: MyMembersPanel,
|
|
789
|
+
}}
|
|
790
|
+
/>
|
|
779
791
|
```
|
|
780
792
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
Production:
|
|
793
|
+
### Case 3 — Style with classNames only
|
|
784
794
|
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
|
|
795
|
+
```tsx
|
|
796
|
+
<ChatMain
|
|
797
|
+
{...chatConfig}
|
|
798
|
+
clientId={chatConfig.clientId}
|
|
799
|
+
classNames={{
|
|
800
|
+
channelListItemActive: "bg-indigo-50 ring-1 ring-indigo-200",
|
|
801
|
+
messageBubbleSender: "rounded-2xl shadow-sm",
|
|
802
|
+
messageBubbleReceiver: "rounded-2xl bg-white",
|
|
803
|
+
messageInput: "border-t border-gray-200",
|
|
804
|
+
}}
|
|
805
|
+
/>
|
|
788
806
|
```
|
|
789
807
|
|
|
790
|
-
|
|
808
|
+
---
|
|
791
809
|
|
|
792
|
-
|
|
793
|
-
pnpm add @realtimexsco/live-chat@latest
|
|
794
|
-
rm -rf node_modules/.vite dist
|
|
795
|
-
pnpm dev
|
|
796
|
-
```
|
|
810
|
+
## Phone & video calling
|
|
797
811
|
|
|
798
|
-
|
|
812
|
+
Call buttons appear in **DM chats only** (hidden for groups).
|
|
799
813
|
|
|
800
|
-
|
|
814
|
+
### Option A — Keep default buttons, add callbacks
|
|
801
815
|
|
|
802
|
-
|
|
816
|
+
```tsx
|
|
817
|
+
features={{
|
|
818
|
+
showPhoneCall: true,
|
|
819
|
+
showVideoCall: true,
|
|
820
|
+
onPhoneCall: ({ activeChannel, conversationId }) => {
|
|
821
|
+
yourVoiceSDK.start(activeChannel.id, conversationId);
|
|
822
|
+
},
|
|
823
|
+
onVideoCall: ({ activeChannel, conversationId }) => {
|
|
824
|
+
yourVideoSDK.start(activeChannel.id, conversationId);
|
|
825
|
+
},
|
|
826
|
+
}}
|
|
827
|
+
```
|
|
803
828
|
|
|
804
|
-
###
|
|
829
|
+
### Option B — Replace the call button group
|
|
805
830
|
|
|
806
|
-
```
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
│ ├── layout.tsx
|
|
816
|
-
│ ├── page.tsx
|
|
817
|
-
│ └── chat/
|
|
818
|
-
│ └── page.tsx
|
|
819
|
-
├── configs/
|
|
820
|
-
│ ├── chat.config.ts
|
|
821
|
-
│ └── chat.layout.config.ts
|
|
822
|
-
└── components/
|
|
823
|
-
└── chat/
|
|
824
|
-
└── ModularChatLayout.tsx # optional — or use ChatLayout
|
|
831
|
+
```tsx
|
|
832
|
+
components={{
|
|
833
|
+
HeaderCallActions: ({ onPhoneCall, onVideoCall, activeChannel }) => (
|
|
834
|
+
<div className="flex gap-2">
|
|
835
|
+
<button type="button" onClick={onPhoneCall}>Call {activeChannel.name}</button>
|
|
836
|
+
<button type="button" onClick={onVideoCall}>Video</button>
|
|
837
|
+
</div>
|
|
838
|
+
),
|
|
839
|
+
}}
|
|
825
840
|
```
|
|
826
841
|
|
|
827
|
-
|
|
842
|
+
> Do **not** import `HeaderCallActions` from the package inside your custom `HeaderCallActions` — that causes infinite recursion. Render your own buttons instead.
|
|
828
843
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
844
|
+
### Option C — Replace individual buttons
|
|
845
|
+
|
|
846
|
+
```tsx
|
|
847
|
+
components={{
|
|
848
|
+
PhoneCallButton: ({ onPhoneCall, className }) => (
|
|
849
|
+
<button type="button" className={className} onClick={onPhoneCall}>📞</button>
|
|
850
|
+
),
|
|
851
|
+
VideoCallButton: ({ onVideoCall, className }) => (
|
|
852
|
+
<button type="button" className={className} onClick={onVideoCall}>📹</button>
|
|
853
|
+
),
|
|
854
|
+
}}
|
|
832
855
|
```
|
|
833
856
|
|
|
834
|
-
###
|
|
857
|
+
### Hide calls
|
|
835
858
|
|
|
836
|
-
```
|
|
837
|
-
{
|
|
838
|
-
"dependencies": {
|
|
839
|
-
"@realtimexsco/live-chat": "^1.0.0",
|
|
840
|
-
"classnames": "^2.5.1",
|
|
841
|
-
"next": "16.2.9",
|
|
842
|
-
"react": "19.2.4",
|
|
843
|
-
"react-dom": "19.2.4",
|
|
844
|
-
"zustand": "^5.0.14"
|
|
845
|
-
},
|
|
846
|
-
"devDependencies": {
|
|
847
|
-
"@tailwindcss/postcss": "^4",
|
|
848
|
-
"tailwindcss": "^4",
|
|
849
|
-
"typescript": "^5"
|
|
850
|
-
}
|
|
851
|
-
}
|
|
859
|
+
```tsx
|
|
860
|
+
features={{ showPhoneCall: false, showVideoCall: false }}
|
|
852
861
|
```
|
|
853
862
|
|
|
854
|
-
|
|
863
|
+
---
|
|
855
864
|
|
|
856
|
-
|
|
857
|
-
{
|
|
858
|
-
"compilerOptions": {
|
|
859
|
-
"paths": { "@/*": ["./src/*"] }
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
```
|
|
865
|
+
## Theme, light/dark, date/time
|
|
863
866
|
|
|
864
|
-
###
|
|
867
|
+
### Brand color
|
|
865
868
|
|
|
866
869
|
```tsx
|
|
867
|
-
|
|
868
|
-
|
|
870
|
+
<ChatMain {...chatConfig} clientId={chatConfig.clientId} themeColor="#7c3aed" />
|
|
871
|
+
```
|
|
869
872
|
|
|
870
|
-
|
|
871
|
-
title: "RealtimeX Chat",
|
|
872
|
-
description: "RealtimeX chat integration with Next.js",
|
|
873
|
-
};
|
|
873
|
+
Runtime updates `--chat-theme` CSS variables automatically.
|
|
874
874
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
```
|
|
875
|
+
### Light / dark mode
|
|
876
|
+
|
|
877
|
+
| Pattern | Props |
|
|
878
|
+
|---------|-------|
|
|
879
|
+
| Your app controls theme (e.g. admin template) | `colorMode={mode}` `onColorModeChange={setMode}` `showColorModeToggle={false}` |
|
|
880
|
+
| Chat manages its own theme | `defaultColorMode="light"` (built-in toggle shown) |
|
|
881
|
+
| Start dark | `defaultColorMode="dark"` |
|
|
883
882
|
|
|
884
|
-
###
|
|
883
|
+
### Date / time
|
|
884
|
+
|
|
885
|
+
```tsx
|
|
886
|
+
<ChatMain
|
|
887
|
+
{...chatConfig}
|
|
888
|
+
clientId={chatConfig.clientId}
|
|
889
|
+
locale="en-GB"
|
|
890
|
+
timeZone="Europe/London"
|
|
891
|
+
hour12={false}
|
|
892
|
+
showDateTimeSettings={false}
|
|
893
|
+
onDateTimePreferencesChange={(prefs) => console.log(prefs)}
|
|
894
|
+
/>
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
---
|
|
898
|
+
|
|
899
|
+
## Layout sections (`layout` prop)
|
|
900
|
+
|
|
901
|
+
Toggle parts of the default UI without writing new components.
|
|
902
|
+
|
|
903
|
+
```ts
|
|
904
|
+
import { defaultChatLayoutConfig } from "@realtimexsco/live-chat";
|
|
905
|
+
|
|
906
|
+
export const chatLayoutConfig = {
|
|
907
|
+
...defaultChatLayoutConfig,
|
|
908
|
+
// sidebar: true, // left panel wrapper
|
|
909
|
+
// loggedUserDetails: true, // profile + new chat button
|
|
910
|
+
// channelList: true, // conversation list
|
|
911
|
+
// header: true, // channel header bar
|
|
912
|
+
// messageList: true, // message bubbles
|
|
913
|
+
// input: true, // composer
|
|
914
|
+
// forwardModal: true, // forward dialog
|
|
915
|
+
};
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
Examples:
|
|
919
|
+
|
|
920
|
+
```tsx
|
|
921
|
+
layout={{ input: false }} // read-only (no composer)
|
|
922
|
+
layout={{ sidebar: false }} // hide sidebar — messages only
|
|
923
|
+
layout={{ forwardModal: false }} // disable forward modal
|
|
924
|
+
layout={{ loggedUserDetails: false, channelList: true }} // list without profile block
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
---
|
|
928
|
+
|
|
929
|
+
## All component slots + examples
|
|
930
|
+
|
|
931
|
+
Pass only the slots you want to override via `components` on `ChatMain` (or `uiConfig` on `Chat`).
|
|
932
|
+
|
|
933
|
+
Import prop types from the package for TypeScript, e.g. `AvatarSlotProps`, `MessageItemSlotProps`.
|
|
934
|
+
|
|
935
|
+
| Slot | Replaces | Prop type |
|
|
936
|
+
|------|----------|-----------|
|
|
937
|
+
| `ChatLayout` | Entire chat shell | `ChatLayoutSlotProps` |
|
|
938
|
+
| `LoggedUserDetails` | Sidebar profile + new chat | `LoggedUserDetailsSlotProps` |
|
|
939
|
+
| `ChannelList` | Conversation list | `ChannelListSlotProps` |
|
|
940
|
+
| `ChannelListItem` | One conversation row | `ChannelListItemSlotProps` |
|
|
941
|
+
| `ChannelHeader` | Full header bar | `ChannelHeaderSlotProps` |
|
|
942
|
+
| `HeaderLeftSide` | Back, name, pin/star | `HeaderLeftSideSlotProps` |
|
|
943
|
+
| `HeaderRightSide` | Search, menu, settings | `HeaderRightSideSlotProps` |
|
|
944
|
+
| `HeaderCallActions` | Phone + video group | `HeaderCallActionsSlotProps` |
|
|
945
|
+
| `PhoneCallButton` | Voice call button | `PhoneCallButtonSlotProps` |
|
|
946
|
+
| `VideoCallButton` | Video call button | `VideoCallButtonSlotProps` |
|
|
947
|
+
| `HeaderSearchOverlay` | Message search dropdown | `HeaderSearchOverlaySlotProps` |
|
|
948
|
+
| `MessageList` | Scrollable messages area | `ActiveChannelMessagesSlotProps` |
|
|
949
|
+
| `MessageItem` | Single message bubble | `MessageItemSlotProps` |
|
|
950
|
+
| `MessageInput` | Composer | `ChannelMessageBoxSlotProps` |
|
|
951
|
+
| `ForwardModal` | Forward dialog | `ForwardModalSlotProps` |
|
|
952
|
+
| `ChatPanelAlerts` | Error/info banner | `ChatPanelAlertsSlotProps` |
|
|
953
|
+
| `ChatActionModal` | Clear/delete/block confirm | `ChatActionModalSlotProps` |
|
|
954
|
+
| `ChannelDetailsDrawer` | Channel details sheet | `ChannelDetailsDrawerSlotProps` |
|
|
955
|
+
| `MembersDrawer` | Group members sheet | `MembersDrawerSlotProps` |
|
|
956
|
+
| `AdminsDrawer` | Group admins sheet | `AdminsDrawerSlotProps` |
|
|
957
|
+
| `PermissionDrawer` | Permissions sheet | `PermissionDrawerSlotProps` |
|
|
958
|
+
| `AttachmentsDrawer` | Attachments sheet | `AttachmentsDrawerSlotProps` |
|
|
959
|
+
| `PinnedMessagesDrawer` | Pinned messages sheet | `PinnedMessagesDrawerSlotProps` |
|
|
960
|
+
| `StarredMessagesDrawer` | Starred messages sheet | `StarredMessagesDrawerSlotProps` |
|
|
961
|
+
| `Avatar` | Avatar everywhere | `AvatarSlotProps` |
|
|
962
|
+
| `MessageToolbar` | Reply/forward/edit menu | `MessageToolbarSlotProps` |
|
|
963
|
+
| `MessageStatus` | Sent/delivered/read ticks | `MessageStatusSlotProps` |
|
|
964
|
+
| `MessageAttachments` | Files/images in bubble | `MessageAttachmentsSlotProps` |
|
|
965
|
+
| `MessageReactions` | Emoji chips | `MessageReactionsSlotProps` |
|
|
966
|
+
| `MessageReplySnippet` | Quoted reply in bubble | `MessageReplySnippetSlotProps` |
|
|
967
|
+
| `ChatThemeToggle` | Light/dark toggle | `{ className?: string }` |
|
|
968
|
+
| `ChatDateTimeSettings` | Date/time settings button | `Record<string, never>` |
|
|
969
|
+
| `ChatSocketStatus` | Connection indicator | `{ className?: string }` |
|
|
970
|
+
|
|
971
|
+
### Code examples for common slots
|
|
972
|
+
|
|
973
|
+
**MembersDrawer** (full custom drawer — Elstar-style)
|
|
974
|
+
|
|
975
|
+
```tsx
|
|
976
|
+
import { useChatStore, type MembersDrawerSlotProps } from "@realtimexsco/live-chat";
|
|
977
|
+
|
|
978
|
+
const MyMembersDrawer = ({
|
|
979
|
+
isOpen,
|
|
980
|
+
onOpenChange,
|
|
981
|
+
conversationId,
|
|
982
|
+
conversationInfo,
|
|
983
|
+
}: MembersDrawerSlotProps) => {
|
|
984
|
+
const manageGroupMembers = useChatStore((s) => s.manageGroupMembers);
|
|
985
|
+
const participants = conversationInfo?.participants ?? [];
|
|
986
|
+
|
|
987
|
+
if (!isOpen) return null;
|
|
988
|
+
|
|
989
|
+
return (
|
|
990
|
+
<aside className="members-drawer">
|
|
991
|
+
<header>
|
|
992
|
+
<h2>Members ({participants.length})</h2>
|
|
993
|
+
<button type="button" onClick={() => onOpenChange(false)}>Close</button>
|
|
994
|
+
</header>
|
|
995
|
+
<ul>
|
|
996
|
+
{participants.map((p, i) => {
|
|
997
|
+
const id = typeof p === "string" ? p : p._id;
|
|
998
|
+
const name = typeof p === "string" ? "User" : p.name;
|
|
999
|
+
return <li key={`${id}-${i}`}>{name}</li>;
|
|
1000
|
+
})}
|
|
1001
|
+
</ul>
|
|
1002
|
+
</aside>
|
|
1003
|
+
);
|
|
1004
|
+
};
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
**ChannelDetailsDrawer**
|
|
1008
|
+
|
|
1009
|
+
```tsx
|
|
1010
|
+
import type { ChannelDetailsDrawerSlotProps } from "@realtimexsco/live-chat";
|
|
1011
|
+
|
|
1012
|
+
const MyDetailsDrawer = ({ isOpen, onOpenChange, activeChannel, conversationInfo }: ChannelDetailsDrawerSlotProps) => (
|
|
1013
|
+
<dialog open={isOpen} onClose={() => onOpenChange(false)}>
|
|
1014
|
+
<h2>{activeChannel.name}</h2>
|
|
1015
|
+
<p>{conversationInfo?.groupDescription ?? "Direct message"}</p>
|
|
1016
|
+
</dialog>
|
|
1017
|
+
);
|
|
1018
|
+
```
|
|
1019
|
+
|
|
1020
|
+
**Avatar**
|
|
1021
|
+
|
|
1022
|
+
```tsx
|
|
1023
|
+
import type { AvatarSlotProps } from "@realtimexsco/live-chat";
|
|
1024
|
+
|
|
1025
|
+
const MyAvatar = ({ src, name, size = "md", isOnline, showOnline }: AvatarSlotProps) => (
|
|
1026
|
+
<div className="relative">
|
|
1027
|
+
<img src={src || undefined} alt={name} className={`avatar avatar--${size}`} />
|
|
1028
|
+
{showOnline && isOnline && <span className="online-dot" />}
|
|
1029
|
+
</div>
|
|
1030
|
+
);
|
|
1031
|
+
```
|
|
1032
|
+
|
|
1033
|
+
**MessageItem** (custom bubble)
|
|
1034
|
+
|
|
1035
|
+
```tsx
|
|
1036
|
+
import type { MessageItemSlotProps } from "@realtimexsco/live-chat";
|
|
1037
|
+
|
|
1038
|
+
const MyMessageItem = ({ isSender, message, createdAt }: MessageItemSlotProps) => (
|
|
1039
|
+
<div className={isSender ? "text-right" : "text-left"}>
|
|
1040
|
+
<div className={isSender ? "bubble-sent" : "bubble-received"}>{message}</div>
|
|
1041
|
+
<time className="text-xs opacity-60">{createdAt}</time>
|
|
1042
|
+
</div>
|
|
1043
|
+
);
|
|
1044
|
+
```
|
|
1045
|
+
|
|
1046
|
+
**ChannelListItem**
|
|
1047
|
+
|
|
1048
|
+
```tsx
|
|
1049
|
+
import type { ChannelListItemSlotProps } from "@realtimexsco/live-chat";
|
|
1050
|
+
|
|
1051
|
+
const MyRow = ({ channel, isActive, onSelect }: ChannelListItemSlotProps) => (
|
|
1052
|
+
<button
|
|
1053
|
+
type="button"
|
|
1054
|
+
className={isActive ? "row-active" : "row"}
|
|
1055
|
+
onClick={() => onSelect(channel)}
|
|
1056
|
+
>
|
|
1057
|
+
{channel.name}
|
|
1058
|
+
{channel.unreadCount ? ` (${channel.unreadCount})` : ""}
|
|
1059
|
+
</button>
|
|
1060
|
+
);
|
|
1061
|
+
```
|
|
1062
|
+
|
|
1063
|
+
**MessageInput**
|
|
1064
|
+
|
|
1065
|
+
```tsx
|
|
1066
|
+
import type { ChannelMessageBoxSlotProps } from "@realtimexsco/live-chat";
|
|
1067
|
+
|
|
1068
|
+
const MyInput = ({ onSendMessage, replyTo, onCancelReply }: ChannelMessageBoxSlotProps) => {
|
|
1069
|
+
const [text, setText] = useState("");
|
|
1070
|
+
return (
|
|
1071
|
+
<div>
|
|
1072
|
+
{replyTo && <button type="button" onClick={onCancelReply}>Cancel reply</button>}
|
|
1073
|
+
<input value={text} onChange={(e) => setText(e.target.value)} />
|
|
1074
|
+
<button type="button" onClick={() => { onSendMessage?.(text, [], replyTo); setText(""); }}>
|
|
1075
|
+
Send
|
|
1076
|
+
</button>
|
|
1077
|
+
</div>
|
|
1078
|
+
);
|
|
1079
|
+
};
|
|
1080
|
+
```
|
|
1081
|
+
|
|
1082
|
+
---
|
|
1083
|
+
|
|
1084
|
+
## All classNames keys
|
|
1085
|
+
|
|
1086
|
+
Merged with package defaults — your classes extend styling without replacing components.
|
|
1087
|
+
|
|
1088
|
+
| Key | UI region |
|
|
1089
|
+
|-----|-----------|
|
|
1090
|
+
| `shell` | Outermost chat wrapper |
|
|
1091
|
+
| `mainPanel` | Inner rounded panel |
|
|
1092
|
+
| `sidebar` | Left sidebar |
|
|
1093
|
+
| `loggedUserDetails` | Profile block at top |
|
|
1094
|
+
| `channelList` | Conversation list container |
|
|
1095
|
+
| `channelListItem` | Each conversation row |
|
|
1096
|
+
| `channelListItemActive` | Selected conversation row |
|
|
1097
|
+
| `channelHeader` | Header bar |
|
|
1098
|
+
| `headerLeft` | Header left section |
|
|
1099
|
+
| `headerRight` | Header right section |
|
|
1100
|
+
| `headerCallActions` | Call buttons wrapper |
|
|
1101
|
+
| `phoneCallButton` | Phone button |
|
|
1102
|
+
| `videoCallButton` | Video button |
|
|
1103
|
+
| `messageList` | Messages scroll area |
|
|
1104
|
+
| `messageItem` | Message row wrapper |
|
|
1105
|
+
| `messageBubble` | Bubble (both sides) |
|
|
1106
|
+
| `messageBubbleSender` | Outgoing bubble |
|
|
1107
|
+
| `messageBubbleReceiver` | Incoming bubble |
|
|
1108
|
+
| `messageInput` | Composer container |
|
|
1109
|
+
| `messageInputTextarea` | Textarea element |
|
|
1110
|
+
| `avatar` | Avatar wrapper |
|
|
1111
|
+
| `avatarFallback` | Initials fallback |
|
|
1112
|
+
| `dateDivider` | "Today" / date separator |
|
|
1113
|
+
| `emptyState` | No messages placeholder |
|
|
1114
|
+
| `forwardModal` | Forward dialog |
|
|
1115
|
+
| `chatPanelAlerts` | Error/info alert bar |
|
|
1116
|
+
| `drawer` | Drawer overlay/shell |
|
|
1117
|
+
| `drawerContent` | Drawer panel body |
|
|
1118
|
+
| `searchOverlay` | In-header search results |
|
|
1119
|
+
|
|
1120
|
+
```tsx
|
|
1121
|
+
classNames={{
|
|
1122
|
+
shell: "chat-container-root",
|
|
1123
|
+
channelListItemActive: "bg-indigo-50 dark:bg-indigo-500/10",
|
|
1124
|
+
messageBubbleSender: "rounded-2xl shadow-sm",
|
|
1125
|
+
messageBubbleReceiver: "rounded-2xl",
|
|
1126
|
+
drawer: "z-[60]",
|
|
1127
|
+
}}
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
---
|
|
1131
|
+
|
|
1132
|
+
## Wrapping package defaults (`packageDefaultComponents`)
|
|
1133
|
+
|
|
1134
|
+
The package exports every built-in slot component so you can wrap defaults with your theme instead of rebuilding from scratch.
|
|
1135
|
+
|
|
1136
|
+
```tsx
|
|
1137
|
+
import {
|
|
1138
|
+
packageDefaultComponents,
|
|
1139
|
+
type ChatComponents,
|
|
1140
|
+
} from "@realtimexsco/live-chat";
|
|
1141
|
+
|
|
1142
|
+
function withTheme<P extends object>(
|
|
1143
|
+
Default: React.ComponentType<P>,
|
|
1144
|
+
className: string
|
|
1145
|
+
): React.ComponentType<P> {
|
|
1146
|
+
return (props) => (
|
|
1147
|
+
<div className={className}>
|
|
1148
|
+
<Default {...props} />
|
|
1149
|
+
</div>
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
const components: ChatComponents = {
|
|
1154
|
+
MessageItem: withTheme(packageDefaultComponents.MessageItem!, "my-message"),
|
|
1155
|
+
ChannelListItem: withTheme(packageDefaultComponents.ChannelListItem!, "my-row"),
|
|
1156
|
+
Avatar: MyFullyCustomAvatar, // or use DefaultChatAvatar from package
|
|
1157
|
+
};
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
Also exported for reference:
|
|
1161
|
+
|
|
1162
|
+
- `packageDefaultComponents` — map of all slot defaults
|
|
1163
|
+
- `DefaultChatAvatar` — inner avatar (use when customizing `Avatar` slot)
|
|
1164
|
+
- `DefaultChannelListItem` — inner list row (use when customizing `ChannelListItem` slot)
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
## API query params
|
|
1169
|
+
|
|
1170
|
+
Pass extra query string params from your app into package HTTP requests. Params are merged in the axios interceptor (built-in params like `limit` / `page` / `conversationId` still win if they conflict).
|
|
1171
|
+
|
|
1172
|
+
**Default behavior:** `queryParams` apply to **all GET APIs** only (`queryParamApis` defaults to `['get']`). Login, create group, and other POST/PATCH calls are **not** affected unless you opt in with `queryParamApis={['all']}`.
|
|
1173
|
+
|
|
1174
|
+
---
|
|
1175
|
+
|
|
1176
|
+
### 1. Default — same params on every GET API
|
|
1177
|
+
|
|
1178
|
+
**When to use:** Your app needs the same filter (e.g. `branchId`) on every list/fetch GET call inside the chat package.
|
|
1179
|
+
|
|
1180
|
+
**Explanation:** You only pass `queryParams`. You do **not** need `queryParamApis` — it defaults to `['get']`, so every GET endpoint below receives these params. POST/PATCH stay clean.
|
|
1181
|
+
|
|
1182
|
+
```tsx
|
|
1183
|
+
<ChatMain
|
|
1184
|
+
{...chatConfig}
|
|
1185
|
+
queryParams={{ branchId: "12" }}
|
|
1186
|
+
// queryParamApis default = ['get']
|
|
1187
|
+
/>
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
**What happens on the network:**
|
|
1191
|
+
|
|
1192
|
+
| Request | Query string |
|
|
1193
|
+
|---------|----------------|
|
|
1194
|
+
| `GET /user/list` | `?branchId=12` |
|
|
1195
|
+
| `GET /conversation/list` | `?limit=20&page=1&branchId=12` |
|
|
1196
|
+
| `GET /message/get-all-from-conversation` | `?conversationId=…&limit=…&branchId=12` |
|
|
1197
|
+
| `GET …/pinned-messages` | `?conversationId=…&branchId=12` |
|
|
1198
|
+
| `POST /user/login/client-user` | *(no extra query params)* |
|
|
1199
|
+
|
|
1200
|
+
**GET APIs covered by default (`CHAT_GET_API_KEYS`):**
|
|
1201
|
+
|
|
1202
|
+
| Key | Endpoint |
|
|
1203
|
+
|-----|----------|
|
|
1204
|
+
| `users` | `GET /user/list` |
|
|
1205
|
+
| `conversations` | `GET /conversation/list` |
|
|
1206
|
+
| `conversationInfo` | `GET /conversation/get/information/:id` |
|
|
1207
|
+
| `messages` | `GET /message/get-all-from-conversation` |
|
|
1208
|
+
| `pinnedMessages` | `GET message/pinned-messages` |
|
|
1209
|
+
| `starredMessages` | `GET message/starred-messages` |
|
|
1210
|
+
| `searchMessages` | `GET /message/search-messages` |
|
|
1211
|
+
| `searchMessagesContext` | `GET /message/searched-message/context` |
|
|
1212
|
+
|
|
1213
|
+
```tsx
|
|
1214
|
+
import { CHAT_GET_API_KEYS, CHAT_API_KEYS } from "@realtimexsco/live-chat";
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
---
|
|
1218
|
+
|
|
1219
|
+
### 2. Different params per GET API
|
|
1220
|
+
|
|
1221
|
+
**When to use:** Each endpoint needs its own query keys (e.g. conversations need `branchId`, messages need `locale`, users need `orgId`).
|
|
1222
|
+
|
|
1223
|
+
**Explanation:** Use `queryParamsByApi` with named API keys. Only the APIs you list get params — and each can have a different object. You do not need `queryParams` or `queryParamApis` for this pattern.
|
|
1224
|
+
|
|
1225
|
+
```tsx
|
|
1226
|
+
<ChatMain
|
|
1227
|
+
{...chatConfig}
|
|
1228
|
+
queryParamsByApi={{
|
|
1229
|
+
conversations: { branchId: "12" },
|
|
1230
|
+
messages: { locale: "en" },
|
|
1231
|
+
users: { orgId: "x" },
|
|
1232
|
+
}}
|
|
1233
|
+
/>
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
**What happens on the network:**
|
|
1237
|
+
|
|
1238
|
+
| Request | Query string |
|
|
1239
|
+
|---------|----------------|
|
|
1240
|
+
| `GET /conversation/list` | `?…&branchId=12` |
|
|
1241
|
+
| `GET /message/get-all-from-conversation` | `?…&locale=en` |
|
|
1242
|
+
| `GET /user/list` | `?orgId=x` |
|
|
1243
|
+
| Other GET APIs (not listed) | *(no extra params from this prop)* |
|
|
1244
|
+
|
|
1245
|
+
---
|
|
1246
|
+
|
|
1247
|
+
### 3. Only one GET API
|
|
1248
|
+
|
|
1249
|
+
**When to use:** You want shared-style `queryParams`, but only on a single endpoint (e.g. conversation list), not on messages or users.
|
|
1250
|
+
|
|
1251
|
+
**Explanation:** Pass `queryParams` for the values, and restrict with `queryParamApis={['conversations']}`. Other GET APIs will not receive `branchId`.
|
|
1252
|
+
|
|
1253
|
+
```tsx
|
|
1254
|
+
<ChatMain
|
|
1255
|
+
{...chatConfig}
|
|
1256
|
+
queryParams={{ branchId: "12" }}
|
|
1257
|
+
queryParamApis={["conversations"]}
|
|
1258
|
+
/>
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
**What happens on the network:**
|
|
1262
|
+
|
|
1263
|
+
| Request | Query string |
|
|
1264
|
+
|---------|----------------|
|
|
1265
|
+
| `GET /conversation/list` | `?limit=20&page=1&branchId=12` |
|
|
1266
|
+
| `GET /message/get-all-from-conversation` | *(no `branchId`)* |
|
|
1267
|
+
| `GET /user/list` | *(no `branchId`)* |
|
|
1268
|
+
|
|
1269
|
+
You can also target multiple selected GETs:
|
|
1270
|
+
|
|
1271
|
+
```tsx
|
|
1272
|
+
<ChatMain
|
|
1273
|
+
{...chatConfig}
|
|
1274
|
+
queryParams={{ orgId: "x" }}
|
|
1275
|
+
queryParamApis={["messages", "users"]}
|
|
1276
|
+
/>
|
|
1277
|
+
```
|
|
1278
|
+
|
|
1279
|
+
---
|
|
1280
|
+
|
|
1281
|
+
### Bonus: shared + per-API together
|
|
1282
|
+
|
|
1283
|
+
**Explanation:** Shared params apply to the APIs in `queryParamApis`. Per-API params from `queryParamsByApi` merge on top for that endpoint.
|
|
1284
|
+
|
|
1285
|
+
```tsx
|
|
1286
|
+
<ChatMain
|
|
1287
|
+
{...chatConfig}
|
|
1288
|
+
queryParams={{ tenant: "abc" }}
|
|
1289
|
+
queryParamApis={["conversations", "messages"]}
|
|
1290
|
+
queryParamsByApi={{
|
|
1291
|
+
conversations: { branchId: "12" },
|
|
1292
|
+
messages: { locale: "en" },
|
|
1293
|
+
}}
|
|
1294
|
+
/>
|
|
1295
|
+
```
|
|
1296
|
+
|
|
1297
|
+
- conversations → `?tenant=abc&branchId=12`
|
|
1298
|
+
- messages → `?tenant=abc&locale=en`
|
|
1299
|
+
|
|
1300
|
+
---
|
|
1301
|
+
|
|
1302
|
+
### `queryParamApis` options
|
|
1303
|
+
|
|
1304
|
+
| Value | Meaning |
|
|
1305
|
+
|-------|---------|
|
|
1306
|
+
| `['get']` | **Default.** All GET endpoints in the table above |
|
|
1307
|
+
| `['all']` | Every `apiClient` request (GET + POST + PATCH) |
|
|
1308
|
+
| Specific keys | Only those endpoints (e.g. `['conversations']`) |
|
|
1309
|
+
|
|
1310
|
+
### Imperative helpers
|
|
1311
|
+
|
|
1312
|
+
```tsx
|
|
1313
|
+
import {
|
|
1314
|
+
setChatQueryParams,
|
|
1315
|
+
setChatQueryParamApis,
|
|
1316
|
+
setChatQueryParamsByApi,
|
|
1317
|
+
} from "@realtimexsco/live-chat";
|
|
1318
|
+
|
|
1319
|
+
setChatQueryParams({ branchId: "12" });
|
|
1320
|
+
setChatQueryParamApis(["conversations"]);
|
|
1321
|
+
setChatQueryParamsByApi({ messages: { locale: "en" } });
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
Socket connection query and S3 presigned uploads are **not** changed by these props.
|
|
1325
|
+
|
|
1326
|
+
---
|
|
1327
|
+
|
|
1328
|
+
## ChatMain props reference
|
|
1329
|
+
|
|
1330
|
+
| Prop | Type | Default | Description |
|
|
1331
|
+
|------|------|---------|-------------|
|
|
1332
|
+
| `clientId` | `string` | — | **Required.** Tenant / client id |
|
|
1333
|
+
| `accessToken` | `string` | — | **Required.** JWT access token |
|
|
1334
|
+
| `loggedUserDetails` | `object` | — | **Required.** `{ _id, name, email }` — `_id` must match JWT |
|
|
1335
|
+
| `apiUrl` | `string` | — | **Required.** REST API ending with `/api/v1` (pass from your env/config) |
|
|
1336
|
+
| `socketUrl` | `string` | derived | Optional socket host (no `/api/v1`). Defaults to `apiUrl` without `/api/v1` |
|
|
1337
|
+
| `queryParams` | `Record<string, string \| number \| boolean>` | — | Shared query params for selected APIs (see `queryParamApis`) |
|
|
1338
|
+
| `queryParamApis` | `ChatApiKey[]` | `['get']` | Which APIs receive `queryParams`. Default = all GET endpoints |
|
|
1339
|
+
| `queryParamsByApi` | `ChatQueryParamsByApi` | — | Different query params per API key |
|
|
1340
|
+
| `viewportHeight` | `"full" \| "screen" \| string` | `"full"` | Chat root height. Use `"screen"` for full page, or `"calc(100dvh - 4rem)"` below fixed header |
|
|
1341
|
+
| `viewportClassName` | `string` | — | Extra classes on the built-in viewport wrapper |
|
|
1342
|
+
| `colorMode` | `"light" \| "dark"` | — | Controlled light/dark (use with your app theme) |
|
|
1343
|
+
| `defaultColorMode` | `"light" \| "dark"` | `"light"` | Initial mode when uncontrolled |
|
|
1344
|
+
| `onColorModeChange` | `(mode) => void` | — | Called when theme changes |
|
|
1345
|
+
| `showColorModeToggle` | `boolean` | `true` | Show built-in light/dark toggle |
|
|
1346
|
+
| `locale` | `string` | browser | BCP 47 locale, e.g. `"en-US"` |
|
|
1347
|
+
| `timeZone` | `string` | browser | IANA timezone, e.g. `"Asia/Kolkata"` |
|
|
1348
|
+
| `hour12` | `boolean` | locale | `true` = 12-hour clock |
|
|
1349
|
+
| `defaultLocale` | `string` | — | Initial locale when uncontrolled |
|
|
1350
|
+
| `defaultTimeZone` | `string` | — | Initial timezone when uncontrolled |
|
|
1351
|
+
| `defaultHour12` | `boolean` | — | Initial 12/24h when uncontrolled |
|
|
1352
|
+
| `onDateTimePreferencesChange` | `(prefs) => void` | — | User changed date/time settings |
|
|
1353
|
+
| `showDateTimeSettings` | `boolean` | `true` | Show in-panel date/time button |
|
|
1354
|
+
| `layout` | `ChatLayoutConfig` | all `true` | Toggle UI sections |
|
|
1355
|
+
| `components` | `ChatComponents` | — | Replace UI components (Case 1 & 2) |
|
|
1356
|
+
| `classNames` | `ChatClassNames` | — | Per-region CSS classes (Case 3) |
|
|
1357
|
+
| `features` | `ChatFeatures` | — | Phone/video call flags & callbacks |
|
|
1358
|
+
| `uiConfig` | `UIConfig` | — | Advanced: merged with `components` / `classNames` / `features` |
|
|
1359
|
+
| `error` | `string \| null` | — | Error message in alert bar |
|
|
1360
|
+
| `info` | `string \| null` | — | Info message in alert bar |
|
|
1361
|
+
| `onErrorDismiss` | `() => void` | — | Dismiss error |
|
|
1362
|
+
| `onInfoDismiss` | `() => void` | — | Dismiss info |
|
|
1363
|
+
|
|
1364
|
+
---
|
|
1365
|
+
|
|
1366
|
+
## Chat provider (composable mode)
|
|
1367
|
+
|
|
1368
|
+
Use `Chat` + `ChatLayout` when you need a custom page wrapper but not a full `ChatMain` replacement.
|
|
885
1369
|
|
|
886
1370
|
```tsx
|
|
887
1371
|
"use client";
|
|
888
1372
|
|
|
889
|
-
import { Chat, ChatLayout } from "@realtimexsco/live-chat";
|
|
1373
|
+
import { Chat, ChatLayout, type ChatComponents } from "@realtimexsco/live-chat";
|
|
890
1374
|
import { chatConfig } from "@/configs/chat.config";
|
|
891
|
-
|
|
1375
|
+
|
|
1376
|
+
const components: ChatComponents = {
|
|
1377
|
+
Avatar: MyAvatar,
|
|
1378
|
+
};
|
|
892
1379
|
|
|
893
1380
|
export default function ChatPage() {
|
|
894
1381
|
return (
|
|
895
|
-
<div className="
|
|
1382
|
+
<div className="h-dvh min-h-0 overflow-hidden">
|
|
896
1383
|
<Chat
|
|
897
1384
|
client={{ id: chatConfig.clientId }}
|
|
898
1385
|
accessToken={chatConfig.accessToken}
|
|
@@ -900,286 +1387,467 @@ export default function ChatPage() {
|
|
|
900
1387
|
apiUrl={chatConfig.apiUrl}
|
|
901
1388
|
socketUrl={chatConfig.socketUrl}
|
|
902
1389
|
themeColor={chatConfig.themeColor}
|
|
1390
|
+
uiConfig={{
|
|
1391
|
+
components,
|
|
1392
|
+
classNames: { sidebar: "bg-slate-900" },
|
|
1393
|
+
features: {
|
|
1394
|
+
onPhoneCall: (ctx) => console.log("call", ctx),
|
|
1395
|
+
},
|
|
1396
|
+
}}
|
|
903
1397
|
>
|
|
904
|
-
<ChatLayout
|
|
1398
|
+
<ChatLayout themeColor={chatConfig.themeColor} />
|
|
905
1399
|
</Chat>
|
|
906
1400
|
</div>
|
|
907
1401
|
);
|
|
908
1402
|
}
|
|
909
1403
|
```
|
|
910
1404
|
|
|
911
|
-
|
|
1405
|
+
### `Chat` provider props
|
|
1406
|
+
|
|
1407
|
+
| Prop | Type | Required | Description |
|
|
1408
|
+
|------|------|----------|-------------|
|
|
1409
|
+
| `client` | `{ id: string }` | Yes | Client id |
|
|
1410
|
+
| `accessToken` | `string` | Yes | JWT |
|
|
1411
|
+
| `loggedUserDetails` | `object` | Yes | User profile |
|
|
1412
|
+
| `apiUrl` | `string` | No | REST base URL |
|
|
1413
|
+
| `socketUrl` | `string` | No | Socket host |
|
|
1414
|
+
| `queryParams` | `ChatQueryParams` | No | Shared query params (default target: all GET APIs) |
|
|
1415
|
+
| `queryParamApis` | `ChatApiKey[]` | No | Which APIs receive `queryParams` (default `['get']`) |
|
|
1416
|
+
| `queryParamsByApi` | `ChatQueryParamsByApi` | No | Per-API query params |
|
|
1417
|
+
| `themeColor` | `string` | No | Accent color |
|
|
1418
|
+
| `uiConfig` | `UIConfig` | No | `components`, `classNames`, `features` |
|
|
1419
|
+
| `onMessageReceived` | `(msg) => void` | No | Background message callback |
|
|
1420
|
+
| `onSocketConnected` | `(id) => void` | No | Socket connected |
|
|
1421
|
+
| `onSocketError` | `(err) => void` | No | Socket error |
|
|
1422
|
+
| `children` | `ReactNode` | Yes | Usually `<ChatLayout />` |
|
|
912
1423
|
|
|
913
|
-
|
|
1424
|
+
---
|
|
914
1425
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1426
|
+
## Hooks & store
|
|
1427
|
+
|
|
1428
|
+
| Hook / export | Purpose |
|
|
1429
|
+
|---------------|---------|
|
|
1430
|
+
| `useChatController()` | State + handlers inside custom `ChatLayout` (Case 1) |
|
|
1431
|
+
| `useChatStore()` | Zustand store — messages, conversations, socket actions |
|
|
1432
|
+
| `useChatCustomization()` | Current `components` / `classNames` inside custom children |
|
|
1433
|
+
| `useChatFeatures()` | Phone/video flags and callbacks |
|
|
1434
|
+
| `useChatTheme()` | `themeColor`, `isDark`, CSS variables |
|
|
1435
|
+
| `useChatLocale()` | Format dates/times with current locale |
|
|
1436
|
+
| `useChatAlerts()` | Error/info alert state |
|
|
1437
|
+
| `useChatContext()` | Low-level chat provider context |
|
|
1438
|
+
|
|
1439
|
+
```tsx
|
|
1440
|
+
import { useChatStore } from "@realtimexsco/live-chat";
|
|
1441
|
+
|
|
1442
|
+
const conversations = useChatStore((s) => s.conversations);
|
|
1443
|
+
const emitDmSend = useChatStore((s) => s.emitDmSend);
|
|
918
1444
|
```
|
|
919
1445
|
|
|
920
|
-
|
|
1446
|
+
---
|
|
1447
|
+
|
|
1448
|
+
## React (Vite) — full setup
|
|
921
1449
|
|
|
922
|
-
|
|
1450
|
+
### 1. Create project
|
|
923
1451
|
|
|
924
1452
|
```bash
|
|
925
|
-
|
|
926
|
-
|
|
1453
|
+
pnpm create vite my-chat-app --template react-ts
|
|
1454
|
+
cd my-chat-app
|
|
927
1455
|
```
|
|
928
1456
|
|
|
929
|
-
###
|
|
1457
|
+
### 2. Install
|
|
1458
|
+
|
|
1459
|
+
```bash
|
|
1460
|
+
pnpm add @realtimexsco/live-chat react react-dom zustand tailwindcss
|
|
1461
|
+
pnpm add -D @tailwindcss/vite @vitejs/plugin-react
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
### 3. File structure
|
|
1465
|
+
|
|
1466
|
+
```
|
|
1467
|
+
src/
|
|
1468
|
+
├── main.tsx
|
|
1469
|
+
├── App.tsx
|
|
1470
|
+
├── index.css ← Tailwind + package CSS (see above)
|
|
1471
|
+
└── configs/
|
|
1472
|
+
└── chat.config.ts
|
|
1473
|
+
```
|
|
1474
|
+
|
|
1475
|
+
### 4. `src/main.tsx`
|
|
1476
|
+
|
|
1477
|
+
```tsx
|
|
1478
|
+
import { createRoot } from "react-dom/client";
|
|
1479
|
+
import "./index.css";
|
|
1480
|
+
import App from "./App";
|
|
1481
|
+
|
|
1482
|
+
createRoot(document.getElementById("root")!).render(<App />);
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
### 5. `src/App.tsx`
|
|
1486
|
+
|
|
1487
|
+
```tsx
|
|
1488
|
+
import ChatMain from "@realtimexsco/live-chat";
|
|
1489
|
+
import { chatConfig } from "./configs/chat.config";
|
|
1490
|
+
|
|
1491
|
+
export default function App() {
|
|
1492
|
+
return (
|
|
1493
|
+
<div className="h-dvh min-h-0 overflow-hidden">
|
|
1494
|
+
<ChatMain
|
|
1495
|
+
clientId={chatConfig.clientId}
|
|
1496
|
+
accessToken={chatConfig.accessToken}
|
|
1497
|
+
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
1498
|
+
apiUrl={chatConfig.apiUrl}
|
|
1499
|
+
socketUrl={chatConfig.socketUrl}
|
|
1500
|
+
themeColor={chatConfig.themeColor}
|
|
1501
|
+
/>
|
|
1502
|
+
</div>
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
### 6. Run
|
|
930
1508
|
|
|
931
1509
|
```bash
|
|
932
|
-
pnpm add @realtimexsco/live-chat@latest
|
|
933
|
-
rm -rf .next
|
|
934
1510
|
pnpm dev
|
|
935
1511
|
```
|
|
936
1512
|
|
|
937
1513
|
---
|
|
938
1514
|
|
|
939
|
-
##
|
|
940
|
-
|
|
941
|
-
```
|
|
942
|
-
┌─────────────────────────────────────────────────────────────────────┐
|
|
943
|
-
│ <Chat> — provider │
|
|
944
|
-
│ initializes auth (clientId + accessToken), REST API client, │
|
|
945
|
-
│ Socket.IO connection, and the Zustand store │
|
|
946
|
-
└───────────────┬───────────────────────────────────────────────────────┘
|
|
947
|
-
│ provides context + store
|
|
948
|
-
┌───────┴────────┐
|
|
949
|
-
▼ ▼
|
|
950
|
-
<ChannelList> <Channel> (wraps the active conversation)
|
|
951
|
-
lists convos 1. resolves conversationId (DM vs group)
|
|
952
|
-
click → sets 2. fetches message history via API
|
|
953
|
-
activeChannel 3. joins the socket room, marks messages read
|
|
954
|
-
│ │
|
|
955
|
-
▼ ▼
|
|
956
|
-
activeChannel <MessageList> renders the thread
|
|
957
|
-
in store <MessageInput> sends (optimistic UI)
|
|
958
|
-
│
|
|
959
|
-
▼
|
|
960
|
-
Socket events (new / edited / deleted message,
|
|
961
|
-
reactions, read receipts, forwards) update the
|
|
962
|
-
Zustand store → React re-renders automatically
|
|
963
|
-
```
|
|
964
|
-
|
|
965
|
-
**Lifecycle in short:**
|
|
966
|
-
|
|
967
|
-
1. `<Chat>` boots auth, the API client, the socket, and the store.
|
|
968
|
-
2. `<ChannelList>` shows conversations; selecting one calls `setActiveChannel`.
|
|
969
|
-
3. `<Channel>` reacts to the active channel — fetches messages, joins the socket
|
|
970
|
-
room, and marks them read.
|
|
971
|
-
4. `<MessageInput>` sends a message **optimistically** (it shows immediately),
|
|
972
|
-
then reconciles with the server acknowledgement.
|
|
973
|
-
5. Incoming socket events mutate the Zustand store, and the UI updates reactively.
|
|
1515
|
+
## Next.js — full setup
|
|
974
1516
|
|
|
975
|
-
|
|
1517
|
+
### 1. Create project
|
|
976
1518
|
|
|
977
|
-
|
|
1519
|
+
```bash
|
|
1520
|
+
pnpm create next-app@latest my-chat-app --typescript --tailwind --app --src-dir
|
|
1521
|
+
cd my-chat-app
|
|
1522
|
+
```
|
|
978
1523
|
|
|
979
|
-
|
|
980
|
-
| ---- | ---- | -------- | ----------- |
|
|
981
|
-
| `client` | `{ id: string }` | Yes | RealtimeX client / app id |
|
|
982
|
-
| `accessToken` | `string` | Yes | JWT access token |
|
|
983
|
-
| `loggedUserDetails` | `{ _id, name, email, ... }` | Yes | Current user profile |
|
|
984
|
-
| `apiUrl` | `string` | No | REST API base (default: `http://localhost:5000/api/v1`) |
|
|
985
|
-
| `socketUrl` | `string` | No | Socket server URL (default: `http://localhost:5000`) |
|
|
986
|
-
| `themeColor` | `string` | No | Primary chat accent color (default: `#7494ec`) |
|
|
987
|
-
| `theme` | `"light" \| "dark"` | No | Color scheme |
|
|
988
|
-
| `uiConfig` | `UIConfig` | No | Sidebar / header UI toggles |
|
|
989
|
-
| `onMessageReceived` | `(msg) => void` | No | Fired when a message arrives in a non-active chat |
|
|
990
|
-
| `onSocketConnected` | `(socketId) => void` | No | Socket connected callback |
|
|
991
|
-
| `onSocketError` | `(error) => void` | No | Socket error callback |
|
|
992
|
-
| `children` | `ReactNode` | Yes | Your chat layout |
|
|
993
|
-
|
|
994
|
-
### `UIConfig`
|
|
1524
|
+
### 2. Install
|
|
995
1525
|
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1526
|
+
```bash
|
|
1527
|
+
pnpm add @realtimexsco/live-chat zustand
|
|
1528
|
+
pnpm add -D @tailwindcss/postcss
|
|
1529
|
+
```
|
|
1530
|
+
|
|
1531
|
+
### 3. `src/app/chat/page.tsx`
|
|
1532
|
+
|
|
1533
|
+
```tsx
|
|
1534
|
+
"use client";
|
|
1535
|
+
|
|
1536
|
+
import ChatMain from "@realtimexsco/live-chat";
|
|
1537
|
+
import { chatConfig } from "@/configs/chat.config";
|
|
1538
|
+
|
|
1539
|
+
export default function ChatPage() {
|
|
1540
|
+
return (
|
|
1541
|
+
<div className="h-dvh min-h-0 overflow-hidden">
|
|
1542
|
+
<ChatMain
|
|
1543
|
+
clientId={chatConfig.clientId}
|
|
1544
|
+
accessToken={chatConfig.accessToken}
|
|
1545
|
+
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
1546
|
+
apiUrl={chatConfig.apiUrl}
|
|
1547
|
+
socketUrl={chatConfig.socketUrl}
|
|
1548
|
+
themeColor={chatConfig.themeColor}
|
|
1549
|
+
/>
|
|
1550
|
+
</div>
|
|
1551
|
+
);
|
|
1002
1552
|
}
|
|
1003
1553
|
```
|
|
1004
1554
|
|
|
1555
|
+
### 4. Run
|
|
1556
|
+
|
|
1557
|
+
```bash
|
|
1558
|
+
pnpm dev
|
|
1559
|
+
```
|
|
1560
|
+
|
|
1561
|
+
Open `http://localhost:3000/chat`
|
|
1562
|
+
|
|
1005
1563
|
---
|
|
1006
1564
|
|
|
1007
|
-
##
|
|
1565
|
+
## Environment variables
|
|
1566
|
+
|
|
1567
|
+
Optional — use in `chat.config.ts`:
|
|
1008
1568
|
|
|
1009
|
-
|
|
1569
|
+
```env
|
|
1570
|
+
# .env.local (Next.js) or .env (Vite with VITE_ prefix)
|
|
1571
|
+
NEXT_PUBLIC_CHAT_CLIENT_ID=your-client-id
|
|
1572
|
+
NEXT_PUBLIC_API_V1_BASE_URL=https://realtimex.softwareco.com/api/v1
|
|
1573
|
+
NEXT_PUBLIC_SOCKET_URL=https://realtimex.softwareco.com
|
|
1574
|
+
```
|
|
1010
1575
|
|
|
1011
1576
|
```ts
|
|
1012
|
-
export const
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1577
|
+
export const chatConfig = {
|
|
1578
|
+
clientId: process.env.NEXT_PUBLIC_CHAT_CLIENT_ID ?? "",
|
|
1579
|
+
accessToken: "YOUR_JWT_ACCESS_TOKEN",
|
|
1580
|
+
apiUrl: process.env.NEXT_PUBLIC_API_V1_BASE_URL ?? "",
|
|
1581
|
+
socketUrl:
|
|
1582
|
+
process.env.NEXT_PUBLIC_SOCKET_URL ??
|
|
1583
|
+
(process.env.NEXT_PUBLIC_API_V1_BASE_URL ?? "").replace(/\/api\/v1\/?$/, ""),
|
|
1584
|
+
// ...
|
|
1020
1585
|
};
|
|
1021
1586
|
```
|
|
1022
1587
|
|
|
1023
|
-
|
|
1588
|
+
Vite uses `import.meta.env.VITE_*` instead of `NEXT_PUBLIC_*`.
|
|
1589
|
+
|
|
1590
|
+
---
|
|
1591
|
+
|
|
1592
|
+
## Dev proxy (CORS)
|
|
1593
|
+
|
|
1594
|
+
If the API blocks browser requests during local dev, proxy through your dev server.
|
|
1595
|
+
|
|
1596
|
+
**Vite `vite.config.ts`:**
|
|
1024
1597
|
|
|
1025
1598
|
```ts
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1599
|
+
server: {
|
|
1600
|
+
proxy: {
|
|
1601
|
+
"/chat-api": {
|
|
1602
|
+
target: "https://YOUR_API_HOST",
|
|
1603
|
+
changeOrigin: true,
|
|
1604
|
+
rewrite: (path) => path.replace(/^\/chat-api/, "/api/v1"),
|
|
1605
|
+
},
|
|
1606
|
+
},
|
|
1607
|
+
},
|
|
1029
1608
|
```
|
|
1030
1609
|
|
|
1031
|
-
|
|
1610
|
+
Then set `apiUrl: "/chat-api"` in `chat.config.ts` for local development.
|
|
1611
|
+
|
|
1612
|
+
---
|
|
1613
|
+
|
|
1614
|
+
## Architecture
|
|
1032
1615
|
|
|
1033
|
-
```tsx
|
|
1034
|
-
<ChatLayout layout={{ sidebar: true, input: false }} />
|
|
1035
1616
|
```
|
|
1617
|
+
ChatMain
|
|
1618
|
+
├── ChatThemeProvider (themeColor, light/dark)
|
|
1619
|
+
├── ChatLocaleProvider (locale, timezone, hour12)
|
|
1620
|
+
├── Chat
|
|
1621
|
+
│ ├── Auth + REST API + Socket.IO + Zustand store
|
|
1622
|
+
│ └── ChatCustomizationProvider (components, classNames, features)
|
|
1623
|
+
│ └── ChatLayout (resolveComponent → DefaultChatLayout or custom)
|
|
1624
|
+
│ ├── Sidebar: LoggedUserDetails + ChannelList
|
|
1625
|
+
│ └── Main: ChannelHeader + MessageList + MessageInput
|
|
1626
|
+
│ └── ChannelHeaderView
|
|
1627
|
+
│ ├── resolveComponent → drawers (Members, Details, …)
|
|
1628
|
+
│ └── resolveComponent → modals (Clear, Delete, Block)
|
|
1629
|
+
└── ChatAlertsProvider (error / info banner)
|
|
1630
|
+
```
|
|
1631
|
+
|
|
1632
|
+
**Customization flow:**
|
|
1633
|
+
|
|
1634
|
+
1. You pass `components` / `classNames` / `features` on `ChatMain`.
|
|
1635
|
+
2. `ChatCustomizationProvider` merges your config (unset slots stay empty).
|
|
1636
|
+
3. Each render site calls `resolveComponent(slot, PackageDefault)` — custom wins, else default.
|
|
1637
|
+
4. No `components` prop → every slot uses package defaults (full default chat UI).
|
|
1638
|
+
|
|
1639
|
+
**Runtime flow:**
|
|
1640
|
+
|
|
1641
|
+
1. `ChatMain` runs client-user login, sets JWT, connects socket.
|
|
1642
|
+
2. User picks a conversation → messages fetch + socket room join.
|
|
1643
|
+
3. Send is optimistic — message appears immediately, reconciles on server ack.
|
|
1644
|
+
4. Socket events update Zustand → UI re-renders automatically.
|
|
1036
1645
|
|
|
1037
1646
|
---
|
|
1038
1647
|
|
|
1039
|
-
##
|
|
1040
|
-
|
|
1041
|
-
| Export | Description |
|
|
1042
|
-
| ------ | ----------- |
|
|
1043
|
-
| `ChatMain` (default) | All-in-one chat UI with optional `layout` prop |
|
|
1044
|
-
| `Chat` | Provider — initializes auth, API, socket, store |
|
|
1045
|
-
| `ChatLayout` | Composable shell — toggle sections via `layout` prop |
|
|
1046
|
-
| `Channel` | Active conversation lifecycle (fetch, join, mark-read) |
|
|
1047
|
-
| `ChannelList` | Conversation sidebar list |
|
|
1048
|
-
| `ChannelHeader` | Chat header with search, details, actions |
|
|
1049
|
-
| `MessageList` | Message thread (`ActiveChannelMessages`) |
|
|
1050
|
-
| `MessageInput` | Compose box (`ChannelMessageBox`) |
|
|
1051
|
-
| `LoggedUserDetails` | User profile + new chat / create group |
|
|
1052
|
-
| `ForwardModal` | Forward message modal |
|
|
1053
|
-
| `useChatContext` | `{ activeChannel, setActiveChannel, themeColor, uiConfig }` |
|
|
1054
|
-
| `useChatStore` | Full Zustand store (messages, socket actions, status, error) |
|
|
1055
|
-
| `useChatController` | State + handlers used internally by `ChatLayout` |
|
|
1056
|
-
| `ChatLayoutConfig` | Type for layout section toggles |
|
|
1057
|
-
| `defaultChatLayoutConfig` | Default layout (all sections enabled) |
|
|
1058
|
-
| `mergeChatLayoutConfig` | Merge partial layout with defaults |
|
|
1059
|
-
| `socketService` | Low-level socket service |
|
|
1060
|
-
| `getSocketConfig` | Socket configuration helper |
|
|
1061
|
-
| `setChatClientId`, `setChatAccessToken`, `setChatBaseURL` | Manual API config |
|
|
1062
|
-
| `IChannel`, `IMessage`, `IAttachment` | Core TypeScript types |
|
|
1063
|
-
| `ISocketConfig` | Socket config type |
|
|
1064
|
-
|
|
1065
|
-
### Import example
|
|
1648
|
+
## All exports
|
|
1066
1649
|
|
|
1067
1650
|
```ts
|
|
1068
|
-
import {
|
|
1651
|
+
import ChatMain, {
|
|
1652
|
+
// ── Providers & layout ──
|
|
1069
1653
|
Chat,
|
|
1070
|
-
ChatMain,
|
|
1071
1654
|
ChatLayout,
|
|
1655
|
+
DefaultChatLayout,
|
|
1072
1656
|
Channel,
|
|
1657
|
+
ChatThemeProvider,
|
|
1658
|
+
ChatLocaleProvider,
|
|
1659
|
+
ChatAlertsProvider,
|
|
1660
|
+
ChatCustomizationProvider,
|
|
1661
|
+
|
|
1662
|
+
// ── UI components (standalone or customization reference) ──
|
|
1073
1663
|
ChannelList,
|
|
1074
1664
|
ChannelHeader,
|
|
1075
1665
|
MessageList,
|
|
1076
1666
|
MessageInput,
|
|
1077
1667
|
LoggedUserDetails,
|
|
1078
1668
|
ForwardModal,
|
|
1669
|
+
MessageItem,
|
|
1670
|
+
ChatAvatar,
|
|
1671
|
+
DefaultChatAvatar,
|
|
1672
|
+
ChannelListItem,
|
|
1673
|
+
DefaultChannelListItem,
|
|
1674
|
+
HeaderCallActions,
|
|
1675
|
+
packageDefaultComponents,
|
|
1676
|
+
|
|
1677
|
+
// ── Hooks ──
|
|
1079
1678
|
useChatStore,
|
|
1080
1679
|
useChatController,
|
|
1680
|
+
useChatContext,
|
|
1681
|
+
useChatCustomization,
|
|
1682
|
+
useChatCustomizationOptional,
|
|
1683
|
+
useChatFeatures,
|
|
1684
|
+
useChatFeaturesOptional,
|
|
1685
|
+
useChatTheme,
|
|
1686
|
+
useChatLocale,
|
|
1687
|
+
useChatAlerts,
|
|
1688
|
+
|
|
1689
|
+
// ── Layout helpers ──
|
|
1690
|
+
mergeChatLayoutConfig,
|
|
1691
|
+
defaultChatLayoutConfig,
|
|
1692
|
+
mergeChatCustomization,
|
|
1693
|
+
defaultChatClassNames,
|
|
1694
|
+
defaultChatComponents,
|
|
1695
|
+
defaultChatFeatures,
|
|
1696
|
+
|
|
1697
|
+
// ── API / socket helpers ──
|
|
1698
|
+
setChatClientId,
|
|
1699
|
+
setChatAccessToken,
|
|
1700
|
+
setChatBaseURL,
|
|
1701
|
+
setChatSocketUrl,
|
|
1702
|
+
setChatQueryParams,
|
|
1703
|
+
getChatQueryParams,
|
|
1704
|
+
setChatQueryParamApis,
|
|
1705
|
+
getChatQueryParamApis,
|
|
1706
|
+
setChatQueryParamsByApi,
|
|
1707
|
+
getChatQueryParamsByApi,
|
|
1708
|
+
CHAT_API_KEYS,
|
|
1709
|
+
CHAT_GET_API_KEYS,
|
|
1710
|
+
resolveChatApiKey,
|
|
1711
|
+
socketService,
|
|
1081
1712
|
getSocketConfig,
|
|
1713
|
+
showNotification,
|
|
1714
|
+
downloadFile,
|
|
1715
|
+
|
|
1716
|
+
// ── Types ──
|
|
1717
|
+
type ChatApiKey,
|
|
1718
|
+
type ChatGetApiKey,
|
|
1719
|
+
type ChatQueryParams,
|
|
1720
|
+
type ChatQueryParamsByApi,
|
|
1721
|
+
type ChatComponents,
|
|
1722
|
+
type ChatClassNames,
|
|
1723
|
+
type ChatFeatures,
|
|
1724
|
+
type ChatCallContext,
|
|
1082
1725
|
type ChatLayoutConfig,
|
|
1726
|
+
type ChatColorMode,
|
|
1727
|
+
type ChatCustomizationConfig,
|
|
1728
|
+
type UIConfig,
|
|
1729
|
+
type IChannel,
|
|
1730
|
+
type IMessage,
|
|
1731
|
+
type IAttachment,
|
|
1732
|
+
type AvatarSlotProps,
|
|
1733
|
+
type MessageItemSlotProps,
|
|
1734
|
+
type ChannelHeaderSlotProps,
|
|
1735
|
+
type ChannelListItemSlotProps,
|
|
1736
|
+
type ChatLayoutSlotProps,
|
|
1737
|
+
type HeaderCallActionsSlotProps,
|
|
1738
|
+
type PhoneCallButtonSlotProps,
|
|
1739
|
+
type VideoCallButtonSlotProps,
|
|
1740
|
+
type ActiveChannelMessagesSlotProps,
|
|
1741
|
+
type ChannelMessageBoxSlotProps,
|
|
1742
|
+
type ForwardModalSlotProps,
|
|
1743
|
+
type MembersDrawerSlotProps,
|
|
1744
|
+
// … all *SlotProps types are exported
|
|
1083
1745
|
} from "@realtimexsco/live-chat";
|
|
1084
1746
|
```
|
|
1085
1747
|
|
|
1086
|
-
|
|
1748
|
+
**CSS import (not from JS):**
|
|
1087
1749
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
Set a single accent color via the `themeColor` prop; it drives the
|
|
1091
|
-
`--chat-theme*` CSS variables at runtime. Override any of the variables in your
|
|
1092
|
-
global CSS to customize further, and pass `theme="dark"` for dark mode.
|
|
1093
|
-
|
|
1094
|
-
```tsx
|
|
1095
|
-
<Chat themeColor="#7c3aed" theme="dark">{/* … */}</Chat>
|
|
1750
|
+
```css
|
|
1751
|
+
@import "@realtimexsco/live-chat/styles.css";
|
|
1096
1752
|
```
|
|
1097
1753
|
|
|
1098
1754
|
---
|
|
1099
1755
|
|
|
1100
1756
|
## Troubleshooting
|
|
1101
1757
|
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
|
1105
|
-
|
|
|
1106
|
-
| Wrong `@source`
|
|
1107
|
-
|
|
|
1108
|
-
|
|
|
1109
|
-
|
|
|
1110
|
-
|
|
|
1111
|
-
|
|
|
1112
|
-
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1758
|
+
| Problem | Cause | Fix |
|
|
1759
|
+
|---------|-------|-----|
|
|
1760
|
+
| Unstyled / broken UI | Missing Tailwind `@source` | Add `@source` to package `dist/index.mjs` and `dist/index.cjs` |
|
|
1761
|
+
| No styles at all | Missing `styles.css` import | `@import "@realtimexsco/live-chat/styles.css"` |
|
|
1762
|
+
| Wrong `@source` depth | Next.js vs Vite path | Use `../../node_modules` in `src/app/globals.css` |
|
|
1763
|
+
| TypeScript: no declaration file | Published tarball missing `dist/index.d.ts` | Reinstall latest version; verify `dist/index.d.ts` exists |
|
|
1764
|
+
| Next.js build error | Package not transpiled | `transpilePackages: ["@realtimexsco/live-chat"]` |
|
|
1765
|
+
| `use client` error | Server Component import | Add `"use client"` to chat page |
|
|
1766
|
+
| Whole page scrolls, not messages | Missing height chain | Set `viewportHeight="screen"` or `viewportHeight="calc(100dvh - 4rem)"` on `<ChatMain />` |
|
|
1767
|
+
| Messages don't load | Wrong credentials / CORS | Check `apiUrl`, JWT, proxy in dev |
|
|
1768
|
+
| Socket won't connect | Wrong `socketUrl` | No `/api/v1` on socket URL |
|
|
1769
|
+
| Duplicate React hooks error | Duplicate React copies | `dedupe: ["react", "react-dom"]` in Vite |
|
|
1770
|
+
| Sent message on **left** and **right** | `_id` mismatch or duplicate echo | Ensure `loggedUserDetails._id` matches JWT `_id` |
|
|
1771
|
+
| Custom layout has no data | Missing hook | Call `useChatController()` inside custom `ChatLayout` |
|
|
1772
|
+
| Custom `MembersDrawer` still shows package UI | Old package version (< 1.1.55) | Upgrade to **1.1.55+** — drawer slots now use `resolveComponent` |
|
|
1773
|
+
| Custom drawer works but other slots are default | Expected behavior | Only pass slots you want to override in `components` |
|
|
1774
|
+
| Passed `components` but slot unchanged | Slot not wired or parent replaced | See [Which slots are wired](#which-slots-are-wired); use `ChannelHeader` to replace all header drawers |
|
|
1775
|
+
| Custom `HeaderCallActions` crashes | Recursive import | Don't import package `HeaderCallActions` inside your slot |
|
|
1776
|
+
| Vite stale UI after upgrade | Cached prebundle | `rm -rf node_modules/.vite && pnpm dev` |
|
|
1777
|
+
| Rate limit banner | API returned 429 | Wait for cooldown; reduce rapid API calls in dev |
|
|
1120
1778
|
|
|
1121
|
-
|
|
1122
|
-
messages by `conversationId` internally — don't key DM messages by user id.
|
|
1123
|
-
|
|
1124
|
-
### Group events firing in DM (or vice versa)
|
|
1779
|
+
---
|
|
1125
1780
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1781
|
+
## Setup checklist
|
|
1782
|
+
|
|
1783
|
+
Copy this list when integrating into a new project:
|
|
1784
|
+
|
|
1785
|
+
- [ ] Node.js 18+
|
|
1786
|
+
- [ ] `pnpm add @realtimexsco/live-chat react react-dom zustand tailwindcss`
|
|
1787
|
+
- [ ] Tailwind v4 plugin installed (`@tailwindcss/vite` or `@tailwindcss/postcss`)
|
|
1788
|
+
- [ ] `index.css` / `globals.css` imports `tailwindcss` + `@realtimexsco/live-chat/styles.css`
|
|
1789
|
+
- [ ] `@source` points to `dist/index.mjs` and `dist/index.cjs` (correct depth for your CSS path)
|
|
1790
|
+
- [ ] `:root` has `--chat-theme` and `--chat-theme-5` … `--chat-theme-60`
|
|
1791
|
+
- [ ] `@theme inline` maps `--color-chat-theme*` variables
|
|
1792
|
+
- [ ] `chat.config.ts` with real `clientId`, `accessToken`, `apiUrl`, `socketUrl`
|
|
1793
|
+
- [ ] `loggedUserDetails._id` matches JWT `_id`
|
|
1794
|
+
- [ ] Chat page wrapped in `h-dvh min-h-0 overflow-hidden`
|
|
1795
|
+
- [ ] Next.js: `"use client"` on chat page + `transpilePackages`
|
|
1796
|
+
- [ ] Vite: `optimizeDeps.exclude` includes `@realtimexsco/live-chat`
|
|
1797
|
+
- [ ] Optional: `components` for custom UI (Case 1 / 2)
|
|
1798
|
+
- [ ] Optional: `classNames` for theme tweaks (Case 3)
|
|
1799
|
+
- [ ] Optional: `features` for phone/video callbacks
|
|
1800
|
+
- [ ] Optional: `layout` to hide sections
|
|
1801
|
+
- [ ] Optional: `colorMode` synced with your app theme
|
|
1802
|
+
- [ ] Verify: `node_modules/@realtimexsco/live-chat/dist/index.d.ts` exists
|
|
1803
|
+
- [ ] Use package **≥ 1.1.56** for drawer customization + default fallback fixes
|
|
1128
1804
|
|
|
1129
|
-
|
|
1805
|
+
---
|
|
1130
1806
|
|
|
1131
|
-
|
|
1807
|
+
## Package development (maintainers)
|
|
1132
1808
|
|
|
1133
|
-
|
|
1809
|
+
This package is built from the **live-chat-frontend** reference app. Source lives in `src/`; published output is `dist/`.
|
|
1134
1810
|
|
|
1135
|
-
|
|
1811
|
+
### Sync from frontend
|
|
1136
1812
|
|
|
1137
|
-
```
|
|
1138
|
-
|
|
1813
|
+
```bash
|
|
1814
|
+
cd chat-ui-package
|
|
1815
|
+
pnpm sync # copies app/chat → src/chat, patches API client & types, fixes imports
|
|
1816
|
+
pnpm build # tsup → dist/
|
|
1139
1817
|
```
|
|
1140
1818
|
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
- `apiUrl` must end with `/api/v1`
|
|
1144
|
-
- `socketUrl` = base host only (no `/api/v1`)
|
|
1145
|
-
- Use a dev proxy (`/chat-api`) or Next.js rewrite if CORS blocks requests
|
|
1819
|
+
`scripts/sync-from-frontend.mjs` copies UI from the frontend repo. Protected files (not overwritten):
|
|
1146
1820
|
|
|
1147
|
-
|
|
1821
|
+
- `src/index.ts`, `ChatMain.tsx`, `ChatContext.tsx`, `DefaultChatLayout.tsx`
|
|
1822
|
+
- `src/chat/channel-header/ChannelHeaderView.tsx` (customization wiring)
|
|
1823
|
+
- Store/context entrypoints
|
|
1148
1824
|
|
|
1149
|
-
|
|
1150
|
-
socket-dependent UI in server components.
|
|
1151
|
-
|
|
1152
|
-
---
|
|
1825
|
+
After UI changes in **live-chat-frontend**, run `pnpm sync` then `pnpm build`, bump version, publish.
|
|
1153
1826
|
|
|
1154
|
-
|
|
1827
|
+
### Local install in consumer app (e.g. Elstar)
|
|
1155
1828
|
|
|
1156
|
-
|
|
1829
|
+
```bash
|
|
1830
|
+
cd chat-ui-package && pnpm pack --pack-destination .
|
|
1831
|
+
```
|
|
1157
1832
|
|
|
1158
|
-
|
|
1159
|
-
- [ ] Peer deps: `react`, `react-dom`, `zustand`, `tailwindcss` v4
|
|
1160
|
-
- [ ] Global CSS imports `tailwindcss` + `@realtimexsco/live-chat/styles.css`
|
|
1161
|
-
- [ ] `@source` paths point to package `dist/index.mjs` and `dist/index.cjs`
|
|
1162
|
-
- [ ] `--chat-theme*` CSS variables defined in `:root`
|
|
1163
|
-
- [ ] `src/configs/chat.config.ts` (or `.js`) — your credentials
|
|
1164
|
-
- [ ] Chat wrapped in `h-dvh min-h-0 overflow-hidden` container
|
|
1833
|
+
In consumer `package.json`:
|
|
1165
1834
|
|
|
1166
|
-
|
|
1835
|
+
```json
|
|
1836
|
+
"@realtimexsco/live-chat": "file:../path/to/realtimexsco-live-chat-1.1.0.tgz"
|
|
1837
|
+
```
|
|
1167
1838
|
|
|
1168
|
-
|
|
1169
|
-
- [ ] `jsconfig.json` / `tsconfig.json` — `"@/*": ["src/*"]`
|
|
1170
|
-
- [ ] `src/main.jsx` imports `index.css`
|
|
1171
|
-
- [ ] Clear `node_modules/.vite` after every package upgrade
|
|
1839
|
+
Then `pnpm install` in the consumer app.
|
|
1172
1840
|
|
|
1173
|
-
###
|
|
1841
|
+
### Version history (customization)
|
|
1174
1842
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1843
|
+
| Version | Change |
|
|
1844
|
+
|---------|--------|
|
|
1845
|
+
| **1.1.56** | Hardened default fallback (`resolveComponent` null check); drawer slots use optional customization hook |
|
|
1846
|
+
| **1.1.55** | Drawer slots (`MembersDrawer`, `ChannelDetailsDrawer`, etc.) wired through `resolveComponent` |
|
|
1847
|
+
| **1.1.53** | UI polish synced from frontend (bubble curves, drawer headers, destructive menu icons) |
|
|
1180
1848
|
|
|
1181
1849
|
---
|
|
1182
1850
|
|
|
1183
1851
|
## License
|
|
1184
1852
|
|
|
1185
|
-
[MIT](LICENSE)
|
|
1853
|
+
[MIT](LICENSE)
|