Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ChatbotModal): Add style-able modal for general use #307

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React from 'react';
import { Button, FormGroup, ModalBody, ModalFooter, ModalHeader, Radio } from '@patternfly/react-core';
import { ChatbotModal } from '@patternfly/virtual-assistant/dist/dynamic/ChatbotModal';
import Chatbot, { ChatbotDisplayMode } from '@patternfly/virtual-assistant/dist/dynamic/Chatbot';

export const ChatbotModalExample: React.FunctionComponent = () => {
const [isModalOpen, setIsModalOpen] = React.useState(false);
const [displayMode, setDisplayMode] = React.useState(ChatbotDisplayMode.default);

const handleModalToggle = (_event: React.MouseEvent | MouseEvent | KeyboardEvent) => {
setIsModalOpen(!isModalOpen);
};

return (
<>
<div
style={{
position: 'fixed',
padding: 'var(--pf-t--global--spacer--lg)',
zIndex: '601',
boxShadow: 'var(--pf-t--global--box-shadow--lg)'
}}
>
<FormGroup role="radiogroup" isInline fieldId="basic-form-radio-group" label="Display mode">
<Radio
isChecked={displayMode === ChatbotDisplayMode.default}
onChange={() => setDisplayMode(ChatbotDisplayMode.default)}
name="basic-inline-radio"
label="Default"
id="default"
/>
<Radio
isChecked={displayMode === ChatbotDisplayMode.docked}
onChange={() => setDisplayMode(ChatbotDisplayMode.docked)}
name="basic-inline-radio"
label="Docked"
id="docked"
/>
<Radio
isChecked={displayMode === ChatbotDisplayMode.fullscreen}
onChange={() => setDisplayMode(ChatbotDisplayMode.fullscreen)}
name="basic-inline-radio"
label="Fullscreen"
id="fullscreen"
/>
<Radio
isChecked={displayMode === ChatbotDisplayMode.embedded}
onChange={() => setDisplayMode(ChatbotDisplayMode.embedded)}
name="basic-inline-radio"
label="Embedded"
id="embedded"
/>
</FormGroup>
<Button onClick={handleModalToggle}>Launch modal</Button>
</div>
<Chatbot displayMode={displayMode} isVisible></Chatbot>
<ChatbotModal
isOpen={isModalOpen}
displayMode={displayMode}
onClose={handleModalToggle}
ouiaId="ChatbotModal"
aria-labelledby="basic-modal-title"
aria-describedby="modal-box-body-basic"
>
<ModalHeader title="Basic modal" labelId="basic-modal-title" />
<ModalBody id="modal-box-body-basic">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
est laborum.
</ModalBody>
<ModalFooter>
<Button key="confirm" variant="primary" onClick={handleModalToggle}>
Confirm
</Button>
<Button key="cancel" variant="link" onClick={handleModalToggle}>
Cancel
</Button>
</ModalFooter>
</ChatbotModal>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,33 @@ id: UI
source: react
# If you use typescript, the name of the interface to display props for
# These are found through the sourceProps function provided in patternfly-docs.source.js
propComponents: [
'Chatbot',
'ChatbotContent',
'MessageBox',
'ChatbotWelcomePrompt',
'WelcomePrompt',
'ChatbotToggle',
'ChatbotHeader',
'ChatbotHeaderMain',
'ChatbotHeaderMenu',
'ChatbotHeaderActions',
'ChatbotHeaderTitle',
'ChatbotHeaderOptionsDropdown',
'ChatbotHeaderSelectorDropdown',
'ChatbotFooter',
'MessageBar',
'ChatbotFootnote',
'ChatbotFootnotePopover',
'ChatbotFootnotePopoverCTA',
'ChatbotFootnotePopoverBannerImage',
'ChatbotFootnotePopoverLink',
'MessageBarWithAttachMenuProps',
'SourceDetailsMenuItem',
'ChatbotConversationHistoryNav',
'Conversation'
]
propComponents:
[
'Chatbot',
'ChatbotContent',
'MessageBox',
'ChatbotWelcomePrompt',
'WelcomePrompt',
'ChatbotToggle',
'ChatbotHeader',
'ChatbotHeaderMain',
'ChatbotHeaderMenu',
'ChatbotHeaderActions',
'ChatbotHeaderTitle',
'ChatbotHeaderOptionsDropdown',
'ChatbotHeaderSelectorDropdown',
'ChatbotFooter',
'MessageBar',
'ChatbotFootnote',
'ChatbotFootnotePopover',
'ChatbotFootnotePopoverCTA',
'ChatbotFootnotePopoverBannerImage',
'ChatbotFootnotePopoverLink',
'MessageBarWithAttachMenuProps',
'SourceDetailsMenuItem',
'ChatbotConversationHistoryNav',
'Conversation'
]
sortValue: 2
---

Expand Down Expand Up @@ -63,6 +64,7 @@ ChatbotHeaderSelectorDropdown
import { ChatbotFooter, ChatbotFootnote } from '@patternfly/virtual-assistant/dist/dynamic/ChatbotFooter';
import { MessageBar } from '@patternfly/virtual-assistant/dist/dynamic/MessageBar';
import SourceDetailsMenuItem from '@patternfly/virtual-assistant/dist/dynamic/SourceDetailsMenuItem';
import { ChatbotModal } from '@patternfly/virtual-assistant/dist/dynamic/ChatbotModal';
import { BellIcon, CalendarAltIcon, ClipboardIcon, CodeIcon, UploadIcon } from '@patternfly/react-icons';
import { useDropzone } from 'react-dropzone';

Expand Down Expand Up @@ -265,6 +267,7 @@ To enable the stop button, set `hasStopButton` to `true` and pass in a `handleSt
## Navigation

### Side nav in a drawer

The chatbot conversation history is contained in an interactive drawer, where users can interact with previous conversations or start a new conversation.

The `<ChatbotConversationHistoryNav>` component is a wrapper placed within `<Chatbot>`, which contains all other chatbot components in `drawerContent`. There is a focus trap so users can only tab within the drawer while it is open.
Expand Down Expand Up @@ -313,3 +316,11 @@ Actions can be added to conversations with `menuItems`. Optionally, you can also
```js file="./ChatbotHeaderDrawerWithActions.tsx"

```

### Modal

Based on the [PatternFly modal](/components/modal), this modal adapts to the chatbot display mode and accepts components typically used in a modal. It is primarily used and tested in the context of the attachment modals, but you can customize this modal to adapt it to other use cases as needed. The modal will overlay the chatbot in default and docked modes, and will behave more like a traditional PatternFly modal in fullscreen and embedded modes.

```js file="./ChatbotModal.tsx" isFullscreen

```
93 changes: 93 additions & 0 deletions packages/module/src/ChatbotModal/ChatbotModal.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
.pf-chatbot__chatbot-modal-backdrop {
position: static;
}

.pf-chatbot__chatbot-modal {
--pf-v6-c-modal-box--BorderRadius: var(--pf-t--global--border--radius--medium);
position: fixed;
inset-block-end: var(--pf-t--global--spacer--800); // no associated semantic token
inset-inline-end: var(--pf-t--global--spacer--lg);
width: 30rem;
height: 70vh;
background-color: var(--pf-t--global--background--color--secondary--default);

.pf-v6-c-modal-box__title {
--pf-v6-c-modal-box__title--FontSize: var(--pf-t--global--font--size--heading--h3);
}
.pf-v6-c-button.pf-m-primary.pf-m-block,
.pf-v6-c-button.pf-m-link.pf-m-block {
--pf-v6-c-button--FontWeight: 500;
rebeccaalpert marked this conversation as resolved.
Show resolved Hide resolved
}
.pf-v6-c-modal-box__footer {
padding-block-start: var(--pf-t--global--spacer--xl);
padding-block-end: var(--pf-t--global--spacer--xl);
}
.pf-v6-c-modal-box__header {
padding-block-end: var(--pf-t--global--spacer--lg);
}
}

// ============================================================================
// Chatbot Display Mode - Fullscreen and Embedded
// ============================================================================
@media screen and (max-width: 600px) {
.pf-chatbot__chatbot-modal--embedded,
.pf-chatbot__chatbot-modal--fullscreen {
inset-block-end: 0;
inset-inline-end: 0;
width: 100%;
height: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
@media screen and (min-width: 601px) {
.pf-chatbot__chatbot-modal--embedded,
.pf-chatbot__chatbot-modal--fullscreen {
inset-block-end: 0;
inset-inline-end: 0;
width: 50%;
height: fit-content;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}

// ============================================================================
// Chatbot Display Mode - Default
// ============================================================================
.pf-chatbot__chatbot-modal--default {
box-shadow: unset;
}

// ============================================================================
// Chatbot Display Mode - Docked
// ============================================================================
.pf-chatbot__chatbot-modal--docked {
height: 100vh;
inset-block-end: 0;
inset-inline-end: 0;
border-radius: 0;
--pf-v6-c-modal-box--MaxHeight: 100vh;
box-shadow: unset;
}

// ============================================================================
// Dark theme
// ============================================================================
.pf-v6-theme-dark {
.pf-v6-c-modal-box.pf-chatbot__chatbot-modal {
.pf-v6-c-modal-box__title {
color: #fff;
}
}
}

// ============================================================================
// Backdrop
// ============================================================================
.pf-v6-c-backdrop.pf-chatbot__backdrop {
position: absolute;
}
43 changes: 43 additions & 0 deletions packages/module/src/ChatbotModal/ChatbotModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// ============================================================================
// Code Modal - Chatbot Modal with Code Editor
// ============================================================================
import React from 'react';

// Import PatternFly components
import { Modal, ModalProps } from '@patternfly/react-core';
import { ChatbotDisplayMode } from '../Chatbot';

export interface ChatbotModalProps extends Omit<ModalProps, 'ref'> {
/** Display mode for the Chatbot parent; this influences the styles applied */
displayMode?: ChatbotDisplayMode;
className?: string;
}

export const ChatbotModal: React.FunctionComponent<ChatbotModalProps> = ({
children,
displayMode = ChatbotDisplayMode.default,
className,
isOpen,
...props
}: ChatbotModalProps) => {
const modal = (
<Modal
isOpen={isOpen}
ouiaId="ChatbotModal"
aria-labelledby="chatbot-modal-title"
aria-describedby="chatbot-modal"
className={`pf-chatbot__chatbot-modal pf-chatbot__chatbot-modal--${displayMode} ${className}`}
backdropClassName="pf-chatbot__chatbot-modal-backdrop"
{...props}
>
{children}
</Modal>
);

if ((displayMode === ChatbotDisplayMode.fullscreen || displayMode === ChatbotDisplayMode.embedded) && isOpen) {
return <div className="pf-v6-c-backdrop pf-chatbot__backdrop">{modal}</div>;
}
return modal;
};

export default ChatbotModal;
3 changes: 3 additions & 0 deletions packages/module/src/ChatbotModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default } from './ChatbotModal';

export * from './ChatbotModal';
Loading
Loading