Skip to content

Commit

Permalink
Merge pull request patternfly#307 from rebeccaalpert/custom-modal
Browse files Browse the repository at this point in the history
feat(ChatbotModal): Add style-able modal for general use
  • Loading branch information
nicolethoen authored Nov 19, 2024
2 parents 8b55ef1 + 3980bc1 commit 3116c95
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 124 deletions.
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;
}
.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

0 comments on commit 3116c95

Please sign in to comment.