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

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 (168) hide show
  1. package/dist/AnimatedText/AnimatedText.d.cts +3 -3
  2. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.cts +2 -2
  3. package/dist/Carousel/Carousel.d.cts +2 -2
  4. package/dist/Carousel/Carousel.d.ts +2 -2
  5. package/dist/ChatFooter/ChatFooter.d.cts +2 -2
  6. package/dist/ChatFooter/ChatFooter.d.ts +2 -2
  7. package/dist/ChatFooter/components/index.d.cts +5 -5
  8. package/dist/ChatFooter/components/index.d.ts +3 -3
  9. package/dist/ChatFooter/hooks/useGetContainerProperties.cjs +1 -1
  10. package/dist/ChatFooter/hooks/useGetContainerProperties.js +1 -1
  11. package/dist/ChatHeader/ChatHeader.d.cts +2 -2
  12. package/dist/ChatHeader/ChatHeader.d.ts +2 -2
  13. package/dist/ChatHeader/components/Handle.cjs +6 -4
  14. package/dist/ChatHeader/components/Handle.js +6 -4
  15. package/dist/ChatPreview/ChatPreview.cjs +14 -5
  16. package/dist/ChatPreview/ChatPreview.d.cts +2 -2
  17. package/dist/ChatPreview/ChatPreview.d.ts +2 -2
  18. package/dist/ChatPreview/ChatPreview.js +14 -5
  19. package/dist/ChatPreview/components/Message.cjs +3 -2
  20. package/dist/ChatPreview/components/Message.js +3 -2
  21. package/dist/ChatPreview/types/types.d.cts +34 -2
  22. package/dist/ChatPreview/types/types.d.ts +34 -2
  23. package/dist/ChatPreviewComparison/ChatPreviewComparison.cjs +11 -4
  24. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
  25. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.ts +2 -2
  26. package/dist/ChatPreviewComparison/ChatPreviewComparison.js +11 -4
  27. package/dist/ChatPreviewComparison/components/Message.cjs +3 -2
  28. package/dist/ChatPreviewComparison/components/Message.js +3 -2
  29. package/dist/ChatPreviewComparison/types/types.d.cts +34 -1
  30. package/dist/ChatPreviewComparison/types/types.d.ts +34 -1
  31. package/dist/ChatPreviewLoading/ChatPreviewLoading.cjs +1 -1
  32. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
  33. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
  34. package/dist/ChatPreviewLoading/ChatPreviewLoading.js +1 -1
  35. package/dist/Container/Container.d.cts +180 -180
  36. package/dist/Container/Container.d.ts +10 -10
  37. package/dist/DesignTokens/DesignTokensComponent.d.cts +2 -2
  38. package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
  39. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
  40. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.ts +2 -2
  41. package/dist/FloatingButton/FloatingButton.d.cts +2 -2
  42. package/dist/FloatingButton/FloatingButton.d.ts +2 -2
  43. package/dist/FloatingChat/FloatingChat.cjs +4 -8
  44. package/dist/FloatingChat/FloatingChat.d.cts +2 -2
  45. package/dist/FloatingChat/FloatingChat.d.ts +2 -2
  46. package/dist/FloatingChat/FloatingChat.js +4 -8
  47. package/dist/FloatingChat/components/AgentMessage.cjs +1 -2
  48. package/dist/FloatingChat/components/AgentMessage.js +1 -2
  49. package/dist/FloatingChat/components/ChatMessages.cjs +1 -2
  50. package/dist/FloatingChat/components/ChatMessages.js +1 -2
  51. package/dist/FloatingChat/components/ModalSheet.cjs +13 -3
  52. package/dist/FloatingChat/components/ModalSheet.js +14 -4
  53. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.cjs +2 -3
  54. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.js +2 -3
  55. package/dist/FloatingChat/hooks/useFilteredChatMessages.cjs +1 -1
  56. package/dist/FloatingChat/hooks/useFilteredChatMessages.js +1 -1
  57. package/dist/FloatingChat/hooks/useSnapControl.cjs +17 -16
  58. package/dist/FloatingChat/hooks/useSnapControl.js +17 -16
  59. package/dist/FloatingChat/hooks/useSnapSetup.cjs +9 -27
  60. package/dist/FloatingChat/hooks/useSnapSetup.js +10 -28
  61. package/dist/FloatingChat/snapConstants.cjs +10 -0
  62. package/dist/FloatingChat/snapConstants.js +7 -0
  63. package/dist/FullPageSalesAgent/FullPageSalesAgent.cjs +5 -2
  64. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.cts +2 -2
  65. package/dist/FullPageSalesAgent/FullPageSalesAgent.d.ts +2 -2
  66. package/dist/FullPageSalesAgent/FullPageSalesAgent.js +5 -2
  67. package/dist/FullPageSalesAgent/components/Layout.cjs +3 -1
  68. package/dist/FullPageSalesAgent/components/Layout.js +3 -1
  69. package/dist/FullPageSalesAgent/hooks/useGetFooterStyles.cjs +2 -1
  70. package/dist/FullPageSalesAgent/hooks/useGetFooterStyles.js +2 -1
  71. package/dist/FullPageSalesAgent/hooks/useGetMessagesStyles.cjs +10 -0
  72. package/dist/FullPageSalesAgent/hooks/useGetMessagesStyles.js +10 -0
  73. package/dist/FullPageSalesAgent/hooks/useGetScrollContentStyles.cjs +4 -2
  74. package/dist/FullPageSalesAgent/hooks/useGetScrollContentStyles.js +4 -2
  75. package/dist/Image/Image.d.cts +2 -2
  76. package/dist/Image/Image.d.ts +2 -2
  77. package/dist/ImageGallery/ImageGallery.d.cts +2 -2
  78. package/dist/ImageGallery/ImageGallery.d.ts +2 -2
  79. package/dist/MarkdownProcessor/MarkdownProcessor.cjs +2 -1
  80. package/dist/MarkdownProcessor/MarkdownProcessor.d.cts +2 -2
  81. package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
  82. package/dist/MarkdownProcessor/MarkdownProcessor.js +2 -1
  83. package/dist/ProductCard/ProductCard.cjs +8 -2
  84. package/dist/ProductCard/ProductCard.d.cts +8 -2
  85. package/dist/ProductCard/ProductCard.d.ts +8 -2
  86. package/dist/ProductCard/ProductCard.js +8 -2
  87. package/dist/ProductCard/components/Carousel.cjs +9 -3
  88. package/dist/ProductCard/components/Carousel.js +9 -3
  89. package/dist/ProductCard/types/index.d.cts +34 -0
  90. package/dist/ProductCard/types/index.d.ts +34 -0
  91. package/dist/PromptButton/PromptButton.d.cts +2 -2
  92. package/dist/PromptButton/PromptButton.d.ts +2 -2
  93. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
  94. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
  95. package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.cjs +15 -21
  96. package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.js +15 -21
  97. package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
  98. package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
  99. package/dist/ReviewCard/ReviewCard.d.cts +2 -2
  100. package/dist/ReviewCard/ReviewCard.d.ts +2 -2
  101. package/dist/ReviewCard/components/index.d.cts +6 -6
  102. package/dist/ReviewCard/components/index.d.ts +6 -6
  103. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.cts +2 -2
  104. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.ts +2 -2
  105. package/dist/SalesAgentProductCard/components/index.d.cts +8 -8
  106. package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
  107. package/dist/SocialProof/SocialProof.cjs +4 -4
  108. package/dist/SocialProof/SocialProof.d.cts +2 -2
  109. package/dist/SocialProof/SocialProof.d.ts +2 -2
  110. package/dist/SocialProof/SocialProof.js +1 -1
  111. package/dist/SparkleAnimation/SparkleAnimation.d.cts +2 -2
  112. package/dist/SparkleAnimation/SparkleAnimation.d.ts +2 -2
  113. package/dist/Stack/Stack.d.cts +2 -2
  114. package/dist/Stack/Stack.d.ts +2 -2
  115. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
  116. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
  117. package/dist/TypingAnimation/TypingAnimation.cjs +8 -2
  118. package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
  119. package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
  120. package/dist/TypingAnimation/TypingAnimation.js +8 -2
  121. package/dist/TypingAnimation/types/index.d.cts +28 -0
  122. package/dist/TypingAnimation/types/index.d.ts +28 -0
  123. package/dist/Typography/Typography.d.cts +4 -4
  124. package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
  125. package/dist/WidgetTextField/WidgetTextField.d.ts +2 -2
  126. package/dist/WidgetTextField/components/Icon.cjs +2 -2
  127. package/dist/WidgetTextField/components/Icon.js +2 -2
  128. package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
  129. package/dist/WidgetWrapper/WidgetWrapper.d.ts +2 -2
  130. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.ts +2 -2
  131. package/dist/styles.css +1 -1
  132. package/package.json +3 -3
  133. package/src/components/ChatFooter/__tests__/ChatFooter.test.tsx +3 -0
  134. package/src/components/ChatFooter/hooks/useGetContainerProperties.ts +1 -1
  135. package/src/components/ChatHeader/components/Handle.tsx +14 -4
  136. package/src/components/ChatPreview/ChatPreview.tsx +27 -6
  137. package/src/components/ChatPreview/__tests__/ChatPreview.test.tsx +16 -5
  138. package/src/components/ChatPreview/components/Message.tsx +3 -1
  139. package/src/components/ChatPreview/types/types.ts +35 -2
  140. package/src/components/ChatPreviewComparison/ChatPreviewComparison.tsx +23 -6
  141. package/src/components/ChatPreviewComparison/__tests__/ChatPreviewComparison.test.tsx +16 -5
  142. package/src/components/ChatPreviewComparison/components/Message.tsx +7 -1
  143. package/src/components/ChatPreviewComparison/types/types.ts +35 -1
  144. package/src/components/FloatingChat/FloatingChat.tsx +3 -9
  145. package/src/components/FloatingChat/components/AgentMessage.tsx +0 -3
  146. package/src/components/FloatingChat/components/ChatMessages.tsx +0 -3
  147. package/src/components/FloatingChat/components/ModalSheet.tsx +18 -10
  148. package/src/components/FloatingChat/components/SalesAgentProductCardsCarousel.tsx +1 -5
  149. package/src/components/FloatingChat/hooks/useFilteredChatMessages.ts +2 -2
  150. package/src/components/FloatingChat/hooks/useScrollToBottom.ts +1 -0
  151. package/src/components/FloatingChat/hooks/useSnapControl.ts +17 -22
  152. package/src/components/FloatingChat/hooks/useSnapSetup.ts +16 -37
  153. package/src/components/FloatingChat/snapConstants.ts +7 -0
  154. package/src/components/FullPageSalesAgent/FullPageSalesAgent.tsx +5 -2
  155. package/src/components/FullPageSalesAgent/components/Layout.tsx +3 -1
  156. package/src/components/FullPageSalesAgent/hooks/useGetFooterStyles.ts +7 -2
  157. package/src/components/FullPageSalesAgent/hooks/useGetMessagesStyles.ts +11 -0
  158. package/src/components/FullPageSalesAgent/hooks/useGetScrollContentStyles.ts +5 -1
  159. package/src/components/MarkdownProcessor/MarkdownProcessor.tsx +1 -1
  160. package/src/components/Message/__tests__/Message.test.tsx +3 -3
  161. package/src/components/ProductCard/ProductCard.tsx +13 -1
  162. package/src/components/ProductCard/__tests__/ProductCard.test.tsx +73 -1
  163. package/src/components/ProductCard/components/Carousel.tsx +13 -1
  164. package/src/components/ProductCard/types/index.ts +34 -0
  165. package/src/components/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.tsx +12 -9
  166. package/src/components/TypingAnimation/TypingAnimation.tsx +22 -7
  167. package/src/components/TypingAnimation/types/index.ts +29 -0
  168. package/src/components/WidgetTextField/components/Icon.tsx +2 -2
