@realtimexsco/live-chat 1.2.0 → 1.3.1

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