@envive-ai/react-toolkit-v3 0.3.26 → 0.3.27

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 (142) hide show
  1. package/dist/AnimatedText/AnimatedText.d.cts +3 -3
  2. package/dist/AnimatedText/AnimatedText.d.ts +3 -3
  3. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.ts +2 -2
  4. package/dist/Carousel/Carousel.d.cts +2 -2
  5. package/dist/Carousel/Carousel.d.ts +2 -2
  6. package/dist/ChatFooter/ChatFooter.cjs +1 -0
  7. package/dist/ChatFooter/ChatFooter.d.cts +2 -2
  8. package/dist/ChatFooter/ChatFooter.d.ts +2 -2
  9. package/dist/ChatFooter/ChatFooter.js +1 -0
  10. package/dist/ChatFooter/components/Layout.cjs +2 -2
  11. package/dist/ChatFooter/components/Layout.d.cts +1 -0
  12. package/dist/ChatFooter/components/Layout.d.ts +1 -0
  13. package/dist/ChatFooter/components/Layout.js +2 -2
  14. package/dist/ChatFooter/components/index.d.cts +3 -2
  15. package/dist/ChatFooter/components/index.d.ts +6 -5
  16. package/dist/ChatHeader/ChatHeader.d.cts +2 -2
  17. package/dist/ChatHeader/ChatHeader.d.ts +2 -2
  18. package/dist/ChatPreview/ChatPreview.d.cts +2 -2
  19. package/dist/ChatPreview/ChatPreview.d.ts +2 -2
  20. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
  21. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.ts +2 -2
  22. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
  23. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
  24. package/dist/Container/Container.d.cts +175 -175
  25. package/dist/Container/Container.d.ts +175 -175
  26. package/dist/DesignTokens/DesignTokensComponent.d.cts +2 -2
  27. package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
  28. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
  29. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.ts +2 -2
  30. package/dist/DocumentRetrievalCard/components/ViewArticleButton.cjs +1 -1
  31. package/dist/DocumentRetrievalCard/components/ViewArticleButton.js +1 -1
  32. package/dist/FloatingButton/FloatingButton.d.cts +2 -2
  33. package/dist/FloatingButton/FloatingButton.d.ts +2 -2
  34. package/dist/FloatingChat/FloatingChat.d.cts +2 -2
  35. package/dist/FloatingChat/FloatingChat.d.ts +2 -2
  36. package/dist/FloatingChat/components/SalesAgentBadgeContent.cjs +5 -1
  37. package/dist/FloatingChat/components/SalesAgentBadgeContent.js +5 -1
  38. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.cjs +2 -1
  39. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.js +2 -1
  40. package/dist/FullPageSalesAgent/FullPageSalesAgent.cjs +89 -25
  41. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.cts +5 -7
  42. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.ts +5 -7
  43. package/dist/FullPageSalesAgent/FullPageSalesAgent.js +90 -26
  44. package/dist/FullPageSalesAgent/components/Layout.cjs +34 -27
  45. package/dist/FullPageSalesAgent/components/Layout.js +34 -27
  46. package/dist/FullPageSalesAgent/components/WelcomeOverlay.cjs +102 -0
  47. package/dist/FullPageSalesAgent/components/WelcomeOverlay.js +100 -0
  48. package/dist/FullPageSalesAgent/components/index.cjs +1 -0
  49. package/dist/FullPageSalesAgent/components/index.js +1 -0
  50. package/dist/FullPageSalesAgent/hooks/useGetFooterStyles.cjs +16 -6
  51. package/dist/FullPageSalesAgent/hooks/useGetFooterStyles.js +16 -6
  52. package/dist/FullPageSalesAgent/hooks/useWelcomeOverlayProducts.cjs +43 -0
  53. package/dist/FullPageSalesAgent/hooks/useWelcomeOverlayProducts.js +41 -0
  54. package/dist/Image/Image.d.cts +2 -2
  55. package/dist/ImageGallery/ImageGallery.d.cts +2 -2
  56. package/dist/ImageGallery/ImageGallery.d.ts +2 -2
  57. package/dist/MarkdownProcessor/MarkdownProcessor.d.cts +2 -2
  58. package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
  59. package/dist/Message/components/LinkButton.cjs +1 -1
  60. package/dist/Message/components/LinkButton.js +1 -1
  61. package/dist/ProductCard/ProductCard.d.cts +2 -2
  62. package/dist/ProductCard/ProductCard.d.ts +2 -2
  63. package/dist/PromptButton/PromptButton.d.cts +2 -2
  64. package/dist/PromptButton/PromptButton.d.ts +2 -2
  65. package/dist/PromptButton/hooks/useGetLayoutBaseProperties.cjs +1 -1
  66. package/dist/PromptButton/hooks/useGetLayoutBaseProperties.js +1 -1
  67. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
  68. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
  69. package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
  70. package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
  71. package/dist/ReviewCard/ReviewCard.d.cts +2 -2
  72. package/dist/ReviewCard/ReviewCard.d.ts +2 -2
  73. package/dist/ReviewCard/components/index.d.cts +6 -6
  74. package/dist/ReviewCard/components/index.d.ts +6 -6
  75. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.cts +2 -2
  76. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.ts +2 -2
  77. package/dist/SalesAgentProductCard/components/Price.cjs +2 -2
  78. package/dist/SalesAgentProductCard/components/Price.js +2 -2
  79. package/dist/SalesAgentProductCard/components/Rate.cjs +1 -1
  80. package/dist/SalesAgentProductCard/components/Rate.js +1 -1
  81. package/dist/SalesAgentProductCard/components/index.d.cts +8 -8
  82. package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
  83. package/dist/SocialProof/SocialProof.cjs +4 -3
  84. package/dist/SocialProof/SocialProof.d.cts +2 -2
  85. package/dist/SocialProof/SocialProof.d.ts +2 -2
  86. package/dist/SocialProof/SocialProof.js +4 -3
  87. package/dist/SocialProof/components/Headline.cjs +1 -1
  88. package/dist/SocialProof/components/Headline.js +1 -1
  89. package/dist/SocialProof/components/LayoutSingle.cjs +3 -4
  90. package/dist/SocialProof/components/LayoutSingle.js +3 -4
  91. package/dist/SocialProof/hooks/useSocialProofCount.cjs +3 -2
  92. package/dist/SocialProof/hooks/useSocialProofCount.d.cts +2 -0
  93. package/dist/SocialProof/hooks/useSocialProofCount.d.ts +2 -0
  94. package/dist/SocialProof/hooks/useSocialProofCount.js +3 -2
  95. package/dist/SocialProof/types/types.d.cts +5 -0
  96. package/dist/SocialProof/types/types.d.ts +5 -0
  97. package/dist/SparkleAnimation/SparkleAnimation.d.cts +2 -2
  98. package/dist/SparkleAnimation/SparkleAnimation.d.ts +2 -2
  99. package/dist/Stack/Stack.d.cts +2 -2
  100. package/dist/Stack/Stack.d.ts +2 -2
  101. package/dist/TextField/TextField.cjs +3 -0
  102. package/dist/TextField/TextField.js +3 -0
  103. package/dist/TextField/hooks/useVoiceInput.cjs +9 -1
  104. package/dist/TextField/hooks/useVoiceInput.js +9 -1
  105. package/dist/TextField/types/index.d.cts +12 -0
  106. package/dist/TextField/types/index.d.ts +12 -0
  107. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
  108. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
  109. package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
  110. package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
  111. package/dist/Typography/Typography.d.cts +4 -4
  112. package/dist/Typography/Typography.d.ts +4 -4
  113. package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
  114. package/dist/WidgetTextField/WidgetTextField.d.ts +2 -2
  115. package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
  116. package/dist/WidgetWrapper/WidgetWrapper.d.ts +2 -2
  117. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.cts +2 -2
  118. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.ts +2 -2
  119. package/dist/styles.css +1 -1
  120. package/package.json +1 -1
  121. package/src/components/ChatFooter/ChatFooter.tsx +1 -0
  122. package/src/components/ChatFooter/components/Layout.tsx +3 -1
  123. package/src/components/FloatingChat/components/SalesAgentBadgeContent.tsx +8 -0
  124. package/src/components/FloatingChat/components/SalesAgentProductCardsCarousel.tsx +3 -0
  125. package/src/components/FullPageSalesAgent/FullPageSalesAgent.tsx +139 -49
  126. package/src/components/FullPageSalesAgent/components/Layout.tsx +10 -3
  127. package/src/components/FullPageSalesAgent/components/WelcomeOverlay.tsx +121 -0
  128. package/src/components/FullPageSalesAgent/components/index.ts +2 -0
  129. package/src/components/FullPageSalesAgent/hooks/useGetFooterStyles.ts +15 -5
  130. package/src/components/FullPageSalesAgent/hooks/useWelcomeOverlayProducts.ts +51 -0
  131. package/src/components/PromptButton/hooks/useGetLayoutBaseProperties.ts +3 -1
  132. package/src/components/SalesAgentProductCard/components/Price.tsx +2 -2
  133. package/src/components/SalesAgentProductCard/components/Rate.tsx +1 -1
  134. package/src/components/SocialProof/SocialProof.tsx +4 -2
  135. package/src/components/SocialProof/components/Headline.tsx +1 -1
  136. package/src/components/SocialProof/components/LayoutSingle.tsx +6 -5
  137. package/src/components/SocialProof/hooks/useSocialProofCount.ts +8 -1
  138. package/src/components/SocialProof/types/types.ts +6 -0
  139. package/src/components/TextField/TextField.tsx +10 -2
  140. package/src/components/TextField/__tests__/VoiceInputButton.test.tsx +22 -58
  141. package/src/components/TextField/hooks/useVoiceInput.ts +8 -0
  142. package/src/components/TextField/types/index.ts +12 -0
