@envive-ai/react-toolkit-v3 0.3.6 → 0.3.7

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 (157) hide show
  1. package/dist/AnimatedText/AnimatedText.d.cts +3 -3
  2. package/dist/AnimatedText/AnimatedText.d.ts +3 -3
  3. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.cts +2 -2
  4. package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.ts +2 -2
  5. package/dist/Carousel/Carousel.d.cts +2 -2
  6. package/dist/Carousel/Carousel.d.ts +2 -2
  7. package/dist/ChatFooter/ChatFooter.d.cts +2 -2
  8. package/dist/ChatFooter/ChatFooter.d.ts +2 -2
  9. package/dist/ChatFooter/components/index.d.cts +5 -5
  10. package/dist/ChatFooter/components/index.d.ts +5 -5
  11. package/dist/ChatFooter/hooks/useGetContainerProperties.cjs +2 -2
  12. package/dist/ChatFooter/hooks/useGetContainerProperties.js +2 -2
  13. package/dist/ChatHeader/ChatHeader.cjs +3 -2
  14. package/dist/ChatHeader/ChatHeader.d.cts +3 -2
  15. package/dist/ChatHeader/ChatHeader.d.ts +3 -2
  16. package/dist/ChatHeader/ChatHeader.js +3 -2
  17. package/dist/ChatHeader/components/Handle.cjs +27 -1
  18. package/dist/ChatHeader/components/Handle.js +27 -1
  19. package/dist/ChatHeader/types/index.d.cts +2 -0
  20. package/dist/ChatHeader/types/index.d.ts +2 -0
  21. package/dist/ChatPreview/ChatPreview.d.cts +2 -2
  22. package/dist/ChatPreview/ChatPreview.d.ts +2 -2
  23. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
  24. package/dist/ChatPreviewComparison/ChatPreviewComparison.d.ts +2 -2
  25. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
  26. package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
  27. package/dist/Container/Container.d.cts +176 -176
  28. package/dist/Container/Container.d.ts +176 -176
  29. package/dist/DesignTokens/DesignTokensComponent.d.cts +2 -2
  30. package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
  31. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
  32. package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.ts +2 -2
  33. package/dist/FloatingButton/FloatingButton.cjs +2 -1
  34. package/dist/FloatingButton/FloatingButton.d.cts +4 -3
  35. package/dist/FloatingButton/FloatingButton.d.ts +4 -3
  36. package/dist/FloatingButton/FloatingButton.js +2 -1
  37. package/dist/FloatingButton/components/Container.cjs +2 -2
  38. package/dist/FloatingButton/components/Container.js +2 -2
  39. package/dist/FloatingButton/hooks/useGetContainerProperties.cjs +2 -2
  40. package/dist/FloatingButton/hooks/useGetContainerProperties.js +2 -2
  41. package/dist/FloatingButton/types/types.cjs +4 -4
  42. package/dist/FloatingButton/types/types.d.cts +8 -4
  43. package/dist/FloatingButton/types/types.d.ts +8 -4
  44. package/dist/FloatingButton/types/types.js +4 -4
  45. package/dist/FloatingChat/FloatingChat.cjs +106 -39
  46. package/dist/FloatingChat/FloatingChat.d.cts +3 -2
  47. package/dist/FloatingChat/FloatingChat.d.ts +3 -2
  48. package/dist/FloatingChat/FloatingChat.js +108 -41
  49. package/dist/FloatingChat/components/AgentMessage.cjs +3 -2
  50. package/dist/FloatingChat/components/AgentMessage.js +3 -2
  51. package/dist/FloatingChat/components/ChatMessages.cjs +59 -41
  52. package/dist/FloatingChat/components/ChatMessages.js +58 -40
  53. package/dist/FloatingChat/components/Layout.cjs +2 -2
  54. package/dist/FloatingChat/components/Layout.js +2 -2
  55. package/dist/FloatingChat/components/ModalSheet.cjs +184 -0
  56. package/dist/FloatingChat/components/ModalSheet.js +182 -0
  57. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.cjs +3 -2
  58. package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.js +3 -2
  59. package/dist/FloatingChat/components/index.cjs +1 -0
  60. package/dist/FloatingChat/components/index.js +1 -0
  61. package/dist/FloatingChat/hooks/useChatSuggestions.cjs +25 -0
  62. package/dist/FloatingChat/hooks/useChatSuggestions.js +24 -0
  63. package/dist/FloatingChat/hooks/useFilteredChatMessages.cjs +24 -0
  64. package/dist/FloatingChat/hooks/useFilteredChatMessages.js +23 -0
  65. package/dist/FloatingChat/hooks/usePreventScroll.cjs +117 -0
  66. package/dist/FloatingChat/hooks/usePreventScroll.js +116 -0
  67. package/dist/FloatingChat/hooks/useSnapCalculator.cjs +37 -0
  68. package/dist/FloatingChat/hooks/useSnapCalculator.js +35 -0
  69. package/dist/FloatingChat/hooks/useSnapControl.cjs +82 -0
  70. package/dist/FloatingChat/hooks/useSnapControl.js +81 -0
  71. package/dist/FloatingChat/hooks/useSnapSetup.cjs +59 -0
  72. package/dist/FloatingChat/hooks/useSnapSetup.js +58 -0
  73. package/dist/FloatingChat/types/types.d.cts +4 -0
  74. package/dist/FloatingChat/types/types.d.ts +4 -0
  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.d.cts +2 -2
  80. package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
  81. package/dist/PromptButton/PromptButton.d.cts +2 -2
  82. package/dist/PromptButton/PromptButton.d.ts +2 -2
  83. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
  84. package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
  85. package/dist/PromptCarousel/PromptCarousel.cjs +0 -1
  86. package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
  87. package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
  88. package/dist/PromptCarousel/PromptCarousel.js +0 -1
  89. package/dist/ReviewCard/ReviewCard.d.cts +2 -2
  90. package/dist/ReviewCard/ReviewCard.d.ts +2 -2
  91. package/dist/ReviewCard/components/index.d.cts +6 -6
  92. package/dist/ReviewCard/components/index.d.ts +6 -6
  93. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.cts +2 -2
  94. package/dist/SalesAgentProductCard/SalesAgentProductCard.d.ts +2 -2
  95. package/dist/SalesAgentProductCard/components/index.d.cts +2 -2
  96. package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
  97. package/dist/SocialProof/SocialProof.cjs +9 -2
  98. package/dist/SocialProof/SocialProof.d.cts +2 -2
  99. package/dist/SocialProof/SocialProof.d.ts +2 -2
  100. package/dist/SocialProof/SocialProof.js +9 -2
  101. package/dist/SocialProof/hooks/index.cjs +1 -0
  102. package/dist/SocialProof/hooks/index.js +3 -0
  103. package/dist/SocialProof/hooks/useSocialProofCount.cjs +48 -0
  104. package/dist/SocialProof/hooks/useSocialProofCount.d.cts +15 -0
  105. package/dist/SocialProof/hooks/useSocialProofCount.d.ts +15 -0
  106. package/dist/SocialProof/hooks/useSocialProofCount.js +46 -0
  107. package/dist/SocialProof/index.cjs +5 -1
  108. package/dist/SocialProof/index.d.cts +3 -2
  109. package/dist/SocialProof/index.d.ts +3 -2
  110. package/dist/SocialProof/index.js +4 -2
  111. package/dist/SocialProof/types/types.cjs +8 -1
  112. package/dist/SocialProof/types/types.d.cts +16 -6
  113. package/dist/SocialProof/types/types.d.ts +16 -6
  114. package/dist/SocialProof/types/types.js +7 -1
  115. package/dist/Stack/Stack.d.cts +2 -2
  116. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
  117. package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
  118. package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
  119. package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
  120. package/dist/Typography/Typography.d.cts +4 -4
  121. package/dist/Typography/Typography.d.ts +4 -4
  122. package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
  123. package/dist/WidgetTextField/WidgetTextField.d.ts +2 -2
  124. package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
  125. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.cts +2 -2
  126. package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.ts +2 -2
  127. package/dist/styles.css +1 -1
  128. package/package.json +1 -1
  129. package/src/components/ChatFooter/hooks/useGetContainerProperties.ts +2 -2
  130. package/src/components/ChatHeader/ChatHeader.tsx +2 -0
  131. package/src/components/ChatHeader/components/Handle.tsx +29 -1
  132. package/src/components/ChatHeader/types/index.ts +3 -0
  133. package/src/components/FloatingButton/FloatingButton.tsx +2 -0
  134. package/src/components/FloatingButton/components/Container.tsx +3 -0
  135. package/src/components/FloatingButton/hooks/useGetContainerProperties.ts +2 -1
  136. package/src/components/FloatingButton/types/types.ts +9 -4
  137. package/src/components/FloatingChat/FloatingChat.tsx +160 -59
  138. package/src/components/FloatingChat/components/AgentMessage.tsx +3 -0
  139. package/src/components/FloatingChat/components/ChatMessages.tsx +25 -0
  140. package/src/components/FloatingChat/components/Layout.tsx +2 -2
  141. package/src/components/FloatingChat/components/ModalSheet.tsx +288 -0
  142. package/src/components/FloatingChat/components/SalesAgentProductCardsCarousel.tsx +7 -2
  143. package/src/components/FloatingChat/components/index.ts +3 -0
  144. package/src/components/FloatingChat/hooks/useChatSuggestions.ts +49 -0
  145. package/src/components/FloatingChat/hooks/useFilteredChatMessages.ts +43 -0
  146. package/src/components/FloatingChat/hooks/usePreventScroll.ts +207 -0
  147. package/src/components/FloatingChat/hooks/useSnapCalculator.ts +41 -0
  148. package/src/components/FloatingChat/hooks/useSnapControl.ts +131 -0
  149. package/src/components/FloatingChat/hooks/useSnapSetup.ts +106 -0
  150. package/src/components/FloatingChat/types/types.ts +4 -0
  151. package/src/components/PromptCarousel/PromptCarousel.tsx +0 -1
  152. package/src/components/SocialProof/SocialProof.tsx +8 -2
  153. package/src/components/SocialProof/__tests__/SocialProof.test.tsx +66 -17
  154. package/src/components/SocialProof/hooks/index.ts +2 -0
  155. package/src/components/SocialProof/hooks/useSocialProofCount.ts +67 -0
  156. package/src/components/SocialProof/index.ts +2 -1
  157. package/src/components/SocialProof/types/types.ts +18 -6
