@xcelsior/ui-chat 2.0.4 → 2.0.5

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.
Files changed (46) hide show
  1. package/dist/index.d.mts +58 -5
  2. package/dist/index.d.ts +58 -5
  3. package/dist/index.js +1042 -427
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +1009 -397
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +2 -1
  8. package/src/components/BookingCancelledCard.tsx +103 -0
  9. package/src/components/BookingCards.stories.tsx +102 -0
  10. package/src/components/BookingConfirmationCard.tsx +170 -0
  11. package/src/components/BookingSlotPicker.stories.tsx +87 -0
  12. package/src/components/BookingSlotPicker.tsx +253 -0
  13. package/src/components/BrandIcons.stories.tsx +32 -1
  14. package/src/components/BrandIcons.tsx +21 -17
  15. package/src/components/Chat.tsx +43 -9
  16. package/src/components/ChatWidget.tsx +30 -2
  17. package/src/components/MessageItem.tsx +83 -72
  18. package/src/components/MessageList.tsx +4 -0
  19. package/src/hooks/useDraggablePosition.ts +147 -42
  20. package/src/hooks/useMessages.ts +106 -53
  21. package/src/hooks/useWebSocket.ts +17 -4
  22. package/src/index.tsx +11 -0
  23. package/src/types.ts +39 -2
  24. package/src/utils/api.ts +1 -0
  25. package/storybook-static/assets/BookingCancelledCard-XHuB-Ebp.js +31 -0
  26. package/storybook-static/assets/BookingCards.stories-DfJ482RS.js +66 -0
  27. package/storybook-static/assets/BookingSlotPicker-BkfssueW.js +1 -0
  28. package/storybook-static/assets/BookingSlotPicker.stories-fYlg1zLg.js +50 -0
  29. package/storybook-static/assets/BrandIcons-BsRAdWzL.js +4 -0
  30. package/storybook-static/assets/BrandIcons.stories-C6gBovfU.js +106 -0
  31. package/storybook-static/assets/Chat.stories-BrR7LHsz.js +830 -0
  32. package/storybook-static/assets/{Color-YHDXOIA2-CSuNIR0a.js → Color-YHDXOIA2-azE51u2m.js} +1 -1
  33. package/storybook-static/assets/{DocsRenderer-CFRXHY34-dpuOKTQp.js → DocsRenderer-CFRXHY34-jTmzKIDk.js} +3 -3
  34. package/storybook-static/assets/MessageItem-pEOwuLyh.js +34 -0
  35. package/storybook-static/assets/{MessageItem.stories-CsxqSqu-.js → MessageItem.stories-Cs5Vtkle.js} +2 -2
  36. package/storybook-static/assets/{entry-preview-C_-WO6GJ.js → entry-preview-vcpiajAT.js} +1 -1
  37. package/storybook-static/assets/globe-BtMvkLMD.js +31 -0
  38. package/storybook-static/assets/{iframe-BXTccXxS.js → iframe-Cx1n-SeE.js} +2 -2
  39. package/storybook-static/assets/{preview-Cyx3pE7Q.js → preview-Do3b3dZv.js} +2 -2
  40. package/storybook-static/iframe.html +1 -1
  41. package/storybook-static/index.json +1 -1
  42. package/storybook-static/project.json +1 -1
  43. package/storybook-static/assets/BrandIcons-Cjy5INAp.js +0 -4
  44. package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +0 -64
  45. package/storybook-static/assets/Chat.stories-BkbpOOSG.js +0 -830
  46. package/storybook-static/assets/MessageItem-Dlb6dSKL.js +0 -14
@@ -4,7 +4,10 @@ import type { UseWebSocketReturn } from './useWebSocket';
4
4
  import { fetchMessages } from '../utils/api';
5
5
 
6
6
  /** Max time (ms) to show the bot thinking indicator before auto-clearing */
7
- const BOT_THINKING_TIMEOUT = 45_000;
7
+ const BOT_THINKING_TIMEOUT = 15_000;
8
+
9
+ /** Conversation statuses where the bot will NOT respond */
10
+ const AGENT_STATUSES = new Set(['agent_active', 'pending_agent']);
8
11
 
