@envive-ai/react-toolkit-v3 0.3.26 → 0.3.28
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.ts +3 -3
- package/dist/CSSVariablesEditor/CssVariablesEditorComponent.d.cts +2 -2
- package/dist/Carousel/Carousel.cjs +1 -1
- package/dist/Carousel/Carousel.d.cts +2 -2
- package/dist/Carousel/Carousel.d.ts +2 -2
- package/dist/Carousel/Carousel.js +1 -1
- package/dist/Carousel/components/Container.cjs +2 -2
- package/dist/Carousel/components/Container.js +2 -2
- package/dist/ChatFooter/ChatFooter.cjs +3 -1
- package/dist/ChatFooter/ChatFooter.d.cts +2 -2
- package/dist/ChatFooter/ChatFooter.d.ts +2 -2
- package/dist/ChatFooter/ChatFooter.js +3 -1
- package/dist/ChatFooter/components/Layout.cjs +4 -4
- package/dist/ChatFooter/components/Layout.d.cts +1 -0
- package/dist/ChatFooter/components/Layout.d.ts +1 -0
- package/dist/ChatFooter/components/Layout.js +4 -4
- package/dist/ChatFooter/components/index.d.cts +5 -3
- package/dist/ChatFooter/components/index.d.ts +7 -5
- package/dist/ChatFooter/types/types.d.cts +3 -2
- package/dist/ChatFooter/types/types.d.ts +3 -2
- package/dist/ChatHeader/ChatHeader.d.cts +2 -2
- package/dist/ChatHeader/ChatHeader.d.ts +2 -2
- package/dist/ChatHeader/components/Handle.cjs +2 -2
- package/dist/ChatHeader/components/Handle.js +2 -2
- package/dist/ChatHeader/components/Toggle.cjs +3 -3
- package/dist/ChatHeader/components/Toggle.js +3 -3
- package/dist/ChatPreview/ChatPreview.cjs +1 -1
- package/dist/ChatPreview/ChatPreview.d.cts +2 -2
- package/dist/ChatPreview/ChatPreview.d.ts +2 -2
- package/dist/ChatPreview/ChatPreview.js +1 -1
- package/dist/ChatPreviewComparison/ChatPreviewComparison.cjs +1 -1
- package/dist/ChatPreviewComparison/ChatPreviewComparison.d.cts +2 -2
- package/dist/ChatPreviewComparison/ChatPreviewComparison.d.ts +2 -2
- package/dist/ChatPreviewComparison/ChatPreviewComparison.js +1 -1
- package/dist/ChatPreviewComparison/components/Headline.cjs +2 -2
- package/dist/ChatPreviewComparison/components/Headline.js +2 -2
- package/dist/ChatPreviewComparison/components/Layout.cjs +4 -4
- package/dist/ChatPreviewComparison/components/Layout.js +4 -4
- package/dist/ChatPreviewComparison/components/Message.cjs +2 -2
- package/dist/ChatPreviewComparison/components/Message.js +2 -2
- package/dist/ChatPreviewLoading/ChatPreviewLoading.d.cts +2 -2
- package/dist/ChatPreviewLoading/ChatPreviewLoading.d.ts +2 -2
- package/dist/Container/Container.d.cts +56 -56
- package/dist/Container/Container.d.ts +220 -220
- package/dist/DesignTokens/DesignTokensComponent.d.ts +2 -2
- package/dist/Disclaimer/components/Container.cjs +2 -2
- package/dist/Disclaimer/components/Container.js +2 -2
- package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.cts +2 -2
- package/dist/DocumentRetrievalCard/DocumentRetrievalCard.d.ts +2 -2
- package/dist/DocumentRetrievalCard/components/Layout.cjs +2 -2
- package/dist/DocumentRetrievalCard/components/Layout.js +2 -2
- package/dist/DocumentRetrievalCard/components/ViewArticleButton/components/Icon.cjs +1 -1
- package/dist/DocumentRetrievalCard/components/ViewArticleButton/components/Icon.js +1 -1
- package/dist/FloatingButton/FloatingButton.d.cts +2 -2
- package/dist/FloatingButton/FloatingButton.d.ts +2 -2
- package/dist/FloatingChat/FloatingChat.d.cts +2 -2
- package/dist/FloatingChat/FloatingChat.d.ts +2 -2
- package/dist/FloatingChat/components/ChatMessages.cjs +1 -1
- package/dist/FloatingChat/components/ChatMessages.js +1 -1
- package/dist/FloatingChat/components/Layout.cjs +3 -3
- package/dist/FloatingChat/components/Layout.js +3 -3
- package/dist/FloatingChat/components/ResultsGridView.cjs +1 -1
- package/dist/FloatingChat/components/ResultsGridView.js +1 -1
- package/dist/FloatingChat/components/SalesAgentBadgeContent.cjs +6 -2
- package/dist/FloatingChat/components/SalesAgentBadgeContent.js +6 -2
- package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.cjs +2 -1
- package/dist/FloatingChat/components/SalesAgentProductCardsCarousel.js +2 -1
- package/dist/FullPageSalesAgent/FullPageSalesAgent.cjs +126 -27
- package/dist/FullPageSalesAgent/FullPageSalesAgent.d.cts +5 -7
- package/dist/FullPageSalesAgent/FullPageSalesAgent.d.ts +5 -7
- package/dist/FullPageSalesAgent/FullPageSalesAgent.js +127 -28
- package/dist/FullPageSalesAgent/components/Layout.cjs +34 -27
- package/dist/FullPageSalesAgent/components/Layout.js +34 -27
- package/dist/FullPageSalesAgent/components/WelcomeOverlay.cjs +109 -0
- package/dist/FullPageSalesAgent/components/WelcomeOverlay.js +107 -0
- package/dist/FullPageSalesAgent/components/index.cjs +1 -0
- package/dist/FullPageSalesAgent/components/index.js +1 -0
- package/dist/FullPageSalesAgent/hooks/useGetFooterStyles.cjs +16 -6
- package/dist/FullPageSalesAgent/hooks/useGetFooterStyles.js +16 -6
- package/dist/FullPageSalesAgent/hooks/useWelcomeOverlayProducts.cjs +44 -0
- package/dist/FullPageSalesAgent/hooks/useWelcomeOverlayProducts.js +42 -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/ImageGallery/components/Layout.cjs +1 -1
- package/dist/ImageGallery/components/Layout.js +1 -1
- package/dist/MarkdownProcessor/MarkdownProcessor.d.cts +2 -2
- package/dist/MarkdownProcessor/MarkdownProcessor.d.ts +2 -2
- package/dist/Message/components/LinkButton.cjs +1 -1
- package/dist/Message/components/LinkButton.js +1 -1
- package/dist/OrderLookupCard/OrderLookupCard.cjs +1 -1
- package/dist/OrderLookupCard/OrderLookupCard.js +1 -1
- package/dist/ProductCard/ProductCard.cjs +2 -2
- package/dist/ProductCard/ProductCard.d.cts +2 -2
- package/dist/ProductCard/ProductCard.d.ts +2 -2
- package/dist/ProductCard/ProductCard.js +2 -2
- package/dist/PromptButton/PromptButton.d.cts +2 -2
- package/dist/PromptButton/PromptButton.d.ts +2 -2
- package/dist/PromptButton/hooks/useGetLayoutBaseProperties.cjs +1 -1
- package/dist/PromptButton/hooks/useGetLayoutBaseProperties.js +1 -1
- package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.cts +2 -2
- package/dist/PromptButtonCarouselWithImage/PromptButtonCarouselWithImage.d.ts +2 -2
- package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.cjs +1 -1
- package/dist/PromptButtonCarouselWithImage/components/PromptButtonsCarousel.js +1 -1
- package/dist/PromptCarousel/PromptCarousel.cjs +3 -3
- package/dist/PromptCarousel/PromptCarousel.d.cts +2 -2
- package/dist/PromptCarousel/PromptCarousel.d.ts +2 -2
- package/dist/PromptCarousel/PromptCarousel.js +3 -3
- package/dist/ReviewCard/ReviewCard.d.ts +2 -2
- package/dist/ReviewCard/components/Container.cjs +2 -2
- package/dist/ReviewCard/components/Container.js +2 -2
- package/dist/ReviewCard/components/ReadMoreButton.cjs +1 -1
- package/dist/ReviewCard/components/ReadMoreButton.js +1 -1
- package/dist/ReviewCard/components/index.d.cts +4 -4
- 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/Container.cjs +2 -2
- package/dist/SalesAgentProductCard/components/Container.js +2 -2
- package/dist/SalesAgentProductCard/components/Price.cjs +2 -2
- package/dist/SalesAgentProductCard/components/Price.js +2 -2
- package/dist/SalesAgentProductCard/components/Rate.cjs +1 -1
- package/dist/SalesAgentProductCard/components/Rate.js +1 -1
- package/dist/SalesAgentProductCard/components/index.d.cts +8 -8
- package/dist/SalesAgentProductCard/components/index.d.ts +8 -8
- package/dist/SocialProof/SocialProof.cjs +5 -4
- package/dist/SocialProof/SocialProof.d.cts +2 -2
- package/dist/SocialProof/SocialProof.d.ts +2 -2
- package/dist/SocialProof/SocialProof.js +5 -4
- package/dist/SocialProof/components/Headline.cjs +4 -4
- package/dist/SocialProof/components/Headline.js +4 -4
- package/dist/SocialProof/components/LayoutFourHorizontal.cjs +1 -1
- package/dist/SocialProof/components/LayoutFourHorizontal.js +1 -1
- package/dist/SocialProof/components/LayoutSingle.cjs +3 -4
- package/dist/SocialProof/components/LayoutSingle.js +3 -4
- package/dist/SocialProof/components/Subheadline.cjs +1 -1
- package/dist/SocialProof/components/Subheadline.js +1 -1
- package/dist/SocialProof/hooks/useSocialProofCount.cjs +3 -2
- package/dist/SocialProof/hooks/useSocialProofCount.d.cts +2 -0
- package/dist/SocialProof/hooks/useSocialProofCount.d.ts +2 -0
- package/dist/SocialProof/hooks/useSocialProofCount.js +3 -2
- package/dist/SocialProof/types/types.d.cts +5 -0
- package/dist/SocialProof/types/types.d.ts +5 -0
- package/dist/SparkleAnimation/SparkleAnimation.d.cts +2 -2
- package/dist/SparkleAnimation/SparkleAnimation.d.ts +2 -2
- package/dist/Stack/Stack.d.cts +2 -2
- package/dist/Stack/Stack.d.ts +2 -2
- package/dist/TextField/TextField.cjs +10 -3
- package/dist/TextField/TextField.d.cts +1 -0
- package/dist/TextField/TextField.d.ts +1 -0
- package/dist/TextField/TextField.js +10 -3
- package/dist/TextField/components/Input.cjs +31 -2
- package/dist/TextField/components/Input.js +31 -2
- package/dist/TextField/components/Layout.cjs +7 -2
- package/dist/TextField/components/Layout.js +7 -2
- package/dist/TextField/hooks/useAutoResize.cjs +17 -0
- package/dist/TextField/hooks/useAutoResize.js +16 -0
- package/dist/TextField/hooks/usePlaceholderAnimation.cjs +58 -0
- package/dist/TextField/hooks/usePlaceholderAnimation.js +57 -0
- package/dist/TextField/hooks/useVoiceInput.cjs +9 -1
- package/dist/TextField/hooks/useVoiceInput.js +9 -1
- package/dist/TextField/types/index.d.cts +19 -1
- package/dist/TextField/types/index.d.ts +19 -1
- package/dist/TitledPromptCarousel/TitledPromptCarousel.cjs +1 -1
- package/dist/TitledPromptCarousel/TitledPromptCarousel.d.cts +2 -2
- package/dist/TitledPromptCarousel/TitledPromptCarousel.d.ts +2 -2
- package/dist/TitledPromptCarousel/TitledPromptCarousel.js +1 -1
- package/dist/Tokens/index.cjs +1 -1
- package/dist/Tokens/index.js +1 -1
- package/dist/TypingAnimation/TypingAnimation.cjs +1 -1
- package/dist/TypingAnimation/TypingAnimation.d.cts +2 -2
- package/dist/TypingAnimation/TypingAnimation.d.ts +2 -2
- package/dist/TypingAnimation/TypingAnimation.js +1 -1
- package/dist/TypingAnimation/hooks/useGetTypographyVariant.cjs +1 -1
- package/dist/TypingAnimation/hooks/useGetTypographyVariant.js +1 -1
- package/dist/Typography/Typography.d.cts +4 -4
- package/dist/Typography/Typography.d.ts +4 -4
- package/dist/WelcomeMessage/components/Container.cjs +2 -2
- package/dist/WelcomeMessage/components/Container.js +2 -2
- package/dist/WidgetTextField/WidgetTextField.cjs +4 -4
- package/dist/WidgetTextField/WidgetTextField.d.cts +2 -2
- package/dist/WidgetTextField/WidgetTextField.d.ts +2 -2
- package/dist/WidgetTextField/WidgetTextField.js +1 -1
- package/dist/WidgetTextField/components/Container.cjs +2 -2
- package/dist/WidgetTextField/components/Container.js +2 -2
- package/dist/WidgetTextField/components/Icon.cjs +1 -1
- package/dist/WidgetTextField/components/Icon.js +1 -1
- package/dist/WidgetWrapper/WidgetWrapper.d.cts +2 -2
- package/dist/WidgetWrapper/WidgetWrapper.d.ts +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/ChatFooter.tsx +2 -0
- package/src/components/ChatFooter/components/Layout.tsx +3 -1
- package/src/components/ChatFooter/components/TextField.tsx +4 -1
- package/src/components/ChatFooter/types/types.ts +3 -2
- package/src/components/FloatingChat/components/SalesAgentBadgeContent.tsx +8 -0
- package/src/components/FloatingChat/components/SalesAgentProductCardsCarousel.tsx +3 -0
- package/src/components/FullPageSalesAgent/FullPageSalesAgent.tsx +207 -49
- package/src/components/FullPageSalesAgent/components/Layout.tsx +10 -3
- package/src/components/FullPageSalesAgent/components/WelcomeOverlay.tsx +143 -0
- package/src/components/FullPageSalesAgent/components/index.ts +2 -0
- package/src/components/FullPageSalesAgent/hooks/useGetFooterStyles.ts +15 -5
- package/src/components/FullPageSalesAgent/hooks/useWelcomeOverlayProducts.ts +52 -0
- package/src/components/PromptButton/hooks/useGetLayoutBaseProperties.ts +3 -1
- package/src/components/SalesAgentProductCard/components/Price.tsx +2 -2
- package/src/components/SalesAgentProductCard/components/Rate.tsx +1 -1
- package/src/components/SocialProof/SocialProof.tsx +4 -2
- package/src/components/SocialProof/components/Headline.tsx +1 -1
- package/src/components/SocialProof/components/LayoutSingle.tsx +6 -5
- package/src/components/SocialProof/hooks/useSocialProofCount.ts +8 -1
- package/src/components/SocialProof/types/types.ts +6 -0
- package/src/components/TextField/TextField.tsx +17 -3
- package/src/components/TextField/__tests__/VoiceInputButton.test.tsx +22 -58
- package/src/components/TextField/components/Input.tsx +43 -9
- package/src/components/TextField/components/Layout.tsx +7 -1
- package/src/components/TextField/hooks/useAutoResize.ts +14 -0
- package/src/components/TextField/hooks/usePlaceholderAnimation.ts +67 -0
- package/src/components/TextField/hooks/useTextFieldSubmit.ts +1 -1
- package/src/components/TextField/hooks/useVoiceInput.ts +8 -0
- package/src/components/TextField/types/index.ts +19 -1
|
@@ -36,6 +36,7 @@ export const SocialProof = ({
|
|
|
36
36
|
} = widgetStyleProps ?? {};
|
|
37
37
|
|
|
38
38
|
const {
|
|
39
|
+
numberOfCustomersText,
|
|
39
40
|
customerQueryText,
|
|
40
41
|
primaryButtonText,
|
|
41
42
|
secondaryButtonTitleText,
|
|
@@ -47,9 +48,10 @@ export const SocialProof = ({
|
|
|
47
48
|
voiceInputEnabled,
|
|
48
49
|
} = widgetContentProps ?? {};
|
|
49
50
|
|
|
50
|
-
const
|
|
51
|
+
const socialProofCounter = useSocialProofCount({
|
|
51
52
|
pageVariant,
|
|
52
53
|
countKey,
|
|
54
|
+
numberOfCustomersText,
|
|
53
55
|
id,
|
|
54
56
|
});
|
|
55
57
|
|
|
@@ -91,7 +93,7 @@ export const SocialProof = ({
|
|
|
91
93
|
const headline = (
|
|
92
94
|
<SocialProofComponents.Headline
|
|
93
95
|
theme={finalTheme}
|
|
94
|
-
socialProofCounter={
|
|
96
|
+
socialProofCounter={socialProofCounter}
|
|
95
97
|
socialProofHeadline={customerQueryText}
|
|
96
98
|
primaryPromptButtonText={primaryButtonText}
|
|
97
99
|
primaryPromptButtonVariant={primaryButtonVariant}
|
|
@@ -48,7 +48,7 @@ export const Headline = forwardRef<HTMLDivElement, HeadlineProps>(
|
|
|
48
48
|
<Stack
|
|
49
49
|
ref={ref}
|
|
50
50
|
gap="4"
|
|
51
|
-
className={classNames('envive-tw-h-fit', className)}
|
|
51
|
+
className={classNames('envive-tw-h-fit envive-tw-overflow-x-auto', className)}
|
|
52
52
|
>
|
|
53
53
|
<Stack gap="2">
|
|
54
54
|
<Container>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { cloneElement } from 'react';
|
|
2
|
-
import { useCheckIsMobile } from '../../utils/useCheckIsMobile';
|
|
3
2
|
import { ImageGalleryImage, ImageGalleryLayout } from '../../ImageGallery/types/types';
|
|
4
|
-
import { useGetContentSize } from '../../utils/useGetContentSize';
|
|
5
|
-
import { ImageGallery } from './ImageGallery';
|
|
6
3
|
import { Stack } from '../../Stack';
|
|
4
|
+
import { useCheckIsMobile } from '../../utils/useCheckIsMobile';
|
|
5
|
+
import { useGetContentSize } from '../../utils/useGetContentSize';
|
|
7
6
|
import { DynamicLayout } from '../types/types';
|
|
8
|
-
import { calculateWidthImageGallery } from '../utils/functions';
|
|
9
7
|
import { HeadlineProps } from './Headline';
|
|
8
|
+
import { ImageGallery } from './ImageGallery';
|
|
10
9
|
|
|
11
10
|
export type LayoutSingleProps = {
|
|
12
11
|
dynamicLayout: DynamicLayout;
|
|
@@ -35,7 +34,9 @@ export const LayoutSingle = ({
|
|
|
35
34
|
const { height, ref } = useGetContentSize({
|
|
36
35
|
heightDefault: 245,
|
|
37
36
|
});
|
|
38
|
-
|
|
37
|
+
|
|
38
|
+
// For single image, image width must be fixed 80px.
|
|
39
|
+
const imageGalleryWidth = 80;
|
|
39
40
|
|
|
40
41
|
return (
|
|
41
42
|
<Stack
|
|
@@ -9,13 +9,16 @@ type SocialProofCount = Record<string, Record<string, number>>;
|
|
|
9
9
|
|
|
10
10
|
interface UseSocialProofCountParams {
|
|
11
11
|
pageVariant?: PageVariant;
|
|
12
|
+
numberOfCustomersText?: string;
|
|
12
13
|
countKey?: string;
|
|
13
14
|
id?: string;
|
|
14
15
|
}
|
|
16
|
+
const DEFAULT_CUSTOMER_TEXT = '{count} customers';
|
|
15
17
|
|
|
16
18
|
export const useSocialProofCount = ({
|
|
17
19
|
pageVariant,
|
|
18
20
|
countKey,
|
|
21
|
+
numberOfCustomersText = DEFAULT_CUSTOMER_TEXT,
|
|
19
22
|
id,
|
|
20
23
|
}: UseSocialProofCountParams): string => {
|
|
21
24
|
const [customerCount, setCustomerCount] = useState<number>(() =>
|
|
@@ -63,5 +66,9 @@ export const useSocialProofCount = ({
|
|
|
63
66
|
}
|
|
64
67
|
}, [pageVariant, countKey, id]);
|
|
65
68
|
|
|
66
|
-
|
|
69
|
+
// For safety ensure that the replacement string occurs in the string for now
|
|
70
|
+
const finalCustomerText = numberOfCustomersText.includes('{count}')
|
|
71
|
+
? numberOfCustomersText
|
|
72
|
+
: DEFAULT_CUSTOMER_TEXT;
|
|
73
|
+
return finalCustomerText.replace('{count}', customerCount.toString());
|
|
67
74
|
};
|
|
@@ -82,6 +82,12 @@ export type WidgetContentProps = {
|
|
|
82
82
|
*/
|
|
83
83
|
customerQueryText?: string;
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Text displaying how many customers asked the customerQueryText question (e.g., "123 customers")
|
|
87
|
+
* Utilizes replacement to replace the value {count} with the replaced value
|
|
88
|
+
*/
|
|
89
|
+
numberOfCustomersText?: string;
|
|
90
|
+
|
|
85
91
|
/**
|
|
86
92
|
* Text label for the primary prompt button.
|
|
87
93
|
* Displayed prominently in the headline section.
|
|
@@ -8,6 +8,7 @@ import { useGetSkeletonProperties } from './hooks/useGetSkeletonProperties';
|
|
|
8
8
|
import { useTextFieldFocus } from './hooks/useTextFieldFocus';
|
|
9
9
|
import { useTextFieldSubmit } from './hooks/useTextFieldSubmit';
|
|
10
10
|
import { useTextFieldValue } from './hooks/useTextFieldValue';
|
|
11
|
+
import { usePlaceholderAnimation } from './hooks/usePlaceholderAnimation';
|
|
11
12
|
import { useVoiceInput } from './hooks/useVoiceInput';
|
|
12
13
|
import type { TextFieldProps } from './types';
|
|
13
14
|
|
|
@@ -30,10 +31,13 @@ export const TextField = ({
|
|
|
30
31
|
style,
|
|
31
32
|
ariaLabel,
|
|
32
33
|
isLoading = false,
|
|
34
|
+
multiline = false,
|
|
33
35
|
enableVoiceInput = false,
|
|
34
36
|
onTranscriptionStarted,
|
|
35
37
|
onTranscriptionCompleted,
|
|
36
38
|
}: TextFieldProps): JSX.Element => {
|
|
39
|
+
const resolvedPlaceholder = usePlaceholderAnimation(placeholder);
|
|
40
|
+
|
|
37
41
|
const { currentValue, hasValue, handleChange, resetValue } = useTextFieldValue(
|
|
38
42
|
controlledValue,
|
|
39
43
|
onChange,
|
|
@@ -48,7 +52,7 @@ export const TextField = ({
|
|
|
48
52
|
resetValue,
|
|
49
53
|
);
|
|
50
54
|
|
|
51
|
-
const
|
|
55
|
+
const handleVoiceTranscriptCompleted = useCallback(
|
|
52
56
|
(transcript: string) => {
|
|
53
57
|
resetValue();
|
|
54
58
|
handleChange(transcript);
|
|
@@ -57,6 +61,13 @@ export const TextField = ({
|
|
|
57
61
|
[handleChange, resetValue, onTranscriptionCompleted],
|
|
58
62
|
);
|
|
59
63
|
|
|
64
|
+
const handleVoiceTranscription = useCallback(
|
|
65
|
+
(transcript: string) => {
|
|
66
|
+
handleChange(transcript);
|
|
67
|
+
},
|
|
68
|
+
[handleChange],
|
|
69
|
+
);
|
|
70
|
+
|
|
60
71
|
const {
|
|
61
72
|
isListening,
|
|
62
73
|
handleToggleListening,
|
|
@@ -64,7 +75,8 @@ export const TextField = ({
|
|
|
64
75
|
browserSupportsSpeechRecognition,
|
|
65
76
|
} = useVoiceInput({
|
|
66
77
|
onTranscriptionStarted,
|
|
67
|
-
onTranscriptionCompleted:
|
|
78
|
+
onTranscriptionCompleted: handleVoiceTranscriptCompleted,
|
|
79
|
+
onTranscription: handleVoiceTranscription,
|
|
68
80
|
disabled,
|
|
69
81
|
});
|
|
70
82
|
|
|
@@ -80,7 +92,7 @@ export const TextField = ({
|
|
|
80
92
|
const input = (
|
|
81
93
|
<TextFieldComponents.Input
|
|
82
94
|
theme={resolvedTheme}
|
|
83
|
-
placeholder={isLoading ? '' :
|
|
95
|
+
placeholder={isLoading ? '' : resolvedPlaceholder}
|
|
84
96
|
value={isLoading ? '' : currentValue}
|
|
85
97
|
onChange={handleChange}
|
|
86
98
|
onKeyDown={handleKeyDown}
|
|
@@ -89,6 +101,7 @@ export const TextField = ({
|
|
|
89
101
|
disabled={disabled || isLoading}
|
|
90
102
|
ariaLabel={ariaLabel}
|
|
91
103
|
className={inputClassName}
|
|
104
|
+
multiline={multiline}
|
|
92
105
|
/>
|
|
93
106
|
);
|
|
94
107
|
|
|
@@ -130,6 +143,7 @@ export const TextField = ({
|
|
|
130
143
|
isFocused={isFocused}
|
|
131
144
|
hasValue={hasValue}
|
|
132
145
|
disabled={disabled}
|
|
146
|
+
multiline={multiline}
|
|
133
147
|
id={id}
|
|
134
148
|
testId={testId}
|
|
135
149
|
className={classNames(className, skeletonClass, disabled && 'envive-tw-cursor-not-allowed')}
|
|
@@ -57,83 +57,62 @@ vi.mock('jotai', () => ({
|
|
|
57
57
|
}));
|
|
58
58
|
|
|
59
59
|
describe('useVoiceInput', () => {
|
|
60
|
-
// Reset mocks before each test to ensure clean state
|
|
61
60
|
beforeEach(() => {
|
|
62
61
|
vi.clearAllMocks();
|
|
63
|
-
// Reset the implementation to default successful behavior
|
|
64
62
|
spies.mockStartListening.mockImplementation(() => Promise.resolve());
|
|
63
|
+
sharedState = '';
|
|
65
64
|
});
|
|
66
65
|
|
|
67
66
|
describe('Basic initialization', () => {
|
|
68
67
|
it('should initialize with isListening as false', () => {
|
|
69
68
|
const { result } = renderHook(() => useVoiceInput({}));
|
|
70
|
-
// Voice input should not be active when the hook first mounts
|
|
71
69
|
expect(result.current.isListening).toBe(false);
|
|
72
70
|
});
|
|
73
71
|
|
|
74
72
|
it('should report browser supports speech recognition', () => {
|
|
75
73
|
const { result } = renderHook(() => useVoiceInput({}));
|
|
76
|
-
// Check if the browser has speech recognition support
|
|
77
74
|
expect(result.current.browserSupportsSpeechRecognition).toBe(true);
|
|
78
75
|
});
|
|
79
76
|
});
|
|
80
77
|
|
|
81
|
-
describe('
|
|
82
|
-
it('should
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
await act(async () => {
|
|
86
|
-
// Call handleToggleListening to start listening
|
|
87
|
-
result.current.handleToggleListening();
|
|
88
|
-
});
|
|
78
|
+
describe('onTranscriptionStarted callback', () => {
|
|
79
|
+
it('should accept onTranscriptionStarted prop', () => {
|
|
80
|
+
const onTranscriptionStarted = vi.fn();
|
|
89
81
|
|
|
90
|
-
|
|
91
|
-
|
|
82
|
+
renderHook(() =>
|
|
83
|
+
useVoiceInput({
|
|
84
|
+
onTranscriptionStarted,
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
92
87
|
});
|
|
88
|
+
});
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
await act(async () => {
|
|
98
|
-
result.current.handleToggleListening();
|
|
99
|
-
});
|
|
90
|
+
describe('onTranscription callback', () => {
|
|
91
|
+
it('should accept onTranscription prop', () => {
|
|
92
|
+
const onTranscription = vi.fn();
|
|
100
93
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
94
|
+
renderHook(() =>
|
|
95
|
+
useVoiceInput({
|
|
96
|
+
onTranscription,
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
104
99
|
});
|
|
105
100
|
});
|
|
106
101
|
|
|
107
|
-
describe('
|
|
108
|
-
it('should
|
|
102
|
+
describe('onTranscriptionCompleted callback', () => {
|
|
103
|
+
it('should accept onTranscriptionCompleted prop', () => {
|
|
109
104
|
const onTranscriptionCompleted = vi.fn();
|
|
110
105
|
|
|
111
|
-
|
|
106
|
+
renderHook(() =>
|
|
112
107
|
useVoiceInput({
|
|
113
108
|
onTranscriptionCompleted,
|
|
114
109
|
}),
|
|
115
110
|
);
|
|
116
|
-
|
|
117
|
-
// First toggle: start listening
|
|
118
|
-
await act(async () => {
|
|
119
|
-
result.current.handleToggleListening();
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Second toggle: stop listening - this should call onTranscript callback
|
|
123
|
-
await act(async () => {
|
|
124
|
-
result.current.handleToggleListening();
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// Verify that stopListening was called to stop speech recognition
|
|
128
|
-
expect(spies.mockStopListening).toHaveBeenCalled();
|
|
129
|
-
// Verify that the onTranscript callback was called with current transcript
|
|
130
|
-
expect(onTranscriptionCompleted).toHaveBeenCalledWith('');
|
|
131
111
|
});
|
|
132
112
|
});
|
|
133
113
|
|
|
134
114
|
describe('Mic permission handling', () => {
|
|
135
115
|
it('should handle mic permission denied error', async () => {
|
|
136
|
-
// Configure the mock to throw an error simulating permission denial
|
|
137
116
|
spies.mockStartListening.mockImplementationOnce(() => {
|
|
138
117
|
throw new Error('Permission denied');
|
|
139
118
|
});
|
|
@@ -144,43 +123,28 @@ describe('useVoiceInput', () => {
|
|
|
144
123
|
result.current.handleToggleListening();
|
|
145
124
|
});
|
|
146
125
|
|
|
147
|
-
// The hook should catch the error and set isListening back to false
|
|
148
126
|
expect(result.current.isListening).toBe(false);
|
|
149
127
|
});
|
|
150
128
|
|
|
151
129
|
it('should not start listening when disabled', () => {
|
|
152
|
-
// When disabled prop is true, the hook should not attempt to start listening
|
|
153
130
|
const { result } = renderHook(() => useVoiceInput({ disabled: true }));
|
|
154
131
|
|
|
155
132
|
result.current.handleToggleListening();
|
|
156
133
|
|
|
157
|
-
// isListening should remain false
|
|
158
134
|
expect(result.current.isListening).toBe(false);
|
|
159
|
-
// Verify that startListening was never called
|
|
160
135
|
expect(spies.mockStartListening).not.toHaveBeenCalled();
|
|
161
136
|
});
|
|
162
137
|
});
|
|
163
138
|
|
|
164
139
|
describe('Abort listening', () => {
|
|
165
|
-
it('should abort listening
|
|
140
|
+
it('should abort listening when handleAbortListening is called', async () => {
|
|
166
141
|
const { result } = renderHook(() => useVoiceInput({}));
|
|
167
142
|
|
|
168
|
-
// Start listening first
|
|
169
|
-
await act(async () => {
|
|
170
|
-
result.current.handleToggleListening();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
expect(result.current.isListening).toBe(true);
|
|
174
|
-
|
|
175
|
-
// Call abort to stop listening immediately without collecting transcript
|
|
176
143
|
await act(async () => {
|
|
177
144
|
result.current.handleAbortListening();
|
|
178
145
|
});
|
|
179
146
|
|
|
180
|
-
// Verify abortListening was called to immediately stop speech recognition
|
|
181
147
|
expect(spies.mockAbortListening).toHaveBeenCalled();
|
|
182
|
-
// Verify state is reset to false
|
|
183
|
-
expect(result.current.isListening).toBe(false);
|
|
184
148
|
});
|
|
185
149
|
});
|
|
186
150
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import classNames from 'classnames';
|
|
2
2
|
import { getInputClasses } from '../utils/getInputClasses';
|
|
3
3
|
import { createInputChangeHandler } from '../utils/createInputChangeHandler';
|
|
4
|
+
import { useAutoResize } from '../hooks/useAutoResize';
|
|
4
5
|
import { Theme } from '../../../../tokens/theme/theme';
|
|
5
6
|
|
|
6
7
|
type InputProps = {
|
|
@@ -8,10 +9,11 @@ type InputProps = {
|
|
|
8
9
|
placeholder: string;
|
|
9
10
|
value?: string;
|
|
10
11
|
onChange?: (value: string) => void;
|
|
11
|
-
onKeyDown?: (event: React.KeyboardEvent<
|
|
12
|
-
onFocus?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
13
|
-
onBlur?: (event: React.FocusEvent<HTMLInputElement>) => void;
|
|
12
|
+
onKeyDown?: (event: React.KeyboardEvent<HTMLElement>) => void;
|
|
13
|
+
onFocus?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
14
|
+
onBlur?: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
14
15
|
disabled?: boolean;
|
|
16
|
+
multiline?: boolean;
|
|
15
17
|
id?: string;
|
|
16
18
|
testId?: string;
|
|
17
19
|
className?: string;
|
|
@@ -28,6 +30,7 @@ export const Input = ({
|
|
|
28
30
|
onFocus,
|
|
29
31
|
onBlur,
|
|
30
32
|
disabled = false,
|
|
33
|
+
multiline = false,
|
|
31
34
|
id,
|
|
32
35
|
testId,
|
|
33
36
|
className,
|
|
@@ -36,6 +39,42 @@ export const Input = ({
|
|
|
36
39
|
}: InputProps): JSX.Element => {
|
|
37
40
|
const { inputClasses } = getInputClasses(theme);
|
|
38
41
|
const handleChange = createInputChangeHandler(disabled, onChange);
|
|
42
|
+
const textareaRef = useAutoResize(multiline ? value : undefined);
|
|
43
|
+
|
|
44
|
+
const sharedClasses = classNames(
|
|
45
|
+
inputClasses,
|
|
46
|
+
'focus-visible:envive-tw-shadow-none focus-visible:envive-tw-outline-none',
|
|
47
|
+
className,
|
|
48
|
+
disabled && 'envive-tw-cursor-not-allowed',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (multiline) {
|
|
52
|
+
return (
|
|
53
|
+
<textarea
|
|
54
|
+
ref={textareaRef}
|
|
55
|
+
id={id}
|
|
56
|
+
data-testid={testId}
|
|
57
|
+
placeholder={placeholder}
|
|
58
|
+
value={value}
|
|
59
|
+
rows={1}
|
|
60
|
+
onChange={e => {
|
|
61
|
+
if (!disabled) onChange?.(e.target.value);
|
|
62
|
+
}}
|
|
63
|
+
onKeyDown={e => {
|
|
64
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
65
|
+
onKeyDown?.(e);
|
|
66
|
+
}
|
|
67
|
+
}}
|
|
68
|
+
onFocus={onFocus}
|
|
69
|
+
onBlur={onBlur}
|
|
70
|
+
disabled={disabled}
|
|
71
|
+
className={sharedClasses}
|
|
72
|
+
style={{ resize: 'none', overflow: 'hidden', padding: 0, ...style }}
|
|
73
|
+
aria-label={ariaLabel}
|
|
74
|
+
aria-disabled={disabled}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
39
78
|
|
|
40
79
|
return (
|
|
41
80
|
<input
|
|
@@ -49,12 +88,7 @@ export const Input = ({
|
|
|
49
88
|
onFocus={onFocus}
|
|
50
89
|
onBlur={onBlur}
|
|
51
90
|
disabled={disabled}
|
|
52
|
-
className={
|
|
53
|
-
inputClasses,
|
|
54
|
-
'focus-visible:envive-tw-shadow-none focus-visible:envive-tw-outline-none',
|
|
55
|
-
className,
|
|
56
|
-
disabled && 'envive-tw-cursor-not-allowed',
|
|
57
|
-
)}
|
|
91
|
+
className={sharedClasses}
|
|
58
92
|
style={style}
|
|
59
93
|
aria-label={ariaLabel}
|
|
60
94
|
aria-disabled={disabled}
|
|
@@ -11,6 +11,7 @@ type LayoutProps = {
|
|
|
11
11
|
isFocused: boolean;
|
|
12
12
|
hasValue: boolean;
|
|
13
13
|
disabled?: boolean;
|
|
14
|
+
multiline?: boolean;
|
|
14
15
|
id?: string;
|
|
15
16
|
testId?: string;
|
|
16
17
|
className?: string;
|
|
@@ -25,6 +26,7 @@ export const Layout = ({
|
|
|
25
26
|
isFocused,
|
|
26
27
|
hasValue,
|
|
27
28
|
disabled = false,
|
|
29
|
+
multiline = false,
|
|
28
30
|
id,
|
|
29
31
|
testId,
|
|
30
32
|
className,
|
|
@@ -61,7 +63,11 @@ export const Layout = ({
|
|
|
61
63
|
disabled && containerDisabledClasses,
|
|
62
64
|
className,
|
|
63
65
|
)}
|
|
64
|
-
style={
|
|
66
|
+
style={
|
|
67
|
+
multiline
|
|
68
|
+
? { height: 'auto', minHeight: '40px', alignItems: 'flex-end', ...style }
|
|
69
|
+
: style
|
|
70
|
+
}
|
|
65
71
|
>
|
|
66
72
|
{input}
|
|
67
73
|
{sendIcon}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useAutoResize = (value: string | undefined): React.RefObject<HTMLTextAreaElement> => {
|
|
4
|
+
const ref = useRef<HTMLTextAreaElement>(null);
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const el = ref.current;
|
|
8
|
+
if (!el) return;
|
|
9
|
+
el.style.height = 'auto';
|
|
10
|
+
el.style.height = `${el.scrollHeight}px`;
|
|
11
|
+
}, [value]);
|
|
12
|
+
|
|
13
|
+
return ref;
|
|
14
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TYPING_DURATION = 400;
|
|
5
|
+
const DEFAULT_TRANSITION = 3000;
|
|
6
|
+
const CLEAR_PAUSE = 200;
|
|
7
|
+
|
|
8
|
+
export const usePlaceholderAnimation = (
|
|
9
|
+
placeholder: string | string[],
|
|
10
|
+
typingDuration = DEFAULT_TYPING_DURATION,
|
|
11
|
+
transition = DEFAULT_TRANSITION,
|
|
12
|
+
): string => {
|
|
13
|
+
const [current, setCurrent] = useState(() => (Array.isArray(placeholder) ? '' : placeholder));
|
|
14
|
+
const timeoutsRef = useRef<NodeJS.Timeout[]>([]);
|
|
15
|
+
const isMountedRef = useRef(true);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (!Array.isArray(placeholder)) {
|
|
19
|
+
setCurrent(placeholder);
|
|
20
|
+
return () => {};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (placeholder.length === 0) {
|
|
24
|
+
setCurrent('');
|
|
25
|
+
return () => {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isMountedRef.current = true;
|
|
29
|
+
timeoutsRef.current = [];
|
|
30
|
+
|
|
31
|
+
const delay = (ms: number): Promise<void> =>
|
|
32
|
+
new Promise(resolve => {
|
|
33
|
+
const id = setTimeout(resolve, ms);
|
|
34
|
+
timeoutsRef.current.push(id);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const animateChars = async (text: string) => {
|
|
38
|
+
const charDelay = text.length > 1 ? typingDuration / (text.length - 1) : 0;
|
|
39
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
40
|
+
await delay(charDelay);
|
|
41
|
+
if (isMountedRef.current) setCurrent(text.substring(0, i + 1));
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const run = async () => {
|
|
46
|
+
while (isMountedRef.current) {
|
|
47
|
+
for (const element of placeholder) {
|
|
48
|
+
if (!isMountedRef.current) return;
|
|
49
|
+
await animateChars(element);
|
|
50
|
+
await delay(transition);
|
|
51
|
+
if (isMountedRef.current) setCurrent('');
|
|
52
|
+
await delay(CLEAR_PAUSE);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
run();
|
|
58
|
+
|
|
59
|
+
return () => {
|
|
60
|
+
isMountedRef.current = false;
|
|
61
|
+
timeoutsRef.current.forEach(clearTimeout);
|
|
62
|
+
timeoutsRef.current = [];
|
|
63
|
+
};
|
|
64
|
+
}, [placeholder, typingDuration, transition]);
|
|
65
|
+
|
|
66
|
+
return current;
|
|
67
|
+
};
|
|
@@ -20,7 +20,7 @@ export const useTextFieldSubmit = (
|
|
|
20
20
|
}, [onSubmit, hasValue, disabled, isLoading, currentValue, resetValue]);
|
|
21
21
|
|
|
22
22
|
const handleKeyDown = useCallback(
|
|
23
|
-
(event: React.KeyboardEvent<
|
|
23
|
+
(event: React.KeyboardEvent<HTMLElement>) => {
|
|
24
24
|
if (event.key === 'Enter' && hasValue && !disabled && !isLoading) {
|
|
25
25
|
event.preventDefault();
|
|
26
26
|
handleSubmit();
|
|
@@ -6,6 +6,7 @@ import SpeechRecognition, { useSpeechRecognition } from 'react-speech-recognitio
|
|
|
6
6
|
export type UseVoiceInputProps = {
|
|
7
7
|
onTranscriptionStarted?: () => void;
|
|
8
8
|
onTranscriptionCompleted?: (transcript: string) => void;
|
|
9
|
+
onTranscription?: (transcript: string) => void;
|
|
9
10
|
disabled?: boolean;
|
|
10
11
|
};
|
|
11
12
|
|
|
@@ -19,6 +20,7 @@ export type UseVoiceInputReturn = {
|
|
|
19
20
|
export const useVoiceInput = ({
|
|
20
21
|
onTranscriptionCompleted,
|
|
21
22
|
onTranscriptionStarted,
|
|
23
|
+
onTranscription,
|
|
22
24
|
disabled = false,
|
|
23
25
|
}: UseVoiceInputProps): UseVoiceInputReturn => {
|
|
24
26
|
const [listeningToSpeech, setListeningToSpeech] = useAtom(listeningToSpeechAtom);
|
|
@@ -78,6 +80,12 @@ export const useVoiceInput = ({
|
|
|
78
80
|
}
|
|
79
81
|
}, [listeningToSpeech]);
|
|
80
82
|
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
if (!disabled && listeningToSpeech === 'start' && transcript) {
|
|
85
|
+
onTranscription?.(transcript);
|
|
86
|
+
}
|
|
87
|
+
}, [transcript, listeningToSpeech, disabled, onTranscription]);
|
|
88
|
+
|
|
81
89
|
return {
|
|
82
90
|
isListening: listeningToSpeech === 'start',
|
|
83
91
|
handleToggleListening,
|
|
@@ -8,8 +8,9 @@ export type TextFieldProps = {
|
|
|
8
8
|
theme?: Theme;
|
|
9
9
|
/**
|
|
10
10
|
* Placeholder text displayed when the input is empty.
|
|
11
|
+
* Pass an array of strings to cycle through them with a typing animation.
|
|
11
12
|
*/
|
|
12
|
-
placeholder: string;
|
|
13
|
+
placeholder: string | string[];
|
|
13
14
|
/**
|
|
14
15
|
* Controlled value of the input. If not provided, the component manages its own state.
|
|
15
16
|
*/
|
|
@@ -47,11 +48,28 @@ export type TextFieldProps = {
|
|
|
47
48
|
* @default false
|
|
48
49
|
*/
|
|
49
50
|
isLoading?: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* When true the input expands to multiple lines instead of scrolling horizontally.
|
|
53
|
+
* @default false
|
|
54
|
+
*/
|
|
55
|
+
multiline?: boolean;
|
|
50
56
|
/**
|
|
51
57
|
* Enable voice input button
|
|
52
58
|
* @default false
|
|
53
59
|
*/
|
|
54
60
|
enableVoiceInput?: boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Callback function invoked when voice transcription begins.
|
|
63
|
+
*/
|
|
55
64
|
onTranscriptionStarted?: () => void;
|
|
65
|
+
/**
|
|
66
|
+
* Callback function invoked when voice transcription completes.
|
|
67
|
+
* @param transcript - The transcribed text from voice input.
|
|
68
|
+
*/
|
|
56
69
|
onTranscriptionCompleted?: (transcript: string) => void;
|
|
70
|
+
/**
|
|
71
|
+
* Callback function invoked during voice transcription as audio is processed.
|
|
72
|
+
* @param transcript - The current transcribed text from voice input.
|
|
73
|
+
*/
|
|
74
|
+
onTranscription?: (transcript: string) => void;
|
|
57
75
|
};
|