@@ -1,21 +1,25 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useRef, useState } from 'react';
2
2
 
3
+ import { motion } from 'framer-motion';
3
4
  import { ColorNames } from '../../models/colorsConfig';
4
5
  import { Theme } from '../../../tokens/theme/theme';
5
6
 
6
- import { FloatingChatComponents } from './components';
7
+ import { FloatingChatComponents, ModalSheet } from './components';
7
8
  import { useScrollToBottom } from './hooks/useScrollToBottom';
9
+ import { useSnapSetup } from './hooks/useSnapSetup';
10
+ import { useChatSuggestions } from './hooks/useChatSuggestions';
8
11
  import { ChatHeader } from '../ChatHeader';
9
- import { ChatHeaderOptions, ChatHeaderVariant } from '../ChatHeader/types';
12
+ import { ChatHeaderOptions, ChatHeaderVariant, HandleState } from '../ChatHeader/types';
10
13
  import { WelcomeMessage } from '../WelcomeMessage/WelcomeMessage';
11
14
  import { SparkleIconColor } from '../WelcomeMessage/types/types';
12
- import { Stack } from '../Stack';
13
15
  import { ChatFooter } from '../ChatFooter';
14
16
  import { PromptCarousel, PromptCarouselRows } from '../PromptCarousel';
