Skip to content

Commit

Permalink
feat(SkipToContent): Add SkipToContent to demos
Browse files Browse the repository at this point in the history
  • Loading branch information
rebeccaalpert committed Nov 5, 2024
1 parent c7b51b8 commit e91991c
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ This demo displays a basic chatbot, which includes:

6. A [`<ChatbotConversationHistoryNav>`](/patternfly-ai/chatbot/chatbot-conversation-history) toggled open and closed by the `<ChatbotHeaderMenu`> in the `<ChatbotHeader>`.

7. A skip to chatbot link, made with a [PatternFly 6 Skip to Content](/components/skip-to-content)

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

```
Expand All @@ -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 [`<Chatbot>`](/patternfly-ai/chatbot/chatbot-container) container.
3. A [`<ChatbotHeader>`](/patternfly-ai/chatbot/chatbot-header) with all built sub-components laid out, including a `<ChatbotHeaderTitle>`
4. [`<ChatbotContent>` and `<MessageBox>`](/patternfly-ai/chatbot/chatbot-container#content-and-message-box) with:
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -169,6 +169,7 @@ export const ChatbotDemo: React.FunctionComponent = () => {
);
const [announcement, setAnnouncement] = React.useState<string>();
const scrollToBottomRef = React.useRef<HTMLDivElement>(null);
const toggleRef = React.useRef<HTMLButtonElement>(null);

// Autu-scrolls to the latest message
React.useEffect(() => {
Expand Down Expand Up @@ -281,12 +282,24 @@ export const ChatbotDemo: React.FunctionComponent = () => {
</>
);

const handleSkipToContent = (e) => {
e.preventDefault();
if (toggleRef.current) {
toggleRef.current.focus();
}
};

return (
<>
<SkipToContent onClick={handleSkipToContent} href="#">
Skip to chatbot
</SkipToContent>
<ChatbotToggle
toolTipLabel="Chatbot"
isChatbotVisible={chatbotVisible}
onToggleChatbot={() => setChatbotVisible(!chatbotVisible)}
id="chatbot-toggle"
ref={toggleRef}
/>
<Chatbot isVisible={chatbotVisible} displayMode={displayMode}>
<ChatbotConversationHistoryNav
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
PageSidebarBody,
PageSidebar,
MastheadToggle,
PageToggleButton
PageToggleButton,
SkipToContent
} from '@patternfly/react-core';

import Chatbot, { ChatbotDisplayMode } from '@patternfly/virtual-assistant/dist/dynamic/Chatbot';
Expand Down Expand Up @@ -175,6 +176,8 @@ export const EmbeddedChatbotDemo: React.FunctionComponent = () => {
const [isSidebarOpen, setIsSidebarOpen] = React.useState(false);
const [announcement, setAnnouncement] = React.useState<string>();
const scrollToBottomRef = React.useRef<HTMLDivElement>(null);
const chatbotRef = React.useRef<HTMLDivElement>(null);

const displayMode = ChatbotDisplayMode.embedded;
// Autu-scrolls to the latest message
React.useEffect(() => {
Expand Down Expand Up @@ -302,8 +305,20 @@ export const EmbeddedChatbotDemo: React.FunctionComponent = () => {
</PageSidebar>
);

const skipToChatbot = (event: React.MouseEvent) => {
event.preventDefault();
chatbotRef.current?.focus();
};

const skipToContent = (
/* You can also add a SkipToContent for your main content here */
<SkipToContent href="#" onClick={skipToChatbot}>
Skip to chatbot
</SkipToContent>
);

return (
<Page masthead={masthead} sidebar={sidebar} isContentFilled>
<Page skipToContent={skipToContent} masthead={masthead} sidebar={sidebar} isContentFilled>
<Chatbot displayMode={displayMode}>
<ChatbotConversationHistoryNav
displayMode={displayMode}
Expand Down Expand Up @@ -357,7 +372,7 @@ export const EmbeddedChatbotDemo: React.FunctionComponent = () => {
<ChatbotContent>
{/* Update the announcement prop on MessageBox whenever a new message is sent
so that users of assistive devices receive sufficient context */}
<MessageBox announcement={announcement}>
<MessageBox announcement={announcement} ref={chatbotRef}>
<ChatbotWelcomePrompt
title="Hello, Chatbot User"
description="How may I help you today?"
Expand Down
4 changes: 3 additions & 1 deletion packages/module/src/Chatbot/Chatbot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const Chatbot: React.FunctionComponent<ChatbotProps> = ({
children,
displayMode = ChatbotDisplayMode.default,
isVisible = true,
className
className,
...props
}: ChatbotProps) => {
// Configure docked mode
React.useEffect(() => {
Expand All @@ -49,6 +50,7 @@ export const Chatbot: React.FunctionComponent<ChatbotProps> = ({
variants={motionChatbot}
initial="hidden"
animate={isVisible ? 'visible' : 'hidden'}
{...props}
>
{isVisible ? children : undefined}
</motion.div>
Expand Down
15 changes: 9 additions & 6 deletions packages/module/src/ChatbotToggle/ChatbotToggle.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<HTMLButtonElement>;
}

const ChatIcon = () => (
Expand All @@ -44,13 +41,14 @@ const ChatIcon = () => (
</svg>
);

export const ChatbotToggle: React.FunctionComponent<ChatbotToggleProps> = ({
const ChatbotToggleBase: React.FunctionComponent<ChatbotToggleProps> = ({
toolTipLabel,
isChatbotVisible,
onToggleChatbot,
tooltipProps,
toggleButtonLabel,
closedToggleIcon: ClosedToggleIcon,
innerRef,
...props
}: ChatbotToggleProps) => {
// Configure icon
Expand All @@ -66,6 +64,7 @@ export const ChatbotToggle: React.FunctionComponent<ChatbotToggleProps> = ({
onClick={onToggleChatbot}
aria-expanded={isChatbotVisible}
icon={<Icon isInline>{icon}</Icon>}
ref={innerRef}
{...props}
>
{/* Notification dot placeholder */}
Expand All @@ -74,4 +73,8 @@ export const ChatbotToggle: React.FunctionComponent<ChatbotToggleProps> = ({
);
};

const ChatbotToggle = React.forwardRef((props: ChatbotToggleProps, ref: React.Ref<any>) => (

Check warning on line 76 in packages/module/src/ChatbotToggle/ChatbotToggle.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type
<ChatbotToggleBase innerRef={ref} {...props} />
));

export default ChatbotToggle;
19 changes: 16 additions & 3 deletions packages/module/src/MessageBox/MessageBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,27 @@ export interface MessageBoxProps extends React.HTMLProps<HTMLDivElement> {
children: React.ReactNode;
/** Custom classname for the MessageBox component */
className?: string;
/** Ref applied to message box */
innerRef?: React.Ref<HTMLDivElement>;
}

const MessageBox: React.FunctionComponent<MessageBoxProps> = ({
const MessageBoxBase: React.FunctionComponent<MessageBoxProps> = ({
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<HTMLDivElement>(null);
const defaultRef = React.useRef<HTMLDivElement>(null);
let messageBoxRef;
if (innerRef) {
messageBoxRef = innerRef;
} else {
messageBoxRef = defaultRef;
}

// Configure handlers
const handleScroll = React.useCallback(() => {
Expand Down Expand Up @@ -83,7 +92,7 @@ const MessageBox: React.FunctionComponent<MessageBoxProps> = ({
tabIndex={0}
aria-label={ariaLabel}
className={`pf-chatbot__messagebox ${className ?? ''}`}
ref={messageBoxRef}
ref={innerRef ?? messageBoxRef}
>
{children}
<div className="pf-chatbot__messagebox-announcement" aria-live="polite">
Expand All @@ -95,4 +104,8 @@ const MessageBox: React.FunctionComponent<MessageBoxProps> = ({
);
};

export const MessageBox = React.forwardRef((props: MessageBoxProps, ref: React.Ref<any>) => (
<MessageBoxBase innerRef={ref} {...props} />
));

export default MessageBox;

0 comments on commit e91991c

Please sign in to comment.