@realtimexsco/live-chat 1.0.0 → 1.1.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/README.md +935 -148
- package/dist/index.cjs +493 -220
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +72 -3
- package/dist/index.d.ts +72 -3
- package/dist/index.mjs +487 -222
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# @realtimexsco/live-chat
|
|
2
2
|
|
|
3
|
-
> Drop-in real-time chat UI for React — direct messages, group chats, reactions, replies, forwarding, pinned/starred messages, and more. Built on Socket.IO, Zustand, Tailwind CSS v4, and Radix UI.
|
|
3
|
+
> Drop-in real-time chat UI for React and Next.js — direct messages, group chats, reactions, replies, forwarding, pinned/starred messages, and more. Built on Socket.IO, Zustand, Tailwind CSS v4, and Radix UI.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@realtimexsco/live-chat)
|
|
6
6
|
[](https://www.npmjs.com/package/@realtimexsco/live-chat)
|
|
7
7
|
[](https://www.npmjs.com/package/@realtimexsco/live-chat)
|
|
8
8
|
|
|
9
|
-
Use the all-in-one `<ChatMain />` for a complete chat app in minutes,
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Use the all-in-one `<ChatMain />` for a complete chat app in minutes, drop in
|
|
10
|
+
`<ChatLayout />` for a composable shell with section toggles, or build your own
|
|
11
|
+
layout from the individual pieces (`<Chat>`, `<ChannelList>`, `<MessageList>`, …).
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -18,13 +18,19 @@ control over layout and styling.
|
|
|
18
18
|
- [Requirements](#requirements)
|
|
19
19
|
- [Installation](#installation)
|
|
20
20
|
- [Tailwind CSS v4 setup (required)](#tailwind-css-v4-setup-required)
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
21
|
+
- [Configuration](#configuration)
|
|
22
|
+
- [Quick start — full UI (`ChatMain`)](#quick-start--full-ui-chatmain)
|
|
23
|
+
- [Composable shell (`ChatLayout`)](#composable-shell-chatlayout)
|
|
24
|
+
- [Custom layout (advanced)](#custom-layout-advanced)
|
|
25
|
+
- [React (Vite) — complete setup](#react-vite--complete-setup)
|
|
26
|
+
- [Next.js (App Router) — complete setup](#nextjs-app-router--complete-setup)
|
|
23
27
|
- [How it works](#how-it-works)
|
|
24
28
|
- [`Chat` provider props](#chat-provider-props)
|
|
29
|
+
- [Layout customization](#layout-customization)
|
|
25
30
|
- [Exported components & hooks](#exported-components--hooks)
|
|
26
31
|
- [Theming](#theming)
|
|
27
32
|
- [Troubleshooting](#troubleshooting)
|
|
33
|
+
- [Checklist](#checklist)
|
|
28
34
|
- [License](#license)
|
|
29
35
|
|
|
30
36
|
---
|
|
@@ -36,29 +42,32 @@ control over layout and styling.
|
|
|
36
42
|
- 😀 Reactions, replies, forwarding, pinned, starred & saved messages
|
|
37
43
|
- 🔎 Conversation + message search, attachments, member/admin management
|
|
38
44
|
- 🎨 **Themeable** via a single `themeColor` prop and CSS variables (light/dark)
|
|
39
|
-
- 🧩 **
|
|
45
|
+
- 🧩 **Three integration modes** — all-in-one, composable shell, or fully custom
|
|
40
46
|
- 🦾 Ships TypeScript types and ESM + CJS builds
|
|
47
|
+
- 📦 Published on the **public npm registry** — no private registry or auth token needed
|
|
41
48
|
|
|
42
49
|
---
|
|
43
50
|
|
|
44
51
|
## Requirements
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
|
49
|
-
|
|
|
50
|
-
| `react`
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
|
|
|
53
|
+
| Requirement | Version |
|
|
54
|
+
| ----------- | ------- |
|
|
55
|
+
| Node.js | 18+ |
|
|
56
|
+
| React | ≥ 17 (19 recommended) |
|
|
57
|
+
| `react-dom` | ≥ 17 |
|
|
58
|
+
| `zustand` | ≥ 4 |
|
|
59
|
+
| `tailwindcss` | **v4** (required) |
|
|
60
|
+
| Next.js | ≥ 13 App Router (optional — for Next.js projects) |
|
|
54
61
|
|
|
55
62
|
You also need, at runtime:
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
| Field | Description |
|
|
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` |
|
|
62
71
|
|
|
63
72
|
---
|
|
64
73
|
|
|
@@ -70,200 +79,860 @@ npm install @realtimexsco/live-chat
|
|
|
70
79
|
npm install react react-dom zustand tailwindcss @tailwindcss/vite
|
|
71
80
|
```
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
Or with pnpm:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm add @realtimexsco/live-chat react react-dom zustand tailwindcss @tailwindcss/vite
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> Published on the public npm registry — **no `.npmrc` or auth token needed**.
|
|
89
|
+
|
|
90
|
+
Verify the installed version:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cat node_modules/@realtimexsco/live-chat/package.json | grep version
|
|
94
|
+
```
|
|
74
95
|
|
|
75
96
|
---
|
|
76
97
|
|
|
77
98
|
## Tailwind CSS v4 setup (required)
|
|
78
99
|
|
|
79
|
-
The package ships Tailwind utility classes, so your app must
|
|
80
|
-
package stylesheet and (2) scan the built package output so Tailwind generates
|
|
81
|
-
the classes it uses.
|
|
100
|
+
The package ships Tailwind utility classes, so your app must:
|
|
82
101
|
|
|
83
|
-
|
|
102
|
+
1. Import the package stylesheet
|
|
103
|
+
2. Scan the built package output so Tailwind generates the classes it uses
|
|
104
|
+
3. Define `--chat-theme*` CSS variables
|
|
105
|
+
|
|
106
|
+
### Vite — `src/index.css`
|
|
107
|
+
|
|
108
|
+
> From `src/index.css`, `@source` uses `../node_modules`.
|
|
84
109
|
|
|
85
110
|
```css
|
|
86
111
|
@import "tailwindcss";
|
|
87
112
|
@import "@realtimexsco/live-chat/styles.css";
|
|
88
113
|
|
|
89
|
-
/* Scan the package dist so Tailwind emits the classes it uses */
|
|
90
114
|
@source "../node_modules/@realtimexsco/live-chat/dist/index.mjs";
|
|
91
115
|
@source "../node_modules/@realtimexsco/live-chat/dist/index.cjs";
|
|
116
|
+
@source "../**/*.{js,jsx,ts,tsx}";
|
|
117
|
+
|
|
118
|
+
.chat-page h1,
|
|
119
|
+
.chat-container-root h1 {
|
|
120
|
+
@apply text-[15px] font-semibold leading-snug text-gray-900;
|
|
121
|
+
}
|
|
92
122
|
|
|
93
123
|
:root {
|
|
94
124
|
--radius: 0.625rem;
|
|
125
|
+
--background: oklch(1 0 0);
|
|
126
|
+
--foreground: oklch(0.145 0 0);
|
|
127
|
+
--primary: oklch(0.205 0 0);
|
|
128
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
129
|
+
--muted: oklch(0.97 0 0);
|
|
130
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
131
|
+
--border: oklch(0.922 0 0);
|
|
132
|
+
--ring: oklch(0.708 0 0);
|
|
133
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
134
|
+
--card: oklch(1 0 0);
|
|
135
|
+
--card-foreground: oklch(0.145 0 0);
|
|
136
|
+
--popover: oklch(1 0 0);
|
|
137
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
138
|
+
--accent: oklch(0.97 0 0);
|
|
139
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
140
|
+
--secondary: oklch(0.97 0 0);
|
|
141
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
142
|
+
--input: oklch(0.922 0 0);
|
|
95
143
|
|
|
96
144
|
/* Chat theme — overridden at runtime by the Chat `themeColor` prop */
|
|
97
|
-
--chat-theme: #
|
|
98
|
-
--chat-theme-5: #
|
|
99
|
-
--chat-theme-10: #
|
|
100
|
-
--chat-theme-20: #
|
|
101
|
-
--chat-theme-40: #
|
|
102
|
-
--chat-theme-60: #
|
|
145
|
+
--chat-theme: #6366f1;
|
|
146
|
+
--chat-theme-5: #6366f10D;
|
|
147
|
+
--chat-theme-10: #6366f11A;
|
|
148
|
+
--chat-theme-20: #6366f133;
|
|
149
|
+
--chat-theme-40: #6366f166;
|
|
150
|
+
--chat-theme-60: #6366f199;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@theme inline {
|
|
154
|
+
--color-background: var(--background);
|
|
155
|
+
--color-foreground: var(--foreground);
|
|
156
|
+
--color-primary: var(--primary);
|
|
157
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
158
|
+
--color-muted: var(--muted);
|
|
159
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
160
|
+
--color-border: var(--border);
|
|
161
|
+
--color-ring: var(--ring);
|
|
162
|
+
--color-destructive: var(--destructive);
|
|
163
|
+
--color-card: var(--card);
|
|
164
|
+
--color-card-foreground: var(--card-foreground);
|
|
165
|
+
--color-popover: var(--popover);
|
|
166
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
167
|
+
--color-accent: var(--accent);
|
|
168
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
169
|
+
--color-secondary: var(--secondary);
|
|
170
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
171
|
+
--color-input: var(--input);
|
|
172
|
+
--color-chat-theme: var(--chat-theme);
|
|
173
|
+
--color-chat-theme-5: var(--chat-theme-5);
|
|
174
|
+
--color-chat-theme-10: var(--chat-theme-10);
|
|
175
|
+
--color-chat-theme-20: var(--chat-theme-20);
|
|
176
|
+
--color-chat-theme-40: var(--chat-theme-40);
|
|
177
|
+
--color-chat-theme-60: var(--chat-theme-60);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@layer base {
|
|
181
|
+
html,
|
|
182
|
+
body,
|
|
183
|
+
#root {
|
|
184
|
+
height: 100%;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
body {
|
|
188
|
+
@apply bg-background text-foreground;
|
|
189
|
+
}
|
|
103
190
|
}
|
|
104
191
|
```
|
|
105
192
|
|
|
106
|
-
|
|
193
|
+
### Next.js — `src/app/globals.css`
|
|
194
|
+
|
|
195
|
+
> **Critical:** `globals.css` is in `src/app/`, so `@source` must use `../../node_modules` (NOT `../node_modules`).
|
|
196
|
+
|
|
197
|
+
```css
|
|
198
|
+
@import "tailwindcss";
|
|
199
|
+
@import "@realtimexsco/live-chat/styles.css";
|
|
200
|
+
|
|
201
|
+
@source "../../node_modules/@realtimexsco/live-chat/dist/index.mjs";
|
|
202
|
+
@source "../../node_modules/@realtimexsco/live-chat/dist/index.cjs";
|
|
203
|
+
|
|
204
|
+
/* …same :root, @theme inline, and @layer base blocks as above… */
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `@source` path reference
|
|
208
|
+
|
|
209
|
+
| CSS file location | Correct `@source` |
|
|
210
|
+
| ----------------- | ----------------- |
|
|
211
|
+
| `src/index.css` | `../node_modules/...` |
|
|
212
|
+
| `src/app/globals.css` | `../../node_modules/...` |
|
|
213
|
+
|
|
214
|
+
Wrong path → Tailwind skips package classes → **old / broken UI**.
|
|
215
|
+
|
|
216
|
+
### Vite config
|
|
107
217
|
|
|
108
218
|
```ts
|
|
109
219
|
// vite.config.ts
|
|
220
|
+
import path from "path";
|
|
221
|
+
import { fileURLToPath } from "url";
|
|
110
222
|
import { defineConfig } from "vite";
|
|
111
223
|
import react from "@vitejs/plugin-react";
|
|
112
224
|
import tailwindcss from "@tailwindcss/vite";
|
|
113
225
|
|
|
226
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
227
|
+
|
|
114
228
|
export default defineConfig({
|
|
115
229
|
plugins: [react(), tailwindcss()],
|
|
116
|
-
resolve: {
|
|
117
|
-
|
|
230
|
+
resolve: {
|
|
231
|
+
alias: { "@": path.resolve(__dirname, "src") },
|
|
232
|
+
dedupe: ["react", "react-dom", "zustand", "socket.io-client", "@realtimexsco/live-chat"],
|
|
233
|
+
},
|
|
234
|
+
optimizeDeps: {
|
|
235
|
+
// Required: stale .vite/deps cache shows OLD UI after package upgrade
|
|
236
|
+
exclude: ["@realtimexsco/live-chat"],
|
|
237
|
+
include: ["zustand", "socket.io-client"],
|
|
238
|
+
},
|
|
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
|
+
},
|
|
118
248
|
});
|
|
119
249
|
```
|
|
120
250
|
|
|
251
|
+
### Next.js config
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
// next.config.ts
|
|
255
|
+
import type { NextConfig } from "next";
|
|
256
|
+
|
|
257
|
+
const nextConfig: NextConfig = {
|
|
258
|
+
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
|
+
};
|
|
268
|
+
|
|
269
|
+
export default nextConfig;
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
`transpilePackages` is required so Next.js compiles the chat package correctly.
|
|
273
|
+
|
|
274
|
+
### PostCSS (Next.js)
|
|
275
|
+
|
|
276
|
+
```js
|
|
277
|
+
// postcss.config.mjs
|
|
278
|
+
const config = {
|
|
279
|
+
plugins: { "@tailwindcss/postcss": {} },
|
|
280
|
+
};
|
|
281
|
+
export default config;
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Configuration
|
|
287
|
+
|
|
288
|
+
Create a shared config file and replace `YOUR_*` placeholders with real credentials.
|
|
289
|
+
|
|
290
|
+
**TypeScript** — `src/configs/chat.config.ts`:
|
|
291
|
+
|
|
292
|
+
```ts
|
|
293
|
+
export const chatConfig = {
|
|
294
|
+
clientId: "YOUR_CLIENT_ID",
|
|
295
|
+
accessToken: "YOUR_JWT_ACCESS_TOKEN",
|
|
296
|
+
loggedUserDetails: {
|
|
297
|
+
_id: "YOUR_USER_ID",
|
|
298
|
+
name: "Your Name",
|
|
299
|
+
email: "you@example.com",
|
|
300
|
+
},
|
|
301
|
+
apiUrl: "http://YOUR_API_HOST/api/v1",
|
|
302
|
+
socketUrl: "http://YOUR_API_HOST",
|
|
303
|
+
themeColor: "#6366f1",
|
|
304
|
+
} as const;
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**JavaScript** — `src/configs/chat.config.js`:
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
export const chatConfig = {
|
|
311
|
+
clientId: "YOUR_CLIENT_ID",
|
|
312
|
+
accessToken: "YOUR_JWT_ACCESS_TOKEN",
|
|
313
|
+
loggedUserDetails: {
|
|
314
|
+
_id: "YOUR_USER_ID",
|
|
315
|
+
name: "Your Name",
|
|
316
|
+
email: "you@example.com",
|
|
317
|
+
},
|
|
318
|
+
apiUrl: "http://YOUR_API_HOST/api/v1",
|
|
319
|
+
socketUrl: "http://YOUR_API_HOST",
|
|
320
|
+
themeColor: "#6366f1",
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
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
|
+
|
|
121
333
|
---
|
|
122
334
|
|
|
123
|
-
## Quick start — full UI
|
|
335
|
+
## Quick start — full UI (`ChatMain`)
|
|
124
336
|
|
|
125
|
-
The fastest path. `ChatMain` renders the sidebar, chat area, and modals in one
|
|
126
|
-
component.
|
|
337
|
+
The fastest path. `ChatMain` renders the sidebar, chat area, and modals in one component.
|
|
127
338
|
|
|
128
339
|
```tsx
|
|
129
340
|
import ChatMain from "@realtimexsco/live-chat";
|
|
341
|
+
import { chatConfig } from "@/configs/chat.config";
|
|
342
|
+
|
|
343
|
+
export default function App() {
|
|
344
|
+
return (
|
|
345
|
+
<div className="chat-page h-dvh min-h-0 overflow-hidden">
|
|
346
|
+
<ChatMain
|
|
347
|
+
clientId={chatConfig.clientId}
|
|
348
|
+
accessToken={chatConfig.accessToken}
|
|
349
|
+
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
350
|
+
apiUrl={chatConfig.apiUrl}
|
|
351
|
+
socketUrl={chatConfig.socketUrl}
|
|
352
|
+
themeColor={chatConfig.themeColor}
|
|
353
|
+
/>
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Hide sections via the `layout` prop:
|
|
360
|
+
|
|
361
|
+
```tsx
|
|
362
|
+
<ChatMain
|
|
363
|
+
{...chatConfig}
|
|
364
|
+
clientId={chatConfig.clientId}
|
|
365
|
+
layout={{ input: false }} // read-only chat
|
|
366
|
+
/>
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Composable shell (`ChatLayout`)
|
|
372
|
+
|
|
373
|
+
Recommended when you want control over the page wrapper but not every internal
|
|
374
|
+
detail. `ChatLayout` is shipped inside the package — no need to copy a large
|
|
375
|
+
layout file.
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
import { Chat, ChatLayout } from "@realtimexsco/live-chat";
|
|
379
|
+
import { chatConfig } from "@/configs/chat.config";
|
|
380
|
+
import { chatLayoutConfig } from "@/configs/chat.layout.config";
|
|
130
381
|
|
|
131
382
|
export default function App() {
|
|
132
383
|
return (
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
384
|
+
<div className="chat-page h-dvh min-h-0 overflow-hidden">
|
|
385
|
+
<Chat
|
|
386
|
+
client={{ id: chatConfig.clientId }}
|
|
387
|
+
accessToken={chatConfig.accessToken}
|
|
388
|
+
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
389
|
+
apiUrl={chatConfig.apiUrl}
|
|
390
|
+
socketUrl={chatConfig.socketUrl}
|
|
391
|
+
themeColor={chatConfig.themeColor}
|
|
392
|
+
>
|
|
393
|
+
<ChatLayout layout={chatLayoutConfig} themeColor={chatConfig.themeColor} />
|
|
394
|
+
</Chat>
|
|
395
|
+
</div>
|
|
145
396
|
);
|
|
146
397
|
}
|
|
147
398
|
```
|
|
148
399
|
|
|
400
|
+
**Layout config** — `src/configs/chat.layout.config.ts`:
|
|
401
|
+
|
|
402
|
+
```ts
|
|
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
|
+
```
|
|
415
|
+
|
|
416
|
+
Or re-export the built-in helpers:
|
|
417
|
+
|
|
418
|
+
```ts
|
|
419
|
+
import {
|
|
420
|
+
defaultChatLayoutConfig,
|
|
421
|
+
mergeChatLayoutConfig,
|
|
422
|
+
type ChatLayoutConfig,
|
|
423
|
+
} from "@realtimexsco/live-chat";
|
|
424
|
+
|
|
425
|
+
export const chatLayoutConfig = defaultChatLayoutConfig;
|
|
426
|
+
|
|
427
|
+
export function mergeLayout(layout: ChatLayoutConfig) {
|
|
428
|
+
return mergeChatLayoutConfig(layout);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
149
432
|
---
|
|
150
433
|
|
|
151
|
-
##
|
|
434
|
+
## Custom layout (advanced)
|
|
435
|
+
|
|
436
|
+
For full control, compose the individual pieces. Wrap the active chat panel in
|
|
437
|
+
`<Channel>` — it resolves the conversation id, fetches messages, joins the socket
|
|
438
|
+
room, and marks messages read. **Do not** manually emit `group_join` /
|
|
439
|
+
`group_message_read` yourself.
|
|
440
|
+
|
|
441
|
+
Use `useChatStore` selectors and pass explicit props to each component (the
|
|
442
|
+
package does not auto-wire props when used outside `ChatLayout`).
|
|
152
443
|
|
|
153
|
-
|
|
154
|
-
sidebar, routing, and styling.
|
|
444
|
+
**TypeScript** — `src/components/chat/ModularChatLayout.tsx`:
|
|
155
445
|
|
|
156
446
|
```tsx
|
|
157
|
-
|
|
447
|
+
"use client"; // required in Next.js
|
|
448
|
+
|
|
449
|
+
import { useCallback, useEffect, useState } from "react";
|
|
158
450
|
import {
|
|
159
|
-
Chat,
|
|
160
451
|
Channel,
|
|
161
|
-
ChannelList,
|
|
162
452
|
ChannelHeader,
|
|
163
|
-
|
|
164
|
-
MessageInput,
|
|
165
|
-
LoggedUserDetails,
|
|
453
|
+
ChannelList,
|
|
166
454
|
ForwardModal,
|
|
167
|
-
|
|
455
|
+
LoggedUserDetails,
|
|
456
|
+
MessageInput,
|
|
457
|
+
MessageList,
|
|
168
458
|
useChatStore,
|
|
459
|
+
mergeChatLayoutConfig,
|
|
460
|
+
type ChatLayoutConfig,
|
|
169
461
|
} from "@realtimexsco/live-chat";
|
|
462
|
+
import classNames from "classnames";
|
|
463
|
+
|
|
464
|
+
type ModularChatLayoutProps = {
|
|
465
|
+
layout?: ChatLayoutConfig;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const ModularChatLayout = ({ layout }: ModularChatLayoutProps) => {
|
|
469
|
+
const sections = mergeChatLayoutConfig(layout);
|
|
470
|
+
const showSidebar =
|
|
471
|
+
sections.sidebar && (sections.loggedUserDetails || sections.channelList);
|
|
472
|
+
|
|
473
|
+
const activeChannel = useChatStore((s) => s.activeChannel);
|
|
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
|
+
);
|
|
170
518
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const { setActiveChannel, error, status } = useChatStore();
|
|
174
|
-
|
|
175
|
-
const [search, setSearch] = useState("");
|
|
176
|
-
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
177
|
-
const [replyToMessage, setReplyToMessage] = useState<any>(null);
|
|
178
|
-
const [forwardMessage, setForwardMessage] = useState<any>(null);
|
|
179
|
-
|
|
180
|
-
// ⚠️ Declare all hooks BEFORE any conditional return (see Troubleshooting)
|
|
181
|
-
const handleUserSelect = useCallback(
|
|
182
|
-
(channel: any) => {
|
|
519
|
+
const handleForwardComplete = useCallback(
|
|
520
|
+
(channel: Parameters<typeof setActiveChannel>[0]) => {
|
|
183
521
|
setActiveChannel(channel);
|
|
184
|
-
|
|
185
|
-
setReplyToMessage(null);
|
|
522
|
+
setIsForwardModalOpen(false);
|
|
186
523
|
setForwardMessage(null);
|
|
187
524
|
},
|
|
188
|
-
[setActiveChannel]
|
|
525
|
+
[setActiveChannel],
|
|
189
526
|
);
|
|
190
527
|
|
|
191
|
-
|
|
192
|
-
|
|
528
|
+
const handleSendMessage = useCallback(
|
|
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
|
+
);
|
|
193
568
|
|
|
194
569
|
return (
|
|
195
|
-
<div className="flex h-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
570
|
+
<div className="chat-shell chat-container-root flex h-full w-full overflow-hidden bg-[#f4f5f7] p-3 md:p-4">
|
|
571
|
+
<div className="relative flex flex-1 overflow-hidden rounded-2xl border border-black/[0.06] bg-white shadow-[0_8px_30px_rgba(15,23,42,0.06)]">
|
|
572
|
+
{showSidebar && (
|
|
573
|
+
<aside
|
|
574
|
+
className={classNames(
|
|
575
|
+
"flex h-full shrink-0 flex-col border-r border-black/[0.06] bg-white transition-all duration-300 ease-in-out",
|
|
576
|
+
"w-full md:w-[340px] lg:w-[360px] xl:w-[380px]",
|
|
577
|
+
activeChannel ? "hidden md:flex" : "flex",
|
|
578
|
+
)}
|
|
579
|
+
>
|
|
580
|
+
{sections.loggedUserDetails && (
|
|
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
|
+
)}
|
|
206
599
|
|
|
207
|
-
<main className="flex flex-col flex-1 min-w-0">
|
|
208
|
-
{/* Channel resolves the conversation, fetches messages, and joins the socket room */}
|
|
209
600
|
<Channel>
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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">
|
|
219
639
|
<MessageInput
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
640
|
+
activeChannel={activeChannel}
|
|
641
|
+
conversationId={conversationId}
|
|
642
|
+
onSendMessage={handleSendMessage}
|
|
643
|
+
replyTo={replyTo}
|
|
644
|
+
onCancelReply={() => setReplyTo(null)}
|
|
223
645
|
/>
|
|
224
|
-
</>
|
|
225
|
-
) : (
|
|
226
|
-
<div className="flex h-full items-center justify-center text-gray-400">
|
|
227
|
-
Select a conversation
|
|
228
646
|
</div>
|
|
229
647
|
)}
|
|
230
648
|
</Channel>
|
|
231
|
-
</
|
|
649
|
+
</div>
|
|
232
650
|
|
|
233
|
-
{
|
|
651
|
+
{sections.forwardModal && (
|
|
234
652
|
<ForwardModal
|
|
235
|
-
isOpen
|
|
236
|
-
onClose={() =>
|
|
653
|
+
isOpen={isForwardModalOpen}
|
|
654
|
+
onClose={() => {
|
|
655
|
+
setIsForwardModalOpen(false);
|
|
656
|
+
setForwardMessage(null);
|
|
657
|
+
}}
|
|
237
658
|
message={forwardMessage}
|
|
238
|
-
onForwardComplete={
|
|
659
|
+
onForwardComplete={handleForwardComplete}
|
|
239
660
|
/>
|
|
240
661
|
)}
|
|
241
662
|
</div>
|
|
242
663
|
);
|
|
243
|
-
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
export default ModularChatLayout;
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
Wrap with `<Chat>`:
|
|
670
|
+
|
|
671
|
+
```tsx
|
|
672
|
+
import { Chat } from "@realtimexsco/live-chat";
|
|
673
|
+
import ModularChatLayout from "@/components/chat/ModularChatLayout";
|
|
674
|
+
import { chatConfig } from "@/configs/chat.config";
|
|
675
|
+
import { chatLayoutConfig } from "@/configs/chat.layout.config";
|
|
244
676
|
|
|
245
677
|
export default function App() {
|
|
246
678
|
return (
|
|
247
|
-
<
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
679
|
+
<div className="chat-page h-dvh min-h-0 overflow-hidden">
|
|
680
|
+
<Chat
|
|
681
|
+
client={{ id: chatConfig.clientId }}
|
|
682
|
+
accessToken={chatConfig.accessToken}
|
|
683
|
+
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
684
|
+
apiUrl={chatConfig.apiUrl}
|
|
685
|
+
socketUrl={chatConfig.socketUrl}
|
|
686
|
+
themeColor={chatConfig.themeColor}
|
|
687
|
+
onMessageReceived={(msg) => console.log("Background message:", msg)}
|
|
688
|
+
onSocketConnected={(id) => console.log("Socket connected:", id)}
|
|
689
|
+
onSocketError={(err) => console.error("Socket error:", err)}
|
|
690
|
+
>
|
|
691
|
+
<ModularChatLayout layout={chatLayoutConfig} />
|
|
692
|
+
</Chat>
|
|
693
|
+
</div>
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
> **Important:** Declare all hooks (`useState`, `useCallback`, `useEffect`, …) at
|
|
699
|
+
> the **top** of the component, before any conditional `return`.
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## React (Vite) — complete setup
|
|
704
|
+
|
|
705
|
+
### Project structure
|
|
706
|
+
|
|
707
|
+
```
|
|
708
|
+
my-app/
|
|
709
|
+
├── package.json
|
|
710
|
+
├── vite.config.ts
|
|
711
|
+
├── jsconfig.json # or tsconfig.json
|
|
712
|
+
├── index.html
|
|
713
|
+
└── src/
|
|
714
|
+
├── main.jsx
|
|
715
|
+
├── App.jsx
|
|
716
|
+
├── index.css
|
|
717
|
+
├── configs/
|
|
718
|
+
│ ├── chat.config.js
|
|
719
|
+
│ └── chat.layout.config.js
|
|
720
|
+
└── components/
|
|
721
|
+
└── chat/
|
|
722
|
+
└── ModularChatLayout.jsx # optional — or use ChatLayout
|
|
723
|
+
```
|
|
724
|
+
|
|
725
|
+
### Create app
|
|
726
|
+
|
|
727
|
+
```bash
|
|
728
|
+
pnpm create vite my-app --template react
|
|
729
|
+
cd my-app
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### `package.json` dependencies
|
|
733
|
+
|
|
734
|
+
```json
|
|
735
|
+
{
|
|
736
|
+
"dependencies": {
|
|
737
|
+
"@realtimexsco/live-chat": "^1.0.0",
|
|
738
|
+
"classnames": "^2.5.1",
|
|
739
|
+
"react": "^19.2.7",
|
|
740
|
+
"react-dom": "^19.2.7",
|
|
741
|
+
"zustand": "^5.0.14"
|
|
742
|
+
},
|
|
743
|
+
"devDependencies": {
|
|
744
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
745
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
746
|
+
"tailwindcss": "^4.3.1",
|
|
747
|
+
"vite": "^6.3.5"
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### `jsconfig.json`
|
|
753
|
+
|
|
754
|
+
```json
|
|
755
|
+
{
|
|
756
|
+
"compilerOptions": {
|
|
757
|
+
"baseUrl": ".",
|
|
758
|
+
"paths": { "@/*": ["src/*"] }
|
|
759
|
+
},
|
|
760
|
+
"include": ["src"]
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
### `src/main.jsx`
|
|
765
|
+
|
|
766
|
+
```jsx
|
|
767
|
+
import { createRoot } from "react-dom/client";
|
|
768
|
+
import "./index.css";
|
|
769
|
+
import App from "./App.jsx";
|
|
770
|
+
|
|
771
|
+
createRoot(document.getElementById("root")).render(<App />);
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
### Run
|
|
775
|
+
|
|
776
|
+
```bash
|
|
777
|
+
pnpm install
|
|
778
|
+
pnpm dev
|
|
779
|
+
```
|
|
780
|
+
|
|
781
|
+
Open **http://localhost:3000**
|
|
782
|
+
|
|
783
|
+
Production:
|
|
784
|
+
|
|
785
|
+
```bash
|
|
786
|
+
pnpm build
|
|
787
|
+
pnpm preview
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### Update package
|
|
791
|
+
|
|
792
|
+
```bash
|
|
793
|
+
pnpm add @realtimexsco/live-chat@latest
|
|
794
|
+
rm -rf node_modules/.vite dist
|
|
795
|
+
pnpm dev
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
Hard refresh: **Ctrl + Shift + R**
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## Next.js (App Router) — complete setup
|
|
803
|
+
|
|
804
|
+
### Project structure
|
|
805
|
+
|
|
806
|
+
```
|
|
807
|
+
my-chat-app/
|
|
808
|
+
├── package.json
|
|
809
|
+
├── next.config.ts
|
|
810
|
+
├── postcss.config.mjs
|
|
811
|
+
├── tsconfig.json
|
|
812
|
+
└── src/
|
|
813
|
+
├── app/
|
|
814
|
+
│ ├── globals.css
|
|
815
|
+
│ ├── layout.tsx
|
|
816
|
+
│ ├── page.tsx
|
|
817
|
+
│ └── chat/
|
|
818
|
+
│ └── page.tsx
|
|
819
|
+
├── configs/
|
|
820
|
+
│ ├── chat.config.ts
|
|
821
|
+
│ └── chat.layout.config.ts
|
|
822
|
+
└── components/
|
|
823
|
+
└── chat/
|
|
824
|
+
└── ModularChatLayout.tsx # optional — or use ChatLayout
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
### Create app
|
|
828
|
+
|
|
829
|
+
```bash
|
|
830
|
+
pnpm create next-app@latest my-chat-app --typescript --tailwind --app --src-dir
|
|
831
|
+
cd my-chat-app
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
### `package.json` dependencies
|
|
835
|
+
|
|
836
|
+
```json
|
|
837
|
+
{
|
|
838
|
+
"dependencies": {
|
|
839
|
+
"@realtimexsco/live-chat": "^1.0.0",
|
|
840
|
+
"classnames": "^2.5.1",
|
|
841
|
+
"next": "16.2.9",
|
|
842
|
+
"react": "19.2.4",
|
|
843
|
+
"react-dom": "19.2.4",
|
|
844
|
+
"zustand": "^5.0.14"
|
|
845
|
+
},
|
|
846
|
+
"devDependencies": {
|
|
847
|
+
"@tailwindcss/postcss": "^4",
|
|
848
|
+
"tailwindcss": "^4",
|
|
849
|
+
"typescript": "^5"
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
```
|
|
853
|
+
|
|
854
|
+
### `tsconfig.json` path alias
|
|
855
|
+
|
|
856
|
+
```json
|
|
857
|
+
{
|
|
858
|
+
"compilerOptions": {
|
|
859
|
+
"paths": { "@/*": ["./src/*"] }
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
### `src/app/layout.tsx`
|
|
865
|
+
|
|
866
|
+
```tsx
|
|
867
|
+
import type { Metadata } from "next";
|
|
868
|
+
import "./globals.css";
|
|
869
|
+
|
|
870
|
+
export const metadata: Metadata = {
|
|
871
|
+
title: "RealtimeX Chat",
|
|
872
|
+
description: "RealtimeX chat integration with Next.js",
|
|
873
|
+
};
|
|
874
|
+
|
|
875
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
876
|
+
return (
|
|
877
|
+
<html lang="en" className="h-full antialiased">
|
|
878
|
+
<body className="min-h-full flex flex-col">{children}</body>
|
|
879
|
+
</html>
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
```
|
|
883
|
+
|
|
884
|
+
### `src/app/chat/page.tsx`
|
|
885
|
+
|
|
886
|
+
```tsx
|
|
887
|
+
"use client";
|
|
888
|
+
|
|
889
|
+
import { Chat, ChatLayout } from "@realtimexsco/live-chat";
|
|
890
|
+
import { chatConfig } from "@/configs/chat.config";
|
|
891
|
+
import { chatLayoutConfig } from "@/configs/chat.layout.config";
|
|
892
|
+
|
|
893
|
+
export default function ChatPage() {
|
|
894
|
+
return (
|
|
895
|
+
<div className="chat-page h-dvh min-h-0 overflow-hidden">
|
|
896
|
+
<Chat
|
|
897
|
+
client={{ id: chatConfig.clientId }}
|
|
898
|
+
accessToken={chatConfig.accessToken}
|
|
899
|
+
loggedUserDetails={chatConfig.loggedUserDetails}
|
|
900
|
+
apiUrl={chatConfig.apiUrl}
|
|
901
|
+
socketUrl={chatConfig.socketUrl}
|
|
902
|
+
themeColor={chatConfig.themeColor}
|
|
903
|
+
>
|
|
904
|
+
<ChatLayout layout={chatLayoutConfig} themeColor={chatConfig.themeColor} />
|
|
905
|
+
</Chat>
|
|
906
|
+
</div>
|
|
260
907
|
);
|
|
261
908
|
}
|
|
262
909
|
```
|
|
263
910
|
|
|
264
|
-
>
|
|
265
|
-
|
|
266
|
-
|
|
911
|
+
> `'use client'` is required — the chat package uses React hooks and Zustand.
|
|
912
|
+
|
|
913
|
+
### Run
|
|
914
|
+
|
|
915
|
+
```bash
|
|
916
|
+
pnpm install
|
|
917
|
+
pnpm dev
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
Open **http://localhost:3000/chat**
|
|
921
|
+
|
|
922
|
+
Clear cache after CSS or package changes:
|
|
923
|
+
|
|
924
|
+
```bash
|
|
925
|
+
rm -rf .next
|
|
926
|
+
pnpm dev
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
### Update package
|
|
930
|
+
|
|
931
|
+
```bash
|
|
932
|
+
pnpm add @realtimexsco/live-chat@latest
|
|
933
|
+
rm -rf .next
|
|
934
|
+
pnpm dev
|
|
935
|
+
```
|
|
267
936
|
|
|
268
937
|
---
|
|
269
938
|
|
|
@@ -335,64 +1004,182 @@ export default function App() {
|
|
|
335
1004
|
|
|
336
1005
|
---
|
|
337
1006
|
|
|
1007
|
+
## Layout customization
|
|
1008
|
+
|
|
1009
|
+
Edit your layout config or pass overrides inline:
|
|
1010
|
+
|
|
1011
|
+
```ts
|
|
1012
|
+
export const chatLayoutConfig = {
|
|
1013
|
+
sidebar: true, // left panel wrapper
|
|
1014
|
+
loggedUserDetails: true, // user profile + new chat button
|
|
1015
|
+
channelList: true, // conversation list ("Messages")
|
|
1016
|
+
header: true, // channel header (name, calls, menu)
|
|
1017
|
+
messageList: true, // message bubbles area
|
|
1018
|
+
input: true, // message composer
|
|
1019
|
+
forwardModal: true, // forward message dialog
|
|
1020
|
+
};
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
Examples:
|
|
1024
|
+
|
|
1025
|
+
```ts
|
|
1026
|
+
{ input: false } // read-only chat (no composer)
|
|
1027
|
+
{ sidebar: false } // full-width messages (hide sidebar)
|
|
1028
|
+
{ forwardModal: false } // hide forward modal
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
Pass to `ChatMain`, `ChatLayout`, or `ModularChatLayout`:
|
|
1032
|
+
|
|
1033
|
+
```tsx
|
|
1034
|
+
<ChatLayout layout={{ sidebar: true, input: false }} />
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
---
|
|
1038
|
+
|
|
338
1039
|
## Exported components & hooks
|
|
339
1040
|
|
|
340
1041
|
| Export | Description |
|
|
341
1042
|
| ------ | ----------- |
|
|
342
|
-
| `ChatMain` | All-in-one chat UI
|
|
1043
|
+
| `ChatMain` (default) | All-in-one chat UI with optional `layout` prop |
|
|
343
1044
|
| `Chat` | Provider — initializes auth, API, socket, store |
|
|
1045
|
+
| `ChatLayout` | Composable shell — toggle sections via `layout` prop |
|
|
344
1046
|
| `Channel` | Active conversation lifecycle (fetch, join, mark-read) |
|
|
345
1047
|
| `ChannelList` | Conversation sidebar list |
|
|
346
1048
|
| `ChannelHeader` | Chat header with search, details, actions |
|
|
347
|
-
| `MessageList` | Message thread |
|
|
348
|
-
| `MessageInput` | Compose box |
|
|
1049
|
+
| `MessageList` | Message thread (`ActiveChannelMessages`) |
|
|
1050
|
+
| `MessageInput` | Compose box (`ChannelMessageBox`) |
|
|
349
1051
|
| `LoggedUserDetails` | User profile + new chat / create group |
|
|
350
1052
|
| `ForwardModal` | Forward message modal |
|
|
351
1053
|
| `useChatContext` | `{ activeChannel, setActiveChannel, themeColor, uiConfig }` |
|
|
352
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 |
|
|
353
1059
|
| `socketService` | Low-level socket service |
|
|
1060
|
+
| `getSocketConfig` | Socket configuration helper |
|
|
354
1061
|
| `setChatClientId`, `setChatAccessToken`, `setChatBaseURL` | Manual API config |
|
|
1062
|
+
| `IChannel`, `IMessage`, `IAttachment` | Core TypeScript types |
|
|
1063
|
+
| `ISocketConfig` | Socket config type |
|
|
1064
|
+
|
|
1065
|
+
### Import example
|
|
1066
|
+
|
|
1067
|
+
```ts
|
|
1068
|
+
import {
|
|
1069
|
+
Chat,
|
|
1070
|
+
ChatMain,
|
|
1071
|
+
ChatLayout,
|
|
1072
|
+
Channel,
|
|
1073
|
+
ChannelList,
|
|
1074
|
+
ChannelHeader,
|
|
1075
|
+
MessageList,
|
|
1076
|
+
MessageInput,
|
|
1077
|
+
LoggedUserDetails,
|
|
1078
|
+
ForwardModal,
|
|
1079
|
+
useChatStore,
|
|
1080
|
+
useChatController,
|
|
1081
|
+
getSocketConfig,
|
|
1082
|
+
type ChatLayoutConfig,
|
|
1083
|
+
} from "@realtimexsco/live-chat";
|
|
1084
|
+
```
|
|
355
1085
|
|
|
356
1086
|
---
|
|
357
1087
|
|
|
358
1088
|
## Theming
|
|
359
1089
|
|
|
360
1090
|
Set a single accent color via the `themeColor` prop; it drives the
|
|
361
|
-
`--chat-theme*` CSS variables. Override any of the variables
|
|
362
|
-
|
|
363
|
-
`theme="dark"` for dark mode.
|
|
1091
|
+
`--chat-theme*` CSS variables at runtime. Override any of the variables in your
|
|
1092
|
+
global CSS to customize further, and pass `theme="dark"` for dark mode.
|
|
364
1093
|
|
|
365
1094
|
```tsx
|
|
366
|
-
<Chat themeColor="#7c3aed" theme="dark"
|
|
1095
|
+
<Chat themeColor="#7c3aed" theme="dark">{/* … */}</Chat>
|
|
367
1096
|
```
|
|
368
1097
|
|
|
369
1098
|
---
|
|
370
1099
|
|
|
371
1100
|
## Troubleshooting
|
|
372
1101
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
1102
|
+
### Old or broken UI
|
|
1103
|
+
|
|
1104
|
+
| Cause | Fix |
|
|
1105
|
+
| ----- | --- |
|
|
1106
|
+
| Wrong `@source` path in CSS | Use `../node_modules` from `src/index.css`, or `../../node_modules` from `src/app/globals.css` |
|
|
1107
|
+
| Missing `styles.css` import | `@import "@realtimexsco/live-chat/styles.css"` |
|
|
1108
|
+
| Missing `--chat-theme` CSS vars | Copy full CSS block from [Tailwind setup](#tailwind-css-v4-setup-required) |
|
|
1109
|
+
| Stale Vite cache | `rm -rf node_modules/.vite && pnpm dev` |
|
|
1110
|
+
| Stale Next.js cache | `rm -rf .next && pnpm dev` |
|
|
1111
|
+
| Package in `optimizeDeps.include` (Vite) | Use `exclude: ["@realtimexsco/live-chat"]` |
|
|
1112
|
+
| Missing `transpilePackages` (Next.js) | Add `transpilePackages: ["@realtimexsco/live-chat"]` in `next.config.ts` |
|
|
1113
|
+
|
|
1114
|
+
### `Rendered more hooks than during the previous render`
|
|
1115
|
+
|
|
1116
|
+
Declare all hooks at the **top** of the component, before any conditional
|
|
1117
|
+
`return`. Handle loading / error states after the hooks.
|
|
376
1118
|
|
|
377
|
-
|
|
378
|
-
1. Confirm the `@source` paths in your CSS point to the package `dist/` files.
|
|
379
|
-
2. Import `@realtimexsco/live-chat/styles.css`.
|
|
380
|
-
3. Restart your dev server after installing or updating the package.
|
|
1119
|
+
### Chat loads but messages don't appear
|
|
381
1120
|
|
|
382
|
-
**Chat loads but messages don't appear**
|
|
383
1121
|
Ensure `<Channel>` wraps `MessageList` and `MessageInput`. The package keys
|
|
384
1122
|
messages by `conversationId` internally — don't key DM messages by user id.
|
|
385
1123
|
|
|
386
|
-
|
|
1124
|
+
### Group events firing in DM (or vice versa)
|
|
1125
|
+
|
|
387
1126
|
Always use the composable pattern with `<Channel>`, and don't manually emit
|
|
388
1127
|
`group_join` / `group_message_read` outside the package.
|
|
389
1128
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
`vite.config.ts`.
|
|
1129
|
+
### Duplicate React / invalid hook call
|
|
1130
|
+
|
|
1131
|
+
Add `resolve: { dedupe: ["react", "react-dom", "zustand"] }` to `vite.config.ts`.
|
|
1132
|
+
|
|
1133
|
+
### `use client` error (Next.js)
|
|
1134
|
+
|
|
1135
|
+
Any file importing `Chat`, `useChatStore`, or chat hooks must start with:
|
|
1136
|
+
|
|
1137
|
+
```tsx
|
|
1138
|
+
"use client";
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
### Socket / API errors
|
|
1142
|
+
|
|
1143
|
+
- `apiUrl` must end with `/api/v1`
|
|
1144
|
+
- `socketUrl` = base host only (no `/api/v1`)
|
|
1145
|
+
- Use a dev proxy (`/chat-api`) or Next.js rewrite if CORS blocks requests
|
|
1146
|
+
|
|
1147
|
+
### Hydration warnings (Next.js)
|
|
1148
|
+
|
|
1149
|
+
Keep chat inside a client component (`chat/page.tsx`). Do not render
|
|
1150
|
+
socket-dependent UI in server components.
|
|
1151
|
+
|
|
1152
|
+
---
|
|
1153
|
+
|
|
1154
|
+
## Checklist
|
|
1155
|
+
|
|
1156
|
+
### All projects
|
|
1157
|
+
|
|
1158
|
+
- [ ] `@realtimexsco/live-chat` installed from npm
|
|
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
|
|
1165
|
+
|
|
1166
|
+
### React (Vite)
|
|
1167
|
+
|
|
1168
|
+
- [ ] `vite.config.ts` — `@` alias, dedupe, **exclude** package from `optimizeDeps`
|
|
1169
|
+
- [ ] `jsconfig.json` / `tsconfig.json` — `"@/*": ["src/*"]`
|
|
1170
|
+
- [ ] `src/main.jsx` imports `index.css`
|
|
1171
|
+
- [ ] Clear `node_modules/.vite` after every package upgrade
|
|
1172
|
+
|
|
1173
|
+
### Next.js
|
|
1174
|
+
|
|
1175
|
+
- [ ] `next.config.ts` — `transpilePackages: ["@realtimexsco/live-chat"]`
|
|
1176
|
+
- [ ] `postcss.config.mjs` — `@tailwindcss/postcss`
|
|
1177
|
+
- [ ] `globals.css` uses **`../../node_modules`** in `@source`
|
|
1178
|
+
- [ ] `src/app/chat/page.tsx` — `'use client'` + `<Chat>` + `<ChatLayout>`
|
|
1179
|
+
- [ ] Clear `.next` after every package upgrade
|
|
393
1180
|
|
|
394
1181
|
---
|
|
395
1182
|
|
|
396
1183
|
## License
|
|
397
1184
|
|
|
398
|
-
[MIT](
|
|
1185
|
+
[MIT](LICENSE) © RealtimeX
|