@envive-ai/react-toolkit-v3 0.3.17 → 0.3.19

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 (189) hide show
  1. package/dist/AnimatedText/AnimatedText.d.cts +3 -3
  2. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.cts +2 -2
  3. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.ts +2 -2
  4. package/dist/Carousel/Carousel.cjs +7 -6
  5. package/dist/Carousel/Carousel.d.cts +3 -2
  6. package/dist/Carousel/Carousel.d.ts +3 -2
  7. package/dist/Carousel/Carousel.js +7 -6
  8. package/dist/Carousel/components/Container.cjs +2 -2
  9. package/dist/Carousel/components/Container.js +2 -2
  10. package/dist/Carousel/types/types.d.cts +5 -0
  11. package/dist/Carousel/types/types.d.ts +5 -0
  12. package/dist/ChatFooter/ChatFooter.cjs +1 -1
  13. package/dist/ChatFooter/ChatFooter.d.cts +2 -2
  14. package/dist/ChatFooter/ChatFooter.d.ts +2 -2
  15. package/dist/ChatFooter/ChatFooter.js +1 -1
  16. package/dist/ChatFooter/components/Layout.cjs +2 -2
  17. package/dist/ChatFooter/components/Layout.js +2 -2
  18. package/dist/ChatFooter/components/index.d.cts +5 -5
  19. package/dist/ChatFooter/components/index.d.ts +5 -5
  20. package/dist/ChatHeader/ChatHeader.d.cts +2 -2
  21. package/dist/ChatHeader/ChatHeader.d.ts +2 -2
  22. package/dist/ChatHeader/components/Handle.cjs +2 -2
  23. package/dist/ChatHeader/components/Handle.js +2 -2
  24. package/dist/ChatHeader/components/Toggle.cjs +3 -3
  25. package/dist/ChatHeader/components/Toggle.js +3 -3
  26. package/dist/ChatPreview/ChatPreview.cjs +1 -1
  27. package/dist/ChatPreview/ChatPreview.d.cts +2 -2
  28. package/dist/ChatPreview/ChatPreview.d.ts +2 -2
  29. package/dist/ChatPreview/ChatPreview.js +1 -1
  30. package/dist/ChatPreviewComparison/ChatPreviewComparison.cjs +1 -1
  31. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
  32. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.ts +2 -2
  33. package/dist/ChatPreviewComparison/ChatPreviewComparison.js +1 -1
  34. package/dist/ChatPreviewComparison/components/Headline.cjs +2 -2
  35. package/dist/ChatPreviewComparison/components/Headline.js +2 -2
  36. package/dist/ChatPreviewComparison/components/Layout.cjs +4 -4
  37. package/dist/ChatPreviewComparison/components/Layout.js +4 -4
  38. package/dist/ChatPreviewComparison/components/Message.cjs +2 -2
  39. package/dist/ChatPreviewComparison/components/Message.js +2 -2
  40. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
  41. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
  42. package/dist/Container/Container.d.cts +14 -14
  43. package/dist/Container/Container.d.ts +177 -177
  44. package/dist/DesignTokens/DesignTokensComponent.d.cts +2 -2
  45. package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
  46. package/dist/Disclaimer/components/Container.cjs +2 -2
  47. package/dist/Disclaimer/components/Container.js +2 -2
  48. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
  49. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.ts +2 -2
  50. package/dist/DocumentRetrievalCard/components/Layout.cjs +2 -2
  51. package/dist/DocumentRetrievalCard/components/Layout.js +2 -2
  52. package/dist/DocumentRetrievalCard/components/ViewArticleButton/components/Icon.cjs +1 -1
  53. package/dist/DocumentRetrievalCard/components/ViewArticleButton/components/Icon.js +1 -1
  54. package/dist/FloatingButton/FloatingButton.d.cts +2 -2
  55. package/dist/FloatingButton/FloatingButton.d.ts +2 -2
  56. package/dist/FloatingChat/FloatingChat.cjs +61 -16
  57. package/dist/FloatingChat/FloatingChat.d.cts +2 -2
  58. package/dist/FloatingChat/FloatingChat.d.ts +2 -2
  59. package/dist/FloatingChat/FloatingChat.js +63 -18
  60. package/dist/FloatingChat/components/AgentMessage.cjs +8 -3
  61. package/dist/FloatingChat/components/AgentMessage.js +8 -3
  62. package/dist/FloatingChat/components/ChatMessages.cjs +5 -4
  63. package/dist/FloatingChat/components/ChatMessages.js +5 -4
  64. package/dist/FloatingChat/components/Layout.cjs +5 -4
  65. package/dist/FloatingChat/components/Layout.js +5 -4
  66. package/dist/FloatingChat/components/ResultsGridView.cjs +72 -0
  67. package/dist/FloatingChat/components/ResultsGridView.js +71 -0
  68. package/dist/FloatingChat/components/SalesAgentBadgeContent.cjs +59 -0
  69. package/dist/FloatingChat/components/SalesAgentBadgeContent.js +56 -0
  70. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.cjs +14 -5
  71. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.js +14 -5
  72. package/dist/FloatingChat/components/SlideChatContent.cjs +46 -0
  73. package/dist/FloatingChat/components/SlideChatContent.js +45 -0
  74. package/dist/FloatingChat/components/index.cjs +4 -0
  75. package/dist/FloatingChat/components/index.js +4 -0
  76. package/dist/FloatingChat/hooks/useProductResultsView.cjs +36 -0
  77. package/dist/FloatingChat/hooks/useProductResultsView.js +35 -0
  78. package/dist/FloatingChat/utils/functions.cjs +28 -1
  79. package/dist/FloatingChat/utils/functions.js +25 -1
  80. package/dist/FullPageSalesAgent/FullPageSalesAgent.cjs +12 -7
  81. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.cts +2 -2
  82. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.ts +2 -2
  83. package/dist/FullPageSalesAgent/FullPageSalesAgent.js +12 -7
  84. package/dist/FullPageSalesAgent/components/Layout.cjs +1 -2
  85. package/dist/FullPageSalesAgent/components/Layout.js +1 -2
  86. package/dist/FullPageSalesAgent/hooks/useContainerResizerObserver.cjs +4 -1
  87. package/dist/FullPageSalesAgent/hooks/useContainerResizerObserver.js +4 -1
  88. package/dist/Image/Image.d.cts +2 -2
  89. package/dist/Image/Image.d.ts +2 -2
  90. package/dist/ImageGallery/ImageGallery.d.cts +2 -2
  91. package/dist/ImageGallery/ImageGallery.d.ts +2 -2
  92. package/dist/ImageGallery/components/Layout.cjs +1 -1
  93. package/dist/ImageGallery/components/Layout.js +1 -1
  94. package/dist/MarkdownProcessor/MarkdownProcessor.d.cts +2 -2
  95. package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
  96. package/dist/Message/components/LinkButton.cjs +1 -1
  97. package/dist/Message/components/LinkButton.js +1 -1
  98. package/dist/OrderLookupCard/OrderLookupCard.cjs +1 -1
  99. package/dist/OrderLookupCard/OrderLookupCard.js +1 -1
  100. package/dist/ProductCard/ProductCard.cjs +2 -2
  101. package/dist/ProductCard/ProductCard.d.cts +2 -2
  102. package/dist/ProductCard/ProductCard.d.ts +2 -2
  103. package/dist/ProductCard/ProductCard.js +2 -2
  104. package/dist/PromptButton/PromptButton.d.cts +2 -2
  105. package/dist/PromptButton/PromptButton.d.ts +2 -2
  106. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
  107. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
  108. package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.cjs +1 -1
  109. package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.js +1 -1
  110. package/dist/PromptCarousel/PromptCarousel.cjs +3 -3
  111. package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
  112. package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
  113. package/dist/PromptCarousel/PromptCarousel.js +3 -3
  114. package/dist/ReviewCard/ReviewCard.d.cts +2 -2
  115. package/dist/ReviewCard/ReviewCard.d.ts +2 -2
  116. package/dist/ReviewCard/components/Container.cjs +2 -2
  117. package/dist/ReviewCard/components/Container.js +2 -2
  118. package/dist/ReviewCard/components/ReadMoreButton.cjs +1 -1
  119. package/dist/ReviewCard/components/ReadMoreButton.js +1 -1
  120. package/dist/ReviewCard/components/index.d.cts +6 -6
  121. package/dist/ReviewCard/components/index.d.ts +6 -6
  122. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.cts +2 -2
  123. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.ts +2 -2
  124. package/dist/SalesAgentProductCard/components/Container.cjs +2 -2
  125. package/dist/SalesAgentProductCard/components/Container.js +2 -2
  126. package/dist/SalesAgentProductCard/components/index.d.cts +8 -8
  127. package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
  128. package/dist/SocialProof/SocialProof.cjs +1 -1
  129. package/dist/SocialProof/SocialProof.d.ts +2 -2
  130. package/dist/SocialProof/SocialProof.js +1 -1
  131. package/dist/SocialProof/components/Headline.cjs +3 -3
  132. package/dist/SocialProof/components/Headline.js +3 -3
  133. package/dist/SocialProof/components/LayoutFourHorizontal.cjs +1 -1
  134. package/dist/SocialProof/components/LayoutFourHorizontal.js +1 -1
  135. package/dist/SocialProof/components/Subheadline.cjs +1 -1
  136. package/dist/SocialProof/components/Subheadline.js +1 -1
  137. package/dist/SparkleAnimation/SparkleAnimation.d.cts +2 -2
  138. package/dist/SparkleAnimation/SparkleAnimation.d.ts +2 -2
  139. package/dist/Stack/Stack.d.cts +2 -2
  140. package/dist/Stack/Stack.d.ts +2 -2
  141. package/dist/TitledPromptCarousel/TitledPromptCarousel.cjs +1 -1
  142. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
  143. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
  144. package/dist/TitledPromptCarousel/TitledPromptCarousel.js +1 -1
  145. package/dist/Tokens/index.cjs +1 -1
  146. package/dist/Tokens/index.js +1 -1
  147. package/dist/TypingAnimation/TypingAnimation.cjs +1 -1
  148. package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
  149. package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
  150. package/dist/TypingAnimation/TypingAnimation.js +1 -1
  151. package/dist/TypingAnimation/hooks/useGetTypographyVariant.cjs +1 -1
  152. package/dist/TypingAnimation/hooks/useGetTypographyVariant.js +1 -1
  153. package/dist/Typography/Typography.d.cts +4 -4
  154. package/dist/Typography/Typography.d.ts +4 -4
  155. package/dist/WelcomeMessage/components/Container.cjs +2 -2
  156. package/dist/WelcomeMessage/components/Container.js +2 -2
  157. package/dist/WidgetTextField/WidgetTextField.cjs +1 -1
  158. package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
  159. package/dist/WidgetTextField/WidgetTextField.js +1 -1
  160. package/dist/WidgetTextField/components/Container.cjs +2 -2
  161. package/dist/WidgetTextField/components/Container.js +2 -2
  162. package/dist/WidgetTextField/components/Icon.cjs +1 -1
  163. package/dist/WidgetTextField/components/Icon.js +1 -1
  164. package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
  165. package/dist/WidgetWrapper/WidgetWrapper.d.ts +2 -2
  166. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.cts +2 -2
  167. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.ts +2 -2
  168. package/dist/styles.css +1 -1
  169. package/package.json +1 -1
  170. package/src/components/Carousel/Carousel.tsx +11 -8
  171. package/src/components/Carousel/types/types.ts +5 -0
  172. package/src/components/FloatingChat/FloatingChat.tsx +91 -19
  173. package/src/components/FloatingChat/components/AgentMessage.tsx +8 -0
  174. package/src/components/FloatingChat/components/ChatMessages.tsx +3 -0
  175. package/src/components/FloatingChat/components/Layout.tsx +7 -1
  176. package/src/components/FloatingChat/components/ModalSheet.tsx +1 -1
  177. package/src/components/FloatingChat/components/ResultsGridView.tsx +93 -0
  178. package/src/components/FloatingChat/components/SalesAgentBadgeContent.tsx +86 -0
  179. package/src/components/FloatingChat/components/SalesAgentProductCardsCarousel.tsx +18 -7
  180. package/src/components/FloatingChat/components/SlideChatContent.tsx +72 -0
  181. package/src/components/FloatingChat/components/index.ts +4 -1
  182. package/src/components/FloatingChat/hooks/useFilteredChatMessages.ts +1 -1
  183. package/src/components/FloatingChat/hooks/useProductResultsView.ts +49 -0
  184. package/src/components/FloatingChat/utils/functions.ts +41 -0
  185. package/src/components/FullPageSalesAgent/FullPageSalesAgent.tsx +10 -6
  186. package/src/components/FullPageSalesAgent/components/Layout.tsx +1 -2
  187. package/src/components/FullPageSalesAgent/hooks/useContainerResizerObserver.ts +1 -0
  188. package/src/logging/logger.ts +33 -8
  189. package/src/mocks/productCardMocks.ts +955 -0
