@xcelsior/ui-chat 1.0.4 → 1.0.6
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/.turbo/turbo-lint.log +5 -0
- package/CHANGELOG.md +6 -0
- package/README.md +82 -0
- package/dist/index.d.mts +337 -0
- package/dist/index.d.ts +337 -0
- package/dist/index.js +1730 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1680 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/Chat.tsx +60 -1
- package/src/components/ChatWidget.tsx +13 -37
- package/src/components/PreChatForm.tsx +65 -4
- package/src/hooks/useChatWidgetState.ts +68 -0
- package/src/index.tsx +8 -3
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -62,6 +62,88 @@ function App() {
|
|
|
62
62
|
}
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
+
### Chat Widget States (Bubble Mode)
|
|
66
|
+
|
|
67
|
+
The chat widget supports three states in popover mode:
|
|
68
|
+
|
|
69
|
+
- **`open`**: Fully open with chat interface visible
|
|
70
|
+
- **`minimized`**: Show bubble button only (default)
|
|
71
|
+
- **`closed`**: Fully hidden, no bubble visible
|
|
72
|
+
|
|
73
|
+
#### Uncontrolled Mode (Default)
|
|
74
|
+
|
|
75
|
+
By default, the widget starts in `minimized` state and manages its own state:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { ChatWidget } from '@xcelsior/ui-chat';
|
|
79
|
+
|
|
80
|
+
function App() {
|
|
81
|
+
return (
|
|
82
|
+
<ChatWidget
|
|
83
|
+
config={chatConfig}
|
|
84
|
+
// Starts minimized by default
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
You can also set a different default state:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
<ChatWidget
|
|
94
|
+
config={chatConfig}
|
|
95
|
+
defaultState="open" // or "minimized" or "closed"
|
|
96
|
+
/>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Controlled Mode
|
|
100
|
+
|
|
101
|
+
Control the widget state externally:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { ChatWidget, ChatWidgetState } from '@xcelsior/ui-chat';
|
|
105
|
+
import { useState } from 'react';
|
|
106
|
+
|
|
107
|
+
function App() {
|
|
108
|
+
const [chatState, setChatState] = useState<ChatWidgetState>('minimized');
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<button onClick={() => setChatState('open')}>
|
|
113
|
+
Open Chat
|
|
114
|
+
</button>
|
|
115
|
+
<button onClick={() => setChatState('minimized')}>
|
|
116
|
+
Minimize Chat
|
|
117
|
+
</button>
|
|
118
|
+
<button onClick={() => setChatState('closed')}>
|
|
119
|
+
Close Chat
|
|
120
|
+
</button>
|
|
121
|
+
|
|
122
|
+
<ChatWidget
|
|
123
|
+
config={chatConfig}
|
|
124
|
+
state={chatState}
|
|
125
|
+
onStateChange={setChatState}
|
|
126
|
+
/>
|
|
127
|
+
</>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
#### Listen to State Changes (Semi-controlled)
|
|
133
|
+
|
|
134
|
+
You can listen to state changes while keeping the widget uncontrolled:
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<ChatWidget
|
|
138
|
+
config={chatConfig}
|
|
139
|
+
defaultState="minimized"
|
|
140
|
+
onStateChange={(state) => {
|
|
141
|
+
console.log('Chat state changed to:', state);
|
|
142
|
+
// Save to localStorage, analytics, etc.
|
|
143
|
+
}}
|
|
144
|
+
/>
|
|
145
|
+
```
|
|
146
|
+
|
|
65
147
|
### With File Upload
|
|
66
148
|
|
|
67
149
|
The file upload feature uses a presigned URL approach for secure, direct-to-S3 uploads:
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface IUser {
|
|
4
|
+
name: string;
|
|
5
|
+
email: string;
|
|
6
|
+
avatar?: string;
|
|
7
|
+
type: 'customer' | 'agent';
|
|
8
|
+
status?: 'online' | 'offline' | 'away' | 'busy';
|
|
9
|
+
}
|
|
10
|
+
type MessageType = 'text' | 'image' | 'file' | 'system';
|
|
11
|
+
type MessageStatus = 'sent' | 'delivered' | 'read';
|
|
12
|
+
interface IMessage {
|
|
13
|
+
id: string;
|
|
14
|
+
conversationId: string;
|
|
15
|
+
senderId: string;
|
|
16
|
+
senderType: 'customer' | 'agent' | 'system';
|
|
17
|
+
content: string;
|
|
18
|
+
messageType: MessageType;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
status: MessageStatus;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
type ConversationStatus = 'open' | 'pending' | 'closed' | 'archived';
|
|
24
|
+
type ConversationPriority = 'low' | 'medium' | 'high' | 'urgent';
|
|
25
|
+
type ConversationChannel = 'web' | 'mobile' | 'email';
|
|
26
|
+
interface IConversation {
|
|
27
|
+
id: string;
|
|
28
|
+
customerId: string;
|
|
29
|
+
assignedAgentId?: string;
|
|
30
|
+
status: ConversationStatus;
|
|
31
|
+
priority: ConversationPriority;
|
|
32
|
+
subject?: string;
|
|
33
|
+
channel: ConversationChannel;
|
|
34
|
+
tags?: string[];
|
|
35
|
+
createdAt: string;
|
|
36
|
+
updatedAt: string;
|
|
37
|
+
closedAt?: string;
|
|
38
|
+
lastMessageAt?: string;
|
|
39
|
+
messageCount?: number;
|
|
40
|
+
unreadCount?: number;
|
|
41
|
+
satisfaction?: 1 | 2 | 3 | 4 | 5;
|
|
42
|
+
metadata?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
interface IWebSocketMessage {
|
|
45
|
+
type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system';
|
|
46
|
+
data: any;
|
|
47
|
+
}
|
|
48
|
+
interface ISendMessageData {
|
|
49
|
+
conversationId: string;
|
|
50
|
+
content: string;
|
|
51
|
+
messageType?: MessageType;
|
|
52
|
+
}
|
|
53
|
+
interface ITypingData {
|
|
54
|
+
conversationId: string;
|
|
55
|
+
isTyping: boolean;
|
|
56
|
+
}
|
|
57
|
+
interface IReadMessageData {
|
|
58
|
+
messageId: string;
|
|
59
|
+
conversationId: string;
|
|
60
|
+
}
|
|
61
|
+
interface IFileUploadConfig {
|
|
62
|
+
uploadUrl: string;
|
|
63
|
+
maxFileSize?: number;
|
|
64
|
+
allowedTypes?: string[];
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
}
|
|
67
|
+
interface IUploadedFile {
|
|
68
|
+
url: string;
|
|
69
|
+
name: string;
|
|
70
|
+
size: number;
|
|
71
|
+
type: string;
|
|
72
|
+
markdown?: string;
|
|
73
|
+
}
|
|
74
|
+
interface IChatConfig {
|
|
75
|
+
websocketUrl: string;
|
|
76
|
+
conversationId?: string;
|
|
77
|
+
apiKey: string;
|
|
78
|
+
currentUser: IUser;
|
|
79
|
+
fileUpload?: IFileUploadConfig;
|
|
80
|
+
httpApiUrl?: string;
|
|
81
|
+
headers?: Record<string, string>;
|
|
82
|
+
enableEmoji?: boolean;
|
|
83
|
+
enableFileUpload?: boolean;
|
|
84
|
+
enableTypingIndicator?: boolean;
|
|
85
|
+
enableReadReceipts?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* For agents, set this to true to disable creating a WebSocket connection in the chat widget.
|
|
88
|
+
* Agents should have a global WebSocket connection instead.
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
disableWebSocket?: boolean;
|
|
92
|
+
onMessageSent?: (message: IMessage) => void;
|
|
93
|
+
onMessageReceived?: (message: IMessage) => void;
|
|
94
|
+
onConversationChange?: (conversation: IConversation) => void;
|
|
95
|
+
onConnectionChange?: (connected: boolean) => void;
|
|
96
|
+
onError?: (error: Error) => void;
|
|
97
|
+
toast?: {
|
|
98
|
+
success: (message: string) => void;
|
|
99
|
+
error: (message: string) => void;
|
|
100
|
+
info: (message: string) => void;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
interface IApiResponse<T> {
|
|
104
|
+
success: boolean;
|
|
105
|
+
data?: T;
|
|
106
|
+
error?: {
|
|
107
|
+
code: string;
|
|
108
|
+
message: string;
|
|
109
|
+
};
|
|
110
|
+
pagination?: {
|
|
111
|
+
nextPageToken?: string;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface ChatWidgetProps {
|
|
116
|
+
config: IChatConfig;
|
|
117
|
+
className?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Variant of the chat widget:
|
|
120
|
+
* - 'popover': Fixed positioned floating widget (default)
|
|
121
|
+
* - 'fullPage': Full page layout that fills the container
|
|
122
|
+
*/
|
|
123
|
+
variant?: 'popover' | 'fullPage';
|
|
124
|
+
/**
|
|
125
|
+
* External WebSocket connection (for agents with global connection)
|
|
126
|
+
*/
|
|
127
|
+
externalWebSocket?: WebSocket | null;
|
|
128
|
+
/**
|
|
129
|
+
* Callback when user wants to minimize the widget
|
|
130
|
+
*/
|
|
131
|
+
onMinimize?: () => void;
|
|
132
|
+
/**
|
|
133
|
+
* Callback when user wants to close the widget
|
|
134
|
+
*/
|
|
135
|
+
onClose?: () => void;
|
|
136
|
+
}
|
|
137
|
+
declare function ChatWidget({ config, className, variant, externalWebSocket, onMinimize, onClose, }: ChatWidgetProps): react_jsx_runtime.JSX.Element;
|
|
138
|
+
|
|
139
|
+
type ChatWidgetState = 'open' | 'minimized' | 'closed' | 'undefined';
|
|
140
|
+
interface UseChatWidgetStateOptions {
|
|
141
|
+
/**
|
|
142
|
+
* Controlled state. When provided, the component is controlled.
|
|
143
|
+
*/
|
|
144
|
+
state?: ChatWidgetState;
|
|
145
|
+
/**
|
|
146
|
+
* Default state for uncontrolled mode
|
|
147
|
+
* @default 'minimized'
|
|
148
|
+
*/
|
|
149
|
+
defaultState?: ChatWidgetState;
|
|
150
|
+
/**
|
|
151
|
+
* Callback when state changes
|
|
152
|
+
*/
|
|
153
|
+
onStateChange?: (state: ChatWidgetState) => void;
|
|
154
|
+
}
|
|
155
|
+
interface UseChatWidgetStateReturn {
|
|
156
|
+
/**
|
|
157
|
+
* Current state of the widget
|
|
158
|
+
*/
|
|
159
|
+
currentState: ChatWidgetState;
|
|
160
|
+
/**
|
|
161
|
+
* Function to update the state
|
|
162
|
+
*/
|
|
163
|
+
setState: (newState: ChatWidgetState) => void;
|
|
164
|
+
/**
|
|
165
|
+
* Whether the state is controlled
|
|
166
|
+
*/
|
|
167
|
+
isControlled: boolean;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Hook to manage chat widget state (controlled vs uncontrolled)
|
|
171
|
+
* Encapsulates the logic for handling both controlled and uncontrolled state patterns
|
|
172
|
+
*/
|
|
173
|
+
declare function useChatWidgetState({ state: controlledState, defaultState, onStateChange, }: UseChatWidgetStateOptions): UseChatWidgetStateReturn;
|
|
174
|
+
|
|
175
|
+
interface ChatWidgetWrapperProps {
|
|
176
|
+
/**
|
|
177
|
+
* Base configuration for the chat widget (without user info and conversationId)
|
|
178
|
+
*/
|
|
179
|
+
config: Omit<IChatConfig, 'currentUser' | 'conversationId' | 'userId'> & {
|
|
180
|
+
currentUser?: Partial<IUser>;
|
|
181
|
+
conversationId?: string;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Custom className for the wrapper
|
|
185
|
+
*/
|
|
186
|
+
className?: string;
|
|
187
|
+
/**
|
|
188
|
+
* Storage key prefix for persisting user data
|
|
189
|
+
* Defaults to 'xcelsior_chat'
|
|
190
|
+
*/
|
|
191
|
+
storageKeyPrefix?: string;
|
|
192
|
+
/**
|
|
193
|
+
* Callback when user submits the pre-chat form
|
|
194
|
+
*/
|
|
195
|
+
onPreChatSubmit?: (user: IUser) => void;
|
|
196
|
+
/**
|
|
197
|
+
* Controlled state. When provided, the component is controlled.
|
|
198
|
+
* - 'open': Fully open with chat interface
|
|
199
|
+
* - 'minimized': Show bubble button only
|
|
200
|
+
* - 'closed': Fully hidden
|
|
201
|
+
*/
|
|
202
|
+
state?: ChatWidgetState;
|
|
203
|
+
/**
|
|
204
|
+
* Default state for uncontrolled mode
|
|
205
|
+
* @default 'minimized'
|
|
206
|
+
*/
|
|
207
|
+
defaultState?: ChatWidgetState;
|
|
208
|
+
/**
|
|
209
|
+
* Callback when state changes
|
|
210
|
+
*/
|
|
211
|
+
onStateChange?: (state: ChatWidgetState) => void;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Chat component that handles:
|
|
215
|
+
* - Automatic conversation ID generation
|
|
216
|
+
* - Pre-chat form for collecting user information
|
|
217
|
+
* - Session persistence in localStorage
|
|
218
|
+
*/
|
|
219
|
+
declare function Chat({ config, className, storageKeyPrefix, onPreChatSubmit, state, defaultState, onStateChange, }: ChatWidgetWrapperProps): react_jsx_runtime.JSX.Element | null;
|
|
220
|
+
|
|
221
|
+
interface ChatHeaderProps {
|
|
222
|
+
agent?: IUser;
|
|
223
|
+
onClose?: () => void;
|
|
224
|
+
onMinimize?: () => void;
|
|
225
|
+
}
|
|
226
|
+
declare function ChatHeader({ agent, onClose, onMinimize }: ChatHeaderProps): react_jsx_runtime.JSX.Element;
|
|
227
|
+
|
|
228
|
+
interface UseFileUploadReturn {
|
|
229
|
+
uploadFile: (file: File) => Promise<IUploadedFile | null>;
|
|
230
|
+
isUploading: boolean;
|
|
231
|
+
uploadProgress: number;
|
|
232
|
+
error: Error | null;
|
|
233
|
+
canUpload: boolean;
|
|
234
|
+
}
|
|
235
|
+
declare function useFileUpload(apiKey: string, config?: IFileUploadConfig): UseFileUploadReturn;
|
|
236
|
+
|
|
237
|
+
interface ChatInputProps {
|
|
238
|
+
onSend: (message: string) => void;
|
|
239
|
+
onTyping?: (isTyping: boolean) => void;
|
|
240
|
+
config: IChatConfig;
|
|
241
|
+
fileUpload: UseFileUploadReturn;
|
|
242
|
+
disabled?: boolean;
|
|
243
|
+
}
|
|
244
|
+
declare function ChatInput({ onSend, onTyping, config, fileUpload, disabled, }: ChatInputProps): react_jsx_runtime.JSX.Element;
|
|
245
|
+
|
|
246
|
+
interface MessageItemProps {
|
|
247
|
+
message: IMessage;
|
|
248
|
+
currentUser: IUser;
|
|
249
|
+
showAvatar?: boolean;
|
|
250
|
+
showTimestamp?: boolean;
|
|
251
|
+
}
|
|
252
|
+
declare function MessageItem({ message, currentUser, showAvatar, showTimestamp, }: MessageItemProps): react_jsx_runtime.JSX.Element;
|
|
253
|
+
|
|
254
|
+
interface MessageListProps {
|
|
255
|
+
messages: IMessage[];
|
|
256
|
+
currentUser: IUser;
|
|
257
|
+
isLoading?: boolean;
|
|
258
|
+
isTyping?: boolean;
|
|
259
|
+
typingUser?: string;
|
|
260
|
+
autoScroll?: boolean;
|
|
261
|
+
onLoadMore?: () => void;
|
|
262
|
+
hasMore?: boolean;
|
|
263
|
+
isLoadingMore?: boolean;
|
|
264
|
+
}
|
|
265
|
+
declare function MessageList({ messages, currentUser, isLoading, isTyping, typingUser, autoScroll, onLoadMore, hasMore, isLoadingMore, }: MessageListProps): react_jsx_runtime.JSX.Element;
|
|
266
|
+
|
|
267
|
+
interface TypingIndicatorProps {
|
|
268
|
+
isTyping: boolean;
|
|
269
|
+
userName?: string;
|
|
270
|
+
}
|
|
271
|
+
declare function TypingIndicator({ isTyping, userName }: TypingIndicatorProps): react_jsx_runtime.JSX.Element | null;
|
|
272
|
+
|
|
273
|
+
interface PreChatFormProps {
|
|
274
|
+
onSubmit: (name: string, email: string) => void;
|
|
275
|
+
className?: string;
|
|
276
|
+
initialName?: string;
|
|
277
|
+
initialEmail?: string;
|
|
278
|
+
/**
|
|
279
|
+
* Callback when user wants to minimize the form
|
|
280
|
+
*/
|
|
281
|
+
onMinimize: () => void;
|
|
282
|
+
/**
|
|
283
|
+
* Callback when user wants to close the form
|
|
284
|
+
*/
|
|
285
|
+
onClose: () => void;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* PreChatForm component for collecting user information before starting chat
|
|
289
|
+
*/
|
|
290
|
+
declare function PreChatForm({ onSubmit, className, initialName, initialEmail, onMinimize, onClose, }: PreChatFormProps): react_jsx_runtime.JSX.Element;
|
|
291
|
+
|
|
292
|
+
interface UseWebSocketReturn {
|
|
293
|
+
isConnected: boolean;
|
|
294
|
+
sendMessage: (action: string, data: any) => void;
|
|
295
|
+
lastMessage: IWebSocketMessage | null;
|
|
296
|
+
error: Error | null;
|
|
297
|
+
reconnect: () => void;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Hook for WebSocket connection in chat widget.
|
|
301
|
+
* Can use an external WebSocket connection (for agents) via the externalWebSocket prop.
|
|
302
|
+
*/
|
|
303
|
+
declare function useWebSocket(config: IChatConfig, externalWebSocket?: WebSocket | null): UseWebSocketReturn;
|
|
304
|
+
|
|
305
|
+
interface UseMessagesReturn {
|
|
306
|
+
messages: IMessage[];
|
|
307
|
+
addMessage: (message: IMessage) => void;
|
|
308
|
+
updateMessageStatus: (messageId: string, status: IMessage['status']) => void;
|
|
309
|
+
clearMessages: () => void;
|
|
310
|
+
isLoading: boolean;
|
|
311
|
+
error: Error | null;
|
|
312
|
+
loadMore: () => Promise<void>;
|
|
313
|
+
hasMore: boolean;
|
|
314
|
+
isLoadingMore: boolean;
|
|
315
|
+
}
|
|
316
|
+
declare function useMessages(websocket: UseWebSocketReturn, config: IChatConfig): UseMessagesReturn;
|
|
317
|
+
|
|
318
|
+
interface UseTypingIndicatorReturn {
|
|
319
|
+
isTyping: boolean;
|
|
320
|
+
typingUsers: string[];
|
|
321
|
+
}
|
|
322
|
+
declare function useTypingIndicator(websocket: UseWebSocketReturn): UseTypingIndicatorReturn;
|
|
323
|
+
|
|
324
|
+
interface FetchMessagesParams {
|
|
325
|
+
conversationId: string;
|
|
326
|
+
limit?: number;
|
|
327
|
+
pageToken?: string;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Fetch messages for a conversation from the REST API
|
|
331
|
+
*/
|
|
332
|
+
declare function fetchMessages(baseUrl: string, params: FetchMessagesParams, headers?: Record<string, string>): Promise<{
|
|
333
|
+
data: IMessage[];
|
|
334
|
+
nextPageToken: string | undefined;
|
|
335
|
+
}>;
|
|
336
|
+
|
|
337
|
+
export { Chat, ChatHeader, ChatInput, ChatWidget, type ChatWidgetProps, type ChatWidgetState, type ConversationChannel, type ConversationPriority, type ConversationStatus, type IApiResponse, type IChatConfig, type IConversation, type IFileUploadConfig, type IMessage, type IReadMessageData, type ISendMessageData, type ITypingData, type IUploadedFile, type IUser, type IWebSocketMessage, MessageItem, MessageList, type MessageStatus, type MessageType, PreChatForm, TypingIndicator, type UseChatWidgetStateOptions, type UseChatWidgetStateReturn, fetchMessages, useChatWidgetState, useFileUpload, useMessages, useTypingIndicator, useWebSocket };
|