@patternfly/chatbot 6.5.0-prerelease.18 → 6.5.0-prerelease.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +29 -2
  2. package/dist/cjs/CodeModal/CodeModal.d.ts +2 -0
  3. package/dist/cjs/CodeModal/CodeModal.js +53 -12
  4. package/dist/cjs/Onboarding/Onboarding.d.ts +36 -0
  5. package/dist/cjs/Onboarding/Onboarding.js +37 -0
  6. package/dist/cjs/Onboarding/Onboarding.test.d.ts +1 -0
  7. package/dist/cjs/Onboarding/Onboarding.test.js +80 -0
  8. package/dist/cjs/Onboarding/index.d.ts +2 -0
  9. package/dist/cjs/Onboarding/index.js +23 -0
  10. package/dist/cjs/index.d.ts +2 -0
  11. package/dist/cjs/index.js +4 -1
  12. package/dist/css/main.css +83 -0
  13. package/dist/css/main.css.map +1 -1
  14. package/dist/dynamic/Onboarding/package.json +1 -0
  15. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +30 -3
  16. package/dist/esm/CodeModal/CodeModal.d.ts +2 -0
  17. package/dist/esm/CodeModal/CodeModal.js +54 -13
  18. package/dist/esm/Onboarding/Onboarding.d.ts +36 -0
  19. package/dist/esm/Onboarding/Onboarding.js +30 -0
  20. package/dist/esm/Onboarding/Onboarding.test.d.ts +1 -0
  21. package/dist/esm/Onboarding/Onboarding.test.js +75 -0
  22. package/dist/esm/Onboarding/index.d.ts +2 -0
  23. package/dist/esm/Onboarding/index.js +2 -0
  24. package/dist/esm/index.d.ts +2 -0
  25. package/dist/esm/index.js +2 -0
  26. package/dist/tsconfig.tsbuildinfo +1 -1
  27. package/package.json +12 -3
  28. package/patternfly-docs/content/extensions/chatbot/about-chatbot.md +3 -3
  29. package/patternfly-docs/content/extensions/chatbot/design-guidelines.md +3 -3
  30. package/patternfly-docs/content/extensions/chatbot/examples/Analytics/Analytics.md +1 -1
  31. package/patternfly-docs/content/extensions/chatbot/examples/Customizing Messages/Customizing Messages.md +1 -1
  32. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithQuickResponses.tsx +11 -0
  33. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +1 -1
  34. package/patternfly-docs/content/extensions/chatbot/examples/UI/CompactOnboarding.tsx +141 -0
  35. package/patternfly-docs/content/extensions/chatbot/examples/UI/Onboarding.tsx +151 -0
  36. package/patternfly-docs/content/extensions/chatbot/examples/UI/RH-Hat-Image.svg +9 -0
  37. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +38 -20
  38. package/patternfly-docs/content/extensions/chatbot/examples/demos/AttachmentDemos.md +1 -1
  39. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +2 -2
  40. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +56 -14
  41. package/src/ChatbotModal/ChatbotModal.scss +3 -0
  42. package/src/CodeModal/CodeModal.tsx +71 -26
  43. package/src/Onboarding/Onboarding.scss +101 -0
  44. package/src/Onboarding/Onboarding.test.tsx +148 -0
  45. package/src/Onboarding/Onboarding.tsx +126 -0
  46. package/src/Onboarding/index.ts +3 -0
  47. package/src/index.ts +3 -0
  48. package/src/main.scss +1 -0
  49. package/tsconfig.json +1 -1
@@ -2,7 +2,7 @@
2
2
  # Sidenav top-level section
3
3
  # should be the same for all markdown files
4
4
  section: extensions
5
- subsection: chatbot
5
+ subsection: ChatBot
6
6
  # Sidenav secondary level section
7
7
  # should be the same for all markdown files
8
8
  id: UI
@@ -53,6 +53,7 @@ import FileDropZone from '@patternfly/chatbot/dist/dynamic/FileDropZone';
53
53
  import { PreviewAttachment } from '@patternfly/chatbot/dist/dynamic/PreviewAttachment';
