@customerhero/react 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,896 @@
1
+ // src/components/chat-widget.tsx
2
+ import { useEffect as useEffect7, useRef as useRef4 } from "react";
3
+
4
+ // src/context.tsx
5
+ import {
6
+ createContext,
7
+ useContext,
8
+ useEffect,
9
+ useRef
10
+ } from "react";
11
+ import {
12
+ CustomerHeroChat
13
+ } from "@customerhero/js";
14
+ import { jsx } from "react/jsx-runtime";
15
+ var CustomerHeroContext = createContext(null);
16
+ function CustomerHeroProvider({
17
+ children,
18
+ ...config
19
+ }) {
20
+ const clientRef = useRef(null);
21
+ if (!clientRef.current) {
22
+ clientRef.current = new CustomerHeroChat(config);
23
+ }
24
+ useEffect(() => {
25
+ clientRef.current?.fetchConfig();
26
+ }, []);
27
+ return /* @__PURE__ */ jsx(CustomerHeroContext.Provider, { value: clientRef.current, children });
28
+ }
29
+ function useCustomerHeroClient() {
30
+ const client = useContext(CustomerHeroContext);
31
+ if (!client) {
32
+ throw new Error(
33
+ "useCustomerHeroClient must be used within a <CustomerHeroProvider>"
34
+ );
35
+ }
36
+ return client;
37
+ }
38
+
39
+ // src/components/chat-bubble.tsx
40
+ import { useEffect as useEffect3, useState as useState2 } from "react";
41
+
42
+ // src/use-chat.ts
43
+ import { useCallback, useSyncExternalStore } from "react";
44
+ function useChat() {
45
+ const client = useCustomerHeroClient();
46
+ const state = useSyncExternalStore(
47
+ useCallback((cb) => client.subscribe(cb), [client]),
48
+ () => client.getState(),
49
+ () => client.getState()
50
+ );
51
+ return {
52
+ ...state,
53
+ t: client.t,
54
+ sendMessage: useCallback(
55
+ (message) => client.sendMessage(message),
56
+ [client]
57
+ ),
58
+ rateMessage: useCallback(
59
+ (messageId, rating) => client.rateMessage(messageId, rating),
60
+ [client]
61
+ ),
62
+ toggle: useCallback(() => client.toggle(), [client]),
63
+ open: useCallback(() => client.open(), [client]),
64
+ close: useCallback(() => client.close(), [client]),
65
+ reset: useCallback(() => client.reset(), [client]),
66
+ identify: useCallback(
67
+ (payload) => client.identify(payload),
68
+ [client]
69
+ )
70
+ };
71
+ }
72
+
73
+ // src/use-reduced-motion.ts
74
+ import { useEffect as useEffect2, useState } from "react";
75
+ function useReducedMotion() {
76
+ const [reduced, setReduced] = useState(() => {
77
+ if (typeof window === "undefined") return false;
78
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
79
+ });
80
+ useEffect2(() => {
81
+ const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
82
+ const handler = (e) => setReduced(e.matches);
83
+ mq.addEventListener("change", handler);
84
+ return () => mq.removeEventListener("change", handler);
85
+ }, []);
86
+ return reduced;
87
+ }
88
+
89
+ // src/components/chat-bubble.tsx
90
+ import { jsx as jsx2 } from "react/jsx-runtime";
91
+ function ChatBubble() {
92
+ const { toggle, config, t } = useChat();
93
+ const reduced = useReducedMotion();
94
+ const [mounted, setMounted] = useState2(false);
95
+ useEffect3(() => {
96
+ const id = requestAnimationFrame(() => setMounted(true));
97
+ return () => cancelAnimationFrame(id);
98
+ }, []);
99
+ const visible = mounted;
100
+ const style = {
101
+ position: "fixed",
102
+ bottom: 20,
103
+ [config.position === "bottom-left" ? "left" : "right"]: 20,
104
+ width: 56,
105
+ height: 56,
106
+ borderRadius: "50%",
107
+ background: config.primaryColor,
108
+ color: "white",
109
+ display: "flex",
110
+ alignItems: "center",
111
+ justifyContent: "center",
112
+ cursor: "pointer",
113
+ boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
114
+ zIndex: 99999,
115
+ border: "none",
116
+ padding: 0,
117
+ opacity: visible ? 1 : 0,
118
+ transform: visible ? "scale(1)" : "scale(0.6)",
119
+ transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease",
120
+ pointerEvents: visible ? "auto" : "none"
121
+ };
122
+ return /* @__PURE__ */ jsx2(
123
+ "button",
124
+ {
125
+ onClick: toggle,
126
+ style,
127
+ "aria-label": t("open_chat"),
128
+ onMouseEnter: (e) => {
129
+ if (!reduced) e.currentTarget.style.transform = "scale(1.1)";
130
+ },
131
+ onMouseLeave: (e) => {
132
+ if (!reduced) e.currentTarget.style.transform = "scale(1)";
133
+ },
134
+ children: /* @__PURE__ */ jsx2(
135
+ "svg",
136
+ {
137
+ width: "24",
138
+ height: "24",
139
+ viewBox: "0 0 24 24",
140
+ fill: "none",
141
+ stroke: "currentColor",
142
+ strokeWidth: "2",
143
+ strokeLinecap: "round",
144
+ strokeLinejoin: "round",
145
+ children: /* @__PURE__ */ jsx2("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
146
+ }
147
+ )
148
+ }
149
+ );
150
+ }
151
+
152
+ // src/components/chat-window.tsx
153
+ import { useEffect as useEffect6, useState as useState6 } from "react";
154
+
155
+ // src/components/chat-header.tsx
156
+ import { useState as useState3, useEffect as useEffect4, useRef as useRef2 } from "react";
157
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
158
+ function ChatHeader() {
159
+ const { config, close, reset, t } = useChat();
160
+ const reduced = useReducedMotion();
161
+ const [menuOpen, setMenuOpen] = useState3(false);
162
+ const menuRef = useRef2(null);
163
+ useEffect4(() => {
164
+ if (!menuOpen) return;
165
+ const handleClick = (e) => {
166
+ if (menuRef.current && !menuRef.current.contains(e.target)) {
167
+ setMenuOpen(false);
168
+ }
169
+ };
170
+ document.addEventListener("mousedown", handleClick);
171
+ return () => document.removeEventListener("mousedown", handleClick);
172
+ }, [menuOpen]);
173
+ const headerStyle = {
174
+ background: config.primaryColor,
175
+ padding: 16,
176
+ display: "flex",
177
+ alignItems: "center",
178
+ gap: 12
179
+ };
180
+ const avatarStyle = {
181
+ width: 36,
182
+ height: 36,
183
+ borderRadius: "50%",
184
+ background: "rgba(255,255,255,0.2)",
185
+ display: "flex",
186
+ alignItems: "center",
187
+ justifyContent: "center",
188
+ flexShrink: 0
189
+ };
190
+ const titleStyle = {
191
+ fontSize: 14,
192
+ fontWeight: 600,
193
+ color: "white",
194
+ margin: 0
195
+ };
196
+ const subtitleStyle = {
197
+ fontSize: 11,
198
+ color: "rgba(255,255,255,0.7)",
199
+ margin: 0
200
+ };
201
+ const headerButtonStyle = {
202
+ background: "none",
203
+ border: "none",
204
+ color: "white",
205
+ cursor: "pointer",
206
+ opacity: 0.7,
207
+ padding: 4
208
+ };
209
+ const menuStyle = {
210
+ position: "absolute",
211
+ top: "100%",
212
+ right: 0,
213
+ marginTop: 4,
214
+ background: "white",
215
+ borderRadius: 8,
216
+ boxShadow: "0 4px 16px rgba(0,0,0,0.15)",
217
+ minWidth: 180,
218
+ overflow: "hidden",
219
+ zIndex: 10,
220
+ transformOrigin: "top right",
221
+ animation: reduced ? "none" : "ch-menu-in 0.15s ease"
222
+ };
223
+ const menuItemStyle = {
224
+ display: "flex",
225
+ alignItems: "center",
226
+ gap: 8,
227
+ width: "100%",
228
+ padding: "10px 14px",
229
+ border: "none",
230
+ background: "none",
231
+ cursor: "pointer",
232
+ fontSize: 13,
233
+ color: "#333",
234
+ textAlign: "left",
235
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
236
+ };
237
+ return /* @__PURE__ */ jsxs("div", { style: headerStyle, children: [
238
+ /* @__PURE__ */ jsx3("div", { style: avatarStyle, children: config.avatarUrl ? /* @__PURE__ */ jsx3(
239
+ "img",
240
+ {
241
+ src: config.avatarUrl,
242
+ alt: "",
243
+ style: { width: 36, height: 36, borderRadius: "50%" }
244
+ }
245
+ ) : /* @__PURE__ */ jsx3(
246
+ "svg",
247
+ {
248
+ width: "18",
249
+ height: "18",
250
+ viewBox: "0 0 24 24",
251
+ fill: "none",
252
+ stroke: "white",
253
+ strokeWidth: "2",
254
+ strokeLinecap: "round",
255
+ strokeLinejoin: "round",
256
+ children: /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" })
257
+ }
258
+ ) }),
259
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
260
+ /* @__PURE__ */ jsx3("h3", { style: titleStyle, children: config.title }),
261
+ /* @__PURE__ */ jsx3("p", { style: subtitleStyle, children: t("online") })
262
+ ] }),
263
+ /* @__PURE__ */ jsxs("div", { ref: menuRef, style: { position: "relative" }, children: [
264
+ /* @__PURE__ */ jsx3(
265
+ "button",
266
+ {
267
+ onClick: () => setMenuOpen(!menuOpen),
268
+ style: headerButtonStyle,
269
+ "aria-label": t("menu"),
270
+ onMouseEnter: (e) => {
271
+ e.currentTarget.style.opacity = "1";
272
+ },
273
+ onMouseLeave: (e) => {
274
+ e.currentTarget.style.opacity = "0.7";
275
+ },
276
+ children: /* @__PURE__ */ jsxs(
277
+ "svg",
278
+ {
279
+ width: "18",
280
+ height: "18",
281
+ viewBox: "0 0 24 24",
282
+ fill: "none",
283
+ stroke: "currentColor",
284
+ strokeWidth: "2",
285
+ strokeLinecap: "round",
286
+ strokeLinejoin: "round",
287
+ children: [
288
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "5", r: "1" }),
289
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "1" }),
290
+ /* @__PURE__ */ jsx3("circle", { cx: "12", cy: "19", r: "1" })
291
+ ]
292
+ }
293
+ )
294
+ }
295
+ ),
296
+ menuOpen && /* @__PURE__ */ jsxs("div", { style: menuStyle, children: [
297
+ /* @__PURE__ */ jsx3("style", { children: `@keyframes ch-menu-in {
298
+ from { opacity: 0; transform: scale(0.9); }
299
+ to { opacity: 1; transform: scale(1); }
300
+ }` }),
301
+ /* @__PURE__ */ jsxs(
302
+ "button",
303
+ {
304
+ style: menuItemStyle,
305
+ onClick: () => {
306
+ setMenuOpen(false);
307
+ reset();
308
+ },
309
+ onMouseEnter: (e) => {
310
+ e.currentTarget.style.background = "#f5f5f5";
311
+ },
312
+ onMouseLeave: (e) => {
313
+ e.currentTarget.style.background = "none";
314
+ },
315
+ children: [
316
+ /* @__PURE__ */ jsxs(
317
+ "svg",
318
+ {
319
+ width: "14",
320
+ height: "14",
321
+ viewBox: "0 0 24 24",
322
+ fill: "none",
323
+ stroke: "currentColor",
324
+ strokeWidth: "2",
325
+ strokeLinecap: "round",
326
+ strokeLinejoin: "round",
327
+ children: [
328
+ /* @__PURE__ */ jsx3("path", { d: "M12 20h9" }),
329
+ /* @__PURE__ */ jsx3("path", { d: "M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" })
330
+ ]
331
+ }
332
+ ),
333
+ t("new_conversation")
334
+ ]
335
+ }
336
+ )
337
+ ] })
338
+ ] }),
339
+ /* @__PURE__ */ jsx3(
340
+ "button",
341
+ {
342
+ onClick: close,
343
+ style: headerButtonStyle,
344
+ "aria-label": t("close_chat"),
345
+ onMouseEnter: (e) => {
346
+ e.currentTarget.style.opacity = "1";
347
+ },
348
+ onMouseLeave: (e) => {
349
+ e.currentTarget.style.opacity = "0.7";
350
+ },
351
+ children: /* @__PURE__ */ jsxs(
352
+ "svg",
353
+ {
354
+ width: "20",
355
+ height: "20",
356
+ viewBox: "0 0 24 24",
357
+ fill: "none",
358
+ stroke: "currentColor",
359
+ strokeWidth: "2",
360
+ children: [
361
+ /* @__PURE__ */ jsx3("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
362
+ /* @__PURE__ */ jsx3("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
363
+ ]
364
+ }
365
+ )
366
+ }
367
+ )
368
+ ] });
369
+ }
370
+
371
+ // src/components/chat-messages.tsx
372
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState4 } from "react";
373
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
374
+ function MessageRatingButtons({
375
+ messageId,
376
+ onRate,
377
+ primaryColor,
378
+ t,
379
+ reduced
380
+ }) {
381
+ const [rated, setRated] = useState4(null);
382
+ const handleRate = (rating) => {
383
+ setRated(rating);
384
+ onRate(messageId, rating);
385
+ };
386
+ const buttonStyle = (isRated) => ({
387
+ background: isRated ? `${primaryColor}11` : "none",
388
+ border: `1px solid ${isRated ? primaryColor : "#ddd"}`,
389
+ borderRadius: 4,
390
+ padding: "4px 6px",
391
+ cursor: rated ? "default" : "pointer",
392
+ color: isRated ? primaryColor : "#888",
393
+ display: "flex",
394
+ alignItems: "center",
395
+ justifyContent: "center",
396
+ transition: reduced ? "color 0.15s" : "all 0.15s",
397
+ transform: isRated && !reduced ? "scale(1.15)" : "scale(1)"
398
+ });
399
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 4, marginTop: 4 }, children: [
400
+ /* @__PURE__ */ jsx4(
401
+ "button",
402
+ {
403
+ onClick: () => handleRate("positive"),
404
+ disabled: rated !== null,
405
+ style: buttonStyle(rated === "positive"),
406
+ title: t("helpful"),
407
+ children: /* @__PURE__ */ jsxs2(
408
+ "svg",
409
+ {
410
+ width: "14",
411
+ height: "14",
412
+ viewBox: "0 0 24 24",
413
+ fill: "none",
414
+ stroke: "currentColor",
415
+ strokeWidth: "2",
416
+ strokeLinecap: "round",
417
+ strokeLinejoin: "round",
418
+ children: [
419
+ /* @__PURE__ */ jsx4("path", { d: "M7 10v12" }),
420
+ /* @__PURE__ */ jsx4("path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2h0a3.13 3.13 0 0 1 3 3.88Z" })
421
+ ]
422
+ }
423
+ )
424
+ }
425
+ ),
426
+ /* @__PURE__ */ jsx4(
427
+ "button",
428
+ {
429
+ onClick: () => handleRate("negative"),
430
+ disabled: rated !== null,
431
+ style: buttonStyle(rated === "negative"),
432
+ title: t("not_helpful"),
433
+ children: /* @__PURE__ */ jsxs2(
434
+ "svg",
435
+ {
436
+ width: "14",
437
+ height: "14",
438
+ viewBox: "0 0 24 24",
439
+ fill: "none",
440
+ stroke: "currentColor",
441
+ strokeWidth: "2",
442
+ strokeLinecap: "round",
443
+ strokeLinejoin: "round",
444
+ children: [
445
+ /* @__PURE__ */ jsx4("path", { d: "M17 14V2" }),
446
+ /* @__PURE__ */ jsx4("path", { d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22h0a3.13 3.13 0 0 1-3-3.88Z" })
447
+ ]
448
+ }
449
+ )
450
+ }
451
+ )
452
+ ] });
453
+ }
454
+ function AnimatedMessage({
455
+ children,
456
+ isUser,
457
+ animate,
458
+ reduced
459
+ }) {
460
+ const [visible, setVisible] = useState4(!animate);
461
+ useEffect5(() => {
462
+ if (animate && !reduced) {
463
+ const id = requestAnimationFrame(() => setVisible(true));
464
+ return () => cancelAnimationFrame(id);
465
+ }
466
+ }, [animate, reduced]);
467
+ const style = {
468
+ alignSelf: isUser ? "flex-end" : "flex-start",
469
+ maxWidth: "80%",
470
+ opacity: visible ? 1 : 0,
471
+ transform: visible ? "translateX(0)" : `translateX(${isUser ? "12px" : "-12px"})`,
472
+ transition: animate && !reduced ? "opacity 0.2s ease, transform 0.2s ease" : "none"
473
+ };
474
+ return /* @__PURE__ */ jsx4("div", { style, children });
475
+ }
476
+ function Message({
477
+ message,
478
+ config,
479
+ onRate,
480
+ hasConversation,
481
+ t,
482
+ animate,
483
+ reduced
484
+ }) {
485
+ const isUser = message.role === "user";
486
+ const bubbleStyle = {
487
+ padding: "10px 14px",
488
+ borderRadius: 16,
489
+ fontSize: 14,
490
+ lineHeight: 1.5,
491
+ wordBreak: "break-word",
492
+ ...isUser ? {
493
+ background: config.primaryColor,
494
+ color: "white",
495
+ borderBottomRightRadius: 4
496
+ } : {
497
+ background: "#f0f0f0",
498
+ color: config.textColor,
499
+ borderBottomLeftRadius: 4
500
+ }
501
+ };
502
+ return /* @__PURE__ */ jsxs2(AnimatedMessage, { isUser, animate, reduced, children: [
503
+ /* @__PURE__ */ jsx4("div", { style: bubbleStyle, children: message.content }),
504
+ message.role === "bot" && message.id && hasConversation && /* @__PURE__ */ jsx4(
505
+ MessageRatingButtons,
506
+ {
507
+ messageId: message.id,
508
+ onRate,
509
+ primaryColor: config.primaryColor,
510
+ t,
511
+ reduced
512
+ }
513
+ )
514
+ ] });
515
+ }
516
+ function TypingDots({ reduced }) {
517
+ const dotStyle = (delay) => ({
518
+ width: 6,
519
+ height: 6,
520
+ borderRadius: "50%",
521
+ background: "#999",
522
+ animation: reduced ? "none" : `ch-dot-pulse 1.2s ease-in-out ${delay}s infinite`
523
+ });
524
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
525
+ /* @__PURE__ */ jsx4("style", { children: `@keyframes ch-dot-pulse {
526
+ 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); }
527
+ 40% { opacity: 1; transform: scale(1); }
528
+ }` }),
529
+ /* @__PURE__ */ jsxs2(
530
+ "div",
531
+ {
532
+ style: {
533
+ alignSelf: "flex-start",
534
+ padding: "12px 16px",
535
+ borderRadius: 16,
536
+ borderBottomLeftRadius: 4,
537
+ background: "#f0f0f0",
538
+ display: "flex",
539
+ gap: 4,
540
+ alignItems: "center"
541
+ },
542
+ children: [
543
+ /* @__PURE__ */ jsx4("div", { style: dotStyle(0) }),
544
+ /* @__PURE__ */ jsx4("div", { style: dotStyle(0.2) }),
545
+ /* @__PURE__ */ jsx4("div", { style: dotStyle(0.4) })
546
+ ]
547
+ }
548
+ )
549
+ ] });
550
+ }
551
+ function ChatMessages() {
552
+ const { messages, isLoading, error, config, conversationId, rateMessage, t } = useChat();
553
+ const reduced = useReducedMotion();
554
+ const messagesEndRef = useRef3(null);
555
+ const isFirstRender = useRef3(true);
556
+ const prevMessageCount = useRef3(0);
557
+ useEffect5(() => {
558
+ if (messagesEndRef.current) {
559
+ messagesEndRef.current.scrollIntoView({
560
+ behavior: isFirstRender.current || reduced ? "auto" : "smooth"
561
+ });
562
+ isFirstRender.current = false;
563
+ }
564
+ }, [messages, isLoading, reduced]);
565
+ const newStartIndex = isFirstRender.current ? messages.length : prevMessageCount.current;
566
+ useEffect5(() => {
567
+ prevMessageCount.current = messages.length;
568
+ }, [messages.length]);
569
+ const containerStyle = {
570
+ flex: 1,
571
+ overflowY: "auto",
572
+ padding: 16,
573
+ display: "flex",
574
+ flexDirection: "column",
575
+ gap: 12
576
+ };
577
+ return /* @__PURE__ */ jsxs2("div", { style: containerStyle, children: [
578
+ messages.map((msg, i) => /* @__PURE__ */ jsx4(
579
+ Message,
580
+ {
581
+ message: msg,
582
+ config,
583
+ onRate: rateMessage,
584
+ hasConversation: conversationId !== null,
585
+ t,
586
+ animate: i >= newStartIndex,
587
+ reduced
588
+ },
589
+ i
590
+ )),
591
+ isLoading && /* @__PURE__ */ jsx4(TypingDots, { reduced }),
592
+ error && /* @__PURE__ */ jsx4(
593
+ "div",
594
+ {
595
+ style: {
596
+ alignSelf: "flex-start",
597
+ padding: "10px 14px",
598
+ borderRadius: 16,
599
+ borderBottomLeftRadius: 4,
600
+ fontSize: 13,
601
+ background: "#fee2e2",
602
+ color: "#b91c1c"
603
+ },
604
+ children: error
605
+ }
606
+ ),
607
+ /* @__PURE__ */ jsx4("div", { ref: messagesEndRef })
608
+ ] });
609
+ }
610
+
611
+ // src/components/chat-suggestions.tsx
612
+ import { jsx as jsx5 } from "react/jsx-runtime";
613
+ function ChatSuggestions() {
614
+ const { messages, isLoading, config, sendMessage } = useChat();
615
+ const reduced = useReducedMotion();
616
+ const hasUserMessage = messages.some((m) => m.role === "user");
617
+ if (config.suggestedMessages.length === 0 || hasUserMessage || isLoading) {
618
+ return null;
619
+ }
620
+ const containerStyle = {
621
+ padding: "8px 16px",
622
+ display: "flex",
623
+ flexWrap: "wrap",
624
+ gap: 6,
625
+ justifyContent: "flex-end"
626
+ };
627
+ const chipStyle = {
628
+ background: "none",
629
+ border: "1px solid #e0e0e0",
630
+ borderRadius: 20,
631
+ padding: "7px 14px",
632
+ fontSize: 13,
633
+ color: "#333",
634
+ cursor: "pointer",
635
+ textAlign: "left",
636
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
637
+ transition: reduced ? "none" : "border-color 0.15s, background 0.15s"
638
+ };
639
+ return /* @__PURE__ */ jsx5("div", { style: containerStyle, children: config.suggestedMessages.map((text) => /* @__PURE__ */ jsx5(
640
+ "button",
641
+ {
642
+ style: chipStyle,
643
+ onClick: () => sendMessage(text),
644
+ onMouseEnter: (e) => {
645
+ e.currentTarget.style.borderColor = config.primaryColor;
646
+ e.currentTarget.style.background = `${config.primaryColor}08`;
647
+ },
648
+ onMouseLeave: (e) => {
649
+ e.currentTarget.style.borderColor = "#e0e0e0";
650
+ e.currentTarget.style.background = "none";
651
+ },
652
+ children: text
653
+ },
654
+ text
655
+ )) });
656
+ }
657
+
658
+ // src/components/chat-input.tsx
659
+ import { useState as useState5 } from "react";
660
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
661
+ function ChatInput() {
662
+ const { sendMessage, isLoading, config, t } = useChat();
663
+ const reduced = useReducedMotion();
664
+ const [value, setValue] = useState5("");
665
+ const handleSend = () => {
666
+ if (!value.trim() || isLoading) return;
667
+ sendMessage(value);
668
+ setValue("");
669
+ };
670
+ const handleKeyDown = (e) => {
671
+ if (e.key === "Enter" && !e.shiftKey) {
672
+ e.preventDefault();
673
+ handleSend();
674
+ }
675
+ };
676
+ const containerStyle = {
677
+ padding: "12px 16px",
678
+ borderTop: "1px solid #eee",
679
+ display: "flex",
680
+ alignItems: "center",
681
+ gap: 8
682
+ };
683
+ const inputStyle = {
684
+ flex: 1,
685
+ border: "1px solid #e0e0e0",
686
+ borderRadius: 24,
687
+ padding: "10px 16px",
688
+ fontSize: 14,
689
+ outline: "none",
690
+ background: "#fafafa",
691
+ color: config.textColor,
692
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
693
+ };
694
+ const buttonStyle = {
695
+ width: 36,
696
+ height: 36,
697
+ borderRadius: "50%",
698
+ background: config.primaryColor,
699
+ border: "none",
700
+ color: "white",
701
+ cursor: isLoading ? "not-allowed" : "pointer",
702
+ display: "flex",
703
+ alignItems: "center",
704
+ justifyContent: "center",
705
+ opacity: isLoading ? 0.5 : 1,
706
+ transition: reduced ? "opacity 0.2s" : "opacity 0.2s, transform 0.15s",
707
+ flexShrink: 0,
708
+ transform: "scale(1)"
709
+ };
710
+ return /* @__PURE__ */ jsxs3("div", { style: containerStyle, children: [
711
+ /* @__PURE__ */ jsx6(
712
+ "input",
713
+ {
714
+ type: "text",
715
+ value,
716
+ onChange: (e) => setValue(e.target.value),
717
+ onKeyDown: handleKeyDown,
718
+ placeholder: config.placeholderText,
719
+ style: inputStyle,
720
+ disabled: isLoading
721
+ }
722
+ ),
723
+ /* @__PURE__ */ jsx6(
724
+ "button",
725
+ {
726
+ onClick: handleSend,
727
+ disabled: isLoading || !value.trim(),
728
+ style: buttonStyle,
729
+ "aria-label": t("send_message"),
730
+ onMouseEnter: (e) => {
731
+ if (!reduced && !isLoading)
732
+ e.currentTarget.style.transform = "scale(1.1)";
733
+ },
734
+ onMouseLeave: (e) => {
735
+ if (!reduced) e.currentTarget.style.transform = "scale(1)";
736
+ },
737
+ children: /* @__PURE__ */ jsxs3(
738
+ "svg",
739
+ {
740
+ width: "16",
741
+ height: "16",
742
+ viewBox: "0 0 24 24",
743
+ fill: "none",
744
+ stroke: "currentColor",
745
+ strokeWidth: "2",
746
+ strokeLinecap: "round",
747
+ strokeLinejoin: "round",
748
+ children: [
749
+ /* @__PURE__ */ jsx6("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
750
+ /* @__PURE__ */ jsx6("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
751
+ ]
752
+ }
753
+ )
754
+ }
755
+ )
756
+ ] });
757
+ }
758
+
759
+ // src/components/chat-window.tsx
760
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
761
+ function ConfigError({ message, title }) {
762
+ const errorStyle = {
763
+ flex: 1,
764
+ display: "flex",
765
+ flexDirection: "column",
766
+ alignItems: "center",
767
+ justifyContent: "center",
768
+ padding: 24,
769
+ textAlign: "center",
770
+ gap: 8
771
+ };
772
+ return /* @__PURE__ */ jsxs4("div", { style: errorStyle, children: [
773
+ /* @__PURE__ */ jsxs4(
774
+ "svg",
775
+ {
776
+ width: "32",
777
+ height: "32",
778
+ viewBox: "0 0 24 24",
779
+ fill: "none",
780
+ stroke: "#999",
781
+ strokeWidth: "2",
782
+ strokeLinecap: "round",
783
+ strokeLinejoin: "round",
784
+ children: [
785
+ /* @__PURE__ */ jsx7("circle", { cx: "12", cy: "12", r: "10" }),
786
+ /* @__PURE__ */ jsx7("line", { x1: "12", y1: "8", x2: "12", y2: "12" }),
787
+ /* @__PURE__ */ jsx7("line", { x1: "12", y1: "16", x2: "12.01", y2: "16" })
788
+ ]
789
+ }
790
+ ),
791
+ /* @__PURE__ */ jsx7("p", { style: { fontSize: 14, fontWeight: 500, color: "#333", margin: 0 }, children: title }),
792
+ /* @__PURE__ */ jsx7("p", { style: { fontSize: 12, color: "#999", margin: 0 }, children: message })
793
+ ] });
794
+ }
795
+ function ChatWindow() {
796
+ const { isOpen, config, configError, t } = useChat();
797
+ const reduced = useReducedMotion();
798
+ const [visible, setVisible] = useState6(false);
799
+ const [shouldRender, setShouldRender] = useState6(false);
800
+ useEffect6(() => {
801
+ if (isOpen) {
802
+ setShouldRender(true);
803
+ requestAnimationFrame(() => {
804
+ requestAnimationFrame(() => setVisible(true));
805
+ });
806
+ } else {
807
+ setVisible(false);
808
+ if (reduced) {
809
+ setShouldRender(false);
810
+ } else {
811
+ const timer = setTimeout(() => setShouldRender(false), 250);
812
+ return () => clearTimeout(timer);
813
+ }
814
+ }
815
+ }, [isOpen, reduced]);
816
+ if (!shouldRender) return null;
817
+ const style = {
818
+ position: "fixed",
819
+ bottom: 90,
820
+ [config.position === "bottom-left" ? "left" : "right"]: 20,
821
+ width: 380,
822
+ maxWidth: "calc(100vw - 40px)",
823
+ height: 520,
824
+ maxHeight: "calc(100vh - 120px)",
825
+ borderRadius: 16,
826
+ overflow: "hidden",
827
+ display: "flex",
828
+ flexDirection: "column",
829
+ boxShadow: "0 8px 40px rgba(0,0,0,0.15)",
830
+ zIndex: 99999,
831
+ background: config.backgroundColor,
832
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
833
+ opacity: visible ? 1 : 0,
834
+ transform: visible ? "translateY(0) scale(1)" : "translateY(16px) scale(0.97)",
835
+ transition: reduced ? "none" : "opacity 0.25s ease, transform 0.25s ease"
836
+ };
837
+ const poweredStyle = {
838
+ textAlign: "center",
839
+ padding: 6,
840
+ fontSize: 10,
841
+ color: "#aaa"
842
+ };
843
+ const linkStyle = {
844
+ color: "#888",
845
+ textDecoration: "underline",
846
+ textUnderlineOffset: 2
847
+ };
848
+ return /* @__PURE__ */ jsxs4("div", { style, children: [
849
+ /* @__PURE__ */ jsx7(ChatHeader, {}),
850
+ configError ? /* @__PURE__ */ jsx7(ConfigError, { title: t("unable_to_load"), message: configError }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
851
+ /* @__PURE__ */ jsx7(ChatMessages, {}),
852
+ /* @__PURE__ */ jsx7(ChatSuggestions, {}),
853
+ /* @__PURE__ */ jsx7(ChatInput, {})
854
+ ] }),
855
+ /* @__PURE__ */ jsxs4("div", { style: poweredStyle, children: [
856
+ t("powered_by"),
857
+ " ",
858
+ /* @__PURE__ */ jsx7(
859
+ "a",
860
+ {
861
+ href: "https://customerhero.app",
862
+ target: "_blank",
863
+ rel: "noopener noreferrer",
864
+ style: linkStyle,
865
+ children: "CustomerHero"
866
+ }
867
+ )
868
+ ] })
869
+ ] });
870
+ }
871
+
872
+ // src/components/chat-widget.tsx
873
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
874
+ function ChatWidgetInner({ identity }) {
875
+ const client = useCustomerHeroClient();
876
+ const prevIdentityRef = useRef4(void 0);
877
+ useEffect7(() => {
878
+ const key = identity ? JSON.stringify(identity) : void 0;
879
+ if (key !== prevIdentityRef.current) {
880
+ prevIdentityRef.current = key;
881
+ if (identity) {
882
+ client.identify(identity);
883
+ }
884
+ }
885
+ }, [identity, client]);
886
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
887
+ /* @__PURE__ */ jsx8(ChatBubble, {}),
888
+ /* @__PURE__ */ jsx8(ChatWindow, {})
889
+ ] });
890
+ }
891
+ function ChatWidget({ identity, ...config }) {
892
+ return /* @__PURE__ */ jsx8(CustomerHeroProvider, { ...config, children: /* @__PURE__ */ jsx8(ChatWidgetInner, { identity }) });
893
+ }
894
+ export {
895
+ ChatWidget
896
+ };