From 7cece91f3fad5e03c5c5bb7c4a1a9d9c5a6f2ad7 Mon Sep 17 00:00:00 2001 From: Rebecca Alpert Date: Mon, 11 Nov 2024 18:15:18 -0500 Subject: [PATCH] feat(ChatbotModal): Add style-able modal for general use --- .../examples/UI/ChatbotModal.tsx | 43 +++++++++ .../virtual-assistant/examples/UI/UI.md | 63 ++++++++----- .../module/src/ChatbotModal/ChatbotModal.scss | 93 ++++++++++++++++++ .../module/src/ChatbotModal/ChatbotModal.tsx | 43 +++++++++ packages/module/src/ChatbotModal/index.ts | 3 + packages/module/src/CodeModal/CodeModal.scss | 94 +------------------ packages/module/src/CodeModal/CodeModal.tsx | 12 +-- packages/module/src/index.ts | 3 + packages/module/src/main.scss | 1 + 9 files changed, 231 insertions(+), 124 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/ChatbotModal.tsx create mode 100644 packages/module/src/ChatbotModal/ChatbotModal.scss create mode 100644 packages/module/src/ChatbotModal/ChatbotModal.tsx create mode 100644 packages/module/src/ChatbotModal/index.ts diff --git a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/ChatbotModal.tsx b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/ChatbotModal.tsx new file mode 100644 index 00000000..0218efdb --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/ChatbotModal.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { Button, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core'; +import { ChatbotModal } from '@patternfly/virtual-assistant/dist/dynamic/ChatbotModal'; +import { ChatbotDisplayMode } from '@patternfly/virtual-assistant/dist/dynamic/Chatbot'; + +export const ChatbotModalExample: React.FunctionComponent = () => { + const [isModalOpen, setIsModalOpen] = React.useState(false); + + const handleModalToggle = (_event: React.MouseEvent | MouseEvent | KeyboardEvent) => { + setIsModalOpen(!isModalOpen); + }; + + return ( + <> + + + + + 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. + + + + + + + + ); +}; diff --git a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/UI.md b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/UI.md index 179f6566..6a22bd75 100644 --- a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/UI.md +++ b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/UI/UI.md @@ -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 --- @@ -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'; @@ -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 `` component is a wrapper placed within ``, 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. @@ -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](/patternfly-ai/chatbot/messages#attachment-preview), but you can customize this modal to adapt it to other use cases as needed. + +```js file="./ChatbotModal.tsx" + +``` diff --git a/packages/module/src/ChatbotModal/ChatbotModal.scss b/packages/module/src/ChatbotModal/ChatbotModal.scss new file mode 100644 index 00000000..5d561cbb --- /dev/null +++ b/packages/module/src/ChatbotModal/ChatbotModal.scss @@ -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; +} diff --git a/packages/module/src/ChatbotModal/ChatbotModal.tsx b/packages/module/src/ChatbotModal/ChatbotModal.tsx new file mode 100644 index 00000000..28c1450b --- /dev/null +++ b/packages/module/src/ChatbotModal/ChatbotModal.tsx @@ -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 { + /** Display mode for the Chatbot parent; this influences the styles applied */ + displayMode?: ChatbotDisplayMode; + className?: string; +} + +export const ChatbotModal: React.FunctionComponent = ({ + children, + displayMode = ChatbotDisplayMode.default, + className, + isOpen, + ...props +}: ChatbotModalProps) => { + const modal = ( + + {children} + + ); + + if ((displayMode === ChatbotDisplayMode.fullscreen || displayMode === ChatbotDisplayMode.embedded) && isOpen) { + return
{modal}
; + } + return modal; +}; + +export default ChatbotModal; diff --git a/packages/module/src/ChatbotModal/index.ts b/packages/module/src/ChatbotModal/index.ts new file mode 100644 index 00000000..fab93022 --- /dev/null +++ b/packages/module/src/ChatbotModal/index.ts @@ -0,0 +1,3 @@ +export { default } from './ChatbotModal'; + +export * from './ChatbotModal'; diff --git a/packages/module/src/CodeModal/CodeModal.scss b/packages/module/src/CodeModal/CodeModal.scss index 0b2a93e1..8598921c 100644 --- a/packages/module/src/CodeModal/CodeModal.scss +++ b/packages/module/src/CodeModal/CodeModal.scss @@ -1,19 +1,4 @@ -.pf-chatbot__code-modal-backdrop { - position: static; -} - .pf-chatbot__code-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-code-editor { --pf-v6-c-code-editor__main--BackgroundColor: #1f1f1f; --pf-v6-c-code-editor__main--BorderEndStartRadius: 0; @@ -26,6 +11,9 @@ border-start-start-radius: var(--pf-t--global--border--radius--small); border-start-end-radius: var(--pf-t--global--border--radius--small); } + .pf-chatbot__code-modal-body { + gap: var(--pf-t--global--spacer--lg); + } .pf-chatbot__code-modal--controls > .pf-v6-c-code-editor__header { flex-direction: row-reverse; border-radius: var(--pf-t--global--border--radius--small); @@ -70,75 +58,11 @@ .pf-v6-c-code-editor__header-main { display: none; } - .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__close { - --pf-v6-c-modal-box__close--InsetBlockStart: 1.125rem; - } - .pf-chatbot__code-modal-body { - gap: var(--pf-t--global--spacer--lg); - } - .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); - } .pf-chatbot__code-modal-file-details { padding-inline-start: var(--pf-t--global--spacer--md); } } -// ============================================================================ -// Chatbot Display Mode - Fullscreen and Embedded -// ============================================================================ -@media screen and (max-width: 600px) { - .pf-chatbot__code-modal--embedded, - .pf-chatbot__code-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__code-modal--embedded, - .pf-chatbot__code-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__code-modal--default { - box-shadow: unset; -} - -// ============================================================================ -// Chatbot Display Mode - Docked -// ============================================================================ -.pf-chatbot__code-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 // ============================================================================ @@ -149,16 +73,4 @@ --pf-v6-c-button--hover__icon--Color: #c7c7c7; } } - .pf-v6-c-modal-box.pf-chatbot__code-modal { - .pf-v6-c-modal-box__title { - color: #fff; - } - } -} - -// ============================================================================ -// Backdrop -// ============================================================================ -.pf-v6-c-backdrop.pf-chatbot__backdrop { - position: absolute; } diff --git a/packages/module/src/CodeModal/CodeModal.tsx b/packages/module/src/CodeModal/CodeModal.tsx index f3b30984..09844471 100644 --- a/packages/module/src/CodeModal/CodeModal.tsx +++ b/packages/module/src/CodeModal/CodeModal.tsx @@ -6,9 +6,10 @@ import path from 'path-browserify'; // Import PatternFly components import { CodeEditor } from '@patternfly/react-code-editor'; -import { Button, Modal, ModalBody, ModalFooter, ModalHeader, Stack, StackItem } from '@patternfly/react-core'; +import { Button, ModalBody, ModalFooter, ModalHeader, Stack, StackItem } from '@patternfly/react-core'; import FileDetails, { extensionToLanguage } from '../FileDetails'; import { ChatbotDisplayMode } from '../Chatbot'; +import ChatbotModal from '../ChatbotModal/ChatbotModal'; export interface CodeModalProps { /** Class applied to code editor */ @@ -98,14 +99,14 @@ export const CodeModal: React.FunctionComponent = ({ /* eslint-enable indent */ const modal = ( - @@ -143,12 +144,9 @@ export const CodeModal: React.FunctionComponent = ({ {secondaryActionBtn} - + ); - if ((displayMode === ChatbotDisplayMode.fullscreen || displayMode === ChatbotDisplayMode.embedded) && isModalOpen) { - return
{modal}
; - } return modal; }; diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index b1bfb770..7b599e2c 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -24,6 +24,9 @@ export * from './ChatbotFooter'; export { default as ChatbotHeader } from './ChatbotHeader'; export * from './ChatbotHeader'; +export { default as ChatbotModal } from './ChatbotModal'; +export * from './ChatbotModal'; + export { default as ChatbotPopover } from './ChatbotPopover'; export * from './ChatbotPopover'; diff --git a/packages/module/src/main.scss b/packages/module/src/main.scss index bb4c8d6c..c11add72 100644 --- a/packages/module/src/main.scss +++ b/packages/module/src/main.scss @@ -5,6 +5,7 @@ @import './ChatbotConversationHistoryNav/ChatbotConversationHistoryNav'; @import './ChatbotFooter/ChatbotFooter'; @import './ChatbotHeader/ChatbotHeader'; +@import './ChatbotModal/ChatbotModal'; @import './ChatbotPopover/ChatbotPopover'; @import './ChatbotToggle/ChatbotToggle'; @import './ChatbotWelcomePrompt/ChatbotWelcomePrompt';