Skip to content

Commit bb2dac9

Browse files
committed
feat(Onboarding): Add modal
1 parent c082f9c commit bb2dac9

File tree

9 files changed

+519
-0
lines changed

9 files changed

+519
-0
lines changed
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import {
2+
useRef,
3+
useState,
4+
FunctionComponent,
5+
MouseEvent,
6+
CSSProperties,
7+
Ref,
8+
MouseEvent as ReactMouseEvent
9+
} from 'react';
10+
import {
11+
Button,
12+
Checkbox,
13+
SkipToContent,
14+
MenuToggle,
15+
MenuToggleElement,
16+
Select,
17+
SelectList,
18+
SelectOption,
19+
Stack
20+
} from '@patternfly/react-core';
21+
import Onboarding from '@patternfly/chatbot/dist/dynamic/Onboarding';
22+
import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot';
23+
import onboardingHeader from './RH-Hat-Image.svg';
24+
25+
export const OnboardingExample: FunctionComponent = () => {
26+
const [isModalOpen, setIsModalOpen] = useState(true);
27+
const [displayMode, setDisplayMode] = useState(ChatbotDisplayMode.default);
28+
const [hasImage, setHasImage] = useState(true);
29+
const chatbotRef = useRef<HTMLDivElement>(null);
30+
const termsRef = useRef<HTMLDivElement>(null);
31+
const [isOpen, setIsOpen] = useState(false);
32+
const [selected, setSelected] = useState<string>('Select display mode');
33+
34+
const handleSkipToContent = (e) => {
35+
e.preventDefault();
36+
if (!isModalOpen && chatbotRef.current) {
37+
chatbotRef.current.focus();
38+
}
39+
if (isModalOpen && termsRef.current) {
40+
termsRef.current.focus();
41+
}
42+
};
43+
44+
const handleModalToggle = (_event: MouseEvent | MouseEvent | KeyboardEvent) => {
45+
setIsModalOpen(!isModalOpen);
46+
};
47+
48+
const onPrimaryAction = () => {
49+
// eslint-disable-next-line no-console
50+
console.log('Clicked primary action');
51+
};
52+
53+
const onSecondaryAction = () => {
54+
// eslint-disable-next-line no-console
55+
console.log('Clicked secondary action');
56+
};
57+
const onSelect = (_event: ReactMouseEvent<Element, MouseEvent> | undefined, value: string | number | undefined) => {
58+
setSelected(value as string);
59+
setIsOpen(false);
60+
if (value === 'Default') {
61+
setDisplayMode(ChatbotDisplayMode.default);
62+
}
63+
if (value === 'Docked') {
64+
setDisplayMode(ChatbotDisplayMode.docked);
65+
}
66+
if (value === 'Fullscreen') {
67+
setDisplayMode(ChatbotDisplayMode.fullscreen);
68+
}
69+
if (value === 'Embedded') {
70+
setDisplayMode(ChatbotDisplayMode.embedded);
71+
}
72+
};
73+
74+
const onToggleClick = () => {
75+
setIsOpen(!isOpen);
76+
};
77+
78+
const toggle = (toggleRef: Ref<MenuToggleElement>) => (
79+
<MenuToggle
80+
ref={toggleRef}
81+
onClick={onToggleClick}
82+
isExpanded={isOpen}
83+
style={
84+
{
85+
width: '200px'
86+
} as CSSProperties
87+
}
88+
>
89+
{selected}
90+
</MenuToggle>
91+
);
92+
93+
const body =
94+
'Experience personalized assistance and seamless problem-solving, simplifying your journey with Red Hat every step of the way.';
95+
96+
return (
97+
<>
98+
<SkipToContent style={{ zIndex: '999' }} onClick={handleSkipToContent} href="#">
99+
Skip to chatbot
100+
</SkipToContent>
101+
<div
102+
style={{
103+
position: 'fixed',
104+
padding: 'var(--pf-t--global--spacer--lg)',
105+
zIndex: '601',
106+
boxShadow: 'var(--pf-t--global--box-shadow--lg)'
107+
}}
108+
>
109+
<Stack hasGutter>
110+
<Select
111+
id="single-select"
112+
isOpen={isOpen}
113+
selected={selected}
114+
onSelect={onSelect}
115+
onOpenChange={(isOpen) => setIsOpen(isOpen)}
116+
toggle={toggle}
117+
shouldFocusToggleOnSelect
118+
>
119+
<SelectList>
120+
<SelectOption value="Default">Default</SelectOption>
121+
<SelectOption value="Docked">Docked</SelectOption>
122+
<SelectOption value="Fullscreen">Fullscreen</SelectOption>
123+
<SelectOption value="Embedded">Embedded</SelectOption>
124+
</SelectList>
125+
</Select>
126+
<Checkbox
127+
isChecked={hasImage}
128+
aria-label="Toggle whether terms and conditions has a header image"
129+
id="toggle-header-image"
130+
name="toggle-header-image"
131+
label="Has image in header"
132+
onChange={(_event, checked) => setHasImage(checked)}
133+
></Checkbox>
134+
<Button onClick={handleModalToggle}>Launch modal</Button>
135+
</Stack>
136+
</div>
137+
<Chatbot ref={chatbotRef} displayMode={displayMode} isVisible></Chatbot>
138+
<Onboarding
139+
ref={termsRef}
140+
displayMode={displayMode}
141+
isModalOpen={isModalOpen}
142+
handleModalToggle={handleModalToggle}
143+
onPrimaryAction={onPrimaryAction}
144+
onSecondaryAction={onSecondaryAction}
145+
image={hasImage ? onboardingHeader : undefined}
146+
altText={hasImage ? 'Open book' : undefined}
147+
title="Redefine work in the age of AI"
148+
>
149+
{body}
150+
</Onboarding>
151+
</>
152+
);
153+
};

packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/RH-Hat-Image.svg

Lines changed: 9 additions & 0 deletions
Loading

packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import FileDropZone from '@patternfly/chatbot/dist/dynamic/FileDropZone';
5353
import { PreviewAttachment } from '@patternfly/chatbot/dist/dynamic/PreviewAttachment';
5454
import ChatbotAlert from '@patternfly/chatbot/dist/dynamic/ChatbotAlert';
5555
import TermsOfUse from '@patternfly/chatbot/dist/dynamic/TermsOfUse';
56+
import Onboarding from '@patternfly/chatbot/dist/dynamic/Onboarding';
5657
import {
5758
ChatbotHeader,
5859
ChatbotHeaderCloseButton,
@@ -85,6 +86,7 @@ import PFHorizontalLogoReverse from './PF-HorizontalLogo-Reverse.svg';
8586
import userAvatar from '../Messages/user_avatar.svg';
8687
import patternflyAvatar from '../Messages/patternfly_avatar.jpg';
8788
import termsAndConditionsHeader from './PF-TermsAndConditionsHeader.svg';
89+
import onboardingHeader from './RH-Hat-Image.svg';
8890
import { CloseIcon, SearchIcon, OutlinedCommentsIcon } from '@patternfly/react-icons';
8991
import { FunctionComponent, FormEvent, useState, useRef, MouseEvent, isValidElement, cloneElement, Children, ReactNode, Ref, MouseEvent as ReactMouseEvent, CSSProperties, useEffect} from 'react';
9092
import FilePreview from '@patternfly/chatbot/dist/dynamic/FilePreview';
@@ -450,6 +452,12 @@ To make the settings menu compact, with less spacing between the menu contents,
450452

451453
```
452454

455+
### Onboarding
456+
457+
```js file="./Onboarding.tsx" isFullscreen
458+
459+
```
460+
453461
## Modals
454462

455463
### Modal
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
.pf-chatbot__onboarding-modal {
2+
.pf-v6-c-content {
3+
font-size: var(--pf-t--global--font--size--body--lg);
4+
5+
h2 {
6+
font-size: var(--pf-t--global--icon--size--font--heading--h2);
7+
font-family: var(--pf-t--global--font--family--heading);
8+
margin-bottom: var(--pf-t--global--spacer--md);
9+
margin-top: var(--pf-t--global--spacer--md);
10+
font-weight: var(--pf-t--global--font--weight--heading--default);
11+
}
12+
h2:first-of-type {
13+
margin-top: 0;
14+
}
15+
}
16+
17+
.pf-chatbot__onboarding--header {
18+
display: flex;
19+
align-items: center;
20+
justify-content: center;
21+
flex-direction: column;
22+
gap: var(--pf-t--global--spacer--xl);
23+
margin-block-start: var(--pf-t--global--spacer--xl);
24+
}
25+
26+
.pf-chatbot__onboarding--title {
27+
font-size: var(--pf-t--global--font--size--heading--h1);
28+
font-family: var(--pf-t--global--font--family--heading);
29+
font-weight: var(--pf-t--global--font--weight--heading--bold);
30+
}
31+
32+
.pf-chatbot__onboarding--footer {
33+
margin-block-start: var(--pf-t--global--spacer--md);
34+
}
35+
36+
.pf-chatbot__onboarding--section {
37+
display: flex;
38+
flex-direction: column;
39+
height: 100%;
40+
width: 100%;
41+
}
42+
43+
// for handling zoom conditions; zoom to 125% or higher to see this
44+
@media screen and (max-height: 620px) {
45+
.pf-v6-c-modal-box__body {
46+
--pf-v6-c-modal-box__body--MinHeight: auto;
47+
overflow: visible;
48+
}
49+
}
50+
}
51+
52+
.pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--fullscreen.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--fullscreen,
53+
.pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--embedded.pf-chatbot__onboarding-modal.pf-chatbot__onboarding-modal--embedded {
54+
// override parent modal style
55+
height: inherit !important;
56+
57+
.pf-v6-c-content {
58+
h2 {
59+
font-size: var(--pf-t--global--icon--size--font--heading--h1);
60+
}
61+
}
62+
63+
.pf-chatbot__onboarding--title {
64+
font-size: var(--pf-t--global--font--size--heading--2xl);
65+
}
66+
}
67+
68+
.pf-chatbot__onboarding-modal.pf-m-compact {
69+
.pf-chatbot__onboarding--header {
70+
gap: var(--pf-t--global--spacer--md);
71+
align-items: flex-start;
72+
margin-block-start: var(--pf-t--global--spacer--lg);
73+
}
74+
75+
.pf-chatbot__onboarding--modal-header {
76+
--pf-v6-c-modal-box__header--PaddingBlockStart: var(--pf-t--global--spacer--md);
77+
--pf-v6-c-modal-box__header--PaddingBlockEnd: var(--pf-t--global--spacer--md);
78+
--pf-v6-c-modal-box__header--PaddingInlineStart: var(--pf-t--global--spacer--md);
79+
--pf-v6-c-modal-box__header--PaddingInlineEnd: var(--pf-t--global--spacer--md);
80+
}
81+
82+
.pf-chatbot__onboarding--modal-body {
83+
--pf-v6-c-modal-box__body--PaddingInlineStart: var(--pf-t--global--spacer--md);
84+
--pf-v6-c-modal-box__body--PaddingInlineEnd: var(--pf-t--global--spacer--md);
85+
}
86+
}

0 commit comments

Comments
 (0)