@@ -3,14 +3,19 @@ import { listeningToSpeechAtom } from '@envive-ai/react-hooks/atoms/chat';
3
3
  import { EnviveMetricsEventName } from '@envive-ai/react-hooks/contexts/amplitudeContext';
4
4
  import { HardcopyResponse } from '@envive-ai/react-hooks/contexts/hardcopyContext';
5
5
  import { useSalesAgent } from '@envive-ai/react-hooks/contexts/salesAgentContext';
6
- import { FloatingChatConfig, LookAndFeelConfig } from '@envive-ai/react-hooks/contexts/typesV3';
6
+ import {
7
+ FloatingChatConfig,
8
+ FullPageSalesAgentWidgetV3Config,
9
+ LookAndFeelConfig,
10
+ } from '@envive-ai/react-hooks/contexts/typesV3';
7
11
  import { useWidgetInteraction } from '@envive-ai/react-hooks/hooks/WidgetInteraction';
8
12
  import {
9
13
  WidgetInteractionComponent,
10
14
  WidgetInteractionType,
11
15
  } from '@envive-ai/react-hooks/hooks/WidgetInteraction/types';
16
+ import { LayoutGroup, motion } from 'framer-motion';
12
17
  import { useSetAtom } from 'jotai';
13
- import { useCallback, useRef, useState } from 'react';
18
+ import { useCallback, useMemo, useRef, useState } from 'react';
14
19
  import { ChatFooter } from '../ChatFooter';
