@aslaluroba/help-center-react 3.2.10 → 3.2.12

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.
@@ -5,6 +5,7 @@ interface HelpscreenListProps {
5
5
  selectedOption: Option | null;
6
6
  onToggleOption: (option: Option) => void;
7
7
  onStartChat: () => void;
8
+ showChatNowButton?: boolean;
8
9
  }
9
10
  declare const HelpscreenList: React.FC<HelpscreenListProps>;
10
11
  export default HelpscreenList;
@@ -3,6 +3,7 @@ interface OptionsListScreenProps {
3
3
  helpScreen: HelpScreenData | null;
4
4
  handleStartChat: (option: Option) => void;
5
5
  handleMinimize: () => void;
6
+ hasActiveSession?: boolean;
6
7
  }
7
8
  declare const OptionsListScreen: React.FC<OptionsListScreenProps>;
8
9
  export default OptionsListScreen;
@@ -1,7 +1,9 @@
1
1
  import { HelpScreenData, Message, Option, ReviewProps } from '@/lib/types';
2
2
  type HelpPopupProps = {
3
3
  isOpen: boolean;
4
+ isClosing?: boolean;
4
5
  onClose: () => void;
6
+ onCloseAnimationEnd?: () => void;
5
7
  helpScreen: HelpScreenData | null;
6
8
  status: string;
7
9
  error: string | null;
@@ -30,5 +32,5 @@ type HelpPopupProps = {
30
32
  onReviewDialogSubmit?: (payload: ReviewProps) => void | Promise<void>;
31
33
  onReviewDialogClose?: () => void;
32
34
  };
33
- declare const HelpPopup: ({ isOpen, onClose, helpScreen, status, error, onStartChat, onSendMessage, onEnsureSession, onEndChat, messages, assistantStatus, needsAgent, sessionId, isChatClosed, selectedOption, setSelectedOption, inChatReviewSessionId, onInChatReviewSubmit, onInChatReviewDone, navigateToOptionsListAfterReview, onNavigatedToOptionsList, isReviewDialogOpen, reviewSessionId, isSubmittingReview, onReviewDialogSubmit, onReviewDialogClose, }: HelpPopupProps) => import("react/jsx-runtime").JSX.Element;
35
+ declare const HelpPopup: ({ isOpen, isClosing, onClose, onCloseAnimationEnd, helpScreen, status, error, onStartChat, onSendMessage, onEnsureSession, onEndChat, messages, assistantStatus, needsAgent, sessionId, isChatClosed, selectedOption, setSelectedOption, inChatReviewSessionId, onInChatReviewSubmit, onInChatReviewDone, navigateToOptionsListAfterReview, onNavigatedToOptionsList, isReviewDialogOpen, reviewSessionId, isSubmittingReview, onReviewDialogSubmit, onReviewDialogClose, }: HelpPopupProps) => import("react/jsx-runtime").JSX.Element;
34
36
  export default HelpPopup;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "main": "dist/index.js",
4
4
  "module": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
- "version": "3.2.10",
6
+ "version": "3.2.12",
7
7
  "description": "BabylAI Help Center Widget for React and Next.js",
8
8
  "private": false,
9
9
  "exports": {
@@ -35,7 +35,7 @@ const AgentResponse = ({ senderType, messageContent, messageId, onType }: AgentR
35
35
  components={{
36
36
  p: ({ node, ...props }: { node?: Element; [key: string]: any }) => (
37
37
  <p
38
- className='babylai:m-0 babylai:leading-6 babylai:text-sm babylai:font-sans babylai:wrap-break-word babylai:dark:text-white babylai:text-start'
38
+ className='babylai:m-0 babylai:leading-snug babylai:text-sm babylai:font-sans babylai:wrap-break-word babylai:text-start'
39
39
  {...props}
40
40
  />
41
41
  ),
package/src/globals.css CHANGED
@@ -136,6 +136,33 @@
136
136
  transform: translateY(-10px);
137
137
  }
138
138
  }
139
+
140
+ @keyframes typing-dot {
141
+ 0%,
142
+ 60%,
143
+ 100% {
144
+ opacity: 0.35;
145
+ transform: scale(0.85);
146
+ }
147
+ 30% {
148
+ opacity: 1;
149
+ transform: scale(1);
150
+ }
151
+ }
152
+ }
153
+
154
+ /* Typing indicator dots - staggered pulse animation */
155
+ .babylai-typing-dot {
156
+ animation: typing-dot 1.4s ease-in-out infinite;
157
+ }
158
+ .babylai-typing-dot:nth-child(1) {
159
+ animation-delay: 0ms;
160
+ }
161
+ .babylai-typing-dot:nth-child(2) {
162
+ animation-delay: 0.2s;
163
+ }
164
+ .babylai-typing-dot:nth-child(3) {
165
+ animation-delay: 0.4s;
139
166
  }
140
167
 
141
168
  /* Re-scope primary theme vars on widget root so primaryColor prop (inline style) wins over :root.
@@ -192,7 +219,7 @@
192
219
  }
193
220
 
194
221
  /* Dark theme colors */
195
- @media (prefers-color-scheme: dark) {
222
+ /* @media (prefers-color-scheme: dark) {
196
223
  :root {
197
224
  --babylai-black-white-50: #000000;
198
225
  --babylai-black-white-100: #050505;
@@ -219,7 +246,7 @@
219
246
  --babylai-border: var(--babylai-black-white-200);
220
247
  --babylai-ring: var(--babylai-primary-color);
221
248
  }
222
- }
249
+ } */
223
250
 
224
251
  .bg-header {
225
252
  background: linear-gradient(171deg,
@@ -22,7 +22,7 @@
22
22
  "comment_placeholder": "اكتب تعليقك هنا...",
23
23
  "submit_button": "إرسال التقييم",
24
24
  "rate_button": "تقييم",
25
- "comment_error": "يجب أن يكون التعليق بين 10 و500 حرف.",
25
+ "comment_error": "يجب أن يكون الحقل التعليق نصًا بحد أقصى 500 حرف.",
26
26
  "rating_error": "يجب أن يكون التقييم بين 1 و5.",
27
27
  "skip_button": "تخطي"
28
28
  },
@@ -22,7 +22,7 @@
22
22
  "comment_placeholder": "Write your comment here...",
23
23
  "submit_button": "Submit Review",
24
24
  "rate_button": "Rate",
25
- "comment_error": "Comment must be between 10 and 500 characters.",
25
+ "comment_error": "The field Comment must be a string with a maximum length of 500.",
26
26
  "rating_error": "Rating must be between 1 and 5.",
27
27
  "skip_button": "Skip"
28
28
  },
@@ -1,10 +1,13 @@
1
1
  import React, { useState, useCallback } from 'react';
2
2
  import { ReviewProps } from '@/lib/types';
3
+ import { cn } from '@/lib/utils';
3
4
  import { Rating } from '@/ui/review-dialog/rating';
4
5
  import { useLocalTranslation } from '@/useLocalTranslation';
5
6
  import LogoIcon from '@/assets/logo.svg';
6
7
  import { Button } from '@/components';
7
8
 
9
+ const COMMENT_MAX_LENGTH = 500;
10
+
8
11
  interface InChatReviewProps {
9
12
  onSubmit: (payload: ReviewProps) => void | Promise<void>;
10
13
  onDone?: () => void;
@@ -15,6 +18,7 @@ export const InChatReview: React.FC<InChatReviewProps> = ({ onSubmit, isSubmitti
15
18
  const { t } = useLocalTranslation();
16
19
  const [rating, setRating] = useState<number>(0);
17
20
  const [comment, setComment] = useState<string>('');
21
+ const [commentError, setCommentError] = useState<string | null>(null);
18
22
 
19
23
  const hasRating = rating >= 1 && rating <= 5;
20
24
 
@@ -22,9 +26,23 @@ export const InChatReview: React.FC<InChatReviewProps> = ({ onSubmit, isSubmitti
22
26
  setRating(val);
23
27
  }, []);
24
28
 
29
+ const handleCommentChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
30
+ const value = e.target.value;
31
+ setComment(value);
32
+ if (commentError && value.length <= COMMENT_MAX_LENGTH) {
33
+ setCommentError(null);
34
+ }
35
+ }, [commentError]);
36
+
25
37
  const handleSubmit = useCallback(() => {
26
- void onSubmit({ rating, comment: comment.trim() });
27
- }, [rating, comment, onSubmit]);
38
+ const trimmed = comment.trim();
39
+ if (trimmed.length > COMMENT_MAX_LENGTH) {
40
+ setCommentError(t('homeSdk.ReviewDialog.comment_error') || 'The field Comment must be a string with a maximum length of 500.');
41
+ return;
42
+ }
43
+ setCommentError(null);
44
+ void onSubmit({ rating, comment: trimmed });
45
+ }, [rating, comment, onSubmit, t]);
28
46
 
29
47
  return (
30
48
  <section className="babylai:mb-4 babylai:flex babylai:justify-start">
@@ -36,7 +54,7 @@ export const InChatReview: React.FC<InChatReviewProps> = ({ onSubmit, isSubmitti
36
54
  <h2 className='babylai:text-card-foreground'>
37
55
  {t('homeSdk.InChatReview.title')}
38
56
  </h2>
39
- <p className="babylai:font-normal babylai:text-muted-foreground">
57
+ <p className="babylai:font-normal babylai:text-muted-foreground babylai:leading-snug">
40
58
  {t('homeSdk.InChatReview.description')}
41
59
  </p>
42
60
 
@@ -46,16 +64,30 @@ export const InChatReview: React.FC<InChatReviewProps> = ({ onSubmit, isSubmitti
46
64
 
47
65
  {hasRating && (
48
66
  <>
49
- <p className="babylai:text-card-foreground">{t('homeSdk.InChatReview.follow_up')}</p>
67
+ <p className="babylai:text-card-foreground babylai:leading-snug">{t('homeSdk.InChatReview.follow_up')}</p>
50
68
 
51
69
  <textarea
52
- className="babylai:w-full babylai:bg-secondary babylai:border babylai:border-black-white-200 babylai:rounded-xl babylai:text-card-foreground babylai:text-sm babylai:p-3 babylai:resize-vertical babylai:min-h-20 babylai:disabled:opacity-50 babylai:disabled:cursor-not-allowed babylai:disabled:bg-secondary"
70
+ className={cn(
71
+ 'babylai:resize-none babylai:w-full babylai:bg-secondary babylai:border babylai:rounded-xl babylai:text-card-foreground babylai:text-sm babylai:p-3 babylai:resize-vertical babylai:min-h-20 babylai:disabled:opacity-50 babylai:disabled:cursor-not-allowed babylai:disabled:bg-secondary',
72
+ commentError ? 'babylai:border-destructive' : 'babylai:border-black-white-200'
73
+ )}
53
74
  rows={4}
75
+ maxLength={COMMENT_MAX_LENGTH}
54
76
  placeholder={t('homeSdk.InChatReview.note_placeholder')}
55
77
  value={comment}
56
- onChange={(e) => setComment(e.target.value)}
78
+ onChange={handleCommentChange}
57
79
  aria-label={t('homeSdk.InChatReview.note_placeholder')}
80
+ aria-invalid={!!commentError}
81
+ aria-describedby={commentError ? 'in-chat-review-comment-error' : undefined}
58
82
  />
83
+ {commentError && (
84
+ <p id="in-chat-review-comment-error" className="babylai:text-sm babylai:text-destructive" role="alert">
85
+ {commentError}
86
+ </p>
87
+ )}
88
+ <p className="babylai:text-xs babylai:text-muted-foreground">
89
+ {comment.length}/{COMMENT_MAX_LENGTH}
90
+ </p>
59
91
 
60
92
  <Button
61
93
  variant='default'
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import LoadingGif from '../../../assets/animatedLogo.gif';
2
+ import LogoIcon from '../../../assets/logo.svg';
3
3
 
4
4
  interface TypingIndicatorProps {
5
5
  firstHumanAgentIndex: number;
@@ -11,12 +11,16 @@ export const TypingIndicator = React.memo(({ firstHumanAgentIndex }: TypingIndic
11
11
  return (
12
12
  <div className='babylai:mb-4 babylai:flex'>
13
13
  <div className='babylai:shrink-0 babylai:me-3'>
14
- <div className='babylai:w-8 babylai:h-8 babylai:rounded-full babylai:flex babylai:items-center babylai:justify-center'>
15
- <img src={LoadingGif} alt='Loading' className='babylai:w-8 babylai:h-8' />
14
+ <div className='babylai:w-8 babylai:h-8 babylai:rounded-full babylai:flex babylai:items-center babylai:justify-center babylai:bg-primary'>
15
+ <LogoIcon className='babylai:w-4 babylai:h-4 babylai:text-primary' />
16
16
  </div>
17
17
  </div>
18
18
  <div className='babylai:max-w-[80%] babylai:rounded-2xl babylai:p-4 babylai:bg-card'>
19
- <p className='babylai:text-sm babylai:opacity-70 babylai:text-muted-foreground babylai:m-0'>...</p>
19
+ <p className='babylai:text-sm babylai:text-muted-foreground babylai:m-0 babylai:flex babylai:gap-0.5 babylai:items-center' aria-hidden="true">
20
+ <span className="babylai-typing-dot babylai:inline-block babylai:w-1.5 babylai:h-1.5 babylai:rounded-full babylai:bg-current" />
21
+ <span className="babylai-typing-dot babylai:inline-block babylai:w-1.5 babylai:h-1.5 babylai:rounded-full babylai:bg-current" />
22
+ <span className="babylai-typing-dot babylai:inline-block babylai:w-1.5 babylai:h-1.5 babylai:rounded-full babylai:bg-current" />
23
+ </p>
20
24
  </div>
21
25
  </div>
22
26
  );
@@ -3,12 +3,14 @@ import { Option } from '@/lib/types';
3
3
  import HelpscreenOption from '@/ui/chatbot-popup/options-list-screen/helpscreen-option';
4
4
  import { useLocalTranslation } from '@/useLocalTranslation';
5
5
  import { Button } from '@/components';
6
+ import SolarPlain2BoldDuotone from '~icons/solar/plain-2-bold-duotone'
6
7
 
7
8
  interface HelpscreenListProps {
8
9
  options: Option[] | undefined;
9
10
  selectedOption: Option | null;
10
11
  onToggleOption: (option: Option) => void;
11
12
  onStartChat: () => void;
13
+ showChatNowButton?: boolean;
12
14
  }
13
15
 
14
16
  const HelpscreenList: React.FC<HelpscreenListProps> = ({
@@ -16,6 +18,7 @@ const HelpscreenList: React.FC<HelpscreenListProps> = ({
16
18
  selectedOption,
17
19
  onToggleOption,
18
20
  onStartChat,
21
+ showChatNowButton = true,
19
22
  }) => {
20
23
  const { t } = useLocalTranslation();
21
24
 
@@ -31,13 +34,18 @@ const HelpscreenList: React.FC<HelpscreenListProps> = ({
31
34
  />
32
35
  ))}
33
36
  </div>
34
- <div className="babylai:sticky babylai:bottom-0 babylai:z-10">
35
- <Button
36
- variant='default'
37
- onClick={onStartChat}
38
- disabled={!selectedOption}
39
- >{t('homeSdk.chatNow')}</Button>
40
- </div>
37
+ {showChatNowButton && (
38
+ <div className="babylai:sticky babylai:bottom-0 babylai:z-10">
39
+ <Button
40
+ variant='default'
41
+ onClick={onStartChat}
42
+ disabled={!selectedOption}
43
+ >
44
+ {t('homeSdk.chatNow')}
45
+ <SolarPlain2BoldDuotone className="babylai:w-6 babylai:h-6" />
46
+ </Button>
47
+ </div>
48
+ )}
41
49
  </section>
42
50
  );
43
51
  };
@@ -15,17 +15,27 @@ const HelpscreenOption: React.FC<HelpscreenOptionProps> = ({
15
15
  }) => {
16
16
  return (
17
17
  <div
18
+ role="button"
19
+ tabIndex={0}
18
20
  className={cn(
19
21
  'babylai:flex babylai:flex-col babylai:gap-2 babylai:p-6 babylai:rounded-3xl babylai:text-start babylai:border babylai:border-black-white-200 babylai:bg-card babylai:cursor-pointer',
22
+ 'babylai:transition-all babylai:duration-200 babylai:ease-out',
23
+ 'babylai:active:scale-[0.98] babylai:active:opacity-95',
20
24
  isSelected && 'babylai:ring babylai:ring-primary-500 babylai:shadow-md'
21
25
  )}
22
26
  onClick={onClick}
27
+ onKeyDown={(e) => {
28
+ if (e.key === 'Enter' || e.key === ' ') {
29
+ e.preventDefault();
30
+ onClick();
31
+ }
32
+ }}
23
33
  >
24
34
  <h2 className="babylai:text-base! babylai:font-semibold! babylai:text-card-foreground" dir="auto">
25
35
  {option.title}
26
36
  </h2>
27
37
  {option.paragraphs.map((paragraph, index) => (
28
- <p key={index} className="babylai:text-sm babylai:text-muted-foreground" dir="auto">
38
+ <p key={index} className="babylai:text-sm babylai:text-muted-foreground babylai:leading-snug" dir="auto">
29
39
  {paragraph}
30
40
  </p>
31
41
  ))}
@@ -10,12 +10,14 @@ interface OptionsListScreenProps {
10
10
  helpScreen: HelpScreenData | null;
11
11
  handleStartChat: (option: Option) => void;
12
12
  handleMinimize: () => void;
13
+ hasActiveSession?: boolean;
13
14
  }
14
15
 
15
16
  const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
16
17
  helpScreen,
17
18
  handleStartChat,
18
19
  handleMinimize,
20
+ hasActiveSession = false,
19
21
  }) => {
20
22
  const [selectedOption, setSelectedOption] = useState<Option | null>(null);
21
23
 
@@ -56,6 +58,7 @@ const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
56
58
  selectedOption={selectedOption}
57
59
  onToggleOption={handleToggleExpandOption}
58
60
  onStartChat={handleStartChatWithSelected}
61
+ showChatNowButton={!hasActiveSession}
59
62
  />
60
63
  </div >
61
64
  <PoweredBy />
@@ -3,6 +3,7 @@ import { useLocalTranslation } from "@/useLocalTranslation";
3
3
  import SolarChatRoundUnreadBoldDuotone from '~icons/solar/chat-round-unread-bold-duotone'
4
4
  import SolarCloseCircleLineDuotone from '~icons/solar/close-circle-line-duotone'
5
5
  import SolarPlain2BoldDuotone from '~icons/solar/plain-2-bold-duotone'
6
+ import PoweredBy from "../powered-by";
6
7
 
7
8
  interface ConfirmationModalProps {
8
9
  title: string;
@@ -17,39 +18,42 @@ const ConfirmationModal = ({ title, message, onCancel, onConfirm }: Confirmation
17
18
  return (
18
19
  <div className='babylai:absolute babylai:inset-0 babylai:z-50 babylai:flex babylai:items-end babylai:rounded-3xl babylai:overflow-hidden'>
19
20
  <div className='babylai:absolute babylai:inset-0 babylai:bg-black/60' onClick={onCancel}></div>
20
- <div className='babylai:flex babylai:flex-col babylai:bg-card babylai:rounded-2xl babylai:p-6 babylai:pb-5 babylai:w-full babylai:z-50 babylai:shadow-lg'>
21
- <button className="babylai:border-0 babylai:p-0 babylai:flex babylai:bg-transparent babylai:cursor-pointer babylai:mb-6 babylai:ms-auto babylai:text-card-foreground"
22
- type='button'
23
- onClick={onCancel}
24
- >
25
- <SolarCloseCircleLineDuotone className="babylai:w-6 babylai:h-6" />
26
- </button>
27
-
28
- <section className="babylai:flex babylai:items-center babylai:justify-center babylai:border-b babylai:border-black-white-200 babylai:pb-6 babylai:mb-6">
29
- <div className="babylai:flex babylai:items-center babylai:justify-center babylai:w-20 babylai:h-20 babylai:rounded-full babylai:p-3 babylai:bg-primary/15 babylai:text-primary">
30
- <SolarChatRoundUnreadBoldDuotone className="babylai:w-16 babylai:h-16" />
31
- </div>
32
- </section>
33
-
34
- <h3 className='babylai:text-2xl! babylai:text-center babylai:font-bold! babylai:mb-2! babylai:text-card-foreground'>{title}</h3>
35
-
36
- <p className='babylai:text-sm babylai:text-center babylai:text-muted-foreground'>{message}</p>
37
-
38
- <div className='babylai:flex babylai:justify-between babylai:gap-3 babylai:mt-6'>
39
- <Button
40
- onClick={onConfirm}
41
- variant='secondary'
42
- >
43
- {t('homeSdk.ConfirmationModal.confirmation_button')}
44
- </Button>
45
- <Button
21
+ <div className='babylai:flex babylai:flex-col babylai:bg-card babylai:rounded-2xl babylai:w-full babylai:z-50 babylai:shadow-lg'>
22
+ <div className='babylai:flex babylai:flex-col babylai:p-6 babylai:pb-5 babylai:w-full'>
23
+ <button className="babylai:border-0 babylai:p-0 babylai:flex babylai:bg-transparent babylai:cursor-pointer babylai:mb-6 babylai:ms-auto babylai:text-card-foreground"
24
+ type='button'
46
25
  onClick={onCancel}
47
- variant='default'
48
26
  >
49
- {t('homeSdk.ConfirmationModal.cancel_button')}
50
- <SolarPlain2BoldDuotone className="babylai:w-6 babylai:h-6" />
51
- </Button>
27
+ <SolarCloseCircleLineDuotone className="babylai:w-7 babylai:h-7" />
28
+ </button>
29
+
30
+ <section className="babylai:flex babylai:items-center babylai:justify-center babylai:border-b babylai:border-black-white-200 babylai:pb-6 babylai:mb-6">
31
+ <div className="babylai:flex babylai:items-center babylai:justify-center babylai:w-20 babylai:h-20 babylai:rounded-full babylai:p-3 babylai:bg-primary/15 babylai:text-primary">
32
+ <SolarChatRoundUnreadBoldDuotone className="babylai:w-16 babylai:h-16" />
33
+ </div>
34
+ </section>
35
+
36
+ <h3 className='babylai:text-2xl! babylai:text-center babylai:font-bold! babylai:mb-2! babylai:text-card-foreground'>{title}</h3>
37
+
38
+ <p className='babylai:text-sm babylai:text-center babylai:text-muted-foreground'>{message}</p>
39
+
40
+ <div className='babylai:flex babylai:justify-between babylai:gap-3 babylai:mt-6'>
41
+ <Button
42
+ onClick={onCancel}
43
+ variant='secondary'
44
+ >
45
+ {t('homeSdk.ConfirmationModal.cancel_button')}
46
+ <SolarPlain2BoldDuotone className="babylai:w-6 babylai:h-6" />
47
+ </Button>
48
+ <Button
49
+ onClick={onConfirm}
50
+ variant='default'
51
+ >
52
+ {t('homeSdk.ConfirmationModal.confirmation_button')}
53
+ </Button>
54
+ </div>
52
55
  </div>
56
+ <PoweredBy />
53
57
  </div>
54
58
  </div>
55
59
  );
@@ -36,6 +36,7 @@ const HelpCenterContent = ({
36
36
  }: HelpCenterProps) => {
37
37
  const { t } = useLocalTranslation();
38
38
  const [isOpen, setIsOpen] = useState(false);
39
+ const [isClosing, setIsClosing] = useState(false);
39
40
  const [showArrowAnimation, setShowArrowAnimation] = useState(showArrow);
40
41
  const [helpScreenData, setHelpScreenData] = useState<HelpScreenData | null>(null);
41
42
  const [status, setStatus] = useState('idle');
@@ -57,10 +58,25 @@ const HelpCenterContent = ({
57
58
  const actionHandler = useActionHandler();
58
59
 
59
60
  const handleTogglePopup = () => {
60
- setIsOpen(!isOpen);
61
- setShowArrowAnimation(isOpen);
61
+ if (isOpen) {
62
+ setIsClosing(true);
63
+ setIsOpen(false);
64
+ setShowArrowAnimation(true);
65
+ } else {
66
+ setIsOpen(true);
67
+ setShowArrowAnimation(false);
68
+ }
62
69
  };
63
70
 
71
+ const handleClosePopup = useCallback(() => {
72
+ setIsClosing(true);
73
+ setIsOpen(false);
74
+ }, []);
75
+
76
+ const handlePopupCloseAnimationEnd = useCallback(() => {
77
+ setIsClosing(false);
78
+ }, []);
79
+
64
80
  const handleCloseArrowAnimation = () => {
65
81
  setShowArrowAnimation(false);
66
82
  };
@@ -360,16 +376,18 @@ const HelpCenterContent = ({
360
376
 
361
377
  return (
362
378
  <div className='babylai-theme-root babylai:mb-4' style={themeStyles}>
363
- {showArrowAnimation && !isOpen && (
379
+ {showArrowAnimation && !isOpen && !isClosing && (
364
380
  <FloatingMessage onClose={handleCloseArrowAnimation} message={messageLabel || t('homeSdk.needAssistance')} />
365
381
  )}
366
382
 
367
383
  <HelpButton onClick={handleTogglePopup} />
368
384
 
369
- {isOpen && (
385
+ {(isOpen || isClosing) && (
370
386
  <HelpPopup
371
387
  isOpen={isOpen}
372
- onClose={() => setIsOpen(false)}
388
+ isClosing={isClosing}
389
+ onClose={handleClosePopup}
390
+ onCloseAnimationEnd={handlePopupCloseAnimationEnd}
373
391
  helpScreen={helpScreenData}
374
392
  status={status}
375
393
  error={error}
@@ -6,13 +6,18 @@ import OptionsListScreen from '@/ui/chatbot-popup/options-list-screen';
6
6
  import { ActiveChatActions } from '@/ui/chatbot-popup/active-chat-actions';
7
7
  import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
8
8
  import { HelpScreenData, Message, Option, ReviewProps } from '@/lib/types';
9
+ import { cn } from '@/lib/utils';
9
10
  import { useLocalTranslation } from '../useLocalTranslation';
10
11
  import ConfirmationModal from './confirmation-modal';
11
12
  import ReviewDialog from './review-dialog';
12
13
 
14
+ const POPUP_ANIMATION_DURATION_MS = 250;
15
+
13
16
  type HelpPopupProps = {
14
17
  isOpen: boolean;
18
+ isClosing?: boolean;
15
19
  onClose: () => void;
20
+ onCloseAnimationEnd?: () => void;
16
21
  helpScreen: HelpScreenData | null;
17
22
  status: string;
18
23
  error: string | null;
@@ -42,7 +47,9 @@ type HelpPopupProps = {
42
47
 
43
48
  const HelpPopup = ({
44
49
  isOpen,
50
+ isClosing = false,
45
51
  onClose,
52
+ onCloseAnimationEnd,
46
53
  helpScreen,
47
54
  status,
48
55
  error,
@@ -73,12 +80,28 @@ const HelpPopup = ({
73
80
  const [endChatConfirmation, setEndChatConfirmation] = useState<boolean>(false);
74
81
  const [startNewChatConfirmation, setStartNewChatConfirmation] = useState<boolean>(false);
75
82
  const [tempSelectedOption, setTempSelectedOption] = useState<Option | null>(null);
83
+ const [isEntering, setIsEntering] = useState(true);
76
84
 
77
85
  const chatBoxRef = useRef<HTMLDivElement>(null);
78
86
  const prevIsOpenRef = useRef(false);
79
87
  const { t } = useLocalTranslation();
80
88
  const messagesRef = useRef<Message[]>([]);
81
89
 
90
+ // Enter animation: transition from initial state to visible
91
+ useEffect(() => {
92
+ const id = requestAnimationFrame(() => {
93
+ requestAnimationFrame(() => setIsEntering(false));
94
+ });
95
+ return () => cancelAnimationFrame(id);
96
+ }, []);
97
+
98
+ // Exit animation: notify parent to unmount after transition
99
+ useEffect(() => {
100
+ if (!isClosing || !onCloseAnimationEnd) return;
101
+ const tId = setTimeout(onCloseAnimationEnd, POPUP_ANIMATION_DURATION_MS);
102
+ return () => clearTimeout(tId);
103
+ }, [isClosing, onCloseAnimationEnd]);
104
+
82
105
  // Memoize the current messages to prevent unnecessary re-renders
83
106
  const memoizedMessages = useMemo(() => {
84
107
  return messages;
@@ -202,6 +225,7 @@ const HelpPopup = ({
202
225
  helpScreen={helpScreen}
203
226
  handleStartChat={handleStartChat}
204
227
  handleMinimize={handleMinimize}
228
+ hasActiveSession={!!sessionId}
205
229
  />
206
230
  );
207
231
  }, [
@@ -277,15 +301,19 @@ const HelpPopup = ({
277
301
  }
278
302
  }, [messages, scrollToBottom]);
279
303
 
304
+ const popupBaseClasses =
305
+ 'babylai:fixed babylai:inset-auto babylai:max-w-sm babylai:h-[calc(100vh-220px)] babylai:max-h-[800px] babylai:overflow-auto babylai:w-full babylai:bg-secondary babylai:mb-4 babylai:bottom-24 babylai:right-4 babylai:rounded-3xl babylai:shadow-lg babylai:z-50 babylai:flex babylai:flex-col';
306
+ const popupAnimationClasses = cn(
307
+ 'babylai:transition-all babylai:duration-[250ms] babylai:ease-out',
308
+ (isEntering || isClosing) && 'babylai:opacity-0 babylai:scale-[0.96] babylai:translate-y-2',
309
+ !isEntering && !isClosing && 'babylai:opacity-100 babylai:scale-100 babylai:translate-y-0'
310
+ );
311
+
280
312
  // EARLY RETURNS MUST COME AFTER ALL HOOKS
281
313
  // Early returns for performance - moved after all hooks
282
314
  if (status === 'loading' && !helpScreen) {
283
315
  return (
284
- <div
285
- className='babylai:fixed babylai:inset-auto babylai:max-w-sm babylai:h-[calc(100vh-220px)] babylai:max-h-[800px]
286
- babylai:overflow-auto babylai:w-full babylai:bg-secondary babylai:mb-4
287
- babylai:bottom-24 babylai:right-4 babylai:rounded-3xl babylai:shadow-lg babylai:z-50 babylai:flex babylai:flex-col'
288
- >
316
+ <div className={cn(popupBaseClasses, popupAnimationClasses)}>
289
317
  <ChatBotLoadingScreen onClose={onClose} />
290
318
  </div>
291
319
  );
@@ -293,22 +321,14 @@ babylai:bottom-24 babylai:right-4 babylai:rounded-3xl babylai:shadow-lg babylai:
293
321
 
294
322
  if (error) {
295
323
  return (
296
- <div
297
- className='babylai:fixed babylai:inset-auto babylai:max-w-sm babylai:h-[calc(100vh-220px)] babylai:max-h-[800px]
298
- babylai:overflow-auto babylai:w-full babylai:bg-secondary babylai:mb-4
299
- babylai:bottom-24 babylai:right-4 babylai:rounded-3xl babylai:shadow-lg babylai:z-50 babylai:flex babylai:flex-col'
300
- >
324
+ <div className={cn(popupBaseClasses, popupAnimationClasses)}>
301
325
  <ChatBotErrorScreen onClose={onClose} error={error || ''} />
302
326
  </div>
303
327
  );
304
328
  }
305
329
 
306
330
  return (
307
- <div
308
- className='babylai:fixed babylai:inset-auto babylai:max-w-sm babylai:h-[calc(100vh-220px)] babylai:max-h-[800px]
309
- babylai:overflow-auto babylai:w-full babylai:bg-secondary babylai:mb-4
310
- babylai:bottom-24 babylai:right-4 babylai:rounded-3xl babylai:shadow-lg babylai:z-50 babylai:flex babylai:flex-col'
311
- >
331
+ <div className={cn(popupBaseClasses, popupAnimationClasses)}>
312
332
  <div className='babylai:h-full babylai:rounded-3xl babylai:flex babylai:flex-col babylai:relative'
313
333
  >
314
334
  {activeChatButton}