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