diff --git a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.md b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.md index 49959439..957fdde1 100644 --- a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.md +++ b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.md @@ -77,6 +77,8 @@ This demo displays a basic chatbot, which includes: 6. A [``](/patternfly-ai/chatbot/chatbot-conversation-history) toggled open and closed by the ` in the ``. +7. A skip to chatbot link, made with a [PatternFly 6 Skip to Content](/components/skip-to-content) + ```js file="./Chatbot.tsx" isFullscreen ``` @@ -85,7 +87,7 @@ This demo displays a basic chatbot, which includes: This demo displays an embedded chatbot. Embedded chatbots are meant to be placed within a page in your product. This demo includes: -1. A [PatternFly page](/components/page) with a sidebar and masthead +1. A [PatternFly page](/components/page) with a sidebar, skip to chatbot link, and masthead 2. A [``](/patternfly-ai/chatbot/chatbot-container) container. 3. A [``](/patternfly-ai/chatbot/chatbot-header) with all built sub-components laid out, including a `` 4. [`` and ``](/patternfly-ai/chatbot/chatbot-container#content-and-message-box) with: diff --git a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.tsx b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.tsx index 7698dfdf..cbdf386f 100644 --- a/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.tsx +++ b/packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/demos/Chatbot.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Bullseye, Brand, DropdownList, DropdownItem, DropdownGroup } from '@patternfly/react-core'; +import { Bullseye, Brand, DropdownList, DropdownItem, DropdownGroup, SkipToContent } from '@patternfly/react-core'; import ChatbotToggle from '@patternfly/virtual-assistant/dist/dynamic/ChatbotToggle'; import Chatbot, { ChatbotDisplayMode } from '@patternfly/virtual-assistant/dist/dynamic/Chatbot'; @@ -169,6 +169,7 @@ export const ChatbotDemo: React.FunctionComponent = () => { ); const [announcement, setAnnouncement] = React.useState(); const scrollToBottomRef = React.useRef(null); + const toggleRef = React.useRef(null); // Autu-scrolls to the latest message React.useEffect(() => { @@ -281,12 +282,24 @@ export const ChatbotDemo: React.FunctionComponent = () => { ); + const handleSkipToContent = (e) => { + e.preventDefault(); + if (toggleRef.current) { + toggleRef.current.focus(); + } + }; + return ( <> + + Skip to chatbot + setChatbotVisible(!chatbotVisible)} + id="chatbot-toggle" + ref={toggleRef} /> { const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); const [announcement, setAnnouncement] = React.useState(); const scrollToBottomRef = React.useRef(null); + const chatbotRef = React.useRef(null); + const displayMode = ChatbotDisplayMode.embedded; // Autu-scrolls to the latest message React.useEffect(() => { @@ -302,8 +305,20 @@ export const EmbeddedChatbotDemo: React.FunctionComponent = () => { ); + const skipToChatbot = (event: React.MouseEvent) => { + event.preventDefault(); + chatbotRef.current?.focus(); + }; + + const skipToContent = ( + /* You can also add a SkipToContent for your main content here */ + + Skip to chatbot + + ); + return ( - + { {/* Update the announcement prop on MessageBox whenever a new message is sent so that users of assistive devices receive sufficient context */} - + = ({ children, displayMode = ChatbotDisplayMode.default, isVisible = true, - className + className, + ...props }: ChatbotProps) => { // Configure docked mode React.useEffect(() => { @@ -49,6 +50,7 @@ export const Chatbot: React.FunctionComponent = ({ variants={motionChatbot} initial="hidden" animate={isVisible ? 'visible' : 'hidden'} + {...props} > {isVisible ? children : undefined} diff --git a/packages/module/src/ChatbotToggle/ChatbotToggle.tsx b/packages/module/src/ChatbotToggle/ChatbotToggle.tsx index 6a9d76b4..f7692c91 100644 --- a/packages/module/src/ChatbotToggle/ChatbotToggle.tsx +++ b/packages/module/src/ChatbotToggle/ChatbotToggle.tsx @@ -1,15 +1,10 @@ // ============================================================================ // Chatbot Toggle // ============================================================================ - import React from 'react'; - -// Import PatternFly components import { Button, ButtonProps, Tooltip, TooltipProps, Icon } from '@patternfly/react-core'; import AngleDownIcon from '@patternfly/react-icons/dist/esm/icons/angle-down-icon'; -// Import Chatbot components - export interface ChatbotToggleProps extends ButtonProps { /** Contents of the tooltip applied to the toggle button */ toolTipLabel?: React.ReactNode; @@ -23,6 +18,8 @@ export interface ChatbotToggleProps extends ButtonProps { toggleButtonLabel?: string; /** An image displayed in the chatbot toggle when it is closed */ closedToggleIcon?: () => JSX.Element; + /** Ref applied to toggle */ + innerRef?: React.Ref; } const ChatIcon = () => ( @@ -44,13 +41,14 @@ const ChatIcon = () => ( ); -export const ChatbotToggle: React.FunctionComponent = ({ +const ChatbotToggleBase: React.FunctionComponent = ({ toolTipLabel, isChatbotVisible, onToggleChatbot, tooltipProps, toggleButtonLabel, closedToggleIcon: ClosedToggleIcon, + innerRef, ...props }: ChatbotToggleProps) => { // Configure icon @@ -66,6 +64,7 @@ export const ChatbotToggle: React.FunctionComponent = ({ onClick={onToggleChatbot} aria-expanded={isChatbotVisible} icon={{icon}} + ref={innerRef} {...props} > {/* Notification dot placeholder */} @@ -74,4 +73,8 @@ export const ChatbotToggle: React.FunctionComponent = ({ ); }; +const ChatbotToggle = React.forwardRef((props: ChatbotToggleProps, ref: React.Ref) => ( + +)); + export default ChatbotToggle; diff --git a/packages/module/src/MessageBox/MessageBox.tsx b/packages/module/src/MessageBox/MessageBox.tsx index f16555a0..ed8e2b4e 100644 --- a/packages/module/src/MessageBox/MessageBox.tsx +++ b/packages/module/src/MessageBox/MessageBox.tsx @@ -13,18 +13,27 @@ export interface MessageBoxProps extends React.HTMLProps { children: React.ReactNode; /** Custom classname for the MessageBox component */ className?: string; + /** Ref applied to message box */ + innerRef?: React.Ref; } -const MessageBox: React.FunctionComponent = ({ +const MessageBoxBase: React.FunctionComponent = ({ announcement, ariaLabel = 'Scrollable message log', children, + innerRef, className }: MessageBoxProps) => { const [atTop, setAtTop] = React.useState(false); const [atBottom, setAtBottom] = React.useState(true); const [isOverflowing, setIsOverflowing] = React.useState(false); - const messageBoxRef = React.useRef(null); + const defaultRef = React.useRef(null); + let messageBoxRef; + if (innerRef) { + messageBoxRef = innerRef; + } else { + messageBoxRef = defaultRef; + } // Configure handlers const handleScroll = React.useCallback(() => { @@ -83,7 +92,7 @@ const MessageBox: React.FunctionComponent = ({ tabIndex={0} aria-label={ariaLabel} className={`pf-chatbot__messagebox ${className ?? ''}`} - ref={messageBoxRef} + ref={innerRef ?? messageBoxRef} > {children}
@@ -95,4 +104,8 @@ const MessageBox: React.FunctionComponent = ({ ); }; +export const MessageBox = React.forwardRef((props: MessageBoxProps, ref: React.Ref) => ( + +)); + export default MessageBox;