shopify-chatbot-widget 0.9.7 → 0.9.8

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.
@@ -72,12 +72,7 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
72
72
  calendly,
73
73
  disableCheckout,
74
74
  isfetchingMessage,
75
-
76
75
  }) => {
77
-
78
-
79
-
80
-
81
76
  const bottomRef = useRef<HTMLDivElement>(null);
82
77
 
83
78
  useEffect(() => {
@@ -85,12 +80,10 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
85
80
 
86
81
  const timeout = setTimeout(() => {
87
82
  bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
88
- }, 150); // Slight delay ensures DOM settles in Shopify
83
+ }, 150);
89
84
 
90
85
  return () => clearTimeout(timeout);
91
86
  }, [messages]);
92
-
93
-
94
87
 
95
88
  if (isfetchingMessage || isLoading || !messages) {
96
89
  return (
@@ -105,31 +98,30 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
105
98
  }}
106
99
  >
107
100
  <div style={{backgroundColor:colorCode}} className="box-loader"></div>
108
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
109
- <div style={{backgroundColor:colorCode}} className="box-loader"></div>
101
+ <div style={{backgroundColor:colorCode}} className="box-loader"></div>
102
+ <div style={{backgroundColor:colorCode}} className="box-loader"></div>
110
103
  </div>
111
104
  );
112
105
  }
113
-
114
106
 
