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.
- package/dist/jaweb-chatbot.css +1 -1
- package/dist/jaweb-chatbot.iife.js +380 -133
- package/package.json +1 -1
- package/src/Chatbot/Chatbot.css +82 -171
- package/src/Chatbot/components/ChatInput.css +426 -114
- package/src/Chatbot/components/ChatInput.tsx +313 -219
- package/src/Chatbot/components/Messages/ChatMessages.css +17 -1
- package/src/Chatbot/components/Messages/ChatMessages.tsx +161 -192
- package/src/Chatbot/components/Messages/Typing.css +114 -11
- package/src/Chatbot/components/Messages/Typing.tsx +22 -8
- package/src/Chatbot/components/Messenger/Messenger.tsx +26 -27
- package/src/Chatbot/components/Sessions/RenderList.tsx +445 -141
- package/src/hooks/useChatbotAPI.tsx +2 -16
|
@@ -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);
|
|
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
|
|
109
|
-
<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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
)
|
|
130
|
-
}
|
|
120
|
+
if (msg.isConnect) {
|
|
121
|
+
return <SupportConnect key={i} sessionId={sessionId} />;
|
|
122
|
+
}
|
|
131
123
|
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
<
|
|
165
|
-
|
|
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
|
-
|
|
175
|
-
|
|
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=
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
</div>
|
|
298
|
-
);
|
|
299
|
-
}
|
|
300
|
-
|
|
286
|
+
);
|
|
287
|
+
}
|
|
301
288
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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-
|
|
9
|
-
border-radius:
|
|
14
|
+
.typing-dot {
|
|
15
|
+
border-radius: 50%;
|
|
10
16
|
display: inline-block;
|
|
11
|
-
animation: pulseBox 0.8s infinite alternate;
|
|
12
17
|
}
|
|
13
18
|
|
|
14
|
-
|
|
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
|
|
57
|
+
.typing-boxes .typing-dot:nth-child(3) {
|
|
19
58
|
animation-delay: 0.4s;
|
|
20
59
|
}
|
|
21
60
|
|
|
22
61
|
@keyframes pulseBox {
|
|
23
|
-
|
|
24
|
-
transform: scale(
|
|
62
|
+
0%, 100% {
|
|
63
|
+
transform: scale(0.8);
|
|
25
64
|
opacity: 0.5;
|
|
26
65
|
}
|
|
27
|
-
|
|
28
|
-
transform: scale(1.
|
|
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;
|
|
6
|
-
size?: number;
|
|
5
|
+
color?: string;
|
|
6
|
+
size?: number;
|
|
7
|
+
variant?: 'dots' | 'boxes' | 'wave'; // Different animation styles
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
const Typing: React.FC<TypingProps> = ({
|
|
10
|
+
const Typing: React.FC<TypingProps> = ({
|
|
11
|
+
color = '#6b7280',
|
|
12
|
+
size = 8,
|
|
13
|
+
variant = 'dots'
|
|
14
|
+
}) => {
|
|
10
15
|
return (
|
|
11
|
-
<div className=
|
|
12
|
-
<span
|
|
13
|
-
|
|
14
|
-
|
|
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;
|