@@ -19,7 +19,7 @@ export const useGetContainerProperties = ({
19
19
  return themeContainerClassNameMap[theme] ?? '';
20
20
  }, [theme, isScrolled]);
21
21
 
22
- const baseClassName = `envive-tw-w-full envive-tw-border-border-light ${!isScrolled ? 'envive-tw-opacity-90 envive-tw-backdrop-blur-[12px]' : ''}`;
22
+ const baseClassName = `envive-tw-w-full envive-tw-border-border-light ${!isScrolled ? 'envive-tw-bg-[color-mix(in_srgb,var(--envive-colors-background-light),transparent_10%)] envive-tw-backdrop-blur-[12px]' : ''}`;
23
23
 
24
24
  return { baseClassName, themeContainerClassNames };
25
25
  };
@@ -1,4 +1,5 @@
1
1
  import { motion, useMotionValue, useTransform } from 'framer-motion';
2
+ import { MAX_SWIPEABLE_VIEW_HEIGHT } from 'src/components/FloatingChat/snapConstants';
2
3
  import { Container } from '../../Container';
3
4
  import { Stack } from '../../Stack';
4
5
  import { useGetHandleProperties } from '../hooks/useGetHandleProperties';
@@ -7,14 +8,23 @@ import { HandleProps } from '../types';
7
8
  export const Handle = ({ theme, variant, state, animationKey }: HandleProps) => {
8
9
  const { containerClasses, leftBar, rightBar } = useGetHandleProperties(theme, variant, state);
9
10
 
10
- const snap43Percent = ((100 - 43) / 100) * window.innerHeight;
11
- const snap100Percent = 0;
11
+ const swipeviewFactor = MAX_SWIPEABLE_VIEW_HEIGHT / 100;
12
+ const closetSheetHeight = Math.floor(window.innerHeight * swipeviewFactor);
13
+ const snapFullView = 0;
12
14
 
13
15
  const defaultAnimationKey = useMotionValue(0);
14
16
  const activeAnimationKey = animationKey || defaultAnimationKey;
15
17
 
16
- const leftRotation = useTransform(activeAnimationKey, [snap100Percent, snap43Percent], [0, -10]);
17
- const rightRotation = useTransform(activeAnimationKey, [snap100Percent, snap43Percent], [0, 10]);
18
+ const leftRotation = useTransform(
19
+ activeAnimationKey,
20
+ [snapFullView, closetSheetHeight],
21
+ [0, -10],
22
+ );
23
+ const rightRotation = useTransform(
24
+ activeAnimationKey,
25
+ [snapFullView, closetSheetHeight],
26
+ [0, 10],
27
+ );
18
28
 
19
29
  if (animationKey) {
20
30
  return (
@@ -1,12 +1,12 @@
1
1
  import { useCallback, useMemo } from 'react';
2
- import { WidgetWrapperVariant } from '../WidgetWrapper/types/types';
3
- import { Theme } from '../Tokens';
4
- import { resolveTheme } from '../utils/resolveTheme';
5
- import { ChatPreviewComponents } from './components';
6
2
  import { PromptButtonVariant } from '../PromptButton';
7
3
  import { PromptCarousel, PromptCarouselRows } from '../PromptCarousel';
4
+ import { Theme } from '../Tokens';
5
+ import { resolveTheme } from '../utils/resolveTheme';
8
6
  import { WidgetTextField } from '../WidgetTextField';
7
+ import { WidgetWrapperVariant } from '../WidgetWrapper/types/types';
9
8
  import { WidgetWrapperWithTitle } from '../WidgetWrapperWithTitle/WidgetWrapperWithTitle';
9
+ import { ChatPreviewComponents } from './components';
10
10
  import { ChatPreviewProps } from './types/types';
11
11
 
12
12
  export const ChatPreview = ({
@@ -28,7 +28,17 @@ export const ChatPreview = ({
28
28
  const { messageText, promptButtons, textFieldPlaceholderText, logoSrc, titleLabel } =
29
29
  widgetContentProps ?? {};
30
30
 
31
- const { handlePromptButtonClick, handleTextFieldClick } = widgetEventProps ?? {};
31
+ const {
32
+ handlePromptButtonClick,
33
+ handlePromptButtonDrag,
34
+ handlePromptButtonHover,
35
+ handlePromptButtonMouseDown,
36
+ handlePromptButtonMouseUp,
37
+ handlePromptButtonTouchStart,
38
+ handlePromptButtonTouchEnd,
39
+ handleLinkClick,
40
+ handleTextFieldClick,
41
+ } = widgetEventProps ?? {};
32
42
 
33
43
  const suggestionTextMap = useMemo(() => {
34
44
  return Object.fromEntries(
@@ -48,7 +58,12 @@ export const ChatPreview = ({
48
58
 
49
59
  const finalTheme = resolveTheme(theme);
50
60
 
51
- const message = <ChatPreviewComponents.Message messageText={messageText ?? ''} />;
61
+ const message = (
62
+ <ChatPreviewComponents.Message
63
+ messageText={messageText ?? ''}
64
+ onLinkClick={handleLinkClick}
65
+ />
66
+ );
52
67
 
53
68
  const promptCarousel = (
54
69
  <PromptCarousel
@@ -56,6 +71,12 @@ export const ChatPreview = ({
56
71
  promptButtonType={PromptButtonVariant.SUGGESTED_RESPONSE}
57
72
  promptCarouselRows={PromptCarouselRows.ALWAYS_ONE}
58
73
  handleButtonClick={handleSuggestionButtonClick}
74
+ handleButtonDrag={handlePromptButtonDrag}
75
+ handleButtonHover={text => handlePromptButtonHover(suggestionTextMap[text].id)}
76
+ handleButtonMouseDown={text => handlePromptButtonMouseDown(suggestionTextMap[text].id)}
77
+ handleButtonMouseUp={handlePromptButtonMouseUp}
78
+ handleButtonTouchStart={text => handlePromptButtonTouchStart(suggestionTextMap[text].id)}
79
+ handleButtonTouchEnd={handlePromptButtonTouchEnd}
59
80
  />
60
81
  );
61
82
 
@@ -2,10 +2,21 @@ import '@testing-library/jest-dom';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { fireEvent, render, screen, waitFor } from '@testing-library/react';
4
4
  import { createElement } from 'react';
5
+ import { Suggestion } from '@envive-ai/react-hooks/application/models';
6
+ import { SuggestionCategory } from '@envive-ai/react-hooks/contexts/salesAgentContext';
5
7
  import { ChatPreview } from '../ChatPreview';
6
8
  import { WidgetWrapperVariant } from '../../WidgetWrapper/types/types';
7
9
  import { Theme } from '../../Tokens';
8
10
 
11
+ const createSuggestions = (texts: string[]): Suggestion[] =>
12
+ texts.map((content, i) => ({
13
+ id: `suggestion-${i}`,
14
+ content,
15
+ category: SuggestionCategory.ProductBased,
16
+ createdAt: new Date().toISOString(),
17
+ isAnswer: false,
18
+ }));
19
+
9
20
  vi.mock('../../MarkdownProcessor/MarkdownProcessor', () => {
10
21
  return {
11
22
  MarkdownProcessor: function MockMarkdownProcessor({ content }: { content: string }) {
@@ -20,12 +31,12 @@ describe('ChatPreview', () => {
20
31
  widgetContentProps: {
21
32
  titleLabel: 'Shopping Assistant',
22
33
  messageText: 'How can I help you find the perfect product today?',
23
- promptButtonTexts: [
34
+ promptButtons: createSuggestions([
24
35
  'What are the best products for gifting?',
25
36
  'Show me summer dresses',
26
37
  'Do you have free shipping?',
27
38
  'What sizes are available?',
28
- ],
39
+ ]),
29
40
  textFieldPlaceholderText: 'What can I help you find?',
30
41
  logoSrc: 'https://example.com/logo.svg',
31
42
  },
@@ -290,13 +301,13 @@ describe('ChatPreview', () => {
290
301
  });
291
302
 
292
303
  describe('Edge cases', () => {
293
- it('should render with empty promptButtonTexts array', async () => {
304
+ it('should render with empty promptButtons array', async () => {
294
305
  render(
295
306
  <ChatPreview
296
307
  {...defaultProps}
297
308
  widgetContentProps={{
298
309
  ...defaultProps.widgetContentProps,
299
- promptButtonTexts: [],
310
+ promptButtons: [],
300
311
  }}
301
312
  />,
302
313
  );
@@ -326,7 +337,7 @@ describe('ChatPreview', () => {
326
337
  widgetContentProps={{
327
338
  titleLabel: 'Shopping Assistant',
328
339
  messageText: 'Test message',
329
- promptButtonTexts: ['Button 1'],
340
+ promptButtons: createSuggestions(['Button 1']),
330
341
  }}
331
342
  widgetStyleProps={{}}
332
343
  widgetEventProps={{}}
@@ -7,9 +7,10 @@ const MarkdownProcessor = lazy(async () => ({
7
7
 
8
8
  type MessageProps = {
9
9
  messageText: string;
10
+ onLinkClick?: (url: string) => void;
10
11
  };
11
12
 
12
- export const Message = ({ messageText }: MessageProps) => {
13
+ export const Message = ({ messageText, onLinkClick }: MessageProps) => {
13
14
  return (
14
15
  <Suspense>
15
16
  <MarkdownProcessor
@@ -17,6 +18,7 @@ export const Message = ({ messageText }: MessageProps) => {
17
18
  clampParagraphs={4}
18
19
  textColor={TypographyColor.TEXT_PRIMARY}
19
20
  textVariant={TypographyVariant.B3_RG}
21
+ onLinkClick={onLinkClick}
20
22
  />
21
23
  </Suspense>
22
24
  );
@@ -130,11 +130,44 @@ export type WidgetEventProps = {
130
130
  * @param text - The text label of the clicked prompt button
131
131
  */
132
132
  handlePromptButtonClick?: (suggestion: Suggestion) => void;
133
+ /**
134
+ * Callback function invoked when the prompt buttons carousel is dragged.
135
+ * Useful for tracking drag-to-scroll interactions.
136
+ */
137
+ handlePromptButtonDrag?: () => void;
138
+ /**
139
+ * Callback function invoked when the user hovers over a prompt button.
140
+ * Receives the hovered button's text as a parameter.
141
+ */
142
+ handlePromptButtonHover?: (text: string) => void;
143
+ /**
144
+ * Callback function invoked when the mouse button is pressed down on a prompt button.
145
+ * Receives the pressed button's text as a parameter.
146
+ */
147
+ handlePromptButtonMouseDown?: (text: string) => void;
148
+ /**
149
+ * Callback function invoked when the mouse button is released after pressing a prompt button.
150
+ */
151
+ handlePromptButtonMouseUp?: () => void;
152
+ /**
153
+ * Callback function invoked when a touch interaction starts on a prompt button.
154
+ * Receives the touched button's text as a parameter.
155
+ */
156
+ handlePromptButtonTouchStart?: (text: string) => void;
157
+ /**
158
+ * Callback function invoked when a touch interaction ends after touching a prompt button.
159
+ */
160
+ handlePromptButtonTouchEnd?: () => void;
133
161
 
134
162
  /**
135
- * Callback function invoked when the text field is clicked.
163
+ * Callback function invoked when a link inside the widget is clicked.
136
164
  *
137
- * @param text - The placeholder text of the text field
165
+ * @param url - The URL of the clicked link
166
+ */
167
+ handleLinkClick?: (url: string) => void;
168
+
169
+ /**
170
+ * Callback function invoked when the text field is clicked.
138
171
  */
139
172
  handleTextFieldClick?: () => void;
140
173
  };
@@ -1,13 +1,13 @@
1
1
  import { useCallback, useMemo } from 'react';
2
- import { WidgetWrapperVariant } from '../WidgetWrapper/types/types';
3
- import { Theme } from '../Tokens';
4
- import { resolveTheme } from '../utils/resolveTheme';
5
- import { ChatPreviewComparisonComponents } from './components';
6
2
  import { PromptButtonVariant } from '../PromptButton';
7
3
  import { PromptCarousel, PromptCarouselRows } from '../PromptCarousel';
4
+ import { Theme } from '../Tokens';
5
+ import { resolveTheme } from '../utils/resolveTheme';
8
6
  import { WidgetTextField } from '../WidgetTextField';
9
- import { ChatPreviewComparisonProps } from './types/types';
7
+ import { WidgetWrapperVariant } from '../WidgetWrapper/types/types';
10
8
  import { WidgetWrapperWithTitle } from '../WidgetWrapperWithTitle/WidgetWrapperWithTitle';
9
+ import { ChatPreviewComparisonComponents } from './components';
10
+ import { ChatPreviewComparisonProps } from './types/types';
11
11
 
12
12
  export const ChatPreviewComparison = ({
13
13
  baseProps,
@@ -36,7 +36,17 @@ export const ChatPreviewComparison = ({
36
36
  titleLabel,
37
37
  } = widgetContentProps ?? {};
38
38
 
39
- const { handlePromptButtonClick, handleTextFieldClick } = widgetEventProps ?? {};
39
+ const {
40
+ handlePromptButtonClick,
41
+ handlePromptButtonDrag,
42
+ handlePromptButtonHover,
43
+ handlePromptButtonMouseDown,
44
+ handlePromptButtonMouseUp,
45
+ handlePromptButtonTouchStart,
46
+ handlePromptButtonTouchEnd,
47
+ handleLinkClick,
48
+ handleTextFieldClick,
49
+ } = widgetEventProps ?? {};
40
50
 
41
51
  const finalTheme = resolveTheme(theme);
42
52
 
@@ -67,6 +77,7 @@ export const ChatPreviewComparison = ({
67
77
  <ChatPreviewComparisonComponents.Message
68
78
  messageText={messageText ?? ''}
69
79
  theme={finalTheme}
80
+ onLinkClick={handleLinkClick}
70
81
  />
71
82
  );
72
83
 
@@ -76,6 +87,12 @@ export const ChatPreviewComparison = ({
76
87
  promptButtonType={PromptButtonVariant.SUGGESTED_RESPONSE}
77
88
  promptCarouselRows={PromptCarouselRows.ALWAYS_ONE}
78
89
  handleButtonClick={handleSuggestionButtonClick}
90
+ handleButtonDrag={handlePromptButtonDrag}
91
+ handleButtonHover={text => handlePromptButtonHover(suggestionTextMap[text].id)}
92
+ handleButtonMouseDown={text => handlePromptButtonMouseDown(suggestionTextMap[text].id)}
93
+ handleButtonMouseUp={handlePromptButtonMouseUp}
94
+ handleButtonTouchStart={text => handlePromptButtonTouchStart(suggestionTextMap[text].id)}
95
+ handleButtonTouchEnd={handlePromptButtonTouchEnd}
79
96
  />
80
97
  );
81
98
 
@@ -2,10 +2,21 @@ import '@testing-library/jest-dom';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { fireEvent, render, screen, waitFor } from '@testing-library/react';
4
4
  import { createElement } from 'react';
5
+ import { Suggestion } from '@envive-ai/react-hooks/application/models';
6
+ import { SuggestionCategory } from '@envive-ai/react-hooks/contexts/salesAgentContext';
5
7
  import { ChatPreviewComparison } from '../ChatPreviewComparison';
6
8
  import { WidgetWrapperVariant } from '../../WidgetWrapper/types/types';
7
9
  import { Theme } from '../../Tokens';
8
10
 
11
+ const createSuggestions = (texts: string[]): Suggestion[] =>
12
+ texts.map((content, i) => ({
13
+ id: `suggestion-${i}`,
14
+ content,
15
+ category: SuggestionCategory.ProductBased,
16
+ createdAt: new Date().toISOString(),
17
+ isAnswer: false,
18
+ }));
19
+
9
20
  const mockUseCheckIsMobile = vi.fn(() => ({ isMobile: false, viewportWidth: 1024 }));
10
21
  vi.mock('../../utils/useCheckIsMobile', () => ({
11
22
  useCheckIsMobile: () => mockUseCheckIsMobile(),
@@ -40,12 +51,12 @@ describe('ChatPreviewComparison', () => {
40
51
  headlineText: 'Compare these products',
41
52
  messageText:
42
53
  'I can help you compare these two products side by side. Let me know what specific features, specifications, or details you would like to know more about.',
43
- promptButtonTexts: [
54
+ promptButtons: createSuggestions([
44
55
  'Tell me about product A',
45
56
  'Tell me about product B',
46
57
  'What are the differences?',
47
58
  'Which one is better?',
48
- ],
59
+ ]),
49
60
  textFieldPlaceholderText: 'What can I help you find?',
50
61
  images: [
51
62
  { src: 'https://picsum.photos/400/600?random=1', alt: 'Image 1' },
@@ -349,13 +360,13 @@ describe('ChatPreviewComparison', () => {
349
360
  expect(screen.getByText('Compare these products')).toBeInTheDocument();
350
361
  });
351
362
 
352
- it('should render with empty promptButtonTexts array', () => {
363
+ it('should render with empty promptButtons array', () => {
353
364
  render(
354
365
  <ChatPreviewComparison
355
366
  {...defaultProps}
356
367
  widgetContentProps={{
357
368
  ...defaultProps.widgetContentProps,
358
- promptButtonTexts: [],
369
+ promptButtons: [],
359
370
  }}
360
371
  />,
361
372
  );
@@ -396,7 +407,7 @@ describe('ChatPreviewComparison', () => {
396
407
  titleLabel: 'Shopping Assistant',
397
408
  headlineText: 'Compare these products',
398
409
  messageText: 'Test message',
399
- promptButtonTexts: ['Button 1'],
410
+ promptButtons: createSuggestions(['Button 1']),
400
411
  }}
401
412
  widgetStyleProps={{}}
402
413
  widgetEventProps={{}}
@@ -11,9 +11,14 @@ const MarkdownProcessor = lazy(async () => ({
11
11
  type MessageProps = {
12
12
  messageText: string;
13
13
  theme: Theme;
14
+ onLinkClick?: (url: string) => void;
14
15
  };
15
16
 
16
- export const Message = ({ messageText, theme = Theme.GLOBAL_CUSTOM }: MessageProps) => {
17
+ export const Message = ({
18
+ messageText,
19
+ theme = Theme.GLOBAL_CUSTOM,
20
+ onLinkClick,
21
+ }: MessageProps) => {
17
22
  const { isMobile } = useCheckIsMobile();
18
23
 
19
24
  const finalTheme = resolveTheme(theme);
@@ -28,6 +33,7 @@ export const Message = ({ messageText, theme = Theme.GLOBAL_CUSTOM }: MessagePro
28
33
  clampParagraphs={isStandardAndMobile ? 7 : 3}
29
34
  textColor={TypographyColor.TEXT_PRIMARY}
30
35
  textVariant={TypographyVariant.B3_RG}
36
+ onLinkClick={onLinkClick}
31
37
  />
32
38
  </Suspense>
33
39
  );
@@ -136,10 +136,44 @@ export type WidgetEventProps = {
136
136
  * @param text - The text label of the clicked prompt button
137
137
  */
138
138
  handlePromptButtonClick?: (suggestion: Suggestion) => void;
139
+ /**
140
+ * Callback function invoked when the prompt buttons carousel is dragged.
141
+ * Useful for tracking drag-to-scroll interactions.
142
+ */
143
+ handlePromptButtonDrag?: () => void;
144
+ /**
145
+ * Callback function invoked when the user hovers over a prompt button.
146
+ * Receives the hovered button's text as a parameter.
147
+ */
148
+ handlePromptButtonHover?: (text: string) => void;
149
+ /**
150
+ * Callback function invoked when the mouse button is pressed down on a prompt button.
151
+ * Receives the pressed button's text as a parameter.
152
+ */
153
+ handlePromptButtonMouseDown?: (text: string) => void;
154
+ /**
155
+ * Callback function invoked when the mouse button is released after pressing a prompt button.
156
+ */
157
+ handlePromptButtonMouseUp?: () => void;
158
+ /**
159
+ * Callback function invoked when a touch interaction starts on a prompt button.
160
+ * Receives the touched button's text as a parameter.
161
+ */
162
+ handlePromptButtonTouchStart?: (text: string) => void;
163
+ /**
164
+ * Callback function invoked when a touch interaction ends after touching a prompt button.
165
+ */
166
+ handlePromptButtonTouchEnd?: () => void;
139
167
 
140
168
  /**
141
- * Callback function invoked when the text field is clicked.
169
+ * Callback function invoked when a link inside the widget is clicked.
142
170
  *
171
+ * @param url - The URL of the clicked link
172
+ */
173
+ handleLinkClick?: (url: string) => void;
174
+
175
+ /**
176
+ * Callback function invoked when the text field is clicked.
143
177
  */
144
178
  handleTextFieldClick?: () => void;
145
179
  };
@@ -65,7 +65,6 @@ export const FloatingChat = ({
65
65
  showEnviveLogo,
66
66
  ignoreFirstModelResponse,
67
67
  neverShowSingleProductCards,
68
- partialViewConfig,
69
68
  } = floatingChatConfig;
70
69
 
71
70
  const {
@@ -108,8 +107,6 @@ export const FloatingChat = ({
108
107
  }
109
108
  };
110
109
 
111
- const partialViewDisabled = partialViewConfig?.disabled ?? false;
112
-
113
110
  const {
114
111
  modalSheetControl,
115
112
  maxSwipeableViewHeight,
@@ -122,10 +119,8 @@ export const FloatingChat = ({
122
119
  handleSnapChange,
123
120
  animationKey,
124
121
  mobileHeaderHeight,
125
- shouldShowHeader,
126
122
  isFullView,
127
- isPartialView,
128
- } = useSnapSetup({ isFloatingChatOpen, partialViewDisabled });
123
+ } = useSnapSetup({ isFloatingChatOpen });
129
124
 
130
125
  const { showScrollButton, scrollToBottom, isFloatingLayout } = useScrollToBottom({
131
126
  messagesRef: chatMessagesRef,
@@ -137,7 +132,7 @@ export const FloatingChat = ({
137
132
  generalSuggestions,
138
133
  ],
139
134
  scrollThreshold: 100,
140
- useFloatingLayout: isMobile && isPartialView,
135
+ useFloatingLayout: false,
141
136
  footerHeight: 160,
142
137
  isOpen: isFloatingChatOpen,
143
138
  });
@@ -304,7 +299,6 @@ export const FloatingChat = ({
304
299
  ignoreFirstModelResponse={ignoreFirstModelResponse}
305
300
  neverShowSingleProductCards={neverShowSingleProductCards}
306
301
  showVerifiedBuyer={showVerifiedBuyer}
307
- isPartialView={isPartialView}
308
302
  onFormResponseSubmitted={onFormResponseSubmitted}
309
303
  parentWidget={WidgetInteractionComponent.FLOATING_CHAT}
310
304
  />
@@ -367,7 +361,7 @@ export const FloatingChat = ({
367
361
  style={style}
368
362
  testId={testId}
369
363
  theme={finalTheme}
370
- header={shouldShowHeader ? mobileHeader : undefined}
364
+ header={mobileHeader}
371
365
  footer={footer}
372
366
  welcomeMessage={welcomeMessage}
373
367
  chatMessages={chatMessages}
@@ -40,7 +40,6 @@ export interface AgentMessageProps {
40
40
  isPendingResponse?: boolean;
41
41
  neverShowSingleProductCards?: boolean;
42
42
  showVerifiedBuyer?: boolean;
43
- isPartialView?: boolean;
44
43
  onFormResponseSubmitted?: (formResponse: FormSubmittedAttributes) => void;
45
44
  parentWidget: WidgetInteractionComponent;
46
45
  }
@@ -55,7 +54,6 @@ export const AgentMessage = ({
55
54
  isPendingResponse = false,
56
55
  neverShowSingleProductCards = false,
57
56
  showVerifiedBuyer = false,
58
- isPartialView,
59
57
  onFormResponseSubmitted,
60
58
  parentWidget,
61
59
  }: AgentMessageProps) => {
@@ -175,7 +173,6 @@ export const AgentMessage = ({
175
173
  products={products}
176
174
  numberOfProducts={products.length}
177
175
  theme={finalTheme}
178
- isPartialView={isPartialView}
179
176
  onProductCardClick={handleProductCardClick}
180
177
  />
181
178
  );
@@ -30,7 +30,6 @@ export interface ChatMessagesProps {
30
30
  ignoreFirstModelResponse?: boolean;
31
31
  neverShowSingleProductCards?: boolean;
32
32
  showVerifiedBuyer?: boolean;
33
- isPartialView?: boolean;
34
33
  onFormResponseSubmitted?: (formResponse: FormSubmittedAttributes) => void;
35
34
  parentWidget: WidgetInteractionComponent;
36
35
  }
@@ -49,7 +48,6 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
49
48
  ignoreFirstModelResponse,
50
49
  neverShowSingleProductCards,
51
50
  showVerifiedBuyer,
52
- isPartialView,
53
51
  onFormResponseSubmitted,
54
52
  parentWidget,
55
53
  },
@@ -143,7 +141,6 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
143
141
  isPendingResponse={isLoading && isLastMessageTurn}
144
142
  neverShowSingleProductCards={neverShowSingleProductCards}
145
143
  showVerifiedBuyer={showVerifiedBuyer}
146
- isPartialView={isPartialView}
147
144
  onFormResponseSubmitted={onFormResponseSubmitted}
148
145
  parentWidget={parentWidget}
149
146
  />
@@ -5,14 +5,12 @@ import {
5
5
  motion,
6
6
  useAnimate,
7
7
  } from 'framer-motion';
8
- import React, { FC, TouchEvent, useEffect, useImperativeHandle } from 'react';
8
+ import React, { FC, TouchEvent, useEffect, useImperativeHandle, useRef } from 'react';
9
9
  import { usePreventScroll } from '../hooks/usePreventScroll';
10
10
  import { Unit } from '../hooks/useSnapCalculator';
11
11
  import { useSnapControl } from '../hooks/useSnapControl';
12
-
13
- export interface ModalSheetControl {
14
- jumpToSnap: (snapIndex: number) => void;
15
- }
12
+ import { FULL_SNAP_INDEX, HIDDEN_SNAP_INDEX } from '../snapConstants';
13
+ import type { ModalSheetControl } from '../snapConstants';
16
14
 
17
15
  interface ModalSheetProps {
18
16
  animationKey: MotionValue;
@@ -47,6 +45,7 @@ export const ModalSheet: FC<ModalSheetProps> = ({
47
45
  }) => {
48
46
  const [layerRef] = useAnimate();
49
47
  const [containerRef] = useAnimate();
48
+ const justDraggedRef = useRef(false);
50
49
 
51
50
  const {
52
51
  gestureRef,
@@ -98,10 +97,15 @@ export const ModalSheet: FC<ModalSheetProps> = ({
98
97
  },
99
98
  }));
100
99
 
101
- const expandToFullView = () => {
102
- const idx = snaps.indexOf(currentSnap);
103
- if (idx === 1) {
104
- jumpTo(getSnapToPixel(2));
100
+ const handleHandlerClick = () => {
101
+ if (justDraggedRef.current) {
102
+ justDraggedRef.current = false;
103
+ return;
104
+ }
105
+ if (snaps.length === 2) {
106
+ // Toggle between open and closed. if full (100): close. if closed (0): open.
107
+ const targetSnap = currentSnap === 100 ? snaps[HIDDEN_SNAP_INDEX] : snaps[FULL_SNAP_INDEX];
108
+ jumpTo(getSnapToPixel(targetSnap));
105
109
  }
106
110
  };
107
111
 
@@ -148,6 +152,10 @@ export const ModalSheet: FC<ModalSheetProps> = ({
148
152
  }, [open]);
149
153
 
150
154
  const handleDragEnd = () => {
155
+ justDraggedRef.current = true;
156
+ setTimeout(() => {
157
+ justDraggedRef.current = false;
158
+ }, 200);
151
159
  const snap = defineNextSnapByPosition();
152
160
  jumpTo(snap);
153
161
  };
@@ -229,7 +237,7 @@ export const ModalSheet: FC<ModalSheetProps> = ({
229
237
  exit={{ y: swipeviewHeightPx }}
230
238
  transition={animation}
231
239
  onDragEnd={handleDragEnd}
232
- onClick={expandToFullView}
240
+ onClick={handleHandlerClick}
233
241
  style={{
234
242
  y: animatedY,
235
243
  bottom: swipeviewHeightPx,
@@ -13,7 +13,6 @@ export interface SalesAgentProductCardsCarouselProps {
13
13
  numberOfProducts?: number;
14
14
  products: SalesAgentProductCardProps[];
15
15
  variant?: SalesAgentProductCardVariant;
16
- isPartialView?: boolean;
17
16
  onProductCardClick?: (product: SalesAgentProductCardProps) => void;
18
17
  }
19
18
 
@@ -23,13 +22,10 @@ export const SalesAgentProductCardsCarousel = ({
23
22
  hideNavigation = false,
24
23
  numberOfProducts,
25
24
  variant = SalesAgentProductCardVariant.LARGE,
26
- isPartialView = false,
27
25
  onProductCardClick,
28
26
  }: SalesAgentProductCardsCarouselProps) => {
29
27
  const finalTheme = resolveTheme(theme);
30
28
 
31
- const finalVariant = isPartialView ? SalesAgentProductCardVariant.SMALL : variant;
32
-
33
29
  const forceShowCurrentPriceSpace = products.some(
34
30
  product => product.currentPrice && product.previousPrice !== product.currentPrice,
35
31
  );
@@ -44,7 +40,7 @@ export const SalesAgentProductCardsCarousel = ({
44
40
  elements={products.map(product => (
45
41
  <SalesAgentProductCard
46
42
  key={product.id}
47
- variant={finalVariant}
43
+ variant={variant}
48
44
  productName={product.productName}
49
45
  currentPrice={product.currentPrice}
50
46
  previousPrice={product.previousPrice}
@@ -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 { ModalSheetControl } from '../components';
4
+ import type { ModalSheetControl } from '../types';
5
5
 
6
6
  export interface UseFilteredChatMessagesProps {
7
7
  messages: Message[][];
@@ -33,7 +33,7 @@ export const useFilteredChatMessages = ({
33
33
 
34
34
  const hasFilteredMessages = filteredMessages.length !== messages.length && isMobile;
35
35
 
36
- const handlePreviousDiscussions = () => modalSheetControl.current?.jumpToSnap(2);
36
+ const handlePreviousDiscussions = () => modalSheetControl.current?.jumpToSnap(1);
37
37
 
38
38
  return {
39
39
  filteredMessages,
@@ -125,6 +125,7 @@ export const useScrollToBottom = ({
125
125
  }
126
126
  });
127
127
  }
128
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- autoScrollDependencies is intentionally spread for dynamic deps
128
129
  }, [...autoScrollDependencies, getScrollableContainer, useFloatingLayout]);
129
130
 
130
131
  useEffect(() => {