// ============================================================================ // Chatbot Header - Chatbot Conversation History Nav // ============================================================================ import React from 'react'; // Import PatternFly components import { Button, Drawer, DrawerPanelContent, DrawerContent, DrawerPanelBody, DrawerProps, DrawerHead, DrawerActions, DrawerCloseButton, DrawerContentBody, SearchInput, Menu, MenuList, MenuGroup, MenuItem, MenuContent, MenuItemProps, MenuProps, DrawerPanelContentProps, DrawerContentProps, DrawerContentBodyProps, DrawerHeadProps, DrawerActionsProps, DrawerCloseButtonProps, DrawerPanelBodyProps, SkeletonProps } from '@patternfly/react-core'; import { OutlinedCommentAltIcon } from '@patternfly/react-icons'; import { ChatbotDisplayMode } from '../Chatbot/Chatbot'; import ConversationHistoryDropdown from './ChatbotConversationHistoryDropdown'; import LoadingState from './LoadingState'; import HistoryEmptyState, { HistoryEmptyStateProps } from './EmptyState'; export interface Conversation { /** Conversation id */ id: string; /** Conversation icon */ icon?: React.ReactNode; /** Flag for no icon */ noIcon?: boolean; /** Conversation */ text: string; /** Dropdown items rendered in conversation settings dropdown */ menuItems?: React.ReactNode; /** Optional classname applied to conversation settings dropdown */ menuClassName?: string; /** Tooltip content and aria-label applied to conversation settings dropdown */ label?: string; /** Callback for when user selects item. */ onSelect?: (event?: React.MouseEvent, value?: string | number) => void; /** Additional props passed to conversation menu item */ additionalProps?: MenuItemProps; } export interface ChatbotConversationHistoryNavProps extends DrawerProps { /** Function called to toggle drawer */ onDrawerToggle: (event: React.KeyboardEvent | React.MouseEvent | React.TransitionEvent) => void; /** Flag to indicate whether drawer is open */ isDrawerOpen: boolean; /** Function called to close drawer */ setIsDrawerOpen: (bool: boolean) => void; /* itemId of the currently active item. */ activeItemId?: string | number; /** Callback function for when an item is selected */ onSelectActiveItem?: (event?: React.MouseEvent, itemId?: string | number) => void; /** Items shown in conversation history */ conversations: Conversation[] | { [key: string]: Conversation[] }; /** Text shown in blue button */ newChatButtonText?: string; /** Callback function for when blue button is clicked. Omit to hide blue "new chat button" */ onNewChat?: () => void; /** Content wrapped by conversation history nav */ drawerContent?: React.ReactNode; /** Placeholder for search input */ searchInputPlaceholder?: string; /** Aria label for search input */ searchInputAriaLabel?: string; /** A callback for when the input value changes. Omit to hide input field */ handleTextInputChange?: (value: string) => void; /** Display mode of chatbot */ displayMode: ChatbotDisplayMode; /** Reverses the order of the drawer action buttons */ reverseButtonOrder?: boolean; /** Custom test id for the drawer actions */ drawerActionsTestId?: string; /** Additional props applied to menu */ menuProps?: MenuProps; /** Additional props applied to panel */ drawerPanelContentProps?: DrawerPanelContentProps; /** Additional props applied to drawer content */ drawerContentProps?: Omit; /** Additional props applied to drawer content body */ drawerContentBodyProps?: DrawerContentBodyProps; /** Additional props applied to drawer head */ drawerHeadProps?: DrawerHeadProps; /** Additional props applied to drawer actions */ drawerActionsProps?: DrawerActionsProps; /** Additional props applied to drawer close button */ drawerCloseButtonProps?: DrawerCloseButtonProps; /** Additional props appleid to drawer panel body */ drawerPanelBodyProps?: DrawerPanelBodyProps; /** Whether to show drawer loading state */ isLoading?: boolean; /** Additional props for loading state */ loadingState?: SkeletonProps; /** Content to show in error state. Error state will appear once content is passed in. */ errorState?: HistoryEmptyStateProps; } export const ChatbotConversationHistoryNav: React.FunctionComponent = ({ onDrawerToggle, isDrawerOpen, setIsDrawerOpen, activeItemId, onSelectActiveItem, conversations, newChatButtonText = 'New chat', drawerContent, onNewChat, searchInputPlaceholder = 'Search previous conversations...', searchInputAriaLabel = 'Filter menu items', handleTextInputChange, displayMode, reverseButtonOrder = false, drawerActionsTestId = 'chatbot-nav-drawer-actions', menuProps, drawerPanelContentProps, drawerContentProps, drawerContentBodyProps, drawerHeadProps, drawerActionsProps, drawerCloseButtonProps, drawerPanelBodyProps, isLoading, loadingState, errorState, ...props }: ChatbotConversationHistoryNavProps) => { const drawerRef = React.useRef(null); const onExpand = () => { drawerRef.current && drawerRef.current.focus(); }; const getNavItem = (conversation: Conversation) => ( })} /* eslint-disable indent */ {...(conversation.menuItems ? { actions: ( ) } : {})} {...conversation.additionalProps} /* eslint-enable indent */ > {conversation.text} ); const buildMenu = () => { if (Array.isArray(conversations)) { // Render for array of MenuItemObject return ( {conversations.map((conversation) => ( {getNavItem(conversation)} ))} ); } else { // Render for object with NavItemObject arrays as values return ( <> {Object.keys(conversations).map((navGroup) => ( {conversations[navGroup].map((conversation) => ( {getNavItem(conversation)} ))} ))} ); } }; // Menu Content // - Consumers should pass an array to of the list of conversations // - Groups could be optional, but items need to be ordered by date const renderMenuContent = () => { if (errorState) { return ; } return ( {buildMenu()} ); }; const renderDrawerContent = () => ( <> {handleTextInputChange && (
handleTextInputChange(value)} placeholder={searchInputPlaceholder} />
)} {renderMenuContent()} ); const renderPanelContent = () => { const drawer = ( <> {onNewChat && } {isLoading ? : renderDrawerContent()} ); return ( {drawer} ); }; // An onKeyDown property must be passed to the Drawer component to handle closing // the drawer panel and deactivating the focus trap via the Escape key. const onEscape = (event: React.KeyboardEvent) => { if (event.key === 'Escape') { // prevents using escape key on menu buttons from closing the panel, but I'm not sure if this is allowed if (event.target instanceof HTMLInputElement && event.target.type !== 'button') { setIsDrawerOpen(false); } } }; return ( <>
{drawerContent}
); }; export default ChatbotConversationHistoryNav;