54
54
  import ChatbotAlert from '@patternfly/chatbot/dist/dynamic/ChatbotAlert';
55
55
  import TermsOfUse from '@patternfly/chatbot/dist/dynamic/TermsOfUse';
56
+ import Onboarding from '@patternfly/chatbot/dist/dynamic/Onboarding';
56
57
  import {
57
58
  ChatbotHeader,
58
59
  ChatbotHeaderCloseButton,
@@ -85,6 +86,7 @@ import PFHorizontalLogoReverse from './PF-HorizontalLogo-Reverse.svg';
85
86
  import userAvatar from '../Messages/user_avatar.svg';
86
87
  import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
87
88
  import termsAndConditionsHeader from './PF-TermsAndConditionsHeader.svg';
89
+ import onboardingHeader from './RH-Hat-Image.svg';
88
90
  import { CloseIcon, SearchIcon, OutlinedCommentsIcon } from '@patternfly/react-icons';
89
91
  import { FunctionComponent, FormEvent, useState, useRef, MouseEvent, isValidElement, cloneElement, Children, ReactNode, Ref, MouseEvent as ReactMouseEvent, CSSProperties, useEffect} from 'react';
90
92
  import FilePreview from '@patternfly/chatbot/dist/dynamic/FilePreview';
@@ -127,7 +129,7 @@ Your code structure should look like this:
127
129
 
128
130
  ### Welcome message
129
131
 
130
- To introduce users to the ChatBot experience, display a welcome message before they input their first message. This brief message should follow our [conversation design guidelines](/patternfly-ai/conversation-design) to welcome users to the ChatBot experience and encourage them to interact.
132
+ To introduce users to the ChatBot experience, display a welcome message before they input their first message. This brief message should follow our [conversation design guidelines](/ai/conversation-design) to welcome users to the ChatBot experience and encourage them to interact.
131
133
 
132
134
  This message can be dismissed once a user sends their first message. To change the arrangement of the message within the message box, specify the `position` in the `<MessageBox>` component.
133
135
 
@@ -424,24 +426,6 @@ The drawer can also be used to display a list of basic menu items.
424
426
 
425
427
  ```
426
428
 
427
- ### Terms of use
428
-
429
- Based on the [PatternFly modal](/components/modal), this modal adapts to the ChatBot display mode and is meant to display terms and conditions for using a ChatBot in your project. The image in the header can be toggled on or off depending on whether the `image` and `altText` props are provided.
430
-
431
- This example also includes an example of how to use [skip to content](/extensions/chatbot/ui#skip-to-content). When the terms of use modal is open, focus is placed on the terms of use container. When it is closed, focus is placed on the ChatBot. In a real example with a functioning ChatBot toggle, you would also want to place focus on the toggle when appropriate.
432
-
433
- ```js file="./TermsOfUse.tsx" isFullscreen
434
-
435
- ```
436
-
437
- ### Compact terms of use
438
-
439
- To apply compact styling to the terms of use modal, pass `isCompact` to `<TermsOfUse>`. This will remove the header image and adjust the spacing of text, so that there is less white space in the modal.
440
-
441
- ```js file="./TermsOfUseCompact.tsx" isFullscreen
442
-
443
- ```
444
-
445
429
  ### Settings
446
430
 
447
431
  To contain user preference controls and other ChatBot setting options, you can create a separate settings page that can accept any number of buttons, dropdown menus, toggles, labels, and so on. This settings page will render all components appropriately within all 4 display modes.
@@ -469,3 +453,37 @@ Based on the [PatternFly modal](/components/modal), this modal adapts to the Cha
469
453
  ```js file="./ChatbotModal.tsx" isFullscreen
470
454
 
471
455
  ```
456
+
457
+ ### Onboarding
458
+
459
+ You can use the onboarding modal to introduce users to your ChatBot and provide necessary information. The title, image, and body text are customizable.
460
+
461
+ ```js file="./Onboarding.tsx" isFullscreen
462
+
463
+ ```
464
+
465
+ ### Compact onboarding
466
+
467
+ To make the onboarding modal compact, with less spacing, pass `isCompact` to the `<Onboarding>` component.
468
+
469
+ ```js file="./CompactOnboarding.tsx" isFullscreen
470
+
471
+ ```
472
+
473
+ ### Terms of use
474
+
475
+ Based on the [PatternFly modal](/components/modal), this modal adapts to the ChatBot display mode and is meant to display terms and conditions for using a ChatBot in your project. The image in the header can be toggled on or off depending on whether the `image` and `altText` props are provided.
476
+
477
+ This example also includes an example of how to use [skip to content](/extensions/chatbot/ui#skip-to-content). When the terms of use modal is open, focus is placed on the terms of use container. When it is closed, focus is placed on the ChatBot. In a real example with a functioning ChatBot toggle, you would also want to place focus on the toggle when appropriate.
478
+
479
+ ```js file="./TermsOfUse.tsx" isFullscreen
480
+
481
+ ```
482
+
483
+ ### Compact terms of use
484
+
485
+ To apply compact styling to the terms of use modal, pass `isCompact` to `<TermsOfUse>`. This will remove the header image and adjust the spacing of text, so that there is less white space in the modal.
486
+
487
+ ```js file="./TermsOfUseCompact.tsx" isFullscreen
488
+
489
+ ```
@@ -2,7 +2,7 @@
2
2
  # Sidenav top-level section
3
3
  # should be the same for all markdown files
4
4
  section: extensions
5
- subsection: chatbot
5
+ subsection: ChatBot
6
6
  # Sidenav secondary level section
7
7
  # should be the same for all markdown files
8
8
  id: Messages
@@ -2,7 +2,7 @@
2
2
  # Sidenav top-level section
3
3
  # should be the same for all markdown files
4
4
  section: extensions
5
- subsection: chatbot
5
+ subsection: ChatBot
6
6
  # Sidenav secondary level section
7
7
  # should be the same for all markdown files
8
8
  id: Overview
@@ -133,7 +133,7 @@ This demo displays a ChatBot in a static, inline drawer. This demo includes:
133
133
 
134
134
  ### Primary color background
135
135
 
136
- This demo displays an embedded ChatBot with a [primary background color](/design-foundations/colors#background-colors). This example includes the same features as the [Embedded ChatBot demo](/patternfly-ai/chatbot/overview/demo/#embedded-chatbot)&mdash;the only differences are that the background color is adjusted via the `isPrimary` prop and some of the sample Messages have changed. You can use the same logic to adjust the background color in any ChatBot layout.
136
+ This demo displays an embedded ChatBot with a [primary background color](/design-foundations/colors#background-colors). This example includes the same features as the [Embedded ChatBot demo](/extensions/chatbot/overview/demo/#embedded-chatbot)&mdash;the only differences are that the background color is adjusted via the `isPrimary` prop and some of the sample Messages have changed. You can use the same logic to adjust the background color in any ChatBot layout.
137
137
 
138
138
  ```js file="./WhiteEmbeddedChatbot.tsx" isFullscreen
139
139
 
@@ -1,5 +1,5 @@
1
1
  import type { Ref, FunctionComponent } from 'react';
2
- import { forwardRef } from 'react';
2
+ import { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
3
3
 
4
4
  import { Button, ButtonProps, Icon, Tooltip, TooltipProps } from '@patternfly/react-core';
5
5
  import BarsIcon from '@patternfly/react-icons/dist/esm/icons/bars-icon';
@@ -30,21 +30,43 @@ const ChatbotHeaderMenuBase: FunctionComponent<ChatbotHeaderMenuProps> = ({
30
30
  tooltipContent = 'Chat history menu',
31
31
  isCompact,
32
32
  ...props
33
- }: ChatbotHeaderMenuProps) => (
34
- <div className={`pf-chatbot__menu ${className}`}>
35
- <Tooltip
36
- content={tooltipContent}
37
- position="bottom"
38
- // prevents VO announcements of both aria label and tooltip
39
- aria="none"
40
- {...tooltipProps}
41
- >
33
+ }: ChatbotHeaderMenuProps) => {
34
+ const [isDrawerAnimating, setIsDrawerAnimating] = useState(false);
35
+ // I'd like to use a prop here later if this works
36
+ const drawerState = props['aria-expanded'];
37
+ const isDrawerOpen = drawerState === true;
38
+ const prevDrawerStateRef = useRef<boolean | undefined>(isDrawerOpen);
39
+ const buttonRef = useRef<HTMLButtonElement | null>(null);
40
+
41
+ useEffect(() => {
42
+ if (drawerState !== undefined) {
43
+ const wasDrawerOpen = prevDrawerStateRef.current === true;
44
+ const isDrawerClosing = wasDrawerOpen && !isDrawerOpen;
45
+
46
+ setIsDrawerAnimating(true);
47
+ const timeout = setTimeout(() => {
48
+ setIsDrawerAnimating(false);
49
+
50
+ if (isDrawerClosing) {
51
+ requestAnimationFrame(() => {
52
+ buttonRef.current?.focus();
53
+ });
54
+ }
55
+ }, 350);
56
+
57
+ prevDrawerStateRef.current = isDrawerOpen;
58
+ return () => clearTimeout(timeout);
59
+ }
60
+ }, [drawerState, isDrawerOpen]);
61
+
62
+ const button = useMemo(
63
+ () => (
42
64
  <Button
43
65
  className={`pf-chatbot__button--toggle-menu ${isCompact ? 'pf-m-compact' : ''}`}
44
66
  variant="plain"
45
67
  onClick={onMenuToggle}
46
68
  aria-label={menuAriaLabel}
47
- ref={innerRef}
69
+ ref={innerRef ?? buttonRef}
48
70
  icon={
49
71
  <Icon size={isCompact ? 'lg' : 'xl'} isInline>
50
72
  <BarsIcon />
@@ -53,9 +75,29 @@ const ChatbotHeaderMenuBase: FunctionComponent<ChatbotHeaderMenuProps> = ({
53
75
  size={isCompact ? 'sm' : undefined}
54
76
  {...props}
55
77
  />
56
- </Tooltip>
57
- </div>
58
- );
78
+ ),
79
+ // eslint-disable-next-line react-hooks/exhaustive-deps
80
+ [isCompact, menuAriaLabel, onMenuToggle, innerRef, buttonRef]
81
+ );
82
+
83
+ return (
84
+ <div className={`pf-chatbot__menu ${className}`}>
85
+ {isDrawerAnimating ? (
86
+ button
87
+ ) : (
88
+ <Tooltip
89
+ content={tooltipContent}
90
+ position="bottom"
91
+ // prevents VO announcements of both aria label and tooltip
92
+ aria="none"
93
+ {...tooltipProps}
94
+ >
95
+ {button}
96
+ </Tooltip>
97
+ )}
98
+ </div>
99
+ );
100
+ };
59
101
 
60
102
  export const ChatbotHeaderMenu = forwardRef((props: ChatbotHeaderMenuProps, ref: Ref<HTMLButtonElement>) => (
61
103
  <ChatbotHeaderMenuBase innerRef={ref} {...props} />
@@ -18,7 +18,10 @@
18
18
  .pf-v6-c-modal-box__footer {
19
19
  padding-block-start: var(--pf-t--global--spacer--xl);
20
20
  padding-block-end: var(--pf-t--global--spacer--xl);
21
+ border-top: var(--pf-t--global--border--width--high-contrast--regular) solid
22
+ var(--pf-t--global--border--color--high-contrast);
21
23
  }
24
+
22
25
  .pf-v6-c-modal-box__header {
23
26
  padding-block-end: var(--pf-t--global--spacer--sm);
24
27
  }
@@ -5,17 +5,17 @@
5
5
  import type { FunctionComponent, MouseEvent } from 'react';
6
6
  import { useState, useEffect, useRef } from 'react';
7
7
  import path from 'path-browserify';
8
- import * as monaco from 'monaco-editor';
9
- import { loader } from '@monaco-editor/react';
10
8
 
11
9
  // Import PatternFly components
12
10
  import { CodeEditor } from '@patternfly/react-code-editor';
13
11
  import {
12
+ Bullseye,
14
13
  Button,
15
14
  getResizeObserver,
16
15
  ModalBody,
17
16
  ModalFooter,
18
17
  ModalHeader,
18
+ Spinner,
19
19
  Stack,
20
20
  StackItem
21
21
  } from '@patternfly/react-core';
@@ -23,8 +23,16 @@ import FileDetails, { extensionToLanguage } from '../FileDetails';
23
23
  import { ChatbotDisplayMode } from '../Chatbot';
24
24
  import ChatbotModal from '../ChatbotModal/ChatbotModal';
25
25
 
26
- // Configure Monaco loader to use the npm package instead of CDN
27
- loader.config({ monaco });
26
+ // Try to lazy load - some consumers need to be below a certain bundle size, but can't use the CDN and don't have webpack
27
+ let monacoInstance: typeof import('monaco-editor') | null = null;
28
+ const loadMonaco = async () => {
29
+ if (!monacoInstance) {
30
+ const [monaco, { loader }] = await Promise.all([import('monaco-editor'), import('@monaco-editor/react')]);
31
+ monacoInstance = monaco;
32
+ loader.config({ monaco });
33
+ }
34
+ return monacoInstance;
35
+ };
28
36
 
29
37
  export interface CodeModalProps {
30
38
  /** Class applied to code editor */
@@ -63,6 +71,8 @@ export interface CodeModalProps {
63
71
  modalBodyClassName?: string;
64
72
  /** Class applied to modal footer */
65
73
  modalFooterClassName?: string;
74
+ /** Aria label applied to spinner when loading Monaco */
75
+ spinnerAriaLabel?: string;
66
76
  }
67
77
 
68
78
  export const CodeModal: FunctionComponent<CodeModalProps> = ({
@@ -84,13 +94,32 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
84
94
  modalHeaderClassName,
85
95
  modalBodyClassName,
86
96
  modalFooterClassName,
97
+ spinnerAriaLabel = 'Loading',
87
98
  ...props
88
99
  }: CodeModalProps) => {
89
100
  const [newCode, setNewCode] = useState(code);
90
- const [editorInstance, setEditorInstance] = useState<monaco.editor.IStandaloneCodeEditor | null>(null);
101
+ const [editorInstance, setEditorInstance] = useState<any>(null);
91
102
  const [isEditorReady, setIsEditorReady] = useState(false);
103
+ const [isMonacoLoading, setIsMonacoLoading] = useState(false);
104
+ const [isMonacoLoaded, setIsMonacoLoaded] = useState(false);
92
105
  const containerRef = useRef<HTMLDivElement>(null);
93
106
 
107
+ useEffect(() => {
108
+ if (isModalOpen && !isMonacoLoaded && !isMonacoLoading) {
109
+ setIsMonacoLoading(true);
110
+ loadMonaco()
111
+ .then(() => {
112
+ setIsMonacoLoaded(true);
113
+ setIsMonacoLoading(false);
114
+ })
115
+ .catch((error) => {
116
+ // eslint-disable-next-line no-console
117
+ console.error('Failed to load Monaco editor:', error);
118
+ setIsMonacoLoading(false);
119
+ });
120
+ }
121
+ }, [isModalOpen, isMonacoLoaded, isMonacoLoading]);
122
+
94
123
  useEffect(() => {
95
124
  if (!isModalOpen || !isEditorReady || !editorInstance || !containerRef.current) {
96
125
  return;
@@ -148,6 +177,42 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
148
177
  }
149
178
  };
150
179
 
180
+ const renderMonacoEditor = () => {
181
+ if (isMonacoLoading) {
182
+ return (
183
+ <Bullseye>
184
+ <Spinner aria-label={spinnerAriaLabel} />
185
+ </Bullseye>
186
+ );
187
+ }
188
+ if (isMonacoLoaded) {
189
+ return (
190
+ <CodeEditor
191
+ isDarkTheme
192
+ isLineNumbersVisible={isLineNumbersVisible}
193
+ isLanguageLabelVisible
194
+ isCopyEnabled={isCopyEnabled}
195
+ isReadOnly={isReadOnly}
196
+ code={newCode}
197
+ language={extensionToLanguage[path.extname(fileName).slice(1)]}
198
+ onEditorDidMount={onEditorDidMount}
199
+ onCodeChange={onCodeChange}
200
+ className={codeEditorClassName}
201
+ isFullHeight
202
+ options={{
203
+ glyphMargin: false,
204
+ folding: false,
205
+ // prevents Monaco from handling resizing itself
206
+ // was causing ResizeObserver issues
207
+ automaticLayout: false
208
+ }}
209
+ {...props}
210
+ />
211
+ );
212
+ }
213
+ return null;
214
+ };
215
+
151
216
  const modal = (
152
217
  <ChatbotModal
153
218
  isOpen={isModalOpen}
@@ -166,27 +231,7 @@ export const CodeModal: FunctionComponent<CodeModalProps> = ({
166
231
  <FileDetails fileName={fileName} />
167
232
  </StackItem>
168
233
  <div className="pf-v6-l-stack__item pf-chatbot__code-modal-editor" ref={containerRef}>
169
- <CodeEditor
170
- isDarkTheme
171
- isLineNumbersVisible={isLineNumbersVisible}
172
- isLanguageLabelVisible
173
- isCopyEnabled={isCopyEnabled}
174
- isReadOnly={isReadOnly}
175
- code={newCode}
176
- language={extensionToLanguage[path.extname(fileName).slice(1)]}
177
- onEditorDidMount={onEditorDidMount}
178
- onCodeChange={onCodeChange}
179
- className={codeEditorClassName}
180
- isFullHeight
181
- options={{
182
- glyphMargin: false,
183
- folding: false,
184
- // prevents Monaco from handling resizing itself
185
- // was causing ResizeObserver issues
186
- automaticLayout: false
187
- }}
188
- {...props}
189
- />
234
+ {renderMonacoEditor()}
190
235
  </div>
191
236
  </Stack>
192
237
  </ModalBody>
@@ -0,0 +1,101 @@
1
+ .pf-chatbot__onboarding-modal {
2
+ overflow-x: hidden;
3
+
4
+ .pf-chatbot__onboarding--title {
5
+ margin-block-end: var(--pf-t--global--spacer--md);
6
+ }
7
+
8
+ .pf-chatbot__onboarding--section {
9
+ display: flex;
10
+ flex-direction: column;
11
+ width: 100%;
12
+ height: 100%;
13
+ }
14
+
15
+ .pf-chatbot__onboarding--modal-body {
16
+ display: flex;
17
+ flex-direction: column;
18
+ }
19
+
20
+ .pf-chatbot__onboarding--modal-text {
21
+ display: flex;
22
+ flex-direction: column;
23
+ justify-content: flex-end;
24
+ }
25
+
26
+ .pf-v6-c-content {
27
+ font-size: var(--pf-t--global--font--size--body--lg);
28
+ }
29
+
30
+ .pf-chatbot__onboarding--header {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ flex-direction: column;
35
+ max-height: 65%;
36
+
37
+ img {
38
+ max-width: unset;
39
+ height: 100%;
40
+ }
41
+ }
42
+
43
+ .pf-chatbot__onboarding--title {
44
+ font-size: var(--pf-t--global--font--size--heading--h1);
45
+ font-family: var(--pf-t--global--font--family--heading);
46
+ font-weight: var(--pf-t--global--font--weight--heading--bold);
47
+ }
48
+
49
+ .pf-chatbot__onboarding--footer {
50
+ margin-block-start: var(--pf-t--global--spacer--md);
51
+ }
52
+
53
+ // for handling zoom conditions; zoom to 125% or higher to see this
54
+ @media screen and (max-height: 620px) {
55
+ .pf-v6-c-modal-box__body {
56
+ --pf-v6-c-modal-box__body--MinHeight: auto;
57
+ overflow: visible;
58
+ }
59
+ }
60
+ }
61
+
62
+ .pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--docked.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--docked,
63
+ .pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--fullscreen.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--fullscreen,
64
+ .pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--embedded.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--embedded {
65
+ .pf-chatbot__onboarding--header {
66
+ img {
67
+ max-width: 100%;
68
+ height: auto;
69
+ }
70
+ }
71
+ }
72
+
73
+ .pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--fullscreen.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--fullscreen,
74
+ .pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--embedded.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--embedded {
75
+ // override parent modal style
76
+ height: inherit !important;
77
+
78
+ .pf-chatbot__onboarding--title {
79
+ font-size: var(--pf-t--global--font--size--heading--2xl);
80
+ }
81
+ }
82
+
83
+ .pf-chatbot__onboarding-modal.pf-m-compact {
84
+ .pf-chatbot__onboarding--header {
85
+ gap: var(--pf-t--global--spacer--md);
86
+ align-items: flex-start;
87
+ margin-block-start: var(--pf-t--global--spacer--lg);
88
+ }
89
+
90
+ .pf-chatbot__onboarding--modal-header {
91
+ --pf-v6-c-modal-box__header--PaddingBlockStart: var(--pf-t--global--spacer--md);
92
+ --pf-v6-c-modal-box__header--PaddingBlockEnd: var(--pf-t--global--spacer--md);
93
+ --pf-v6-c-modal-box__header--PaddingInlineStart: var(--pf-t--global--spacer--md);
94
+ --pf-v6-c-modal-box__header--PaddingInlineEnd: var(--pf-t--global--spacer--md);
95
+ }
96
+
97
+ .pf-chatbot__onboarding--modal-body {
98
+ --pf-v6-c-modal-box__body--PaddingInlineStart: var(--pf-t--global--spacer--md);
99
+ --pf-v6-c-modal-box__body--PaddingInlineEnd: var(--pf-t--global--spacer--md);
100
+ }
101
+ }
@@ -0,0 +1,148 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import '@testing-library/jest-dom';
3
+ import userEvent from '@testing-library/user-event';
4
+ import Onboarding from './Onboarding';
5
+
6
+ const handleModalToggle = jest.fn();
7
+ const onPrimaryAction = jest.fn();
8
+ const onSecondaryAction = jest.fn();
9
+
10
+ const body =
11
+ 'Experience personalized assistance and seamless problem-solving, simplifying your journey with Red Hat every step of the way.';
12
+
13
+ describe('Onboarding', () => {
14
+ afterEach(() => {
15
+ jest.clearAllMocks();
16
+ });
17
+ it('should render modal correctly', () => {
18
+ render(
19
+ <Onboarding
20
+ isModalOpen
21
+ onSecondaryAction={onSecondaryAction}
22
+ handleModalToggle={handleModalToggle}
23
+ ouiaId="Terms"
24
+ >
25
+ {body}
26
+ </Onboarding>
27
+ );
28
+ expect(screen.getByRole('heading', { name: /Onboarding/i })).toBeTruthy();
29
+ expect(screen.getByRole('button', { name: /Continue/i })).toBeTruthy();
30
+ expect(screen.getByRole('button', { name: /Skip/i })).toBeTruthy();
31
+ expect(screen.getByText(body)).toBeTruthy();
32
+ expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__onboarding-modal');
33
+ expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__onboarding-modal--default');
34
+ });
35
+ it('should handle image and altText props', () => {
36
+ render(
37
+ <Onboarding
38
+ isModalOpen
39
+ onSecondaryAction={onSecondaryAction}
40
+ handleModalToggle={handleModalToggle}
41
+ headerImage="./image.png"
42
+ headerImageAltText="Test image"
43
+ >
44
+ {body}
45
+ </Onboarding>
46
+ );
47
+ expect(screen.getByRole('img')).toBeTruthy();
48
+ expect(screen.getByRole('img')).toHaveAttribute('alt', 'Test image');
49
+ });
50
+ it('should handle className prop', () => {
51
+ render(
52
+ <Onboarding
53
+ isModalOpen
54
+ onSecondaryAction={onSecondaryAction}
55
+ handleModalToggle={handleModalToggle}
56
+ className="test"
57
+ >
58
+ {body}
59
+ </Onboarding>
60
+ );
61
+ expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__onboarding-modal');
62
+ expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__onboarding-modal--default');
63
+ expect(screen.getByRole('dialog')).toHaveClass('test');
64
+ });
65
+ it('should handle title prop', () => {
66
+ render(
67
+ <Onboarding
68
+ isModalOpen
69
+ onSecondaryAction={onSecondaryAction}
70
+ handleModalToggle={handleModalToggle}
71
+ title="Updated title"
72
+ >
73
+ {body}
74
+ </Onboarding>
75
+ );
76
+ expect(screen.getByRole('heading', { name: /Updated title/i })).toBeTruthy();
77
+ expect(screen.queryByRole('heading', { name: /Onboarding/i })).toBeFalsy();
78
+ });
79
+ it('should handle primary button prop', () => {
80
+ render(
81
+ <Onboarding
82
+ isModalOpen
83
+ onSecondaryAction={onSecondaryAction}
84
+ handleModalToggle={handleModalToggle}
85
+ primaryActionBtn="First"
86
+ >
87
+ {body}
88
+ </Onboarding>
89
+ );
90
+ expect(screen.getByRole('button', { name: /First/i })).toBeTruthy();
91
+ expect(screen.queryByRole('button', { name: /Continue/i })).toBeFalsy();
92
+ });
93
+ it('should handle secondary button prop', () => {
94
+ render(
95
+ <Onboarding
96
+ isModalOpen
97
+ onSecondaryAction={onSecondaryAction}
98
+ handleModalToggle={handleModalToggle}
99
+ secondaryActionBtn="Second"
100
+ >
101
+ {body}
102
+ </Onboarding>
103
+ );
104
+ expect(screen.getByRole('button', { name: /Second/i })).toBeTruthy();
105
+ expect(screen.queryByRole('button', { name: /Skip/i })).toBeFalsy();
106
+ });
107
+ it('should handle primary button click', async () => {
108
+ render(
109
+ <Onboarding
110
+ isModalOpen
111
+ onPrimaryAction={onPrimaryAction}
112
+ onSecondaryAction={onSecondaryAction}
113
+ handleModalToggle={handleModalToggle}
114
+ >
115
+ {body}
116
+ </Onboarding>
117
+ );
118
+ await userEvent.click(screen.getByRole('button', { name: /Continue/i }));
119
+ expect(onPrimaryAction).toHaveBeenCalledTimes(1);
120
+ expect(handleModalToggle).toHaveBeenCalledTimes(1);
121
+ });
122
+ it('should handle secondary button click', async () => {
123
+ render(
124
+ <Onboarding isModalOpen onSecondaryAction={onSecondaryAction} handleModalToggle={handleModalToggle}>
125
+ {body}
126
+ </Onboarding>
127
+ );
128
+ await userEvent.click(screen.getByRole('button', { name: /Skip/i }));
129
+ expect(onSecondaryAction).toHaveBeenCalledTimes(1);
130
+ expect(handleModalToggle).not.toHaveBeenCalled();
131
+ });
132
+ it('should handle isCompact prop', () => {
133
+ render(
134
+ <Onboarding
135
+ isModalOpen
136
+ onSecondaryAction={onSecondaryAction}
137
+ handleModalToggle={handleModalToggle}
138
+ isCompact={true}
139
+ headerImage="./image.png"
140
+ headerImageAltText="Test image"
141
+ >
142
+ {body}
143
+ </Onboarding>
144
+ );
145
+ expect(screen.getByRole('dialog')).toHaveClass('pf-m-compact');
146
+ expect(screen.queryByRole('img')).toBeFalsy();
147
+ });
148
+ });