@usereq/widget 0.1.2 → 0.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usereq/widget",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -7,3 +7,4 @@ export * from "./stop-confirmation";
7
7
  export * from "./styles/chat-widget-bubble";
8
8
  export * from "./styles/chat-widget-box";
9
9
  export * from "./styles/chat-widget-chatbar";
10
+ export * from "./styles/chat-widget-messenger";
@@ -0,0 +1,396 @@
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 { X, SendHorizonal, Phone, Video, Info, ThumbsUp } from "lucide-react";
14
+ import {
15
+ DEFAULT_AGENT_NAME_BUBBLE,
16
+ DEFAULT_COMPOSER_PLACEHOLDER,
17
+ DEFAULT_POWERED_BY,
18
+ DEFAULT_SPOTLIGHT_MESSAGES,
19
+ } from "../chat-widget-defaults";
20
+ import {
21
+ ChatWidgetMessages,
22
+ ChatWidgetPanel,
23
+ ChatWidgetPoweredBy,
24
+ useIsChatWidgetFullscreenViewport,
25
+ useSpotlightFade,
26
+ type ChatWidgetDataProps,
27
+ } from "../components/chat-widget-primitives";
28
+
29
+ const MESSENGER_GRADIENT =
30
+ "linear-gradient(135deg, #00B2FF 0%, #006AFF 50%, #8E5BFF 100%)";
31
+
32
+ const PANEL_WIDTH = 372;
33
+ const PANEL_HEIGHT = 580;
34
+ const LAUNCHER_SIZE = 60;
35
+ const SPOTLIGHT_GAP = 10;
36
+ const PANEL_GAP = 14;
37
+
38
+ const fadeTransition = { duration: 0.35 };
39
+
40
+ export function ChatWidgetMessenger({
41
+ agentName = DEFAULT_AGENT_NAME_BUBBLE,
42
+ agentAvatarUrl,
43
+ appearance,
44
+ spotlightMessages = [],
45
+ messages = [],
46
+ poweredBy = DEFAULT_POWERED_BY,
47
+ headerStatus = "Active now",
48
+ errorMessage,
49
+ status = "idle",
50
+ sendDisabled = false,
51
+ onSendMessage,
52
+ isTyping,
53
+ onSuggestion,
54
+ onLauncherClick,
55
+ className,
56
+ open,
57
+ defaultOpen = false,
58
+ onOpenChange,
59
+ previewOnly = false,
60
+ onStopConversation,
61
+ stopDisabled,
62
+ onConfirmationDecision,
63
+ onLinkClick,
64
+ emptyAction,
65
+ portalContainer,
66
+ }: ChatWidgetDataProps & {
67
+ open?: boolean;
68
+ className?: string;
69
+ defaultOpen?: boolean;
70
+ onOpenChange?: (open: boolean) => void;
71
+ onLauncherClick?: () => void;
72
+ previewOnly?: boolean;
73
+ portalContainer?: React.ComponentProps<
74
+ typeof ChatWidgetPanel
75
+ >["portalContainer"];
76
+ }) {
77
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(
78
+ previewOnly ? false : defaultOpen,
79
+ );
80
+ const [draftValue, setDraftValue] = React.useState("");
81
+ const spotlightForFade =
82
+ spotlightMessages.length > 0
83
+ ? spotlightMessages
84
+ : [...DEFAULT_SPOTLIGHT_MESSAGES];
85
+ const spotlightText = useSpotlightFade(spotlightForFade, 3200);
86
+ const effectiveOpen = previewOnly ? false : (open ?? uncontrolledOpen);
87
+ const handleOpenChange = React.useCallback(
88
+ (nextOpen: boolean) => {
89
+ if (onOpenChange) {
90
+ onOpenChange(nextOpen);
91
+ return;
92
+ }
93
+ setUncontrolledOpen(nextOpen);
94
+ },
95
+ [onOpenChange],
96
+ );
97
+ const isFullscreenViewport = useIsChatWidgetFullscreenViewport();
98
+ const hideLauncher = isFullscreenViewport && effectiveOpen;
99
+
100
+ const sendIsDisabled = sendDisabled || status === "booting";
101
+
102
+ return (
103
+ <div
104
+ className={cn("relative", className)}
105
+ style={{
106
+ width: spotlightForFade.length > 0 ? 280 : LAUNCHER_SIZE,
107
+ height:
108
+ spotlightForFade.length > 0
109
+ ? LAUNCHER_SIZE + SPOTLIGHT_GAP + 56
110
+ : LAUNCHER_SIZE,
111
+ }}
112
+ >
113
+ {spotlightForFade.length > 0 && !effectiveOpen && (
114
+ <AnimatePresence mode="wait">
115
+ <motion.div
116
+ key={spotlightText}
117
+ initial={{ opacity: 0, y: 6 }}
118
+ animate={{ opacity: 1, y: 0 }}
119
+ exit={{ opacity: 0, y: 6 }}
120
+ transition={fadeTransition}
121
+ className="absolute right-0 bottom-[calc(60px+10px)] flex w-full max-w-70 flex-col items-end"
122
+ >
123
+ <div className="rounded-3xl bg-white px-4 py-2.5 text-right shadow-[0_4px_16px_rgba(0,0,0,0.12)]">
124
+ <p className="text-[14px] leading-snug text-foreground">
125
+ {spotlightText}
126
+ </p>
127
+ </div>
128
+ </motion.div>
129
+ </AnimatePresence>
130
+ )}
131
+
132
+ {!previewOnly && (
133
+ <ChatWidgetPanel
134
+ open={effectiveOpen}
135
+ onOpenChange={handleOpenChange}
136
+ portalContainer={portalContainer}
137
+ desktopClassName="absolute right-0 overflow-hidden rounded-3xl bg-background shadow-[0_8px_32px_rgba(0,0,0,0.16)] border border-black/5"
138
+ desktopStyle={{
139
+ width: PANEL_WIDTH,
140
+ height: PANEL_HEIGHT,
141
+ bottom: LAUNCHER_SIZE + PANEL_GAP,
142
+ }}
143
+ >
144
+ <MessengerHeader
145
+ agentName={agentName}
146
+ agentAvatarUrl={agentAvatarUrl}
147
+ statusText={headerStatus}
148
+ onClose={() => handleOpenChange(false)}
149
+ onStopConversation={onStopConversation ?? undefined}
150
+ stopDisabled={stopDisabled}
151
+ />
152
+ <div className="flex min-h-0 flex-1 flex-col bg-[#F5F5F7] dark:bg-background overflow-hidden">
153
+ <ChatWidgetMessages
154
+ messages={messages}
155
+ isTyping={isTyping ?? status === "sending"}
156
+ status={status}
157
+ suggestions={spotlightForFade}
158
+ errorMessage={errorMessage}
159
+ appearance={appearance}
160
+ onConfirmationDecision={onConfirmationDecision}
161
+ onLinkClick={onLinkClick}
162
+ onSuggestion={(value: string) => {
163
+ setDraftValue(value);
164
+ onSuggestion?.(value);
165
+ }}
166
+ emptyAction={emptyAction}
167
+ />
168
+ </div>
169
+ {!(emptyAction && messages.length === 0 && !isTyping) && (
170
+ <MessengerComposer
171
+ value={draftValue}
172
+ onChange={setDraftValue}
173
+ onSubmit={onSendMessage}
174
+ disabled={sendIsDisabled}
175
+ />
176
+ )}
177
+ <ChatWidgetPoweredBy
178
+ text={poweredBy}
179
+ className="bg-background py-1.5"
180
+ />
181
+ </ChatWidgetPanel>
182
+ )}
183
+
184
+ {!hideLauncher && (
185
+ <motion.button
186
+ type="button"
187
+ onClick={
188
+ previewOnly
189
+ ? undefined
190
+ : () => {
191
+ onLauncherClick?.();
192
+ handleOpenChange(!effectiveOpen);
193
+ }
194
+ }
195
+ className={cn(
196
+ "absolute bottom-0 right-0 flex items-center justify-center rounded-full text-white",
197
+ previewOnly && "pointer-events-none",
198
+ )}
199
+ style={{
200
+ width: LAUNCHER_SIZE,
201
+ height: LAUNCHER_SIZE,
202
+ background: MESSENGER_GRADIENT,
203
+ boxShadow: "0 6px 20px rgba(0, 132, 255, 0.45)",
204
+ }}
205
+ aria-label={effectiveOpen ? "Close chat" : "Open chat"}
206
+ aria-expanded={effectiveOpen}
207
+ animate={{ scale: 1 }}
208
+ whileHover={previewOnly ? undefined : { scale: 1.04 }}
209
+ whileTap={previewOnly ? undefined : { scale: 0.96 }}
210
+ >
211
+ {effectiveOpen ? (
212
+ <X className="size-7" strokeWidth={2.4} />
213
+ ) : (
214
+ <MessengerLogo className="size-8" />
215
+ )}
216
+ </motion.button>
217
+ )}
218
+ </div>
219
+ );
220
+ }
221
+
222
+ function MessengerHeader({
223
+ agentName,
224
+ agentAvatarUrl,
225
+ statusText,
226
+ onClose,
227
+ onStopConversation,
228
+ stopDisabled,
229
+ }: {
230
+ agentName: string;
231
+ agentAvatarUrl?: string | null;
232
+ statusText: string;
233
+ onClose: () => void;
234
+ onStopConversation?: () => void;
235
+ stopDisabled?: boolean;
236
+ }) {
237
+ return (
238
+ <div
239
+ className="flex shrink-0 items-center gap-3 px-4 py-3 text-white"
240
+ style={{ background: MESSENGER_GRADIENT }}
241
+ >
242
+ <div className="relative">
243
+ <Avatar size="lg" className="ring-2 ring-white/30">
244
+ {agentAvatarUrl ? (
245
+ <AvatarImage src={agentAvatarUrl} alt={agentName} />
246
+ ) : null}
247
+ <AvatarFallback className="bg-white/20 text-white">
248
+ {agentName.slice(0, 2).toUpperCase()}
249
+ </AvatarFallback>
250
+ </Avatar>
251
+ <span className="absolute -bottom-0.5 -right-0.5 size-3 rounded-full bg-[#31D778] ring-2 ring-white" />
252
+ </div>
253
+ <div className="min-w-0 flex-1">
254
+ <div className="truncate text-[15px] font-semibold leading-tight">
255
+ {agentName}
256
+ </div>
257
+ <div className="text-[12px] leading-tight text-white/80">
258
+ {statusText}
259
+ </div>
260
+ </div>
261
+ <div className="flex shrink-0 items-center gap-0.5">
262
+ <button
263
+ type="button"
264
+ aria-label="Phone"
265
+ className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white"
266
+ >
267
+ <Phone className="size-4.5" strokeWidth={2.2} />
268
+ </button>
269
+ <button
270
+ type="button"
271
+ aria-label="Video"
272
+ className="flex size-8 items-center justify-center rounded-full text-white/85 transition-colors hover:bg-white/15 hover:text-white"
273
+ >
274
+ <Video className="size-4.5" strokeWidth={2.2} />
275
+ </button>
276
+ {onStopConversation != null && (
277
+ <button
278
+ type="button"
279
+ aria-label="Conversation info"
280
+ title="End conversation"
281
+ disabled={stopDisabled}
282
+ onClick={onStopConversation}
283
+ 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"
284
+ >
285
+ <Info className="size-4.5" strokeWidth={2.2} />
286
+ </button>
287
+ )}
288
+ <button
289
+ type="button"
290
+ aria-label="Close"
291
+ onClick={onClose}
292
+ 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"
293
+ >
294
+ <X className="size-4.5" strokeWidth={2.4} />
295
+ </button>
296
+ </div>
297
+ </div>
298
+ );
299
+ }
300
+
301
+ function MessengerComposer({
302
+ value,
303
+ onChange,
304
+ onSubmit,
305
+ disabled,
306
+ }: {
307
+ value: string;
308
+ onChange: (value: string) => void;
309
+ onSubmit?: (value: string) => void;
310
+ disabled?: boolean;
311
+ }) {
312
+ const textareaRef = React.useRef<HTMLTextAreaElement>(null);
313
+
314
+ const submit = () => {
315
+ const trimmed = value.trim();
316
+ if (!trimmed || !onSubmit) return;
317
+ onSubmit(trimmed);
318
+ onChange("");
319
+ textareaRef.current?.focus();
320
+ };
321
+
322
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
323
+ if (e.key !== "Enter") return;
324
+ if (e.shiftKey) return;
325
+ e.preventDefault();
326
+ submit();
327
+ };
328
+
329
+ const trimmedValue = value.trim();
330
+ const showSend = trimmedValue.length > 0;
331
+
332
+ return (
333
+ <div className="shrink-0 border-t border-black/5 bg-background px-3 py-2.5">
334
+ <div className="flex items-end gap-2">
335
+ <div className="flex flex-1 items-end rounded-3xl bg-[#F0F0F2] px-3 dark:bg-muted">
336
+ <Textarea
337
+ ref={textareaRef}
338
+ placeholder={DEFAULT_COMPOSER_PLACEHOLDER}
339
+ value={value}
340
+ onChange={(e) => onChange(e.target.value)}
341
+ onKeyDown={handleKeyDown}
342
+ rows={1}
343
+ disabled={disabled}
344
+ className={cn(
345
+ "min-h-9 max-h-32 flex-1 resize-none border-0 bg-transparent py-2 text-[14px] shadow-none",
346
+ "focus-visible:ring-0 focus-visible:border-0",
347
+ )}
348
+ />
349
+ </div>
350
+ {showSend ? (
351
+ <Button
352
+ type="button"
353
+ size="icon"
354
+ aria-label="Send"
355
+ onClick={submit}
356
+ disabled={disabled || !onSubmit}
357
+ className="h-9 w-9 shrink-0 rounded-full text-white hover:opacity-90 disabled:opacity-60"
358
+ style={{ background: MESSENGER_GRADIENT }}
359
+ >
360
+ <SendHorizonal className="size-4" />
361
+ </Button>
362
+ ) : (
363
+ <button
364
+ type="button"
365
+ aria-label="Like"
366
+ disabled
367
+ className="flex size-9 shrink-0 items-center justify-center rounded-full text-[#0084FF] disabled:opacity-100"
368
+ >
369
+ <ThumbsUp className="size-5" strokeWidth={2.2} />
370
+ </button>
371
+ )}
372
+ </div>
373
+ </div>
374
+ );
375
+ }
376
+
377
+ function MessengerLogo({ className }: { className?: string }) {
378
+ return (
379
+ <svg
380
+ viewBox="0 0 24 24"
381
+ fill="currentColor"
382
+ aria-hidden="true"
383
+ className={className}
384
+ >
385
+ <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" />
386
+ </svg>
387
+ );
388
+ }
389
+
390
+ export const MESSENGER_PRESET_GRADIENT = MESSENGER_GRADIENT;
391
+
392
+ export const MESSENGER_PRESET_APPEARANCE = {
393
+ avatarOrbColor1: "#00B2FF",
394
+ avatarOrbColor2: "#006AFF",
395
+ actionText: "Chat with us",
396
+ } as const;
@@ -3,11 +3,14 @@ import { createRoot, type Root } from "react-dom/client";
3
3
  import { DEFAULT_START_CONVERSATION_LABEL } from "../chat-widget/chat-widget-defaults";