15
17
  import { PromptButtonVariant } from '../PromptButton';
16
18
 
17
19
  import { resolveTheme } from '../utils/resolveTheme';
18
20
  import { FloatingChatProps } from './types/types';
21
+ import { useFilteredChatMessages } from './hooks/useFilteredChatMessages';
22
+ import { Unit } from './hooks/useSnapCalculator';
19
23
 
20
24
  export const FloatingChat = ({
21
25
  id,
@@ -27,6 +31,7 @@ export const FloatingChat = ({
27
31
  floatingChatConfig,
28
32
  lookAndFeelConfig,
29
33
  isCXButtonSwitchEnabled,
34
+ isFloatingChatOpen,
30
35
  onClose,
31
36
  onToggleCXButton,
32
37
  }: FloatingChatProps) => {
@@ -41,7 +46,8 @@ export const FloatingChat = ({
41
46
  headerMode,
42
47
  welcomeMessageIconColor,
43
48
  showVerifiedBuyer,
44
- userQueryInputEnabled,
49
+ // Default this to true to avoid needing it in the default config
50
+ userQueryInputEnabled = true,
45
51
  showEnviveLogo,
46
52
  ignoreFirstModelResponse,
47
53
  neverShowSingleProductCards,
@@ -61,24 +67,17 @@ export const FloatingChat = ({
61
67
  onTypedMessageSubmitted,
62
68
  } = salesAgentData;
63
69
 
64
- const [answerSuggestions, setAnswerSuggestions] = useState<string[]>([]);
65
- const [generalSuggestions, setGeneralSuggestions] = useState<string[]>([]);
66
-
67
- const showAnswerSuggestions =
68
- answerSuggestions.length > 0 && !isPendingResponse && !isResponseStreaming;
69
-
70
- useEffect(() => {
71
- if (suggestions.length > 0) {
72
- setAnswerSuggestions(
73
- suggestions.filter(suggestion => suggestion.isAnswer).map(suggestion => suggestion.content),
74
- );
75
- setGeneralSuggestions(
76
- suggestions
77
- .filter(suggestion => !suggestion.isAnswer)
78
- .map(suggestion => suggestion.content),
79
- );
80
- }
81
- }, [suggestions]);
70
+ const {
71
+ answerSuggestions,
72
+ generalSuggestions,
73
+ showAnswerSuggestions,
74
+ setAnswerSuggestions,
75
+ setGeneralSuggestions,
76
+ } = useChatSuggestions({
77
+ suggestions,
78
+ isPendingResponse,
79
+ isResponseStreaming,
80
+ });
82
81
 
83
82
  const handleToggleCXButton = (item: ChatHeaderOptions) => {
84
83
  if (item === ChatHeaderOptions.LIVE_SUPPORT) {
@@ -99,6 +98,32 @@ export const FloatingChat = ({
99
98
  scrollThreshold: 30,
100
99
  });
101
100
 
101
+ const {
102
+ modalSheetControl,
103
+ maxSwipeableViewHeight,
104
+ isOpen,
105
+ isMobile,
106
+ snaps,
107
+ initialSnap,
108
+ shouldAutoExpand,
109
+ currentSnapPercentage,
110
+ handleSnapChange,
111
+ animationKey,
112
+ mobileHeaderHeight,
113
+ shouldShowHeader,
114
+ shouldShowScrolled,
115
+ isFullView,
116
+ isPartialView,
117
+ } = useSnapSetup({ isFloatingChatOpen });
118
+
119
+ const { filteredMessages, hasFilteredMessages, handlePreviousDiscussions } =
120
+ useFilteredChatMessages({
121
+ messages,
122
+ isMobile,
123
+ currentSnapPercentage,
124
+ modalSheetControl,
125
+ });
126
+
102
127
  const header = (
103
128
  <ChatHeader
104
129
  logoDark={chatHeaderLogoDarkSrc}
@@ -116,43 +141,76 @@ export const FloatingChat = ({
116
141
  />
117
142
  );
118
143
 
119
- const footer = (
120
- <Stack
121
- direction="column"
122
- gap={4}
144
+ const mobileHeader = (
145
+ <motion.div
146
+ style={{
147
+ maxHeight: mobileHeaderHeight,
148
+ overflow: 'hidden',
149
+ }}
123
150
  >
124
- <ChatFooter
151
+ <ChatHeader
152
+ logoDark={chatHeaderLogoDarkSrc}
153
+ logoLight={chatHeaderLogoLightSrc}
125
154
  theme={finalTheme}
126
- isScrolled={false}
127
- onChange={setQuery}
128
- onSubmit={() => {
129
- onTypedMessageSubmitted({ query });
130
- setAnswerSuggestions([]);
131
- setGeneralSuggestions([]);
132
- }}
133
- textFieldPlaceholderText={chatFooterTextFieldPlaceholderText}
134
- promptSuggestions={
135
- isPendingResponse || isResponseStreaming || generalSuggestions.length === 0
136
- ? ['Loading suggestions 1...', 'Loading suggestions 2...'] // This is strings will not be shown to the user
137
- : generalSuggestions
138
- }
139
- handleButtonClick={buttonText => {
140
- const suggestion = suggestions.find(s => s.content === buttonText && !s.isAnswer);
141
- if (suggestion) {
142
- onSuggestionClicked(suggestion);
143
- }
144
-
145
- setAnswerSuggestions([]);
146
- setGeneralSuggestions([]);
147
- }}
148
- disabled={isPendingResponse || isResponseStreaming}
149
- disabledInput={!userQueryInputEnabled}
150
- isLoadingPromptSuggestions={
151
- isPendingResponse || isResponseStreaming || generalSuggestions.length === 0
152
- }
153
- hideEnviveWatermark={!showEnviveLogo}
155
+ variant={headerMode as ChatHeaderVariant}
156
+ headerBgColor={headerBackgroundColor as ColorNames}
157
+ showLogo
158
+ showCXToggle={isCXButtonSwitchEnabled}
159
+ centralizeCXToggle
160
+ selectedOption={ChatHeaderOptions.ASSISTANT}
161
+ onToggle={handleToggleCXButton}
162
+ onClose={onClose}
154
163
  />
155
- </Stack>
164
+ </motion.div>
165
+ );
166
+
167
+ const handler = (
168
+ <ChatHeader
169
+ theme={finalTheme}
170
+ variant={headerMode as ChatHeaderVariant}
171
+ headerBgColor={headerBackgroundColor as ColorNames}
172
+ showHandle
173
+ handleState={isFullView ? HandleState.OPEN : HandleState.CLOSED}
174
+ handleAnimationKey={animationKey}
175
+ showMainContent={false}
176
+ selectedOption={ChatHeaderOptions.ASSISTANT}
177
+ onToggle={handleToggleCXButton}
178
+ onClose={onClose}
179
+ />
180
+ );
181
+
182
+ const footer = (
183
+ <ChatFooter
184
+ theme={finalTheme}
185
+ isScrolled={isMobile ? shouldShowScrolled : false}
186
+ onChange={setQuery}
187
+ onSubmit={() => {
188
+ onTypedMessageSubmitted({ query, userTyped: true });
189
+ setAnswerSuggestions([]);
190
+ setGeneralSuggestions([]);
191
+ }}
192
+ textFieldPlaceholderText={chatFooterTextFieldPlaceholderText}
193
+ promptSuggestions={
194
+ isPendingResponse || isResponseStreaming || generalSuggestions.length === 0
195
+ ? ['Loading suggestions 1...', 'Loading suggestions 2...'] // This is strings will not be shown to the user
196
+ : generalSuggestions
197
+ }
198
+ handleButtonClick={buttonText => {
199
+ const suggestion = suggestions.find(s => s.content === buttonText && !s.isAnswer);
200
+ if (suggestion) {
201
+ onSuggestionClicked(suggestion);
202
+ }
203
+
204
+ setAnswerSuggestions([]);
205
+ setGeneralSuggestions([]);
206
+ }}
207
+ disabled={isPendingResponse || isResponseStreaming}
208
+ disabledInput={!userQueryInputEnabled}
209
+ isLoadingPromptSuggestions={
210
+ isPendingResponse || isResponseStreaming || generalSuggestions.length === 0
211
+ }
212
+ hideEnviveWatermark={!showEnviveLogo}
213
+ />
156
214
  );
157
215
 
158
216
  const welcomeMessage = (
@@ -170,11 +228,14 @@ export const FloatingChat = ({
170
228
  ref={chatMessagesRef}
171
229
  isLoading={isPendingResponse && !isResponseStreaming}
172
230
  agentName={agentName}
173
- messages={messages}
231
+ messages={filteredMessages}
232
+ hasFilteredMessages={hasFilteredMessages}
233
+ handlePreviousDiscussions={handlePreviousDiscussions}
174
234
  isResponseStreaming={isResponseStreaming}
175
235
  ignoreFirstModelResponse={ignoreFirstModelResponse}
176
236
  neverShowSingleProductCards={neverShowSingleProductCards}
177
237
  showVerifiedBuyer={showVerifiedBuyer}
238
+ isPartialView={isPartialView}
178
239
  />
179
240
  );
180
241
 
@@ -196,7 +257,7 @@ export const FloatingChat = ({
196
257
  />
197
258
  );
198
259
 
199
- return (
260
+ const desktopLayout = (
200
261
  <FloatingChatComponents.Layout
201
262
  id={id}
202
263
  className={className}
@@ -215,4 +276,44 @@ export const FloatingChat = ({
215
276
  }
216
277
  />
217
278
  );
279
+
280
+ const mobileLayout = (
281
+ <FloatingChatComponents.Layout
282
+ id={id}
283
+ className={className}
284
+ style={style}
285
+ testId={testId}
286
+ theme={finalTheme}
287
+ header={shouldShowHeader ? mobileHeader : undefined}
288
+ footer={footer}
289
+ welcomeMessage={welcomeMessage}
290
+ chatMessages={chatMessages}
291
+ answerSuggestions={showAnswerSuggestions ? answerSuggestionsComponent : undefined}
292
+ scrollToBottomButton={
293
+ showScrollButton ? (
294
+ <FloatingChatComponents.ScrollToBottomButton onClick={scrollToBottom} />
295
+ ) : undefined
296
+ }
297
+ />
298
+ );
299
+
300
+ return isMobile ? (
301
+ <ModalSheet
302
+ animationKey={animationKey}
303
+ open={isOpen}
304
+ height={maxSwipeableViewHeight}
305
+ handler={handler}
306
+ unit={Unit.PERCENT}
307
+ snaps={snaps}
308
+ initSnap={initialSnap}
309
+ onSnap={handleSnapChange}
310
+ onClose={onClose}
311
+ controlRef={modalSheetControl}
312
+ disableDrag={shouldAutoExpand}
313
+ >
314
+ {mobileLayout}
315
+ </ModalSheet>
316
+ ) : (
317
+ desktopLayout
318
+ );
218
319
  };
@@ -23,6 +23,7 @@ export interface AgentMessageProps {
23
23
  isResponseStreaming?: boolean;
24
24
  neverShowSingleProductCards?: boolean;
25
25
  showVerifiedBuyer?: boolean;
26
+ isPartialView?: boolean;
26
27
  }
27
28
 
28
29
  export const AgentMessage = ({
@@ -34,6 +35,7 @@ export const AgentMessage = ({
34
35
  isResponseStreaming = false,
35
36
  neverShowSingleProductCards = false,
36
37
  showVerifiedBuyer = false,
38
+ isPartialView,
37
39
  }: AgentMessageProps) => {
38
40
  const finalTheme = resolveTheme(theme);
39
41
 
@@ -86,6 +88,7 @@ export const AgentMessage = ({
86
88
  products={products}
87
89
  numberOfProducts={products.length}
88
90
  theme={finalTheme}
91
+ isPartialView={isPartialView}
89
92
  />
90
93
  );
91
94
  }
@@ -9,17 +9,22 @@ import { Message as MessageComponent, MessageVariant } from '../../Message';
9
9
  import { MessageDivider } from './MessageDivider';
10
10
  import { Theme } from '../../../../tokens/theme/theme';
11
11
  import { resolveTheme } from '../../utils/resolveTheme';
12
+ import { Typography, TypographyVariant } from '../../Typography';
13
+ import { TypographyColor } from '../../Typography/types';
12
14
 
13
15
  export interface ChatMessagesProps {
14
16
  theme: Theme;
15
17
  className?: string;
16
18
  agentName: string;
17
19
  messages: Message[][];
20
+ hasFilteredMessages: boolean;
21
+ handlePreviousDiscussions: () => void;
18
22
  isLoading: boolean;
19
23
  isResponseStreaming: boolean;
20
24
  ignoreFirstModelResponse?: boolean;
21
25
  neverShowSingleProductCards?: boolean;
22
26
  showVerifiedBuyer?: boolean;
27
+ isPartialView?: boolean;
23
28
  }
24
29
 
25
30
  export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
@@ -29,11 +34,14 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
29
34
  className,
30
35
  agentName,
31
36
  messages,
37
+ hasFilteredMessages,
38
+ handlePreviousDiscussions,
32
39
  isLoading,
33
40
  isResponseStreaming,
34
41
  ignoreFirstModelResponse,
35
42
  neverShowSingleProductCards,
36
43
  showVerifiedBuyer,
44
+ isPartialView,
37
45
  },
38
46
  ref,
39
47
  ) => {
@@ -45,6 +53,22 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
45
53
  className={classNames('envive-tw-px-4', className)}
46
54
  gap={4}
47
55
  >
56
+ {hasFilteredMessages && (
57
+ <button
58
+ type="button"
59
+ className="envive-tw-w-full envive-tw-cursor-pointer envive-tw-px-4 envive-tw-py-2 envive-tw-text-center"
60
+ onClick={handlePreviousDiscussions}
61
+ >
62
+ {/* TODO: Get hard copy */}
63
+ <Typography
64
+ color={TypographyColor.TEXT_PRIMARY}
65
+ variant={TypographyVariant.B3_RG}
66
+ className="hover:envive-tw-underline"
67
+ >
68
+ View Previous Messages
69
+ </Typography>
70
+ </button>
71
+ )}
48
72
  {messages.map((message: Message[], index: number) => {
49
73
  if (
50
74
  ignoreFirstModelResponse &&
@@ -108,6 +132,7 @@ export const ChatMessages = forwardRef<HTMLDivElement, ChatMessagesProps>(
108
132
  isResponseStreaming={isResponseStreaming && isLastMessageTurn}
109
133
  neverShowSingleProductCards={neverShowSingleProductCards}
110
134
  showVerifiedBuyer={showVerifiedBuyer}
135
+ isPartialView={isPartialView}
111
136
  />
112
137
  );
113
138
  }
@@ -42,7 +42,7 @@ export const Layout = ({
42
42
  testId={testId}
43
43
  style={style}
44
44
  className={classNames(
45
- 'envive-tw-flex envive-tw-h-full envive-tw-w-[512px] envive-tw-flex-col envive-tw-bg-[#FFFFFF]',
45
+ 'envive-tw-flex envive-tw-h-full envive-tw-w-full envive-tw-max-w-[512px] envive-tw-flex-col envive-tw-bg-[#FFFFFF]',
46
46
  className,
47
47
  )}
48
48
  >
@@ -50,7 +50,7 @@ export const Layout = ({
50
50
  direction="column"
51
51
  className="envive-tw-relative envive-tw-h-full envive-tw-overflow-hidden"
52
52
  >
53
- {header && <div className="envive-tw-flex-shrink-0">{header}</div>}
53
+ {header && <div className="envive-tw-flex-shrink-0 envive-tw-leading-[0]">{header}</div>}
54
54
 
55
55
  <div className="envive-tw-flex-1 envive-tw-overflow-y-auto envive-tw-overflow-x-hidden">
56
56
  <Stack
@@ -0,0 +1,288 @@
1
+ import {
2
+ AnimatePresence,
3
+ MotionValue,
4
+ ValueAnimationTransition,
5
+ motion,
6
+ useAnimate,
7
+ } from 'framer-motion';
8
+ import React, { FC, TouchEvent, useEffect, useImperativeHandle } from 'react';
9
+ import { Unit } from '../hooks/useSnapCalculator';
10
+ import { useSnapControl } from '../hooks/useSnapControl';
11
+ import { usePreventScroll } from '../hooks/usePreventScroll';
12
+
13
+ const BOTTOM_OFFSET = 100;
14
+
15
+ export interface ModalSheetControl {
16
+ jumpToSnap: (snapIndex: number) => void;
17
+ }
18
+
19
+ interface ModalSheetProps {
20
+ animationKey: MotionValue;
21
+ open: boolean;
22
+ height: number;
23
+ unit: Unit;
24
+ snaps?: number[];
25
+ initSnap?: number;
26
+ children?: React.ReactNode;
27
+ handler?: React.ReactNode;
28
+ animation?: ValueAnimationTransition;
29
+ onClose: () => void;
30
+ onSnap?: (snap: number) => void;
31
+ controlRef?: React.MutableRefObject<ModalSheetControl | undefined>;
32
+ disableDrag?: boolean;
33
+ }
34
+
35
+ export const ModalSheet: FC<ModalSheetProps> = ({
36
+ animationKey,
37
+ children,
38
+ open,
39
+ height,
40
+ unit,
41
+ snaps = [],
42
+ initSnap = 0,
43
+ handler,
44
+ animation = { type: 'spring', stiffness: 300, damping: 30 },
45
+ controlRef,
46
+ onClose,
47
+ onSnap,
48
+ disableDrag = false,
49
+ }) => {
50
+ const [layerRef] = useAnimate();
51
+ const [containerRef] = useAnimate();
52
+
53
+ const {
54
+ gestureRef,
55
+ isScrollableArea,
56
+ getScrollParent,
57
+ getGestureDirection,
58
+ getScrollDirections,
59
+ } = usePreventScroll();
60
+
61
+ const onSnapComplete = (currentSnap: number, nextSnap: number, collapsed: boolean) => {
62
+ if (collapsed) {
63
+ onClose?.();
64
+ return;
65
+ }
66
+
67
+ if (currentSnap !== nextSnap) {
68
+ onSnap?.(nextSnap);
69
+ }
70
+ };
71
+
72
+ const {
73
+ scope,
74
+ animatedY,
75
+ swipeviewHeightPx,
76
+ initSnapInPixels,
77
+ contentHeight,
78
+ currentSnap,
79
+ displayOverlay,
80
+ opacityOverlay,
81
+ jumpTo,
82
+ defineNextSnapByPosition,
83
+ resetControls,
84
+ getSnapToPixel,
85
+ } = useSnapControl({
86
+ animationKey,
87
+ animation,
88
+ height,
89
+ unit,
90
+ snaps,
91
+ initSnap,
92
+ overlayOpacity: 0.5,
93
+ onSnapComplete,
94
+ });
95
+
96
+ useImperativeHandle(controlRef, () => ({
97
+ jumpToSnap: (snapIndex: number) => {
98
+ const snapPx = getSnapToPixel(snaps[snapIndex]);
99
+ jumpTo(snapPx);
100
+ },
101
+ }));
102
+
103
+ const expandToFullView = () => {
104
+ const idx = snaps.indexOf(currentSnap);
105
+ if (idx === 1) {
106
+ jumpTo(getSnapToPixel(2));
107
+ }
108
+ };
109
+
110
+ const notifySnapChanges = (isModalOpen: boolean) => {
111
+ if (isModalOpen) {
112
+ onSnap?.(currentSnap);
113
+ return;
114
+ }
115
+
116
+ const snap = resetControls();
117
+ onSnap?.(snap);
118
+ };
119
+
120
+ useEffect(() => {
121
+ // Making iOS compatible
122
+ const preventDefault = (event: TouchEvent) => event.preventDefault();
123
+ layerRef?.current?.addEventListener('touchmove', preventDefault, { passive: false });
124
+
125
+ return () => {
126
+ layerRef?.current?.removeEventListener('touchmove', preventDefault);
127
+ };
128
+ }, [layerRef?.current]);
129
+
130
+ useEffect(() => {
131
+ const preventScroll = (event: TouchEvent) => {
132
+ const gestureDirection = getGestureDirection(
133
+ event.touches[0].clientX,
134
+ event.touches[0].clientY,
135
+ );
136
+ const isScrollable = isScrollableArea(gestureDirection);
137
+
138
+ if (!isScrollable) {
139
+ event.preventDefault();
140
+ event.stopPropagation();
141
+ }
142
+ };
143
+
144
+ containerRef?.current?.addEventListener('touchmove', preventScroll, { passive: false });
145
+ notifySnapChanges(open);
146
+
147
+ return () => {
148
+ containerRef?.current?.removeEventListener('touchmove', preventScroll);
149
+ };
150
+ }, [open]);
151
+
152
+ const handleDragEnd = () => {
153
+ const snap = defineNextSnapByPosition();
154
+ jumpTo(snap);
155
+ };
156
+
157
+ const handleTouchStart = (event: React.TouchEvent) => {
158
+ const scrollableElement = getScrollParent(event.target as HTMLElement);
159
+ const scrollableDirections = getScrollDirections(scrollableElement);
160
+ const parentScrollableElement = getScrollParent(scrollableElement, true);
161
+ const parentScrollableDirections = getScrollDirections(parentScrollableElement);
162
+ const grandparentScrollableElement = getScrollParent(parentScrollableElement, true);
163
+ const grandparentScrollableDirections = getScrollDirections(grandparentScrollableElement);
164
+ gestureRef.current = {
165
+ event: event.touches?.[0],
166
+ scrollableElements: [
167
+ {
168
+ element: scrollableElement,
169
+ scrollableDirections,
170
+ },
171
+ {
172
+ element: parentScrollableElement,
173
+ scrollableDirections: parentScrollableDirections,
174
+ },
175
+ {
176
+ element: grandparentScrollableElement,
177
+ scrollableDirections: grandparentScrollableDirections,
178
+ },
179
+ ],
180
+ };
181
+ };
182
+
183
+ const handleTouchEnd = () => {
184
+ handleDragEnd();
185
+ gestureRef.current = {
186
+ scrollableElements: [],
187
+ event: null,
188
+ };
189
+ };
190
+
191
+ const handleContentGesture = (fingerX: number, fingerY: number) => {
192
+ if (!gestureRef.current?.event) {
193
+ return;
194
+ }
195
+
196
+ const pointerDistance = (gestureRef.current?.event?.clientY || 0) - fingerY;
197
+ const snapPx = getSnapToPixel(currentSnap);
198
+ const distanceToTravel = snapPx - pointerDistance;
199
+ const gestureDirection = getGestureDirection(fingerX, fingerY);
200
+ const isScrollable = isScrollableArea(gestureDirection);
201
+ const animationBlocked = isScrollable || (gestureDirection !== 'down' && currentSnap === 100);
202
+
203
+ if (!animationBlocked) {
204
+ animatedY.set(distanceToTravel);
205
+ }
206
+ };
207
+
208
+ return (
209
+ <AnimatePresence>
210
+ {open && (
211
+ <>
212
+ <motion.div
213
+ ref={layerRef}
214
+ style={{
215
+ display: displayOverlay,
216
+ position: 'fixed',
217
+ top: 0,
218
+ left: 0,
219
+ right: 0,
220
+ bottom: 0,
221
+ opacity: opacityOverlay,
222
+ background: 'black',
223
+ }}
224
+ />
225
+ <motion.div
226
+ ref={scope}
227
+ drag="y"
228
+ dragConstraints={{ top: 0, bottom: swipeviewHeightPx }}
229
+ initial={{ y: swipeviewHeightPx }}
230
+ animate={{ y: initSnapInPixels }}
231
+ exit={{ y: swipeviewHeightPx + BOTTOM_OFFSET }}
232
+ transition={animation}
233
+ onDragEnd={handleDragEnd}
234
+ onClick={expandToFullView}
235
+ style={{
236
+ y: animatedY,
237
+ bottom: swipeviewHeightPx,
238
+ right: 0,
239
+ position: 'fixed',
240
+ width: '100%',
241
+ zIndex: 2147483647,
242
+ }}
243
+ >
244
+ {handler}
245
+ </motion.div>
246
+
247
+ <motion.div
248
+ ref={scope}
249
+ onTouchStart={handleTouchStart}
250
+ onTouchEnd={handleTouchEnd}
251
+ onTouchMove={event =>
252
+ handleContentGesture(event.touches[0].clientX, event.touches[0].clientY)
253
+ }
254
+ onPointerMove={
255
+ disableDrag ? undefined : event => handleContentGesture(event.clientX, event.clientY)
256
+ }
257
+ initial={{ y: swipeviewHeightPx + BOTTOM_OFFSET }}
258
+ animate={{ y: initSnapInPixels }}
259
+ exit={{ y: swipeviewHeightPx + BOTTOM_OFFSET }}
260
+ transition={animation}
261
+ style={{
262
+ y: animatedY,
263
+ width: '100%',
264
+ height: swipeviewHeightPx + BOTTOM_OFFSET,
265
+ position: 'fixed',
266
+ bottom: -BOTTOM_OFFSET + 1,
267
+ right: 0,
268
+ boxSizing: 'content-box',
269
+ zIndex: 2147483646,
270
+ }}
271
+ >
272
+ <motion.div
273
+ ref={containerRef}
274
+ initial={{ height: 0 }}
275
+ style={{
276
+ width: '100%',
277
+ height: contentHeight,
278
+ userSelect: 'none',
279
+ }}
280
+ >
281
+ {children}
282
+ </motion.div>
283
+ </motion.div>
284
+ </>
285
+ )}
286
+ </AnimatePresence>
287
+ );
288
+ };