@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.
- package/dist/AnimatedText/AnimatedText.d.cts +3 -3
- package/dist/AnimatedText/AnimatedText.d.ts +3 -3
- package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.cts +2 -2
- package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.ts +2 -2
- package/dist/Carousel/Carousel.d.cts +2 -2
- package/dist/Carousel/Carousel.d.ts +2 -2
- package/dist/ChatFooter/ChatFooter.d.cts +2 -2
- package/dist/ChatFooter/ChatFooter.d.ts +2 -2
- package/dist/ChatFooter/components/index.d.cts +5 -5
- package/dist/ChatFooter/components/index.d.ts +5 -5
- package/dist/ChatFooter/hooks/useGetContainerProperties.cjs +2 -2
- package/dist/ChatFooter/hooks/useGetContainerProperties.js +2 -2
- package/dist/ChatHeader/ChatHeader.cjs +3 -2
- package/dist/ChatHeader/ChatHeader.d.cts +3 -2
- package/dist/ChatHeader/ChatHeader.d.ts +3 -2
- package/dist/ChatHeader/ChatHeader.js +3 -2
- package/dist/ChatHeader/components/Handle.cjs +27 -1
- package/dist/ChatHeader/components/Handle.js +27 -1
- package/dist/ChatHeader/types/index.d.cts +2 -0
- package/dist/ChatHeader/types/index.d.ts +2 -0
- package/dist/ChatPreview/ChatPreview.d.cts +2 -2
- package/dist/ChatPreview/ChatPreview.d.ts +2 -2
- package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
- package/dist/ChatPreviewComparison/ChatPreviewComparison.d.ts +2 -2
- package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
- package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
- package/dist/Container/Container.d.cts +176 -176
- package/dist/Container/Container.d.ts +176 -176
- package/dist/DesignTokens/DesignTokensComponent.d.cts +2 -2
- package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
- package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
- package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.ts +2 -2
- package/dist/FloatingButton/FloatingButton.cjs +2 -1
- package/dist/FloatingButton/FloatingButton.d.cts +4 -3
- package/dist/FloatingButton/FloatingButton.d.ts +4 -3
- package/dist/FloatingButton/FloatingButton.js +2 -1
- package/dist/FloatingButton/components/Container.cjs +2 -2
- package/dist/FloatingButton/components/Container.js +2 -2
- package/dist/FloatingButton/hooks/useGetContainerProperties.cjs +2 -2
- package/dist/FloatingButton/hooks/useGetContainerProperties.js +2 -2
- package/dist/FloatingButton/types/types.cjs +4 -4
- package/dist/FloatingButton/types/types.d.cts +8 -4
- package/dist/FloatingButton/types/types.d.ts +8 -4
- package/dist/FloatingButton/types/types.js +4 -4
- package/dist/FloatingChat/FloatingChat.cjs +106 -39
- package/dist/FloatingChat/FloatingChat.d.cts +3 -2
- package/dist/FloatingChat/FloatingChat.d.ts +3 -2
- package/dist/FloatingChat/FloatingChat.js +108 -41
- package/dist/FloatingChat/components/AgentMessage.cjs +3 -2
- package/dist/FloatingChat/components/AgentMessage.js +3 -2
- package/dist/FloatingChat/components/ChatMessages.cjs +59 -41
- package/dist/FloatingChat/components/ChatMessages.js +58 -40
- package/dist/FloatingChat/components/Layout.cjs +2 -2
- package/dist/FloatingChat/components/Layout.js +2 -2
- package/dist/FloatingChat/components/ModalSheet.cjs +184 -0
- package/dist/FloatingChat/components/ModalSheet.js +182 -0
- package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.cjs +3 -2
- package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.js +3 -2
- package/dist/FloatingChat/components/index.cjs +1 -0
- package/dist/FloatingChat/components/index.js +1 -0
- package/dist/FloatingChat/hooks/useChatSuggestions.cjs +25 -0
- package/dist/FloatingChat/hooks/useChatSuggestions.js +24 -0
- package/dist/FloatingChat/hooks/useFilteredChatMessages.cjs +24 -0
- package/dist/FloatingChat/hooks/useFilteredChatMessages.js +23 -0
- package/dist/FloatingChat/hooks/usePreventScroll.cjs +117 -0
- package/dist/FloatingChat/hooks/usePreventScroll.js +116 -0
- package/dist/FloatingChat/hooks/useSnapCalculator.cjs +37 -0
- package/dist/FloatingChat/hooks/useSnapCalculator.js +35 -0
- package/dist/FloatingChat/hooks/useSnapControl.cjs +82 -0
- package/dist/FloatingChat/hooks/useSnapControl.js +81 -0
- package/dist/FloatingChat/hooks/useSnapSetup.cjs +59 -0
- package/dist/FloatingChat/hooks/useSnapSetup.js +58 -0
- package/dist/FloatingChat/types/types.d.cts +4 -0
- package/dist/FloatingChat/types/types.d.ts +4 -0
- package/dist/Image/Image.d.cts +2 -2
- package/dist/Image/Image.d.ts +2 -2
- package/dist/ImageGallery/ImageGallery.d.cts +2 -2
- package/dist/ImageGallery/ImageGallery.d.ts +2 -2
- package/dist/MarkdownProcessor/MarkdownProcessor.d.cts +2 -2
- package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
- package/dist/PromptButton/PromptButton.d.cts +2 -2
- package/dist/PromptButton/PromptButton.d.ts +2 -2
- package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
- package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
- package/dist/PromptCarousel/PromptCarousel.cjs +0 -1
- package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
- package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
- package/dist/PromptCarousel/PromptCarousel.js +0 -1
- package/dist/ReviewCard/ReviewCard.d.cts +2 -2
- package/dist/ReviewCard/ReviewCard.d.ts +2 -2
- package/dist/ReviewCard/components/index.d.cts +6 -6
- package/dist/ReviewCard/components/index.d.ts +6 -6
- package/dist/SalesAgentProductCard/SalesAgentProductCard.d.cts +2 -2
- package/dist/SalesAgentProductCard/SalesAgentProductCard.d.ts +2 -2
- package/dist/SalesAgentProductCard/components/index.d.cts +2 -2
- package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
- package/dist/SocialProof/SocialProof.cjs +9 -2
- package/dist/SocialProof/SocialProof.d.cts +2 -2
- package/dist/SocialProof/SocialProof.d.ts +2 -2
- package/dist/SocialProof/SocialProof.js +9 -2
- package/dist/SocialProof/hooks/index.cjs +1 -0
- package/dist/SocialProof/hooks/index.js +3 -0
- package/dist/SocialProof/hooks/useSocialProofCount.cjs +48 -0
- package/dist/SocialProof/hooks/useSocialProofCount.d.cts +15 -0
- package/dist/SocialProof/hooks/useSocialProofCount.d.ts +15 -0
- package/dist/SocialProof/hooks/useSocialProofCount.js +46 -0
- package/dist/SocialProof/index.cjs +5 -1
- package/dist/SocialProof/index.d.cts +3 -2
- package/dist/SocialProof/index.d.ts +3 -2
- package/dist/SocialProof/index.js +4 -2
- package/dist/SocialProof/types/types.cjs +8 -1
- package/dist/SocialProof/types/types.d.cts +16 -6
- package/dist/SocialProof/types/types.d.ts +16 -6
- package/dist/SocialProof/types/types.js +7 -1
- package/dist/Stack/Stack.d.cts +2 -2
- package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
- package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
- package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
- package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
- package/dist/Typography/Typography.d.cts +4 -4
- package/dist/Typography/Typography.d.ts +4 -4
- package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
- package/dist/WidgetTextField/WidgetTextField.d.ts +2 -2
- package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
- package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.cts +2 -2
- package/dist/WidgetWrapperWithTitle/WidgetWrapperWithTitle.d.ts +2 -2
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ChatFooter/hooks/useGetContainerProperties.ts +2 -2
- package/src/components/ChatHeader/ChatHeader.tsx +2 -0
- package/src/components/ChatHeader/components/Handle.tsx +29 -1
- package/src/components/ChatHeader/types/index.ts +3 -0
- package/src/components/FloatingButton/FloatingButton.tsx +2 -0
- package/src/components/FloatingButton/components/Container.tsx +3 -0
- package/src/components/FloatingButton/hooks/useGetContainerProperties.ts +2 -1
- package/src/components/FloatingButton/types/types.ts +9 -4
- package/src/components/FloatingChat/FloatingChat.tsx +160 -59
- package/src/components/FloatingChat/components/AgentMessage.tsx +3 -0
- package/src/components/FloatingChat/components/ChatMessages.tsx +25 -0
- package/src/components/FloatingChat/components/Layout.tsx +2 -2
- package/src/components/FloatingChat/components/ModalSheet.tsx +288 -0
- package/src/components/FloatingChat/components/SalesAgentProductCardsCarousel.tsx +7 -2
- package/src/components/FloatingChat/components/index.ts +3 -0
- package/src/components/FloatingChat/hooks/useChatSuggestions.ts +49 -0
- package/src/components/FloatingChat/hooks/useFilteredChatMessages.ts +43 -0
- package/src/components/FloatingChat/hooks/usePreventScroll.ts +207 -0
- package/src/components/FloatingChat/hooks/useSnapCalculator.ts +41 -0
- package/src/components/FloatingChat/hooks/useSnapControl.ts +131 -0
- package/src/components/FloatingChat/hooks/useSnapSetup.ts +106 -0
- package/src/components/FloatingChat/types/types.ts +4 -0
- package/src/components/PromptCarousel/PromptCarousel.tsx +0 -1
- package/src/components/SocialProof/SocialProof.tsx +8 -2
- package/src/components/SocialProof/__tests__/SocialProof.test.tsx +66 -17
- package/src/components/SocialProof/hooks/index.ts +2 -0
- package/src/components/SocialProof/hooks/useSocialProofCount.ts +67 -0
- package/src/components/SocialProof/index.ts +2 -1
- package/src/components/SocialProof/types/types.ts +18 -6
|
@@ -12,6 +12,8 @@ export interface SalesAgentProductCardsCarouselProps {
|
|
|
12
12
|
hideNavigation?: boolean;
|
|
13
13
|
numberOfProducts?: number;
|
|
14
14
|
products: SalesAgentProductCardProps[];
|
|
15
|
+
variant?: SalesAgentProductCardVariant;
|
|
16
|
+
isPartialView?: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
export const SalesAgentProductCardsCarousel = ({
|
|
@@ -19,9 +21,13 @@ export const SalesAgentProductCardsCarousel = ({
|
|
|
19
21
|
theme,
|
|
20
22
|
hideNavigation = false,
|
|
21
23
|
numberOfProducts,
|
|
24
|
+
variant = SalesAgentProductCardVariant.LARGE,
|
|
25
|
+
isPartialView = false,
|
|
22
26
|
}: SalesAgentProductCardsCarouselProps) => {
|
|
23
27
|
const finalTheme = resolveTheme(theme);
|
|
24
28
|
|
|
29
|
+
const finalVariant = isPartialView ? SalesAgentProductCardVariant.SMALL : variant;
|
|
30
|
+
|
|
25
31
|
const forceShowCurrentPriceSpace = products.some(
|
|
26
32
|
product => product.currentPrice && product.previousPrice !== product.currentPrice,
|
|
27
33
|
);
|
|
@@ -36,8 +42,7 @@ export const SalesAgentProductCardsCarousel = ({
|
|
|
36
42
|
elements={products.map(product => (
|
|
37
43
|
<SalesAgentProductCard
|
|
38
44
|
key={product.id}
|
|
39
|
-
|
|
40
|
-
variant={SalesAgentProductCardVariant.LARGE}
|
|
45
|
+
variant={finalVariant}
|
|
41
46
|
productName={product.productName}
|
|
42
47
|
currentPrice={product.currentPrice}
|
|
43
48
|
previousPrice={product.previousPrice}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Suggestion } from '@envive-ai/react-hooks/application/models';
|
|
3
|
+
|
|
4
|
+
export interface UseChatSuggestionsProps {
|
|
5
|
+
suggestions: Suggestion[];
|
|
6
|
+
isPendingResponse: boolean;
|
|
7
|
+
isResponseStreaming: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UseChatSuggestionsReturn {
|
|
11
|
+
answerSuggestions: string[];
|
|
12
|
+
generalSuggestions: string[];
|
|
13
|
+
showAnswerSuggestions: boolean;
|
|
14
|
+
setAnswerSuggestions: React.Dispatch<React.SetStateAction<string[]>>;
|
|
15
|
+
setGeneralSuggestions: React.Dispatch<React.SetStateAction<string[]>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const useChatSuggestions = ({
|
|
19
|
+
suggestions,
|
|
20
|
+
isPendingResponse,
|
|
21
|
+
isResponseStreaming,
|
|
22
|
+
}: UseChatSuggestionsProps): UseChatSuggestionsReturn => {
|
|
23
|
+
const [answerSuggestions, setAnswerSuggestions] = useState<string[]>([]);
|
|
24
|
+
const [generalSuggestions, setGeneralSuggestions] = useState<string[]>([]);
|
|
25
|
+
|
|
26
|
+
const showAnswerSuggestions =
|
|
27
|
+
answerSuggestions.length > 0 && !isPendingResponse && !isResponseStreaming;
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (suggestions.length > 0) {
|
|
31
|
+
setAnswerSuggestions(
|
|
32
|
+
suggestions.filter(suggestion => suggestion.isAnswer).map(suggestion => suggestion.content),
|
|
33
|
+
);
|
|
34
|
+
setGeneralSuggestions(
|
|
35
|
+
suggestions
|
|
36
|
+
.filter(suggestion => !suggestion.isAnswer)
|
|
37
|
+
.map(suggestion => suggestion.content),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}, [suggestions]);
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
answerSuggestions,
|
|
44
|
+
generalSuggestions,
|
|
45
|
+
showAnswerSuggestions,
|
|
46
|
+
setAnswerSuggestions,
|
|
47
|
+
setGeneralSuggestions,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RefObject, useMemo } from 'react';
|
|
2
|
+
import { Message } from '@envive-ai/react-hooks/application/models';
|
|
3
|
+
import { useMessageFilter } from '@envive-ai/react-hooks/hooks/MessageFilter';
|
|
4
|
+
import { ModalSheetControl } from '../components';
|
|
5
|
+
|
|
6
|
+
export interface UseFilteredChatMessagesProps {
|
|
7
|
+
messages: Message[][];
|
|
8
|
+
isMobile: boolean;
|
|
9
|
+
currentSnapPercentage: number;
|
|
10
|
+
modalSheetControl: RefObject<ModalSheetControl>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseFilteredChatMessagesReturn {
|
|
14
|
+
filteredMessages: Message[][];
|
|
15
|
+
hasFilteredMessages: boolean;
|
|
16
|
+
handlePreviousDiscussions: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useFilteredChatMessages = ({
|
|
20
|
+
messages,
|
|
21
|
+
isMobile,
|
|
22
|
+
currentSnapPercentage,
|
|
23
|
+
modalSheetControl,
|
|
24
|
+
}: UseFilteredChatMessagesProps): UseFilteredChatMessagesReturn => {
|
|
25
|
+
const { getFilteredMessages } = useMessageFilter();
|
|
26
|
+
|
|
27
|
+
const keepMessagesExpanded = !isMobile || (isMobile && currentSnapPercentage === 100);
|
|
28
|
+
|
|
29
|
+
const filteredMessages = useMemo(
|
|
30
|
+
() => getFilteredMessages(messages, keepMessagesExpanded),
|
|
31
|
+
[getFilteredMessages, messages, keepMessagesExpanded],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const hasFilteredMessages = filteredMessages.length !== messages.length && isMobile;
|
|
35
|
+
|
|
36
|
+
const handlePreviousDiscussions = () => modalSheetControl.current?.jumpToSnap(2);
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
filteredMessages,
|
|
40
|
+
hasFilteredMessages,
|
|
41
|
+
handlePreviousDiscussions,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Touch, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
type GestureDirections = 'left' | 'right' | 'up' | 'down';
|
|
4
|
+
type ScrollableDirections = 'horizontal' | 'vertical';
|
|
5
|
+
|
|
6
|
+
interface ScrollableEl {
|
|
7
|
+
element: HTMLElement | null;
|
|
8
|
+
scrollableDirections: ScrollableDirections[] | null;
|
|
9
|
+
}
|
|
10
|
+
interface GrabGesture {
|
|
11
|
+
event: Touch | null;
|
|
12
|
+
scrollableElements: ScrollableEl[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Prevent the body element from scrolling when a modal (partial view) is open.
|
|
17
|
+
*
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
export const usePreventScroll = () => {
|
|
21
|
+
const gestureRef = useRef<GrabGesture | null>(null);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Calculate the angle of a user gesture (grab/drop).
|
|
25
|
+
*
|
|
26
|
+
* @param startX
|
|
27
|
+
* @param startY
|
|
28
|
+
* @param endX
|
|
29
|
+
* @param endY
|
|
30
|
+
* @returns
|
|
31
|
+
*/
|
|
32
|
+
const calculateAngle = (startX: number, startY: number, endX: number, endY: number) => {
|
|
33
|
+
const deltaX = endX - startX;
|
|
34
|
+
const deltaY = endY - startY;
|
|
35
|
+
|
|
36
|
+
// atan2 returns the angle in radians, convert to degrees
|
|
37
|
+
const radians = Math.atan2(deltaY, deltaX);
|
|
38
|
+
const degrees = radians * (180 / Math.PI);
|
|
39
|
+
|
|
40
|
+
// Normalize the angle to 0 - 360 degrees
|
|
41
|
+
return (degrees + 360) % 360;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Test if an element has scrollable capabilities.
|
|
46
|
+
*
|
|
47
|
+
* @param node
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
const isScrollable = (node: Element): boolean => {
|
|
51
|
+
const style = window.getComputedStyle(node);
|
|
52
|
+
return /(auto|scroll)/.test(style.overflow + style.overflowX + style.overflowY);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Find the parent node that has scrollable capabilities.
|
|
57
|
+
*
|
|
58
|
+
* @param node
|
|
59
|
+
* @returns
|
|
60
|
+
*/
|
|
61
|
+
const getScrollParent = (
|
|
62
|
+
node: HTMLElement | null,
|
|
63
|
+
ignoreCurrentNode: boolean = false,
|
|
64
|
+
): HTMLElement | null => {
|
|
65
|
+
let parentNode = ignoreCurrentNode ? node?.parentElement : node;
|
|
66
|
+
|
|
67
|
+
if (!parentNode) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (isScrollable(parentNode)) {
|
|
72
|
+
return parentNode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
while (parentNode && !isScrollable(parentNode)) {
|
|
76
|
+
parentNode = parentNode.parentElement as HTMLElement;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!parentNode && isScrollable(document.body)) {
|
|
80
|
+
return document.body;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
parentNode ||
|
|
85
|
+
(document.scrollingElement as HTMLElement) ||
|
|
86
|
+
(document.documentElement as HTMLElement)
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the scrollable directions an element can scroll.
|
|
92
|
+
*
|
|
93
|
+
* @returns
|
|
94
|
+
*/
|
|
95
|
+
const getScrollDirections = (scrollableElement: HTMLElement | null) => {
|
|
96
|
+
const horizontalScroll =
|
|
97
|
+
(scrollableElement?.scrollWidth || 0) > (scrollableElement?.clientWidth || 0);
|
|
98
|
+
const verticalScroll =
|
|
99
|
+
(scrollableElement?.scrollHeight || 0) > (scrollableElement?.clientHeight || 0);
|
|
100
|
+
|
|
101
|
+
const scrollDirections: ScrollableDirections[] = [];
|
|
102
|
+
if (horizontalScroll) scrollDirections.push('horizontal');
|
|
103
|
+
if (verticalScroll) scrollDirections.push('vertical');
|
|
104
|
+
|
|
105
|
+
return scrollDirections;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Determine the direction of a gesture event based on the angle of a grab/drop event.
|
|
110
|
+
*
|
|
111
|
+
* @param fingerX
|
|
112
|
+
* @param fingerY
|
|
113
|
+
* @returns
|
|
114
|
+
*/
|
|
115
|
+
const getGestureDirection = (fingerX: number, fingerY: number): GestureDirections => {
|
|
116
|
+
const clientX = gestureRef?.current?.event?.clientX || 0;
|
|
117
|
+
const clientY = gestureRef?.current?.event?.clientY || 0;
|
|
118
|
+
const directionAngle = calculateAngle(clientX, clientY, fingerX, fingerY);
|
|
119
|
+
|
|
120
|
+
if (directionAngle < 45 || directionAngle > 315) {
|
|
121
|
+
return 'right';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (directionAngle > 45 && directionAngle < 135) {
|
|
125
|
+
return 'down';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (directionAngle > 135 && directionAngle < 225) {
|
|
129
|
+
return 'left';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return 'up';
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Determine whether the direction of the gesture can scroll the element in context.
|
|
137
|
+
*
|
|
138
|
+
* @param param0
|
|
139
|
+
* @param direction
|
|
140
|
+
* @returns
|
|
141
|
+
*/
|
|
142
|
+
const isScrollDirectionAllowed = (
|
|
143
|
+
{ element, scrollableDirections }: ScrollableEl,
|
|
144
|
+
direction: GestureDirections,
|
|
145
|
+
) => {
|
|
146
|
+
if (
|
|
147
|
+
element?.tagName === 'BODY' ||
|
|
148
|
+
element?.tagName === 'HTML' ||
|
|
149
|
+
scrollableDirections?.length === 0
|
|
150
|
+
) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const {
|
|
155
|
+
clientHeight = 0,
|
|
156
|
+
clientWidth = 0,
|
|
157
|
+
scrollHeight = 0,
|
|
158
|
+
scrollWidth = 0,
|
|
159
|
+
scrollTop = 0,
|
|
160
|
+
scrollLeft = 0,
|
|
161
|
+
} = element || {};
|
|
162
|
+
|
|
163
|
+
const atTop = scrollTop === 0;
|
|
164
|
+
const atLeft = scrollLeft === 0;
|
|
165
|
+
const atBottom = (100 / scrollHeight) * (scrollTop + clientHeight) > 99.9;
|
|
166
|
+
const atRight = (100 / scrollWidth) * (scrollLeft + clientWidth) > 99.9;
|
|
167
|
+
|
|
168
|
+
const verticalScrollBlocked =
|
|
169
|
+
(atBottom && direction === 'up') ||
|
|
170
|
+
(atTop && direction === 'down') ||
|
|
171
|
+
direction === 'left' ||
|
|
172
|
+
direction === 'right';
|
|
173
|
+
if (scrollableDirections?.includes('vertical') && verticalScrollBlocked) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const horizontalScrollBlocked =
|
|
178
|
+
(atRight && direction === 'left') ||
|
|
179
|
+
(atLeft && direction === 'right') ||
|
|
180
|
+
direction === 'up' ||
|
|
181
|
+
direction === 'down';
|
|
182
|
+
if (scrollableDirections?.includes('horizontal') && horizontalScrollBlocked) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return true;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Test if the selected scrollable element can scroll in the direction of the grab/drop event.
|
|
191
|
+
*
|
|
192
|
+
* @param direction
|
|
193
|
+
* @returns
|
|
194
|
+
*/
|
|
195
|
+
const isScrollableArea = (direction: GestureDirections) => {
|
|
196
|
+
const { scrollableElements } = gestureRef.current || {};
|
|
197
|
+
return scrollableElements?.some(el => isScrollDirectionAllowed(el, direction));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
gestureRef,
|
|
202
|
+
getScrollParent,
|
|
203
|
+
isScrollableArea,
|
|
204
|
+
getGestureDirection,
|
|
205
|
+
getScrollDirections,
|
|
206
|
+
};
|
|
207
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
export enum Unit {
|
|
4
|
+
PERCENT = 'percent',
|
|
5
|
+
PIXEL = 'pixel',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const useSnapCalculator = (snaps: number[], maxHeight: number, unit: Unit) => {
|
|
9
|
+
const viewportHeightPx = document.documentElement.clientHeight;
|
|
10
|
+
const swipeviewHeightPx =
|
|
11
|
+
unit === Unit.PERCENT ? Math.floor(viewportHeightPx * (maxHeight / 100)) : maxHeight;
|
|
12
|
+
|
|
13
|
+
const snapsToPixels = useMemo(
|
|
14
|
+
() =>
|
|
15
|
+
snaps?.map(snap =>
|
|
16
|
+
Math.abs(
|
|
17
|
+
(unit === Unit.PERCENT ? Math.floor(swipeviewHeightPx * (snap / 100)) : snap) -
|
|
18
|
+
swipeviewHeightPx,
|
|
19
|
+
),
|
|
20
|
+
),
|
|
21
|
+
[swipeviewHeightPx, unit, snaps],
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const getPixelToSnap = (pixels: number) => {
|
|
25
|
+
const snapIdx = snapsToPixels?.indexOf(pixels) || 0;
|
|
26
|
+
return snaps?.[snapIdx] || 0;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getSnapToPixel = (snap: number) => {
|
|
30
|
+
const snapIdx = snaps?.indexOf(snap) || 0;
|
|
31
|
+
return snapsToPixels?.[snapIdx] || 0;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
viewportHeightPx,
|
|
36
|
+
snapsToPixels,
|
|
37
|
+
swipeviewHeightPx,
|
|
38
|
+
getPixelToSnap,
|
|
39
|
+
getSnapToPixel,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MotionValue,
|
|
3
|
+
ValueAnimationTransition,
|
|
4
|
+
transform,
|
|
5
|
+
useAnimate,
|
|
6
|
+
useMotionValue,
|
|
7
|
+
useTransform,
|
|
8
|
+
} from 'framer-motion';
|
|
9
|
+
import { useMemo, useState } from 'react';
|
|
10
|
+
import { Unit, useSnapCalculator } from './useSnapCalculator';
|
|
11
|
+
|
|
12
|
+
type SnapControl = {
|
|
13
|
+
animationKey?: MotionValue;
|
|
14
|
+
animation: ValueAnimationTransition;
|
|
15
|
+
height: number;
|
|
16
|
+
unit: Unit;
|
|
17
|
+
snaps: number[];
|
|
18
|
+
initSnap: number;
|
|
19
|
+
threshold?: number;
|
|
20
|
+
overlayOpacity: number;
|
|
21
|
+
onSnapComplete: (currentSnap: number, nextSnap: number, collapsed: boolean) => void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const MIDDLE_SNAP_OFFSET = 5;
|
|
25
|
+
|
|
26
|
+
export const useSnapControl = ({
|
|
27
|
+
animationKey,
|
|
28
|
+
animation,
|
|
29
|
+
height,
|
|
30
|
+
unit,
|
|
31
|
+
snaps,
|
|
32
|
+
initSnap,
|
|
33
|
+
onSnapComplete,
|
|
34
|
+
overlayOpacity,
|
|
35
|
+
}: SnapControl) => {
|
|
36
|
+
const [scope, animate] = useAnimate();
|
|
37
|
+
const defaultAnimationKey = useMotionValue(-1);
|
|
38
|
+
const animatedY = animationKey || defaultAnimationKey;
|
|
39
|
+
|
|
40
|
+
const { swipeviewHeightPx, snapsToPixels, getPixelToSnap, getSnapToPixel } = useSnapCalculator(
|
|
41
|
+
snaps,
|
|
42
|
+
height,
|
|
43
|
+
unit,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const contentHeight = useTransform(animatedY, [0, swipeviewHeightPx], [swipeviewHeightPx, 0]);
|
|
47
|
+
|
|
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
|
+
);
|
|
68
|
+
|
|
69
|
+
const initSnapInPixels = snapsToPixels?.[initSnap];
|
|
70
|
+
const getInitSnap = () => snaps?.[initSnap];
|
|
71
|
+
const [currentSnap, setCurrentSnap] = useState<number>(getInitSnap());
|
|
72
|
+
|
|
73
|
+
const defineNextSnapByPosition = () => {
|
|
74
|
+
const currentY = animatedY.get();
|
|
75
|
+
|
|
76
|
+
let closestSnap = snapsToPixels[0];
|
|
77
|
+
let minDistance = Math.abs(currentY - snapsToPixels[0]);
|
|
78
|
+
|
|
79
|
+
snapsToPixels.forEach(snapPx => {
|
|
80
|
+
const distance = Math.abs(currentY - snapPx);
|
|
81
|
+
if (distance < minDistance) {
|
|
82
|
+
minDistance = distance;
|
|
83
|
+
closestSnap = snapPx;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return closestSnap;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const jumpTo = (snapPx: number) => {
|
|
91
|
+
const collapsed = snapPx === swipeviewHeightPx;
|
|
92
|
+
const nextSnap = getPixelToSnap(snapPx);
|
|
93
|
+
animate(
|
|
94
|
+
scope.current,
|
|
95
|
+
{ y: snapPx },
|
|
96
|
+
{
|
|
97
|
+
...animation,
|
|
98
|
+
onComplete: () => {
|
|
99
|
+
onSnapComplete(currentSnap, nextSnap, collapsed);
|
|
100
|
+
setCurrentSnap(nextSnap);
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
nextSnap,
|
|
107
|
+
collapsed,
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const resetControls = () => {
|
|
112
|
+
setCurrentSnap(getInitSnap());
|
|
113
|
+
return 0;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
scope,
|
|
118
|
+
animatedY,
|
|
119
|
+
animate,
|
|
120
|
+
swipeviewHeightPx,
|
|
121
|
+
initSnapInPixels,
|
|
122
|
+
contentHeight,
|
|
123
|
+
currentSnap,
|
|
124
|
+
displayOverlay,
|
|
125
|
+
opacityOverlay,
|
|
126
|
+
getSnapToPixel,
|
|
127
|
+
jumpTo,
|
|
128
|
+
defineNextSnapByPosition,
|
|
129
|
+
resetControls,
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { MotionValue, useMotionValue, useTransform } from 'framer-motion';
|
|
3
|
+
import { useCheckIsMobile } from '../../utils/useCheckIsMobile';
|
|
4
|
+
import { ModalSheetControl } from '../components';
|
|
5
|
+
|
|
6
|
+
export interface UseSnapSetupReturn {
|
|
7
|
+
modalSheetControl: React.RefObject<ModalSheetControl>;
|
|
8
|
+
maxSwipeableViewHeight: number;
|
|
9
|
+
isOpen: boolean;
|
|
10
|
+
|
|
11
|
+
isMobile: boolean;
|
|
12
|
+
|
|
13
|
+
snaps: number[];
|
|
14
|
+
initialSnap: number;
|
|
15
|
+
shouldAutoExpand: boolean;
|
|
16
|
+
|
|
17
|
+
currentSnapPercentage: number;
|
|
18
|
+
handleSnapChange: (snapPercentage: number) => void;
|
|
19
|
+
|
|
20
|
+
animationKey: MotionValue<number>;
|
|
21
|
+
mobileHeaderHeight: MotionValue<number>;
|
|
22
|
+
|
|
23
|
+
snap43Percent: number;
|
|
24
|
+
snap100Percent: number;
|
|
25
|
+
|
|
26
|
+
shouldShowHeader: boolean;
|
|
27
|
+
shouldShowScrolled: boolean;
|
|
28
|
+
isFullView: boolean;
|
|
29
|
+
isPartialView: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseSnapSetupProps {
|
|
33
|
+
isFloatingChatOpen: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const useSnapSetup = ({ isFloatingChatOpen }: UseSnapSetupProps): UseSnapSetupReturn => {
|
|
37
|
+
const maxSwipeableViewHeight = 89;
|
|
38
|
+
const modalSheetControl = useRef<ModalSheetControl>(null);
|
|
39
|
+
const { viewportWidth } = useCheckIsMobile();
|
|
40
|
+
|
|
41
|
+
const isMobile = viewportWidth !== undefined && viewportWidth < 512;
|
|
42
|
+
|
|
43
|
+
const shouldAutoExpand = Boolean(
|
|
44
|
+
isMobile &&
|
|
45
|
+
(window as Window & { _spiffy?: { selectedCustomizeOption?: string } })?._spiffy
|
|
46
|
+
?.selectedCustomizeOption,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const snaps = shouldAutoExpand ? [43, 100] : [0, 43, 100];
|
|
50
|
+
const initialSnap = 1;
|
|
51
|
+
|
|
52
|
+
const [currentSnapPercentage, setCurrentSnapPercentage] = useState<number>(
|
|
53
|
+
snaps[initialSnap] || 43,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const animationKey = useMotionValue(0);
|
|
57
|
+
|
|
58
|
+
const snap43Percent = ((100 - 43) / 100) * window.innerHeight;
|
|
59
|
+
const snap100Percent = 0;
|
|
60
|
+
|
|
61
|
+
const mobileHeaderHeight = useTransform(animationKey, [snap100Percent, snap43Percent], [56, 0]);
|
|
62
|
+
|
|
63
|
+
const [shouldShowHeader, setShouldShowHeader] = useState(false);
|
|
64
|
+
const [shouldShowScrolled, setShouldShowScrolled] = useState(false);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const unsubscribe = animationKey.on('change', value => {
|
|
68
|
+
setShouldShowHeader(value < snap43Percent - 10);
|
|
69
|
+
setShouldShowScrolled(value > snap43Percent);
|
|
70
|
+
});
|
|
71
|
+
return unsubscribe;
|
|
72
|
+
}, [animationKey, snap43Percent]);
|
|
73
|
+
|
|
74
|
+
const isFullView = isMobile && currentSnapPercentage === 100;
|
|
75
|
+
const isPartialView = isMobile && currentSnapPercentage >= 0 && currentSnapPercentage <= 43;
|
|
76
|
+
|
|
77
|
+
const handleSnapChange = (snapPercentage: number) => {
|
|
78
|
+
setCurrentSnapPercentage(snapPercentage);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const isOpen =
|
|
82
|
+
(window as Window & { _spiffy?: { selectedCustomizeOption?: string } })?._spiffy
|
|
83
|
+
?.selectedCustomizeOption === 'SalesAgent'
|
|
84
|
+
? true
|
|
85
|
+
: Boolean(isFloatingChatOpen);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
modalSheetControl,
|
|
89
|
+
maxSwipeableViewHeight,
|
|
90
|
+
isOpen,
|
|
91
|
+
isMobile,
|
|
92
|
+
snaps,
|
|
93
|
+
initialSnap,
|
|
94
|
+
shouldAutoExpand,
|
|
95
|
+
currentSnapPercentage,
|
|
96
|
+
handleSnapChange,
|
|
97
|
+
animationKey,
|
|
98
|
+
mobileHeaderHeight,
|
|
99
|
+
snap43Percent,
|
|
100
|
+
snap100Percent,
|
|
101
|
+
shouldShowHeader,
|
|
102
|
+
shouldShowScrolled,
|
|
103
|
+
isFullView,
|
|
104
|
+
isPartialView,
|
|
105
|
+
};
|
|
106
|
+
};
|
|
@@ -54,7 +54,6 @@ export const PromptCarousel = ({
|
|
|
54
54
|
isAnimated: isAnimatedValue,
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
console.log('PromptCarousel', promptButtonTexts, isLoading);
|
|
58
57
|
const { visibleButtonsFirstRow, visibleButtonsSecondRow } = useCarouselButtons({
|
|
59
58
|
promptButtonTexts,
|
|
60
59
|
shouldShowTwoRows: shouldShowTwoRowsValue,
|
|
@@ -8,6 +8,7 @@ import { WidgetWrapperVariant } from '../WidgetWrapper';
|
|
|
8
8
|
import { PromptCarouselRows } from '../PromptCarousel';
|
|
9
9
|
import { PromptButtonVariant } from '../PromptButton';
|
|
10
10
|
import { WidgetWrapperWithTitle } from '../WidgetWrapperWithTitle/WidgetWrapperWithTitle';
|
|
11
|
+
import { useSocialProofCount } from './hooks';
|
|
11
12
|
|
|
12
13
|
export const SocialProof = ({
|
|
13
14
|
baseProps,
|
|
@@ -15,7 +16,7 @@ export const SocialProof = ({
|
|
|
15
16
|
widgetStyleProps,
|
|
16
17
|
widgetEventProps,
|
|
17
18
|
}: SocialProofProps) => {
|
|
18
|
-
const { id, testId, className, style } = baseProps ?? {};
|
|
19
|
+
const { id, testId, className, style, pageVariant, countKey } = baseProps ?? {};
|
|
19
20
|
|
|
20
21
|
const {
|
|
21
22
|
theme = Theme.GLOBAL_CUSTOM,
|
|
@@ -32,7 +33,6 @@ export const SocialProof = ({
|
|
|
32
33
|
} = widgetStyleProps ?? {};
|
|
33
34
|
|
|
34
35
|
const {
|
|
35
|
-
numberOfCustomersText,
|
|
36
36
|
customerQueryText,
|
|
37
37
|
primaryButtonText,
|
|
38
38
|
secondaryButtonTitleText,
|
|
@@ -43,6 +43,12 @@ export const SocialProof = ({
|
|
|
43
43
|
titleLabel,
|
|
44
44
|
} = widgetContentProps ?? {};
|
|
45
45
|
|
|
46
|
+
const numberOfCustomersText = useSocialProofCount({
|
|
47
|
+
pageVariant,
|
|
48
|
+
countKey,
|
|
49
|
+
id,
|
|
50
|
+
});
|
|
51
|
+
|
|
46
52
|
const { handlePrimaryButtonClick, handleSecondaryButtonClick, handleTextFieldClick } =
|
|
47
53
|
widgetEventProps ?? {};
|
|
48
54
|
|