From f34e871ea8fdefddf28968859c850ba1c664feac Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Mon, 21 Sep 2026 15:21:28 +0200 Subject: [PATCH 1/8] feat(docs): add fullscreen docked nav demo --- .../chatbot/examples/demos/Chatbot.md | 10 ++ .../examples/demos/FullscreenDockedNav.tsx | 158 ++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md index 92924d61..8624486d 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md @@ -58,6 +58,8 @@ import RhUiUploadIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload- import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import { RhUiBuildFillIcon, RhUiCopyFillIcon } from '@patternfly/react-icons'; import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; +import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; import { Button, Divider, @@ -188,6 +190,14 @@ This demo showcases how the ChatBot can be rendered in different display modes t ``` +### Fullscreen ChatBot with docked navigation + +This demo shows a headerless fullscreen ChatBot with a vertical navigation rail for switching between chat history, a new chat, and settings. + +```js file="./FullscreenDockedNav.tsx" isFullscreen + +``` + ### Comparing ChatBots To let users compare how different ChatBots respond to the same prompt, you can add multiple ChatBots within the same window. The following demo illustrates a comparison view pattern that allows users to toggle between different conversations in a single ChatBot window. diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx new file mode 100644 index 00000000..941400c7 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx @@ -0,0 +1,158 @@ +import { FunctionComponent, useState } from 'react'; +import { Avatar, Brand, Divider, Flex, Nav, NavItem, NavList } from '@patternfly/react-core'; +import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot'; +import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent'; +import ChatbotWelcomePrompt from '@patternfly/chatbot/dist/dynamic/ChatbotWelcomePrompt'; +import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter'; +import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar'; +import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox'; +import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message'; +import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; +import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; +import userAvatar from '../Messages/user_avatar.svg'; +import '@patternfly/react-core/dist/styles/base.css'; +import '@patternfly/chatbot/dist/css/main.css'; + +const initialMessages: MessageProps[] = [ + { + id: '1', + role: 'user', + content: 'Show me how a fullscreen chatbot can use docked navigation.', + name: 'You', + avatar: userAvatar, + timestamp: new Date().toLocaleString(), + avatarProps: { isBordered: true } + }, + { + id: '2', + role: 'bot', + content: 'Use the rail on the left to switch between the conversation, history, and a new chat.', + name: 'Bot', + timestamp: new Date().toLocaleString() + } +]; + +const welcomePrompts = [ + { + title: 'Topic 1', + message: 'Helpful prompt for Topic 1' + }, + { + title: 'Topic 2', + message: 'Helpful prompt for Topic 2' + } +]; + +const conversations = [ + { id: '1', text: 'Fullscreen chatbot navigation' }, + { id: '2', text: 'Review deployment options' } +]; + +export const FullscreenDockedNav: FunctionComponent = () => { + const [messages, setMessages] = useState(initialMessages); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [announcement, setAnnouncement] = useState(); + + const startNewChat = () => { + setMessages([]); + setIsDrawerOpen(false); + }; + + const sendMessage = (content: string) => { + const message: MessageProps = { + id: Date.now().toString(), + role: 'user', + content, + name: 'You', + avatar: userAvatar, + timestamp: new Date().toLocaleString(), + avatarProps: { isBordered: true } + }; + setMessages((currentMessages) => [...currentMessages, message]); + setAnnouncement(`Message from You: ${content}`); + }; + + const dockedNav = ( + + ); + + return ( + +
+ setIsDrawerOpen((open) => !open)} + conversations={{ Today: conversations }} + activeItemId="1" + onNewChat={startNewChat} + drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }} + drawerContent={ +
+ + + + {messages.map((message) => ( + + ))} + + + + + + +
+ } + /> +
+
+ ); +}; From a8be46ce64d1151b0661b3049e95f977fe1bc22d Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Mon, 21 Sep 2026 15:27:03 +0200 Subject: [PATCH 2/8] style(docs): format fullscreen docked nav demo --- .../chatbot/examples/demos/FullscreenDockedNav.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx index 941400c7..ac5bb979 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx @@ -134,7 +134,11 @@ export const FullscreenDockedNav: FunctionComponent = () => { drawerContent={
- + Date: Mon, 21 Sep 2026 15:31:21 +0200 Subject: [PATCH 3/8] test(History): fix drawer escape assertion --- .../ChatbotConversationHistoryNav.test.tsx | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/module/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx b/packages/module/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx index e7634ba7..0d0c134f 100644 --- a/packages/module/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +++ b/packages/module/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx @@ -161,17 +161,23 @@ describe('ChatbotConversationHistoryNav', () => { }); it('should close the drawer when escape key is pressed', async () => { - render( - - ); + const HistoryNavigationTest = () => { + const [isDrawerOpen, setIsDrawerOpen] = useState(true); + + return ( + + ); + }; + + render(); fireEvent.keyDown(screen.getByPlaceholderText(/Search/i), { key: 'Escape', @@ -180,8 +186,8 @@ describe('ChatbotConversationHistoryNav', () => { charCode: 27 }); - waitFor(() => { - expect(screen.queryByText('ChatBot documentation')).not.toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByRole('dialog', { hidden: true })).toHaveAttribute('hidden'); }); }); From 35fbf356f55b1a16b48a78d29245ef955f67ca0b Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Mon, 21 Sep 2026 17:10:21 +0200 Subject: [PATCH 4/8] feat(Chatbot): add Canvas docked navigation demo --- .../chatbot/examples/demos/Canvas.md | 25 +- .../chatbot/examples/demos/Canvas.tsx | 20 +- .../examples/demos/CanvasWithDockedNav.tsx | 309 ++++++++++++++++++ packages/module/src/Canvas/Canvas.scss | 12 + packages/module/src/Chatbot/Chatbot.scss | 14 +- packages/module/src/Chatbot/Chatbot.test.tsx | 11 + packages/module/src/Chatbot/Chatbot.tsx | 6 +- 7 files changed, 383 insertions(+), 14 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md index 633fdc09..5c0138da 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md @@ -6,6 +6,7 @@ source: demo import { useState, useRef, useCallback, useEffect, FunctionComponent, ReactNode } from 'react'; import { + Avatar, Brand, Divider, Drawer, @@ -21,6 +22,9 @@ Flex, FlexItem, Label, MenuToggle, + Nav, + NavItem, + NavList, Popover, Select, SelectList, @@ -44,8 +48,16 @@ ChatbotHeaderMenu, ChatbotHeaderTitle } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader'; import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; -import RhUiAddIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; -import RhUiTaskFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; +import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; +import { RhUiTaskFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; +import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import { RhUiUploadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; +import { RhUiCopyFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; +import { RhUiDownloadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; +import { RhUiPlayFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-play-fill-icon'; +import { RhUiBackupIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-backup-icon'; import RhUiNotificationFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon'; import RhUiCalendarFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon'; import RhUiExportIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon'; @@ -97,6 +109,7 @@ The general recommended structure is as follows: | `pf-chatbot__canvas-panel-body` | Provides the canvas panel content area and its spacing. | | `pf-chatbot__canvas-head` | Styles and positions the canvas panel header and close action. | | `pf-chatbot__canvas-editor` | Makes the code editor fill the available canvas space. | +| `pf-chatbot__canvas-docked-nav` | Optional vertical navigation rail for a canvas layout. | ## Demos @@ -107,3 +120,11 @@ This demo shows canvas mode being used to render a PatternFly [code editor](/com ```js file="./Canvas.tsx" isFullscreen ``` + +### With docked navigation + +This demo shows canvas mode with an optional docked navigation rail. The rail can toggle chat history and start a new chat without closing the canvas. + +```js file="./CanvasWithDockedNav.tsx" isFullscreen + +``` diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx index 5f6e4199..ac0cf206 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.tsx @@ -39,16 +39,16 @@ import ChatbotHeader, { ChatbotHeaderMenu, ChatbotHeaderTitle } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader'; -import RhUiAiInfoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon'; -import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; -import RhUiAddIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; -import RhUiTaskFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; -import RhUiExportIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon'; -import RhUiRedoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon'; -import RhUiUndoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon'; -import RhUiServerUploadFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon'; -import RhUiNotificationFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon'; -import RhUiCalendarFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon'; +import { RhUiAiInfoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon'; +import { RhUiImageFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; +import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; +import { RhUiTaskFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; +import { RhUiExportIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon'; +import { RhUiRedoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon'; +import { RhUiUndoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon'; +import { RhUiServerUploadFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon'; +import { RhUiNotificationFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon'; +import { RhUiCalendarFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon'; import { useDropzone } from 'react-dropzone'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg'; diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx new file mode 100644 index 00000000..b642d399 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx @@ -0,0 +1,309 @@ +import { useState } from 'react'; +import { + Avatar, + Brand, + Divider, + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelContent, + Flex, + FlexItem, + Label, + MenuToggle, + Nav, + NavItem, + NavList, + Select, + SelectList, + SelectOption, + Title +} from '@patternfly/react-core'; +import { CodeEditor, CodeEditorControl, Language } from '@patternfly/react-code-editor'; +import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot'; +import ChatbotContent from '@patternfly/chatbot/dist/dynamic/ChatbotContent'; +import ChatbotFooter, { ChatbotFootnote } from '@patternfly/chatbot/dist/dynamic/ChatbotFooter'; +import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar'; +import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox'; +import Message from '@patternfly/chatbot/dist/dynamic/Message'; +import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; +import ChatbotHeader, { + ChatbotHeaderMain, + ChatbotHeaderMenu, + ChatbotHeaderTitle +} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader'; +import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; +import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; +import { RhUiCopyFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; +import { RhUiDownloadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; +import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import { RhUiImageFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; +import { RhUiBackupIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-backup-icon'; +import { RhUiPlayFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-play-fill-icon'; +import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import { RhUiUploadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; +import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; +import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg'; +import userAvatar from '../Messages/user_avatar.svg'; +import '@patternfly/react-core/dist/styles/base.css'; +import '@patternfly/chatbot/dist/css/main.css'; + +const sampleCode = ` + + +

This is a paragraph.

+

This is another paragraph.

+ +`; + +const initialMessages = [ + { + id: '1', + role: 'bot', + content: 'Text message from the bot. This message can wrap several lines.', + name: 'Bot', + timestamp: '1:30 PM' + }, + { + id: '2', + role: 'user', + content: 'Canvas mode enabled - I want to live edit code', + name: 'You', + avatar: userAvatar, + avatarProps: { isBordered: true }, + timestamp: '1:30 PM' + } +]; + +const conversations = [ + { id: '1', text: 'Canvas mode enabled - I want to live edit code' }, + { id: '2', text: 'Review deployment options' } +]; + +const iconLogo = ( + <> + + + +); + +export const CanvasWithDockedNavDemo = () => { + const [messages, setMessages] = useState(initialMessages); + const [code, setCode] = useState(sampleCode); + const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [isCanvasOpen, setIsCanvasOpen] = useState(true); + const [isModelSelectOpen, setIsModelSelectOpen] = useState(false); + const [selectedModel, setSelectedModel] = useState('Granite 7B'); + const [announcement, setAnnouncement] = useState(); + + const startNewChat = () => { + setMessages([]); + setIsDrawerOpen(false); + }; + + const sendMessage = (content) => { + const message = { + id: Date.now().toString(), + role: 'user', + content, + name: 'You', + avatar: userAvatar, + avatarProps: { isBordered: true }, + timestamp: new Date().toLocaleTimeString() + }; + setMessages((currentMessages) => [...currentMessages, message]); + setAnnouncement(`Message from You: ${content}`); + }; + + const dockedNav = ( + + ); + + const editorControls = [ + { icon: , label: 'Upload' }, + { icon: , label: 'Copy' }, + { icon: , label: 'Download' }, + { icon: , label: 'Run' }, + { icon: , label: 'Backup' } + ].map(({ icon, label }) => ( + + )); + + const canvasPanel = ( + +
+ + + + + Edit code + + + + + + + + setIsCanvasOpen(false)} /> + + +
+
+ +
+
+
+
+ ); + + return ( + +
+ setIsDrawerOpen((open) => !open)} + activeItemId="1" + conversations={{ Today: conversations }} + onNewChat={startNewChat} + drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }} + drawerContent={ + + + +
+ + + setIsDrawerOpen((open) => !open)} + tooltipContent="Chat history" + menuAriaLabel="Chat history" + /> + {iconLogo} + + + + + {messages.map((message) => ( + + ))} + + + + , + tooltipContent: 'Message actions', + 'aria-label': 'Message actions' + } + }} + additionalActions={ + <> + + + + } + /> + + +
+
+
+
+ } + /> +
+
+ ); +}; diff --git a/packages/module/src/Canvas/Canvas.scss b/packages/module/src/Canvas/Canvas.scss index 7fb3ef27..43b593f1 100644 --- a/packages/module/src/Canvas/Canvas.scss +++ b/packages/module/src/Canvas/Canvas.scss @@ -17,6 +17,10 @@ --pf-chatbot__canvas--BackgroundColor: var(--pf-t--global--background--color--glass--primary--default); } +.pf-chatbot__canvas-docked-nav { + border-inline-end: var(--pf-t--global--border--width--regular) solid var(--pf-t--global--border--color--subtle); +} + // Drawer // ---------------------------------------------------------------------------- .pf-chatbot__canvas-drawer { @@ -56,6 +60,14 @@ min-height: 0; } +.pf-chatbot__canvas-chat-content { + padding-block-start: var(--pf-t--global--spacer--lg); +} + +.pf-chatbot__canvas-chat-footer { + padding-block-end: var(--pf-t--global--spacer--md); +} + // Panel // ---------------------------------------------------------------------------- .pf-chatbot__canvas-section { diff --git a/packages/module/src/Chatbot/Chatbot.scss b/packages/module/src/Chatbot/Chatbot.scss index 7bc17af8..ccee03c9 100644 --- a/packages/module/src/Chatbot/Chatbot.scss +++ b/packages/module/src/Chatbot/Chatbot.scss @@ -141,6 +141,19 @@ } } +// ============================================================================ +// Chatbot With Docked Nav +// ============================================================================ +.pf-chatbot--docked-nav { + .pf-chatbot-container { + flex-direction: row; + } + + .pf-chatbot__header-container { + display: none; + } +} + .pf-chatbot-container--embedded { min-height: 100%; } @@ -180,4 +193,3 @@ border: unset; } } - diff --git a/packages/module/src/Chatbot/Chatbot.test.tsx b/packages/module/src/Chatbot/Chatbot.test.tsx index 147a8a55..228a1ac3 100644 --- a/packages/module/src/Chatbot/Chatbot.test.tsx +++ b/packages/module/src/Chatbot/Chatbot.test.tsx @@ -36,4 +36,15 @@ describe('Chatbot', () => { ); expect(screen.getByTestId('chatbot')).toHaveClass('pf-m-compact'); }); + + it('should render docked navigation and apply the docked navigation class', () => { + render( + Navigation}> + Chatbot Content + + ); + + expect(screen.getByTestId('chatbot')).toHaveClass('pf-chatbot--docked-nav'); + expect(screen.getByRole('navigation', { name: 'Chatbot navigation' })).toBeInTheDocument(); + }); }); diff --git a/packages/module/src/Chatbot/Chatbot.tsx b/packages/module/src/Chatbot/Chatbot.tsx index f70325d6..3e53e02b 100644 --- a/packages/module/src/Chatbot/Chatbot.tsx +++ b/packages/module/src/Chatbot/Chatbot.tsx @@ -20,6 +20,8 @@ export interface ChatbotProps { ariaLabel?: string; /** Density of information within the ChatBot */ isCompact?: boolean; + /** Optional navigation rail displayed before the chatbot content */ + dockedNav?: React.ReactNode; } export enum ChatbotDisplayMode { @@ -38,10 +40,11 @@ const ChatbotBase: FunctionComponent = ({ innerRef, ariaLabel, isCompact, + dockedNav, ...props }: ChatbotProps) => (
{/* Ref is intended for use with skip to chatbot links, etc. */} @@ -52,6 +55,7 @@ const ChatbotBase: FunctionComponent = ({ tabIndex={-1} ref={innerRef} > + {dockedNav && } {children} ) : undefined} From cebd9d0ca1485d281a7ef9eadbbcb249a6acd177 Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Wed, 23 Sep 2026 10:36:39 +0200 Subject: [PATCH 5/8] feat(docs): align docked nav demos --- .../examples/demos/CanvasWithDockedNav.tsx | 155 ++++++++++-------- .../examples/demos/FullscreenDockedNav.tsx | 140 +++++++++++----- 2 files changed, 184 insertions(+), 111 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx index b642d399..5ee946d0 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useRef, useState } from 'react'; import { Avatar, Brand, @@ -13,6 +13,12 @@ import { Flex, FlexItem, Label, + Masthead, + MastheadBrand, + MastheadContent, + MastheadLogo, + MastheadMain, + MastheadToggle, MenuToggle, Nav, NavItem, @@ -20,7 +26,9 @@ import { Select, SelectList, SelectOption, - Title + Title, + PageToggleButton, + Tooltip } from '@patternfly/react-core'; import { CodeEditor, CodeEditorControl, Language } from '@patternfly/react-code-editor'; import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot'; @@ -30,12 +38,6 @@ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar'; import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox'; import Message from '@patternfly/chatbot/dist/dynamic/Message'; import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; -import ChatbotHeader, { - ChatbotHeaderMain, - ChatbotHeaderMenu, - ChatbotHeaderTitle -} from '@patternfly/chatbot/dist/dynamic/ChatbotHeader'; -import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; import { RhUiCopyFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; import { RhUiDownloadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; @@ -46,7 +48,6 @@ import { RhUiPlayFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-p import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; import { RhUiUploadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; -import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg'; import userAvatar from '../Messages/user_avatar.svg'; import '@patternfly/react-core/dist/styles/base.css'; import '@patternfly/chatbot/dist/css/main.css'; @@ -83,12 +84,20 @@ const conversations = [ { id: '2', text: 'Review deployment options' } ]; -const iconLogo = ( - <> - - - -); +const hamburgerHoverStyles = ` + .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger:is(:hover, :focus-visible) { + --pf-v6-c-button--hamburger-icon--top--path: path("M5,1 L9,1"); + --pf-v6-c-button--hamburger-icon--arrow--path: path("M3,7 L1,5 L3,3"); + --pf-v6-c-button--hamburger-icon--bottom--path: path("M9,9 L5,9"); + --pf-v6-c-button--hover__icon--ScaleX: -1; + --pf-v6-c-button__icon--TransitionDelay: 0s; + --pf-v6-c-button--hover__icon--TransitionDelay: 0s; + } + + .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger[aria-expanded="true"]:is(:hover, :focus-visible) { + --pf-v6-c-button--hover__icon--ScaleX: 1; + } +`; export const CanvasWithDockedNavDemo = () => { const [messages, setMessages] = useState(initialMessages); @@ -98,6 +107,7 @@ export const CanvasWithDockedNavDemo = () => { const [isModelSelectOpen, setIsModelSelectOpen] = useState(false); const [selectedModel, setSelectedModel] = useState('Granite 7B'); const [announcement, setAnnouncement] = useState(); + const newChatRef = useRef(null); const startNewChat = () => { setMessages([]); @@ -119,47 +129,69 @@ export const CanvasWithDockedNavDemo = () => { }; const dockedNav = ( - + <> + +
+ + + + setIsDrawerOpen((open) => !open)} + /> + + + + + + + + + + + + + + +
+ ); const editorControls = [ @@ -227,17 +259,6 @@ export const CanvasWithDockedNavDemo = () => {
- - - setIsDrawerOpen((open) => !open)} - tooltipContent="Chat history" - menuAriaLabel="Chat history" - /> - {iconLogo} - - { const [messages, setMessages] = useState(initialMessages); const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [announcement, setAnnouncement] = useState(); + const newChatRef = useRef(null); const startNewChat = () => { setMessages([]); @@ -76,47 +106,69 @@ export const FullscreenDockedNav: FunctionComponent = () => { }; const dockedNav = ( - + <> + +
+ + + + setIsDrawerOpen((open) => !open)} + /> + + + + + + + + + + + + + + +
+ ); return ( From 02a55b1d91c5bbda740a88a8714863abd78ab8db Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Thu, 24 Sep 2026 11:43:28 +0200 Subject: [PATCH 6/8] feat(docs): add docked nav settings --- .../chatbot/examples/demos/Canvas.md | 10 +- .../examples/demos/CanvasWithDockedNav.tsx | 242 +++++++++++++----- .../chatbot/examples/demos/Chatbot.md | 2 + .../examples/demos/FullscreenDockedNav.tsx | 155 +++++++++-- 4 files changed, 318 insertions(+), 91 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md index 5c0138da..91217a9e 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md @@ -6,7 +6,7 @@ source: demo import { useState, useRef, useCallback, useEffect, FunctionComponent, ReactNode } from 'react'; import { - Avatar, +Avatar, Brand, Divider, Drawer, @@ -22,9 +22,9 @@ Flex, FlexItem, Label, MenuToggle, - Nav, - NavItem, - NavList, +Nav, +NavItem, +NavList, Popover, Select, SelectList, @@ -47,6 +47,7 @@ ChatbotHeaderMain, ChatbotHeaderMenu, ChatbotHeaderTitle } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader'; +import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings'; import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; @@ -65,6 +66,7 @@ import RhUiRedoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon import RhUiUndoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon'; import RhUiServerUploadFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon'; import RhUiAiInfoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon'; +import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import { useDropzone } from 'react-dropzone'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg'; diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx index 5ee946d0..ce22e6f3 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx @@ -2,7 +2,11 @@ import { useRef, useState } from 'react'; import { Avatar, Brand, + Button, Divider, + Dropdown, + DropdownItem, + DropdownList, Drawer, DrawerActions, DrawerCloseButton, @@ -26,6 +30,7 @@ import { Select, SelectList, SelectOption, + Switch, Title, PageToggleButton, Tooltip @@ -38,6 +43,7 @@ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar'; import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox'; import Message from '@patternfly/chatbot/dist/dynamic/Message'; import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; +import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings'; import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; import { RhUiCopyFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; import { RhUiDownloadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; @@ -47,6 +53,7 @@ import { RhUiBackupIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-bac import { RhUiPlayFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-play-fill-icon'; import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; import { RhUiUploadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; +import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import userAvatar from '../Messages/user_avatar.svg'; import '@patternfly/react-core/dist/styles/base.css'; @@ -99,10 +106,102 @@ const hamburgerHoverStyles = ` } `; +const SettingsPanel = ({ onClose }) => { + const [openDropdown, setOpenDropdown] = useState(); + const [theme, setTheme] = useState('System'); + const [language, setLanguage] = useState('Auto-detect'); + const [voice, setVoice] = useState('Bot'); + const [isAnalyticsShared, setIsAnalyticsShared] = useState(true); + + const dropdownField = (id, value, options, onSelect) => ( + { + onSelect(String(selectedValue)); + setOpenDropdown(undefined); + }} + onOpenChange={(isOpen: boolean) => setOpenDropdown(isOpen ? id : undefined)} + shouldFocusToggleOnSelect + shouldFocusFirstItemOnOpen + toggle={(toggleRef) => ( + setOpenDropdown(openDropdown === id ? undefined : id)} + isExpanded={openDropdown === id} + > + {value} + + )} + > + + {options.map((option) => ( + + {option} + + ))} + + + ); + + const fields = [ + { id: 'theme', label: 'Theme', field: dropdownField('theme', theme, ['System', 'Light', 'Dark'], setTheme) }, + { + id: 'language', + label: 'Language', + field: dropdownField('language', language, ['Auto-detect', 'English'], setLanguage) + }, + { id: 'voice', label: 'Voice', field: dropdownField('voice', voice, ['Bot', 'User'], setVoice) }, + { + id: 'analytics', + label: 'Share analytics', + field: ( + setIsAnalyticsShared(checked)} + /> + ) + }, + { id: 'archived-chat', label: 'Archived chats', field: }, + { id: 'archive-all', label: 'Archived all chats', field: }, + { + id: 'delete-all', + label: 'Delete all chats', + field: ( + + ) + } + ]; + + return ( +
+
+ + + Settings + +
+
+ ); +}; + export const CanvasWithDockedNavDemo = () => { const [messages, setMessages] = useState(initialMessages); const [code, setCode] = useState(sampleCode); const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [areSettingsOpen, setAreSettingsOpen] = useState(false); const [isCanvasOpen, setIsCanvasOpen] = useState(true); const [isModelSelectOpen, setIsModelSelectOpen] = useState(false); const [selectedModel, setSelectedModel] = useState('Granite 7B'); @@ -112,6 +211,7 @@ export const CanvasWithDockedNavDemo = () => { const startNewChat = () => { setMessages([]); setIsDrawerOpen(false); + setAreSettingsOpen(false); }; const sendMessage = (content) => { @@ -176,6 +276,10 @@ export const CanvasWithDockedNavDemo = () => { icon={} component="button" preventDefault + onClick={() => { + setIsDrawerOpen(false); + setAreSettingsOpen(true); + }} > Settings @@ -255,73 +359,79 @@ export const CanvasWithDockedNavDemo = () => { onNewChat={startNewChat} drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }} drawerContent={ - - - -
- - - {messages.map((message) => ( - - ))} - - - - , - tooltipContent: 'Message actions', - 'aria-label': 'Message actions' - } - }} - additionalActions={ - <> - - { + setSelectedModel(String(value)); + setIsModelSelectOpen(false); + }} + onOpenChange={setIsModelSelectOpen} + toggle={(toggleRef) => ( + setIsModelSelectOpen((open) => !open)} + isExpanded={isModelSelectOpen} + aria-label={`${selectedModel}, Select a model`} + > + {selectedModel} + + )} > - {selectedModel} - - )} - > - - {['Granite 7B', 'Granite 8B', 'Llama 3'].map((option) => ( - - {option} - - ))} - - - - } - /> - - -
-
-
-
+ + {['Granite 7B', 'Granite 8B', 'Llama 3'].map((option) => ( + + {option} + + ))} + + + + } + /> + + +
+
+
+ + )} +
} />
diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md index 8624486d..a29d11cb 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md @@ -37,6 +37,7 @@ import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox'; import Message from '@patternfly/chatbot/dist/dynamic/Message'; import Compare from '@patternfly/chatbot/dist/dynamic/Compare'; import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; +import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings'; import ChatbotHeader, { ChatbotHeaderMain, @@ -60,6 +61,7 @@ import { RhUiBuildFillIcon, RhUiCopyFillIcon } from '@patternfly/react-icons'; import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import { Button, Divider, diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx index e50b8328..55adb26d 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx @@ -2,17 +2,25 @@ import { FunctionComponent, useRef, useState } from 'react'; import { Avatar, Brand, + Button, Divider, + Dropdown, + DropdownItem, + DropdownList, Masthead, MastheadBrand, MastheadContent, MastheadLogo, MastheadMain, MastheadToggle, + Flex, + MenuToggle, Nav, NavItem, NavList, PageToggleButton, + Switch, + Title, Tooltip } from '@patternfly/react-core'; import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot'; @@ -23,8 +31,10 @@ import MessageBar from '@patternfly/chatbot/dist/dynamic/MessageBar'; import MessageBox from '@patternfly/chatbot/dist/dynamic/MessageBox'; import Message, { MessageProps } from '@patternfly/chatbot/dist/dynamic/Message'; import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; +import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings'; import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import userAvatar from '../Messages/user_avatar.svg'; import '@patternfly/react-core/dist/styles/base.css'; @@ -80,15 +90,108 @@ const hamburgerHoverStyles = ` } `; +const SettingsPanel = ({ onClose }) => { + const [openDropdown, setOpenDropdown] = useState(); + const [theme, setTheme] = useState('System'); + const [language, setLanguage] = useState('Auto-detect'); + const [voice, setVoice] = useState('Bot'); + const [isAnalyticsShared, setIsAnalyticsShared] = useState(true); + + const dropdownField = (id, value, options, onSelect) => ( + { + onSelect(String(selectedValue)); + setOpenDropdown(undefined); + }} + onOpenChange={(isOpen: boolean) => setOpenDropdown(isOpen ? id : undefined)} + shouldFocusToggleOnSelect + shouldFocusFirstItemOnOpen + toggle={(toggleRef) => ( + setOpenDropdown(openDropdown === id ? undefined : id)} + isExpanded={openDropdown === id} + > + {value} + + )} + > + + {options.map((option) => ( + + {option} + + ))} + + + ); + + const fields = [ + { id: 'theme', label: 'Theme', field: dropdownField('theme', theme, ['System', 'Light', 'Dark'], setTheme) }, + { + id: 'language', + label: 'Language', + field: dropdownField('language', language, ['Auto-detect', 'English'], setLanguage) + }, + { id: 'voice', label: 'Voice', field: dropdownField('voice', voice, ['Bot', 'User'], setVoice) }, + { + id: 'analytics', + label: 'Share analytics', + field: ( + setIsAnalyticsShared(checked)} + /> + ) + }, + { id: 'archived-chat', label: 'Archived chats', field: }, + { id: 'archive-all', label: 'Archived all chats', field: }, + { + id: 'delete-all', + label: 'Delete all chats', + field: ( + + ) + } + ]; + + return ( +
+
+ + + Settings + +
+
+ ); +}; + export const FullscreenDockedNav: FunctionComponent = () => { const [messages, setMessages] = useState(initialMessages); const [isDrawerOpen, setIsDrawerOpen] = useState(false); + const [areSettingsOpen, setAreSettingsOpen] = useState(false); const [announcement, setAnnouncement] = useState(); const newChatRef = useRef(null); const startNewChat = () => { setMessages([]); setIsDrawerOpen(false); + setAreSettingsOpen(false); }; const sendMessage = (content: string) => { @@ -153,6 +256,10 @@ export const FullscreenDockedNav: FunctionComponent = () => { icon={} component="button" preventDefault + onClick={() => { + setIsDrawerOpen(false); + setAreSettingsOpen(true); + }} > Settings @@ -184,27 +291,33 @@ export const FullscreenDockedNav: FunctionComponent = () => { onNewChat={startNewChat} drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }} drawerContent={ -
- - - - {messages.map((message) => ( - - ))} - - - - - - +
+ {areSettingsOpen ? ( + setAreSettingsOpen(false)} /> + ) : ( +
+ + + + {messages.map((message) => ( + + ))} + + + + + + +
+ )}
} /> From 5d5eaeb797882e936c2de66cde0a1a7761d12912 Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Thu, 24 Sep 2026 15:39:02 +0200 Subject: [PATCH 7/8] fix(docs): address docked nav review feedback --- .../chatbot/examples/demos/Canvas.md | 45 +- .../examples/demos/CanvasWithDockedNav.tsx | 408 ++++++++++++------ .../chatbot/examples/demos/Chatbot.md | 10 +- .../examples/demos/FullscreenDockedNav.tsx | 29 +- .../module/src/MessageBar/MessageBar.test.tsx | 17 + packages/module/src/MessageBar/MessageBar.tsx | 8 +- 6 files changed, 358 insertions(+), 159 deletions(-) diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md index 91217a9e..6f6a379b 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md @@ -49,16 +49,15 @@ ChatbotHeaderTitle } from '@patternfly/chatbot/dist/dynamic/ChatbotHeader'; import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings'; import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; -import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; -import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; -import { RhUiTaskFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; -import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; -import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; -import { RhUiUploadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; -import { RhUiCopyFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; -import { RhUiDownloadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; -import { RhUiPlayFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-play-fill-icon'; -import { RhUiBackupIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-backup-icon'; +import RhUiAddIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; +import RhUiTaskFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; +import RhUiEditFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import RhUiSettingsFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import RhUiUploadIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; +import RhUiCopyFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; +import RhUiDownloadIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; +import RhUiPlayFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-play-fill-icon'; +import RhUiBackupIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-backup-icon'; import RhUiNotificationFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon'; import RhUiCalendarFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon'; import RhUiExportIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon'; @@ -66,7 +65,7 @@ import RhUiRedoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon import RhUiUndoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon'; import RhUiServerUploadFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon'; import RhUiAiInfoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon'; -import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; +import RhMicronsCloseIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import { useDropzone } from 'react-dropzone'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg'; @@ -100,18 +99,18 @@ The general recommended structure is as follows: ## Class names -| Class name | Purpose | -| --- | --- | -| `pf-chatbot__canvas` | Container for the fullscreen canvas layout and its background. | -| `pf-chatbot__canvas-drawer` | Styles the drawer that contains the canvas panel. | -| `pf-chatbot__canvas-section` | Styles the section used within the drawer for focus. | -| `pf-chatbot__canvas-body` | Ensures the drawer content body fills the available height. | -| `pf-chatbot__canvas-column` | Arranges the canvas header, content, and footer vertically. | -| `pf-chatbot__canvas-panel` | Styles the canvas drawer panel and removes the default panel spacing. | -| `pf-chatbot__canvas-panel-body` | Provides the canvas panel content area and its spacing. | -| `pf-chatbot__canvas-head` | Styles and positions the canvas panel header and close action. | -| `pf-chatbot__canvas-editor` | Makes the code editor fill the available canvas space. | -| `pf-chatbot__canvas-docked-nav` | Optional vertical navigation rail for a canvas layout. | +| Class name | Purpose | +| ------------------------------- | --------------------------------------------------------------------- | +| `pf-chatbot__canvas` | Container for the fullscreen canvas layout and its background. | +| `pf-chatbot__canvas-drawer` | Styles the drawer that contains the canvas panel. | +| `pf-chatbot__canvas-section` | Styles the section used within the drawer for focus. | +| `pf-chatbot__canvas-body` | Ensures the drawer content body fills the available height. | +| `pf-chatbot__canvas-column` | Arranges the canvas header, content, and footer vertically. | +| `pf-chatbot__canvas-panel` | Styles the canvas drawer panel and removes the default panel spacing. | +| `pf-chatbot__canvas-panel-body` | Provides the canvas panel content area and its spacing. | +| `pf-chatbot__canvas-head` | Styles and positions the canvas panel header and close action. | +| `pf-chatbot__canvas-editor` | Makes the code editor fill the available canvas space. | +| `pf-chatbot__canvas-docked-nav` | Optional vertical navigation rail for a canvas layout. | ## Demos diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx index ce22e6f3..fe1aaf57 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/CanvasWithDockedNav.tsx @@ -1,4 +1,5 @@ import { useRef, useState } from 'react'; +import { useDropzone } from 'react-dropzone'; import { Avatar, Brand, @@ -33,6 +34,7 @@ import { Switch, Title, PageToggleButton, + Popover, Tooltip } from '@patternfly/react-core'; import { CodeEditor, CodeEditorControl, Language } from '@patternfly/react-code-editor'; @@ -45,27 +47,30 @@ import Message from '@patternfly/chatbot/dist/dynamic/Message'; import ChatbotConversationHistoryNav from '@patternfly/chatbot/dist/dynamic/ChatbotConversationHistoryNav'; import SettingsForm from '@patternfly/chatbot/dist/dynamic/Settings'; import { RhUiAddIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; -import { RhUiCopyFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; -import { RhUiDownloadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-download-icon'; -import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import { RhUiAiInfoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon'; +import { RhUiCalendarFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-calendar-fill-icon'; +import { RhUiExportIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon'; import { RhUiImageFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; -import { RhUiBackupIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-backup-icon'; -import { RhUiPlayFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-play-fill-icon'; +import { RhUiNotificationFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-notification-fill-icon'; +import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import { RhUiRedoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-redo-icon'; +import { RhUiServerUploadFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon'; import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; -import { RhUiUploadIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; +import { RhUiTaskFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-task-fill-icon'; +import { RhUiUndoIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon'; import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import userAvatar from '../Messages/user_avatar.svg'; import '@patternfly/react-core/dist/styles/base.css'; import '@patternfly/chatbot/dist/css/main.css'; -const sampleCode = ` - - -