4
4
  import {
5
5
  DEFAULT_WIDGET_APPEARANCE,
6
+ DEFAULT_WIDGET_PRESET,
6
7
  normalizeWidgetPlacement,
8
+ normalizeWidgetPreset,
7
9
  normalizeWidgetVariant,
8
10
  type WidgetAppearance,
9
11
  type WidgetMessage,
10
12
  type WidgetPlacement,
13
+ type WidgetPreset,
11
14
  type WidgetSessionConfig,
12
15
  type WidgetSessionState,
13
16
  type WidgetVariant,
@@ -38,6 +41,7 @@ export class AgentWidgetElement extends HTMLElement {
38
41
  "agent-id",
39
42
  "mode",
40
43
  "variant",
44
+ "preset",
41
45
  "placement",
42
46
  "avatar-orb-color-1",
43
47
  "avatar-orb-color-2",
@@ -52,6 +56,7 @@ export class AgentWidgetElement extends HTMLElement {
52
56
  private agentId = "";
53
57
  private mode: "embed" | "preview" = "embed";
54
58
  private variant: string | null = null;
59
+ private preset: string | null = null;
55
60
  private placement: string | null = null;
56
61
  private appearanceAttributes: WidgetAppearanceAttributes = {};
57
62
  private open = false;
@@ -139,6 +144,7 @@ export class AgentWidgetElement extends HTMLElement {
139
144
  this.agentId = this.getAttribute("agent-id")?.trim() ?? "";
140
145
  this.mode = normalizeWidgetMode(this.getAttribute("mode"));
141
146
  this.variant = this.getAttribute("variant");
147
+ this.preset = this.getAttribute("preset");
142
148
  this.placement = this.getAttribute("placement");
143
149
  this.appearanceAttributes = parseAppearanceAttributes(this);
144
150
  }
@@ -155,6 +161,14 @@ export class AgentWidgetElement extends HTMLElement {
155
161
  );
156
162
  }
157
163
 
164
+ private getResolvedPreset(): WidgetPreset {
165
+ return normalizeWidgetPreset(
166
+ this.preset ??
167
+ this.sessionConfig?.widgetConfig.widgetPreset ??
168
+ DEFAULT_WIDGET_PRESET,
169
+ );
170
+ }
171
+
158
172
  private getResolvedAppearance(): WidgetAppearance {
159
173
  const base =
160
174
  this.sessionConfig?.widgetConfig.widgetAppearance ?? DEFAULT_WIDGET_APPEARANCE;
@@ -231,6 +245,7 @@ export class AgentWidgetElement extends HTMLElement {
231
245
  const appearance = this.getResolvedAppearance();
232
246
  const placement = this.getResolvedPlacement();
233
247
  const variant = this.getResolvedVariant();
248
+ const preset = this.getResolvedPreset();
234
249
  const emptyAction =
235
250
  this.status === "booting" ||
236
251
  (this.sessionState != null && !this.sessionState.conversationId)
@@ -249,6 +264,7 @@ export class AgentWidgetElement extends HTMLElement {
249
264
  mode: this.mode,
250
265
  agentId: this.agentId,
251
266
  variant,
267
+ preset,
252
268
  placement,
253
269
  appearance,
254
270
  agentAvatarUrl: this.getAgentAvatarUrl(),
@@ -3,12 +3,14 @@ import {
3
3
  ChatWidgetBubble,
4
4
  ChatWidgetBox,
5
5
  ChatWidgetChatBar,
6
+ ChatWidgetMessenger,
6
7
  } from "../chat-widget";
7
8
  import type { WidgetMessage } from "../types";
8
9
  import {
9
10
  splitWidgetPlacement,
10
11
  type WidgetAppearance,
11
12
  type WidgetPlacement,
13
+ type WidgetPreset,
12
14
  type WidgetVariant,
13
15
  } from "../shared/widget-config";
14
16
  import type { WidgetTriggerRule } from "../shared/widget-config";
@@ -25,6 +27,7 @@ export type WidgetRuntimeProps = {
25
27
  mode: WidgetRuntimeMode;
26
28
  agentId: string;
27
29
  variant: WidgetVariant;
30
+ preset?: WidgetPreset;
28
31
  placement: WidgetPlacement;
29
32
  appearance: WidgetAppearance;
30
33
  agentAvatarUrl: string | null;
@@ -57,10 +60,15 @@ const VARIANT_COMPONENTS = {
57
60
  chatbar: ChatWidgetChatBar,
58
61
  } as const;
59
62
 
63
+ const PRESET_COMPONENTS = {
64
+ facebook_messenger: ChatWidgetMessenger,
65
+ } as const;
66
+
60
67
  export function WidgetRuntime({
61
68
  mode,
62
69
  agentId,
63
70
  variant,
71
+ preset = "default",
64
72
  placement,
65
73
  appearance,
66
74
  agentAvatarUrl,
@@ -84,7 +92,12 @@ export function WidgetRuntime({
84
92
  }: WidgetRuntimeProps) {
85
93
  const { ready } = useWidgetTriggerGate(triggerRule);
86
94
  const { horizontal, vertical } = splitWidgetPlacement(placement);
87
- const Component = VARIANT_COMPONENTS[variant] ?? ChatWidgetBubble;
95
+ const PresetComponent =
96
+ preset !== "default"
97
+ ? PRESET_COMPONENTS[preset as Exclude<WidgetPreset, "default">]
98
+ : undefined;
99
+ const Component =
100
+ PresetComponent ?? VARIANT_COMPONENTS[variant] ?? ChatWidgetBubble;
88
101
  const shellStyle = getWidgetShellStyle(mode, placement);
89
102
  const resolvedAvatarUrl = agentAvatarUrl?.trim() || undefined;
90
103
  const pendingConfirmationId = getPendingStopConfirmationId(messages);
@@ -115,6 +128,7 @@ export function WidgetRuntime({
115
128
  data-horizontal={horizontal}
116
129
  data-vertical={vertical}
117
130
  data-variant={variant}
131
+ data-preset={preset}
118
132
  data-open={String(open)}
119
133
  data-status={status}
120
134
  style={shellStyle}
@@ -14,6 +14,10 @@ export type WidgetPlacement =
14
14
 
15
15
  export type WidgetVariant = "default" | "chatbar" | "box";
16
16
 
17
+ export type WidgetPreset = "default" | "facebook_messenger";
18
+
19
+ export const DEFAULT_WIDGET_PRESET: WidgetPreset = "default";
20
+
17
21
  export type WidgetAppearance = ResolvedChatWidgetAppearance;
18
22
 
19
23
  export const DEFAULT_WIDGET_APPEARANCE: WidgetAppearance =
@@ -64,6 +68,20 @@ export function widgetAppearanceToAttributes(
64
68
  ];
65
69
  }
66
70
 
71
+ export function normalizeWidgetPreset(
72
+ value: string | null | undefined,
73
+ ): WidgetPreset {
74
+ switch (value) {
75
+ case "facebook_messenger":
76
+ case "facebook-messenger":
77
+ return "facebook_messenger";
78
+ case "default":
79
+ return "default";
80
+ default:
81
+ return DEFAULT_WIDGET_PRESET;
82
+ }
83
+ }
84
+
67
85
  export function normalizeWidgetVariant(
68
86
  value: string | null | undefined,
69
87
  ): WidgetVariant {
package/src/types.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  import type {
2
2
  WidgetAppearance,
3
3
  WidgetPlacement,
4
+ WidgetPreset,
4
5
  WidgetVariant,
5
6
  WidgetTriggerRule,
6
7
  } from "./shared/widget-config";
7
8
 
8
9
  export {
9
10
  DEFAULT_WIDGET_APPEARANCE,
11
+ DEFAULT_WIDGET_PRESET,
10
12
  type WidgetAppearance,
11
13
  type WidgetPlacement,
14
+ type WidgetPreset,
12
15
  type WidgetVariant,
13
16
  normalizeWidgetAppearance,
14
17
  normalizeWidgetPlacement,
18
+ normalizeWidgetPreset,
15
19
  normalizeWidgetVariant,
16
20
  splitWidgetPlacement,
17
21
  widgetAppearanceToAttributes,
@@ -37,6 +41,7 @@ export type WidgetSessionConfig = {
37
41
  spotlightMessages: string[];
38
42
  widgetAppearance: WidgetAppearance;
39
43
  widgetVariant: WidgetVariant;
44
+ widgetPreset: WidgetPreset;
40
45
  widgetPlacement: WidgetPlacement;
41
46
  allowedDomains: string[];
42
47
  triggerRule: WidgetTriggerRule | null;