@xcelsior/ui-chat 1.0.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.
@@ -0,0 +1,27 @@
1
+ import type { StorybookConfig } from '@storybook/react-vite';
2
+
3
+ const config: StorybookConfig = {
4
+ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
5
+ addons: [
6
+ '@storybook/addon-links',
7
+ '@storybook/addon-essentials',
8
+ '@storybook/addon-interactions',
9
+ ],
10
+ framework: {
11
+ name: '@storybook/react-vite',
12
+ options: {},
13
+ },
14
+ docs: {
15
+ autodocs: 'tag',
16
+ },
17
+ typescript: {
18
+ check: false,
19
+ reactDocgen: 'react-docgen-typescript',
20
+ reactDocgenTypescriptOptions: {
21
+ shouldExtractLiteralValuesFromEnum: true,
22
+ propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
23
+ },
24
+ },
25
+ };
26
+
27
+ export default config;
@@ -0,0 +1,39 @@
1
+ import '@xcelsior/design-system/styles';
2
+ import type { Preview } from '@storybook/react';
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { ToastContainer } from '@xcelsior/design-system';
5
+
6
+ const queryClient = new QueryClient({
7
+ defaultOptions: {
8
+ queries: {
9
+ retry: false,
10
+ },
11
+ },
12
+ });
13
+
14
+ const preview: Preview = {
15
+ parameters: {
16
+ actions: { argTypesRegex: '^on[A-Z].*' },
17
+ controls: {
18
+ matchers: {
19
+ color: /(background|color)$/i,
20
+ date: /Date$/,
21
+ },
22
+ },
23
+ docs: {
24
+ toc: true,
25
+ },
26
+ },
27
+ tags: ['autodocs'],
28
+ decorators: [
29
+ (Story) => (
30
+ <ToastContainer>
31
+ <QueryClientProvider client={queryClient}>
32
+ <Story />
33
+ </QueryClientProvider>
34
+ </ToastContainer>
35
+ ),
36
+ ],
37
+ };
38
+
39
+ export default preview;
package/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 1.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 631caeb: initial version
8
+
9
+ All notable changes to this project will be documented in this file.
10
+
11
+ ## [1.0.0] - 2025-10-28
12
+
13
+ ### Added
14
+
15
+ - Initial release of @xcelsior/ui-chat
16
+ - Real-time WebSocket-based chat functionality
17
+ - Emoji picker support with @emoji-mart/react
18
+ - File upload support with progress tracking
19
+ - Typing indicators
20
+ - Read receipts
21
+ - Message status tracking (sent, delivered, read)
22
+ - Support for text, image, and file messages
23
+ - Markdown rendering in messages
24
+ - Dark mode support
25
+ - Responsive design
26
+ - Accessibility features
27
+ - Comprehensive Storybook documentation
28
+ - TypeScript type definitions
29
+ - Custom hooks for WebSocket, messages, file uploads, and typing indicators
30
+ - Individual components for custom implementations
31
+ - Integration with @xcelsior/design-system and @xcelsior/ui-fields
32
+ - Mock WebSocket for Storybook testing
package/README.md ADDED
@@ -0,0 +1,526 @@
1
+ # @xcelsior/ui-chat
2
+
3
+ A comprehensive, real-time chat widget component for React applications with WebSocket support, emoji picker, file uploads, and more.
4
+
5
+ ## Features
6
+
7
+ - 🔄 **Real-time Communication**: WebSocket-based instant messaging
8
+ - 💬 **Rich Messages**: Support for text, markdown, images, and files
9
+ - 🤖 **AI Assistant**: Visual differentiation for AI-generated messages with robot icon
10
+ - 😊 **Emoji Support**: Built-in emoji picker
11
+ - 📎 **File Uploads**: Upload and share files with progress tracking
12
+ - ⌨️ **Typing Indicators**: Show when users are typing
13
+ - ✓✓ **Read Receipts**: Track message delivery and read status
14
+ - 🎨 **Customizable**: Fully configurable appearance and behavior
15
+ - 📱 **Responsive**: Works great on all screen sizes
16
+ - 🌓 **Dark Mode**: Automatic dark mode support
17
+ - ♿ **Accessible**: Built with accessibility in mind
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pnpm add @xcelsior/ui-chat @xcelsior/design-system @xcelsior/ui-fields
23
+ ```
24
+
25
+ ### Peer Dependencies
26
+
27
+ Make sure you have the following peer dependencies installed:
28
+
29
+ ```bash
30
+ pnpm add react react-dom @tanstack/react-query react-hook-form flowbite-react
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### Basic Example
36
+
37
+ ```tsx
38
+ import { ChatWidget } from '@xcelsior/ui-chat';
39
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
40
+
41
+ const queryClient = new QueryClient();
42
+
43
+ function App() {
44
+ const chatConfig = {
45
+ websocketUrl: 'wss://your-api.com/chat',
46
+ userId: 'user-123',
47
+ userType: 'customer',
48
+ conversationId: 'conv-456',
49
+ currentUser: {
50
+ id: 'user-123',
51
+ name: 'John Doe',
52
+ email: 'john@example.com',
53
+ type: 'customer',
54
+ },
55
+ };
56
+
57
+ return (
58
+ <QueryClientProvider client={queryClient}>
59
+ <ChatWidget config={chatConfig} />
60
+ </QueryClientProvider>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ### With File Upload
66
+
67
+ The file upload feature uses a presigned URL approach for secure, direct-to-S3 uploads:
68
+
69
+ ```tsx
70
+ import { ChatWidget } from '@xcelsior/ui-chat';
71
+
72
+ const chatConfig = {
73
+ // ... other config
74
+ fileUpload: {
75
+ uploadUrl: 'https://your-api.com/attachments/upload-url',
76
+ maxFileSize: 10 * 1024 * 1024, // 10MB (default)
77
+ allowedTypes: [
78
+ 'image/jpeg',
79
+ 'image/png',
80
+ 'image/gif',
81
+ 'image/webp',
82
+ 'application/pdf',
83
+ 'application/msword',
84
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
85
+ 'text/plain',
86
+ 'text/csv',
87
+ ],
88
+ headers: {
89
+ 'Authorization': 'Bearer your-token',
90
+ },
91
+ },
92
+ };
93
+
94
+ <ChatWidget config={chatConfig} />
95
+ ```
96
+
97
+ **How it works:**
98
+
99
+ 1. User selects a file
100
+ 2. Widget requests a presigned upload URL from your API (`POST /attachments/upload-url`)
101
+ 3. Widget uploads file directly to S3 using the presigned URL
102
+ 4. File becomes accessible via CloudFront CDN
103
+ 5. The CloudFront URL is inserted into the message as markdown
104
+
105
+ **Backend API Contract:**
106
+
107
+ ```typescript
108
+ // Request
109
+ POST /attachments/upload-url
110
+ {
111
+ "fileName": "document.pdf",
112
+ "contentType": "application/pdf",
113
+ "fileSize": 1234567
114
+ }
115
+
116
+ // Response
117
+ {
118
+ "data": {
119
+ "uploadUrl": "https://s3.amazonaws.com/...", // Presigned URL for upload
120
+ "attachmentUrl": "https://cdn.example.com/...", // Final public URL
121
+ "key": "attachments/uuid.pdf", // S3 key
122
+ "expiresIn": 300 // URL expiry (seconds)
123
+ }
124
+ }
125
+ ```
126
+
127
+ ### With Event Callbacks
128
+
129
+ ```tsx
130
+ import { ChatWidget } from '@xcelsior/ui-chat';
131
+
132
+ const chatConfig = {
133
+ // ... other config
134
+ onMessageSent: (message) => {
135
+ console.log('Message sent:', message);
136
+ },
137
+ onMessageReceived: (message) => {
138
+ console.log('Message received:', message);
139
+ },
140
+ onConnectionChange: (connected) => {
141
+ console.log('Connection status:', connected);
142
+ },
143
+ onError: (error) => {
144
+ console.error('Chat error:', error);
145
+ },
146
+ toast: {
147
+ success: (message) => console.log('Success:', message),
148
+ error: (message) => console.error('Error:', message),
149
+ info: (message) => console.log('Info:', message),
150
+ },
151
+ };
152
+
153
+ <ChatWidget config={chatConfig} />
154
+ ```
155
+
156
+ ## Configuration
157
+
158
+ ### IChatConfig
159
+
160
+ The main configuration object for the chat widget.
161
+
162
+ | Property | Type | Required | Description |
163
+ |----------|------|----------|-------------|
164
+ | `websocketUrl` | `string` | ✓ | WebSocket server URL |
165
+ | `userId` | `string` | ✓ | Current user's ID |
166
+ | `userType` | `'customer' \| 'agent'` | ✓ | Type of user |
167
+ | `currentUser` | `IUser` | ✓ | Current user information |
168
+ | `conversationId` | `string` | | Conversation ID (auto-generated if not provided) |
169
+ | `httpApiUrl` | `string` | | REST API URL for fetching data |
170
+ | `headers` | `Record<string, string>` | | HTTP headers for API requests |
171
+ | `fileUpload` | `IFileUploadConfig` | | File upload configuration |
172
+ | `enableEmoji` | `boolean` | | Enable emoji picker (default: true) |
173
+ | `enableFileUpload` | `boolean` | | Enable file uploads (default: true) |
174
+ | `enableTypingIndicator` | `boolean` | | Enable typing indicators (default: true) |
175
+ | `enableReadReceipts` | `boolean` | | Enable read receipts (default: true) |
176
+ | `onMessageSent` | `(message: IMessage) => void` | | Callback when message is sent |
177
+ | `onMessageReceived` | `(message: IMessage) => void` | | Callback when message is received |
178
+ | `onConnectionChange` | `(connected: boolean) => void` | | Callback when connection status changes |
179
+ | `onError` | `(error: Error) => void` | | Callback when error occurs |
180
+ | `toast` | `object` | | Toast notification handlers |
181
+
182
+ ### IFileUploadConfig
183
+
184
+ Configuration for file uploads.
185
+
186
+ | Property | Type | Required | Description |
187
+ |----------|------|----------|-------------|
188
+ | `uploadUrl` | `string` | ✓ | Upload endpoint URL |
189
+ | `maxFileSize` | `number` | | Maximum file size in bytes (default: 5MB) |
190
+ | `allowedTypes` | `string[]` | | Allowed MIME types (default: images and PDFs) |
191
+ | `headers` | `Record<string, string>` | | Additional headers for upload request |
192
+
193
+ ## WebSocket Integration
194
+
195
+ The chat widget connects to your WebSocket API using the following protocol:
196
+
197
+ ### Connection
198
+
199
+ ```
200
+ wss://your-api.com/chat?userId=user-123&userType=customer&conversationId=conv-456
201
+ ```
202
+
203
+ ### Sending Messages
204
+
205
+ ```json
206
+ {
207
+ "action": "sendMessage",
208
+ "data": {
209
+ "conversationId": "conv-456",
210
+ "content": "Hello!",
211
+ "messageType": "text"
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### Receiving Messages
217
+
218
+ ```json
219
+ {
220
+ "type": "message",
221
+ "data": {
222
+ "id": "msg-789",
223
+ "conversationId": "conv-456",
224
+ "senderId": "agent-123",
225
+ "senderType": "agent",
226
+ "content": "Hi! How can I help?",
227
+ "messageType": "text",
228
+ "createdAt": "2025-10-28T12:00:00Z",
229
+ "status": "delivered"
230
+ }
231
+ }
232
+ ```
233
+
234
+ ### Typing Indicator
235
+
236
+ ```json
237
+ {
238
+ "action": "typing",
239
+ "data": {
240
+ "conversationId": "conv-456",
241
+ "isTyping": true
242
+ }
243
+ }
244
+ ```
245
+
246
+ ## REST API Integration
247
+
248
+ The chat widget can fetch existing messages from your REST API when `httpApiUrl` is provided in the config.
249
+
250
+ ### Fetching Messages
251
+
252
+ When the widget loads or the `conversationId` changes, it will automatically fetch existing messages:
253
+
254
+ ```
255
+ GET /messages?conversationId=conv-456&limit=50
256
+ ```
257
+
258
+ Expected response format:
259
+
260
+ ```json
261
+ {
262
+ "success": true,
263
+ "data": {
264
+ "items": [...messages],
265
+ "nextPageToken": "optional-pagination-token"
266
+ }
267
+ }
268
+ ```
269
+
270
+ or alternatively:
271
+
272
+ ```json
273
+ {
274
+ "success": true,
275
+ "data": [...messages],
276
+ "nextToken": "optional-pagination-token"
277
+ }
278
+ ```
279
+
280
+ ### Example with HTTP API
281
+
282
+ ```tsx
283
+ const chatConfig = {
284
+ websocketUrl: 'wss://your-api.com/chat',
285
+ httpApiUrl: 'https://your-api.com',
286
+ conversationId: 'conv-456',
287
+ headers: {
288
+ 'Authorization': 'Bearer your-token',
289
+ },
290
+ // ... other config
291
+ };
292
+
293
+ <ChatWidget config={chatConfig} />
294
+ ```
295
+
296
+ ## Performance Optimizations
297
+
298
+ ### Debounced Typing Indicator
299
+
300
+ The typing indicator is now debounced to reduce unnecessary WebSocket messages:
301
+ - **Start typing**: Sent after 300ms of typing activity
302
+ - **Stop typing**: Sent after 1.5s of inactivity
303
+ - This reduces WebSocket traffic while maintaining responsive user feedback
304
+
305
+ ## Components
306
+
307
+ ### ChatWidget
308
+
309
+ The main chat widget component that includes all features.
310
+
311
+ ```tsx
312
+ import { ChatWidget } from '@xcelsior/ui-chat';
313
+
314
+ <ChatWidget config={chatConfig} className="custom-class" />
315
+ ```
316
+
317
+ ### Individual Components
318
+
319
+ For custom implementations, you can use individual components:
320
+
321
+ #### MessageList
322
+
323
+ ```tsx
324
+ import { MessageList } from '@xcelsior/ui-chat';
325
+
326
+ <MessageList
327
+ messages={messages}
328
+ currentUser={currentUser}
329
+ isTyping={false}
330
+ autoScroll={true}
331
+ />
332
+ ```
333
+
334
+ #### MessageItem
335
+
336
+ Displays individual messages with automatic icon detection:
337
+ - 🤖 **Robot icon** for AI-generated messages (when `metadata.isAI === true`)
338
+ - 🎧 **Headset icon** for human agent messages
339
+ - 👤 **User icon** for customer messages
340
+
341
+ ```tsx
342
+ import { MessageItem } from '@xcelsior/ui-chat';
343
+
344
+ // Regular agent message
345
+ <MessageItem
346
+ message={message}
347
+ currentUser={currentUser}
348
+ showAvatar={true}
349
+ showTimestamp={true}
350
+ />
351
+
352
+ // AI-generated message (automatically shows robot icon)
353
+ <MessageItem
354
+ message={{
355
+ ...message,
356
+ senderType: 'agent',
357
+ metadata: { isAI: true }
358
+ }}
359
+ currentUser={currentUser}
360
+ showAvatar={true}
361
+ showTimestamp={true}
362
+ />
363
+ ```
364
+
365
+ #### ChatInput
366
+
367
+ ```tsx
368
+ import { ChatInput } from '@xcelsior/ui-chat';
369
+
370
+ <ChatInput
371
+ onSend={handleSend}
372
+ onTyping={handleTyping}
373
+ config={chatConfig}
374
+ fileUpload={fileUploadHook}
375
+ />
376
+ ```
377
+
378
+ #### ChatHeader
379
+
380
+ ```tsx
381
+ import { ChatHeader } from '@xcelsior/ui-chat';
382
+
383
+ <ChatHeader
384
+ agent={agent}
385
+ onClose={handleClose}
386
+ onMinimize={handleMinimize}
387
+ />
388
+ ```
389
+
390
+ ## Hooks
391
+
392
+ ### useWebSocket
393
+
394
+ Custom hook for WebSocket connection management.
395
+
396
+ ```tsx
397
+ import { useWebSocket } from '@xcelsior/ui-chat';
398
+
399
+ const { isConnected, sendMessage, lastMessage, error, reconnect } = useWebSocket(config);
400
+ ```
401
+
402
+ ### useMessages
403
+
404
+ Custom hook for message management.
405
+
406
+ ```tsx
407
+ import { useMessages } from '@xcelsior/ui-chat';
408
+
409
+ const { messages, addMessage, updateMessageStatus, clearMessages } = useMessages(websocket, config);
410
+ ```
411
+
412
+ ### useFileUpload
413
+
414
+ Custom hook for file uploads.
415
+
416
+ ```tsx
417
+ import { useFileUpload } from '@xcelsior/ui-chat';
418
+
419
+ const { uploadFile, isUploading, uploadProgress, error, canUpload } = useFileUpload(config);
420
+ ```
421
+
422
+ ### useTypingIndicator
423
+
424
+ Custom hook for typing indicators.
425
+
426
+ ```tsx
427
+ import { useTypingIndicator } from '@xcelsior/ui-chat';
428
+
429
+ const { isTyping, typingUsers } = useTypingIndicator(websocket);
430
+ ```
431
+
432
+ ## Styling
433
+
434
+ The component uses Tailwind CSS for styling and supports dark mode automatically. You can customize the appearance by:
435
+
436
+ 1. **Using the className prop**: Pass custom classes to the ChatWidget component
437
+ 2. **Overriding CSS variables**: Define custom CSS variables for colors and sizes
438
+ 3. **Using Tailwind configuration**: Extend your Tailwind config to customize the design system
439
+
440
+ ## Backend Integration
441
+
442
+ This package is designed to work with the `@xcelsior/chat-api` backend service. For more information on setting up the backend, see the [chat-api documentation](../../services/chat-api/README.md).
443
+
444
+ ### Required Backend Endpoints
445
+
446
+ - **WebSocket**: For real-time communication
447
+ - `$connect`: Handle new connections
448
+ - `$disconnect`: Handle disconnections
449
+ - `sendMessage`: Handle message sending
450
+ - `typing`: Handle typing indicators
451
+
452
+ - **REST API** (optional): For fetching historical data
453
+ - `GET /messages?conversationId=xxx`: Fetch message history
454
+ - `GET /conversations/:id`: Fetch conversation details
455
+
456
+ - **File Upload API** (optional): For file attachment support
457
+ - `POST /attachments/upload-url`: Generate presigned S3 upload URL
458
+ - Request: `{ fileName, contentType, fileSize }`
459
+ - Response: `{ uploadUrl, attachmentUrl, key, expiresIn }`
460
+
461
+ ## Examples
462
+
463
+ Check out the Storybook for live examples and interactive demos:
464
+
465
+ ```bash
466
+ pnpm storybook
467
+ ```
468
+
469
+ ## Development
470
+
471
+ ### Build
472
+
473
+ ```bash
474
+ pnpm build
475
+ ```
476
+
477
+ ### Development Mode
478
+
479
+ ```bash
480
+ pnpm dev
481
+ ```
482
+
483
+ ### Storybook
484
+
485
+ ```bash
486
+ pnpm storybook
487
+ ```
488
+
489
+ ### Lint
490
+
491
+ ```bash
492
+ pnpm lint
493
+ ```
494
+
495
+ ## TypeScript
496
+
497
+ This package is written in TypeScript and includes full type definitions. All types are exported from the main package:
498
+
499
+ ```tsx
500
+ import type {
501
+ IChatConfig,
502
+ IMessage,
503
+ IUser,
504
+ IConversation,
505
+ MessageType,
506
+ MessageStatus,
507
+ } from '@xcelsior/ui-chat';
508
+ ```
509
+
510
+ ## Browser Support
511
+
512
+ - Chrome (latest)
513
+ - Firefox (latest)
514
+ - Safari (latest)
515
+ - Edge (latest)
516
+
517
+ WebSocket support is required.
518
+
519
+ ## License
520
+
521
+ MIT
522
+
523
+ ## Support
524
+
525
+ For issues and questions, please contact the development team or create an issue in the repository.
526
+
package/biome.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "//"
3
+ }
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@xcelsior/ui-chat",
3
+ "version": "1.0.1",
4
+ "main": "./dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "sideEffects": false,
7
+ "license": "MIT",
8
+ "devDependencies": {
9
+ "@storybook/addon-essentials": "^8.6.14",
10
+ "@storybook/addon-interactions": "^8.6.14",
11
+ "@storybook/addon-links": "^8.6.14",
12
+ "@storybook/addon-onboarding": "^8.6.14",
13
+ "@storybook/blocks": "^8.6.14",
14
+ "@storybook/nextjs": "^8.6.14",
15
+ "@storybook/react": "^8.6.14",
16
+ "@storybook/react-vite": "^8.6.14",
17
+ "@storybook/testing-library": "^0.2.2",
18
+ "@types/react": "^18.2.0",
19
+ "@types/react-dom": "^18.2.0",
20
+ "@types/node": "^24.9.2",
21
+ "react": "^18.2.0",
22
+ "storybook": "^8.6.14",
23
+ "@tailwindcss/postcss": "^4.1.13",
24
+ "tsup": "^8.0.2",
25
+ "typescript": "^5.3.3",
26
+ "@xcelsior/design-system": "1.0.4",
27
+ "@xcelsior/ui-fields": "1.0.3"
28
+ },
29
+ "peerDependencies": {
30
+ "@xcelsior/design-system": "^1",
31
+ "@xcelsior/ui-fields": "^1",
32
+ "flowbite-react": "^0.12",
33
+ "react": "^18.2.0",
34
+ "react-dom": "^18.2.0",
35
+ "react-hook-form": "^7",
36
+ "@tanstack/react-query": "^5.24.1"
37
+ },
38
+ "dependencies": {
39
+ "@emoji-mart/react": "^1.1.1",
40
+ "axios": "^1.6.7",
41
+ "date-fns": "^3.3.1",
42
+ "react-markdown": "^9.0.1"
43
+ },
44
+ "exports": {
45
+ ".": {
46
+ "import": "./src/index.tsx",
47
+ "require": "./dist/index.js"
48
+ }
49
+ },
50
+ "scripts": {
51
+ "build": "tsup src/index.tsx --format esm,cjs --dts --external react",
52
+ "dev": "tsup src/index.tsx --format esm,cjs --watch --dts --external react",
53
+ "prepublish": "npm run build",
54
+ "lint": "biome check .",
55
+ "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
56
+ "storybook": "storybook dev -p 6007",
57
+ "build-storybook": "storybook build",
58
+ "predeploy": "npm run build-storybook",
59
+ "deploy": "aws s3 sync storybook-static/ s3://excelsior-ui-chat --delete"
60
+ }
61
+ }
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ plugins: {
3
+ '@tailwindcss/postcss': {},
4
+ },
5
+ };