This is a paragraph.

-

This is another paragraph.

- -`; +const sampleCode = `apiVersion: v1 +kind: ConfigMap +metadata: + name: canvas-demo +data: + greeting: Hello, how can I help you today? +`; const initialMessages = [ { @@ -83,6 +88,14 @@ const initialMessages = [ avatar: userAvatar, avatarProps: { isBordered: true }, timestamp: '1:30 PM' + }, + { + id: '3', + role: 'bot', + content: 'The canvas mode text editor is ready. Edit, share or ask me to change something for you.', + name: 'Bot', + timestamp: '1:30 PM', + attachments: [{ name: 'canvas mode.yaml', id: 'canvas-mode' }] } ]; @@ -92,17 +105,24 @@ const conversations = [ ]; const hamburgerHoverStyles = ` + .pf-chatbot__canvas-docked-nav .pf-v6-c-masthead__logo.pf-m-compact { + display: revert; + } + + .pf-chatbot__canvas-docked-nav .pf-v6-c-nav.pf-m-docked .pf-v6-c-nav__link-text { + display: none; + } + .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger:is(:hover, :focus-visible) { - --pf-v6-c-button--hamburger-icon--top--path: path("M5,1 L9,1"); - --pf-v6-c-button--hamburger-icon--arrow--path: path("M3,7 L1,5 L3,3"); - --pf-v6-c-button--hamburger-icon--bottom--path: path("M9,9 L5,9"); - --pf-v6-c-button--hover__icon--ScaleX: -1; + --pf-v6-c-button--hamburger-icon--top--path: var(--pf-v6-c-button--hamburger-icon--top--collapse--path); + --pf-v6-c-button--hamburger-icon--arrow--path: var(--pf-v6-c-button--hamburger-icon--arrow--collapse--path); + --pf-v6-c-button--hamburger-icon--bottom--path: var(--pf-v6-c-button--hamburger-icon--bottom--collapse--path); --pf-v6-c-button__icon--TransitionDelay: 0s; - --pf-v6-c-button--hover__icon--TransitionDelay: 0s; + --pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-expand--ScaleX); } .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger[aria-expanded="true"]:is(:hover, :focus-visible) { - --pf-v6-c-button--hover__icon--ScaleX: 1; + --pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-collapse--ScaleX); } `; @@ -178,7 +198,7 @@ const SettingsPanel = ({ onClose }) => { ]; return ( -
+
{ const [isDrawerOpen, setIsDrawerOpen] = useState(false); const [areSettingsOpen, setAreSettingsOpen] = useState(false); const [isCanvasOpen, setIsCanvasOpen] = useState(true); + const [showCanvasLabel, setShowCanvasLabel] = useState(true); + const [isGeneratedAiPopoverOpen, setIsGeneratedAiPopoverOpen] = useState(false); + const [isAttachMenuOpen, setIsAttachMenuOpen] = useState(false); const [isModelSelectOpen, setIsModelSelectOpen] = useState(false); const [selectedModel, setSelectedModel] = useState('Granite 7B'); const [announcement, setAnnouncement] = useState(); + const [isSendButtonDisabled, setIsSendButtonDisabled] = useState(false); const newChatRef = useRef(null); + const scrollToBottomRef = useRef(null); + const messageActionsRef = useRef(null); + const { open, getInputProps } = useDropzone({ + multiple: true, + // eslint-disable-next-line no-console + onDropAccepted: () => console.log('fileUploaded') + }); const startNewChat = () => { setMessages([]); @@ -214,20 +245,92 @@ export const CanvasWithDockedNavDemo = () => { setAreSettingsOpen(false); }; + const openCanvas = () => { + setShowCanvasLabel(true); + setIsCanvasOpen(true); + }; + + const closeCanvasMode = () => { + setShowCanvasLabel(false); + setIsCanvasOpen(false); + }; + const sendMessage = (content) => { - const message = { + const date = new Date(); + const userMessage = { id: Date.now().toString(), role: 'user', content, name: 'You', avatar: userAvatar, avatarProps: { isBordered: true }, - timestamp: new Date().toLocaleTimeString() + timestamp: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}` }; - setMessages((currentMessages) => [...currentMessages, message]); - setAnnouncement(`Message from You: ${content}`); + const newMessages = [...messages, userMessage]; + newMessages.push({ + id: `${Date.now()}-loading`, + role: 'bot', + name: 'Bot', + isLoading: true, + timestamp: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}` + }); + setMessages(newMessages); + setAnnouncement(`Message from You: ${content}. Message from Bot is loading.`); + setIsSendButtonDisabled(true); + setTimeout(() => { + const loadedMessages = [...newMessages]; + loadedMessages.pop(); + loadedMessages.push({ + id: `${Date.now()}-bot`, + role: 'bot', + content: 'API response from Bot goes here', + name: 'Bot', + isLoading: false, + actions: { + // eslint-disable-next-line no-console + positive: { onClick: () => console.log('Good response') }, + // eslint-disable-next-line no-console + negative: { onClick: () => console.log('Bad response') }, + // eslint-disable-next-line no-console + copy: { onClick: () => console.log('Copy') }, + // eslint-disable-next-line no-console + download: { onClick: () => console.log('Download') }, + // eslint-disable-next-line no-console + listen: { onClick: () => console.log('Listen') } + }, + timestamp: date.toLocaleString() + }); + setMessages(loadedMessages); + setAnnouncement('Message from Bot: API response from Bot goes here'); + setIsSendButtonDisabled(false); + }, 5000); }; + const attachMenuItems = ( + <> + + }> + Alerts + + }> + Events + + }> + Logs + + + + + } onClick={open}> + Upload from computer + + }> + {`${isCanvasOpen ? 'Disable' : 'Enable'} canvas mode`} + + + + ); + const dockedNav = ( <> @@ -291,6 +394,7 @@ export const CanvasWithDockedNavDemo = () => { src={userAvatar} alt="User profile" size="md" + isBordered /> @@ -299,11 +403,9 @@ export const CanvasWithDockedNavDemo = () => { ); const editorControls = [ - { icon: , label: 'Upload' }, - { icon: , label: 'Copy' }, - { icon: , label: 'Download' }, - { icon: , label: 'Run' }, - { icon: , label: 'Backup' } + { icon: , label: 'Undo' }, + { icon: , label: 'Redo' }, + { icon: , label: 'Export' } ].map(({ icon, label }) => ( )); @@ -319,13 +421,31 @@ export const CanvasWithDockedNavDemo = () => { - + setIsGeneratedAiPopoverOpen(true)} + onHide={() => setIsGeneratedAiPopoverOpen(false)} + > + + - setIsCanvasOpen(false)} /> + + + + +
@@ -334,10 +454,12 @@ export const CanvasWithDockedNavDemo = () => { isFullHeight isLineNumbersVisible isLanguageLabelVisible + isCopyEnabled + isDownloadEnabled downloadFileName="canvas-mode" customControls={editorControls} code={code} - language={Language.html} + language={Language.yaml} onCodeChange={setCode} />
@@ -347,94 +469,138 @@ export const CanvasWithDockedNavDemo = () => { ); return ( - -
- setIsDrawerOpen((open) => !open)} - activeItemId="1" - conversations={{ Today: conversations }} - onNewChat={startNewChat} - drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }} - drawerContent={ -
- {areSettingsOpen ? ( - setAreSettingsOpen(false)} /> - ) : ( - - - -
- - - {messages.map((message) => ( - - ))} - - - - , - tooltipContent: 'Message actions', - 'aria-label': 'Message actions' - } - }} - additionalActions={ - <> - - + +
+ setIsDrawerOpen((open) => !open)} + activeItemId="1" + conversations={{ Today: conversations }} + onNewChat={startNewChat} + drawerCloseButtonProps={{ 'aria-label': 'Close chat history' }} + drawerContent={ +
+ {areSettingsOpen ? ( + setAreSettingsOpen(false)} /> + ) : ( + + + +
+ + + {messages.map((message) => ( + ({ + ...attachment, + onClick: openCanvas + }))} + /> + ))} +
+
+
+ + { + if (value === 'canvas') { + if (showCanvasLabel) { + closeCanvasMode(); + } else { + openCanvas(); + } + } + setIsAttachMenuOpen(false); + } + }} + buttonProps={{ + attach: { + innerRef: messageActionsRef, + icon: , + tooltipContent: 'Message actions', + 'aria-label': 'Message actions' + } + }} + additionalActions={ + <> + {showCanvasLabel && ( + )} - > - - {['Granite 7B', 'Granite 8B', 'Llama 3'].map((option) => ( - - {option} - - ))} - - - - } - /> - - -
-
-
-
- )} -
- } - /> -
-
+ + + } + /> + +
+
+
+
+
+ )} +
+ } + /> +
+
+ ); }; diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md index a29d11cb..96d2cb19 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md @@ -56,12 +56,12 @@ import RhUiAddIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-add-icon'; import RhUiClipboardIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-clipboard-icon'; import RhUiCodeIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-code-icon'; import RhUiUploadIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-upload-icon'; -import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon'; -import { RhUiBuildFillIcon, RhUiCopyFillIcon } from '@patternfly/react-icons'; +import RhUiBuildFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-build-fill-icon'; +import RhUiCopyFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-copy-fill-icon'; import RhUiImageFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-image-fill-icon'; -import { RhUiEditFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; -import { RhUiSettingsFillIcon } from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; -import { RhMicronsCloseIcon } from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; +import RhUiEditFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-edit-fill-icon'; +import RhUiSettingsFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-settings-fill-icon'; +import RhMicronsCloseIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; import { Button, Divider, diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx index 55adb26d..fc3ef750 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/FullscreenDockedNav.tsx @@ -76,17 +76,24 @@ const conversations = [ ]; const hamburgerHoverStyles = ` + .pf-chatbot__canvas-docked-nav .pf-v6-c-masthead__logo.pf-m-compact { + display: revert; + } + + .pf-chatbot__canvas-docked-nav .pf-v6-c-nav.pf-m-docked .pf-v6-c-nav__link-text { + display: none; + } + .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger:is(:hover, :focus-visible) { - --pf-v6-c-button--hamburger-icon--top--path: path("M5,1 L9,1"); - --pf-v6-c-button--hamburger-icon--arrow--path: path("M3,7 L1,5 L3,3"); - --pf-v6-c-button--hamburger-icon--bottom--path: path("M9,9 L5,9"); - --pf-v6-c-button--hover__icon--ScaleX: -1; + --pf-v6-c-button--hamburger-icon--top--path: var(--pf-v6-c-button--hamburger-icon--top--collapse--path); + --pf-v6-c-button--hamburger-icon--arrow--path: var(--pf-v6-c-button--hamburger-icon--arrow--collapse--path); + --pf-v6-c-button--hamburger-icon--bottom--path: var(--pf-v6-c-button--hamburger-icon--bottom--collapse--path); --pf-v6-c-button__icon--TransitionDelay: 0s; - --pf-v6-c-button--hover__icon--TransitionDelay: 0s; + --pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-expand--ScaleX); } .pf-chatbot__canvas-history-toggle.pf-v6-c-button.pf-m-hamburger[aria-expanded="true"]:is(:hover, :focus-visible) { - --pf-v6-c-button--hover__icon--ScaleX: 1; + --pf-v6-c-button__icon--ScaleX: var(--pf-v6-c-button--m-hamburger__icon--m-collapse--ScaleX); } `; @@ -271,6 +278,7 @@ export const FullscreenDockedNav: FunctionComponent = () => { src={userAvatar} alt="User profile" size="md" + isBordered /> @@ -314,7 +322,14 @@ export const FullscreenDockedNav: FunctionComponent = () => { - +
)} diff --git a/packages/module/src/MessageBar/MessageBar.test.tsx b/packages/module/src/MessageBar/MessageBar.test.tsx index f9ecd039..07a278ae 100644 --- a/packages/module/src/MessageBar/MessageBar.test.tsx +++ b/packages/module/src/MessageBar/MessageBar.test.tsx @@ -227,6 +227,23 @@ describe('Message bar', () => { await userEvent.click(attachButton); expect(attachToggleClickSpy).toHaveBeenCalledTimes(1); }); + it('can toggle attach menu without an optional toggle callback', async () => { + const setIsAttachMenuOpen = jest.fn(); + render( + + ); + + await userEvent.click(screen.getByRole('button', { name: 'Attach' })); + + expect(setIsAttachMenuOpen).toHaveBeenCalledWith(true); + }); it('can pass searchInputProps to search input in AttachMenu', () => { render( void; + onAttachMenuToggleClick?: () => void; /** A callback for when the input value in the menu changes. */ onAttachMenuInputChange?: (value: string) => void; /** Function callback called when user selects item in menu. */ @@ -368,7 +368,7 @@ export const MessageBarBase: FunctionComponent = ({ const handleAttachMenuToggle = () => { attachMenuProps?.setIsAttachMenuOpen && attachMenuProps?.setIsAttachMenuOpen(!attachMenuProps?.isAttachMenuOpen); - attachMenuProps?.onAttachMenuToggleClick(); + attachMenuProps?.onAttachMenuToggleClick?.(); }; const handleSpeechRecognition = (message) => { @@ -525,7 +525,9 @@ export const MessageBarBase: FunctionComponent = ({ {...(attachMenuProps && { handleTextInputChange: attachMenuProps.onAttachMenuInputChange })} popperProps={{ direction: 'up', distance: 8 }} searchInputPlaceholder={attachMenuProps?.attachMenuInputPlaceholder} - {...attachMenuProps} + menuSearchProps={attachMenuProps?.menuSearchProps} + menuSearchInputProps={attachMenuProps?.menuSearchInputProps} + searchInputProps={attachMenuProps?.searchInputProps} /> ); } From 3be4240db2415fc46d252b6ac13ef6a66c9a2698 Mon Sep 17 00:00:00 2001 From: Gabi Podolnikova Date: Fri, 25 Sep 2026 09:50:12 +0200 Subject: [PATCH 8/8] fix(docs): restore Canvas export icon import --- .../content/extensions/chatbot/examples/demos/Canvas.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md index 781c5ace..8a7d68e6 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/demos/Canvas.md @@ -66,6 +66,7 @@ import RhUiUndoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-undo-icon import RhUiServerUploadFillIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-server-upload-fill-icon'; import RhUiAiInfoIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-ai-info-icon'; import RhMicronsCloseIcon from '@patternfly/react-icons/dist/esm/icons/rh-microns-close-icon'; +import RhUiExportIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-export-icon'; import { useDropzone } from 'react-dropzone'; import PFIconLogoColor from '../UI/PF-IconLogo-Color.svg'; import PFIconLogoReverse from '../UI/PF-IconLogo-Reverse.svg';