115
107
  return (
116
108
  <div className="chatbot-messages space-y-4">
117
- {messages.map((msg, i) => {
118
- if (msg.typing) {
119
- return (
120
- <div key={i} className="chat-msg typing">
121
- <Typing color={colorCode} />
122
- </div>
123
- );
124
- }
109
+ {messages.map((msg, i) => {
110
+ const isFirstMessage = i === 0 && messages.length === 1;
111
+
112
+ if (msg.typing) {
113
+ return (
114
+ <div key={i} className="chat-msg typing">
115
+ <Typing color={colorCode} />
116
+ </div>
117
+ );
118
+ }
125
119
 
126
- if (msg.isConnect){
127
- return(
128
- <SupportConnect sessionId={sessionId}/>
129
- )
130
- }
120
+ if (msg.isConnect) {
121
+ return <SupportConnect key={i} sessionId={sessionId} />;
122
+ }
131
123
 
132
- if (msg.isBookings){
124
+ if (msg.isBookings) {
133
125
  return (
134
126
  <div
135
127
  key={i}
@@ -142,15 +134,37 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
142
134
  <CalendlyEmbeds calendlyLink={calendly} />
143
135
  </div>
144
136
  );
145
- }
146
-
137
+ }
147
138
 
139
+ if (msg.role === 'assistant') {
140
+ if (Array.isArray(msg.content)) {
141
+ return (
142
+ <div
143
+ key={i}
144
+ className={`chat-msg assistant ${isFirstMessage ? 'first-message' : ''}`}
145
+ style={{ backgroundColor: 'transparent', padding: 0 }}
146
+ >
147
+ {msg.image_url && (
148
+ <img
149
+ src={msg.image_url}
150
+ alt="Chat image"
151
+ style={{ width: 200 }}
152
+ className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
153
+ />
154
+ )}
155
+ <ChatImage message={msg.content} />
156
+ <div className="flex justify-start">
157
+ <ChatImageTextAssist message={msg.content} />
158
+ </div>
159
+ </div>
160
+ );
161
+ }
148
162
 
149
- if (msg.role === 'assistant') {
150
- // Handle array content separately
151
- if (Array.isArray(msg.content)) {
152
163
  return (
153
- <div key={i} className="chat-msg assistant" style={{ backgroundColor: 'transparent', padding: 0 }}>
164
+ <div
165
+ key={i}
166
+ className={`chat-msg assistant ${isFirstMessage ? 'first-message' : ''}`}
167
+ >
154
168
  {msg.image_url && (
155
169
  <img
156
170
  src={msg.image_url}
@@ -159,183 +173,138 @@ const ChatMessages: React.FC<ChatMessagesProps> = ({
159
173
  className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
160
174
  />
161
175
  )}
162
- <ChatImage message={msg.content} />
163
- <div className="flex justify-start">
164
- <ChatImageTextAssist message={msg.content} />
165
- </div>
176
+
177
+ {userType === "shopify" ? (
178
+ <AssistantMessage
179
+ message={msg.content || msg.text || ""}
180
+ colorCode={colorCode}
181
+ disableCheckout={disableCheckout}
182
+ handleAddToCart={handleAddToCart}
183
+ handleRemoveFromCart={handleRemoveFromCart}
184
+ />
185
+ ) : userType === "salla" ? (
186
+ <SallaAsssiatnt
187
+ message={msg.content || msg.text || ""}
188
+ colorCode={colorCode}
189
+ handleAddToCart={handleAddToCart}
190
+ handleRemoveFromCart={handleRemoveFromCart}
191
+ />
192
+ ) : (
193
+ <ZidAssistantChatMessage
194
+ message={msg.content || msg.text || ""}
195
+ colorCode={colorCode}
196
+ handleAddToCart={handleAddToCart}
197
+ handleRemoveFromCart={handleRemoveFromCart}
198
+ />
199
+ )}
166
200
  </div>
167
201
  );
168
202
  }
169
203
 
170
-
171
-
204
+ if (msg?.audio_url) {
205
+ return (
206
+ <div
207
+ key={i}
208
+ className="chat-msg user"
209
+ style={{
210
+ backgroundColor: 'transparent',
211
+ padding: 0
212
+ }}
213
+ >
214
+ <div style={{ borderRadius: 12 }}>
215
+ <AudioVisualizer link={msg.audio_url} transcription={msg.content} colorCode={colorCode} />
216
+ </div>
217
+ <div className="mt-1 mr-2 flex items-end justify-end">
218
+ {msg.status === 'uploading' ? (
219
+ <Spin indicator={<LoadingOutlined spin />} size="small" />
220
+ ) : msg.status === 'delivered' ? (
221
+ <CheckCircleOutlined style={{ color: 'green' }} />
222
+ ) : msg.status === 'failed' ? (
223
+ <CloseCircleOutlined style={{ color: 'red' }} />
224
+ ) : null}
225
+ </div>
226
+ </div>
227
+ );
228
+ }
172
229
 
173
-
174
- return (
175
- <div key={i} className="chat-msg assistant">
176
- {msg.image_url && (
230
+ if (msg.image_url) {
231
+ return (
232
+ <div key={i} className="chat-msg user" style={{ backgroundColor: 'transparent' }}>
177
233
  <img
178
234
  src={msg.image_url}
179
235
  alt="Chat image"
180
236
  style={{ width: 200 }}
181
- className="rounded-md mt-2 max-w-xs border border-green-400 shadow-sm"
182
- />
183
- )}
184
-
185
- {userType === "shopify" ? (
186
- <AssistantMessage
187
- message={msg.content || msg.text || ""}
188
- colorCode={colorCode}
189
- disableCheckout={disableCheckout}
190
- handleAddToCart={handleAddToCart}
191
- handleRemoveFromCart={handleRemoveFromCart}
192
- />
193
- ) : userType === "salla" ? (
194
- <SallaAsssiatnt
195
- message={msg.content || msg.text || ""}
196
- colorCode={colorCode}
197
- handleAddToCart={handleAddToCart}
198
- handleRemoveFromCart={handleRemoveFromCart}
237
+ className={`rounded-md mt-2 max-w-xs ${
238
+ msg.content === 'user'
239
+ ? 'border border-gray-300 shadow-sm'
240
+ : 'border border-green-400 shadow-sm'
241
+ }`}
199
242
  />
200
- ) : (
201
- <ZidAssistantChatMessage
202
- message={msg.content || msg.text || ""}
203
- colorCode={colorCode}
204
- handleAddToCart={handleAddToCart}
205
- handleRemoveFromCart={handleRemoveFromCart}
206
- />
207
- )}
208
-
209
-
210
-
211
- </div>
212
- );
213
- }
214
-
215
-
216
-
217
- if(msg?.audio_url){
218
-
219
- return(
220
- <div
221
- className={`chat-msg user`}
222
- style={{
223
- backgroundColor:'transparent',
224
- padding:0
225
- }}
226
- >
227
-
228
- <div style={{
229
- borderRadius:12
230
- }}>
231
-
232
- <AudioVisualizer link={msg.audio_url} transcription={msg.content} colorCode={colorCode} />
233
-
234
- {/* Upload/Delivery Status */}
235
-
236
- </div>
237
-
238
- <div className="mt-1 mr-2 flex items-end justify-end">
239
- {msg.status === 'uploading' ? (
240
- <Spin indicator={<LoadingOutlined spin />} size="small" />
241
- ) : msg.status === 'delivered' ? (
242
- <CheckCircleOutlined style={{ color: 'green' }} />
243
- ) : msg.status === 'failed' ? (
244
- <CloseCircleOutlined style={{ color: 'red' }} />
245
- ) : null}
246
- </div>
247
- </div>
248
- )
243
+ <div className="mt-1 mr-2 flex items-end justify-end">
244
+ {msg.status === 'uploading' ? (
245
+ <Spin indicator={<LoadingOutlined spin />} size="small" />
246
+ ) : msg.status === 'delivered' ? (
247
+ <CheckCircleOutlined style={{ color: 'green' }} />
248
+ ) : msg.status === 'failed' ? (
249
+ <CloseCircleOutlined style={{ color: 'red' }} />
250
+ ) : null}
251
+ </div>
252
+ {msg.content && (
253
+ <div className="flex justify-end mt-2">
254
+ <div
255
+ style={{
256
+ color: 'white',
257
+ border: 1,
258
+ backgroundColor: colorCode,
259
+ paddingTop: 10,
260
+ paddingBottom: 10,
261
+ paddingInline: 14,
262
+ maxWidth: '85%',
263
+ lineHeight: 1.4,
264
+ borderRadius: '16px',
265
+ wordBreak: 'break-word',
266
+ }}
267
+ >
268
+ <span>{msg.content}</span>
269
+ </div>
270
+ </div>
271
+ )}
272
+ </div>
273
+ );
249
274
  }
250
-
251
275
 
252
- // ✅ IMAGE MESSAGE (non-assistant)
253
- if (msg.image_url) {
254
- return (
255
- <div key={i} className="chat-msg user" style={{ backgroundColor: 'transparent' }}>
256
- <img
257
- src={msg.image_url}
258
- alt="Chat image"
259
- style={{ width: 200 }}
260
- className={`rounded-md mt-2 max-w-xs ${
261
- msg.content === 'user'
262
- ? 'border border-gray-300 shadow-sm'
263
- : 'border border-green-400 shadow-sm'
264
- }`}
265
- />
266
-
267
- <div className="mt-1 mr-2 flex items-end justify-end">
268
- {msg.status === 'uploading' ? (
269
- <Spin indicator={<LoadingOutlined spin />} size="small" />
270
- ) : msg.status === 'delivered' ? (
271
- <CheckCircleOutlined style={{ color: 'green' }} />
272
- ) : msg.status === 'failed' ? (
273
- <CloseCircleOutlined style={{ color: 'red' }} />
274
- ) : null}
275
- </div>
276
-
277
- {msg.content && (
278
- <div className="flex justify-end mt-2">
279
- <div
280
- style={{
281
- color: 'white',
282
- border: 1,
283
- backgroundColor: colorCode,
284
- paddingTop: 10,
285
- paddingBottom: 10,
286
- paddingInline: 14,
287
- maxWidth: '85%',
288
- lineHeight: 1.4,
289
- borderRadius: '16px',
290
- wordBreak: 'break-word',
291
- }}
292
- >
293
- <span>{msg.content}</span>
276
+ if (Array.isArray(msg.content)) {
277
+ return (
278
+ <div key={i} className='chat-msg user' style={{backgroundColor:'transparent', padding:0}}>
279
+ <div style={{backgroundColor:'transparent', padding:0}}>
280
+ <ChatImage message={msg.content} />
281
+ </div>
282
+ <div className='flex justify-end'>
283
+ <ChatImageText message={msg.content} colorCode={colorCode} />
284
+ </div>
294
285
  </div>
295
- </div>
296
- )}
297
- </div>
298
- );
299
- }
300
-
286
+ );
287
+ }
301
288
 
302
- // ✅ MULTIPLE IMAGES (array)
303
- if (Array.isArray(msg.content)) {
304
- return (
305
- <div key={i} className='chat-msg user' style={{backgroundColor:'transparent',padding:0}}>
306
- <div style={{backgroundColor:'transparent',padding:0}}>
307
- <ChatImage message={msg.content} />
308
- </div>
309
- <div key={i} className='flex justify-end'>
310
- <ChatImageText message={msg.content} colorCode={colorCode} />
289
+ return (
290
+ <div
291
+ key={i}
292
+ className={`chat-msg ${msg.role}`}
293
+ style={
294
+ msg.role === 'user'
295
+ ? { backgroundColor: colorCode, color: '#fff' }
296
+ : {}
297
+ }
298
+ >
299
+ <AddToCartConfirmation message={msg.content} colorCode={colorCode}/>
311
300
  </div>
312
-
313
- </div>
314
- );
315
- }
316
-
317
-
318
- return (
319
- <div
320
- key={i}
321
- className={`chat-msg ${msg.role}`}
322
- style={
323
- msg.role === 'user'
324
- ? { backgroundColor: colorCode, color: '#fff' }
325
- : {}
326
- }
327
- >
328
-
329
- <AddToCartConfirmation message={msg.content} colorCode={colorCode}/>
330
-
331
- </div>
332
- );
333
- })}
334
- <AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
335
- <div ref={bottomRef} />
336
-
301
+ );
302
+ })}
303
+
304
+ <AgentStatusBanner status={agentStatus?.status} user={agentStatus?.user} />
305
+ <div ref={bottomRef} />
337
306
  </div>
338
307
  );
339
308
  };
340
309
 
341
- export default ChatMessages;
310
+ export default ChatMessages;
@@ -1,31 +1,134 @@
1
- .typing-boxes {
2
- display: flex;
1
+ /* ===== BASE TYPING INDICATOR ===== */
2
+ .typing-indicator {
3
+ display: inline-flex;
3
4
  gap: 6px;
4
5
  align-items: center;
5
6
  justify-content: flex-start;
7
+ padding: 8px 12px;
8
+ background: #eff0f1;
9
+ border-color: black;
10
+ border-radius: 18px;
11
+ width: fit-content;
6
12
  }
7
13
 
8
- .typing-boxes span {
9
- border-radius: 3px; /* keep boxy feel */
14
+ .typing-dot {
15
+ border-radius: 50%;
10
16
  display: inline-block;
11
- animation: pulseBox 0.8s infinite alternate;
12
17
  }
13
18
 
14
- .typing-boxes span:nth-child(2) {
19
+ /* ===== DOTS VARIANT (Default - Smooth bounce) ===== */
20
+ .typing-dots .typing-dot {
21
+ animation: bounceDot 1.4s infinite ease-in-out both;
22
+ }
23
+
24
+ .typing-dots .typing-dot:nth-child(1) {
25
+ animation-delay: -0.32s;
26
+ }
27
+
28
+ .typing-dots .typing-dot:nth-child(2) {
29
+ animation-delay: -0.16s;
30
+ }
31
+
32
+ @keyframes bounceDot {
33
+ 0%, 80%, 100% {
34
+ transform: translateY(0) scale(1);
35
+ opacity: 0.7;
36
+ }
37
+ 40% {
38
+ transform: translateY(-10px) scale(1.1);
39
+ opacity: 1;
40
+ }
41
+ }
42
+
43
+ /* ===== BOXES VARIANT (Pulsing squares) ===== */
44
+ .typing-boxes .typing-dot {
45
+ border-radius: 3px;
46
+ animation: pulseBox 1s infinite ease-in-out both;
47
+ }
48
+
49
+ .typing-boxes .typing-dot:nth-child(1) {
50
+ animation-delay: 0s;
51
+ }
52
+
53
+ .typing-boxes .typing-dot:nth-child(2) {
15
54
  animation-delay: 0.2s;
16
55
  }
17
56
 
18
- .typing-boxes span:nth-child(3) {
57
+ .typing-boxes .typing-dot:nth-child(3) {
19
58
  animation-delay: 0.4s;
20
59
  }
21
60
 
22
61
  @keyframes pulseBox {
23
- from {
24
- transform: scale(1);
62
+ 0%, 100% {
63
+ transform: scale(0.8);
25
64
  opacity: 0.5;
26
65
  }
27
- to {
28
- transform: scale(1.3);
66
+ 50% {
67
+ transform: scale(1.2);
68
+ opacity: 1;
69
+ }
70
+ }
71
+
72
+ /* ===== WAVE VARIANT (Wave motion) ===== */
73
+ .typing-wave .typing-dot {
74
+ animation: wave 1.2s infinite ease-in-out;
75
+ }
76
+
77
+ .typing-wave .typing-dot:nth-child(1) {
78
+ animation-delay: 0s;
79
+ }
80
+
81
+ .typing-wave .typing-dot:nth-child(2) {
82
+ animation-delay: 0.15s;
83
+ }
84
+
85
+ .typing-wave .typing-dot:nth-child(3) {
86
+ animation-delay: 0.3s;
87
+ }
88
+
89
+ @keyframes wave {
90
+ 0%, 60%, 100% {
91
+ transform: translateY(0);
92
+ opacity: 0.6;
93
+ }
94
+ 30% {
95
+ transform: translateY(-12px);
29
96
  opacity: 1;
30
97
  }
31
98
  }
99
+
100
+ /* ===== MINIMAL VARIANT (No background bubble) ===== */
101
+ .typing-indicator.minimal {
102
+ background: transparent;
103
+ padding: 0;
104
+ }
105
+
106
+ /* ===== DARK MODE SUPPORT ===== */
107
+ .typing-indicator.dark {
108
+ background: #374151;
109
+ }
110
+
111
+ /* ===== SIZE VARIANTS ===== */
112
+ .typing-indicator.small {
113
+ padding: 6px 10px;
114
+ gap: 4px;
115
+ }
116
+
117
+ .typing-indicator.large {
118
+ padding: 10px 16px;
119
+ gap: 8px;
120
+ }
121
+
122
+ /* ===== RESPONSIVE ===== */
123
+ @media (max-width: 600px) {
124
+ .typing-indicator {
125
+ padding: 6px 10px;
126
+ gap: 5px;
127
+ }
128
+ }
129
+
130
+ /* ===== SMOOTH PERFORMANCE ===== */
131
+ .typing-dot {
132
+ will-change: transform, opacity;
133
+ backface-visibility: hidden;
134
+ }
@@ -2,18 +2,32 @@ import React from 'react';
2
2
  import './Typing.css';
3
3
 
4
4
  interface TypingProps {
5
- color?: string; // optional, defaults to black
6
- size?: number; // optional, lets you control box size
5
+ color?: string;
6
+ size?: number;
7
+ variant?: 'dots' | 'boxes' | 'wave'; // Different animation styles
7
8
  }
8
9
 
9
- const Typing: React.FC<TypingProps> = ({ color, size = 5 }) => {
10
+ const Typing: React.FC<TypingProps> = ({
11
+ color = '#6b7280',
12
+ size = 8,
13
+ variant = 'dots'
14
+ }) => {
10
15
  return (
11
- <div className="typing-boxes">
12
- <span style={{ backgroundColor: color, width: size, height: size }}></span>
13
- <span style={{ backgroundColor: color, width: size, height: size }}></span>
14
- <span style={{ backgroundColor: color, width: size, height: size }}></span>
16
+ <div className={`typing-indicator typing-${variant}`}>
17
+ <span
18
+ className="typing-dot"
19
+ style={{ backgroundColor: color, width: size, height: size }}
20
+ />
21
+ <span
22
+ className="typing-dot"
23
+ style={{ backgroundColor: color, width: size, height: size }}
24
+ />
25
+ <span
26
+ className="typing-dot"
27
+ style={{ backgroundColor: color, width: size, height: size }}
28
+ />
15
29
  </div>
16
30
  );
17
31
  };
18
32
 
19
- export default Typing;
33
+ export default Typing;