15
20
  import { Disclaimer } from '../Disclaimer';
16
21
  import { FloatingChatComponents } from '../FloatingChat/components';
@@ -28,20 +33,18 @@ import { PromptCarousel, PromptCarouselRows, usePromptCarouselAnalytics } from '
28
33
  import { SalesAgentProductCardProps } from '../SalesAgentProductCard/types/types';
29
34
  import { Theme } from '../Tokens';
30
35
  import { resolveTheme } from '../utils/resolveTheme';
31
- import { SparkleIconColor, WelcomeMessage } from '../WelcomeMessage';
32
36
  import { FullPageSAComponents } from './components';
37
+ import { WelcomeOverlay } from './components/WelcomeOverlay';
33
38
  import { useGetFooterStyles } from './hooks/useGetFooterStyles';
34
39
  import { useGetMessagesStyles } from './hooks/useGetMessagesStyles';
35
- import { useGetScrollContentStyles } from './hooks/useGetScrollContentStyles';
36
40
  import { useIsMobile } from './hooks/useIsMobile';
37
41
 
38
- interface FullPageSalesAgentProps {
42
+ export interface FullPageSalesAgentProps {
39
43
  theme?: Theme;
40
44
  floatingChatConfig: FloatingChatConfig;
41
45
  lookAndFeelConfig: LookAndFeelConfig;
46
+ widgetConfig: FullPageSalesAgentWidgetV3Config;
42
47
  hardcopyContent: HardcopyResponse;
43
- headerContainer?: string;
44
- autoHeight?: boolean;
45
48
  voiceInputEnabled?: boolean;
46
49
  }
47
50
 
@@ -49,18 +52,15 @@ export const FullPageSalesAgent = ({
49
52
  theme = Theme.GLOBAL_CUSTOM,
50
53
  floatingChatConfig,
51
54
  lookAndFeelConfig,
55
+ widgetConfig,
52
56
  hardcopyContent,
53
- headerContainer,
54
- autoHeight,
55
57
  voiceInputEnabled,
56
58
  }: FullPageSalesAgentProps) => {
57
59
  const resolvedTheme = resolveTheme(theme);
58
60
  const salesAgentData = useSalesAgent(WidgetInteractionComponent.FULL_PAGE_SALES_AGENT);
59
61
  const [query, setQuery] = useState('');
60
62
  const chatMessagesRef = useRef<HTMLDivElement>(null);
61
- const { footerStyles, footerClasses } = useGetFooterStyles();
62
63
  const { messageClasses } = useGetMessagesStyles();
63
- const { messageContainerClasses } = useGetScrollContentStyles();
64
64
  const { isMobile } = useIsMobile();
65
65
  const { trackWidgetInteraction } = useWidgetInteraction();
66
66
  const { onDrag, onHover, onMouseDown, onMouseUp, onTouchStart, onTouchEnd } =
@@ -68,23 +68,68 @@ export const FullPageSalesAgent = ({
68
68
  const setListeningToSpeech = useSetAtom(listeningToSpeechAtom);
69
69
 
70
70
  const {
71
- welcomeMessageIconColor,
72
71
  ignoreFirstModelResponse,
73
72
  neverShowSingleProductCards,
74
73
  showVerifiedBuyer,
75
74
  showEnviveLogo,
76
- userQueryInputEnabled = true,
77
75
  } = floatingChatConfig;
78
76
 
77
+ const {
78
+ headerContainer,
79
+ autoHeight,
80
+ sparkleColor = 'var(--envive-colors-accent-primary)',
81
+ suggestionButtonType = PromptButtonVariant.LIGHT,
82
+ useBackgroundImage,
83
+ mobileBackgroundImage,
84
+ desktopBackgroundImage,
85
+ backgroundColor,
86
+ } = widgetConfig;
87
+
88
+ const backgroundStyle = useMemo((): React.CSSProperties => {
89
+ if (useBackgroundImage) {
90
+ const imageUrl = isMobile
91
+ ? (mobileBackgroundImage ?? desktopBackgroundImage)
92
+ : (desktopBackgroundImage ?? mobileBackgroundImage);
93
+ if (imageUrl) {
94
+ return {
95
+ backgroundImage: `url(${imageUrl})`,
96
+ backgroundSize: 'cover',
97
+ backgroundPosition: 'center',
98
+ };
99
+ }
100
+ }
101
+ if (backgroundColor) {
102
+ return { backgroundColor };
103
+ }
104
+ return { backgroundColor: 'white' };
105
+ }, [
106
+ useBackgroundImage,
107
+ mobileBackgroundImage,
108
+ desktopBackgroundImage,
109
+ backgroundColor,
110
+ isMobile,
111
+ ]);
112
+
79
113
  const { agentName } = lookAndFeelConfig;
80
114
 
81
115
  const {
82
- welcomeMessageTitle,
83
- welcomeMessageText,
84
116
  chatFooterTextFieldPlaceholderText,
85
117
  disclaimerText,
118
+ welcomeOverlayTitle,
119
+ welcomeOverlayPromptSuggestions,
120
+ welcomeOverlayProductCarouselTitle,
121
+ welcomeOverlayProductIds,
86
122
  } = hardcopyContent?.values ?? {};
87
123
 
124
+ const overlayEnabled = typeof welcomeOverlayTitle === 'string' && !!welcomeOverlayTitle;
125
+
126
+ const [showOverlay, setShowOverlay] = useState(overlayEnabled);
127
+ const { footerStyles, footerClasses } = useGetFooterStyles({ showOverlay });
128
+ const overlayPromptSuggestions = Array.isArray(welcomeOverlayPromptSuggestions)
129
+ ? welcomeOverlayPromptSuggestions
130
+ : [];
131
+ const overlayProductIds = Array.isArray(welcomeOverlayProductIds) ? welcomeOverlayProductIds : [];
132
+
88
133
  const {
89
134
  messages,
90
135
  isResponseStreaming,
@@ -184,17 +229,36 @@ export const FullPageSalesAgent = ({
184
229
  [trackWidgetInteraction],
185
230
  );
186
231
 
187
- const welcomeMessage = (
188
- <div className={messageContainerClasses}>
189
- <WelcomeMessage
190
- sparkleIconColor={welcomeMessageIconColor as SparkleIconColor}
191
- title={welcomeMessageTitle as string}
192
- text={welcomeMessageText as string}
193
- theme={resolvedTheme}
194
- />
195
- </div>
232
+ const handleOverlayDismiss = useCallback(() => {
233
+ setShowOverlay(false);
234
+ }, []);
235
+
236
+ // TODO: Migrate to Widget and submit analytics for stringId tracking
237
+ const handleOverlaySuggestionClick = useCallback(
238
+ (buttonText: string) => {
239
+ onTypedMessageSubmitted({
240
+ query: buttonText,
241
+ userTyped: true,
242
+ displayLocation: ChatElementDisplayLocationV3.FLOATING_CHAT_TEXT_INPUT,
243
+ });
244
+ setListeningToSpeech('abort');
245
+ setAnswerSuggestions([]);
246
+ setGeneralSuggestions([]);
247
+ setShowOverlay(false);
248
+ },
249
+ [onTypedMessageSubmitted, setListeningToSpeech, setAnswerSuggestions, setGeneralSuggestions],
196
250
  );
197
251
 
252
+ const promptSuggestions = useMemo(() => {
253
+ if (showOverlay) {
254
+ return [];
255
+ }
256
+ if (isPendingResponse || isResponseStreaming) {
257
+ return ['Loading suggestions 1...', 'Loading suggestions 2...'];
258
+ }
259
+ return generalSuggestions;
260
+ }, [showOverlay, isPendingResponse, isResponseStreaming, generalSuggestions]);
261
+
198
262
  const footer = (
199
263
  <ChatFooter
200
264
  className={footerClasses}
@@ -202,17 +266,12 @@ export const FullPageSalesAgent = ({
202
266
  theme={resolvedTheme}
203
267
  isScrolled={isMobile ? isFloatingLayout : false}
204
268
  disabled={isPendingResponse || isResponseStreaming}
205
- disabledInput={!userQueryInputEnabled}
206
269
  isLoadingPromptSuggestions={
207
270
  isPendingResponse || isResponseStreaming || generalSuggestions.length === 0
208
271
  }
209
- hideEnviveWatermark={!showEnviveLogo}
272
+ hideEnviveWatermark={showOverlay || !showEnviveLogo}
210
273
  textFieldPlaceholderText={chatFooterTextFieldPlaceholderText as string}
211
- promptSuggestions={
212
- isPendingResponse || isResponseStreaming
213
- ? ['Loading suggestions 1...', 'Loading suggestions 2...']
214
- : generalSuggestions
215
- }
274
+ promptSuggestions={promptSuggestions}
216
275
  handleButtonClick={buttonText => {
217
276
  const suggestion = suggestions.find(s => s.content === buttonText && !s.isAnswer);
218
277
  if (suggestion) {
@@ -223,6 +282,7 @@ export const FullPageSalesAgent = ({
223
282
  setAnswerSuggestions([]);
224
283
  setGeneralSuggestions([]);
225
284
  handleBackToChat();
285
+ handleOverlayDismiss();
226
286
  }}
227
287
  onChange={setQuery}
228
288
  onSubmit={() => {
@@ -234,6 +294,7 @@ export const FullPageSalesAgent = ({
234
294
  setAnswerSuggestions([]);
235
295
  setGeneralSuggestions([]);
236
296
  handleBackToChat();
297
+ handleOverlayDismiss();
237
298
  }}
238
299
  onFocus={handleInputQueryFocus}
239
300
  parentWidget={WidgetInteractionComponent.FULL_PAGE_SALES_AGENT}
@@ -311,24 +372,53 @@ export const FullPageSalesAgent = ({
311
372
  <Disclaimer disclaimerMarkdown={disclaimerText} />
312
373
  ) : undefined;
313
374
 
375
+ const motionFooter = (
376
+ <motion.div
377
+ transition={{ layout: { duration: 0.2 }, default: { duration: 0 } }}
378
+ layoutId="fpsa-chat-footer"
379
+ >
380
+ {footer}
381
+ </motion.div>
382
+ );
383
+
314
384
  return (
315
- <FullPageSAComponents.Layout
316
- theme={resolvedTheme}
317
- welcomeMessage={isResultsView ? null : welcomeMessage}
318
- footer={footer}
319
- chatMessages={middleContent}
320
- answerSuggestions={
321
- showAnswerSuggestions && !isResultsView ? answerSuggestionsComponent : undefined
322
- }
323
- scrollToBottomButton={
324
- !isResultsView && showScrollButton ? (
325
- <FloatingChatComponents.ScrollToBottomButton onClick={handleScrollToBottom} />
326
- ) : undefined
327
- }
328
- disclaimer={isResultsView ? null : disclaimer}
329
- headerContainer={headerContainer}
330
- autoHeight={autoHeight}
331
- scrollContainerRef={scrollContainerRef}
332
- />
385
+ <LayoutGroup>
386
+ <FullPageSAComponents.Layout
387
+ theme={resolvedTheme}
388
+ welcomeMessage={null} // TODO: Remove this component entirely
389
+ footer={!overlayEnabled || !showOverlay ? motionFooter : null}
390
+ chatMessages={middleContent}
391
+ answerSuggestions={
392
+ showAnswerSuggestions && !isResultsView ? answerSuggestionsComponent : undefined
393
+ }
394
+ scrollToBottomButton={
395
+ !isResultsView && showScrollButton ? (
396
+ <FloatingChatComponents.ScrollToBottomButton onClick={handleScrollToBottom} />
397
+ ) : undefined
398
+ }
399
+ disclaimer={isResultsView ? null : disclaimer}
400
+ headerContainer={headerContainer}
401
+ autoHeight={autoHeight}
402
+ scrollContainerRef={scrollContainerRef}
403
+ backgroundStyle={backgroundStyle}
404
+ overlay={
405
+ overlayEnabled ? (
406
+ <WelcomeOverlay
407
+ show={showOverlay}
408
+ theme={resolvedTheme}
409
+ suggestionButtonType={suggestionButtonType}
410
+ sparkleColor={sparkleColor}
411
+ title={welcomeOverlayTitle as string}
412
+ promptSuggestions={overlayPromptSuggestions}
413
+ productCarouselTitle={welcomeOverlayProductCarouselTitle as string | undefined}
414
+ productIds={overlayProductIds}
415
+ backgroundStyle={backgroundStyle}
416
+ chatFooter={showOverlay ? motionFooter : null}
417
+ onSuggestionClick={handleOverlaySuggestionClick}
418
+ />
419
+ ) : null
420
+ }
421
+ />
422
+ </LayoutGroup>
333
423
  );
334
424
  };
@@ -19,6 +19,10 @@ export interface LayoutProps {
19
19
  autoHeight?: boolean;
20
20
  /** Scrollable content ref (e.g. scroll to top when opening product results grid) */
21
21
  scrollContainerRef?: React.RefObject<HTMLDivElement | null>;
22
+ /** Overlay rendered absolutely over all content (e.g. the welcome overlay). */
23
+ overlay?: React.ReactNode;
24
+ /** Background style applied to the container (background image or color). */
25
+ backgroundStyle?: React.CSSProperties;
22
26
  }
23
27
 
24
28
  export const Layout = ({
@@ -32,12 +36,14 @@ export const Layout = ({
32
36
  headerContainer,
33
37
  autoHeight,
34
38
  scrollContainerRef,
39
+ overlay,
40
+ backgroundStyle,
35
41
  }: LayoutProps) => {
36
42
  const hasWelcomeMessage = isValidElement(welcomeMessage);
37
43
  const hasAnswerSuggestions = isValidElement(answerSuggestions);
38
44
  const { isMobile } = useIsMobile();
39
45
  const { contentClasses } = useGetScrollContentStyles();
40
- const { footerContainerClasses } = useGetFooterStyles();
46
+ const { footerContainerClasses } = useGetFooterStyles({ showOverlay: false });
41
47
  const { containerClasses, containerStyles } = useGetContainerStyles({
42
48
  headerContainer,
43
49
  autoHeight,
@@ -45,8 +51,8 @@ export const Layout = ({
45
51
 
46
52
  return (
47
53
  <div
48
- className={containerClasses}
49
- style={containerStyles}
54
+ className={classNames(containerClasses, 'envive-tw-relative')}
55
+ style={{ ...containerStyles, ...backgroundStyle }}
50
56
  >
51
57
  <div
52
58
  ref={scrollContainerRef}
@@ -83,6 +89,7 @@ export const Layout = ({
83
89
  )}
84
90
  {footer}
85
91
  </div>
92
+ {overlay}
86
93
  </div>
87
94
  );
88
95
  };
@@ -0,0 +1,121 @@
1
+ import { AnimatePresence, motion } from 'framer-motion';
2
+ import classNames from 'classnames';
3
+ import { Theme } from 'tokens/theme/theme';
4
+ import { Typography, TypographyVariant } from 'src/components/Typography';
5
+ import { SparkleAnimation } from 'src/components/SparkleAnimation';
6
+ import { PromptButtonVariant } from '../../PromptButton';
7
+ import { AnimationSpeed, PromptCarousel, PromptCarouselRows } from '../../PromptCarousel';
8
+ import { SalesAgentProductCardsCarousel } from '../../FloatingChat/components/SalesAgentProductCardsCarousel';
9
+ import { useWelcomeOverlayProducts } from '../hooks/useWelcomeOverlayProducts';
10
+
11
+ export interface WelcomeOverlayProps {
12
+ show: boolean;
13
+ theme: Theme;
14
+ title: string;
15
+ promptSuggestions: string[];
16
+ productCarouselTitle?: string;
17
+ productIds: string[];
18
+ suggestionButtonType: PromptButtonVariant;
19
+ sparkleColor: string;
20
+ /** Background style for the overlay surface (image or color). Defaults to white. */
21
+ backgroundStyle?: React.CSSProperties;
22
+ /** The ChatFooter node wrapped in motion.div layoutId — shared-element anchor for the dismiss animation. */
23
+ chatFooter: React.ReactNode;
24
+ onSuggestionClick: (text: string) => void;
25
+ }
26
+
27
+ export const WelcomeOverlay = ({
28
+ show,
29
+ theme,
30
+ title,
31
+ promptSuggestions,
32
+ productCarouselTitle,
33
+ productIds,
34
+ chatFooter,
35
+ suggestionButtonType,
36
+ sparkleColor,
37
+ backgroundStyle = { backgroundColor: 'white' },
38
+ onSuggestionClick,
39
+ }: WelcomeOverlayProps) => {
40
+ const { products, isLoading } = useWelcomeOverlayProducts(productIds);
41
+ const showProductCarousel = !isLoading && products.length > 0 && !!productCarouselTitle;
42
+
43
+ const welcomeOverlayClassNames = classNames([
44
+ 'envive-tw-inset-0',
45
+ 'envive-tw-absolute',
46
+ 'envive-tw-z-20',
47
+ 'envive-tw-flex',
48
+ 'envive-tw-w-full',
49
+ 'envive-tw-h-full',
50
+ 'envive-tw-max-w-[768px]',
51
+ 'envive-tw-flex-col',
52
+ ]);
53
+
54
+ const titleClassNames = classNames([
55
+ 'envive-tw-flex',
56
+ 'envive-tw-flex-row',
57
+ 'envive-tw-gap-2',
58
+ 'envive-tw-items-center',
59
+ ]);
60
+
61
+ const titleBoxClassNames = classNames([
62
+ 'envive-tw-flex',
63
+ 'envive-tw-py-4',
64
+ 'envive-tw-px-[14px]',
65
+ 'envive-tw-flex-col',
66
+ 'envive-tw-gap-4',
67
+ 'envive-tw-bg-background-light',
68
+ 'envive-tw-border-border-light',
69
+ 'envive-tw-border-solid',
70
+ 'envive-tw-border',
71
+ 'envive-tw-rounded-global-custom',
72
+ ]);
73
+ return (
74
+ <AnimatePresence>
75
+ {show && (
76
+ <motion.div
77
+ key="welcome-overlay"
78
+ className={welcomeOverlayClassNames}
79
+ style={backgroundStyle}
80
+ initial={{ opacity: 1 }}
81
+ exit={{ opacity: 0, transition: { duration: 0.5, ease: 'easeIn' } }}
82
+ >
83
+ <div className="envive-tw-flex envive-tw-flex-1 envive-tw-flex-col envive-tw-gap-6 envive-tw-overflow-y-auto envive-tw-overflow-x-hidden envive-tw-px-4 envive-tw-pb-4 envive-tw-pt-6 lg:envive-tw-gap-10">
84
+ <div className={titleBoxClassNames}>
85
+ <div className={titleClassNames}>
86
+ <Typography variant={TypographyVariant.T3_MD}>{title}</Typography>
87
+ <SparkleAnimation
88
+ color={sparkleColor}
89
+ animate={false}
90
+ />
91
+ </div>
92
+ {chatFooter}
93
+ {promptSuggestions.length > 0 && (
94
+ <PromptCarousel
95
+ theme={theme}
96
+ promptButtonTexts={promptSuggestions}
97
+ promptButtonType={suggestionButtonType}
98
+ promptCarouselRows={PromptCarouselRows.ALWAYS_TWO}
99
+ animationSpeed={AnimationSpeed.NONE}
100
+ handleButtonClick={onSuggestionClick}
101
+ />
102
+ )}
103
+ </div>
104
+
105
+ {showProductCarousel && (
106
+ <div className="envive-tw-flex envive-tw-flex-col envive-tw-gap-3">
107
+ <Typography variant={TypographyVariant.B2_MD}>{productCarouselTitle}</Typography>
108
+ <SalesAgentProductCardsCarousel
109
+ theme={theme}
110
+ hideBadgeCount
111
+ products={products}
112
+ hideNavigation={false}
113
+ />
114
+ </div>
115
+ )}
116
+ </div>
117
+ </motion.div>
118
+ )}
119
+ </AnimatePresence>
120
+ );
121
+ };
@@ -3,3 +3,5 @@ import { Layout } from './Layout';
3
3
  export const FullPageSAComponents = {
4
4
  Layout,
5
5
  };
6
+
7
+ export { WelcomeOverlay } from './WelcomeOverlay';
@@ -1,10 +1,20 @@
1
1
  import { useIsMobile } from './useIsMobile';
2
2
 
3
- export const useGetFooterStyles = () => {
3
+ const getStyles = ({ isMobile, showOverlay }: { isMobile: boolean; showOverlay: boolean }) => {
4
+ if (showOverlay) {
5
+ return {
6
+ padding: 0,
7
+ borderTopWidth: '0px',
8
+ };
9
+ }
10
+ return isMobile
11
+ ? undefined
12
+ : { boxShadow: '0px 2px 10px 0px rgba(0, 0, 0, 0.2)', borderWidth: '1px' };
13
+ };
14
+
15
+ export const useGetFooterStyles = ({ showOverlay }: { showOverlay: boolean }) => {
4
16
  const { isMobile } = useIsMobile();
5
- const footerStyles = !isMobile
6
- ? { boxShadow: '0px 2px 10px 0px rgba(0, 0, 0, 0.2)', borderWidth: '1px' }
7
- : null;
17
+ const footerStyles = getStyles({ isMobile, showOverlay });
8
18
 
9
19
  const footerClasses = isMobile
10
20
  ? 'envive-tw-rounded-t-[var(--envive-global-custom-border-radius)]'
@@ -12,7 +22,7 @@ export const useGetFooterStyles = () => {
12
22
 
13
23
  const footerContainerClasses = isMobile
14
24
  ? 'envive-tw-z-10 envive-tw-absolute envive-tw-bottom-[0] envive-tw-left-[0] envive-tw-right-[0]'
15
- : 'envive-tw-z-10 envive-tw-absolute envive-tw-bottom-5 envive-tw-left-[calc((100vw-768px)/2)] envive-tw-right-[calc((100vw-768px)/2)] envive-tw-max-w-[768px]';
25
+ : 'envive-tw-z-10 envive-tw-absolute envive-tw-bottom-5 envive-tw-w-full envive-tw-left-0 envive-tw-right-0';
16
26
 
17
27
  return {
18
28
  footerStyles,
@@ -0,0 +1,51 @@
1
+ import CommerceApiClient from '@envive-ai/react-hooks/application/commerce-api';
2
+ import { useEffect, useState } from 'react';
3
+ import { SalesAgentProductCardProps } from '../../SalesAgentProductCard/types/types';
4
+
5
+ type RetrievedProduct = Awaited<
6
+ ReturnType<typeof CommerceApiClient.retrieveProducts>
7
+ >['products'][0];
8
+
9
+ const mapToCardProps = (p: RetrievedProduct): SalesAgentProductCardProps => ({
10
+ productName: p.title ?? '',
11
+ currentPrice: p.salePrice ?? 0,
12
+ previousPrice: p.originalPrice === p.salePrice ? undefined : p.originalPrice,
13
+ pricePrefix: '$',
14
+ rate: p.averageRating ?? 0,
15
+ numberOfReviews: p.numberReviews ?? 0,
16
+ image: { src: p.imageUrl ?? '', alt: `${p.title ?? ''} image` },
17
+ url: p.url,
18
+ });
19
+
20
+ export const useWelcomeOverlayProducts = (productIds: string[]) => {
21
+ const [products, setProducts] = useState<SalesAgentProductCardProps[]>([]);
22
+ const [isLoading, setIsLoading] = useState(productIds.length > 0);
23
+
24
+ useEffect(() => {
25
+ let cancelled = false;
26
+ if (productIds.length === 0) return () => {};
27
+
28
+ setIsLoading(true);
29
+
30
+ CommerceApiClient.retrieveProducts(productIds)
31
+ .then(result => {
32
+ if (!cancelled) {
33
+ setProducts(result.products.map(mapToCardProps));
34
+ }
35
+ })
36
+ .catch(() => {
37
+ // silently ignore — overlay just won't show products
38
+ })
39
+ .finally(() => {
40
+ if (!cancelled) setIsLoading(false);
41
+ });
42
+
43
+ return () => {
44
+ cancelled = true;
45
+ };
46
+ // productIds is normalized from hardcopy on mount, no need to re-fetch
47
+ // eslint-disable-next-line react-hooks/exhaustive-deps
48
+ }, []);
49
+
50
+ return { products, isLoading };
51
+ };
@@ -14,10 +14,12 @@ export const useGetLayoutBaseProperties = ({
14
14
  isDisabledClasses: string;
15
15
  } => {
16
16
  const baseClasses = classNames(
17
- 'envive-tw-h-fit envive-tw-w-fit',
17
+ 'envive-tw-h-fit',
18
18
  'envive-tw-rounded-global-custom',
19
19
  'envive-tw-px-4 envive-tw-py-2',
20
20
  'envive-tw-group',
21
+ 'envive-tw-w-[stretch]',
22
+ 'envive-tw-max-w-min',
21
23
  );
22
24
 
23
25
  const isLoadingClasses = classNames(
@@ -24,7 +24,7 @@ export const Price = ({
24
24
  {(showSalePrice || forceShowCurrentPriceSpace) && (
25
25
  <Typography
26
26
  variant={TypographyVariant.B3_RG}
27
- color={TypographyColor.TEXT_SECONDARY}
27
+ color={TypographyColor.TEXT_PRIMARY}
28
28
  >
29
29
  {forceShowCurrentPriceSpace && !showSalePrice ? 'ㅤ' : formattedCurrentPrice}
30
30
  </Typography>
@@ -32,7 +32,7 @@ export const Price = ({
32
32
 
33
33
  <Typography
34
34
  variant={TypographyVariant.B3_RG}
35
- color={TypographyColor.TEXT_SECONDARY}
35
+ color={TypographyColor.TEXT_PRIMARY}
36
36
  className={showSalePrice ? 'envive-tw-line-through' : ''}
37
37
  >
38
38
  {formattedPreviousPrice}
@@ -30,7 +30,7 @@ export const Rate = ({ rate, numberOfReviews, theme = Theme.GLOBAL_CUSTOM }: Rat
30
30
  />
31
31
  <Typography
32
32
  variant={TypographyVariant.B3_RG}
33
- color={TypographyColor.TEXT_SECONDARY}
33
+ color={TypographyColor.TEXT_PRIMARY}
34
34
  >
35
35
  {formattedRateString}
36
36
  </Typography>
@@ -36,6 +36,7 @@ export const SocialProof = ({
36
36
  } = widgetStyleProps ?? {};
37
37
 
38
38
  const {
39
+ numberOfCustomersText,
39
40
  customerQueryText,
40
41
  primaryButtonText,
41
42
  secondaryButtonTitleText,
@@ -47,9 +48,10 @@ export const SocialProof = ({
47
48
  voiceInputEnabled,
48
49
  } = widgetContentProps ?? {};
49
50
 
50
- const numberOfCustomersText = useSocialProofCount({
51
+ const socialProofCounter = useSocialProofCount({
51
52
  pageVariant,
52
53
  countKey,
54
+ numberOfCustomersText,
53
55
  id,
54
56
  });
55
57
 
@@ -91,7 +93,7 @@ export const SocialProof = ({
91
93
  const headline = (
92
94
  <SocialProofComponents.Headline
93
95
  theme={finalTheme}
94
- socialProofCounter={numberOfCustomersText}
96
+ socialProofCounter={socialProofCounter}
95
97
  socialProofHeadline={customerQueryText}
96
98
  primaryPromptButtonText={primaryButtonText}
97
99
  primaryPromptButtonVariant={primaryButtonVariant}
@@ -48,7 +48,7 @@ export const Headline = forwardRef<HTMLDivElement, HeadlineProps>(
48
48
  <Stack
49
49
  ref={ref}
50
50
  gap="4"
51
- className={classNames('envive-tw-h-fit', className)}
51
+ className={classNames('envive-tw-h-fit envive-tw-overflow-x-auto', className)}
52
52
  >
53
53
  <Stack gap="2">
54
54
  <Container>
@@ -1,12 +1,11 @@
1
1
  import { cloneElement } from 'react';
2
- import { useCheckIsMobile } from '../../utils/useCheckIsMobile';
3
2
  import { ImageGalleryImage, ImageGalleryLayout } from '../../ImageGallery/types/types';
4
- import { useGetContentSize } from '../../utils/useGetContentSize';
5
- import { ImageGallery } from './ImageGallery';
6
3
  import { Stack } from '../../Stack';
4
+ import { useCheckIsMobile } from '../../utils/useCheckIsMobile';
5
+ import { useGetContentSize } from '../../utils/useGetContentSize';
7
6
  import { DynamicLayout } from '../types/types';
8
- import { calculateWidthImageGallery } from '../utils/functions';
9
7
  import { HeadlineProps } from './Headline';
8
+ import { ImageGallery } from './ImageGallery';
10
9
 
11
10
  export type LayoutSingleProps = {
12
11
  dynamicLayout: DynamicLayout;
@@ -35,7 +34,9 @@ export const LayoutSingle = ({
35
34
  const { height, ref } = useGetContentSize({
36
35
  heightDefault: 245,
37
36
  });
38
- const imageGalleryWidth = calculateWidthImageGallery(height, 1, 1);
37
+
38
+ // For single image, image width must be fixed 80px.
39
+ const imageGalleryWidth = 80;
39
40
 
40
41
  return (
41
42
  <Stack