@usereq/widget 0.2.8 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/widget.js +110 -3530
- package/package.json +1 -1
- package/src/chat-widget/chat-widget-defaults.tsx +1 -40
- package/src/chat-widget/message-reveal.ts +52 -0
- package/src/custom-element/agent-widget-element.tsx +18 -2
- package/src/styles/widget.css.ts +39 -10
- package/dist/widget.css +0 -2
- package/src/chat-widget/components/chat-widget-frame.tsx +0 -56
- package/src/chat-widget/components/chat-widget-layout.ts +0 -6
- package/src/chat-widget/components/chat-widget-primitives.tsx +0 -720
- package/src/chat-widget/components/confirmation.tsx +0 -155
- package/src/chat-widget/components/conversation.tsx +0 -110
- package/src/chat-widget/components/message.tsx +0 -325
- package/src/chat-widget/index.ts +0 -19
- package/src/chat-widget/styles/chat-widget-box.tsx +0 -258
- package/src/chat-widget/styles/chat-widget-bubble.tsx +0 -238
- package/src/chat-widget/styles/chat-widget-chatbar.tsx +0 -248
- package/src/chat-widget/styles/chat-widget-messenger.tsx +0 -573
|
@@ -1,573 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { AnimatePresence, motion } from "motion/react";
|
|
5
|
-
import { cn } from "@usereq/ui/lib/utils";
|
|
6
|
-
import {
|
|
7
|
-
Avatar,
|
|
8
|
-
AvatarFallback,
|
|
9
|
-
AvatarImage,
|
|
10
|
-
} from "@usereq/ui/components/avatar";
|
|
11
|
-
import { Button } from "@usereq/ui/components/button";
|
|
12
|
-
import { Textarea } from "@usereq/ui/components/textarea";
|
|
13
|
-
import {
|
|
14
|
-
ChevronDown,
|
|
15
|
-
SendHorizonal,
|
|
16
|
-
Phone,
|
|
17
|
-
Video,
|
|
18
|
-
Info,
|
|
19
|
-
} from "lucide-react";
|
|
20
|
-
import {
|
|
21
|
-
DEFAULT_AGENT_NAME_BUBBLE,
|
|
22
|
-
DEFAULT_POWERED_BY,
|
|
23
|
-
DEFAULT_SPOTLIGHT_MESSAGES,
|
|
24
|
-
} from "../chat-widget-defaults";
|
|
25
|
-
import {
|
|
26
|
-
ChatWidgetPanel,
|
|
27
|
-
ChatWidgetPoweredBy,
|
|
28
|
-
ChatWidgetTyping,
|
|
29
|
-
useIsChatWidgetFullscreenViewport,
|
|
30
|
-
useSpotlightFade,
|
|
31
|
-
type ChatWidgetDataProps,
|
|
32
|
-
type ChatWidgetPreviewMessage,
|
|
33
|
-
} from "../components/chat-widget-primitives";
|
|
34
|
-
import {
|
|
35
|
-
Conversation,
|
|
36
|
-
ConversationContent,
|
|
37
|
-
ConversationEmptyState,
|
|
38
|
-
ConversationScrollButton,
|
|
39
|
-
} from "../components/conversation";
|
|
40
|
-
import { foldStopConfirmationMessages } from "../stop-confirmation";
|
|
41
|
-
import { getChatWidgetMessagesLayoutClasses } from "../components/chat-widget-layout";
|
|
42
|
-
import {
|
|
43
|
-
Message,
|
|
44
|
-
MessageContent,
|
|
45
|
-
MessageResponse,
|
|
46
|
-
} from "../components/message";
|
|
47
|
-
|
|
48
|
-
const MESSENGER_GRADIENT =
|
|
49
|
-
"linear-gradient(135deg, #00B2FF 0%, #006AFF 50%, #8E5BFF 100%)";
|
|
50
|
-
|
|
51
|
-
const MESSENGER_BRAND_COLOR = "#0084FF";
|
|
52
|
-
|
|
53
|
-
const PANEL_WIDTH = 372;
|
|
54
|
-
const PANEL_HEIGHT = 580;
|
|
55
|
-
const LAUNCHER_SIZE = 60;
|
|
56
|
-
const SPOTLIGHT_GAP = 10;
|
|
57
|
-
const PANEL_GAP = 14;
|
|
58
|
-
|
|
59
|
-
const fadeTransition = { duration: 0.35 };
|
|
60
|
-
|
|
61
|
-
export function ChatWidgetMessenger({
|
|
62
|
-
agentName = DEFAULT_AGENT_NAME_BUBBLE,
|
|
63
|
-
agentAvatarUrl,
|
|
64
|
-
spotlightMessages = [],
|
|
65
|
-
messages = [],
|
|
66
|
-
poweredBy = DEFAULT_POWERED_BY,
|
|
67
|
-
headerStatus = "Active now",
|
|
68
|
-
errorMessage,
|
|
69
|
-
status = "idle",
|
|
70
|
-
sendDisabled = false,
|
|
71
|
-
onSendMessage,
|
|
72
|
-
isTyping,
|
|
73
|
-
onLauncherClick,
|
|
74
|
-
className,
|
|
75
|
-
open,
|
|
76
|
-
defaultOpen = false,
|
|
77
|
-
onOpenChange,
|
|
78
|
-
previewOnly = false,
|
|
79
|
-
onStopConversation,
|
|
80
|
-
stopDisabled,
|
|
81
|
-
onConfirmationDecision,
|
|
82
|
-
onLinkClick,
|
|
83
|
-
emptyAction,
|
|
84
|
-
portalContainer,
|
|
85
|
-
}: ChatWidgetDataProps & {
|
|
86
|
-
open?: boolean;
|
|
87
|
-
className?: string;
|
|
88
|
-
defaultOpen?: boolean;
|
|
89
|
-
onOpenChange?: (open: boolean) => void;
|
|
90
|
-
onLauncherClick?: () => void;
|
|
91
|
-
previewOnly?: boolean;
|
|
92
|
-
portalContainer?: React.ComponentProps<
|
|
93
|
-
typeof ChatWidgetPanel
|
|
94
|
-
>["portalContainer"];
|
|
95
|
-
}) {
|
|
96
|
-
const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
|
|
97
|
-
previewOnly ? false : defaultOpen,
|
|
98
|
-
);
|
|
99
|
-
const [draftValue, setDraftValue] = React.useState("");
|
|
100
|
-
const spotlightForFade =
|
|
101
|
-
spotlightMessages.length > 0
|
|
102
|
-
? spotlightMessages
|
|
103
|
-
: [...DEFAULT_SPOTLIGHT_MESSAGES];
|
|
104
|
-
const spotlightText = useSpotlightFade(spotlightForFade, 3200);
|
|
105
|
-
const effectiveOpen = previewOnly ? false : (open ?? uncontrolledOpen);
|
|
106
|
-
const handleOpenChange = React.useCallback(
|
|
107
|
-
(nextOpen: boolean) => {
|
|
108
|
-
if (onOpenChange) {
|
|
109
|
-
onOpenChange(nextOpen);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
setUncontrolledOpen(nextOpen);
|
|
113
|
-
},
|
|
114
|
-
[onOpenChange],
|
|
115
|
-
);
|
|
116
|
-
const isFullscreenViewport = useIsChatWidgetFullscreenViewport();
|
|
117
|
-
const hideLauncher = isFullscreenViewport && effectiveOpen;
|
|
118
|
-
|
|
119
|
-
const sendIsDisabled = sendDisabled || status === "booting";
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<div
|
|
123
|
-
className={cn("relative", className)}
|
|
124
|
-
style={{
|
|
125
|
-
width: spotlightForFade.length > 0 ? 280 : LAUNCHER_SIZE,
|
|
126
|
-
height:
|
|
127
|
-
spotlightForFade.length > 0
|
|
128
|
-
? LAUNCHER_SIZE + SPOTLIGHT_GAP + 56
|
|
129
|
-
: LAUNCHER_SIZE,
|
|
130
|
-
}}
|
|
131
|
-
>
|
|
132
|
-
{spotlightForFade.length > 0 && !effectiveOpen && (
|
|
133
|
-
<AnimatePresence mode="wait">
|
|
134
|
-
<motion.div
|
|
135
|
-
key={spotlightText}
|
|
136
|
-
initial={{ opacity: 0, y: 6 }}
|
|
137
|
-
animate={{ opacity: 1, y: 0 }}
|
|
138
|
-
exit={{ opacity: 0, y: 6 }}
|
|
139
|
-
transition={fadeTransition}
|
|
140
|
-
className="absolute right-0 bottom-[calc(60px+10px)] flex w-full max-w-70 flex-col items-end"
|
|
141
|
-
>
|
|
142
|
-
<div className="rounded-3xl bg-background px-4 py-2.5 text-right shadow-[0_4px_16px_rgba(0,0,0,0.12)]">
|
|
143
|
-
<p className="text-[14px] leading-snug text-foreground">
|
|
144
|
-
{spotlightText}
|
|
145
|
-
</p>
|
|
146
|
-
</div>
|
|
147
|
-
</motion.div>
|
|
148
|
-
</AnimatePresence>
|
|
149
|
-
)}
|
|
150
|
-
|
|
151
|
-
{!previewOnly && (
|
|
152
|
-
<ChatWidgetPanel
|
|
153
|
-
open={effectiveOpen}
|
|
154
|
-
onOpenChange={handleOpenChange}
|
|
155
|
-
portalContainer={portalContainer}
|
|
156
|
-
desktopClassName="absolute right-0 overflow-hidden rounded-3xl bg-background shadow-[0_8px_32px_rgba(0,0,0,0.16)] border border-border"
|
|
157
|
-
desktopStyle={{
|
|
158
|
-
width: PANEL_WIDTH,
|
|
159
|
-
height: PANEL_HEIGHT,
|
|
160
|
-
bottom: LAUNCHER_SIZE + PANEL_GAP,
|
|
161
|
-
}}
|
|
162
|
-
>
|
|
163
|
-
<MessengerHeader
|
|
164
|
-
agentName={agentName}
|
|
165
|
-
agentAvatarUrl={agentAvatarUrl}
|
|
166
|
-
statusText={headerStatus}
|
|
167
|
-
onClose={() => handleOpenChange(false)}
|
|
168
|
-
onStopConversation={onStopConversation ?? undefined}
|
|
169
|
-
stopDisabled={stopDisabled}
|
|
170
|
-
/>
|
|
171
|
-
<div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background">
|
|
172
|
-
<MessengerMessages
|
|
173
|
-
messages={messages}
|
|
174
|
-
isTyping={isTyping ?? status === "sending"}
|
|
175
|
-
status={status}
|
|
176
|
-
errorMessage={errorMessage}
|
|
177
|
-
agentName={agentName}
|
|
178
|
-
agentAvatarUrl={agentAvatarUrl}
|
|
179
|
-
onConfirmationDecision={onConfirmationDecision}
|
|
180
|
-
onLinkClick={onLinkClick}
|
|
181
|
-
emptyAction={emptyAction}
|
|
182
|
-
/>
|
|
183
|
-
</div>
|
|
184
|
-
{!(emptyAction && messages.length === 0 && !isTyping) && (
|
|
185
|
-
<MessengerComposer
|
|
186
|
-
value={draftValue}
|
|
187
|
-
onChange={setDraftValue}
|
|
188
|
-
onSubmit={onSendMessage}
|
|
189
|
-
disabled={sendIsDisabled}
|
|
190
|
-
/>
|
|
191
|
-
)}
|
|
192
|
-
<ChatWidgetPoweredBy
|
|
193
|
-
text={poweredBy}
|
|
194
|
-
className="bg-background py-1.5"
|
|
195
|
-
/>
|
|
196
|
-
</ChatWidgetPanel>
|
|
197
|
-
)}
|
|
198
|
-
|
|
199
|
-
{!hideLauncher && (
|
|
200
|
-
<motion.button
|
|
201
|
-
type="button"
|
|
202
|
-
onClick={
|
|
203
|
-
previewOnly
|
|
204
|
-
? undefined
|
|
205
|
-
: () => {
|
|
206
|
-
onLauncherClick?.();
|
|
207
|
-
handleOpenChange(!effectiveOpen);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
className={cn(
|
|
211
|
-
"absolute bottom-0 right-0 flex items-center justify-center rounded-full text-white",
|
|
212
|
-
previewOnly && "pointer-events-none",
|
|
213
|
-
)}
|
|
214
|
-
style={{
|
|
215
|
-
width: LAUNCHER_SIZE,
|
|
216
|
-
height: LAUNCHER_SIZE,
|
|
217
|
-
backgroundColor: MESSENGER_BRAND_COLOR,
|
|
218
|
-
boxShadow: "0 6px 20px rgba(0, 132, 255, 0.45)",
|
|
219
|
-
}}
|
|
220
|
-
aria-label={effectiveOpen ? "Close chat" : "Open chat"}
|
|
221
|
-
aria-expanded={effectiveOpen}
|
|
222
|
-
animate={{ scale: 1 }}
|
|
223
|
-
whileHover={previewOnly ? undefined : { scale: 1.04 }}
|
|
224
|
-
whileTap={previewOnly ? undefined : { scale: 0.96 }}
|
|
225
|
-
>
|
|
226
|
-
{effectiveOpen ? (
|
|
227
|
-
<ChevronDown className="size-7" strokeWidth={2.4} />
|
|
228
|
-
) : (
|
|
229
|
-
<MessengerLogo className="size-8" />
|
|
230
|
-
)}
|
|
231
|
-
</motion.button>
|
|
232
|
-
)}
|
|
233
|
-
</div>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function MessengerMessages({
|
|
238
|
-
messages,
|
|
239
|
-
isTyping,
|
|
240
|
-
status,
|
|
241
|
-
errorMessage,
|
|
242
|
-
agentName,
|
|
243
|
-
agentAvatarUrl,
|
|
244
|
-
onConfirmationDecision,
|
|
245
|
-
onLinkClick,
|
|
246
|
-
emptyAction,
|
|
247
|
-
}: {
|
|
248
|
-
messages: ChatWidgetPreviewMessage[];
|
|
249
|
-
isTyping: boolean;
|
|
250
|
-
status: ChatWidgetDataProps["status"];
|
|
251
|
-
errorMessage?: string | null;
|
|
252
|
-
agentName: string;
|
|
253
|
-
agentAvatarUrl?: string | null;
|
|
254
|
-
onConfirmationDecision?: ChatWidgetDataProps["onConfirmationDecision"];
|
|
255
|
-
onLinkClick?: (linkUrl: string) => void;
|
|
256
|
-
emptyAction?: { label: string; onClick: () => void; disabled?: boolean };
|
|
257
|
-
}) {
|
|
258
|
-
const { scrollClassName, contentClassName } =
|
|
259
|
-
getChatWidgetMessagesLayoutClasses();
|
|
260
|
-
const renderedMessages = React.useMemo(
|
|
261
|
-
() => foldStopConfirmationMessages(messages),
|
|
262
|
-
[messages],
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
if (messages.length === 0 && !isTyping) {
|
|
266
|
-
if (status === "error") {
|
|
267
|
-
return (
|
|
268
|
-
<div className="flex h-full items-center justify-center px-6 py-8 text-center text-sm text-destructive">
|
|
269
|
-
{errorMessage ?? "Something went wrong."}
|
|
270
|
-
</div>
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
return (
|
|
275
|
-
<div className="flex min-h-full flex-1 items-center justify-center px-6 py-8">
|
|
276
|
-
<ConversationEmptyState className="w-full max-w-[280px] gap-4">
|
|
277
|
-
<Avatar
|
|
278
|
-
className="size-16 ring-4 ring-background"
|
|
279
|
-
style={{
|
|
280
|
-
boxShadow: "0 6px 18px rgba(0, 132, 255, 0.25)",
|
|
281
|
-
}}
|
|
282
|
-
>
|
|
283
|
-
{agentAvatarUrl ? (
|
|
284
|
-
<AvatarImage src={agentAvatarUrl} alt={agentName} />
|
|
285
|
-
) : null}
|
|
286
|
-
<AvatarFallback
|
|
287
|
-
className="text-white text-base font-semibold"
|
|
288
|
-
style={{ backgroundColor: MESSENGER_BRAND_COLOR }}
|
|
289
|
-
>
|
|
290
|
-
{agentName.slice(0, 2).toUpperCase()}
|
|
291
|
-
</AvatarFallback>
|
|
292
|
-
</Avatar>
|
|
293
|
-
<div>
|
|
294
|
-
<p className="text-[15px] font-semibold leading-tight text-foreground">
|
|
295
|
-
{agentName}
|
|
296
|
-
</p>
|
|
297
|
-
<p className="mt-1.5 text-[13px] leading-snug text-muted-foreground">
|
|
298
|
-
Typically replies in a few minutes.
|
|
299
|
-
</p>
|
|
300
|
-
</div>
|
|
301
|
-
{emptyAction && (
|
|
302
|
-
<Button
|
|
303
|
-
size="lg"
|
|
304
|
-
disabled={emptyAction.disabled}
|
|
305
|
-
onClick={emptyAction.onClick}
|
|
306
|
-
className="rounded-full px-6 text-white shadow-[0_4px_12px_rgba(0,132,255,0.35)] hover:opacity-95 disabled:opacity-60"
|
|
307
|
-
style={{ backgroundColor: MESSENGER_BRAND_COLOR }}
|
|
308
|
-
>
|
|
309
|
-
{emptyAction.label}
|
|
310
|
-
</Button>
|
|
311
|
-
)}
|
|
312
|
-
</ConversationEmptyState>
|
|
313
|
-
</div>
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return (
|
|
318
|
-
<Conversation className="relative size-full">
|
|
319
|
-
<ConversationContent
|
|
320
|
-
scrollClassName={scrollClassName}
|
|
321
|
-
className={cn(contentClassName, "gap-1.5 px-3 py-3")}
|
|
322
|
-
>
|
|
323
|
-
{renderedMessages.map((item) => {
|
|
324
|
-
if (item.type === "confirmation") {
|
|
325
|
-
return (
|
|
326
|
-
<Message key={item.message.id} from="assistant">
|
|
327
|
-
<div className="flex w-fit max-w-[80%] shrink-0 flex-col gap-2">
|
|
328
|
-
<MessageResponse
|
|
329
|
-
confirmation={item.latestEnvelope}
|
|
330
|
-
confirmationPrompt={item.envelope.prompt}
|
|
331
|
-
confirmationActionsDisabled={status === "sending"}
|
|
332
|
-
onConfirmationDecision={onConfirmationDecision}
|
|
333
|
-
/>
|
|
334
|
-
</div>
|
|
335
|
-
</Message>
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const m = item.message;
|
|
340
|
-
const isUser = m.role === "user";
|
|
341
|
-
return (
|
|
342
|
-
<Message key={m.id} from={m.role}>
|
|
343
|
-
<div className="flex w-fit max-w-[80%] shrink-0 flex-col gap-1.5">
|
|
344
|
-
<MessageContent
|
|
345
|
-
className={cn(
|
|
346
|
-
"whitespace-pre-wrap rounded-[18px] px-3.5 py-2 text-[14px] leading-snug",
|
|
347
|
-
isUser
|
|
348
|
-
? "text-white"
|
|
349
|
-
: "bg-secondary text-secondary-foreground",
|
|
350
|
-
)}
|
|
351
|
-
style={
|
|
352
|
-
isUser ? { background: MESSENGER_GRADIENT } : undefined
|
|
353
|
-
}
|
|
354
|
-
>
|
|
355
|
-
{isUser ? (
|
|
356
|
-
m.content
|
|
357
|
-
) : (
|
|
358
|
-
<MessageResponse onLinkClick={onLinkClick}>
|
|
359
|
-
{m.content}
|
|
360
|
-
</MessageResponse>
|
|
361
|
-
)}
|
|
362
|
-
</MessageContent>
|
|
363
|
-
{m.actions && m.actions.length > 0 && (
|
|
364
|
-
<div className="flex flex-wrap gap-2">
|
|
365
|
-
{m.actions.map((action, i) => (
|
|
366
|
-
<Button
|
|
367
|
-
key={i}
|
|
368
|
-
size="sm"
|
|
369
|
-
variant={action.variant ?? "outline"}
|
|
370
|
-
onClick={action.onClick}
|
|
371
|
-
className="rounded-full"
|
|
372
|
-
>
|
|
373
|
-
{action.label}
|
|
374
|
-
</Button>
|
|
375
|
-
))}
|
|
376
|
-
</div>
|
|
377
|
-
)}
|
|
378
|
-
</div>
|
|
379
|
-
</Message>
|
|
380
|
-
);
|
|
381
|
-
})}
|
|
382
|
-
{isTyping && (
|
|
383
|
-
<motion.div
|
|
384
|
-
initial={{ opacity: 0, y: 6 }}
|
|
385
|
-
animate={{ opacity: 1, y: 0 }}
|
|
386
|
-
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
387
|
-
className="flex w-full justify-start"
|
|
388
|
-
>
|
|
389
|
-
<div className="flex max-w-[80%] items-center rounded-[18px] bg-secondary px-3.5 py-2 text-secondary-foreground">
|
|
390
|
-
<ChatWidgetTyping className="bg-transparent! p-0!" />
|
|
391
|
-
</div>
|
|
392
|
-
</motion.div>
|
|
393
|
-
)}
|
|
394
|
-
</ConversationContent>
|
|
395
|
-
<ConversationScrollButton />
|
|
396
|
-
</Conversation>
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
function MessengerHeader({
|
|
401
|
-
agentName,
|
|
402
|
-
agentAvatarUrl,
|
|
403
|
-
statusText,
|
|
404
|
-
onClose,
|
|
405
|
-
onStopConversation,
|
|
406
|
-
stopDisabled,
|
|
407
|
-
}: {
|
|
408
|
-
agentName: string;
|
|
409
|
-
agentAvatarUrl?: string | null;
|
|
410
|
-
statusText: string;
|
|
411
|
-
onClose: () => void;
|
|
412
|
-
onStopConversation?: () => void;
|
|
413
|
-
stopDisabled?: boolean;
|
|
414
|
-
}) {
|
|
415
|
-
return (
|
|
416
|
-
<div
|
|
417
|
-
className="flex shrink-0 items-center gap-3 px-4 py-3 text-white"
|
|
418
|
-
style={{ backgroundColor: MESSENGER_BRAND_COLOR }}
|
|
419
|
-
>
|
|
420
|
-
<div className="relative">
|
|
421
|
-
<Avatar size="lg" className="ring-2 ring-white/30">
|
|
422
|
-
{agentAvatarUrl ? (
|
|
423
|
-
<AvatarImage src={agentAvatarUrl} alt={agentName} />
|
|
424
|
-
) : null}
|
|
425
|
-
<AvatarFallback className="bg-white/20 text-white">
|
|
426
|
-
{agentName.slice(0, 2).toUpperCase()}
|
|
427
|
-
</AvatarFallback>
|
|
428
|
-
</Avatar>
|
|
429
|
-
<span className="absolute -bottom-0.5 -right-0.5 size-3 rounded-full bg-[#31D778] ring-2 ring-white" />
|
|
430
|
-
</div>
|
|
431
|
-
<div className="min-w-0 flex-1">
|
|
432
|
-
<div className="truncate text-[15px] font-semibold leading-tight">
|
|
433
|
-
{agentName}
|
|
434
|
-
</div>
|
|
435
|
-
<div className="text-[12px] leading-tight text-white/80">
|
|
436
|
-
{statusText}
|
|
437
|
-
</div>
|
|
438
|
-
</div>
|
|
439
|
-
<div className="flex shrink-0 items-center gap-0.5">
|
|
440
|
-
<button
|
|
441
|
-
type="button"
|
|
442
|
-
aria-label="Phone"
|
|
443
|
-
className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white"
|
|
444
|
-
>
|
|
445
|
-
<Phone className="size-4.5" strokeWidth={2.2} />
|
|
446
|
-
</button>
|
|
447
|
-
<button
|
|
448
|
-
type="button"
|
|
449
|
-
aria-label="Video"
|
|
450
|
-
className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white"
|
|
451
|
-
>
|
|
452
|
-
<Video className="size-4.5" strokeWidth={2.2} />
|
|
453
|
-
</button>
|
|
454
|
-
{onStopConversation != null && (
|
|
455
|
-
<button
|
|
456
|
-
type="button"
|
|
457
|
-
aria-label="Conversation info"
|
|
458
|
-
title="End conversation"
|
|
459
|
-
disabled={stopDisabled}
|
|
460
|
-
onClick={onStopConversation}
|
|
461
|
-
className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white disabled:opacity-50"
|
|
462
|
-
>
|
|
463
|
-
<Info className="size-4.5" strokeWidth={2.2} />
|
|
464
|
-
</button>
|
|
465
|
-
)}
|
|
466
|
-
<button
|
|
467
|
-
type="button"
|
|
468
|
-
aria-label="Minimize"
|
|
469
|
-
onClick={onClose}
|
|
470
|
-
className="ml-0.5 flex size-8 items-center justify-center rounded-full text-white/90 transition-colors hover:bg-white/15 hover:text-white"
|
|
471
|
-
>
|
|
472
|
-
<ChevronDown className="size-5" strokeWidth={2.4} />
|
|
473
|
-
</button>
|
|
474
|
-
</div>
|
|
475
|
-
</div>
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
function MessengerComposer({
|
|
480
|
-
value,
|
|
481
|
-
onChange,
|
|
482
|
-
onSubmit,
|
|
483
|
-
disabled,
|
|
484
|
-
}: {
|
|
485
|
-
value: string;
|
|
486
|
-
onChange: (value: string) => void;
|
|
487
|
-
onSubmit?: (value: string) => void;
|
|
488
|
-
disabled?: boolean;
|
|
489
|
-
}) {
|
|
490
|
-
const textareaRef = React.useRef<HTMLTextAreaElement>(null);
|
|
491
|
-
|
|
492
|
-
const submit = () => {
|
|
493
|
-
const trimmed = value.trim();
|
|
494
|
-
if (!trimmed || !onSubmit) return;
|
|
495
|
-
onSubmit(trimmed);
|
|
496
|
-
onChange("");
|
|
497
|
-
textareaRef.current?.focus();
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
501
|
-
if (e.key !== "Enter") return;
|
|
502
|
-
if (e.shiftKey) return;
|
|
503
|
-
e.preventDefault();
|
|
504
|
-
submit();
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
const hasText = value.trim().length > 0;
|
|
508
|
-
const sendDisabled = disabled || !onSubmit || !hasText;
|
|
509
|
-
|
|
510
|
-
return (
|
|
511
|
-
<div className="shrink-0 border-t border-border bg-background px-2 py-2">
|
|
512
|
-
<div className="flex items-end gap-0.5">
|
|
513
|
-
{/* Tool icons (Plus / Camera / Image / Mic) temporarily disabled. */}
|
|
514
|
-
<div className="flex flex-1 items-end rounded-3xl bg-secondary px-3.5">
|
|
515
|
-
<Textarea
|
|
516
|
-
ref={textareaRef}
|
|
517
|
-
placeholder="Aa"
|
|
518
|
-
value={value}
|
|
519
|
-
onChange={(e) => onChange(e.target.value)}
|
|
520
|
-
onKeyDown={handleKeyDown}
|
|
521
|
-
rows={1}
|
|
522
|
-
disabled={disabled}
|
|
523
|
-
autoResize
|
|
524
|
-
className={cn(
|
|
525
|
-
"min-h-9 max-h-32 flex-1 border-0 bg-transparent py-2 text-[14px] leading-snug shadow-none",
|
|
526
|
-
"focus-visible:ring-0 focus-visible:border-0",
|
|
527
|
-
)}
|
|
528
|
-
/>
|
|
529
|
-
</div>
|
|
530
|
-
<button
|
|
531
|
-
type="button"
|
|
532
|
-
aria-label="Send"
|
|
533
|
-
onClick={submit}
|
|
534
|
-
disabled={sendDisabled}
|
|
535
|
-
className={cn(
|
|
536
|
-
"ml-1 flex size-9 shrink-0 items-center justify-center rounded-full transition-colors",
|
|
537
|
-
"hover:bg-secondary disabled:hover:bg-transparent",
|
|
538
|
-
)}
|
|
539
|
-
style={{ color: MESSENGER_BRAND_COLOR }}
|
|
540
|
-
>
|
|
541
|
-
<SendHorizonal
|
|
542
|
-
className={cn(
|
|
543
|
-
"size-5 transition-opacity",
|
|
544
|
-
sendDisabled && "opacity-40",
|
|
545
|
-
)}
|
|
546
|
-
strokeWidth={2.2}
|
|
547
|
-
/>
|
|
548
|
-
</button>
|
|
549
|
-
</div>
|
|
550
|
-
</div>
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
function MessengerLogo({ className }: { className?: string }) {
|
|
555
|
-
return (
|
|
556
|
-
<svg
|
|
557
|
-
viewBox="0 0 24 24"
|
|
558
|
-
fill="currentColor"
|
|
559
|
-
aria-hidden="true"
|
|
560
|
-
className={className}
|
|
561
|
-
>
|
|
562
|
-
<path d="M12 2C6.48 2 2 6.18 2 11.32c0 2.93 1.45 5.55 3.7 7.27v3.4l3.39-1.86a10.5 10.5 0 0 0 2.91.41c5.52 0 10-4.18 10-9.22S17.52 2 12 2Zm1.06 12.42-2.55-2.72-4.97 2.72 5.46-5.8 2.61 2.72 4.91-2.72-5.46 5.8Z" />
|
|
563
|
-
</svg>
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
export const MESSENGER_PRESET_GRADIENT = MESSENGER_GRADIENT;
|
|
568
|
-
|
|
569
|
-
export const MESSENGER_PRESET_APPEARANCE = {
|
|
570
|
-
avatarOrbColor1: "#00B2FF",
|
|
571
|
-
avatarOrbColor2: "#006AFF",
|
|
572
|
-
actionText: "Chat with us",
|
|
573
|
-
} as const;
|