Skip to content

Commit

Permalink
Update docs per feedback
Browse files Browse the repository at this point in the history
Co-authored-by: Erin Donehoo <[email protected]>
  • Loading branch information
rebeccaalpert and edonehoo committed Jan 21, 2025
1 parent e9bdc3e commit ca123fb
Show file tree
Hide file tree
Showing 8 changed files with 46 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import patternflyAvatar from './patternfly_avatar.jpg';
import { Checkbox, FormGroup, Stack } from '@patternfly/react-core';

export const MessageWithFeedbackExample: React.FunctionComponent = () => {
const [hasCloseButton, setHasCloseButton] = React.useState(true);
const [hasCloseButton, setHasCloseButton] = React.useState(false);
const [hasTextArea, setHasTextArea] = React.useState(false);

return (
Expand All @@ -25,12 +25,12 @@ export const MessageWithFeedbackExample: React.FunctionComponent = () => {
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="Bot message with feedback form only"
content="This is a message with the feedback card:"
userFeedbackForm={{
quickResponses: [
{ id: '1', content: 'Correct' },
{ id: '1', content: 'Helpful information' },
{ id: '2', content: 'Easy to understand' },
{ id: '3', content: 'Complete' }
{ id: '3', content: 'Resolved my issue' }
],
onSubmit: (quickResponse, additionalFeedback) =>
alert(`Selected ${quickResponse} and received the additional feedback: ${additionalFeedback}`),
Expand All @@ -57,7 +57,7 @@ export const MessageWithFeedbackExample: React.FunctionComponent = () => {
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="Bot message with completion message"
content="This is a thank-you message, which is displayed once the feedback card is submitted:"
// eslint-disable-next-line no-console
userFeedbackComplete={{
// eslint-disable-next-line no-console
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const MessageWithFeedbackTimeoutExample: React.FunctionComponent = () =>
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="Bot message with completion message that times out"
userFeedbackComplete={hasFeedback ? { timeout: true } : undefined}
content="This completion message times out after you click **Show card**:"
userFeedbackComplete={hasFeedback ? { timeout: true, onTimeout: () => setHasFeedback(false) } : undefined}
isLiveRegion
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,24 @@ You can apply a `clickedAriaLabel` and `clickedTooltipContent` once a button is

When a user selects a positive or negative [message action](#message-actions), you can display a message feedback card that acknowledges their response and provides space for additional written feedback. These cards can be manually dismissed via the close button and the thank-you card can be [configured to time out automatically](/patternfly-ai/chatbot/messages#message-feedback-with-timeouts).

You can see a demo of the full feedback flow [in the demos section](/patternfly-ai/chatbot/messages/demo#message-feedback).
You can see the full feedback flow [in the message demos](/patternfly-ai/chatbot/messages/demo#message-feedback).

The message feedback cards will immediately receive focus by default, but you can remove this behavior by passing `focusOnLoad: false` to the `<Message>` (as shown in the following examples). For better usability, you should generally keep the default focus behavior.

The following examples demonstrate:

- A basic card.
- A basic card without text input.
- Thank-you cards, with and without a close button.
- A basic feedback card. To toggle the text input area, select the **Has text area** checkbox.
- A thank-you card. To toggle the close button, select the **Has close button** checkbox.

```js file="./MessageWithFeedback.tsx"

```

### Message feedback with timeouts

The feedback thank you message can be configured to time out and automatically close after a period of time. The default time out period is 8000 ms, but it can be customized via `timeout`.
The feedback thank-you message can be configured to time out and automatically close after a period of time. The default time-out period is 8000 ms, but it can be customized via `timeout`.

To display the thank-you message in this example, click **Show card**.

The card will not dismiss within the default time if a user is hovering over it or if it has keyboard focus. Instead, it will dismiss after they remove focus, via `timeoutAnimation`, which is 3000 ms by default. You can adjust this duration and set an `onTimeout` callback, as well as optional `onMouseEnter` and `onMouseLeave` callbacks.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ When a user selects a positive or negative message action, you can display a mes

The following example demonstrates a full feedback flow, which accepts written feedback submission and displays a thank you card.

It also demonstrates how to handle focus appropriately for accessibility. The card will be focused when it appears in the DOM. When the card closes, place the focus back on the launching button. You can also add `aria-expanded` and `aria-controls` attributes to the feedback buttons to provide additional context on what the button controls.
It also demonstrates how to handle focus appropriately for accessibility. The card will be focused when it appears in the DOM. When the card closes, place the focus back on the launching button. To provide additional context on what the button controls, you can also add `aria-expanded` and `aria-controls` attributes to the feedback buttons.

It is also important to announce when new content appears onscreen for accessibility purposes. `isLiveRegion` is set to true by default on `<Message>` so it will make appropriate announcements for you when the feedback card appears.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const MessageWithFeedbackExample: React.FunctionComponent = () => {
name="Bot"
role="bot"
avatar={patternflyAvatar}
content="Bot message with user feedback flow; click on a message action to launch the feedback flow. Click submit to see the thank you message."
content="Bot message with user feedback flow; click on a message action to launch the feedback flow. Click submit to see the thank-you message."
actions={{
positive: {
onClick: () => {
Expand Down Expand Up @@ -66,9 +66,9 @@ export const MessageWithFeedbackExample: React.FunctionComponent = () => {
? /* eslint-disable indent */
{
quickResponses: [
{ id: '1', content: 'Correct' },
{ id: '1', content: 'Helpful information' },
{ id: '2', content: 'Easy to understand' },
{ id: '3', content: 'Complete' }
{ id: '3', content: 'Resolved my issue' }
],
onSubmit: (quickResponse, additionalFeedback) => {
alert(`Selected ${quickResponse} and received the additional feedback: ${additionalFeedback}`);
Expand Down
16 changes: 8 additions & 8 deletions packages/module/src/Message/UserFeedback/UserFeedback.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import userEvent from '@testing-library/user-event';
import UserFeedback from './UserFeedback';

const MOCK_RESPONSES = [
{ id: '1', content: 'Correct', onClick: () => alert('Clicked correct') },
{ id: '1', content: 'Helpful information', onClick: () => alert('Clicked helpful information') },
{ id: '2', content: 'Easy to understand', onClick: () => alert('Clicked easy to understand') },
{ id: '3', content: 'Complete', onClick: () => alert('Clicked complete') }
{ id: '3', content: 'Resolved my issue', onClick: () => alert('Clicked resolved my issue') }
];

describe('UserFeedback', () => {
it('should render correctly', () => {
render(<UserFeedback onClose={jest.fn} onSubmit={jest.fn} quickResponses={MOCK_RESPONSES} timestamp="12/12/12" />);
expect(screen.getByRole('heading', { name: /Why did you choose this rating?/i })).toBeTruthy();
expect(screen.getByRole('list', { name: 'Quick feedback for message received at 12/12/12' })).toBeTruthy();
expect(screen.getByRole('button', { name: /Correct/i })).toBeTruthy();
expect(screen.getByRole('button', { name: /Helpful information/i })).toBeTruthy();
expect(screen.getByRole('button', { name: /Easy to understand/i })).toBeTruthy();
expect(screen.getByRole('button', { name: /Complete/i })).toBeTruthy();
expect(screen.getByRole('button', { name: /Resolved my issue/i })).toBeTruthy();
expect(screen.getByRole('button', { name: /Submit/i })).toBeTruthy();
expect(screen.getByRole('button', { name: 'Close feedback for message received at 12/12/12' })).toBeTruthy();
expect(screen.getByRole('button', { name: /Cancel/i })).toBeTruthy();
Expand Down Expand Up @@ -181,10 +181,10 @@ describe('UserFeedback', () => {
it('should handle submit correctly when item is selected', async () => {
const spy = jest.fn();
render(<UserFeedback timestamp="12/12/12" onClose={jest.fn} onSubmit={spy} quickResponses={MOCK_RESPONSES} />);
await userEvent.click(screen.getByRole('button', { name: /Complete/i }));
await userEvent.click(screen.getByRole('button', { name: /Easy to understand/i }));
await userEvent.click(screen.getByRole('button', { name: /Submit/i }));
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('3', '');
expect(spy).toHaveBeenCalledWith('2', '');
});
it('should handle submit correctly when there is just text input', async () => {
const spy = jest.fn();
Expand All @@ -204,14 +204,14 @@ describe('UserFeedback', () => {
render(
<UserFeedback timestamp="12/12/12" onClose={jest.fn} onSubmit={spy} quickResponses={MOCK_RESPONSES} hasTextArea />
);
await userEvent.click(screen.getByRole('button', { name: /Complete/i }));
await userEvent.click(screen.getByRole('button', { name: /Easy to understand/i }));
await userEvent.type(
screen.getByRole('textbox', { name: /Provide optional additional feedback/i }),
'What a great experience!'
);
await userEvent.click(screen.getByRole('button', { name: /Submit/i }));
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith('3', 'What a great experience!');
expect(spy).toHaveBeenCalledWith('2', 'What a great experience!');
});
it('should default title heading level to h1', () => {
render(<UserFeedback timestamp="12/12/12" onClose={jest.fn} onSubmit={jest.fn} quickResponses={MOCK_RESPONSES} />);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,24 @@ import UserFeedbackComplete from './UserFeedbackComplete';
describe('UserFeedbackComplete', () => {
it('should render correctly', () => {
render(<UserFeedbackComplete timestamp="12/12/12" />);
expect(screen.getByText('Thank you')).toBeTruthy();
screen.getByText(/You have successfully sent your feedback!/i);
screen.getByText(/Thank you for responding./i);
expect(screen.getByText('Feedback submitted')).toBeTruthy();
screen.getByText(/We've received your response. Thank you for sharing your feedback!/i);
expect(screen.queryByRole('button', { name: /Close/i })).toBeFalsy();
});
it('should render different title correctly', () => {
render(<UserFeedbackComplete timestamp="12/12/12" title="Thanks!" />);
expect(screen.getByText('Thanks!')).toBeTruthy();
screen.getByText(/You have successfully sent your feedback!/i);
screen.getByText(/Thank you for responding./i);
screen.getByText(/We've received your response. Thank you for sharing your feedback!/i);
});
it('should render different string body correctly', () => {
render(<UserFeedbackComplete timestamp="12/12/12" body="Feedback received!" />);
expect(screen.getByText('Thank you')).toBeTruthy();
screen.getByText(/Feedback received!/i);
render(<UserFeedbackComplete timestamp="12/12/12" body="Thanks!" />);
expect(screen.getByText('Feedback submitted')).toBeTruthy();
expect(screen.getByText('Thanks!')).toBeTruthy();
});
it('should render different node body correctly', () => {
render(<UserFeedbackComplete timestamp="12/12/12" body={<div>Feedback received!</div>} />);
expect(screen.getByText('Thank you')).toBeTruthy();
screen.getByText(/Feedback received!/i);
render(<UserFeedbackComplete timestamp="12/12/12" body={<div>Thanks!</div>} />);
expect(screen.getByText('Feedback submitted')).toBeTruthy();
expect(screen.getByText('Thanks!')).toBeTruthy();
});
it('should handle onClose correctly', async () => {
const spy = jest.fn();
Expand Down Expand Up @@ -55,7 +53,7 @@ describe('UserFeedbackComplete', () => {
act(() => {
jest.advanceTimersByTime(8000);
});
expect(screen.getByText('Thank you')).toBeVisible();
expect(screen.getByText('Feedback submitted')).toBeVisible();
jest.useRealTimers();
});
it('should handle timeout correctly after 8000ms when timeout = true', async () => {
Expand All @@ -64,11 +62,11 @@ describe('UserFeedbackComplete', () => {
act(() => {
jest.advanceTimersByTime(7999);
});
expect(screen.getByText('Thank you')).toBeVisible();
expect(screen.getByText('Feedback submitted')).toBeVisible();
act(() => {
jest.advanceTimersByTime(1);
});
expect(screen.queryByText('Thank you')).not.toBeInTheDocument();
expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
jest.useRealTimers();
});
it('should handle timeout correctly when timeout = numeric value', async () => {
Expand All @@ -77,11 +75,11 @@ describe('UserFeedbackComplete', () => {
act(() => {
jest.advanceTimersByTime(299);
});
expect(screen.getByText('Thank you')).toBeVisible();
expect(screen.getByText('Feedback submitted')).toBeVisible();
act(() => {
jest.advanceTimersByTime(1);
});
expect(screen.queryByText('Thank you')).not.toBeInTheDocument();
expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
jest.useRealTimers();
});
it('does not get removed on timeout if the user is focused on the card', async () => {
Expand All @@ -90,12 +88,12 @@ describe('UserFeedbackComplete', () => {
});
jest.useFakeTimers();
render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" />);
expect(screen.getByText('Thank you')).toBeTruthy();
expect(screen.getByText('Feedback submitted')).toBeTruthy();
await user.click(screen.getByTestId('card'));
act(() => {
jest.advanceTimersByTime(8000);
});
expect(screen.getByText('Thank you')).toBeTruthy();
expect(screen.getByText('Feedback submitted')).toBeTruthy();
jest.useRealTimers();
});
it('does not remove the card on timeout if the user is hovered over it', async () => {
Expand Down Expand Up @@ -132,7 +130,7 @@ describe('UserFeedbackComplete', () => {
act(() => {
jest.advanceTimersByTime(3000);
});
expect(screen.queryByText('Thank you')).not.toBeInTheDocument();
expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
jest.useRealTimers();
});

Expand All @@ -156,7 +154,7 @@ describe('UserFeedbackComplete', () => {
act(() => {
jest.advanceTimersByTime(3000);
});
expect(screen.queryByText('Thank you')).not.toBeInTheDocument();
expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
jest.useRealTimers();
});

Expand All @@ -180,7 +178,7 @@ describe('UserFeedbackComplete', () => {
act(() => {
jest.advanceTimersByTime(1000);
});
expect(screen.queryByText('Thank you')).not.toBeInTheDocument();
expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
jest.useRealTimers();
});
it('does not call the onTimeout callback before the timeout period has expired', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,8 @@ export interface UserFeedbackCompleteProps extends Omit<CardProps, 'ref'>, OUIAP

const UserFeedbackComplete: React.FunctionComponent<UserFeedbackCompleteProps> = ({
className,
title = 'Thank you',
body = `You have successfully sent your feedback!
Thank you for responding.`,
title = 'Feedback submitted',
body = "We've received your response. Thank you for sharing your feedback!",
timestamp,
timeout = false,
timeoutAnimation = 3000,
Expand Down

0 comments on commit ca123fb

Please sign in to comment.