diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md index bbccee81..28a60c31 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md @@ -19,6 +19,7 @@ propComponents: 'FileDropZone', 'PreviewAttachment', 'Message', + 'MessageExtraContent', 'PreviewAttachment', 'ActionProps', 'SourcesCardProps' @@ -135,6 +136,14 @@ Messages from users have a different background color to differentiate them from ``` +### User messages with extraContent prop + +The `extraContent` prop makes the `` component more flexible by letting you add extra content in specific parts of a message. This is useful for adding things like timestamps, badges, or custom elements without changing the default layout. This allows you to create dynamic and reusable elements for various use cases. + +```js file="./UserMessageWithExtraContent.tsx" + +``` + ## File attachments ### Messages with attachments diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx new file mode 100644 index 00000000..47d24aab --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/UserMessageWithExtraContent.tsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import Message from '@patternfly/chatbot/dist/dynamic/Message'; +import userAvatar from './user_avatar.svg'; +import { Alert, Badge, Button, Card, CardBody, CardFooter, CardTitle } from '@patternfly/react-core'; + +const UserActionEndContent = () => { + // eslint-disable-next-line no-console + const onClick = () => console.log('custom button click'); + return ( + + + + + ); +}; + +const CardInformationAfterMainContent = () => ( + + This is content card after main content + Body + Footer + +); + +const BeforeMainContent = () => ( +
+ + 7 + + + 24 + +
+); + +export const UserMessageWithExtraContent: React.FunctionComponent = () => ( + <> + , + afterMainContent: , + endContent: + }} + /> + +); diff --git a/packages/module/src/Message/Message.test.tsx b/packages/module/src/Message/Message.test.tsx index fc5e3e16..4f8457eb 100644 --- a/packages/module/src/Message/Message.test.tsx +++ b/packages/module/src/Message/Message.test.tsx @@ -491,4 +491,118 @@ describe('Message', () => { ); expect(screen.getAllByRole('img')[1]).toHaveAttribute('src', 'test.png'); }); + it('should render beforeMainContent with main content', () => { + const mainContent = 'Main message content'; + const beforeMainContentText = 'Before main content'; + const beforeMainContent =
{beforeMainContentText}
; + + render( + + ); + + expect(screen.getByText(beforeMainContentText)).toBeTruthy(); + expect(screen.getByText(mainContent)).toBeTruthy(); + }); + it('should render afterMainContent with main content', () => { + const mainContent = 'Main message content'; + const afterMainContentText = 'After main content'; + const afterMainContent =
{afterMainContentText}
; + + render( + + ); + + expect(screen.getByText(afterMainContentText)).toBeTruthy(); + expect(screen.getByText(mainContent)).toBeTruthy(); + }); + + it('should render endContent with main content', () => { + const mainContent = 'Main message content'; + const endMainContentText = 'End content'; + const endContent =
{endMainContentText}
; + + render(); + + expect(screen.getByText(endMainContentText)).toBeTruthy(); + expect(screen.getByText(mainContent)).toBeTruthy(); + }); + it('should render all parts of extraContent with main content', () => { + const beforeMainContent =
Before main content
; + const afterMainContent =
After main content
; + const endContent =
End content
; + + render( + + ); + + expect(screen.getByText('Before main content')).toBeTruthy(); + expect(screen.getByText('Main message content')).toBeTruthy(); + expect(screen.getByText('After main content')).toBeTruthy(); + expect(screen.getByText('End content')).toBeTruthy(); + }); + + it('should not render extraContent when not provided', () => { + render(); + + // Ensure no extraContent is rendered + expect(screen.getByText('Main message content')).toBeTruthy(); + expect(screen.queryByText('Before main content')).toBeFalsy(); + expect(screen.queryByText('After main content')).toBeFalsy(); + expect(screen.queryByText('end message content')).toBeFalsy(); + }); + + it('should handle undefined or null values in extraContent gracefully', () => { + render( + + ); + + // Ensure that no extraContent is rendered if they are null or undefined + expect(screen.getByText('Main message content')).toBeTruthy(); + expect(screen.queryByText('Before main content')).toBeFalsy(); + expect(screen.queryByText('After main content')).toBeFalsy(); + expect(screen.queryByText('end message content')).toBeFalsy(); + }); + it('should render JSX in extraContent correctly', () => { + const beforeMainContent = ( +
+ Bold before content +
+ ); + const afterMainContent = ( +
+ Bold after content +
+ ); + const endContent = ( +
+ Bold end content +
+ ); + render( + + ); + + // Check that the JSX is correctly rendered + expect(screen.getByTestId('before-main-content')).toContainHTML('Bold before content'); + expect(screen.getByTestId('after-main-content')).toContainHTML('Bold after content'); + expect(screen.getByTestId('end-main-content')).toContainHTML('Bold end content'); + }); }); diff --git a/packages/module/src/Message/Message.tsx b/packages/module/src/Message/Message.tsx index 84ad1102..72388d85 100644 --- a/packages/module/src/Message/Message.tsx +++ b/packages/module/src/Message/Message.tsx @@ -2,7 +2,7 @@ // Chatbot Main - Message // ============================================================================ -import React from 'react'; +import React, { ReactNode } from 'react'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; @@ -39,6 +39,17 @@ export interface MessageAttachment { spinnerTestId?: string; } +export interface MessageExtraContent { + /** Content to display before the main content */ + beforeMainContent?: ReactNode; + + /** Content to display after the main content */ + afterMainContent?: ReactNode; + + /** Content to display at the end */ + endContent?: ReactNode; +} + export interface MessageProps extends Omit, 'role'> { /** Unique id for message */ id?: string; @@ -46,6 +57,8 @@ export interface MessageProps extends Omit, 'rol role: 'user' | 'bot'; /** Message content */ content?: string; + /** Extra Message content */ + extraContent?: MessageExtraContent; /** Name of the user */ name?: string; /** Avatar src for the user */ @@ -96,6 +109,7 @@ export interface MessageProps extends Omit, 'rol export const Message: React.FunctionComponent = ({ role, content, + extraContent, name, avatar, timestamp, @@ -113,6 +127,7 @@ export const Message: React.FunctionComponent = ({ quickStarts, ...props }: MessageProps) => { + const { beforeMainContent, afterMainContent, endContent } = extraContent || {}; let avatarClassName; if (avatarProps && 'className' in avatarProps) { const { className, ...rest } = avatarProps; @@ -155,18 +170,22 @@ export const Message: React.FunctionComponent = ({ {isLoading ? ( ) : ( - {children}, - ul: UnorderedListMessage, - ol: (props) => , - li: ListItemMessage - }} - remarkPlugins={[remarkGfm]} - > - {content} - + <> + {beforeMainContent && <>{beforeMainContent}} + {children}, + ul: UnorderedListMessage, + ol: (props) => , + li: ListItemMessage + }} + remarkPlugins={[remarkGfm]} + > + {content} + + {afterMainContent && <>{afterMainContent}} + )} {!isLoading && sources && } {quickStarts && quickStarts.quickStart && ( @@ -206,6 +225,7 @@ export const Message: React.FunctionComponent = ({ ))} )} + {!isLoading && endContent && <>{endContent}}