simple-support-chat 0.1.0 → 0.2.0

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.
@@ -1,4 +1,4 @@
1
- import { useState, useCallback, useRef, useEffect } from 'react';
1
+ import { useState, useRef, useCallback, useEffect, useMemo } from 'react';
2
2
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
3
 
4
4
  // src/client/ChatBubble.tsx
@@ -48,13 +48,18 @@ function collectAnonymousContext() {
48
48
  }
49
49
 
50
50
  // src/client/useChatEngine.ts
51
+ var POLL_INTERVAL = 4e3;
51
52
  function useChatEngine({
52
53
  apiUrl,
53
- user
54
+ user,
55
+ repliesUrl,
56
+ isOpen = true
54
57
  }) {
55
58
  const [messages, setMessages] = useState([]);
56
59
  const [input, setInput] = useState("");
57
60
  const [sending, setSending] = useState(false);
61
+ const lastReplyTimestampRef = useRef(null);
62
+ const knownReplyIdsRef = useRef(/* @__PURE__ */ new Set());
58
63
  const sendMessage = useCallback(async () => {
59
64
  const text = input.trim();
60
65
  if (!text || sending) return;
@@ -113,8 +118,89 @@ function useChatEngine({
113
118
  },
114
119
  [sendMessage]
115
120
  );
121
+ useEffect(() => {
122
+ if (!repliesUrl || !isOpen) return;
123
+ const sessionId = user?.id ?? getSessionId();
124
+ const fetchReplies = async () => {
125
+ try {
126
+ const params = new URLSearchParams({ sessionId });
127
+ if (lastReplyTimestampRef.current) {
128
+ params.set("since", lastReplyTimestampRef.current);
129
+ }
130
+ const response = await fetch(`${repliesUrl}?${params.toString()}`);
131
+ if (!response.ok) return;
132
+ const data = await response.json();
133
+ if (data.replies.length === 0) return;
134
+ const newReplies = data.replies.filter(
135
+ (r) => !knownReplyIdsRef.current.has(r.id)
136
+ );
137
+ if (newReplies.length === 0) return;
138
+ for (const r of newReplies) {
139
+ knownReplyIdsRef.current.add(r.id);
140
+ }
141
+ const latestTimestamp = newReplies.reduce((latest, r) => {
142
+ return r.timestamp > latest ? r.timestamp : latest;
143
+ }, lastReplyTimestampRef.current ?? "");
144
+ lastReplyTimestampRef.current = latestTimestamp;
145
+ const replyMessages = newReplies.map((r) => ({
146
+ id: r.id,
147
+ text: r.text,
148
+ sender: "received",
149
+ timestamp: new Date(r.timestamp).getTime()
150
+ }));
151
+ setMessages((prev) => [...prev, ...replyMessages]);
152
+ } catch {
153
+ }
154
+ };
155
+ void fetchReplies();
156
+ const intervalId = setInterval(() => void fetchReplies(), POLL_INTERVAL);
157
+ return () => clearInterval(intervalId);
158
+ }, [repliesUrl, isOpen, user]);
116
159
  return { messages, input, setInput, sending, sendMessage, handleKeyDown };
117
160
  }
161
+ function useColorScheme() {
162
+ const [scheme, setScheme] = useState(() => {
163
+ if (typeof window === "undefined") return "light";
164
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
165
+ });
166
+ useEffect(() => {
167
+ if (typeof window === "undefined") return;
168
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
169
+ const handler = (e) => {
170
+ setScheme(e.matches ? "dark" : "light");
171
+ };
172
+ mq.addEventListener("change", handler);
173
+ return () => mq.removeEventListener("change", handler);
174
+ }, []);
175
+ return scheme;
176
+ }
177
+ var lightTokens = {
178
+ panelBg: "#ffffff",
179
+ panelBorder: "#e5e7eb",
180
+ inputBg: "#ffffff",
181
+ inputBorder: "#d1d5db",
182
+ inputText: "#1f2937",
183
+ inputPlaceholder: "#9ca3af",
184
+ receivedBg: "#f3f4f6",
185
+ receivedText: "#1f2937",
186
+ emptyText: "#9ca3af",
187
+ inputAreaBorder: "#e5e7eb"
188
+ };
189
+ var darkTokens = {
190
+ panelBg: "#1f2937",
191
+ panelBorder: "#374151",
192
+ inputBg: "#111827",
193
+ inputBorder: "#4b5563",
194
+ inputText: "#f9fafb",
195
+ inputPlaceholder: "#9ca3af",
196
+ receivedBg: "#374151",
197
+ receivedText: "#f3f4f6",
198
+ emptyText: "#6b7280",
199
+ inputAreaBorder: "#374151"
200
+ };
201
+ function getThemeTokens(scheme) {
202
+ return scheme === "dark" ? darkTokens : lightTokens;
203
+ }
118
204
  function injectKeyframes() {
119
205
  if (typeof document === "undefined") return;
120
206
  if (document.querySelector("[data-support-chat-keyframes]")) return;
@@ -139,10 +225,13 @@ function ChatBubble({
139
225
  title = "Support",
140
226
  placeholder = "Type a message...",
141
227
  show = true,
142
- user
228
+ user,
229
+ repliesUrl
143
230
  }) {
144
231
  const [isOpen, setIsOpen] = useState(false);
145
- const { messages, input, setInput, sending, sendMessage, handleKeyDown } = useChatEngine({ apiUrl, user });
232
+ const colorScheme = useColorScheme();
233
+ const theme = useMemo(() => getThemeTokens(colorScheme), [colorScheme]);
234
+ const { messages, input, setInput, sending, sendMessage, handleKeyDown } = useChatEngine({ apiUrl, user, repliesUrl, isOpen });
146
235
  const [panelState, setPanelState] = useState(
147
236
  "closed"
148
237
  );
@@ -299,8 +388,8 @@ function ChatBubble({
299
388
  boxShadow: "0 8px 30px rgba(0,0,0,0.12)",
300
389
  fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
301
390
  fontSize: "14px",
302
- backgroundColor: "#fff",
303
- border: "1px solid #e5e7eb",
391
+ backgroundColor: theme.panelBg,
392
+ border: `1px solid ${theme.panelBorder}`,
304
393
  animation: panelState === "open" ? "sc-slide-in 0.25s ease-out forwards" : "sc-slide-out 0.2s ease-in forwards"
305
394
  },
306
395
  children: [
@@ -357,7 +446,7 @@ function ChatBubble({
357
446
  "div",
358
447
  {
359
448
  style: {
360
- color: "#9ca3af",
449
+ color: theme.emptyText,
361
450
  textAlign: "center",
362
451
  marginTop: "40px"
363
452
  },
@@ -367,13 +456,14 @@ function ChatBubble({
367
456
  messages.map((msg) => /* @__PURE__ */ jsx(
368
457
  "div",
369
458
  {
459
+ "data-sender": msg.sender,
370
460
  style: {
371
461
  alignSelf: msg.sender === "user" ? "flex-end" : "flex-start",
372
462
  maxWidth: "80%",
373
463
  padding: "10px 14px",
374
464
  borderRadius: msg.sender === "user" ? "16px 16px 4px 16px" : "16px 16px 16px 4px",
375
- backgroundColor: msg.sender === "user" ? color : "#f3f4f6",
376
- color: msg.sender === "user" ? "#fff" : "#1f2937",
465
+ backgroundColor: msg.sender === "user" ? color : theme.receivedBg,
466
+ color: msg.sender === "user" ? "#fff" : theme.receivedText,
377
467
  wordBreak: "break-word"
378
468
  },
379
469
  children: msg.text
@@ -388,7 +478,7 @@ function ChatBubble({
388
478
  "div",
389
479
  {
390
480
  style: {
391
- borderTop: "1px solid #e5e7eb",
481
+ borderTop: `1px solid ${theme.inputAreaBorder}`,
392
482
  padding: "12px",
393
483
  display: "flex",
394
484
  gap: "8px",
@@ -407,12 +497,14 @@ function ChatBubble({
407
497
  "aria-label": "Type your message",
408
498
  style: {
409
499
  flex: 1,
410
- border: "1px solid #d1d5db",
500
+ border: `1px solid ${theme.inputBorder}`,
411
501
  borderRadius: "8px",
412
502
  padding: "10px 12px",
413
503
  fontSize: "14px",
414
504
  outline: "none",
415
- fontFamily: "inherit"
505
+ fontFamily: "inherit",
506
+ backgroundColor: theme.inputBg,
507
+ color: theme.inputText
416
508
  }
417
509
  }
418
510
  ),
@@ -478,9 +570,12 @@ function SupportChatModal({
478
570
  color = "#2563eb",
479
571
  title = "Contact Us",
480
572
  placeholder = "Type a message...",
481
- user
573
+ user,
574
+ repliesUrl
482
575
  }) {
483
- const { messages, input, setInput, sending, sendMessage, handleKeyDown } = useChatEngine({ apiUrl, user });
576
+ const { messages, input, setInput, sending, sendMessage, handleKeyDown } = useChatEngine({ apiUrl, user, repliesUrl, isOpen });
577
+ const colorScheme = useColorScheme();
578
+ const theme = useMemo(() => getThemeTokens(colorScheme), [colorScheme]);
484
579
  const modalRef = useRef(null);
485
580
  const inputRef = useRef(null);
486
581
  const messagesEndRef = useRef(null);
@@ -598,7 +693,7 @@ function SupportChatModal({
598
693
  maxWidth: "calc(100vw - 32px)",
599
694
  height: "600px",
600
695
  maxHeight: "calc(100vh - 64px)",
601
- backgroundColor: "#fff",
696
+ backgroundColor: theme.panelBg,
602
697
  borderRadius: "16px",
603
698
  overflow: "hidden",
604
699
  display: "flex",
@@ -660,7 +755,7 @@ function SupportChatModal({
660
755
  "div",
661
756
  {
662
757
  style: {
663
- color: "#9ca3af",
758
+ color: theme.emptyText,
664
759
  textAlign: "center",
665
760
  marginTop: "60px"
666
761
  },
@@ -670,13 +765,14 @@ function SupportChatModal({
670
765
  messages.map((msg) => /* @__PURE__ */ jsx(
671
766
  "div",
672
767
  {
768
+ "data-sender": msg.sender,
673
769
  style: {
674
770
  alignSelf: msg.sender === "user" ? "flex-end" : "flex-start",
675
771
  maxWidth: "80%",
676
772
  padding: "10px 14px",
677
773
  borderRadius: msg.sender === "user" ? "16px 16px 4px 16px" : "16px 16px 16px 4px",
678
- backgroundColor: msg.sender === "user" ? color : "#f3f4f6",
679
- color: msg.sender === "user" ? "#fff" : "#1f2937",
774
+ backgroundColor: msg.sender === "user" ? color : theme.receivedBg,
775
+ color: msg.sender === "user" ? "#fff" : theme.receivedText,
680
776
  wordBreak: "break-word"
681
777
  },
682
778
  children: msg.text
@@ -691,7 +787,7 @@ function SupportChatModal({
691
787
  "div",
692
788
  {
693
789
  style: {
694
- borderTop: "1px solid #e5e7eb",
790
+ borderTop: `1px solid ${theme.inputAreaBorder}`,
695
791
  padding: "16px 20px",
696
792
  display: "flex",
697
793
  gap: "8px",
@@ -710,12 +806,14 @@ function SupportChatModal({
710
806
  "aria-label": "Type your message",
711
807
  style: {
712
808
  flex: 1,
713
- border: "1px solid #d1d5db",
809
+ border: `1px solid ${theme.inputBorder}`,
714
810
  borderRadius: "8px",
715
811
  padding: "12px 14px",
716
812
  fontSize: "14px",
717
813
  outline: "none",
718
- fontFamily: "inherit"
814
+ fontFamily: "inherit",
815
+ backgroundColor: theme.inputBg,
816
+ color: theme.inputText
719
817
  }
720
818
  }
721
819
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/context.ts","../../src/client/useChatEngine.ts","../../src/client/ChatBubble.tsx","../../src/client/SupportChatModal.tsx","../../src/client/useSupportChat.ts"],"names":["useState","useCallback","useRef","useEffect","jsxs","Fragment","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { AnonymousContext } from \"./types\";\r\n\r\nconst SESSION_KEY = \"support-chat-session-id\";\r\n\r\n/** Generate a random session ID */\r\nfunction generateSessionId(): string {\r\n const chars =\r\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\r\n let id = \"\";\r\n for (let i = 0; i < 16; i++) {\r\n id += chars.charAt(Math.floor(Math.random() * chars.length));\r\n }\r\n return id;\r\n}\r\n\r\n/** Get or create a persistent session ID from localStorage */\r\nexport function getSessionId(): string {\r\n if (typeof window === \"undefined\") return generateSessionId();\r\n\r\n try {\r\n const existing = localStorage.getItem(SESSION_KEY);\r\n if (existing) return existing;\r\n\r\n const newId = generateSessionId();\r\n localStorage.setItem(SESSION_KEY, newId);\r\n return newId;\r\n } catch {\r\n // localStorage may be blocked (private browsing, etc.)\r\n return generateSessionId();\r\n }\r\n}\r\n\r\n/** Collect anonymous browser context */\r\nexport function collectAnonymousContext(): AnonymousContext {\r\n const sessionId = getSessionId();\r\n\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pageUrl: \"\",\r\n referrer: \"\",\r\n userAgent: \"\",\r\n screenSize: \"\",\r\n timezone: \"\",\r\n sessionId,\r\n };\r\n }\r\n\r\n return {\r\n pageUrl: window.location.href,\r\n referrer: document.referrer,\r\n userAgent: navigator.userAgent,\r\n screenSize: `${window.screen.width}x${window.screen.height}`,\r\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\r\n sessionId,\r\n };\r\n}\r\n","import { useState, useCallback } from \"react\";\r\nimport type { ChatMessage, ChatUser } from \"./types\";\r\nimport { collectAnonymousContext, getSessionId } from \"./context\";\r\n\r\n/** Options for initializing the chat engine */\r\nexport interface ChatEngineOptions {\r\n /** URL of the support chat API endpoint */\r\n apiUrl: string;\r\n /** Authenticated user info (optional) */\r\n user?: ChatUser;\r\n}\r\n\r\n/** Return value from useChatEngine */\r\nexport interface ChatEngineState {\r\n /** All chat messages */\r\n messages: ChatMessage[];\r\n /** Current input value */\r\n input: string;\r\n /** Update the input value */\r\n setInput: (value: string) => void;\r\n /** Whether a message is currently being sent */\r\n sending: boolean;\r\n /** Send the current input as a message */\r\n sendMessage: () => Promise<void>;\r\n /** Handle keydown on the input (Enter to send) */\r\n handleKeyDown: (e: React.KeyboardEvent) => void;\r\n}\r\n\r\n/**\r\n * Shared chat engine hook used by both ChatBubble and SupportChatModal.\r\n * Manages message state, input, and API communication.\r\n */\r\nexport function useChatEngine({\r\n apiUrl,\r\n user,\r\n}: ChatEngineOptions): ChatEngineState {\r\n const [messages, setMessages] = useState<ChatMessage[]>([]);\r\n const [input, setInput] = useState(\"\");\r\n const [sending, setSending] = useState(false);\r\n\r\n const sendMessage = useCallback(async () => {\r\n const text = input.trim();\r\n if (!text || sending) return;\r\n\r\n const msg: ChatMessage = {\r\n id: `msg-${Date.now()}`,\r\n text,\r\n sender: \"user\",\r\n timestamp: Date.now(),\r\n };\r\n\r\n setMessages((prev) => [...prev, msg]);\r\n setInput(\"\");\r\n setSending(true);\r\n\r\n try {\r\n const context = collectAnonymousContext();\r\n const response = await fetch(apiUrl, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n message: text,\r\n user: user ?? undefined,\r\n sessionId: user?.id ?? getSessionId(),\r\n context,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n setMessages((prev) => [\r\n ...prev,\r\n {\r\n id: `err-${Date.now()}`,\r\n text: \"Failed to send message. Please try again.\",\r\n sender: \"system\",\r\n timestamp: Date.now(),\r\n },\r\n ]);\r\n }\r\n } catch {\r\n setMessages((prev) => [\r\n ...prev,\r\n {\r\n id: `err-${Date.now()}`,\r\n text: \"Connection error. Please try again.\",\r\n sender: \"system\",\r\n timestamp: Date.now(),\r\n },\r\n ]);\r\n } finally {\r\n setSending(false);\r\n }\r\n }, [input, sending, apiUrl, user]);\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n if (e.key === \"Enter\" && !e.shiftKey) {\r\n e.preventDefault();\r\n void sendMessage();\r\n }\r\n },\r\n [sendMessage],\r\n );\r\n\r\n return { messages, input, setInput, sending, sendMessage, handleKeyDown };\r\n}\r\n","import { useState, useCallback, useRef, useEffect } from \"react\";\r\nimport type { ChatBubbleProps } from \"./types\";\r\nimport { useChatEngine } from \"./useChatEngine\";\r\n\r\n/**\r\n * Inject a <style> tag for keyframe animations.\r\n * Called once on first mount. Uses a data-attribute to avoid duplicates.\r\n */\r\nfunction injectKeyframes(): void {\r\n if (typeof document === \"undefined\") return;\r\n if (document.querySelector(\"[data-support-chat-keyframes]\")) return;\r\n\r\n const style = document.createElement(\"style\");\r\n style.setAttribute(\"data-support-chat-keyframes\", \"\");\r\n style.textContent = `\r\n @keyframes sc-slide-in {\r\n from { opacity: 0; transform: translateY(12px) scale(0.96); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n @keyframes sc-slide-out {\r\n from { opacity: 1; transform: translateY(0) scale(1); }\r\n to { opacity: 0; transform: translateY(12px) scale(0.96); }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/**\r\n * ChatBubble -- floating support chat widget.\r\n *\r\n * Renders a circular button that opens an inline chat panel.\r\n * Messages are sent via POST to the configured `apiUrl`.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ChatBubble apiUrl=\"/api/support\" />\r\n * ```\r\n */\r\nexport function ChatBubble({\r\n apiUrl,\r\n position = \"bottom-right\",\r\n color = \"#2563eb\",\r\n title = \"Support\",\r\n placeholder = \"Type a message...\",\r\n show = true,\r\n user,\r\n}: ChatBubbleProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n // Use shared chat engine for message logic\r\n const { messages, input, setInput, sending, sendMessage, handleKeyDown } =\r\n useChatEngine({ apiUrl, user });\r\n\r\n // Animation state: \"open\" | \"closing\" | \"closed\"\r\n const [panelState, setPanelState] = useState<\"open\" | \"closing\" | \"closed\">(\r\n \"closed\",\r\n );\r\n\r\n const panelRef = useRef<HTMLDivElement>(null);\r\n const messagesEndRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n // Inject keyframe animations on first render\r\n useEffect(() => {\r\n injectKeyframes();\r\n }, []);\r\n\r\n // Sync isOpen -> panelState\r\n useEffect(() => {\r\n if (isOpen) {\r\n setPanelState(\"open\");\r\n } else if (panelState === \"open\") {\r\n // Start closing animation\r\n setPanelState(\"closing\");\r\n }\r\n }, [isOpen]);\r\n\r\n // When closing animation ends, set to fully closed\r\n const handleAnimationEnd = useCallback(() => {\r\n if (panelState === \"closing\") {\r\n setPanelState(\"closed\");\r\n }\r\n }, [panelState]);\r\n\r\n // Focus input when panel opens\r\n useEffect(() => {\r\n if (panelState === \"open\" && inputRef.current) {\r\n inputRef.current.focus();\r\n }\r\n }, [panelState]);\r\n\r\n // Auto-scroll to latest message\r\n useEffect(() => {\r\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\r\n }, [messages]);\r\n\r\n // Focus trap: keep Tab cycling inside the dialog when open\r\n useEffect(() => {\r\n if (panelState !== \"open\" || !panelRef.current) return;\r\n\r\n const panel = panelRef.current;\r\n\r\n const handleTrapKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n setIsOpen(false);\r\n return;\r\n }\r\n\r\n if (e.key !== \"Tab\") return;\r\n\r\n const focusable = panel.querySelectorAll<HTMLElement>(\r\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\r\n );\r\n if (focusable.length === 0) return;\r\n\r\n const first = focusable[0]!;\r\n const last = focusable[focusable.length - 1]!;\r\n\r\n if (e.shiftKey) {\r\n if (document.activeElement === first) {\r\n e.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (document.activeElement === last) {\r\n e.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleTrapKeyDown);\r\n return () => document.removeEventListener(\"keydown\", handleTrapKeyDown);\r\n }, [panelState]);\r\n\r\n const toggle = useCallback(() => setIsOpen((o) => !o), []);\r\n\r\n // Position styles for the bubble button\r\n const positionStyles: React.CSSProperties = {\r\n position: \"fixed\",\r\n zIndex: 99999,\r\n ...(position.includes(\"bottom\") ? { bottom: \"20px\" } : { top: \"20px\" }),\r\n ...(position.includes(\"right\") ? { right: \"20px\" } : { left: \"20px\" }),\r\n };\r\n\r\n // Panel position & responsive sizing\r\n // On mobile (<480px viewport), expand to near-full screen\r\n const panelPositionStyles: React.CSSProperties = {\r\n position: \"fixed\",\r\n zIndex: 99999,\r\n width: \"380px\",\r\n maxWidth: \"calc(100vw - 40px)\",\r\n height: \"500px\",\r\n maxHeight: \"calc(100vh - 120px)\",\r\n ...(position.includes(\"bottom\") ? { bottom: \"80px\" } : { top: \"80px\" }),\r\n ...(position.includes(\"right\") ? { right: \"20px\" } : { left: \"20px\" }),\r\n };\r\n\r\n // Check if panel is visible (open or animating out)\r\n const showPanel = panelState === \"open\" || panelState === \"closing\";\r\n\r\n return (\r\n <>\r\n {/* Responsive overrides injected as inline <style> */}\r\n {showPanel && (\r\n <style>{`\r\n @media (max-width: 479px) {\r\n [data-support-chat-panel] {\r\n width: calc(100vw - 16px) !important;\r\n height: calc(100vh - 100px) !important;\r\n max-width: none !important;\r\n max-height: none !important;\r\n left: 8px !important;\r\n right: 8px !important;\r\n bottom: 72px !important;\r\n border-radius: 12px !important;\r\n }\r\n }\r\n `}</style>\r\n )}\r\n\r\n {/* Floating bubble button */}\r\n {show && (\r\n <button\r\n onClick={toggle}\r\n aria-label={isOpen ? \"Close support chat\" : \"Open support chat\"}\r\n aria-expanded={isOpen}\r\n aria-haspopup=\"dialog\"\r\n style={{\r\n ...positionStyles,\r\n width: \"56px\",\r\n height: \"56px\",\r\n borderRadius: \"50%\",\r\n backgroundColor: color,\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n boxShadow: \"0 4px 12px rgba(0,0,0,0.15)\",\r\n transition: \"transform 0.2s ease, box-shadow 0.2s ease\",\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = \"scale(1.1)\";\r\n e.currentTarget.style.boxShadow = \"0 6px 20px rgba(0,0,0,0.2)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = \"scale(1)\";\r\n e.currentTarget.style.boxShadow = \"0 4px 12px rgba(0,0,0,0.15)\";\r\n }}\r\n >\r\n <svg\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"white\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n {isOpen ? (\r\n <path d=\"M18 6L6 18M6 6l12 12\" />\r\n ) : (\r\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\r\n )}\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Chat panel */}\r\n {showPanel && (\r\n <div\r\n ref={panelRef}\r\n role=\"dialog\"\r\n aria-label=\"Support chat\"\r\n aria-modal=\"true\"\r\n data-support-chat-panel=\"\"\r\n onAnimationEnd={handleAnimationEnd}\r\n style={{\r\n ...panelPositionStyles,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n borderRadius: \"12px\",\r\n overflow: \"hidden\",\r\n boxShadow: \"0 8px 30px rgba(0,0,0,0.12)\",\r\n fontFamily:\r\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n fontSize: \"14px\",\r\n backgroundColor: \"#fff\",\r\n border: \"1px solid #e5e7eb\",\r\n animation:\r\n panelState === \"open\"\r\n ? \"sc-slide-in 0.25s ease-out forwards\"\r\n : \"sc-slide-out 0.2s ease-in forwards\",\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n padding: \"16px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <span style={{ fontWeight: 600, fontSize: \"16px\" }}>{title}</span>\r\n <button\r\n onClick={() => setIsOpen(false)}\r\n aria-label=\"Close chat\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n lineHeight: 1,\r\n fontSize: \"18px\",\r\n }}\r\n >\r\n &#x2715;\r\n </button>\r\n </div>\r\n\r\n {/* Messages */}\r\n <div\r\n role=\"log\"\r\n aria-live=\"polite\"\r\n aria-label=\"Chat messages\"\r\n style={{\r\n flex: 1,\r\n overflowY: \"auto\",\r\n padding: \"16px\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n {messages.length === 0 && (\r\n <div\r\n style={{\r\n color: \"#9ca3af\",\r\n textAlign: \"center\",\r\n marginTop: \"40px\",\r\n }}\r\n >\r\n Send us a message and we will get back to you!\r\n </div>\r\n )}\r\n {messages.map((msg) => (\r\n <div\r\n key={msg.id}\r\n style={{\r\n alignSelf:\r\n msg.sender === \"user\" ? \"flex-end\" : \"flex-start\",\r\n maxWidth: \"80%\",\r\n padding: \"10px 14px\",\r\n borderRadius:\r\n msg.sender === \"user\"\r\n ? \"16px 16px 4px 16px\"\r\n : \"16px 16px 16px 4px\",\r\n backgroundColor:\r\n msg.sender === \"user\" ? color : \"#f3f4f6\",\r\n color: msg.sender === \"user\" ? \"#fff\" : \"#1f2937\",\r\n wordBreak: \"break-word\",\r\n }}\r\n >\r\n {msg.text}\r\n </div>\r\n ))}\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {/* Input */}\r\n <div\r\n style={{\r\n borderTop: \"1px solid #e5e7eb\",\r\n padding: \"12px\",\r\n display: \"flex\",\r\n gap: \"8px\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={input}\r\n onChange={(e) => setInput(e.target.value)}\r\n onKeyDown={handleKeyDown}\r\n placeholder={placeholder}\r\n aria-label=\"Type your message\"\r\n style={{\r\n flex: 1,\r\n border: \"1px solid #d1d5db\",\r\n borderRadius: \"8px\",\r\n padding: \"10px 12px\",\r\n fontSize: \"14px\",\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n }}\r\n />\r\n <button\r\n onClick={() => void sendMessage()}\r\n disabled={sending || !input.trim()}\r\n aria-label=\"Send message\"\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n border: \"none\",\r\n borderRadius: \"8px\",\r\n padding: \"10px 16px\",\r\n cursor:\r\n sending || !input.trim() ? \"not-allowed\" : \"pointer\",\r\n opacity: sending || !input.trim() ? 0.5 : 1,\r\n fontWeight: 600,\r\n fontSize: \"14px\",\r\n fontFamily: \"inherit\",\r\n transition: \"opacity 0.2s ease\",\r\n }}\r\n >\r\n Send\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </>\r\n );\r\n}\r\n","import { useRef, useEffect, useCallback } from \"react\";\r\nimport type { SupportChatModalProps } from \"./types\";\r\nimport { useChatEngine } from \"./useChatEngine\";\r\n\r\n/**\r\n * Inject a <style> tag for modal keyframe animations.\r\n * Called once on first mount. Uses a data-attribute to avoid duplicates.\r\n */\r\nfunction injectModalKeyframes(): void {\r\n if (typeof document === \"undefined\") return;\r\n if (document.querySelector(\"[data-support-chat-modal-keyframes]\")) return;\r\n\r\n const style = document.createElement(\"style\");\r\n style.setAttribute(\"data-support-chat-modal-keyframes\", \"\");\r\n style.textContent = `\r\n @keyframes sc-modal-backdrop-in {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n }\r\n @keyframes sc-modal-backdrop-out {\r\n from { opacity: 1; }\r\n to { opacity: 0; }\r\n }\r\n @keyframes sc-modal-slide-in {\r\n from { opacity: 0; transform: translateY(20px) scale(0.96); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n @keyframes sc-modal-slide-out {\r\n from { opacity: 1; transform: translateY(0) scale(1); }\r\n to { opacity: 0; transform: translateY(20px) scale(0.96); }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/**\r\n * SupportChatModal -- modal-based support chat.\r\n *\r\n * Renders a centered modal dialog for support chat, designed to be\r\n * triggered from custom UI elements (e.g., \"Contact Us\" links).\r\n *\r\n * Desktop: centered modal, max-width ~500px, with backdrop overlay.\r\n * Mobile: full-screen modal.\r\n *\r\n * Use with the `useSupportChat()` hook:\r\n *\r\n * @example\r\n * ```tsx\r\n * import { SupportChatModal, useSupportChat } from 'support-chat';\r\n *\r\n * function App() {\r\n * const { open, close, isOpen } = useSupportChat();\r\n * return (\r\n * <>\r\n * <button onClick={open}>Contact Us</button>\r\n * <SupportChatModal\r\n * apiUrl=\"/api/support\"\r\n * isOpen={isOpen}\r\n * onClose={close}\r\n * />\r\n * </>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function SupportChatModal({\r\n apiUrl,\r\n isOpen,\r\n onClose,\r\n color = \"#2563eb\",\r\n title = \"Contact Us\",\r\n placeholder = \"Type a message...\",\r\n user,\r\n}: SupportChatModalProps) {\r\n const { messages, input, setInput, sending, sendMessage, handleKeyDown } =\r\n useChatEngine({ apiUrl, user });\r\n\r\n const modalRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const messagesEndRef = useRef<HTMLDivElement>(null);\r\n const closingRef = useRef(false);\r\n const backdropRef = useRef<HTMLDivElement>(null);\r\n\r\n // Inject modal keyframes on first render\r\n useEffect(() => {\r\n injectModalKeyframes();\r\n }, []);\r\n\r\n // Focus input when modal opens\r\n useEffect(() => {\r\n if (isOpen && inputRef.current) {\r\n // Small delay to let the modal render before focusing\r\n const timer = setTimeout(() => inputRef.current?.focus(), 50);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isOpen]);\r\n\r\n // Auto-scroll to latest message\r\n useEffect(() => {\r\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\r\n }, [messages]);\r\n\r\n // Prevent body scroll when modal is open\r\n useEffect(() => {\r\n if (isOpen) {\r\n const prev = document.body.style.overflow;\r\n document.body.style.overflow = \"hidden\";\r\n return () => {\r\n document.body.style.overflow = prev;\r\n };\r\n }\r\n }, [isOpen]);\r\n\r\n // Focus trap and Escape key handling\r\n useEffect(() => {\r\n if (!isOpen || !modalRef.current) return;\r\n\r\n const modal = modalRef.current;\r\n\r\n const handleTrapKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n onClose();\r\n return;\r\n }\r\n\r\n if (e.key !== \"Tab\") return;\r\n\r\n const focusable = modal.querySelectorAll<HTMLElement>(\r\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\r\n );\r\n if (focusable.length === 0) return;\r\n\r\n const first = focusable[0]!;\r\n const last = focusable[focusable.length - 1]!;\r\n\r\n if (e.shiftKey) {\r\n if (document.activeElement === first) {\r\n e.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (document.activeElement === last) {\r\n e.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleTrapKeyDown);\r\n return () => document.removeEventListener(\"keydown\", handleTrapKeyDown);\r\n }, [isOpen, onClose]);\r\n\r\n // Handle close with animation\r\n const handleClose = useCallback(() => {\r\n closingRef.current = true;\r\n onClose();\r\n }, [onClose]);\r\n\r\n // Handle backdrop click\r\n const handleBackdropClick = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (e.target === e.currentTarget) {\r\n handleClose();\r\n }\r\n },\r\n [handleClose],\r\n );\r\n\r\n if (!isOpen) return null;\r\n\r\n return (\r\n <>\r\n {/* Responsive style for full-screen on mobile */}\r\n <style>{`\r\n @media (max-width: 479px) {\r\n [data-support-chat-modal] {\r\n width: 100vw !important;\r\n height: 100vh !important;\r\n max-width: none !important;\r\n max-height: none !important;\r\n border-radius: 0 !important;\r\n margin: 0 !important;\r\n }\r\n [data-support-chat-modal-backdrop] {\r\n align-items: stretch !important;\r\n justify-content: stretch !important;\r\n }\r\n }\r\n `}</style>\r\n\r\n {/* Backdrop overlay */}\r\n <div\r\n ref={backdropRef}\r\n data-support-chat-modal-backdrop=\"\"\r\n onClick={handleBackdropClick}\r\n style={{\r\n position: \"fixed\",\r\n inset: 0,\r\n zIndex: 100000,\r\n backgroundColor: \"rgba(0, 0, 0, 0.5)\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n fontFamily:\r\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n fontSize: \"14px\",\r\n animation: \"sc-modal-backdrop-in 0.2s ease-out forwards\",\r\n }}\r\n >\r\n {/* Modal dialog */}\r\n <div\r\n ref={modalRef}\r\n role=\"dialog\"\r\n aria-label=\"Support chat\"\r\n aria-modal=\"true\"\r\n data-support-chat-modal=\"\"\r\n style={{\r\n width: \"500px\",\r\n maxWidth: \"calc(100vw - 32px)\",\r\n height: \"600px\",\r\n maxHeight: \"calc(100vh - 64px)\",\r\n backgroundColor: \"#fff\",\r\n borderRadius: \"16px\",\r\n overflow: \"hidden\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n boxShadow: \"0 20px 60px rgba(0, 0, 0, 0.2)\",\r\n animation: \"sc-modal-slide-in 0.3s ease-out forwards\",\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n padding: \"20px 24px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <span style={{ fontWeight: 600, fontSize: \"18px\" }}>{title}</span>\r\n <button\r\n onClick={handleClose}\r\n aria-label=\"Close chat\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n lineHeight: 1,\r\n fontSize: \"20px\",\r\n }}\r\n >\r\n &#x2715;\r\n </button>\r\n </div>\r\n\r\n {/* Messages */}\r\n <div\r\n role=\"log\"\r\n aria-live=\"polite\"\r\n aria-label=\"Chat messages\"\r\n style={{\r\n flex: 1,\r\n overflowY: \"auto\",\r\n padding: \"20px\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n {messages.length === 0 && (\r\n <div\r\n style={{\r\n color: \"#9ca3af\",\r\n textAlign: \"center\",\r\n marginTop: \"60px\",\r\n }}\r\n >\r\n Send us a message and we will get back to you!\r\n </div>\r\n )}\r\n {messages.map((msg) => (\r\n <div\r\n key={msg.id}\r\n style={{\r\n alignSelf:\r\n msg.sender === \"user\" ? \"flex-end\" : \"flex-start\",\r\n maxWidth: \"80%\",\r\n padding: \"10px 14px\",\r\n borderRadius:\r\n msg.sender === \"user\"\r\n ? \"16px 16px 4px 16px\"\r\n : \"16px 16px 16px 4px\",\r\n backgroundColor:\r\n msg.sender === \"user\" ? color : \"#f3f4f6\",\r\n color: msg.sender === \"user\" ? \"#fff\" : \"#1f2937\",\r\n wordBreak: \"break-word\",\r\n }}\r\n >\r\n {msg.text}\r\n </div>\r\n ))}\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {/* Input */}\r\n <div\r\n style={{\r\n borderTop: \"1px solid #e5e7eb\",\r\n padding: \"16px 20px\",\r\n display: \"flex\",\r\n gap: \"8px\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={input}\r\n onChange={(e) => setInput(e.target.value)}\r\n onKeyDown={handleKeyDown}\r\n placeholder={placeholder}\r\n aria-label=\"Type your message\"\r\n style={{\r\n flex: 1,\r\n border: \"1px solid #d1d5db\",\r\n borderRadius: \"8px\",\r\n padding: \"12px 14px\",\r\n fontSize: \"14px\",\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n }}\r\n />\r\n <button\r\n onClick={() => void sendMessage()}\r\n disabled={sending || !input.trim()}\r\n aria-label=\"Send message\"\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n border: \"none\",\r\n borderRadius: \"8px\",\r\n padding: \"12px 20px\",\r\n cursor:\r\n sending || !input.trim() ? \"not-allowed\" : \"pointer\",\r\n opacity: sending || !input.trim() ? 0.5 : 1,\r\n fontWeight: 600,\r\n fontSize: \"14px\",\r\n fontFamily: \"inherit\",\r\n transition: \"opacity 0.2s ease\",\r\n }}\r\n >\r\n Send\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n );\r\n}\r\n","import { useState, useCallback } from \"react\";\r\nimport type { SupportChatState } from \"./types\";\r\n\r\n/**\r\n * Hook that returns controls for opening/closing the support chat.\r\n * Use with `<SupportChatModal />` for modal-triggered chat.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { open, close, toggle, isOpen } = useSupportChat();\r\n * return <button onClick={open}>Contact Us</button>;\r\n * ```\r\n */\r\nexport function useSupportChat(): SupportChatState {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n const open = useCallback(() => setIsOpen(true), []);\r\n const close = useCallback(() => setIsOpen(false), []);\r\n const toggle = useCallback(() => setIsOpen((prev) => !prev), []);\r\n\r\n return { open, close, toggle, isOpen };\r\n}\r\n"]}
1
+ {"version":3,"sources":["../../src/client/context.ts","../../src/client/useChatEngine.ts","../../src/client/useColorScheme.ts","../../src/client/ChatBubble.tsx","../../src/client/SupportChatModal.tsx","../../src/client/useSupportChat.ts"],"names":["useState","useEffect","useRef","useCallback","useMemo","jsxs","Fragment","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { AnonymousContext } from \"./types\";\r\n\r\nconst SESSION_KEY = \"support-chat-session-id\";\r\n\r\n/** Generate a random session ID */\r\nfunction generateSessionId(): string {\r\n const chars =\r\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\r\n let id = \"\";\r\n for (let i = 0; i < 16; i++) {\r\n id += chars.charAt(Math.floor(Math.random() * chars.length));\r\n }\r\n return id;\r\n}\r\n\r\n/** Get or create a persistent session ID from localStorage */\r\nexport function getSessionId(): string {\r\n if (typeof window === \"undefined\") return generateSessionId();\r\n\r\n try {\r\n const existing = localStorage.getItem(SESSION_KEY);\r\n if (existing) return existing;\r\n\r\n const newId = generateSessionId();\r\n localStorage.setItem(SESSION_KEY, newId);\r\n return newId;\r\n } catch {\r\n // localStorage may be blocked (private browsing, etc.)\r\n return generateSessionId();\r\n }\r\n}\r\n\r\n/** Collect anonymous browser context */\r\nexport function collectAnonymousContext(): AnonymousContext {\r\n const sessionId = getSessionId();\r\n\r\n if (typeof window === \"undefined\") {\r\n return {\r\n pageUrl: \"\",\r\n referrer: \"\",\r\n userAgent: \"\",\r\n screenSize: \"\",\r\n timezone: \"\",\r\n sessionId,\r\n };\r\n }\r\n\r\n return {\r\n pageUrl: window.location.href,\r\n referrer: document.referrer,\r\n userAgent: navigator.userAgent,\r\n screenSize: `${window.screen.width}x${window.screen.height}`,\r\n timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,\r\n sessionId,\r\n };\r\n}\r\n","import { useState, useCallback, useEffect, useRef } from \"react\";\r\nimport type { ChatMessage, ChatUser } from \"./types\";\r\nimport { collectAnonymousContext, getSessionId } from \"./context\";\r\n\r\n/** Options for initializing the chat engine */\r\nexport interface ChatEngineOptions {\r\n /** URL of the support chat API endpoint */\r\n apiUrl: string;\r\n /** Authenticated user info (optional) */\r\n user?: ChatUser;\r\n /** URL to poll for team replies. When set, polling starts while chat is active. */\r\n repliesUrl?: string;\r\n /** Whether the chat panel is currently visible (controls polling lifecycle). Defaults to true. */\r\n isOpen?: boolean;\r\n}\r\n\r\n/** Return value from useChatEngine */\r\nexport interface ChatEngineState {\r\n /** All chat messages */\r\n messages: ChatMessage[];\r\n /** Current input value */\r\n input: string;\r\n /** Update the input value */\r\n setInput: (value: string) => void;\r\n /** Whether a message is currently being sent */\r\n sending: boolean;\r\n /** Send the current input as a message */\r\n sendMessage: () => Promise<void>;\r\n /** Handle keydown on the input (Enter to send) */\r\n handleKeyDown: (e: React.KeyboardEvent) => void;\r\n}\r\n\r\n/** Shape of the replies endpoint response */\r\ninterface RepliesApiResponse {\r\n replies: Array<{\r\n id: string;\r\n text: string;\r\n sender: string;\r\n timestamp: string;\r\n threadTs: string;\r\n }>;\r\n lastChecked: string;\r\n}\r\n\r\n/** Polling interval in milliseconds */\r\nconst POLL_INTERVAL = 4000;\r\n\r\n/**\r\n * Shared chat engine hook used by both ChatBubble and SupportChatModal.\r\n * Manages message state, input, API communication, and reply polling.\r\n */\r\nexport function useChatEngine({\r\n apiUrl,\r\n user,\r\n repliesUrl,\r\n isOpen = true,\r\n}: ChatEngineOptions): ChatEngineState {\r\n const [messages, setMessages] = useState<ChatMessage[]>([]);\r\n const [input, setInput] = useState(\"\");\r\n const [sending, setSending] = useState(false);\r\n\r\n // Track the latest reply timestamp for incremental polling (since parameter)\r\n const lastReplyTimestampRef = useRef<string | null>(null);\r\n // Track known reply IDs for deduplication\r\n const knownReplyIdsRef = useRef<Set<string>>(new Set());\r\n\r\n const sendMessage = useCallback(async () => {\r\n const text = input.trim();\r\n if (!text || sending) return;\r\n\r\n const msg: ChatMessage = {\r\n id: `msg-${Date.now()}`,\r\n text,\r\n sender: \"user\",\r\n timestamp: Date.now(),\r\n };\r\n\r\n setMessages((prev) => [...prev, msg]);\r\n setInput(\"\");\r\n setSending(true);\r\n\r\n try {\r\n const context = collectAnonymousContext();\r\n const response = await fetch(apiUrl, {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify({\r\n message: text,\r\n user: user ?? undefined,\r\n sessionId: user?.id ?? getSessionId(),\r\n context,\r\n }),\r\n });\r\n\r\n if (!response.ok) {\r\n setMessages((prev) => [\r\n ...prev,\r\n {\r\n id: `err-${Date.now()}`,\r\n text: \"Failed to send message. Please try again.\",\r\n sender: \"system\",\r\n timestamp: Date.now(),\r\n },\r\n ]);\r\n }\r\n } catch {\r\n setMessages((prev) => [\r\n ...prev,\r\n {\r\n id: `err-${Date.now()}`,\r\n text: \"Connection error. Please try again.\",\r\n sender: \"system\",\r\n timestamp: Date.now(),\r\n },\r\n ]);\r\n } finally {\r\n setSending(false);\r\n }\r\n }, [input, sending, apiUrl, user]);\r\n\r\n const handleKeyDown = useCallback(\r\n (e: React.KeyboardEvent) => {\r\n if (e.key === \"Enter\" && !e.shiftKey) {\r\n e.preventDefault();\r\n void sendMessage();\r\n }\r\n },\r\n [sendMessage],\r\n );\r\n\r\n // Reply polling effect\r\n useEffect(() => {\r\n if (!repliesUrl || !isOpen) return;\r\n\r\n const sessionId = user?.id ?? getSessionId();\r\n\r\n const fetchReplies = async () => {\r\n try {\r\n const params = new URLSearchParams({ sessionId });\r\n if (lastReplyTimestampRef.current) {\r\n params.set(\"since\", lastReplyTimestampRef.current);\r\n }\r\n\r\n const response = await fetch(`${repliesUrl}?${params.toString()}`);\r\n if (!response.ok) return;\r\n\r\n const data: RepliesApiResponse = await response.json();\r\n\r\n if (data.replies.length === 0) return;\r\n\r\n // Filter out already-known replies (deduplication)\r\n const newReplies = data.replies.filter(\r\n (r) => !knownReplyIdsRef.current.has(r.id),\r\n );\r\n\r\n if (newReplies.length === 0) return;\r\n\r\n // Track new reply IDs\r\n for (const r of newReplies) {\r\n knownReplyIdsRef.current.add(r.id);\r\n }\r\n\r\n // Update the since cursor to the latest reply timestamp\r\n const latestTimestamp = newReplies.reduce((latest, r) => {\r\n return r.timestamp > latest ? r.timestamp : latest;\r\n }, lastReplyTimestampRef.current ?? \"\");\r\n lastReplyTimestampRef.current = latestTimestamp;\r\n\r\n // Convert server replies to ChatMessage format and append\r\n const replyMessages: ChatMessage[] = newReplies.map((r) => ({\r\n id: r.id,\r\n text: r.text,\r\n sender: \"received\" as const,\r\n timestamp: new Date(r.timestamp).getTime(),\r\n }));\r\n\r\n setMessages((prev) => [...prev, ...replyMessages]);\r\n } catch {\r\n // Silently ignore polling errors — don't spam the user with error messages\r\n }\r\n };\r\n\r\n // Fetch immediately, then set up interval\r\n void fetchReplies();\r\n const intervalId = setInterval(() => void fetchReplies(), POLL_INTERVAL);\r\n\r\n return () => clearInterval(intervalId);\r\n }, [repliesUrl, isOpen, user]);\r\n\r\n return { messages, input, setInput, sending, sendMessage, handleKeyDown };\r\n}\r\n","import { useState, useEffect } from \"react\";\r\n\r\nexport type ColorScheme = \"light\" | \"dark\";\r\n\r\n/**\r\n * Detects the user's system color scheme preference via `prefers-color-scheme`.\r\n * Returns \"dark\" or \"light\". Updates reactively if the user changes their system setting.\r\n */\r\nexport function useColorScheme(): ColorScheme {\r\n const [scheme, setScheme] = useState<ColorScheme>(() => {\r\n if (typeof window === \"undefined\") return \"light\";\r\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches\r\n ? \"dark\"\r\n : \"light\";\r\n });\r\n\r\n useEffect(() => {\r\n if (typeof window === \"undefined\") return;\r\n const mq = window.matchMedia(\"(prefers-color-scheme: dark)\");\r\n const handler = (e: MediaQueryListEvent) => {\r\n setScheme(e.matches ? \"dark\" : \"light\");\r\n };\r\n mq.addEventListener(\"change\", handler);\r\n return () => mq.removeEventListener(\"change\", handler);\r\n }, []);\r\n\r\n return scheme;\r\n}\r\n\r\n/** Resolved color tokens for light and dark modes. */\r\nexport interface ThemeTokens {\r\n panelBg: string;\r\n panelBorder: string;\r\n inputBg: string;\r\n inputBorder: string;\r\n inputText: string;\r\n inputPlaceholder: string;\r\n receivedBg: string;\r\n receivedText: string;\r\n emptyText: string;\r\n inputAreaBorder: string;\r\n}\r\n\r\nconst lightTokens: ThemeTokens = {\r\n panelBg: \"#ffffff\",\r\n panelBorder: \"#e5e7eb\",\r\n inputBg: \"#ffffff\",\r\n inputBorder: \"#d1d5db\",\r\n inputText: \"#1f2937\",\r\n inputPlaceholder: \"#9ca3af\",\r\n receivedBg: \"#f3f4f6\",\r\n receivedText: \"#1f2937\",\r\n emptyText: \"#9ca3af\",\r\n inputAreaBorder: \"#e5e7eb\",\r\n};\r\n\r\nconst darkTokens: ThemeTokens = {\r\n panelBg: \"#1f2937\",\r\n panelBorder: \"#374151\",\r\n inputBg: \"#111827\",\r\n inputBorder: \"#4b5563\",\r\n inputText: \"#f9fafb\",\r\n inputPlaceholder: \"#9ca3af\",\r\n receivedBg: \"#374151\",\r\n receivedText: \"#f3f4f6\",\r\n emptyText: \"#6b7280\",\r\n inputAreaBorder: \"#374151\",\r\n};\r\n\r\nexport function getThemeTokens(scheme: ColorScheme): ThemeTokens {\r\n return scheme === \"dark\" ? darkTokens : lightTokens;\r\n}\r\n","import { useState, useCallback, useRef, useEffect, useMemo } from \"react\";\r\nimport type { ChatBubbleProps } from \"./types\";\r\nimport { useChatEngine } from \"./useChatEngine\";\r\nimport { useColorScheme, getThemeTokens } from \"./useColorScheme\";\r\n\r\n/**\r\n * Inject a <style> tag for keyframe animations.\r\n * Called once on first mount. Uses a data-attribute to avoid duplicates.\r\n */\r\nfunction injectKeyframes(): void {\r\n if (typeof document === \"undefined\") return;\r\n if (document.querySelector(\"[data-support-chat-keyframes]\")) return;\r\n\r\n const style = document.createElement(\"style\");\r\n style.setAttribute(\"data-support-chat-keyframes\", \"\");\r\n style.textContent = `\r\n @keyframes sc-slide-in {\r\n from { opacity: 0; transform: translateY(12px) scale(0.96); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n @keyframes sc-slide-out {\r\n from { opacity: 1; transform: translateY(0) scale(1); }\r\n to { opacity: 0; transform: translateY(12px) scale(0.96); }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/**\r\n * ChatBubble -- floating support chat widget.\r\n *\r\n * Renders a circular button that opens an inline chat panel.\r\n * Messages are sent via POST to the configured `apiUrl`.\r\n *\r\n * @example\r\n * ```tsx\r\n * <ChatBubble apiUrl=\"/api/support\" />\r\n * ```\r\n */\r\nexport function ChatBubble({\r\n apiUrl,\r\n position = \"bottom-right\",\r\n color = \"#2563eb\",\r\n title = \"Support\",\r\n placeholder = \"Type a message...\",\r\n show = true,\r\n user,\r\n repliesUrl,\r\n}: ChatBubbleProps) {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n // Detect system color scheme\r\n const colorScheme = useColorScheme();\r\n const theme = useMemo(() => getThemeTokens(colorScheme), [colorScheme]);\r\n\r\n // Use shared chat engine for message logic\r\n const { messages, input, setInput, sending, sendMessage, handleKeyDown } =\r\n useChatEngine({ apiUrl, user, repliesUrl, isOpen });\r\n\r\n // Animation state: \"open\" | \"closing\" | \"closed\"\r\n const [panelState, setPanelState] = useState<\"open\" | \"closing\" | \"closed\">(\r\n \"closed\",\r\n );\r\n\r\n const panelRef = useRef<HTMLDivElement>(null);\r\n const messagesEndRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n\r\n // Inject keyframe animations on first render\r\n useEffect(() => {\r\n injectKeyframes();\r\n }, []);\r\n\r\n // Sync isOpen -> panelState\r\n useEffect(() => {\r\n if (isOpen) {\r\n setPanelState(\"open\");\r\n } else if (panelState === \"open\") {\r\n // Start closing animation\r\n setPanelState(\"closing\");\r\n }\r\n }, [isOpen]);\r\n\r\n // When closing animation ends, set to fully closed\r\n const handleAnimationEnd = useCallback(() => {\r\n if (panelState === \"closing\") {\r\n setPanelState(\"closed\");\r\n }\r\n }, [panelState]);\r\n\r\n // Focus input when panel opens\r\n useEffect(() => {\r\n if (panelState === \"open\" && inputRef.current) {\r\n inputRef.current.focus();\r\n }\r\n }, [panelState]);\r\n\r\n // Auto-scroll to latest message\r\n useEffect(() => {\r\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\r\n }, [messages]);\r\n\r\n // Focus trap: keep Tab cycling inside the dialog when open\r\n useEffect(() => {\r\n if (panelState !== \"open\" || !panelRef.current) return;\r\n\r\n const panel = panelRef.current;\r\n\r\n const handleTrapKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n setIsOpen(false);\r\n return;\r\n }\r\n\r\n if (e.key !== \"Tab\") return;\r\n\r\n const focusable = panel.querySelectorAll<HTMLElement>(\r\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\r\n );\r\n if (focusable.length === 0) return;\r\n\r\n const first = focusable[0]!;\r\n const last = focusable[focusable.length - 1]!;\r\n\r\n if (e.shiftKey) {\r\n if (document.activeElement === first) {\r\n e.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (document.activeElement === last) {\r\n e.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleTrapKeyDown);\r\n return () => document.removeEventListener(\"keydown\", handleTrapKeyDown);\r\n }, [panelState]);\r\n\r\n const toggle = useCallback(() => setIsOpen((o) => !o), []);\r\n\r\n // Position styles for the bubble button\r\n const positionStyles: React.CSSProperties = {\r\n position: \"fixed\",\r\n zIndex: 99999,\r\n ...(position.includes(\"bottom\") ? { bottom: \"20px\" } : { top: \"20px\" }),\r\n ...(position.includes(\"right\") ? { right: \"20px\" } : { left: \"20px\" }),\r\n };\r\n\r\n // Panel position & responsive sizing\r\n // On mobile (<480px viewport), expand to near-full screen\r\n const panelPositionStyles: React.CSSProperties = {\r\n position: \"fixed\",\r\n zIndex: 99999,\r\n width: \"380px\",\r\n maxWidth: \"calc(100vw - 40px)\",\r\n height: \"500px\",\r\n maxHeight: \"calc(100vh - 120px)\",\r\n ...(position.includes(\"bottom\") ? { bottom: \"80px\" } : { top: \"80px\" }),\r\n ...(position.includes(\"right\") ? { right: \"20px\" } : { left: \"20px\" }),\r\n };\r\n\r\n // Check if panel is visible (open or animating out)\r\n const showPanel = panelState === \"open\" || panelState === \"closing\";\r\n\r\n return (\r\n <>\r\n {/* Responsive overrides injected as inline <style> */}\r\n {showPanel && (\r\n <style>{`\r\n @media (max-width: 479px) {\r\n [data-support-chat-panel] {\r\n width: calc(100vw - 16px) !important;\r\n height: calc(100vh - 100px) !important;\r\n max-width: none !important;\r\n max-height: none !important;\r\n left: 8px !important;\r\n right: 8px !important;\r\n bottom: 72px !important;\r\n border-radius: 12px !important;\r\n }\r\n }\r\n `}</style>\r\n )}\r\n\r\n {/* Floating bubble button */}\r\n {show && (\r\n <button\r\n onClick={toggle}\r\n aria-label={isOpen ? \"Close support chat\" : \"Open support chat\"}\r\n aria-expanded={isOpen}\r\n aria-haspopup=\"dialog\"\r\n style={{\r\n ...positionStyles,\r\n width: \"56px\",\r\n height: \"56px\",\r\n borderRadius: \"50%\",\r\n backgroundColor: color,\r\n border: \"none\",\r\n cursor: \"pointer\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n boxShadow: \"0 4px 12px rgba(0,0,0,0.15)\",\r\n transition: \"transform 0.2s ease, box-shadow 0.2s ease\",\r\n }}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = \"scale(1.1)\";\r\n e.currentTarget.style.boxShadow = \"0 6px 20px rgba(0,0,0,0.2)\";\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = \"scale(1)\";\r\n e.currentTarget.style.boxShadow = \"0 4px 12px rgba(0,0,0,0.15)\";\r\n }}\r\n >\r\n <svg\r\n width=\"24\"\r\n height=\"24\"\r\n viewBox=\"0 0 24 24\"\r\n fill=\"none\"\r\n stroke=\"white\"\r\n strokeWidth=\"2\"\r\n strokeLinecap=\"round\"\r\n strokeLinejoin=\"round\"\r\n aria-hidden=\"true\"\r\n >\r\n {isOpen ? (\r\n <path d=\"M18 6L6 18M6 6l12 12\" />\r\n ) : (\r\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\" />\r\n )}\r\n </svg>\r\n </button>\r\n )}\r\n\r\n {/* Chat panel */}\r\n {showPanel && (\r\n <div\r\n ref={panelRef}\r\n role=\"dialog\"\r\n aria-label=\"Support chat\"\r\n aria-modal=\"true\"\r\n data-support-chat-panel=\"\"\r\n onAnimationEnd={handleAnimationEnd}\r\n style={{\r\n ...panelPositionStyles,\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n borderRadius: \"12px\",\r\n overflow: \"hidden\",\r\n boxShadow: \"0 8px 30px rgba(0,0,0,0.12)\",\r\n fontFamily:\r\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n fontSize: \"14px\",\r\n backgroundColor: theme.panelBg,\r\n border: `1px solid ${theme.panelBorder}`,\r\n animation:\r\n panelState === \"open\"\r\n ? \"sc-slide-in 0.25s ease-out forwards\"\r\n : \"sc-slide-out 0.2s ease-in forwards\",\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n padding: \"16px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <span style={{ fontWeight: 600, fontSize: \"16px\" }}>{title}</span>\r\n <button\r\n onClick={() => setIsOpen(false)}\r\n aria-label=\"Close chat\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n lineHeight: 1,\r\n fontSize: \"18px\",\r\n }}\r\n >\r\n &#x2715;\r\n </button>\r\n </div>\r\n\r\n {/* Messages */}\r\n <div\r\n role=\"log\"\r\n aria-live=\"polite\"\r\n aria-label=\"Chat messages\"\r\n style={{\r\n flex: 1,\r\n overflowY: \"auto\",\r\n padding: \"16px\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n {messages.length === 0 && (\r\n <div\r\n style={{\r\n color: theme.emptyText,\r\n textAlign: \"center\",\r\n marginTop: \"40px\",\r\n }}\r\n >\r\n Send us a message and we will get back to you!\r\n </div>\r\n )}\r\n {messages.map((msg) => (\r\n <div\r\n key={msg.id}\r\n data-sender={msg.sender}\r\n style={{\r\n alignSelf:\r\n msg.sender === \"user\" ? \"flex-end\" : \"flex-start\",\r\n maxWidth: \"80%\",\r\n padding: \"10px 14px\",\r\n borderRadius:\r\n msg.sender === \"user\"\r\n ? \"16px 16px 4px 16px\"\r\n : \"16px 16px 16px 4px\",\r\n backgroundColor:\r\n msg.sender === \"user\" ? color : theme.receivedBg,\r\n color: msg.sender === \"user\" ? \"#fff\" : theme.receivedText,\r\n wordBreak: \"break-word\",\r\n }}\r\n >\r\n {msg.text}\r\n </div>\r\n ))}\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {/* Input */}\r\n <div\r\n style={{\r\n borderTop: `1px solid ${theme.inputAreaBorder}`,\r\n padding: \"12px\",\r\n display: \"flex\",\r\n gap: \"8px\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={input}\r\n onChange={(e) => setInput(e.target.value)}\r\n onKeyDown={handleKeyDown}\r\n placeholder={placeholder}\r\n aria-label=\"Type your message\"\r\n style={{\r\n flex: 1,\r\n border: `1px solid ${theme.inputBorder}`,\r\n borderRadius: \"8px\",\r\n padding: \"10px 12px\",\r\n fontSize: \"14px\",\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n backgroundColor: theme.inputBg,\r\n color: theme.inputText,\r\n }}\r\n />\r\n <button\r\n onClick={() => void sendMessage()}\r\n disabled={sending || !input.trim()}\r\n aria-label=\"Send message\"\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n border: \"none\",\r\n borderRadius: \"8px\",\r\n padding: \"10px 16px\",\r\n cursor:\r\n sending || !input.trim() ? \"not-allowed\" : \"pointer\",\r\n opacity: sending || !input.trim() ? 0.5 : 1,\r\n fontWeight: 600,\r\n fontSize: \"14px\",\r\n fontFamily: \"inherit\",\r\n transition: \"opacity 0.2s ease\",\r\n }}\r\n >\r\n Send\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </>\r\n );\r\n}\r\n","import { useRef, useEffect, useCallback, useMemo } from \"react\";\r\nimport type { SupportChatModalProps } from \"./types\";\r\nimport { useChatEngine } from \"./useChatEngine\";\r\nimport { useColorScheme, getThemeTokens } from \"./useColorScheme\";\r\n\r\n/**\r\n * Inject a <style> tag for modal keyframe animations.\r\n * Called once on first mount. Uses a data-attribute to avoid duplicates.\r\n */\r\nfunction injectModalKeyframes(): void {\r\n if (typeof document === \"undefined\") return;\r\n if (document.querySelector(\"[data-support-chat-modal-keyframes]\")) return;\r\n\r\n const style = document.createElement(\"style\");\r\n style.setAttribute(\"data-support-chat-modal-keyframes\", \"\");\r\n style.textContent = `\r\n @keyframes sc-modal-backdrop-in {\r\n from { opacity: 0; }\r\n to { opacity: 1; }\r\n }\r\n @keyframes sc-modal-backdrop-out {\r\n from { opacity: 1; }\r\n to { opacity: 0; }\r\n }\r\n @keyframes sc-modal-slide-in {\r\n from { opacity: 0; transform: translateY(20px) scale(0.96); }\r\n to { opacity: 1; transform: translateY(0) scale(1); }\r\n }\r\n @keyframes sc-modal-slide-out {\r\n from { opacity: 1; transform: translateY(0) scale(1); }\r\n to { opacity: 0; transform: translateY(20px) scale(0.96); }\r\n }\r\n `;\r\n document.head.appendChild(style);\r\n}\r\n\r\n/**\r\n * SupportChatModal -- modal-based support chat.\r\n *\r\n * Renders a centered modal dialog for support chat, designed to be\r\n * triggered from custom UI elements (e.g., \"Contact Us\" links).\r\n *\r\n * Desktop: centered modal, max-width ~500px, with backdrop overlay.\r\n * Mobile: full-screen modal.\r\n *\r\n * Use with the `useSupportChat()` hook:\r\n *\r\n * @example\r\n * ```tsx\r\n * import { SupportChatModal, useSupportChat } from 'support-chat';\r\n *\r\n * function App() {\r\n * const { open, close, isOpen } = useSupportChat();\r\n * return (\r\n * <>\r\n * <button onClick={open}>Contact Us</button>\r\n * <SupportChatModal\r\n * apiUrl=\"/api/support\"\r\n * isOpen={isOpen}\r\n * onClose={close}\r\n * />\r\n * </>\r\n * );\r\n * }\r\n * ```\r\n */\r\nexport function SupportChatModal({\r\n apiUrl,\r\n isOpen,\r\n onClose,\r\n color = \"#2563eb\",\r\n title = \"Contact Us\",\r\n placeholder = \"Type a message...\",\r\n user,\r\n repliesUrl,\r\n}: SupportChatModalProps) {\r\n const { messages, input, setInput, sending, sendMessage, handleKeyDown } =\r\n useChatEngine({ apiUrl, user, repliesUrl, isOpen });\r\n\r\n const colorScheme = useColorScheme();\r\n const theme = useMemo(() => getThemeTokens(colorScheme), [colorScheme]);\r\n\r\n const modalRef = useRef<HTMLDivElement>(null);\r\n const inputRef = useRef<HTMLInputElement>(null);\r\n const messagesEndRef = useRef<HTMLDivElement>(null);\r\n const closingRef = useRef(false);\r\n const backdropRef = useRef<HTMLDivElement>(null);\r\n\r\n // Inject modal keyframes on first render\r\n useEffect(() => {\r\n injectModalKeyframes();\r\n }, []);\r\n\r\n // Focus input when modal opens\r\n useEffect(() => {\r\n if (isOpen && inputRef.current) {\r\n // Small delay to let the modal render before focusing\r\n const timer = setTimeout(() => inputRef.current?.focus(), 50);\r\n return () => clearTimeout(timer);\r\n }\r\n }, [isOpen]);\r\n\r\n // Auto-scroll to latest message\r\n useEffect(() => {\r\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\r\n }, [messages]);\r\n\r\n // Prevent body scroll when modal is open\r\n useEffect(() => {\r\n if (isOpen) {\r\n const prev = document.body.style.overflow;\r\n document.body.style.overflow = \"hidden\";\r\n return () => {\r\n document.body.style.overflow = prev;\r\n };\r\n }\r\n }, [isOpen]);\r\n\r\n // Focus trap and Escape key handling\r\n useEffect(() => {\r\n if (!isOpen || !modalRef.current) return;\r\n\r\n const modal = modalRef.current;\r\n\r\n const handleTrapKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\") {\r\n onClose();\r\n return;\r\n }\r\n\r\n if (e.key !== \"Tab\") return;\r\n\r\n const focusable = modal.querySelectorAll<HTMLElement>(\r\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\r\n );\r\n if (focusable.length === 0) return;\r\n\r\n const first = focusable[0]!;\r\n const last = focusable[focusable.length - 1]!;\r\n\r\n if (e.shiftKey) {\r\n if (document.activeElement === first) {\r\n e.preventDefault();\r\n last.focus();\r\n }\r\n } else {\r\n if (document.activeElement === last) {\r\n e.preventDefault();\r\n first.focus();\r\n }\r\n }\r\n };\r\n\r\n document.addEventListener(\"keydown\", handleTrapKeyDown);\r\n return () => document.removeEventListener(\"keydown\", handleTrapKeyDown);\r\n }, [isOpen, onClose]);\r\n\r\n // Handle close with animation\r\n const handleClose = useCallback(() => {\r\n closingRef.current = true;\r\n onClose();\r\n }, [onClose]);\r\n\r\n // Handle backdrop click\r\n const handleBackdropClick = useCallback(\r\n (e: React.MouseEvent) => {\r\n if (e.target === e.currentTarget) {\r\n handleClose();\r\n }\r\n },\r\n [handleClose],\r\n );\r\n\r\n if (!isOpen) return null;\r\n\r\n return (\r\n <>\r\n {/* Responsive style for full-screen on mobile */}\r\n <style>{`\r\n @media (max-width: 479px) {\r\n [data-support-chat-modal] {\r\n width: 100vw !important;\r\n height: 100vh !important;\r\n max-width: none !important;\r\n max-height: none !important;\r\n border-radius: 0 !important;\r\n margin: 0 !important;\r\n }\r\n [data-support-chat-modal-backdrop] {\r\n align-items: stretch !important;\r\n justify-content: stretch !important;\r\n }\r\n }\r\n `}</style>\r\n\r\n {/* Backdrop overlay */}\r\n <div\r\n ref={backdropRef}\r\n data-support-chat-modal-backdrop=\"\"\r\n onClick={handleBackdropClick}\r\n style={{\r\n position: \"fixed\",\r\n inset: 0,\r\n zIndex: 100000,\r\n backgroundColor: \"rgba(0, 0, 0, 0.5)\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"center\",\r\n fontFamily:\r\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\r\n fontSize: \"14px\",\r\n animation: \"sc-modal-backdrop-in 0.2s ease-out forwards\",\r\n }}\r\n >\r\n {/* Modal dialog */}\r\n <div\r\n ref={modalRef}\r\n role=\"dialog\"\r\n aria-label=\"Support chat\"\r\n aria-modal=\"true\"\r\n data-support-chat-modal=\"\"\r\n style={{\r\n width: \"500px\",\r\n maxWidth: \"calc(100vw - 32px)\",\r\n height: \"600px\",\r\n maxHeight: \"calc(100vh - 64px)\",\r\n backgroundColor: theme.panelBg,\r\n borderRadius: \"16px\",\r\n overflow: \"hidden\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n boxShadow: \"0 20px 60px rgba(0, 0, 0, 0.2)\",\r\n animation: \"sc-modal-slide-in 0.3s ease-out forwards\",\r\n }}\r\n >\r\n {/* Header */}\r\n <div\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n padding: \"20px 24px\",\r\n display: \"flex\",\r\n alignItems: \"center\",\r\n justifyContent: \"space-between\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <span style={{ fontWeight: 600, fontSize: \"18px\" }}>{title}</span>\r\n <button\r\n onClick={handleClose}\r\n aria-label=\"Close chat\"\r\n style={{\r\n background: \"none\",\r\n border: \"none\",\r\n color: \"#fff\",\r\n cursor: \"pointer\",\r\n padding: \"4px\",\r\n lineHeight: 1,\r\n fontSize: \"20px\",\r\n }}\r\n >\r\n &#x2715;\r\n </button>\r\n </div>\r\n\r\n {/* Messages */}\r\n <div\r\n role=\"log\"\r\n aria-live=\"polite\"\r\n aria-label=\"Chat messages\"\r\n style={{\r\n flex: 1,\r\n overflowY: \"auto\",\r\n padding: \"20px\",\r\n display: \"flex\",\r\n flexDirection: \"column\",\r\n gap: \"8px\",\r\n }}\r\n >\r\n {messages.length === 0 && (\r\n <div\r\n style={{\r\n color: theme.emptyText,\r\n textAlign: \"center\",\r\n marginTop: \"60px\",\r\n }}\r\n >\r\n Send us a message and we will get back to you!\r\n </div>\r\n )}\r\n {messages.map((msg) => (\r\n <div\r\n key={msg.id}\r\n data-sender={msg.sender}\r\n style={{\r\n alignSelf:\r\n msg.sender === \"user\" ? \"flex-end\" : \"flex-start\",\r\n maxWidth: \"80%\",\r\n padding: \"10px 14px\",\r\n borderRadius:\r\n msg.sender === \"user\"\r\n ? \"16px 16px 4px 16px\"\r\n : \"16px 16px 16px 4px\",\r\n backgroundColor:\r\n msg.sender === \"user\" ? color : theme.receivedBg,\r\n color: msg.sender === \"user\" ? \"#fff\" : theme.receivedText,\r\n wordBreak: \"break-word\",\r\n }}\r\n >\r\n {msg.text}\r\n </div>\r\n ))}\r\n <div ref={messagesEndRef} />\r\n </div>\r\n\r\n {/* Input */}\r\n <div\r\n style={{\r\n borderTop: `1px solid ${theme.inputAreaBorder}`,\r\n padding: \"16px 20px\",\r\n display: \"flex\",\r\n gap: \"8px\",\r\n flexShrink: 0,\r\n }}\r\n >\r\n <input\r\n ref={inputRef}\r\n type=\"text\"\r\n value={input}\r\n onChange={(e) => setInput(e.target.value)}\r\n onKeyDown={handleKeyDown}\r\n placeholder={placeholder}\r\n aria-label=\"Type your message\"\r\n style={{\r\n flex: 1,\r\n border: `1px solid ${theme.inputBorder}`,\r\n borderRadius: \"8px\",\r\n padding: \"12px 14px\",\r\n fontSize: \"14px\",\r\n outline: \"none\",\r\n fontFamily: \"inherit\",\r\n backgroundColor: theme.inputBg,\r\n color: theme.inputText,\r\n }}\r\n />\r\n <button\r\n onClick={() => void sendMessage()}\r\n disabled={sending || !input.trim()}\r\n aria-label=\"Send message\"\r\n style={{\r\n backgroundColor: color,\r\n color: \"#fff\",\r\n border: \"none\",\r\n borderRadius: \"8px\",\r\n padding: \"12px 20px\",\r\n cursor:\r\n sending || !input.trim() ? \"not-allowed\" : \"pointer\",\r\n opacity: sending || !input.trim() ? 0.5 : 1,\r\n fontWeight: 600,\r\n fontSize: \"14px\",\r\n fontFamily: \"inherit\",\r\n transition: \"opacity 0.2s ease\",\r\n }}\r\n >\r\n Send\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n </>\r\n );\r\n}\r\n","import { useState, useCallback } from \"react\";\r\nimport type { SupportChatState } from \"./types\";\r\n\r\n/**\r\n * Hook that returns controls for opening/closing the support chat.\r\n * Use with `<SupportChatModal />` for modal-triggered chat.\r\n *\r\n * @example\r\n * ```tsx\r\n * const { open, close, toggle, isOpen } = useSupportChat();\r\n * return <button onClick={open}>Contact Us</button>;\r\n * ```\r\n */\r\nexport function useSupportChat(): SupportChatState {\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n const open = useCallback(() => setIsOpen(true), []);\r\n const close = useCallback(() => setIsOpen(false), []);\r\n const toggle = useCallback(() => setIsOpen((prev) => !prev), []);\r\n\r\n return { open, close, toggle, isOpen };\r\n}\r\n"]}