@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
@@ -8,6 +8,7 @@ import {
8
8
  } from 'framer-motion';
9
9
  import { useMemo, useState } from 'react';
10
10
  import { Unit, useSnapCalculator } from './useSnapCalculator';
11
+ import { FULL_SNAP_INDEX, HIDDEN_SNAP_INDEX } from '../snapConstants';
11
12
 
12
13
  type SnapControl = {
13
14
  animationKey?: MotionValue;
@@ -21,8 +22,6 @@ type SnapControl = {
21
22
  onSnapComplete: (currentSnap: number, nextSnap: number, collapsed: boolean) => void;
22
23
  };
23
24
 
24
- const MIDDLE_SNAP_OFFSET = 5;
25
-
26
25
  export const useSnapControl = ({
27
26
  animationKey,
28
27
  animation,
@@ -45,26 +44,22 @@ export const useSnapControl = ({
45
44
 
46
45
  const contentHeight = useTransform(animatedY, [0, swipeviewHeightPx], [swipeviewHeightPx, 0]);
47
46
 
48
- const snapOverlayReference = useMemo(
49
- () => [
50
- getSnapToPixel(snaps[0]),
51
- getSnapToPixel(snaps[1]) - MIDDLE_SNAP_OFFSET,
52
- getSnapToPixel(snaps[2]),
53
- ],
54
- [getSnapToPixel, snaps],
55
- );
56
-
57
- const displayOverlay = useTransform(() =>
58
- animatedY.get() === -1
59
- ? 'none'
60
- : transform(animatedY.get(), snapOverlayReference, ['none', 'none', 'block']),
61
- );
62
-
63
- const opacityOverlay = useTransform(() =>
64
- animatedY.get() === -1
65
- ? 0
66
- : transform(animatedY.get(), snapOverlayReference, [0, 0, overlayOpacity]),
67
- );
47
+ const snapOverlayReference = useMemo(() => {
48
+ if (snaps.length === 1) {
49
+ return [0, swipeviewHeightPx];
50
+ }
51
+ return [getSnapToPixel(snaps[HIDDEN_SNAP_INDEX]), getSnapToPixel(snaps[FULL_SNAP_INDEX])];
52
+ }, [getSnapToPixel, snaps, swipeviewHeightPx]);
53
+
54
+ const displayOverlay = useTransform(() => {
55
+ if (animatedY.get() === -1) return 'none';
56
+ return transform(animatedY.get(), snapOverlayReference, ['none', 'block']) as string;
57
+ });
58
+
59
+ const opacityOverlay = useTransform(() => {
60
+ if (animatedY.get() === -1) return 0;
61
+ return transform(animatedY.get(), snapOverlayReference, [0, overlayOpacity]) as number;
62
+ });
68
63
 
69
64
  const initSnapInPixels = snapsToPixels?.[initSnap];
70
65
  const getInitSnap = () => snaps?.[initSnap];
@@ -1,7 +1,7 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useRef, useState } from 'react';
2
2
  import { MotionValue, useMotionValue, useTransform } from 'framer-motion';
3
3
  import { useCheckIsMobile } from '../../utils/useCheckIsMobile';
4
- import { ModalSheetControl } from '../components';
4
+ import { MAX_SWIPEABLE_VIEW_HEIGHT, ModalSheetControl } from '../snapConstants';
5
5
 
6
6
  export interface UseSnapSetupReturn {
7
7
  modalSheetControl: React.RefObject<ModalSheetControl>;
@@ -20,24 +20,19 @@ export interface UseSnapSetupReturn {
20
20
  animationKey: MotionValue<number>;
21
21
  mobileHeaderHeight: MotionValue<number>;
22
22
 
23
- snap43Percent: number;
24
- snap100Percent: number;
25
-
26
- shouldShowHeader: boolean;
27
23
  isFullView: boolean;
28
- isPartialView: boolean;
29
24
  }
30
25
 
31
26
  export interface UseSnapSetupProps {
32
27
  isFloatingChatOpen: boolean;
33
- partialViewDisabled?: boolean;
34
28
  }
35
29
 
36
- export const useSnapSetup = ({
37
- isFloatingChatOpen,
38
- partialViewDisabled,
39
- }: UseSnapSetupProps): UseSnapSetupReturn => {
40
- const maxSwipeableViewHeight = 89;
30
+ export { MAX_SWIPEABLE_VIEW_HEIGHT, HIDDEN_SNAP_INDEX, FULL_SNAP_INDEX } from '../snapConstants';
31
+
32
+ const SNAPS_NO_AUTO_EXPAND = [0, 100];
33
+
34
+ export const useSnapSetup = ({ isFloatingChatOpen }: UseSnapSetupProps): UseSnapSetupReturn => {
35
+ const maxSwipeableViewHeight = MAX_SWIPEABLE_VIEW_HEIGHT;
41
36
  const modalSheetControl = useRef<ModalSheetControl>(null);
42
37
  const { viewportWidth } = useCheckIsMobile();
43
38
 
@@ -49,35 +44,23 @@ export const useSnapSetup = ({
49
44
  ?.selectedCustomizeOption,
50
45
  );
51
46
 
52
- const snaps = (() => {
53
- if (partialViewDisabled) return shouldAutoExpand ? [100] : [0, 100];
54
- return shouldAutoExpand ? [43, 100] : [0, 43, 100];
55
- })();
56
- const partialViewDisabledValue = shouldAutoExpand ? 0 : 1;
57
- const initialSnap = partialViewDisabled ? partialViewDisabledValue : 1;
47
+ const snaps = shouldAutoExpand ? [100] : SNAPS_NO_AUTO_EXPAND;
48
+ const initialSnap = shouldAutoExpand ? 0 : 1;
58
49
 
59
50
  const [currentSnapPercentage, setCurrentSnapPercentage] = useState<number>(
60
- snaps[initialSnap] || 43,
51
+ snaps[initialSnap] ?? 100,
61
52
  );
62
53
 
63
54
  const animationKey = useMotionValue(0);
64
55
 
65
- const snap43Percent = ((100 - 43) / 100) * window.innerHeight;
66
- const snap100Percent = 0;
67
-
68
- const mobileHeaderHeight = useTransform(animationKey, [snap100Percent, snap43Percent], [56, 0]);
69
-
70
- const [shouldShowHeader, setShouldShowHeader] = useState(false);
56
+ // Y=0 when full screen, Y=swipeviewHeightPx when closed (matches useSnapCalculator)
57
+ const swipeviewHeightPx = Math.floor(
58
+ (typeof window !== 'undefined' ? window.innerHeight : 0) * (maxSwipeableViewHeight / 100),
59
+ );
71
60
 
72
- useEffect(() => {
73
- const unsubscribe = animationKey.on('change', value => {
74
- setShouldShowHeader(value < snap43Percent - 10);
75
- });
76
- return unsubscribe;
77
- }, [animationKey, snap43Percent]);
61
+ const mobileHeaderHeight = useTransform(animationKey, [0, swipeviewHeightPx], [56, 0]);
78
62
 
79
63
  const isFullView = isMobile && currentSnapPercentage === 100;
80
- const isPartialView = isMobile && currentSnapPercentage >= 0 && currentSnapPercentage <= 43;
81
64
 
82
65
  const handleSnapChange = (snapPercentage: number) => {
83
66
  setCurrentSnapPercentage(snapPercentage);
@@ -101,10 +84,6 @@ export const useSnapSetup = ({
101
84
  handleSnapChange,
102
85
  animationKey,
103
86
  mobileHeaderHeight,
104
- snap43Percent,
105
- snap100Percent,
106
- shouldShowHeader,
107
87
  isFullView,
108
- isPartialView,
109
88
  };
110
89
  };
@@ -0,0 +1,7 @@
1
+ export const MAX_SWIPEABLE_VIEW_HEIGHT = 89;
2
+ export const HIDDEN_SNAP_INDEX = 0;
3
+ export const FULL_SNAP_INDEX = 1;
4
+
5
+ export interface ModalSheetControl {
6
+ jumpToSnap: (snapIndex: number) => void;
7
+ }
@@ -21,6 +21,7 @@ import { resolveTheme } from '../utils/resolveTheme';
21
21
  import { SparkleIconColor, WelcomeMessage } from '../WelcomeMessage';
22
22
  import { FullPageSAComponents } from './components';
23
23
  import { useGetFooterStyles } from './hooks/useGetFooterStyles';
24
+ import { useGetMessagesStyles } from './hooks/useGetMessagesStyles';
24
25
  import { useIsMobile } from './hooks/useIsMobile';
25
26
 
26
27
  interface FullPageSalesAgentProps {
@@ -44,7 +45,8 @@ export const FullPageSalesAgent = ({
44
45
  const salesAgentData = useSalesAgent(WidgetInteractionComponent.FULL_PAGE_SALES_AGENT);
45
46
  const [query, setQuery] = useState('');
46
47
  const chatMessagesRef = useRef<HTMLDivElement>(null);
47
- const { footerStyles } = useGetFooterStyles();
48
+ const { footerStyles, footerClasses } = useGetFooterStyles();
49
+ const { messageClasses } = useGetMessagesStyles();
48
50
  const { isMobile } = useIsMobile();
49
51
  const { trackWidgetInteraction } = useWidgetInteraction();
50
52
  const { onDrag, onHover, onMouseDown, onMouseUp, onTouchStart, onTouchEnd } =
@@ -135,7 +137,7 @@ export const FullPageSalesAgent = ({
135
137
 
136
138
  const footer = (
137
139
  <ChatFooter
138
- className="envive-tw-bg-background-light"
140
+ className={footerClasses}
139
141
  style={footerStyles}
140
142
  theme={resolvedTheme}
141
143
  isScrolled={isMobile ? isFloatingLayout : false}
@@ -177,6 +179,7 @@ export const FullPageSalesAgent = ({
177
179
 
178
180
  const chatMessages = (
179
181
  <FloatingChatComponents.ChatMessages
182
+ className={messageClasses}
180
183
  theme={resolvedTheme}
181
184
  ref={chatMessagesRef}
182
185
  isLoading={isPendingResponse && !isResponseStreaming}
@@ -5,6 +5,7 @@ import { Stack } from '../../Stack';
5
5
  import { useGetContainerStyles } from '../hooks/useGetContainerStyles';
6
6
  import { useGetFooterStyles } from '../hooks/useGetFooterStyles';
7
7
  import { useGetScrollContentStyles } from '../hooks/useGetScrollContentStyles';
8
+ import { useIsMobile } from '../hooks/useIsMobile';
8
9
 
9
10
  export interface LayoutProps {
10
11
  theme: Theme;
@@ -31,6 +32,7 @@ export const Layout = ({
31
32
  }: LayoutProps) => {
32
33
  const hasWelcomeMessage = isValidElement(welcomeMessage);
33
34
  const hasAnswerSuggestions = isValidElement(answerSuggestions);
35
+ const { isMobile } = useIsMobile();
34
36
  const { contentClasses, messageContainerClasses } = useGetScrollContentStyles();
35
37
  const { footerContainerClasses } = useGetFooterStyles();
36
38
  const { containerClasses, containerStyles } = useGetContainerStyles({
@@ -59,7 +61,7 @@ export const Layout = ({
59
61
  (!hasWelcomeMessage || (hasWelcomeMessage && theme === Theme.STANDARD)) &&
60
62
  !hasAnswerSuggestions &&
61
63
  'envive-tw-pb-4',
62
- 'envive-tw-pt-4',
64
+ isMobile ? 'envive-tw-pt-4' : 'envive-tw-pt-6',
63
65
  )}
64
66
  >
65
67
  {chatMessages}
@@ -6,12 +6,17 @@ export const useGetFooterStyles = () => {
6
6
  ? { boxShadow: '0px 2px 10px 0px rgba(0, 0, 0, 0.2)', borderWidth: '1px' }
7
7
  : null;
8
8
 
9
+ const footerClasses = isMobile
10
+ ? 'envive-tw-rounded-t-[var(--envive-global-custom-border-radius)]'
11
+ : 'envive-tw-rounded-[var(--envive-global-custom-border-radius)]';
12
+
9
13
  const footerContainerClasses = isMobile
10
- ? 'envive-tw-absolute envive-tw-bottom-[0] envive-tw-left-[0] envive-tw-right-[0]'
11
- : 'envive-tw-pb-5';
14
+ ? '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]';
12
16
 
13
17
  return {
14
18
  footerStyles,
19
+ footerClasses,
15
20
  footerContainerClasses,
16
21
  };
17
22
  };
@@ -0,0 +1,11 @@
1
+ import { useIsMobile } from './useIsMobile';
2
+
3
+ export const useGetMessagesStyles = () => {
4
+ const { isMobile } = useIsMobile();
5
+
6
+ const messageClasses = !isMobile ? '!envive-tw-gap-6' : '';
7
+
8
+ return {
9
+ messageClasses,
10
+ };
11
+ };
@@ -5,10 +5,14 @@ export const useGetScrollContentStyles = () => {
5
5
  const { isMobile } = useIsMobile();
6
6
  const contentClasses = classNames({
7
7
  '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': true,
8
+ 'envive-tw-mb-5': !isMobile,
8
9
  'envive-tw-pb-[131px]': isMobile,
10
+ 'envive-tw-pb-[150px]': !isMobile,
9
11
  });
10
12
 
11
- const messageContainerClasses = !isMobile ? 'envive-tw-pt-4' : null;
13
+ const messageContainerClasses = !isMobile
14
+ ? 'envive-tw-rounded-[var(--envive-global-custom-border-radius)] envive-tw-mt-4 envive-tw-overflow-hidden'
15
+ : 'envive-tw-rounded-[var(--envive-global-custom-border-radius)] envive-tw-mx-4 envive-tw-overflow-hidden';
12
16
 
13
17
  return {
14
18
  contentClasses,
@@ -21,7 +21,7 @@ export const MarkdownProcessor = ({
21
21
  a: createMarkdownLinkComponent({ onLinkClick }),
22
22
  p: createMarkdownParagraphComponent({ clampParagraphs, textColor, textVariant }),
23
23
  }),
24
- [clampParagraphs, textColor, textVariant],
24
+ [clampParagraphs, textColor, textVariant, onLinkClick],
25
25
  );
26
26
 
27
27
  return <ReactMarkdown components={components}>{processedContent}</ReactMarkdown>;
@@ -12,13 +12,13 @@ vi.mock('../../MarkdownProcessor/MarkdownProcessor', () => {
12
12
  const urlRegex = /(https?:\/\/[^\s]+)/g;
13
13
  return {
14
14
  MarkdownProcessor: function MockMarkdownProcessor({ content }: { content: string }) {
15
- const matches = [...content.matchAll(urlRegex)];
15
+ const matches = Array.from(content.matchAll(urlRegex));
16
16
  if (matches.length === 0) {
17
17
  return createElement('p', null, content);
18
18
  }
19
19
  const nodes: ReactNode[] = [];
20
20
  let lastIndex = 0;
21
- matches.forEach((m: RegExpMatchArray, i: number) => {
21
+ matches.forEach((m: RegExpMatchArray) => {
22
22
  const url = m[0];
23
23
  const index = m.index ?? 0;
24
24
  nodes.push(content.slice(lastIndex, index));
@@ -26,7 +26,7 @@ vi.mock('../../MarkdownProcessor/MarkdownProcessor', () => {
26
26
  createElement(
27
27
  'span',
28
28
  {
29
- key: i,
29
+ key: `link-${index}-${url}`,
30
30
  role: 'button',
31
31
  'aria-label': `${url} (opens in new tab)`,
32
32
  className: 'hover:envive-tw-underline',
@@ -1,10 +1,10 @@
1
1
  import classNames from 'classnames';
2
2
  import { Theme } from '../Tokens';
3
+ import { TypographyColor } from '../Typography/types';
3
4
  import { resolveTheme } from '../utils/resolveTheme';
4
5
  import { WidgetTextField } from '../WidgetTextField';
5
6
  import { ProductCardWidgetComponents } from './components';
6
7
  import { ProductCardProps } from './types';
7
- import { TypographyColor } from '../Typography/types';
8
8
 
9
9
  export const ProductCard = ({
10
10
  theme = Theme.GLOBAL_CUSTOM,
@@ -21,6 +21,12 @@ export const ProductCard = ({
21
21
  textTransition,
22
22
  loop,
23
23
  onSelect,
24
+ onDrag,
25
+ onHover,
26
+ onMouseDown,
27
+ onMouseUp,
28
+ onTouchStart,
29
+ onTouchEnd,
24
30
  onInputClick,
25
31
  }: ProductCardProps) => {
26
32
  const resolvedTheme = resolveTheme(theme);
@@ -44,6 +50,12 @@ export const ProductCard = ({
44
50
  prompts={prompts}
45
51
  promptButtonType={promptButtonType}
46
52
  onSelect={onSelect}
53
+ onDrag={onDrag}
54
+ onHover={onHover}
55
+ onMouseDown={onMouseDown}
56
+ onMouseUp={onMouseUp}
57
+ onTouchStart={onTouchStart}
58
+ onTouchEnd={onTouchEnd}
47
59
  />
48
60
  <WidgetTextField
49
61
  theme={resolvedTheme}
@@ -1,6 +1,6 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { render, screen } from '@testing-library/react';
3
+ import { fireEvent, render, screen } from '@testing-library/react';
4
4
  import { Theme } from '../../../../tokens/theme/theme';
5
5
  import { PromptButtonVariant } from '../../PromptButton';
6
6
  import { ProductCard } from '../ProductCard';
@@ -73,6 +73,22 @@ describe('ProductCard', () => {
73
73
  });
74
74
 
75
75
  describe('Callback handlers', () => {
76
+ it('should call onSelect when a prompt is clicked', () => {
77
+ const handleSelect = vi.fn();
78
+ render(
79
+ <ProductCard
80
+ {...defaultProps}
81
+ onSelect={handleSelect}
82
+ />,
83
+ );
84
+
85
+ const promptButton = screen.getByText('Prompt 1');
86
+ promptButton.click();
87
+
88
+ expect(handleSelect).toHaveBeenCalledTimes(1);
89
+ expect(handleSelect).toHaveBeenCalledWith('Prompt 1');
90
+ });
91
+
76
92
  it('should call onInputClick when input is clicked', () => {
77
93
  const handleInputClick = vi.fn();
78
94
  render(
@@ -88,6 +104,62 @@ describe('ProductCard', () => {
88
104
  expect(handleInputClick).toHaveBeenCalledTimes(1);
89
105
  });
90
106
 
107
+ it('should call onHover when a prompt is hovered', () => {
108
+ const handleHover = vi.fn();
109
+ render(
110
+ <ProductCard
111
+ {...defaultProps}
112
+ onHover={handleHover}
113
+ />,
114
+ );
115
+
116
+ const promptButton = screen.getByText('Prompt 1');
117
+ fireEvent.mouseOver(promptButton);
118
+
119
+ expect(handleHover).toHaveBeenCalledTimes(1);
120
+ expect(handleHover).toHaveBeenCalledWith('Prompt 1');
121
+ });
122
+
123
+ it('should call onMouseDown and onMouseUp when interacting with a prompt via mouse', () => {
124
+ const handleMouseDown = vi.fn();
125
+ const handleMouseUp = vi.fn();
126
+ render(
127
+ <ProductCard
128
+ {...defaultProps}
129
+ onMouseDown={handleMouseDown}
130
+ onMouseUp={handleMouseUp}
131
+ />,
132
+ );
133
+
134
+ const promptButton = screen.getByText('Prompt 1');
135
+ fireEvent.mouseDown(promptButton);
136
+ fireEvent.mouseUp(promptButton);
137
+
138
+ expect(handleMouseDown).toHaveBeenCalledTimes(1);
139
+ expect(handleMouseDown).toHaveBeenCalledWith('Prompt 1');
140
+ expect(handleMouseUp).toHaveBeenCalledTimes(1);
141
+ });
142
+
143
+ it('should call onTouchStart and onTouchEnd when interacting with a prompt via touch', () => {
144
+ const handleTouchStart = vi.fn();
145
+ const handleTouchEnd = vi.fn();
146
+ render(
147
+ <ProductCard
148
+ {...defaultProps}
149
+ onTouchStart={handleTouchStart}
150
+ onTouchEnd={handleTouchEnd}
151
+ />,
152
+ );
153
+
154
+ const promptButton = screen.getByText('Prompt 1');
155
+ fireEvent.touchStart(promptButton);
156
+ fireEvent.touchEnd(promptButton);
157
+
158
+ expect(handleTouchStart).toHaveBeenCalledTimes(1);
159
+ expect(handleTouchStart).toHaveBeenCalledWith('Prompt 1');
160
+ expect(handleTouchEnd).toHaveBeenCalledTimes(1);
161
+ });
162
+
91
163
  it('should render prompts in carousel', () => {
92
164
  render(
93
165
  <ProductCard
@@ -9,18 +9,30 @@ export const Carousel = ({
9
9
  prompts,
10
10
  promptButtonType,
11
11
  onSelect,
12
+ onDrag,
13
+ onHover,
14
+ onMouseDown,
15
+ onMouseUp,
16
+ onTouchStart,
17
+ onTouchEnd,
12
18
  }: CarouselProps) => {
13
19
  const resolvedTheme = resolveTheme(theme);
14
20
  const { carouselClasses } = useGetCarouselProperties();
15
21
  return (
16
22
  <PromptCarousel
17
23
  className={carouselClasses}
18
- handleButtonClick={onSelect}
19
24
  id={carouselId}
20
25
  promptCarouselRows={PromptCarouselRows.ALWAYS_TWO}
21
26
  theme={resolvedTheme}
22
27
  promptButtonType={promptButtonType}
23
28
  promptButtonTexts={prompts}
29
+ handleButtonClick={onSelect}
30
+ handleButtonDrag={onDrag}
31
+ handleButtonHover={onHover}
32
+ handleButtonMouseDown={onMouseDown}
33
+ handleButtonMouseUp={onMouseUp}
34
+ handleButtonTouchStart={onTouchStart}
35
+ handleButtonTouchEnd={onTouchEnd}
24
36
  />
25
37
  );
26
38
  };
@@ -18,6 +18,40 @@ export interface CarouselProps {
18
18
  * Callback function invoked when a prompt is selected.
19
19
  */
20
20
  onSelect: (prompt: string) => void;
21
+ /**
22
+ * Callback invoked when the user drags/swipes the carousel.
23
+ * Typically used for analytics or to pause other interactions while dragging.
24
+ */
25
+ onDrag?: () => void;
26
+ /**
27
+ * Callback invoked when the user hovers a specific prompt item.
28
+ *
29
+ * @param prompt The prompt currently being hovered.
30
+ */
31
+ onHover?: (prompt: string) => void;
32
+ /**
33
+ * Callback invoked when the user presses down on a specific prompt item.
34
+ * Useful for distinguishing a click/tap from a drag and for interaction tracking.
35
+ *
36
+ * @param prompt The prompt the user pressed down on.
37
+ */
38
+ onMouseDown?: (prompt: string) => void;
39
+ /**
40
+ * Callback invoked when the user releases the mouse button after interacting
41
+ * with the carousel or a prompt item.
42
+ */
43
+ onMouseUp?: () => void;
44
+ /**
45
+ * Callback invoked when the user starts a touch gesture on a specific prompt.
46
+ *
47
+ * @param prompt The prompt the user touched.
48
+ */
49
+ onTouchStart?: (prompt: string) => void;
50
+ /**
51
+ * Callback invoked when the user ends a touch gesture after interacting with
52
+ * the carousel or a prompt item.
53
+ */
54
+ onTouchEnd?: () => void;
21
55
  }
22
56
 
23
57
  /**
@@ -17,7 +17,9 @@ type PromptButtonsCarouselProps = {
17
17
  handlePromptButtonTouchEnd?: () => void;
18
18
  };
19
19
 
20
- function distributeInThreeRows<T>(items: T[]): [T[], T[], T[]] {
20
+ function distributeInThreeRows<T>(
21
+ items: [number, T][],
22
+ ): [[number, T][], [number, T][], [number, T][]] {
21
23
  const n = items.length;
22
24
  const perRow = [1, 1, 1];
23
25
  let remaining = Math.max(0, n - 3);
@@ -55,13 +57,13 @@ export const PromptButtonsCarousel = ({
55
57
  ? ['Loading 1...', 'Loading 2...', 'Loading 3...']
56
58
  : promptButtonsTexts || [];
57
59
 
58
- const renderButton = (text: string) => (
60
+ const renderButton = ([index, text]: [number, string]) => (
59
61
  <div
60
- key={text}
62
+ key={`${text}-${index}`}
61
63
  className="envive-tw-flex-shrink-0"
62
64
  >
63
65
  <PromptButton
64
- id={`prompt-button-${text}`}
66
+ id={`prompt-button-${index}`}
65
67
  isLoading={isLoading}
66
68
  label={text}
67
69
  variant={promptButtonType}
@@ -77,18 +79,19 @@ export const PromptButtonsCarousel = ({
77
79
  </div>
78
80
  );
79
81
 
82
+ const indexedTexts: [number, string][] = Array.from(promptButtonsTextsFinal.entries());
83
+
80
84
  if (isMobile) {
81
- const [row0, row1, row2] = distributeInThreeRows(promptButtonsTextsFinal);
82
- const rows = [row0, row1, row2].filter(row => row.length > 0);
85
+ const rows = distributeInThreeRows(indexedTexts).filter(row => row.length > 0);
83
86
  return (
84
87
  <Stack
85
88
  direction="column"
86
89
  gap="4"
87
90
  className="envive-tw-w-full"
88
91
  >
89
- {rows.map(row => (
92
+ {rows.map((row, rowIndex) => (
90
93
  <Stack
91
- key={row.join('-')}
94
+ key={rowIndex}
92
95
  direction="row"
93
96
  gap="4"
94
97
  className="envive-tw-w-full envive-tw-flex-nowrap"
@@ -100,5 +103,5 @@ export const PromptButtonsCarousel = ({
100
103
  );
101
104
  }
102
105
 
103
- return <>{promptButtonsTextsFinal.map(renderButton)}</>;
106
+ return <>{indexedTexts.map(renderButton)}</>;
104
107
  };
@@ -1,16 +1,16 @@
1
1
  import { useCallback, useMemo } from 'react';
2
2
  import { AnimatedText } from '../AnimatedText';
3
- import { Typography, TypographyColor } from '../Typography';
3
+ import { PromptButtonVariant } from '../PromptButton/types';
4
4
  import { PromptCarousel, PromptCarouselRows } from '../PromptCarousel';
5
+ import { Stack } from '../Stack';
6
+ import { Theme } from '../Tokens';
7
+ import { Typography, TypographyColor } from '../Typography';
8
+ import { useAnimatedTextMinHeight } from '../utils/useAnimatedTextMinHeight';
5
9
  import { WidgetTextField } from '../WidgetTextField';
6
10
  import { WidgetWrapperVariant } from '../WidgetWrapper';
7
- import { PromptButtonVariant } from '../PromptButton/types';
8
- import { Theme } from '../Tokens';
9
11
  import { WidgetWrapperWithTitle } from '../WidgetWrapperWithTitle';
10
- import type { TypingAnimationProps } from './types';
11
12
  import { useGetTypographyVariant } from './hooks/useGetTypographyVariant';
12
- import { useAnimatedTextMinHeight } from '../utils/useAnimatedTextMinHeight';
13
- import { Stack } from '../Stack';
13
+ import type { TypingAnimationProps } from './types';
14
14
 
15
15
  export const TypingAnimation = ({
16
16
  baseProps,
@@ -41,7 +41,16 @@ export const TypingAnimation = ({
41
41
  isLoading = false,
42
42
  } = widgetStyleProps || {};
43
43
 
44
- const { handleButtonClick, handleTextFieldClick } = widgetEventProps || {};
44
+ const {
45
+ handleButtonClick,
46
+ handleButtonDrag,
47
+ handleButtonHover,
48
+ handleButtonMouseDown,
49
+ handleButtonMouseUp,
50
+ handleButtonTouchStart,
51
+ handleButtonTouchEnd,
52
+ handleTextFieldClick,
53
+ } = widgetEventProps || {};
45
54
  const typographyVariant = useGetTypographyVariant(theme);
46
55
  const { measuringRef, minHeight } = useAnimatedTextMinHeight(animatedTextSequence ?? []);
47
56
 
@@ -153,6 +162,12 @@ export const TypingAnimation = ({
153
162
  promptButtonType={promptButtonType}
154
163
  promptCarouselRows={promptCarouselRows}
155
164
  handleButtonClick={handleButtonClick}
165
+ handleButtonDrag={handleButtonDrag}
166
+ handleButtonHover={handleButtonHover}
167
+ handleButtonMouseDown={handleButtonMouseDown}
168
+ handleButtonMouseUp={handleButtonMouseUp}
169
+ handleButtonTouchStart={handleButtonTouchStart}
170
+ handleButtonTouchEnd={handleButtonTouchEnd}
156
171
  promptButtonTexts={promptButtonTexts}
157
172
  />
158
173
  )}
@@ -133,6 +133,35 @@ export type WidgetEventProps = {
133
133
  */
134
134
  handleButtonClick?: (text: string) => void;
135
135
 
136
+ /**
137
+ * Callback function invoked when the prompt buttons carousel is dragged.
138
+ * Useful for tracking drag-to-scroll interactions.
139
+ */
140
+ handleButtonDrag?: () => void;
141
+ /**
142
+ * Callback function invoked when the user hovers over a prompt button.
143
+ * Receives the hovered button's text as a parameter.
144
+ */
145
+ handleButtonHover?: (text: string) => void;
146
+ /**
147
+ * Callback function invoked when the mouse button is pressed down on a prompt button.
148
+ * Receives the pressed button's text as a parameter.
149
+ */
150
+ handleButtonMouseDown?: (text: string) => void;
151
+ /**
152
+ * Callback function invoked when the mouse button is released after pressing a prompt button.
153
+ */
154
+ handleButtonMouseUp?: () => void;
155
+ /**
156
+ * Callback function invoked when a touch interaction starts on a prompt button.
157
+ * Receives the touched button's text as a parameter.
158
+ */
159
+ handleButtonTouchStart?: (text: string) => void;
160
+ /**
161
+ * Callback function invoked when a touch interaction ends after touching a prompt button.
162
+ */
163
+ handleButtonTouchEnd?: () => void;
164
+
136
165
  /**
137
166
  * Callback function invoked when the TextField is clicked.
138
167
  */