9
12
  export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig): UseMessagesReturn {
10
13
  const [messages, setMessages] = useState<IMessage[]>([]);
@@ -15,8 +18,9 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
15
18
  const [isLoadingMore, setIsLoadingMore] = useState(false);
16
19
  const [isBotThinking, setIsBotThinking] = useState(false);
17
20
  const botThinkingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
21
+ /** True when an agent has taken over — set from conversation status OR message history */
22
+ const agentActiveRef = useRef(false);
18
23
 
19
- // Extract stable references from config
20
24
  const { httpApiUrl, conversationId, headers, onError, toast } = config;
21
25
 
22
26
  const headersWithApiKey = useMemo(
@@ -27,14 +31,44 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
27
31
  [headers, config.apiKey]
28
32
  );
29
33
 
30
- // Fetch existing messages when conversationId changes
34
+ /** Clear bot thinking timer and state */
35
+ const clearBotThinking = useCallback(() => {
36
+ setIsBotThinking(false);
37
+ if (botThinkingTimerRef.current) {
38
+ clearTimeout(botThinkingTimerRef.current);
39
+ botThinkingTimerRef.current = null;
40
+ }
41
+ }, []);
42
+
43
+ // ── Fetch conversation status to seed agentActiveRef ──────────────
44
+ // This is the PRIMARY mechanism — not relying on message history pagination.
31
45
  useEffect(() => {
32
- const loadMessages = async () => {
33
- // Only fetch if we have httpApiUrl and conversationId
34
- if (!httpApiUrl || !conversationId) {
35
- return;
46
+ if (!httpApiUrl || !conversationId) return;
47
+
48
+ const fetchStatus = async () => {
49
+ try {
50
+ const url = `${httpApiUrl}/conversation?conversationId=${encodeURIComponent(conversationId)}`;
51
+ const res = await fetch(url, { headers: headersWithApiKey });
52
+ if (!res.ok) return;
53
+ const json = await res.json();
54
+ // Backend returns { data: { conversation: { status, ... } } } or { data: { status, ... } }
55
+ const conv = json.data?.conversation ?? json.data;
56
+ if (conv?.status && AGENT_STATUSES.has(conv.status)) {
57
+ agentActiveRef.current = true;
58
+ }
59
+ } catch {
60
+ // Non-critical — fallback to message history check
36
61
  }
62
+ };
63
+
64
+ fetchStatus();
65
+ }, [httpApiUrl, conversationId, headersWithApiKey]);
37
66
 
67
+ // ── Fetch existing messages ──────────────────────────────────────
68
+ useEffect(() => {
69
+ if (!httpApiUrl || !conversationId) return;
70
+
71
+ const loadMessages = async () => {
38
72
  setIsLoading(true);
39
73
  setError(null);
40
74
 
@@ -48,6 +82,10 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
48
82
  setMessages(result.data);
49
83
  setNextPageToken(result.nextPageToken);
50
84
  setHasMore(!!result.nextPageToken);
85
+ // Secondary check: agent messages in the visible page
86
+ if (result.data.some((m: IMessage) => m.senderType === 'agent')) {
87
+ agentActiveRef.current = true;
88
+ }
51
89
  } catch (err) {
52
90
  const error = err instanceof Error ? err : new Error('Failed to load messages');
53
91
  setError(error);
@@ -59,56 +97,85 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
59
97
  };
60
98
 
61
99
  loadMessages();
62
- // Only re-run when conversationId or httpApiUrl changes
63
100
  }, [conversationId, httpApiUrl, headersWithApiKey, onError, toast]);
64
101
 
65
- // Extract onMessageReceived callback
66
102
  const { onMessageReceived } = config;
67
103
 
68
- // Listen for incoming messages from WebSocket
104
+ // ── Listen for incoming messages from WebSocket ───────────────────
69
105
  useEffect(() => {
70
106
  if (websocket.lastMessage?.type === 'message' && websocket.lastMessage.data) {
71
107
  const newMessage: IMessage = websocket.lastMessage.data;
72
108
 
73
- if (conversationId && newMessage.conversationId !== conversationId) {
74
- // Ignore messages for other conversations
75
- return;
76
- }
109
+ if (conversationId && newMessage.conversationId !== conversationId) return;
110
+
77
111
  setMessages((prev) => {
78
- // Avoid duplicates
79
- if (prev.some((msg) => msg.id === newMessage.id)) {
80
- return prev;
112
+ if (prev.some((msg) => msg.id === newMessage.id)) return prev;
113
+ const newMsgTime = new Date(newMessage.createdAt).getTime();
114
+ const tempIdx = prev.findIndex(
115
+ (msg) =>
116
+ msg.id.startsWith('temp-') &&
117
+ msg.content === newMessage.content &&
118
+ msg.senderId === newMessage.senderId &&
119
+ Math.abs(new Date(msg.createdAt).getTime() - newMsgTime) < 10_000
120
+ );
121
+ if (tempIdx >= 0) {
122
+ const next = [...prev];
123
+ next[tempIdx] = newMessage;
124
+ return next;
81
125
  }
82
126
  return [...prev, newMessage];
83
127
  });
84
128
 
85
- // Clear bot thinking state when bot or system message arrives
86
- if (newMessage.senderType === 'bot' || newMessage.senderType === 'system') {
87
- setIsBotThinking(false);
88
- if (botThinkingTimerRef.current) {
89
- clearTimeout(botThinkingTimerRef.current);
90
- botThinkingTimerRef.current = null;
91
- }
129
+ if (newMessage.senderType === 'agent') {
130
+ agentActiveRef.current = true;
131
+ }
132
+
133
+ // Clear bot thinking when a non-customer message arrives
134
+ if (newMessage.senderType !== 'customer') {
135
+ clearBotThinking();
92
136
  }
93
137
 
94
- // Notify parent component about new message
95
138
  onMessageReceived?.(newMessage);
96
139
  }
97
- }, [websocket.lastMessage, onMessageReceived, conversationId]);
140
+ }, [websocket.lastMessage, onMessageReceived, conversationId, clearBotThinking]);
141
+
142
+ // ── Handle bot_not_responding + conversation_updated events ───────
143
+ useEffect(() => {
144
+ if (!websocket.lastMessage) return;
145
+ const { type, data } = websocket.lastMessage;
146
+
147
+ if (type === 'bot_not_responding') {
148
+ if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
149
+ clearBotThinking();
150
+ agentActiveRef.current = true;
151
+ }
98
152
 
153
+ if (type === 'conversation_updated') {
154
+ if (data?.conversationId && conversationId && data.conversationId !== conversationId) return;
155
+ if (data?.status && AGENT_STATUSES.has(data.status)) {
156
+ agentActiveRef.current = true;
157
+ clearBotThinking();
158
+ }
159
+ }
160
+ }, [websocket.lastMessage, conversationId, clearBotThinking]);
161
+
162
+ // ── addMessage (used for optimistic customer messages) ────────────
99
163
  const addMessage = useCallback((message: IMessage) => {
164
+ // Read agentActiveRef BEFORE the async setMessages
165
+ const isAgentActive = agentActiveRef.current;
166
+
100
167
  setMessages((prev) => {
101
- // Avoid duplicates
102
- if (prev.some((msg) => msg.id === message.id)) {
103
- return prev;
104
- }
168
+ if (prev.some((msg) => msg.id === message.id)) return prev;
105
169
  return [...prev, message];
106
170
  });
107
- // Show bot thinking indicator immediately when customer sends a message
108
- if (message.senderType === 'customer') {
109
- setIsBotThinking(true);
110
171
 
111
- // Safety timeout — clear thinking state if no bot response arrives
172
+ if (message.senderType === 'agent') {
173
+ agentActiveRef.current = true;
174
+ }
175
+
176
+ // Only show bot thinking for customer messages when NO agent is active
177
+ if (message.senderType === 'customer' && !isAgentActive) {
178
+ setIsBotThinking(true);
112
179
  if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
113
180
  botThinkingTimerRef.current = setTimeout(() => {
114
181
  setIsBotThinking(false);
@@ -122,12 +189,11 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
122
189
 
123
190
  const clearMessages = useCallback(() => {
124
191
  setMessages([]);
192
+ agentActiveRef.current = false;
125
193
  }, []);
126
194
 
127
195
  const loadMore = useCallback(async () => {
128
- if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) {
129
- return;
130
- }
196
+ if (!hasMore || isLoadingMore || !httpApiUrl || !conversationId || !nextPageToken) return;
131
197
 
132
198
  setIsLoadingMore(true);
133
199
  setError(null);
@@ -135,11 +201,7 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
135
201
  try {
136
202
  const result = await fetchMessages(
137
203
  httpApiUrl,
138
- {
139
- conversationId,
140
- limit: 20,
141
- pageToken: nextPageToken,
142
- },
204
+ { conversationId, limit: 20, pageToken: nextPageToken },
143
205
  headersWithApiKey
144
206
  );
145
207
 
@@ -153,17 +215,8 @@ export function useMessages(websocket: UseWebSocketReturn, config: IChatConfig):
153
215
  } finally {
154
216
  setIsLoadingMore(false);
155
217
  }
156
- }, [
157
- hasMore,
158
- isLoadingMore,
159
- httpApiUrl,
160
- conversationId,
161
- nextPageToken,
162
- headersWithApiKey,
163
- onError,
164
- ]);
165
-
166
- // Clean up timer on unmount
218
+ }, [hasMore, isLoadingMore, httpApiUrl, conversationId, nextPageToken, headersWithApiKey, onError]);
219
+
167
220
  useEffect(() => {
168
221
  return () => {
169
222
  if (botThinkingTimerRef.current) clearTimeout(botThinkingTimerRef.current);
@@ -28,6 +28,13 @@ export function useWebSocket(
28
28
  const reconnectAttemptsRef = useRef(0);
29
29
  const messageHandlerRef = useRef<((event: MessageEvent) => void) | null>(null);
30
30
  const abortedRef = useRef(false);
31
+ /**
32
+ * Monotonically increasing counter incremented each time connect() is called.
33
+ * Each WebSocket closure captures the generation at open time; if the generation
34
+ * has advanced by the time onclose fires, the close event belongs to a stale
35
+ * connection and must not trigger reconnection.
36
+ */
37
+ const connectionGenerationRef = useRef(0);
31
38
  const maxReconnectAttempts = 5;
32
39
  const reconnectDelay = 3000;
33
40
 
@@ -47,10 +54,8 @@ export function useWebSocket(
47
54
  const message: IWebSocketMessage = JSON.parse(event.data);
48
55
  setLastMessage(message);
49
56
 
50
- // Handle different message types
51
- if (message.type === 'message' && message.data) {
52
- config.onMessageReceived?.(message.data);
53
- } else if (message.type === 'error') {
57
+ // Handle error messages — regular messages are handled by useMessages via lastMessage
58
+ if (message.type === 'error') {
54
59
  const err = new Error(message.data?.message || 'WebSocket error');
55
60
  setError(err);
56
61
  config.onError?.(err);
@@ -78,6 +83,10 @@ export function useWebSocket(
78
83
  // Skip if the effect was already cleaned up (React Strict Mode)
79
84
  if (abortedRef.current) return;
80
85
 
86
+ // Capture this connection's generation so that stale onclose/onopen
87
+ // callbacks from a previous WebSocket instance are ignored.
88
+ const generation = ++connectionGenerationRef.current;
89
+
81
90
  console.log('connecting to WebSocket...', config.currentUser, config.conversationId);
82
91
  try {
83
92
  // Clean up existing connection
@@ -103,6 +112,7 @@ export function useWebSocket(
103
112
  const ws = new WebSocket(url.toString());
104
113
 
105
114
  ws.onopen = () => {
115
+ if (generation !== connectionGenerationRef.current) return;
106
116
  if (abortedRef.current) {
107
117
  ws.close(1000, 'Effect cleaned up');
108
118
  return;
@@ -115,6 +125,7 @@ export function useWebSocket(
115
125
  };
116
126
 
117
127
  ws.onerror = (event) => {
128
+ if (generation !== connectionGenerationRef.current) return;
118
129
  if (abortedRef.current) return;
119
130
  console.error('WebSocket error:', event);
120
131
  const err = new Error('WebSocket connection error');
@@ -123,6 +134,8 @@ export function useWebSocket(
123
134
  };
124
135
 
125
136
  ws.onclose = (event) => {
137
+ // Ignore close events from superseded connections (reconnect or re-mount).
138
+ if (generation !== connectionGenerationRef.current) return;
126
139
  if (abortedRef.current) return;
127
140
  console.log('WebSocket closed:', event.code, event.reason);
128
141
  setIsConnected(false);
package/src/index.tsx CHANGED
@@ -4,6 +4,9 @@ export type { ChatWidgetProps } from './components/ChatWidget';
4
4
  export { Chat } from './components/Chat';
5
5
 
6
6
  // Individual components (for custom implementations)
7
+ export { BookingCancelledCard } from './components/BookingCancelledCard';
8
+ export { BookingConfirmationCard } from './components/BookingConfirmationCard';
9
+ export { BookingSlotPicker } from './components/BookingSlotPicker';
7
10
  export { ChatHeader } from './components/ChatHeader';
8
11
  export { ChatInput } from './components/ChatInput';
9
12
  export { MarkdownMessage } from './components/MarkdownMessage';
@@ -48,6 +51,10 @@ import type {
48
51
  ConversationPriority,
49
52
  ConversationChannel,
50
53
  UseMessagesReturn,
54
+ IBookingSlot,
55
+ IBookingData,
56
+ IBookingConfirmationData,
57
+ IBookingCancelledData,
51
58
  } from './types';
52
59
 
53
60
  export type {
@@ -68,4 +75,8 @@ export type {
68
75
  ConversationPriority,
69
76
  ConversationChannel,
70
77
  UseMessagesReturn,
78
+ IBookingSlot,
79
+ IBookingData,
80
+ IBookingConfirmationData,
81
+ IBookingCancelledData,
71
82
  };
package/src/types.ts CHANGED
@@ -8,7 +8,7 @@ export interface IUser {
8
8
  }
9
9
 
10
10
  // Message types
11
- export type MessageType = 'text' | 'image' | 'file' | 'system';
11
+ export type MessageType = 'text' | 'image' | 'file' | 'system' | 'booking_slots' | 'booking_confirmation' | 'booking_cancelled';
12
12
  export type MessageStatus = 'sent' | 'delivered' | 'read';
13
13
 
14
14
  export interface IMessage {
@@ -49,9 +49,46 @@ export interface IConversation {
49
49
  metadata?: Record<string, unknown>;
50
50
  }
51
51
 
52
+ // Booking data interfaces
53
+ export interface IBookingSlot {
54
+ date: string; // ISO date: "2026-03-25"
55
+ dayLabel: string; // "Tue 25"
56
+ dayName: string; // "Tuesday"
57
+ slots: string[]; // ["09:00", "09:30", "10:00", ...]
58
+ }
59
+
60
+ export interface IBookingData {
61
+ availableDates: IBookingSlot[];
62
+ timezone: string;
63
+ meetingDuration: number;
64
+ }
65
+
66
+ export interface IBookingConfirmationData {
67
+ bookingId: string;
68
+ visitorName: string;
69
+ meetingDate: string;
70
+ meetingTime: string;
71
+ meetingDuration: number;
72
+ meetingPurpose: string;
73
+ meetLink?: string;
74
+ calendarLink?: string;
75
+ timezone: string;
76
+ }
77
+
78
+ export interface IBookingCancelledData {
79
+ bookingId: string;
80
+ meetingDate: string;
81
+ meetingTime: string;
82
+ meetingPurpose: string;
83
+ cancelledBy: 'visitor' | 'admin';
84
+ }
85
+
52
86
  // WebSocket message types
53
87
  export interface IWebSocketMessage {
54
- type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system' | 'escalation_requested' | 'bot_response' | 'conversation_updated';
88
+ type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system'
89
+ | 'escalation_requested' | 'bot_response' | 'conversation_updated'
90
+ | 'bot_not_responding'
91
+ | 'booking_slots' | 'booking_confirmation' | 'booking_cancelled';
55
92
  data: any;
56
93
  }
57
94
 
package/src/utils/api.ts CHANGED
@@ -26,6 +26,7 @@ export async function fetchMessages(
26
26
  'Content-Type': 'application/json',
27
27
  ...headers,
28
28
  },
29
+ timeout: 8000,
29
30
  });
30
31
 
31
32
  return {
@@ -0,0 +1,31 @@
1
+ import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";import{c as l,C as j,G as S}from"./globe-BtMvkLMD.js";/**
2
+ * @license lucide-react v0.477.0 - ISC
3
+ *
4
+ * This source code is licensed under the ISC license.
5
+ * See the LICENSE file in the root directory of this source tree.
6
+ */const v=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["path",{d:"M21 13V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h8",key:"3spt84"}],["path",{d:"M3 10h18",key:"8toen8"}],["path",{d:"M16 19h6",key:"xwg31i"}],["path",{d:"M19 16v6",key:"tddt3s"}]],_=l("CalendarPlus",v);/**
7
+ * @license lucide-react v0.477.0 - ISC
8
+ *
9
+ * This source code is licensed under the ISC license.
10
+ * See the LICENSE file in the root directory of this source tree.
11
+ */const z=[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}]],k=l("Calendar",z);/**
12
+ * @license lucide-react v0.477.0 - ISC
13
+ *
14
+ * This source code is licensed under the ISC license.
15
+ * See the LICENSE file in the root directory of this source tree.
16
+ */const B=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],I=l("CircleCheck",B);/**
17
+ * @license lucide-react v0.477.0 - ISC
18
+ *
19
+ * This source code is licensed under the ISC license.
20
+ * See the LICENSE file in the root directory of this source tree.
21
+ */const M=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m15 9-6 6",key:"1uzhvr"}],["path",{d:"m9 9 6 6",key:"z0biqf"}]],T=l("CircleX",M);/**
22
+ * @license lucide-react v0.477.0 - ISC
23
+ *
24
+ * This source code is licensed under the ISC license.
25
+ * See the LICENSE file in the root directory of this source tree.
26
+ */const w=[["path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z",key:"1lielz"}]],m=l("MessageSquare",w);/**
27
+ * @license lucide-react v0.477.0 - ISC
28
+ *
29
+ * This source code is licensed under the ISC license.
30
+ * See the LICENSE file in the root directory of this source tree.
31
+ */const L=[["path",{d:"m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5",key:"ftymec"}],["rect",{x:"2",y:"6",width:"14",height:"12",rx:"2",key:"158x01"}]],N=l("Video",L);function h({data:n,theme:r}){const d=(r==null?void 0:r.background)||"#00001a",i=(()=>{if(!d.startsWith("#"))return!1;const c=d.replace("#",""),u=parseInt(c.substring(0,2),16),f=parseInt(c.substring(2,4),16),C=parseInt(c.substring(4,6),16);return(.299*u+.587*f+.114*C)/255>.5})(),t=(r==null?void 0:r.primary)||"#337eff",p=(r==null?void 0:r.statusPositive)||"#22c55e",o=(r==null?void 0:r.text)||(i?"#1a1a2e":"#f7f7f8"),x=(r==null?void 0:r.textMuted)||(i?"rgba(0,0,0,0.45)":"rgba(247,247,248,0.4)"),g=i?{backgroundColor:"rgba(0,0,0,0.03)",boxShadow:"inset 0 0 0 1px rgba(0,0,0,0.08)",borderRadius:"14px",padding:"16px",overflow:"hidden"}:{backgroundColor:"rgba(255,255,255,0.04)",boxShadow:"inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",borderRadius:"14px",padding:"16px",overflow:"hidden"},a={display:"flex",alignItems:"flex-start",gap:"8px",marginBottom:"8px"},y={height:"1px",backgroundColor:i?"rgba(0,0,0,0.06)":"rgba(255,255,255,0.06)",margin:"12px 0"},s={display:"flex",alignItems:"center",justifyContent:"center",gap:"6px",flex:1,padding:"9px 12px",borderRadius:"9px",fontSize:"12px",fontWeight:"600",letterSpacing:"0.01em",cursor:"pointer",textDecoration:"none",border:"none",transition:"opacity 0.15s ease"};return e.jsxs("div",{style:g,children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"10px",marginBottom:"14px"},children:[e.jsx(I,{size:22,color:p,"aria-hidden":"true"}),e.jsxs("div",{children:[e.jsx("p",{style:{fontSize:"14px",fontWeight:"700",color:o,letterSpacing:"0.006em",lineHeight:1.3},children:"Meeting Confirmed"}),e.jsx("p",{style:{fontSize:"11px",color:p,letterSpacing:"0.01em",marginTop:"1px"},children:"You'll receive a calendar invite shortly"})]})]}),e.jsxs("div",{children:[e.jsxs("div",{style:a,children:[e.jsx(k,{size:14,color:t,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsxs("span",{style:{fontSize:"13px",color:o,lineHeight:1.4},children:[n.meetingDate," at ",n.meetingTime]})]}),e.jsxs("div",{style:a,children:[e.jsx(j,{size:14,color:t,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsxs("span",{style:{fontSize:"13px",color:o,lineHeight:1.4},children:[n.meetingDuration," minutes"]})]}),e.jsxs("div",{style:{...a,marginBottom:"0"},children:[e.jsx(m,{size:14,color:t,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsx("span",{style:{fontSize:"13px",color:o,lineHeight:1.4},children:n.meetingPurpose})]})]}),n.timezone&&e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"5px",marginTop:"8px"},children:[e.jsx(S,{size:11,color:x,"aria-hidden":"true"}),e.jsx("span",{style:{fontSize:"11px",color:x,letterSpacing:"0.01em"},children:n.timezone})]}),(n.meetLink||n.calendarLink)&&e.jsxs(e.Fragment,{children:[e.jsx("div",{style:y}),e.jsxs("div",{style:{display:"flex",gap:"8px"},children:[n.meetLink&&e.jsxs("a",{href:n.meetLink,target:"_blank",rel:"noopener noreferrer",style:{...s,background:`linear-gradient(135deg, ${t}, ${(r==null?void 0:r.primaryStrong)||"#005eff"})`,color:"#ffffff",boxShadow:`0 2px 8px -2px ${t}50`},children:[e.jsx(N,{size:13,"aria-hidden":"true"}),"Join Meet"]}),n.calendarLink&&e.jsxs("a",{href:n.calendarLink,target:"_blank",rel:"noopener noreferrer",style:{...s,backgroundColor:i?"rgba(0,0,0,0.05)":"rgba(255,255,255,0.07)",color:o,boxShadow:i?"inset 0 0 0 1px rgba(0,0,0,0.1)":"inset 0 0 0 0.5px rgba(255,255,255,0.12)"},children:[e.jsx(_,{size:13,"aria-hidden":"true"}),"Add to Calendar"]})]})]})]})}try{h.displayName="BookingConfirmationCard",h.__docgenInfo={description:"",displayName:"BookingConfirmationCard",props:{data:{defaultValue:null,description:"",name:"data",required:!0,type:{name:"IBookingConfirmationData"}},theme:{defaultValue:null,description:"",name:"theme",required:!1,type:{name:"IChatTheme | undefined"}}}}}catch{}function b({data:n,theme:r}){const d=(r==null?void 0:r.background)||"#00001a",i=(()=>{if(!d.startsWith("#"))return!1;const s=d.replace("#",""),c=parseInt(s.substring(0,2),16),u=parseInt(s.substring(2,4),16),f=parseInt(s.substring(4,6),16);return(.299*c+.587*u+.114*f)/255>.5})(),t=(r==null?void 0:r.statusNegative)||"#ef4444",p=(r==null?void 0:r.text)||(i?"#1a1a2e":"#f7f7f8"),o=(r==null?void 0:r.textMuted)||(i?"rgba(0,0,0,0.45)":"rgba(247,247,248,0.4)"),x=i?{backgroundColor:"rgba(239,68,68,0.04)",boxShadow:"inset 0 0 0 1px rgba(239,68,68,0.12)",borderRadius:"14px",padding:"16px"}:{backgroundColor:"rgba(239,68,68,0.06)",boxShadow:"inset 0 0 0 0.5px rgba(239,68,68,0.18)",borderRadius:"14px",padding:"16px"},g={fontSize:"13px",color:o,lineHeight:1.4,textDecoration:"line-through",textDecorationColor:i?"rgba(0,0,0,0.25)":"rgba(255,255,255,0.2)"},a={display:"flex",alignItems:"flex-start",gap:"8px",marginBottom:"8px"},y=n.cancelledBy==="visitor"?"Cancelled by you":"Cancelled by support";return e.jsxs("div",{style:x,children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"10px",marginBottom:"14px"},children:[e.jsx(T,{size:22,color:t,"aria-hidden":"true"}),e.jsxs("div",{children:[e.jsx("p",{style:{fontSize:"14px",fontWeight:"700",color:p,letterSpacing:"0.006em",lineHeight:1.3},children:"Meeting Cancelled"}),e.jsx("p",{style:{fontSize:"11px",color:t,letterSpacing:"0.01em",marginTop:"1px",opacity:.8},children:y})]})]}),e.jsxs("div",{children:[e.jsxs("div",{style:a,children:[e.jsx(k,{size:14,color:o,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsxs("span",{style:g,children:[n.meetingDate," at ",n.meetingTime]})]}),e.jsxs("div",{style:{...a,marginBottom:"0"},children:[e.jsx(m,{size:14,color:o,"aria-hidden":"true",style:{marginTop:"1px",flexShrink:0}}),e.jsx("span",{style:g,children:n.meetingPurpose})]})]})]})}try{b.displayName="BookingCancelledCard",b.__docgenInfo={description:"",displayName:"BookingCancelledCard",props:{data:{defaultValue:null,description:"",name:"data",required:!0,type:{name:"IBookingCancelledData"}},theme:{defaultValue:null,description:"",name:"theme",required:!1,type:{name:"IChatTheme | undefined"}}}}}catch{}export{h as B,b as a};
@@ -0,0 +1,66 @@
1
+ import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";import{B,a as j}from"./BookingCancelledCard-XHuB-Ebp.js";import"./index-yBjzXJbu.js";import"./globe-BtMvkLMD.js";import"./index--qcDGAq6.js";const T={title:"Components/BookingConfirmationCard",component:B,parameters:{layout:"padded"},tags:["autodocs"],decorators:[a=>e.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:e.jsx(a,{})})]},d={bookingId:"bk_abc123",visitorName:"John Doe",meetingDate:"2026-03-26",meetingTime:"10:00",meetingDuration:30,meetingPurpose:"Discuss mobile app development project",meetLink:"https://meet.google.com/abc-defg-hij",calendarLink:"https://calendar.google.com/event?eid=abc123",timezone:"Australia/Sydney"},n={args:{data:d}},r={args:{data:{...d,meetLink:void 0}}},o={args:{data:d,theme:{primary:"#337eff",background:"#ffffff",text:"#1a1a2e",textMuted:"rgba(0,0,0,0.35)"}},decorators:[a=>e.jsx("div",{style:{maxWidth:360,background:"#ffffff",padding:16,borderRadius:12},children:e.jsx(a,{})})]},t={render:a=>e.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:e.jsx(j,{...a})}),args:{data:{bookingId:"bk_abc123",meetingDate:"2026-03-26",meetingTime:"10:00",meetingPurpose:"Discuss mobile app development project",cancelledBy:"visitor"}}},i={render:a=>e.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:e.jsx(j,{...a})}),args:{data:{bookingId:"bk_abc123",meetingDate:"2026-03-26",meetingTime:"10:00",meetingPurpose:"Discuss mobile app development project",cancelledBy:"admin"}}};var s,m,c;n.parameters={...n.parameters,docs:{...(s=n.parameters)==null?void 0:s.docs,source:{originalSource:`{
2
+ args: {
3
+ data: mockConfirmation
4
+ }
5
+ }`,...(c=(m=n.parameters)==null?void 0:m.docs)==null?void 0:c.source}}};var g,p,l;r.parameters={...r.parameters,docs:{...(g=r.parameters)==null?void 0:g.docs,source:{originalSource:`{
6
+ args: {
7
+ data: {
8
+ ...mockConfirmation,
9
+ meetLink: undefined
10
+ }
11
+ }
12
+ }`,...(l=(p=r.parameters)==null?void 0:p.docs)==null?void 0:l.source}}};var u,f,b;o.parameters={...o.parameters,docs:{...(u=o.parameters)==null?void 0:u.docs,source:{originalSource:`{
13
+ args: {
14
+ data: mockConfirmation,
15
+ theme: {
16
+ primary: '#337eff',
17
+ background: '#ffffff',
18
+ text: '#1a1a2e',
19
+ textMuted: 'rgba(0,0,0,0.35)'
20
+ }
21
+ },
22
+ decorators: [Story => <div style={{
23
+ maxWidth: 360,
24
+ background: '#ffffff',
25
+ padding: 16,
26
+ borderRadius: 12
27
+ }}>
28
+ <Story />
29
+ </div>]
30
+ }`,...(b=(f=o.parameters)==null?void 0:f.docs)==null?void 0:b.source}}};var k,C,x;t.parameters={...t.parameters,docs:{...(k=t.parameters)==null?void 0:k.docs,source:{originalSource:`{
31
+ render: args => <div style={{
32
+ maxWidth: 360,
33
+ background: '#00001a',
34
+ padding: 16,
35
+ borderRadius: 12
36
+ }}>
37
+ <BookingCancelledCard {...args} />
38
+ </div>,
39
+ args: {
40
+ data: {
41
+ bookingId: 'bk_abc123',
42
+ meetingDate: '2026-03-26',
43
+ meetingTime: '10:00',
44
+ meetingPurpose: 'Discuss mobile app development project',
45
+ cancelledBy: 'visitor'
46
+ } as IBookingCancelledData
47
+ }
48
+ }`,...(x=(C=t.parameters)==null?void 0:C.docs)==null?void 0:x.source}}};var y,h,v;i.parameters={...i.parameters,docs:{...(y=i.parameters)==null?void 0:y.docs,source:{originalSource:`{
49
+ render: args => <div style={{
50
+ maxWidth: 360,
51
+ background: '#00001a',
52
+ padding: 16,
53
+ borderRadius: 12
54
+ }}>
55
+ <BookingCancelledCard {...args} />
56
+ </div>,
57
+ args: {
58
+ data: {
59
+ bookingId: 'bk_abc123',
60
+ meetingDate: '2026-03-26',
61
+ meetingTime: '10:00',
62
+ meetingPurpose: 'Discuss mobile app development project',
63
+ cancelledBy: 'admin'
64
+ } as IBookingCancelledData
65
+ }
66
+ }`,...(v=(h=i.parameters)==null?void 0:h.docs)==null?void 0:v.source}}};const W=["Confirmed","ConfirmedNoMeetLink","ConfirmedLightTheme","CancelledByVisitor","CancelledByAdmin"];export{i as CancelledByAdmin,t as CancelledByVisitor,n as Confirmed,o as ConfirmedLightTheme,r as ConfirmedNoMeetLink,W as __namedExportsOrder,T as default};
@@ -0,0 +1 @@
1
+ import{j as t}from"./jsx-runtime-Cf8x2fCZ.js";import{r as y}from"./index--qcDGAq6.js";import{G as B,C as D}from"./globe-BtMvkLMD.js";function S({data:i,theme:r,onSlotSelected:m,disabled:o=!1}){var b;const[p,h]=y.useState(0),[a,k]=y.useState(null),x=(r==null?void 0:r.background)||"#00001a",d=(()=>{if(!x.startsWith("#"))return!1;const e=x.replace("#",""),n=parseInt(e.substring(0,2),16),c=parseInt(e.substring(2,4),16),I=parseInt(e.substring(4,6),16);return(.299*n+.587*c+.114*I)/255>.5})(),j=(r==null?void 0:r.primary)||"#337eff",g=(r==null?void 0:r.text)||(d?"#1a1a2e":"#f7f7f8"),s=(r==null?void 0:r.textMuted)||(d?"rgba(0,0,0,0.45)":"rgba(247,247,248,0.4)"),C=d?{backgroundColor:"rgba(0,0,0,0.03)",boxShadow:"inset 0 0 0 1px rgba(0,0,0,0.08)",borderRadius:"14px",padding:"14px"}:{backgroundColor:"rgba(255,255,255,0.04)",boxShadow:"inset 0 0 0 0.5px rgba(255,255,255,0.08), inset 0 1px 0 0 rgba(255,255,255,0.1)",borderRadius:"14px",padding:"14px"},u={backgroundColor:j,color:"#ffffff",borderRadius:"20px",padding:"5px 12px",fontSize:"12px",fontWeight:"600",letterSpacing:"0.01em",cursor:o?"default":"pointer",border:"none",flexShrink:0},f=d?{backgroundColor:"rgba(0,0,0,0.04)",color:g,borderRadius:"20px",padding:"5px 12px",fontSize:"12px",fontWeight:"500",letterSpacing:"0.01em",cursor:o?"default":"pointer",border:"none",boxShadow:"inset 0 0 0 1px rgba(0,0,0,0.1)",flexShrink:0}:{backgroundColor:"rgba(255,255,255,0.05)",color:g,borderRadius:"20px",padding:"5px 12px",fontSize:"12px",fontWeight:"500",letterSpacing:"0.01em",cursor:o?"default":"pointer",border:"none",boxShadow:"inset 0 0 0 0.5px rgba(255,255,255,0.1)",flexShrink:0},v=(a==null?void 0:a.date)===((b=i.availableDates[p])==null?void 0:b.date)?a.time:null,z=(e,n)=>{o||(k({date:e,time:n}),m(e,n))},l=i.availableDates[p];return t.jsxs("div",{style:C,children:[t.jsx("p",{style:{fontSize:"11px",fontWeight:"600",letterSpacing:"0.06em",textTransform:"uppercase",color:s,marginBottom:"10px"},children:"Select a date"}),t.jsx("div",{style:{display:"flex",gap:"6px",overflowX:"auto",paddingBottom:"2px",scrollbarWidth:"none",msOverflowStyle:"none",marginBottom:"14px"},children:i.availableDates.map((e,n)=>t.jsx("button",{type:"button",onClick:()=>!o&&h(n),style:n===p?u:f,title:e.dayName,"aria-pressed":n===p,disabled:o,children:e.dayLabel},e.date))}),l&&t.jsxs(t.Fragment,{children:[t.jsx("p",{style:{fontSize:"11px",fontWeight:"600",letterSpacing:"0.06em",textTransform:"uppercase",color:s,marginBottom:"8px"},children:l.dayName}),t.jsx("div",{style:{display:"grid",gridTemplateColumns:"repeat(3, 1fr)",gap:"6px",marginBottom:"14px"},children:l.slots.map(e=>{const n=v===e,c=(a==null?void 0:a.date)===l.date&&a.time===e;return t.jsx("button",{type:"button",onClick:()=>z(l.date,e),disabled:o&&!c,"aria-pressed":n,style:{...n?u:f,padding:"7px 8px",borderRadius:"8px",fontSize:"13px",fontWeight:n?"600":"400",textAlign:"center",opacity:o&&!c?.4:1,transition:"opacity 0.15s ease"},children:e},e)})})]}),t.jsxs("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:"8px"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"5px"},children:[t.jsx(B,{size:11,color:s,"aria-hidden":"true"}),t.jsx("span",{style:{fontSize:"11px",color:s,letterSpacing:"0.01em"},children:i.timezone})]}),t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:"5px"},children:[t.jsx(D,{size:11,color:s,"aria-hidden":"true"}),t.jsxs("span",{style:{fontSize:"11px",color:s,letterSpacing:"0.01em"},children:[i.meetingDuration," min"]})]})]})]})}try{S.displayName="BookingSlotPicker",S.__docgenInfo={description:"",displayName:"BookingSlotPicker",props:{data:{defaultValue:null,description:"",name:"data",required:!0,type:{name:"IBookingData"}},theme:{defaultValue:null,description:"",name:"theme",required:!1,type:{name:"IChatTheme | undefined"}},onSlotSelected:{defaultValue:null,description:"",name:"onSlotSelected",required:!0,type:{name:"(date: string, time: string) => void"}},disabled:{defaultValue:{value:"false"},description:"",name:"disabled",required:!1,type:{name:"boolean | undefined"}}}}}catch{}export{S as B};
@@ -0,0 +1,50 @@
1
+ import{j as r}from"./jsx-runtime-Cf8x2fCZ.js";import{B as j}from"./BookingSlotPicker-BkfssueW.js";import"./index-yBjzXJbu.js";import"./index--qcDGAq6.js";import"./globe-BtMvkLMD.js";const A={title:"Components/BookingSlotPicker",component:j,parameters:{layout:"padded"},tags:["autodocs"],decorators:[e=>r.jsx("div",{style:{maxWidth:360,background:"#00001a",padding:16,borderRadius:12},children:r.jsx(e,{})})]},n={availableDates:[{date:"2026-03-25",dayLabel:"Wed 25",dayName:"Wednesday",slots:["09:00","09:30","10:00","10:30","14:00","14:30","15:00"]},{date:"2026-03-26",dayLabel:"Thu 26",dayName:"Thursday",slots:["09:00","10:00","11:00","13:00","14:00"]},{date:"2026-03-27",dayLabel:"Fri 27",dayName:"Friday",slots:["09:30","10:30","11:30"]},{date:"2026-03-28",dayLabel:"Mon 30",dayName:"Monday",slots:["09:00","09:30","10:00","10:30","11:00","11:30","13:00","14:00","15:00","16:00"]},{date:"2026-03-31",dayLabel:"Tue 31",dayName:"Tuesday",slots:["14:00","14:30","15:00","15:30","16:00"]}],timezone:"Australia/Sydney",meetingDuration:30},t={args:{data:n,onSlotSelected:(e,a)=>console.log("Selected:",e,a)}},o={args:{data:n,onSlotSelected:(e,a)=>console.log("Selected:",e,a),theme:{primary:"#337eff",background:"#ffffff",text:"#1a1a2e",textMuted:"rgba(0,0,0,0.35)"}},decorators:[e=>r.jsx("div",{style:{maxWidth:360,background:"#ffffff",padding:16,borderRadius:12},children:r.jsx(e,{})})]},d={args:{data:n,onSlotSelected:(e,a)=>console.log("Selected:",e,a),disabled:!0}},s={args:{data:{availableDates:[{date:"2026-03-26",dayLabel:"Thu 26",dayName:"Thursday",slots:["14:00"]},{date:"2026-03-27",dayLabel:"Fri 27",dayName:"Friday",slots:["09:00","15:30"]}],timezone:"Australia/Sydney",meetingDuration:30},onSlotSelected:(e,a)=>console.log("Selected:",e,a)}};var i,l,c,m,y;t.parameters={...t.parameters,docs:{...(i=t.parameters)==null?void 0:i.docs,source:{originalSource:`{
2
+ args: {
3
+ data: mockBookingData,
4
+ onSlotSelected: (date, time) => console.log('Selected:', date, time)
5
+ }
6
+ }`,...(c=(l=t.parameters)==null?void 0:l.docs)==null?void 0:c.source},description:{story:"Default dark theme",...(y=(m=t.parameters)==null?void 0:m.docs)==null?void 0:y.description}}};var p,u,g,S,f;o.parameters={...o.parameters,docs:{...(p=o.parameters)==null?void 0:p.docs,source:{originalSource:`{
7
+ args: {
8
+ data: mockBookingData,
9
+ onSlotSelected: (date, time) => console.log('Selected:', date, time),
10
+ theme: {
11
+ primary: '#337eff',
12
+ background: '#ffffff',
13
+ text: '#1a1a2e',
14
+ textMuted: 'rgba(0,0,0,0.35)'
15
+ }
16
+ },
17
+ decorators: [Story => <div style={{
18
+ maxWidth: 360,
19
+ background: '#ffffff',
20
+ padding: 16,
21
+ borderRadius: 12
22
+ }}>
23
+ <Story />
24
+ </div>]
25
+ }`,...(g=(u=o.parameters)==null?void 0:u.docs)==null?void 0:g.source},description:{story:"Light theme",...(f=(S=o.parameters)==null?void 0:S.docs)==null?void 0:f.description}}};var b,h,k,D,x;d.parameters={...d.parameters,docs:{...(b=d.parameters)==null?void 0:b.docs,source:{originalSource:`{
26
+ args: {
27
+ data: mockBookingData,
28
+ onSlotSelected: (date, time) => console.log('Selected:', date, time),
29
+ disabled: true
30
+ }
31
+ }`,...(k=(h=d.parameters)==null?void 0:h.docs)==null?void 0:k.source},description:{story:"Disabled state (after selection)",...(x=(D=d.parameters)==null?void 0:D.docs)==null?void 0:x.description}}};var L,T,N,v,B;s.parameters={...s.parameters,docs:{...(L=s.parameters)==null?void 0:L.docs,source:{originalSource:`{
32
+ args: {
33
+ data: {
34
+ availableDates: [{
35
+ date: '2026-03-26',
36
+ dayLabel: 'Thu 26',
37
+ dayName: 'Thursday',
38
+ slots: ['14:00']
39
+ }, {
40
+ date: '2026-03-27',
41
+ dayLabel: 'Fri 27',
42
+ dayName: 'Friday',
43
+ slots: ['09:00', '15:30']
44
+ }],
45
+ timezone: 'Australia/Sydney',
46
+ meetingDuration: 30
47
+ },
48
+ onSlotSelected: (date, time) => console.log('Selected:', date, time)
49
+ }
50
+ }`,...(N=(T=s.parameters)==null?void 0:T.docs)==null?void 0:N.source},description:{story:"Limited availability",...(B=(v=s.parameters)==null?void 0:v.docs)==null?void 0:B.description}}};const E=["Default","LightTheme","Disabled","LimitedSlots"];export{t as Default,d as Disabled,o as LightTheme,s as LimitedSlots,E as __namedExportsOrder,A as default};
@@ -0,0 +1,4 @@
1
+ import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";function l({size:a=24,color:i="white",className:r="",style:t}){return e.jsxs("svg",{width:a,height:a,viewBox:"0 0 32 32",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:r,style:t,"aria-hidden":"true",children:[e.jsx("path",{d:"M20.582 15.027L31.849 0.036H24.808L17.039 10.303L20.582 15.027ZM24.808 31.837H31.849L20.582 16.846L17.039 21.57L24.808 31.837Z",fill:i}),e.jsx("path",{d:"M14.313 15.027H18.402L7.135 0.036H0.185L9.406 12.392C10.587 13.983 12.359 15.027 14.313 15.027Z",fill:i}),e.jsx("path",{d:"M0.185 31.837H7.135L18.402 16.846H14.313C12.359 16.846 10.588 17.891 9.406 19.481L0.185 31.837Z",fill:i})]})}function s({size:a=36,color:i="white",className:r="",style:t}){return e.jsxs("svg",{width:a,height:a,viewBox:"0 0 32 32",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:r,style:t,"aria-hidden":"true",children:[e.jsx("circle",{cx:"16",cy:"3.5",r:"3",fill:"#67e8f9",opacity:.4}),e.jsx("circle",{cx:"16",cy:"3.5",r:"1.5",fill:"#67e8f9"}),e.jsx("rect",{x:"15",y:"4.5",width:"2",height:"4",rx:"1",fill:i}),e.jsx("rect",{x:"1",y:"15",width:"4",height:"6",rx:"2",fill:i,opacity:.5}),e.jsx("rect",{x:"27",y:"15",width:"4",height:"6",rx:"2",fill:i,opacity:.5}),e.jsx("rect",{x:"5",y:"8.5",width:"22",height:"19",rx:"5",fill:i}),e.jsx("circle",{cx:"12",cy:"16.5",r:"3",fill:"#1a4fd0"}),e.jsx("circle",{cx:"20",cy:"16.5",r:"3",fill:"#1a4fd0"}),e.jsx("circle",{cx:"13",cy:"15.5",r:"1.2",fill:i}),e.jsx("circle",{cx:"21",cy:"15.5",r:"1.2",fill:i}),e.jsx("path",{d:"M13 22.5q3 2.5 6 0",stroke:"#1a4fd0",strokeWidth:"1.5",strokeLinecap:"round"})]})}function n({size:a=40,className:i=""}){const r=Math.round(a*.55);return e.jsx("div",{className:`flex items-center justify-center rounded-full ${i}`,style:{width:a,height:a,background:"linear-gradient(135deg, #337eff, #005eff)"},children:e.jsx(l,{size:r,color:"white"})})}try{l.displayName="XcelsiorSymbol",l.__docgenInfo={description:`Xcelsior "X" symbol mark (from favicon-brand.svg)
2
+ Reusable across the chat widget — FAB button, avatar, header, etc.`,displayName:"XcelsiorSymbol",props:{size:{defaultValue:{value:"24"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},color:{defaultValue:{value:"white"},description:"",name:"color",required:!1,type:{name:"string | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}},style:{defaultValue:null,description:"",name:"style",required:!1,type:{name:"CSSProperties | undefined"}}}}}catch{}try{s.displayName="ChatBubbleIcon",s.__docgenInfo={description:`Expressive robot icon for the FAB launcher button.
3
+ Friendly robot with cyan antenna glow, ear nubs, eyes with highlights, and smile.`,displayName:"ChatBubbleIcon",props:{size:{defaultValue:{value:"36"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},color:{defaultValue:{value:"white"},description:"",name:"color",required:!1,type:{name:"string | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}},style:{defaultValue:null,description:"",name:"style",required:!1,type:{name:"CSSProperties | undefined"}}}}}catch{}try{n.displayName="XcelsiorAvatar",n.__docgenInfo={description:`Xcelsior "X" avatar — symbol inside a gradient circle
4
+ Used as bot avatar in messages and chat header`,displayName:"XcelsiorAvatar",props:{size:{defaultValue:{value:"40"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}}}}}catch{}export{s as C,l as X,n as a};