@@ -1,6 +1,9 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
 
3
- import { ChatElementDisplayLocationV3 } from '@envive-ai/react-hooks/application/models';
3
+ import {
4
+ ChatElementDisplayLocationV3,
5
+ MessageType,
6
+ } from '@envive-ai/react-hooks/application/models';
4
7
  import { motion } from 'framer-motion';
5
8
  import {
6
9
  WidgetInteractionComponent,
@@ -18,8 +21,8 @@ import { PromptButtonVariant } from '../PromptButton';
18
21
  import { PromptCarousel, PromptCarouselRows, usePromptCarouselAnalytics } from '../PromptCarousel';
19
22
  import { WelcomeMessage } from '../WelcomeMessage/WelcomeMessage';
20
23
  import { SparkleIconColor } from '../WelcomeMessage/types/types';
21
- import { FloatingChatComponents, ModalSheet } from './components';
22
24
  import { useChatSuggestions } from './hooks/useChatSuggestions';
25
+ import { useProductResultsView } from './hooks/useProductResultsView';
23
26
  import { useScrollToBottom } from './hooks/useScrollToBottom';
24
27
  import { useSnapSetup } from './hooks/useSnapSetup';
25
28
 
@@ -28,6 +31,9 @@ import { resolveTheme } from '../utils/resolveTheme';
28
31
  import { useFilteredChatMessages } from './hooks/useFilteredChatMessages';
29
32
  import { Unit } from './hooks/useSnapCalculator';
30
33
  import { FloatingChatProps } from './types/types';
34
+ import { getCleanProducts, getSearchQueryFromMessageBlock } from './utils/functions';
35
+ import { FloatingChatComponents, ModalSheet } from './components';
36
+ import { SalesAgentProductCardProps } from '../SalesAgentProductCard/types/types';
31
37
 
32
38
  export const FloatingChat = ({
33
39
  id,
@@ -76,6 +82,19 @@ export const FloatingChat = ({
76
82
  rightToggleLabel,
77
83
  } = hardcopyContent?.values ?? {};
78
84
 
85
+ const disclaimerTextString = useMemo(() => {
86
+ if (disclaimerText && typeof disclaimerText === 'string') {
87
+ return disclaimerText;
88
+ }
89
+ if (disclaimerText && Array.isArray(disclaimerText)) {
90
+ const textString = disclaimerText[0];
91
+ if (textString && typeof textString === 'string') {
92
+ return textString;
93
+ }
94
+ }
95
+ return undefined;
96
+ }, [disclaimerText]);
97
+
79
98
  const { agentName, chatHeaderLogoDarkSrc, chatHeaderLogoLightSrc } = lookAndFeelConfig;
80
99
 
81
100
  const {
@@ -137,6 +156,15 @@ export const FloatingChat = ({
137
156
  isOpen: isFloatingChatOpen,
138
157
  });
139
158
 
159
+ const {
160
+ resultsViewData,
161
+ setResultsViewData,
162
+ isResultsView,
163
+ scrollContainerRef,
164
+ isResultsViewRef,
165
+ handleBackToChat,
166
+ } = useProductResultsView({ scrollToBottom });
167
+
140
168
  useEffect(() => {
141
169
  if (isFloatingChatOpen) {
142
170
  trackWidgetInteraction({
@@ -178,6 +206,21 @@ export const FloatingChat = ({
178
206
  modalSheetControl,
179
207
  });
180
208
 
209
+ const handleProductCardClick = (product: SalesAgentProductCardProps) => {
210
+ trackWidgetInteraction({
211
+ eventName: EnviveMetricsEventName.WidgetInteraction,
212
+ trigger: {
213
+ widget: WidgetInteractionComponent.FLOATING_CHAT,
214
+ widget_interaction: WidgetInteractionType.PRODUCT_CARD_CLICKED,
215
+ widget_interaction_data: {
216
+ product_card_clicked: {
217
+ product_id: product.id,
218
+ },
219
+ },
220
+ },
221
+ });
222
+ };
223
+
181
224
  const header = (
182
225
  <ChatHeader
183
226
  logoDark={chatHeaderLogoDarkSrc}
@@ -250,6 +293,7 @@ export const FloatingChat = ({
250
293
  });
251
294
  setAnswerSuggestions([]);
252
295
  setGeneralSuggestions([]);
296
+ handleBackToChat();
253
297
  }}
254
298
  textFieldPlaceholderText={chatFooterTextFieldPlaceholderText as string}
255
299
  promptSuggestions={
@@ -265,6 +309,7 @@ export const FloatingChat = ({
265
309
 
266
310
  setAnswerSuggestions([]);
267
311
  setGeneralSuggestions([]);
312
+ handleBackToChat();
268
313
  }}
269
314
  disabled={isPendingResponse || isResponseStreaming}
270
315
  disabledInput={!userQueryInputEnabled}
@@ -286,6 +331,21 @@ export const FloatingChat = ({
286
331
  />
287
332
  );
288
333
 
334
+ const handleExploreAllResults = (firstProductMessageId: string) => {
335
+ const blockIndex = messages.findIndex(block =>
336
+ block.some(msg => msg.type === MessageType.Product && msg.id === firstProductMessageId),
337
+ );
338
+ if (blockIndex < 0) return;
339
+
340
+ const messageBlock = messages[blockIndex];
341
+ const productMessages = messageBlock.filter(msg => msg.type === MessageType.Product);
342
+ const products = getCleanProducts(productMessages);
343
+ if (products.length === 0) return;
344
+
345
+ const searchQuery = getSearchQueryFromMessageBlock(messages, blockIndex);
346
+ setResultsViewData({ products, searchQuery });
347
+ };
348
+
289
349
  const chatMessages = (
290
350
  <FloatingChatComponents.ChatMessages
291
351
  theme={finalTheme}
@@ -301,6 +361,20 @@ export const FloatingChat = ({
301
361
  showVerifiedBuyer={showVerifiedBuyer}
302
362
  onFormResponseSubmitted={onFormResponseSubmitted}
303
363
  parentWidget={WidgetInteractionComponent.FLOATING_CHAT}
364
+ onExploreAllResults={handleExploreAllResults}
365
+ />
366
+ );
367
+
368
+ const middleContent = (
369
+ <FloatingChatComponents.SlideChatContent
370
+ isResultsView={isResultsView}
371
+ isResultsViewRef={isResultsViewRef}
372
+ resultsViewData={resultsViewData}
373
+ onBackToChat={handleBackToChat}
374
+ onProductCardClick={handleProductCardClick}
375
+ theme={finalTheme}
376
+ chatMessages={chatMessages}
377
+ scrollToBottom={scrollToBottom}
304
378
  />
305
379
  );
306
380
 
@@ -337,20 +411,19 @@ export const FloatingChat = ({
337
411
  theme={finalTheme}
338
412
  header={header}
339
413
  footer={footer}
340
- welcomeMessage={welcomeMessage}
341
- chatMessages={chatMessages}
342
- answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : undefined}
414
+ welcomeMessage={isResultsView ? null : welcomeMessage}
415
+ chatMessages={middleContent}
416
+ answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : null}
343
417
  scrollToBottomButton={
344
- showScrollButton ? (
418
+ !isResultsView && showScrollButton ? (
345
419
  <FloatingChatComponents.ScrollToBottomButton onClick={handleScrollToBottom} />
346
- ) : undefined
420
+ ) : null
347
421
  }
348
422
  disclaimer={
349
- disclaimerText && typeof disclaimerText === 'string' ? (
350
- <Disclaimer disclaimerMarkdown={disclaimerText} />
351
- ) : undefined
423
+ disclaimerTextString ? <Disclaimer disclaimerMarkdown={disclaimerTextString} /> : null
352
424
  }
353
425
  debugBar={debugBar}
426
+ scrollContainerRef={scrollContainerRef}
354
427
  />
355
428
  );
356
429
 
@@ -363,20 +436,19 @@ export const FloatingChat = ({
363
436
  theme={finalTheme}
364
437
  header={mobileHeader}
365
438
  footer={footer}
366
- welcomeMessage={welcomeMessage}
367
- chatMessages={chatMessages}
368
- answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : undefined}
439
+ welcomeMessage={isResultsView ? null : welcomeMessage}
440
+ chatMessages={middleContent}
441
+ answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : null}
369
442
  scrollToBottomButton={
370
- showScrollButton ? (
443
+ !isResultsView && showScrollButton ? (
371
444
  <FloatingChatComponents.ScrollToBottomButton onClick={handleScrollToBottom} />
372
- ) : undefined
445
+ ) : null
373
446
  }
374
447
  disclaimer={
375
- disclaimerText && typeof disclaimerText === 'string' ? (
376
- <Disclaimer disclaimerMarkdown={disclaimerText} />
377
- ) : undefined
448
+ disclaimerTextString ? <Disclaimer disclaimerMarkdown={disclaimerTextString} /> : null
378
449
  }
379
450
  isFloatingFooterLayout={isFloatingLayout}
451
+ scrollContainerRef={scrollContainerRef}
380
452
  />
381
453
  );
382
454
 
@@ -42,6 +42,7 @@ export interface AgentMessageProps {
42
42
  showVerifiedBuyer?: boolean;
43
43
  onFormResponseSubmitted?: (formResponse: FormSubmittedAttributes) => void;
44
44
  parentWidget: WidgetInteractionComponent;
45
+ onExploreAllResults: (firstProductMessageId: string) => void;
45
46
  }
46
47
 
47
48
  export const AgentMessage = ({
@@ -56,6 +57,7 @@ export const AgentMessage = ({
56
57
  showVerifiedBuyer = false,
57
58
  onFormResponseSubmitted,
58
59
  parentWidget,
60
+ onExploreAllResults,
59
61
  }: AgentMessageProps) => {
60
62
  const handleFormSubmittedAtomFallback = useSetAtom(handleFormSubmittedAtom);
61
63
  const finalTheme = resolveTheme(theme);
@@ -167,6 +169,11 @@ export const AgentMessage = ({
167
169
  return null;
168
170
  }
169
171
 
172
+ const handleExploreAllResults = () => {
173
+ const firstProductMessageId = messages[0]?.id ?? '';
174
+ onExploreAllResults?.(firstProductMessageId);
175
+ };
176
+
170
177
  return (
171
178
  <SalesAgentProductCardsCarousel
172
179
  hideNavigation={products.length === 1}
@@ -174,6 +181,7 @@ export const AgentMessage = ({
174
181
  numberOfProducts={products.length}
175
182
  theme={finalTheme}
176
183
  onProductCardClick={handleProductCardClick}
184
+ onExploreAllResults={handleExploreAllResults}
177
185
  />
178
186
  );
179
187
  }
@@ -32,6 +32,7 @@ export interface ChatMessagesProps {
32
32
  showVerifiedBuyer?: boolean;
33
33
  onFormResponseSubmitted?: (formResponse: FormSubmittedAttributes) => void;
34
34
  parentWidget: WidgetInteractionComponent;
35
+ onExploreAllResults: (firstProductMessageId: string) => void;
35
36
  }
36
37
 
37
38
  export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
@@ -50,6 +51,7 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
50
51
  showVerifiedBuyer,
51
52
  onFormResponseSubmitted,
52
53
  parentWidget,
54
+ onExploreAllResults,
53
55
  },
54
56
  ref,
55
57
  ) => {
@@ -143,6 +145,7 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
143
145
  showVerifiedBuyer={showVerifiedBuyer}
144
146
  onFormResponseSubmitted={onFormResponseSubmitted}
145
147
  parentWidget={parentWidget}
148
+ onExploreAllResults={onExploreAllResults}
146
149
  />
147
150
  );
148
151
  }
@@ -20,6 +20,8 @@ type LayoutProps = {
20
20
  disclaimer?: React.ReactNode;
21
21
  isFloatingFooterLayout?: boolean;
22
22
  debugBar?: React.ReactNode;
23
+ /** Ref for the scrollable content container (used for programmatic scroll on view changes) */
24
+ scrollContainerRef?: React.RefObject<HTMLDivElement | null>;
23
25
  };
24
26
 
25
27
  export const Layout = ({
@@ -37,6 +39,7 @@ export const Layout = ({
37
39
  disclaimer,
38
40
  isFloatingFooterLayout = false,
39
41
  debugBar,
42
+ scrollContainerRef,
40
43
  }: LayoutProps) => {
41
44
  const finalTheme = resolveTheme(theme);
42
45
  const hasWelcomeMessage = isValidElement(welcomeMessage);
@@ -58,7 +61,10 @@ export const Layout = ({
58
61
  >
59
62
  {header && <div className="envive-tw-flex-shrink-0 envive-tw-leading-[0]">{header}</div>}
60
63
 
61
- <div className="envive-tw-flex-1 envive-tw-overflow-y-auto envive-tw-overflow-x-hidden envive-tw-transition-all envive-tw-duration-300 envive-tw-ease-in-out">
64
+ <div
65
+ ref={scrollContainerRef}
66
+ className="envive-tw-flex-1 envive-tw-overflow-y-auto envive-tw-overflow-x-hidden envive-tw-transition-all envive-tw-duration-300 envive-tw-ease-in-out"
67
+ >
62
68
  <Stack
63
69
  className="envive-tw-h-full"
64
70
  direction="column"
@@ -24,7 +24,7 @@ interface ModalSheetProps {
24
24
  animation?: ValueAnimationTransition;
25
25
  onClose: () => void;
26
26
  onSnap?: (snap: number) => void;
27
- controlRef?: React.MutableRefObject<ModalSheetControl | undefined>;
27
+ controlRef?: React.RefObject<ModalSheetControl | undefined>;
28
28
  disableDrag?: boolean;
29
29
  }
30
30
 
@@ -0,0 +1,93 @@
1
+ import { Theme } from '../../../../tokens/theme/theme';
2
+ import { SalesAgentProductCard } from '../../SalesAgentProductCard/SalesAgentProductCard';
3
+ import {
4
+ SalesAgentProductCardProps,
5
+ SalesAgentProductCardVariant,
6
+ } from '../../SalesAgentProductCard/types/types';
7
+ import { resolveTheme } from '../../utils/resolveTheme';
8
+ import { Stack } from '../../Stack';
9
+ import { Typography, TypographyColor, TypographyVariant } from '../../Typography';
10
+
11
+ export interface ResultsGridViewProps {
12
+ theme: Theme;
13
+ searchQuery: string;
14
+ products: SalesAgentProductCardProps[];
15
+ onBackToChat: () => void;
16
+ onProductCardClick?: (product: SalesAgentProductCardProps) => void;
17
+ }
18
+
19
+ export const ResultsGridView = ({
20
+ theme,
21
+ searchQuery,
22
+ products,
23
+ onBackToChat,
24
+ onProductCardClick,
25
+ }: ResultsGridViewProps) => {
26
+ const finalTheme = resolveTheme(theme);
27
+
28
+ const forceShowCurrentPriceSpace = products.some(
29
+ product => product.currentPrice && product.previousPrice !== product.currentPrice,
30
+ );
31
+
32
+ return (
33
+ <Stack
34
+ direction="column"
35
+ gap="4"
36
+ className="envive-tw-pb-4 envive-tw-pl-4 envive-tw-pr-4"
37
+ >
38
+ <div className="envive-tw-top-0 envive-tw-sticky envive-tw-z-10 envive-tw-mb-2 envive-tw-bg-background-light envive-tw-pb-2 envive-tw-pt-2">
39
+ <button
40
+ type="button"
41
+ onClick={onBackToChat}
42
+ className="envive-tw-flex envive-tw-items-center envive-tw-gap-1 envive-tw-rounded-global-custom envive-tw-border envive-tw-border-border-medium envive-tw-bg-background-light envive-tw-p-2 envive-tw-shadow-sm hover:envive-tw-border-border-dark"
43
+ aria-label="Back to Chat"
44
+ >
45
+ <span
46
+ className="envive-tw-text-[--envive-colors-text-primary]"
47
+ aria-hidden
48
+ >
49
+
50
+ </span>
51
+ <Typography
52
+ variant={TypographyVariant.B3_MD}
53
+ color={TypographyColor.TEXT_PRIMARY}
54
+ >
55
+ Back to Chat
56
+ </Typography>
57
+ </button>
58
+ </div>
59
+
60
+ {searchQuery && (
61
+ <Typography
62
+ variant={TypographyVariant.B3_MD}
63
+ color={TypographyColor.TEXT_PRIMARY}
64
+ className="envive-tw-mb-2"
65
+ >
66
+ Results for &quot;{searchQuery}&quot;
67
+ </Typography>
68
+ )}
69
+
70
+ {/* Product grid */}
71
+ <div className="envive-tw-grid envive-tw-grid-cols-2 envive-tw-gap-4">
72
+ {products.map((product, index) => (
73
+ <SalesAgentProductCard
74
+ key={product.id ?? `product-${index}`}
75
+ variant={SalesAgentProductCardVariant.LARGE}
76
+ productName={product.productName}
77
+ currentPrice={product.currentPrice}
78
+ previousPrice={product.previousPrice}
79
+ pricePrefix={product.pricePrefix ?? '$'}
80
+ forceShowCurrentPriceSpace={forceShowCurrentPriceSpace}
81
+ rate={product.rate}
82
+ numberOfReviews={product.numberOfReviews}
83
+ image={product.image}
84
+ url={product.url}
85
+ target="_blank"
86
+ theme={finalTheme}
87
+ onClick={() => onProductCardClick?.(product)}
88
+ />
89
+ ))}
90
+ </div>
91
+ </Stack>
92
+ );
93
+ };
@@ -0,0 +1,86 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import { ArrowIcon } from '../../Carousel/components/ArrowIcon';
4
+ import { Stack } from '../../Stack/Stack';
5
+ import { Typography, TypographyColor, TypographyVariant } from '../../Typography';
6
+ import { MOST_RELEVANT_DISPLAY_LIMIT } from '../utils/functions';
7
+
8
+ export interface ExploreAllResultsBadgeProps {
9
+ totalProducts: number;
10
+ displayLimit?: number;
11
+ onExploreAllResults: () => void;
12
+ }
13
+
14
+ export const SalesAgentBadgeContent = ({
15
+ totalProducts,
16
+ displayLimit = MOST_RELEVANT_DISPLAY_LIMIT,
17
+ onExploreAllResults,
18
+ }: ExploreAllResultsBadgeProps) => {
19
+ return (
20
+ <Stack
21
+ direction="row"
22
+ justify="between"
23
+ align="center"
24
+ gap="1"
25
+ className="envive-tw-w-full envive-tw-rounded-global-custom envive-tw-p-2"
26
+ >
27
+ <Typography
28
+ variant={TypographyVariant.B3_MD}
29
+ color={TypographyColor.TEXT_PRIMARY}
30
+ >
31
+ Most Relevant ({displayLimit})
32
+ </Typography>
33
+ <button
34
+ type="button"
35
+ onClick={onExploreAllResults}
36
+ className={classNames(
37
+ 'envive-tw-flex envive-tw-items-center envive-tw-gap-1',
38
+ 'envive-tw-bg-background-secondary envive-tw-p-2',
39
+ 'envive-tw-rounded-global-custom envive-tw-border envive-tw-border-border-medium',
40
+ 'hover:envive-tw-border-border-dark',
41
+ )}
42
+ aria-label={`Explore all ${totalProducts} results`}
43
+ >
44
+ <Typography
45
+ variant={TypographyVariant.B3_MD}
46
+ color={TypographyColor.TEXT_LINK}
47
+ >
48
+ Explore All Results ({totalProducts})
49
+ </Typography>
50
+ <ArrowIcon direction="right" />
51
+ </button>
52
+ </Stack>
53
+ );
54
+ };
55
+
56
+ export interface GetBadgeContentAndLabelParams {
57
+ totalProducts: number;
58
+ areProductsMoreThanDisplayLimit: boolean;
59
+ onExploreAllResults?: () => void;
60
+ displayLimit?: number;
61
+ }
62
+
63
+ export interface BadgeContentAndLabel {
64
+ badgeContent: React.ReactNode;
65
+ badgeLabel: string | null;
66
+ }
67
+
68
+ export const getBadgeContentAndLabel = ({
69
+ totalProducts,
70
+ areProductsMoreThanDisplayLimit,
71
+ onExploreAllResults,
72
+ displayLimit = MOST_RELEVANT_DISPLAY_LIMIT,
73
+ }: GetBadgeContentAndLabelParams): BadgeContentAndLabel => {
74
+ const badgeContent = areProductsMoreThanDisplayLimit ? (
75
+ <SalesAgentBadgeContent
76
+ totalProducts={totalProducts}
77
+ displayLimit={displayLimit}
78
+ onExploreAllResults={onExploreAllResults}
79
+ />
80
+ ) : null;
81
+
82
+ const badgeLabel =
83
+ !areProductsMoreThanDisplayLimit && totalProducts > 1 ? `${totalProducts} Products` : null;
84
+
85
+ return { badgeContent, badgeLabel };
86
+ };
@@ -6,6 +6,8 @@ import {
6
6
  SalesAgentProductCardVariant,
7
7
  } from '../../SalesAgentProductCard/types/types';
8
8
  import { resolveTheme } from '../../utils/resolveTheme';
9
+ import { getProductCarouselDisplayInfo } from '../utils/functions';
10
+ import { getBadgeContentAndLabel } from './SalesAgentBadgeContent';
9
11
 
10
12
  export interface SalesAgentProductCardsCarouselProps {
11
13
  theme: Theme;
@@ -14,6 +16,8 @@ export interface SalesAgentProductCardsCarouselProps {
14
16
  products: SalesAgentProductCardProps[];
15
17
  variant?: SalesAgentProductCardVariant;
16
18
  onProductCardClick?: (product: SalesAgentProductCardProps) => void;
19
+ /** Called when user clicks "Explore All Results" (only relevant when products.length > 8) */
20
+ onExploreAllResults?: () => void;
17
21
  }
18
22
 
19
23
  export const SalesAgentProductCardsCarousel = ({
@@ -23,23 +27,30 @@ export const SalesAgentProductCardsCarousel = ({
23
27
  numberOfProducts,
24
28
  variant = SalesAgentProductCardVariant.LARGE,
25
29
  onProductCardClick,
30
+ onExploreAllResults,
26
31
  }: SalesAgentProductCardsCarouselProps) => {
27
32
  const finalTheme = resolveTheme(theme);
33
+ const { totalProducts, areProductsMoreThanDisplayLimit, displayedProducts } =
34
+ getProductCarouselDisplayInfo(products, numberOfProducts);
28
35
 
29
- const forceShowCurrentPriceSpace = products.some(
36
+ const forceShowCurrentPriceSpace = displayedProducts.some(
30
37
  product => product.currentPrice && product.previousPrice !== product.currentPrice,
31
38
  );
32
39
 
40
+ const { badgeContent, badgeLabel } = getBadgeContentAndLabel({
41
+ totalProducts,
42
+ areProductsMoreThanDisplayLimit,
43
+ onExploreAllResults,
44
+ });
45
+
33
46
  return (
34
47
  <Carousel
35
48
  hideNavigation={hideNavigation}
36
- badgeLabel={
37
- // TODO: Get hard copy
38
- numberOfProducts && numberOfProducts > 1 ? `${numberOfProducts} Products` : undefined
39
- }
40
- elements={products.map(product => (
49
+ badgeContent={badgeContent}
50
+ badgeLabel={badgeLabel}
51
+ elements={displayedProducts.map((product, index) => (
41
52
  <SalesAgentProductCard
42
- key={product.id}
53
+ key={product.id ?? `product-${index}`}
43
54
  variant={variant}
44
55
  productName={product.productName}
45
56
  currentPrice={product.currentPrice}
@@ -0,0 +1,72 @@
1
+ import { AnimatePresence, motion } from 'framer-motion';
2
+ import { Theme } from '../../../../tokens/theme/theme';
3
+ import { SalesAgentProductCardProps } from '../../SalesAgentProductCard/types/types';
4
+ import { ResultsGridView } from './ResultsGridView';
5
+ import type { ResultsViewData } from '../hooks/useProductResultsView';
6
+
7
+ type SlideChatContentProps = {
8
+ isResultsView: boolean;
9
+ isResultsViewRef: React.MutableRefObject<boolean>;
10
+ resultsViewData: ResultsViewData | null;
11
+ onBackToChat: () => void;
12
+ onProductCardClick: (product: SalesAgentProductCardProps) => void;
13
+ theme: Theme;
14
+ chatMessages: React.ReactNode;
15
+ scrollToBottom: () => void;
16
+ };
17
+
18
+ const slideVariants = {
19
+ // Chat → Grid: chat exits left (-100%), grid enters from right (100%)
20
+ // Grid → Chat: grid exits right (100%), chat enters from left (-100%)
21
+ enter: (isResults: boolean) => ({ x: isResults ? '100%' : '-100%' }),
22
+ center: { x: 0 },
23
+ exit: (isResults: boolean) => ({ x: isResults ? '100%' : '-100%' }),
24
+ };
25
+
26
+ export const SlideChatContent = ({
27
+ isResultsView,
28
+ isResultsViewRef,
29
+ resultsViewData,
30
+ onBackToChat,
31
+ onProductCardClick,
32
+ theme,
33
+ chatMessages,
34
+ scrollToBottom,
35
+ }: SlideChatContentProps) => {
36
+ const resultsViewContent = resultsViewData && (
37
+ <ResultsGridView
38
+ theme={theme}
39
+ searchQuery={resultsViewData.searchQuery}
40
+ products={resultsViewData.products}
41
+ onBackToChat={onBackToChat}
42
+ onProductCardClick={onProductCardClick}
43
+ />
44
+ );
45
+
46
+ return (
47
+ <div className="envive-tw-relative envive-tw-min-h-[200px] envive-tw-w-full envive-tw-overflow-hidden">
48
+ <AnimatePresence
49
+ initial={false}
50
+ mode="wait"
51
+ >
52
+ <motion.div
53
+ key={isResultsView ? 'results' : 'chat'}
54
+ custom={isResultsView}
55
+ variants={slideVariants}
56
+ initial="enter"
57
+ animate="center"
58
+ exit="exit"
59
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
60
+ onAnimationComplete={() => {
61
+ if (!isResultsViewRef.current) {
62
+ scrollToBottom();
63
+ }
64
+ }}
65
+ className="envive-tw-w-full"
66
+ >
67
+ {isResultsView ? resultsViewContent : chatMessages}
68
+ </motion.div>
69
+ </AnimatePresence>
70
+ </div>
71
+ );
72
+ };
@@ -1,4 +1,6 @@
1
1
  import { Layout } from './Layout';
2
+ import { ResultsGridView } from './ResultsGridView';
3
+ import { SlideChatContent } from './SlideChatContent';
2
4
  import { UserMessage } from './UserMessage';
3
5
  import { AgentMessage } from './AgentMessage';
4
6
  import { ChatMessages } from './ChatMessages';
@@ -11,6 +13,8 @@ import { MessageDivider } from './MessageDivider';
11
13
 
12
14
  export const FloatingChatComponents = {
13
15
  Layout,
16
+ ResultsGridView,
17
+ SlideChatContent,
14
18
  UserMessage,
15
19
  AgentMessage,
16
20
  ChatMessages,
@@ -23,4 +27,3 @@ export const FloatingChatComponents = {
23
27
  };
24
28
 
25
29
  export { ModalSheet } from './ModalSheet';
26
- export type { ModalSheetControl } from './ModalSheet';
@@ -1,7 +1,7 @@
1
1
  import { RefObject, useMemo } from 'react';
2
2
  import { Message } from '@envive-ai/react-hooks/application/models';
3
3
  import { useMessageFilter } from '@envive-ai/react-hooks/hooks/MessageFilter';
4
- import type { ModalSheetControl } from '../types';
4
+ import type { ModalSheetControl } from '../snapConstants';
5
5
 
6
6
  export interface UseFilteredChatMessagesProps {
7
7
  messages: Message[][];
@@ -0,0 +1,49 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { getCleanProducts } from '../utils/functions';
3
+
4
+ export type ResultsViewData = {
5
+ products: ReturnType<typeof getCleanProducts>;
6
+ searchQuery: string;
7
+ };
8
+
9
+ type UseResultsViewProps = {
10
+ scrollToBottom: () => void;
11
+ };
12
+
13
+ export const useProductResultsView = ({ scrollToBottom }: UseResultsViewProps) => {
14
+ const [resultsViewData, setResultsViewData] = useState<ResultsViewData | null>(null);
15
+ const isResultsView = resultsViewData !== null;
16
+
17
+ const scrollContainerRef = useRef<HTMLDivElement>(null);
18
+ const isResultsViewRef = useRef(isResultsView);
19
+ isResultsViewRef.current = isResultsView;
20
+
21
+ const handleBackToChat = () => {
22
+ setResultsViewData(null);
23
+ };
24
+
25
+ // Scroll to top when navigating to results view
26
+ useEffect(() => {
27
+ if (isResultsView && scrollContainerRef.current) {
28
+ scrollContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
29
+ }
30
+ }, [isResultsView]);
31
+
32
+ // Scroll to bottom when navigating back to chat
33
+ const prevIsResultsViewRef = useRef(isResultsView);
34
+ useEffect(() => {
35
+ if (prevIsResultsViewRef.current && !isResultsView) {
36
+ scrollToBottom();
37
+ }
38
+ prevIsResultsViewRef.current = isResultsView;
39
+ }, [isResultsView, scrollToBottom]);
40
+
41
+ return {
42
+ resultsViewData,
43
+ setResultsViewData,
44
+ isResultsView,
45
+ scrollContainerRef,
46
+ isResultsViewRef,
47
+ handleBackToChat,
48
+ };
49
+ };