Skip to content

Commit efdf26d

Browse files
committed
feat(ChatbotModal): Add style-able modal for general use
1 parent c7b51b8 commit efdf26d

File tree

9 files changed

+206
-95
lines changed

9 files changed

+206
-95
lines changed

packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/Chatbot/Chatbot.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import ChatbotContent from '@patternfly/virtual-assistant/dist/dynamic/ChatbotCo
2525
import ChatbotWelcomePrompt from '@patternfly/virtual-assistant/dist/dynamic/ChatbotWelcomePrompt';
2626
import MessageBox from '@patternfly/virtual-assistant/dist/dynamic/MessageBox';
2727
import Message from '@patternfly/virtual-assistant/dist/dynamic/Message';
28+
import { ChatbotModal } from '@patternfly/virtual-assistant/dist/dynamic/ChatbotModal';
2829

2930
### Container
3031

@@ -71,3 +72,11 @@ To provide users with a more specific direction, you can also include optional w
7172
```js file="./ChatbotWelcomePrompt.tsx"
7273

7374
```
75+
76+
### Modal
77+
78+
We offer a customized [PatternFly modal](/components/modal) for use in chatbots. It will adapt to the display mode and will accept components usually used in a modal. It is used and primarily tested in the context of the [attachment modals](/patternfly-ai/chatbot/chatbot-attachments#attachment-preview). It is being exposed so that consumers can also use it, but it may require customization for your specific use case.
79+
80+
```js file="./ChatbotModal.tsx"
81+
82+
```
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { Button, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core';
3+
import { ChatbotModal } from '@patternfly/virtual-assistant/dist/dynamic/ChatbotModal';
4+
import { ChatbotDisplayMode } from '../../../../../../dist/esm/Chatbot';
5+
6+
export const ChatbotModalExample: React.FunctionComponent = () => {
7+
const [isModalOpen, setIsModalOpen] = React.useState(false);
8+
9+
const handleModalToggle = (_event: React.MouseEvent | MouseEvent | KeyboardEvent) => {
10+
setIsModalOpen(!isModalOpen);
11+
};
12+
13+
return (
14+
<>
15+
<Button onClick={handleModalToggle}>Launch modal</Button>
16+
<ChatbotModal
17+
isOpen={isModalOpen}
18+
displayMode={ChatbotDisplayMode.default}
19+
onClose={handleModalToggle}
20+
ouiaId="ChatbotModal"
21+
aria-labelledby="basic-modal-title"
22+
aria-describedby="modal-box-body-basic"
23+
>
24+
<ModalHeader title="Basic modal" labelId="basic-modal-title" />
25+
<ModalBody id="modal-box-body-basic">
26+
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
27+
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
28+
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
29+
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
30+
est laborum.
31+
</ModalBody>
32+
<ModalFooter>
33+
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
34+
Confirm
35+
</Button>
36+
<Button key="cancel" variant="link" onClick={handleModalToggle}>
37+
Cancel
38+
</Button>
39+
</ModalFooter>
40+
</ChatbotModal>
41+
</>
42+
);
43+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
.pf-chatbot__chatbot-modal-backdrop {
2+
position: static;
3+
}
4+
5+
.pf-chatbot__chatbot-modal {
6+
--pf-v6-c-modal-box--BorderRadius: var(--pf-t--global--border--radius--medium);
7+
position: fixed;
8+
inset-block-end: var(--pf-t--global--spacer--800); // no associated semantic token
9+
inset-inline-end: var(--pf-t--global--spacer--lg);
10+
width: 30rem;
11+
height: 70vh;
12+
background-color: var(--pf-t--global--background--color--secondary--default);
13+
14+
.pf-v6-c-modal-box__title {
15+
--pf-v6-c-modal-box__title--FontSize: var(--pf-t--global--font--size--heading--h3);
16+
}
17+
.pf-v6-c-button.pf-m-primary.pf-m-block,
18+
.pf-v6-c-button.pf-m-link.pf-m-block {
19+
--pf-v6-c-button--FontWeight: 500;
20+
}
21+
.pf-v6-c-modal-box__close {
22+
--pf-v6-c-modal-box__close--InsetBlockStart: 1.125rem;
23+
}
24+
.pf-v6-c-modal-box__footer {
25+
padding-block-start: var(--pf-t--global--spacer--xl);
26+
padding-block-end: var(--pf-t--global--spacer--xl);
27+
}
28+
.pf-v6-c-modal-box__header {
29+
padding-block-end: var(--pf-t--global--spacer--lg);
30+
}
31+
}
32+
33+
// ============================================================================
34+
// Chatbot Display Mode - Fullscreen and Embedded
35+
// ============================================================================
36+
@media screen and (max-width: 600px) {
37+
.pf-chatbot__chatbot-modal--embedded,
38+
.pf-chatbot__chatbot-modal--fullscreen {
39+
inset-block-end: 0;
40+
inset-inline-end: 0;
41+
width: 100%;
42+
height: 100%;
43+
top: 50%;
44+
left: 50%;
45+
transform: translate(-50%, -50%);
46+
}
47+
}
48+
@media screen and (min-width: 601px) {
49+
.pf-chatbot__chatbot-modal--embedded,
50+
.pf-chatbot__chatbot-modal--fullscreen {
51+
inset-block-end: 0;
52+
inset-inline-end: 0;
53+
width: 50%;
54+
height: fit-content;
55+
top: 50%;
56+
left: 50%;
57+
transform: translate(-50%, -50%);
58+
}
59+
}
60+
61+
// ============================================================================
62+
// Chatbot Display Mode - Default
63+
// ============================================================================
64+
.pf-chatbot__chatbot-modal--default {
65+
box-shadow: unset;
66+
}
67+
68+
// ============================================================================
69+
// Chatbot Display Mode - Docked
70+
// ============================================================================
71+
.pf-chatbot__chatbot-modal--docked {
72+
height: 100vh;
73+
inset-block-end: 0;
74+
inset-inline-end: 0;
75+
border-radius: 0;
76+
--pf-v6-c-modal-box--MaxHeight: 100vh;
77+
box-shadow: unset;
78+
}
79+
80+
// ============================================================================
81+
// Dark theme
82+
// ============================================================================
83+
.pf-v6-theme-dark {
84+
.pf-v6-c-modal-box.pf-chatbot__chatbot-modal {
85+
.pf-v6-c-modal-box__title {
86+
color: #fff;
87+
}
88+
}
89+
}
90+
91+
// ============================================================================
92+
// Backdrop
93+
// ============================================================================
94+
.pf-v6-c-backdrop.pf-chatbot__backdrop {
95+
position: absolute;
96+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// ============================================================================
2+
// Code Modal - Chatbot Modal with Code Editor
3+
// ============================================================================
4+
import React from 'react';
5+
6+
// Import PatternFly components
7+
import { Modal, ModalProps } from '@patternfly/react-core';
8+
import { ChatbotDisplayMode } from '../Chatbot';
9+
10+
export interface ChatbotModalProps extends Omit<ModalProps, 'ref'> {
11+
/** Display mode for the Chatbot parent; this influences the styles applied */
12+
displayMode?: ChatbotDisplayMode;
13+
className?: string;
14+
}
15+
16+
export const ChatbotModal: React.FunctionComponent<ChatbotModalProps> = ({
17+
children,
18+
displayMode = ChatbotDisplayMode.default,
19+
className,
20+
isOpen,
21+
...props
22+
}: ChatbotModalProps) => {
23+
const modal = (
24+
<Modal
25+
isOpen={isOpen}
26+
ouiaId="ChatbotModal"
27+
aria-labelledby="chatbot-modal-title"
28+
aria-describedby="chatbot-modal"
29+
className={`pf-chatbot__chatbot-modal pf-chatbot__chatbot-modal--${displayMode} ${className}`}
30+
backdropClassName="pf-chatbot__chatbot-modal-backdrop"
31+
{...props}
32+
>
33+
{children}
34+
</Modal>
35+
);
36+
37+
if ((displayMode === ChatbotDisplayMode.fullscreen || displayMode === ChatbotDisplayMode.embedded) && isOpen) {
38+
return <div className="pf-v6-c-backdrop pf-chatbot__backdrop">{modal}</div>;
39+
}
40+
return modal;
41+
};
42+
43+
export default ChatbotModal;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { default } from './ChatbotModal';
2+
3+
export * from './ChatbotModal';
Lines changed: 3 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,4 @@
1-
.pf-chatbot__code-modal-backdrop {
2-
position: static;
3-
}
4-
51
.pf-chatbot__code-modal {
6-
--pf-v6-c-modal-box--BorderRadius: var(--pf-t--global--border--radius--medium);
7-
position: fixed;
8-
inset-block-end: var(--pf-t--global--spacer--800); // no associated semantic token
9-
inset-inline-end: var(--pf-t--global--spacer--lg);
10-
width: 30rem;
11-
height: 70vh;
12-
background-color: var(--pf-t--global--background--color--secondary--default);
13-
14-
.pf-v6-c-modal-box__title {
15-
--pf-v6-c-modal-box__title--FontSize: var(--pf-t--global--font--size--heading--h3);
16-
}
172
.pf-v6-c-code-editor {
183
--pf-v6-c-code-editor__main--BackgroundColor: #1f1f1f;
194
--pf-v6-c-code-editor__main--BorderEndStartRadius: 0;
@@ -26,6 +11,9 @@
2611
border-start-start-radius: var(--pf-t--global--border--radius--small);
2712
border-start-end-radius: var(--pf-t--global--border--radius--small);
2813
}
14+
.pf-chatbot__code-modal-body {
15+
gap: var(--pf-t--global--spacer--lg);
16+
}
2917
.pf-chatbot__code-modal--controls > .pf-v6-c-code-editor__header {
3018
flex-direction: row-reverse;
3119
border-radius: var(--pf-t--global--border--radius--small);
@@ -70,75 +58,11 @@
7058
.pf-v6-c-code-editor__header-main {
7159
display: none;
7260
}
73-
.pf-v6-c-button.pf-m-primary.pf-m-block,
74-
.pf-v6-c-button.pf-m-link.pf-m-block {
75-
--pf-v6-c-button--FontWeight: 500;
76-
}
77-
.pf-v6-c-modal-box__close {
78-
--pf-v6-c-modal-box__close--InsetBlockStart: 1.125rem;
79-
}
80-
.pf-chatbot__code-modal-body {
81-
gap: var(--pf-t--global--spacer--lg);
82-
}
83-
.pf-v6-c-modal-box__footer {
84-
padding-block-start: var(--pf-t--global--spacer--xl);
85-
padding-block-end: var(--pf-t--global--spacer--xl);
86-
}
87-
.pf-v6-c-modal-box__header {
88-
padding-block-end: var(--pf-t--global--spacer--lg);
89-
}
9061
.pf-chatbot__code-modal-file-details {
9162
padding-inline-start: var(--pf-t--global--spacer--md);
9263
}
9364
}
9465

95-
// ============================================================================
96-
// Chatbot Display Mode - Fullscreen and Embedded
97-
// ============================================================================
98-
@media screen and (max-width: 600px) {
99-
.pf-chatbot__code-modal--embedded,
100-
.pf-chatbot__code-modal--fullscreen {
101-
inset-block-end: 0;
102-
inset-inline-end: 0;
103-
width: 100%;
104-
height: 100%;
105-
top: 50%;
106-
left: 50%;
107-
transform: translate(-50%, -50%);
108-
}
109-
}
110-
@media screen and (min-width: 601px) {
111-
.pf-chatbot__code-modal--embedded,
112-
.pf-chatbot__code-modal--fullscreen {
113-
inset-block-end: 0;
114-
inset-inline-end: 0;
115-
width: 50%;
116-
height: fit-content;
117-
top: 50%;
118-
left: 50%;
119-
transform: translate(-50%, -50%);
120-
}
121-
}
122-
123-
// ============================================================================
124-
// Chatbot Display Mode - Default
125-
// ============================================================================
126-
.pf-chatbot__code-modal--default {
127-
box-shadow: unset;
128-
}
129-
130-
// ============================================================================
131-
// Chatbot Display Mode - Docked
132-
// ============================================================================
133-
.pf-chatbot__code-modal--docked {
134-
height: 100vh;
135-
inset-block-end: 0;
136-
inset-inline-end: 0;
137-
border-radius: 0;
138-
--pf-v6-c-modal-box--MaxHeight: 100vh;
139-
box-shadow: unset;
140-
}
141-
14266
// ============================================================================
14367
// Dark theme
14468
// ============================================================================
@@ -149,16 +73,4 @@
14973
--pf-v6-c-button--hover__icon--Color: #c7c7c7;
15074
}
15175
}
152-
.pf-v6-c-modal-box.pf-chatbot__code-modal {
153-
.pf-v6-c-modal-box__title {
154-
color: #fff;
155-
}
156-
}
157-
}
158-
159-
// ============================================================================
160-
// Backdrop
161-
// ============================================================================
162-
.pf-v6-c-backdrop.pf-chatbot__backdrop {
163-
position: absolute;
16476
}

packages/module/src/CodeModal/CodeModal.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ import path from 'path-browserify';
66

77
// Import PatternFly components
88
import { CodeEditor } from '@patternfly/react-code-editor';
9-
import { Button, Modal, ModalBody, ModalFooter, ModalHeader, Stack, StackItem } from '@patternfly/react-core';
9+
import { Button, ModalBody, ModalFooter, ModalHeader, Stack, StackItem } from '@patternfly/react-core';
1010
import FileDetails, { extensionToLanguage } from '../FileDetails';
1111
import { ChatbotDisplayMode } from '../Chatbot';
12+
import ChatbotModal from '../ChatbotModal/ChatbotModal';
1213

1314
export interface CodeModalProps {
1415
/** Class applied to code editor */
@@ -98,14 +99,14 @@ export const CodeModal: React.FunctionComponent<CodeModalProps> = ({
9899
/* eslint-enable indent */
99100

100101
const modal = (
101-
<Modal
102+
<ChatbotModal
102103
isOpen={isModalOpen}
103104
onClose={handleModalToggle}
104105
ouiaId="CodeModal"
105106
aria-labelledby="code-modal-title"
106107
aria-describedby="code-modal"
107108
className={`pf-chatbot__code-modal pf-chatbot__code-modal--${displayMode}`}
108-
backdropClassName="pf-chatbot__code-modal-backdrop"
109+
displayMode={displayMode}
109110
>
110111
<ModalHeader title={title} labelId="code-modal-title" />
111112
<ModalBody id="code-modal-body">
@@ -143,7 +144,7 @@ export const CodeModal: React.FunctionComponent<CodeModalProps> = ({
143144
{secondaryActionBtn}
144145
</Button>
145146
</ModalFooter>
146-
</Modal>
147+
</ChatbotModal>
147148
);
148149

149150
if ((displayMode === ChatbotDisplayMode.fullscreen || displayMode === ChatbotDisplayMode.embedded) && isModalOpen) {

packages/module/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export * from './ChatbotFooter';
2424
export { default as ChatbotHeader } from './ChatbotHeader';
2525
export * from './ChatbotHeader';
2626

27+
export { default as ChatbotModal } from './ChatbotModal';
28+
export * from './ChatbotModal';
29+
2730
export { default as ChatbotPopover } from './ChatbotPopover';
2831
export * from './ChatbotPopover';
2932

packages/module/src/main.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
@import './ChatbotConversationHistoryNav/ChatbotConversationHistoryNav';
66
@import './ChatbotFooter/ChatbotFooter';
77
@import './ChatbotHeader/ChatbotHeader';
8+
@import './ChatbotModal/ChatbotModal';
89
@import './ChatbotPopover/ChatbotPopover';
910
@import './ChatbotToggle/ChatbotToggle';
1011
@import './ChatbotWelcomePrompt/ChatbotWelcomePrompt';

0 commit comments

Comments
 (0)