From c4fbe251af043e0439ad9298be0370e81cf2f60e Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Mon, 16 Feb 2026 17:19:23 -0500 Subject: [PATCH 1/8] feat(messages): fill positive, negative, and copy action icons on click --- .../Messages/MessageWithIconSwapping.tsx | 22 ++ .../chatbot/examples/Messages/Messages.md | 9 + packages/module/src/Message/Message.test.tsx | 65 ++++ packages/module/src/Message/Message.tsx | 10 +- .../ResponseActions/ResponseActions.test.tsx | 287 ++++++++++++++++++ .../src/ResponseActions/ResponseActions.tsx | 40 ++- 6 files changed, 428 insertions(+), 5 deletions(-) create mode 100644 packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx new file mode 100644 index 000000000..dc57af6bf --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx @@ -0,0 +1,22 @@ +import { FunctionComponent } from 'react'; + +import Message from '@patternfly/chatbot/dist/dynamic/Message'; +import patternflyAvatar from './patternfly_avatar.jpg'; + +export const IconSwappingExample: FunctionComponent = () => ( + 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('Copied') } + }} + useFilledIconsOnClick + /> +); 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 c6e167960..e38ff3daf 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 @@ -138,6 +138,15 @@ When `persistActionSelection` is `true`: ``` +### Message actions with icon swapping + +To provide enhanced visual feedback when users interact with response actions, you can enable icon swapping by setting `useFilledIconsOnClick` to `true`. When enabled, the predefined actions positive, negative, and copy will automatically swap from outline icon variants to filled icon variants when clicked. + + +```js file="./MessageWithIconSwapping.tsx" + +``` + ### Multiple messsage action groups To maintain finer control over message action selection behavior, you can create groups of actions by passing an array of objects to the `actions` prop. This allows you to separate actions into conceptually or functionally different groups and implement different behavior for each group as needed. For example, you could separate feedback actions (thumbs up/down) form utility actions (copy and download), and have different selection behaviors for each group. diff --git a/packages/module/src/Message/Message.test.tsx b/packages/module/src/Message/Message.test.tsx index 3e7fe1adf..a054e87da 100644 --- a/packages/module/src/Message/Message.test.tsx +++ b/packages/module/src/Message/Message.test.tsx @@ -9,6 +9,24 @@ import rehypeExternalLinks from '../__mocks__/rehype-external-links'; import { AlertActionLink, Button, CodeBlockAction } from '@patternfly/react-core'; import { DeepThinkingProps } from '../DeepThinking'; +// Mock the icon components +jest.mock('@patternfly/react-icons', () => ({ + OutlinedThumbsUpIcon: () =>
OutlinedThumbsUpIcon
, + ThumbsUpIcon: () =>
ThumbsUpIcon
, + OutlinedThumbsDownIcon: () =>
OutlinedThumbsDownIcon
, + ThumbsDownIcon: () =>
ThumbsDownIcon
, + OutlinedCopyIcon: () =>
OutlinedCopyIcon
, + CopyIcon: () =>
CopyIcon
, + DownloadIcon: () =>
DownloadIcon
, + ExternalLinkAltIcon: () =>
ExternalLinkAltIcon
, + VolumeUpIcon: () =>
VolumeUpIcon
, + PencilAltIcon: () =>
PencilAltIcon
, + CheckIcon: () =>
CheckIcon
, + CloseIcon: () =>
CloseIcon
, + ExternalLinkSquareAltIcon: () =>
ExternalLinkSquareAltIcon
, + TimesIcon: () =>
TimesIcon
+})); + const ALL_ACTIONS = [ { label: /Good response/i }, { label: /Bad response/i }, @@ -1351,4 +1369,51 @@ describe('Message', () => { render(); expect(screen.getByRole('region')).toHaveClass('pf-m-end'); }); + + // We're just testing the positive action here to ensure logic passes through as needed, the other actions are + // tested in ResponseActions.test.tsx along with other aspects of this functionality + it('should not swap icons when useFilledIconsOnClick is omitted', async () => { + const user = userEvent.setup(); + + render( + + ); + + expect(screen.getByText('OutlinedThumbsUpIcon')).toBeInTheDocument(); + + await user.click(screen.getByRole('button', { name: /Good response/i })); + + expect(screen.getByText('OutlinedThumbsUpIcon')).toBeInTheDocument(); + expect(screen.queryByText('ThumbsUpIcon')).not.toBeInTheDocument(); + }); + + it('should swap icons when useFilledIconsOnClick is true', async () => { + const user = userEvent.setup(); + + render( + + ); + + await user.click(screen.getByRole('button', { name: /Good response/i })); + + expect(screen.getByText('ThumbsUpIcon')).toBeInTheDocument(); + expect(screen.queryByText('OutlinedThumbsUpIcon')).not.toBeInTheDocument(); + }); }); diff --git a/packages/module/src/Message/Message.tsx b/packages/module/src/Message/Message.tsx index 47bc69533..918a6499b 100644 --- a/packages/module/src/Message/Message.tsx +++ b/packages/module/src/Message/Message.tsx @@ -197,6 +197,8 @@ export interface MessageProps extends Omit, 'role'> { hasNoImagesInUserMessages?: boolean; /** Sets background colors to be appropriate on primary chatbot background */ isPrimary?: boolean; + /** When true, automatically swaps to filled icon variants when predefined actions are clicked. */ + useFilledIconsOnClick?: boolean; } export const MessageBase: FunctionComponent = ({ @@ -249,6 +251,7 @@ export const MessageBase: FunctionComponent = ({ toolCall, hasNoImagesInUserMessages = true, isPrimary, + useFilledIconsOnClick, ...props }: MessageProps) => { const [messageText, setMessageText] = useState(content); @@ -385,11 +388,16 @@ export const MessageBase: FunctionComponent = ({ key={index} actions={actionGroup.actions || actionGroup} persistActionSelection={persistActionSelection || actionGroup.persistActionSelection} + useFilledIconsOnClick={useFilledIconsOnClick} /> ))} ) : ( - + )} )} diff --git a/packages/module/src/ResponseActions/ResponseActions.test.tsx b/packages/module/src/ResponseActions/ResponseActions.test.tsx index 43f62ff04..f51e3f25d 100644 --- a/packages/module/src/ResponseActions/ResponseActions.test.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.test.tsx @@ -5,6 +5,22 @@ import userEvent from '@testing-library/user-event'; import { DownloadIcon, InfoCircleIcon, RedoIcon } from '@patternfly/react-icons'; import Message from '../Message'; +// Mock the icon components +jest.mock('@patternfly/react-icons', () => ({ + OutlinedThumbsUpIcon: () =>
OutlinedThumbsUpIcon
, + ThumbsUpIcon: () =>
ThumbsUpIcon
, + OutlinedThumbsDownIcon: () =>
OutlinedThumbsDownIcon
, + ThumbsDownIcon: () =>
ThumbsDownIcon
, + OutlinedCopyIcon: () =>
OutlinedCopyIcon
, + CopyIcon: () =>
CopyIcon
, + DownloadIcon: () =>
DownloadIcon
, + InfoCircleIcon: () =>
InfoCircleIcon
, + RedoIcon: () =>
RedoIcon
, + ExternalLinkAltIcon: () =>
ExternalLinkAltIcon
, + VolumeUpIcon: () =>
VolumeUpIcon
, + PencilAltIcon: () =>
PencilAltIcon
+})); + const ALL_ACTIONS = [ { type: 'positive', label: 'Good response', clickedLabel: 'Good response recorded' }, { type: 'negative', label: 'Bad response', clickedLabel: 'Bad response recorded' }, @@ -421,4 +437,275 @@ describe('ResponseActions', () => { await userEvent.click(customBtn); expect(customBtn).not.toHaveClass('pf-chatbot__button--response-action-clicked'); }); + + describe('icon swapping with useFilledIconsOnClick', () => { + it('should render outline icons by default', () => { + render( + + ); + + expect(screen.getByText('OutlinedThumbsUpIcon')).toBeInTheDocument(); + expect(screen.getByText('OutlinedThumbsDownIcon')).toBeInTheDocument(); + expect(screen.getByText('OutlinedCopyIcon')).toBeInTheDocument(); + + expect(screen.queryByText('ThumbsUpIcon')).not.toBeInTheDocument(); + expect(screen.queryByText('ThumbsDownIcon')).not.toBeInTheDocument(); + expect(screen.queryByText('CopyIcon')).not.toBeInTheDocument(); + }); + + describe('positive actions', () => { + it('should not swap positive icon when clicked and useFilledIconsOnClick is false', async () => { + const user = userEvent.setup() + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Good response' })); + + expect(screen.getByText('OutlinedThumbsUpIcon')).toBeInTheDocument(); + expect(screen.queryByText('ThumbsUpIcon')).not.toBeInTheDocument(); + }); + + it('should swap positive icon from outline to filled when clicked with useFilledIconsOnClick', async () => { + const user = userEvent.setup() + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Good response' })); + + expect(screen.getByText('ThumbsUpIcon')).toBeInTheDocument(); + expect(screen.queryByText('OutlinedThumbsUpIcon')).not.toBeInTheDocument(); + }); + + it('should revert positive icon to outline icon when clicking outside', async () => { + const user = userEvent.setup() + + render( +
+ +
Outside
+
+ ); + + await user.click(screen.getByRole('button', { name: 'Good response' })); + expect(screen.getByText('ThumbsUpIcon')).toBeInTheDocument(); + + await user.click(screen.getByTestId('outside')); + expect(screen.getByText('OutlinedThumbsUpIcon')).toBeInTheDocument(); + }); + + it('should not revert positive icon to outline icon when clicking outside if persistActionSelection is true', async () => { + const user = userEvent.setup() + + render( +
+ +
Outside
+
+ ); + + await user.click(screen.getByRole('button', { name: 'Good response' })); + expect(screen.getByText('ThumbsUpIcon')).toBeInTheDocument(); + + await user.click(screen.getByTestId('outside')); + expect(screen.getByText('ThumbsUpIcon')).toBeInTheDocument(); + }); + + describe('negative actions', () => { + it('should not swap negative icon when clicked and useFilledIconsOnClick is false', async () => { + const user = userEvent.setup() + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Bad response' })); + + expect(screen.getByText('OutlinedThumbsDownIcon')).toBeInTheDocument(); + expect(screen.queryByText('ThumbsDownIcon')).not.toBeInTheDocument(); + }); + + it('should swap negative icon from outline to filled when clicked with useFilledIconsOnClick', async () => { + const user = userEvent.setup() + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Bad response' })); + + expect(screen.getByText('ThumbsDownIcon')).toBeInTheDocument(); + expect(screen.queryByText('OutlinedThumbsDownIcon')).not.toBeInTheDocument(); + }); + + it('should revert negative icon to outline when clicking outside', async () => { + const user = userEvent.setup() + + render( +
+ +
Outside
+
+ ); + + await user.click(screen.getByRole('button', { name: 'Bad response' })); + expect(screen.getByText('ThumbsDownIcon')).toBeInTheDocument(); + + await user.click(screen.getByTestId('outside')); + expect(screen.getByText('OutlinedThumbsDownIcon')).toBeInTheDocument(); + }); + + it('should not revert negative icon to outline icon when clicking outside if persistActionSelection is true', async () => { + const user = userEvent.setup() + + render( +
+ +
Outside
+
+ ); + + await user.click(screen.getByRole('button', { name: 'Bad response' })); + expect(screen.getByText('ThumbsDownIcon')).toBeInTheDocument(); + + await user.click(screen.getByTestId('outside')); + expect(screen.getByText('ThumbsDownIcon')).toBeInTheDocument(); + }); + }); + + describe('copy actions', () => { + it('should not swap copy icon when clicked and useFilledIconsOnClick is false', async () => { + const user = userEvent.setup() + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Copy' })); + + expect(screen.getByText('OutlinedCopyIcon')).toBeInTheDocument(); + expect(screen.queryByText('CopyIcon')).not.toBeInTheDocument(); + }); + + it('should swap copy icon from outline to filled when clicked with useFilledIconsOnClick', async () => { + const user = userEvent.setup() + + render( + + ); + + await user.click(screen.getByRole('button', { name: 'Copy' })); + + expect(screen.getByText('CopyIcon')).toBeInTheDocument(); + expect(screen.queryByText('OutlinedCopyIcon')).not.toBeInTheDocument(); + }); + + it('should revert copy icon to outline when clicking outside', async () => { + const user = userEvent.setup() + + render( +
+ +
Outside
+
+ ); + + await user.click(screen.getByRole('button', { name: 'Copy' })); + expect(screen.getByText('CopyIcon')).toBeInTheDocument(); + + await user.click(screen.getByTestId('outside')); + expect(screen.getByText('OutlinedCopyIcon')).toBeInTheDocument(); + }); + + it('should not revert copy icon to outline icon when clicking outside if persistActionSelection is true', async () => { + const user = userEvent.setup() + + render( +
+ +
Outside
+
+ ); + + await user.click(screen.getByRole('button', { name: 'Copy' })); + expect(screen.getByText('CopyIcon')).toBeInTheDocument(); + + await user.click(screen.getByTestId('outside')); + expect(screen.getByText('CopyIcon')).toBeInTheDocument(); + }); + }); + }); + }); }); diff --git a/packages/module/src/ResponseActions/ResponseActions.tsx b/packages/module/src/ResponseActions/ResponseActions.tsx index c1cfbcff8..4f60a5197 100644 --- a/packages/module/src/ResponseActions/ResponseActions.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.tsx @@ -4,8 +4,11 @@ import { ExternalLinkAltIcon, VolumeUpIcon, OutlinedThumbsUpIcon, + ThumbsUpIcon, OutlinedThumbsDownIcon, + ThumbsDownIcon, OutlinedCopyIcon, + CopyIcon, DownloadIcon, PencilAltIcon } from '@patternfly/react-icons'; @@ -62,11 +65,15 @@ export interface ResponseActionProps { /** When true, the selected action will persist even when clicking outside the component. * When false (default), clicking outside or clicking another action will deselect the current selection. */ persistActionSelection?: boolean; + /** When true, automatically swaps to filled icon variants when predefined actions are clicked. + * Predefined actions will use filled variants (e.g., ThumbsUpIcon) when clicked and outline variants (e.g., OutlinedThumbsUpIcon) when not clicked. */ + useFilledIconsOnClick?: boolean; } export const ResponseActions: FunctionComponent = ({ actions, - persistActionSelection = false + persistActionSelection = false, + useFilledIconsOnClick = false }) => { const [activeButton, setActiveButton] = useState(); const [clickStatePersisted, setClickStatePersisted] = useState(false); @@ -145,6 +152,31 @@ export const ResponseActions: FunctionComponent = ({ onClick && onClick(e); }; + const iconMap = { + positive: { + filled: , + outlined: + }, + negative: { + filled: , + outlined: + }, + copy: { + filled: , + outlined: + } + }; + + const getIcon = (actionName: string) => { + const isClicked = activeButton === actionName; + + if (isClicked && useFilledIconsOnClick) { + return iconMap[actionName].filled; + } + + return iconMap[actionName].outlined; + }; + return (
{positive && ( @@ -158,7 +190,7 @@ export const ResponseActions: FunctionComponent = ({ tooltipContent={positive.tooltipContent ?? 'Good response'} clickedTooltipContent={positive.clickedTooltipContent ?? 'Good response recorded'} tooltipProps={positive.tooltipProps} - icon={} + icon={getIcon('positive')} isClicked={activeButton === 'positive'} ref={positive.ref} aria-expanded={positive['aria-expanded']} @@ -176,7 +208,7 @@ export const ResponseActions: FunctionComponent = ({ tooltipContent={negative.tooltipContent ?? 'Bad response'} clickedTooltipContent={negative.clickedTooltipContent ?? 'Bad response recorded'} tooltipProps={negative.tooltipProps} - icon={} + icon={getIcon('negative')} isClicked={activeButton === 'negative'} ref={negative.ref} aria-expanded={negative['aria-expanded']} @@ -194,7 +226,7 @@ export const ResponseActions: FunctionComponent = ({ tooltipContent={copy.tooltipContent ?? 'Copy'} clickedTooltipContent={copy.clickedTooltipContent ?? 'Copied'} tooltipProps={copy.tooltipProps} - icon={} + icon={getIcon('copy')} isClicked={activeButton === 'copy'} ref={copy.ref} aria-expanded={copy['aria-expanded']} From fc2278b02d85d097d253e80bea4c11c2e0a0fa33 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Mon, 16 Feb 2026 17:25:58 -0500 Subject: [PATCH 2/8] chore(ResponseActions): lint --- .../ResponseActions/ResponseActions.test.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/module/src/ResponseActions/ResponseActions.test.tsx b/packages/module/src/ResponseActions/ResponseActions.test.tsx index f51e3f25d..72ba0b51e 100644 --- a/packages/module/src/ResponseActions/ResponseActions.test.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.test.tsx @@ -461,7 +461,7 @@ describe('ResponseActions', () => { describe('positive actions', () => { it('should not swap positive icon when clicked and useFilledIconsOnClick is false', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render( { }); it('should swap positive icon from outline to filled when clicked with useFilledIconsOnClick', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render( { }); it('should revert positive icon to outline icon when clicking outside', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render(
@@ -519,7 +519,7 @@ describe('ResponseActions', () => { }); it('should not revert positive icon to outline icon when clicking outside if persistActionSelection is true', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render(
@@ -543,7 +543,7 @@ describe('ResponseActions', () => { describe('negative actions', () => { it('should not swap negative icon when clicked and useFilledIconsOnClick is false', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render( { }); it('should swap negative icon from outline to filled when clicked with useFilledIconsOnClick', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render( { }); it('should revert negative icon to outline when clicking outside', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render(
@@ -601,7 +601,7 @@ describe('ResponseActions', () => { }); it('should not revert negative icon to outline icon when clicking outside if persistActionSelection is true', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render(
@@ -626,7 +626,7 @@ describe('ResponseActions', () => { describe('copy actions', () => { it('should not swap copy icon when clicked and useFilledIconsOnClick is false', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render( { }); it('should swap copy icon from outline to filled when clicked with useFilledIconsOnClick', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render( { }); it('should revert copy icon to outline when clicking outside', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render(
@@ -684,7 +684,7 @@ describe('ResponseActions', () => { }); it('should not revert copy icon to outline icon when clicking outside if persistActionSelection is true', async () => { - const user = userEvent.setup() + const user = userEvent.setup(); render(
From 4739445d5ebe9b0235f0569d9cb3ca5b3b69ab8a Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 17 Feb 2026 14:21:59 -0500 Subject: [PATCH 3/8] fix(ResponseActions): prevent incorrect click outside detection --- packages/module/src/ResponseActions/ResponseActions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/module/src/ResponseActions/ResponseActions.tsx b/packages/module/src/ResponseActions/ResponseActions.tsx index 4f60a5197..ef953452a 100644 --- a/packages/module/src/ResponseActions/ResponseActions.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.tsx @@ -136,6 +136,7 @@ export const ResponseActions: FunctionComponent = ({ id: string, onClick?: (event: MouseEvent | MouseEvent | KeyboardEvent) => void ) => { + e.stopPropagation(); if (persistActionSelection) { if (activeButton === id) { // Toggle off if clicking the same button From 4215d05a7cc3fd73ad9b569cc02fe18ce7bd8ba5 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 17 Feb 2026 15:10:28 -0500 Subject: [PATCH 4/8] feat(ResponseActions): remove copy action from icon swapping --- .../chatbot/examples/Messages/Messages.md | 2 +- packages/module/src/Message/Message.test.tsx | 1 - .../ResponseActions/ResponseActions.test.tsx | 88 +------------------ .../src/ResponseActions/ResponseActions.tsx | 7 +- 4 files changed, 3 insertions(+), 95 deletions(-) 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 e38ff3daf..65246946b 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 @@ -140,7 +140,7 @@ When `persistActionSelection` is `true`: ### Message actions with icon swapping -To provide enhanced visual feedback when users interact with response actions, you can enable icon swapping by setting `useFilledIconsOnClick` to `true`. When enabled, the predefined actions positive, negative, and copy will automatically swap from outline icon variants to filled icon variants when clicked. +To provide enhanced visual feedback when users interact with response actions, you can enable icon swapping by setting `useFilledIconsOnClick` to `true`. When enabled, the predefined actions positive and negative will automatically swap from outline icon variants to filled icon variants when clicked. ```js file="./MessageWithIconSwapping.tsx" diff --git a/packages/module/src/Message/Message.test.tsx b/packages/module/src/Message/Message.test.tsx index a054e87da..754910caa 100644 --- a/packages/module/src/Message/Message.test.tsx +++ b/packages/module/src/Message/Message.test.tsx @@ -16,7 +16,6 @@ jest.mock('@patternfly/react-icons', () => ({ OutlinedThumbsDownIcon: () =>
OutlinedThumbsDownIcon
, ThumbsDownIcon: () =>
ThumbsDownIcon
, OutlinedCopyIcon: () =>
OutlinedCopyIcon
, - CopyIcon: () =>
CopyIcon
, DownloadIcon: () =>
DownloadIcon
, ExternalLinkAltIcon: () =>
ExternalLinkAltIcon
, VolumeUpIcon: () =>
VolumeUpIcon
, diff --git a/packages/module/src/ResponseActions/ResponseActions.test.tsx b/packages/module/src/ResponseActions/ResponseActions.test.tsx index 72ba0b51e..1784bef4a 100644 --- a/packages/module/src/ResponseActions/ResponseActions.test.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.test.tsx @@ -12,7 +12,6 @@ jest.mock('@patternfly/react-icons', () => ({ OutlinedThumbsDownIcon: () =>
OutlinedThumbsDownIcon
, ThumbsDownIcon: () =>
ThumbsDownIcon
, OutlinedCopyIcon: () =>
OutlinedCopyIcon
, - CopyIcon: () =>
CopyIcon
, DownloadIcon: () =>
DownloadIcon
, InfoCircleIcon: () =>
InfoCircleIcon
, RedoIcon: () =>
RedoIcon
, @@ -444,19 +443,16 @@ describe('ResponseActions', () => { ); expect(screen.getByText('OutlinedThumbsUpIcon')).toBeInTheDocument(); expect(screen.getByText('OutlinedThumbsDownIcon')).toBeInTheDocument(); - expect(screen.getByText('OutlinedCopyIcon')).toBeInTheDocument(); expect(screen.queryByText('ThumbsUpIcon')).not.toBeInTheDocument(); expect(screen.queryByText('ThumbsDownIcon')).not.toBeInTheDocument(); - expect(screen.queryByText('CopyIcon')).not.toBeInTheDocument(); }); describe('positive actions', () => { @@ -624,88 +620,6 @@ describe('ResponseActions', () => { }); }); - describe('copy actions', () => { - it('should not swap copy icon when clicked and useFilledIconsOnClick is false', async () => { - const user = userEvent.setup(); - - render( - - ); - - await user.click(screen.getByRole('button', { name: 'Copy' })); - - expect(screen.getByText('OutlinedCopyIcon')).toBeInTheDocument(); - expect(screen.queryByText('CopyIcon')).not.toBeInTheDocument(); - }); - - it('should swap copy icon from outline to filled when clicked with useFilledIconsOnClick', async () => { - const user = userEvent.setup(); - - render( - - ); - - await user.click(screen.getByRole('button', { name: 'Copy' })); - - expect(screen.getByText('CopyIcon')).toBeInTheDocument(); - expect(screen.queryByText('OutlinedCopyIcon')).not.toBeInTheDocument(); - }); - - it('should revert copy icon to outline when clicking outside', async () => { - const user = userEvent.setup(); - - render( -
- -
Outside
-
- ); - - await user.click(screen.getByRole('button', { name: 'Copy' })); - expect(screen.getByText('CopyIcon')).toBeInTheDocument(); - - await user.click(screen.getByTestId('outside')); - expect(screen.getByText('OutlinedCopyIcon')).toBeInTheDocument(); - }); - - it('should not revert copy icon to outline icon when clicking outside if persistActionSelection is true', async () => { - const user = userEvent.setup(); - - render( -
- -
Outside
-
- ); - - await user.click(screen.getByRole('button', { name: 'Copy' })); - expect(screen.getByText('CopyIcon')).toBeInTheDocument(); - - await user.click(screen.getByTestId('outside')); - expect(screen.getByText('CopyIcon')).toBeInTheDocument(); - }); - }); }); }); }); diff --git a/packages/module/src/ResponseActions/ResponseActions.tsx b/packages/module/src/ResponseActions/ResponseActions.tsx index ef953452a..630b3ad2f 100644 --- a/packages/module/src/ResponseActions/ResponseActions.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.tsx @@ -8,7 +8,6 @@ import { OutlinedThumbsDownIcon, ThumbsDownIcon, OutlinedCopyIcon, - CopyIcon, DownloadIcon, PencilAltIcon } from '@patternfly/react-icons'; @@ -161,10 +160,6 @@ export const ResponseActions: FunctionComponent = ({ negative: { filled: , outlined: - }, - copy: { - filled: , - outlined: } }; @@ -227,7 +222,7 @@ export const ResponseActions: FunctionComponent = ({ tooltipContent={copy.tooltipContent ?? 'Copy'} clickedTooltipContent={copy.clickedTooltipContent ?? 'Copied'} tooltipProps={copy.tooltipProps} - icon={getIcon('copy')} + icon={} isClicked={activeButton === 'copy'} ref={copy.ref} aria-expanded={copy['aria-expanded']} From 4d9892f15791446c88a4c16d212a05d9cf4072b5 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 17 Feb 2026 15:36:21 -0500 Subject: [PATCH 5/8] chore(ResponseActions): lint --- packages/module/src/ResponseActions/ResponseActions.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/module/src/ResponseActions/ResponseActions.test.tsx b/packages/module/src/ResponseActions/ResponseActions.test.tsx index 1784bef4a..c3fba1265 100644 --- a/packages/module/src/ResponseActions/ResponseActions.test.tsx +++ b/packages/module/src/ResponseActions/ResponseActions.test.tsx @@ -619,7 +619,6 @@ describe('ResponseActions', () => { expect(screen.getByText('ThumbsDownIcon')).toBeInTheDocument(); }); }); - }); }); }); From b1581bb0908043ef6595599e7d0c07124ed38ed8 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 17 Feb 2026 16:24:15 -0500 Subject: [PATCH 6/8] Update packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md Co-authored-by: Erin Donehoo <105813956+edonehoo@users.noreply.github.com> --- .../content/extensions/chatbot/examples/Messages/Messages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 65246946b..523e6e490 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 @@ -138,7 +138,7 @@ When `persistActionSelection` is `true`: ``` -### Message actions with icon swapping +### Message actions that fill To provide enhanced visual feedback when users interact with response actions, you can enable icon swapping by setting `useFilledIconsOnClick` to `true`. When enabled, the predefined actions positive and negative will automatically swap from outline icon variants to filled icon variants when clicked. From f050157aea5fac90024acdd7bdb3c1adeb17f143 Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 17 Feb 2026 16:24:37 -0500 Subject: [PATCH 7/8] Update packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md Co-authored-by: Erin Donehoo <105813956+edonehoo@users.noreply.github.com> --- .../content/extensions/chatbot/examples/Messages/Messages.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 523e6e490..faf94aab3 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 @@ -140,7 +140,9 @@ When `persistActionSelection` is `true`: ### Message actions that fill -To provide enhanced visual feedback when users interact with response actions, you can enable icon swapping by setting `useFilledIconsOnClick` to `true`. When enabled, the predefined actions positive and negative will automatically swap from outline icon variants to filled icon variants when clicked. +To provide enhanced visual feedback when users interact with response actions, you can enable icon swapping by setting `useFilledIconsOnClick` to `true`. When enabled, the predefined "positive" and "negative" actions will automatically swap to their filled icon counterparts when clicked, replacing the original outlined icon variants. + +This is especially useful for actions that are intended to persist (such as the "positive" and "negative" responses), so that a user's selection is more clear and emphasized. ```js file="./MessageWithIconSwapping.tsx" From 9d0f420221c4f5a278dec06da1d58c36a58191de Mon Sep 17 00:00:00 2001 From: Austin Sullivan Date: Tue, 17 Feb 2026 16:25:08 -0500 Subject: [PATCH 8/8] Update packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx Co-authored-by: Erin Donehoo <105813956+edonehoo@users.noreply.github.com> --- .../chatbot/examples/Messages/MessageWithIconSwapping.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx index dc57af6bf..7d9cb9b17 100644 --- a/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx +++ b/packages/module/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithIconSwapping.tsx @@ -8,7 +8,7 @@ export const IconSwappingExample: FunctionComponent = () => ( name="Bot" role="bot" avatar={patternflyAvatar} - content="Click the response actions to see the icons change from outline to filled variants!" + content="Click the response actions to see the outlined icons swapped with the filled variants!" actions={{ // eslint-disable-next-line no-console positive: { onClick: () => console.log('Good response') },